Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining virtual fields in peewee

Suppose I have a peewee model which looks more or less as follows:

class MyModel(peewee.Model):
    a = peewee.IntegerField()
    b = peewee.IntegerField()

And I wish to add a property to that model as follows:

    @property
    def diff(self):
        return self.a - self.b

This is helpful sometimes; now, if Object is a MyModel instance, I can easily check its diff with Object.diff.

What I can't do is the following:

objects = list(MyModel.select().where(MyModel.diff < 17))

And this is since MyModel.diff is a simple property, and is probably always greater than 17. It is not an Expression like MyModel.a < 17.

It would be very nice to expose diff as if it was a field; so the user of that API will not need to know whether the specific implementation has a and b as real fields and diff as a virtual one, or rather a and diff as real fields and b as a virtual one.

Of course, my real intention is to use properties which involve, in some cases, much more sophisticated calculation that that presented on diff; an example is

@property
def complicated_property(self):
    if 17 <= self.a <= 173:
        return a_complicated_math_function(self.a + self.b)
    return another_complicated_math_function(self.a * self.b ** 2)

On the other hand, it can be a very simple property, such as

@property
def seven(self):
    return 7

This means it cannot, in general, be converted to SQL, but should rather filter the results after they are retrieved from the database.

Is that possible?

Update

I've just discovered peewee playhouse's hybrid methods/properties. These provide a partial solution to my question.

For example, my diff method can become a hybrid_property, and work as expected. My complicated_property cannot become one, or at least it seems like it; the if condition in its beginning will return either True or False constantly, and will not act as a function.

Peewee probably has some more magic hiding there; I'll keep looking and report my findings.

like image 536
Bach Avatar asked Nov 08 '15 13:11

Bach


1 Answers

Sounds like hybrid_property will be what you're looking for. Here is the hybrid methods documentation

As to your update, if you had just read a little further in the docs...

@hybrid_property
def radius(self):
    return abs(self.length) / 2

@radius.expression
def radius(cls):
    return fn.ABS(cls.length) / 2

So there you see two functions for the same property, radius. The first function will be invoked when called on the model instance. The second when called in a query.

You might write:

@hybrid_property
def complicated_property(self):
    if 17 <= self.a <= 173:
        return a_complicated_math_function(self.a + self.b)
    return another_complicated_math_function(self.a * self.b ** 2)

@complicated_property.expression
def complicated_property(cls):
    # Here you will need to use a CASE statement most likely.
    # If you want to turn it into SQL, you obviously need to know
    # what SQL you want to turn it into...
    return case(
        None,
        (cls.a.between(17, 173), fn.math(fn.more_math(cls.a, 1.23))),
        default=fn.another_complicated_math(cls.a))
like image 68
coleifer Avatar answered Oct 13 '22 21:10

coleifer