Reputation: 85
I'm looking to call functions like this:
select().orderBy()
where firstly select()
is called and after orderBy()
is called.
To achieve it I have definig a method into the select method like this :
def select(self):
if self.key is not None:
self.request = """SELECT """ + self.key[0] + """ FROM """ + self.table
else:
self.request = """SELECT * FROM """ + self.table
def where():
self.request += """ WHERE """ + self.key[0] + """ = """ + self.value[0]
return self.request
def orderBy(key, value):
self.request += """ ORDER BY """ + key + """ """ + value
return self.request
def groupBy(arg):
self.request += """ GROUP BY """ + arg
return self.request
def limit(arg):
self.request += """ LIMIT """ + arg
return self.request
But I have this error message :
AttributeError: 'NoneType' object has no attribute 'orderBy'
Does someone know how to do ?
Upvotes: 0
Views: 164
Reputation: 1123420
Chaining methods (a fluent or generative API) only works if the preceding call returns an object that supports the next method; in foo().bar()
, foo()
must return an object that has a .bar()
method. Your .select()
method returns None
, an object that won't support any of the other methods.
Your select
function should return self
:
return self
for the call to be chainable. You probably want the other calls to return self
too, and not self.request
. I'm assuming here that the other methods are indeed methods on the same class (your example is missing self
parameters for each of them).
If you were trying to have those methods available only because you nested them inside the select()
method, then that won't work at all. Those function objects are just un-used local names in the function body, they are not available on the returned object.
If those methods should not exist on the same class that defines select
, define a new class and have that class implement your extra methods. Have select()
return an instance of that class:
class Selectable:
# ...
def select(self):
column = '*' if new_clone.key is None else new_clone.key
request = """SELECT {} FROM {}""".format(column, new_clone.table)
return Query(request)
class Query:
def __init__(self, request):
self.request = request
def where(self):
self.request += """ WHERE """ + self.key[0] + """ = """ + self.value[0]
return self
def orderBy(self, key, value):
self.request += """ ORDER BY """ + key + """ """ + value
return self
def groupBy(self, arg):
self.request += """ GROUP BY """ + arg
return self
def limit(self, arg):
self.request += """ LIMIT """ + arg
return self
Note that each of the methods on Query()
return self
so you can continue to chain.
However, you are then manipulating the same instance, and won't be able to create separate query objects from a common base. SQLAlchemy does this better; it returns a clone of self
, with the added changes:
def _clone(self):
return type(self)(self.request)
def where(self):
new_clone = self._clone()
new_clone.request += """ WHERE {} = {}""".format(self.key[0], self.value[0])
return new_clone
Now base = Selectable().select()
and filtered = base.where()
makes filtered
a distinct clone, so you can continue to use base
to build other queries.
(In reality, SQLAlchemy uses a decorator that replaces self
with a newly created clone).
Upvotes: 5