Jaky Chane
Jaky Chane

Reputation: 85

how to proceed function().function()?

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

Answers (1)

Martijn Pieters
Martijn Pieters

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

Related Questions