String construction using OOP and Proxy pattern

438 Views Asked by At

I find it very interesting the way how SQLAlchemy constructing query strings, eg:

(Session.query(model.User)
        .filter(model.User.age > 18)
        .order_by(model.User.age)
        .all())

As far as I can see, there applied some kind of Proxy Pattern. In my small project I need to make similar string construction using OOP approach. So, I tried to reconstitute this behavior.

Firstly, some kind of object, one of plenty similar objects:

class SomeObject(object):
    items = None

    def __init__(self):
        self.items = []

    def __call__(self):
        return ' '.join(self.items) if self.items is not None else ''

    def a(self):
        self.items.append('a')
        return self

    def b(self):
        self.items.append('b')
        return self

All methods of this object return self, so I can call them in any order and unlimited number of times.

Secondly, proxy object, that will call subject's methods if it's not a perform method, which calls object to see the resulting string.

import operator

class Proxy(object):
    def __init__(self, some_object):
        self.some_object = some_object

    def __getattr__(self, name):
        self.method = operator.methodcaller(name)
        return self

    def __call__(self, *args, **kw):
        self.some_object = self.method(self.some_object, *args, **kw)
        return self

    def perform(self):
        return self.some_object()

And finally:

>>> obj = SomeObject()
>>> p = Proxy(obj)
>>> print p.a().a().b().perform()
a a b

What can you say about this implementation? Is there better ways to make the desirable amount of classes that would make such a string cunstructing with the same syntax?

PS: Sorry for my english, it's not my primary language.

2

There are 2 best solutions below

0
On

I don't know what SQLAlchemy does, but I would implement the interface by having the Session.query() method return a Query object with methods like filter(), order_by(), all() etc. Each of these methods simply returns a new Query object taking into account the applied changes. This allows for method chaining as in your first example.

Your own code example has numerous problems. One example

obj = SomeObject()
p = Proxy(obj)
a = p.a
b = p.b
print a().perform()  # prints b
3
On

Actually what you are looking at is not a proxy pattern but the builder pattern, and yes your implementation is IMHO is the classic one (using the Fluent interface pattern).