5

I'm trying to implement something like Rails dynamic-finders in Python (for webapp/GAE). The dynamic finders work like this:

  • Your Person has some fields: name, age and email.
  • Suppose you want to find all the users whose name is "Robot".

The Person class has a method called "find_by_name" that receives the name and returns the result of the query:

 @classmethod
 def find_by_name(cls, name):
    return Person.gql("WHERE name = :1", name).get()

Instead of having to write a method like that for each attribute, I'd like to have something like Ruby's method_missing that allows me to do it.

So far I've seen these 2 blog posts: http://blog.iffy.us/?p=43 and http://www.whatspop.com/blog/2008/08/method-missing-in-python.cfm but I'd like to hear what's the "most appropiate" way of doing it.

Federico Builes
  • 4,939
  • 4
  • 34
  • 48

4 Answers4

8

There's really no need to use GQL here - it just complicates matters. Here's a simple implementation:

class FindableModel(db.Model):
  def __getattr__(self, name):
    if not name.startswith("find_by_"):
      raise AttributeError(name)
    field = name[len("find_by_"):]
    return lambda value: self.all().filter(field, value)

Note that it returns a Query object, which you can call .get(), .fetch() etc on; this is more versatile, but if you want you can of course make it just return a single entity.

Nick Johnson
  • 100,655
  • 16
  • 128
  • 198
  • I tried to use this code but I still get: `AttributeError("type object 'FindableModel' has no attribute 'find_by_name'",)`. Can you explain a bit more how to use this? – hakunin Mar 10 '12 at 13:11
  • @hakunin How are you using it? You should be making this the parent class of your model. – Nick Johnson Mar 10 '12 at 20:04
  • Here is the model I use: https://gist.github.com/2014164 I would love to use `__getattr__` but it just doesn't get called for some reason. – hakunin Mar 11 '12 at 00:10
  • @hakunin Your model is extending `db.Model`. It needs to extend `FindableModel`. – Nick Johnson Mar 12 '12 at 06:57
1

You could use a 'find_by' method and keywords, like Django does:

class Person (object):
    def find_by(self, *kw):
        qspec = ' AND '.join('%s=%s' % kv for kv in kw.items())
        return self.gql('WHERE ' + qspec)

person.find_by(name='Robot')
person.find_by(name='Robot', age='2')

Down this road you may end up designing your own query syntax. Take a look at what Django does...

Ber
  • 40,356
  • 16
  • 72
  • 88
0
class Person:

    name = ""
    age = 0
    salary = 0

    def __init__(self, name, age):
        self.name = name
        self.age = age

def find(clsobj, *args): return Person(name="Jack", age=20)

The for loop below inserts @classmethod for all class attributes. This makes "find_by_name", "find_by_age" and "sinf_by_salary" bound methods available.

for attr in Person.__dict__.keys():
    setattr(Person, 'find_by_'+attr, find)
    setattr(Person, 'find_by_'+attr, classmethod(find))

print Person.find_by_name("jack").age # will print value 20.

I'm not sure if this the right way of doing things. But if you are able to implement a unified "find" for all attributes, then the above script works.

0
class Person(object):
    def __getattr__(self, name):
        if not name.startswith("find_by"): raise AttributeError(name)
        field_name = name.split("find_by_",1)[1]
        return lambda name: Person.gql("WHERE %s = :1" % field_name, name).get()
Unknown
  • 45,913
  • 27
  • 138
  • 182