Defining virtual fields in peewee
Asked Answered
R

1

7

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.

Reconvert answered 8/11, 2015 at 13:47 Comment(0)
C
9

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))
Cryan answered 20/11, 2015 at 5:13 Comment(1)
Thanks! And I accept your criticism as well... and thanks for peewee!Reconvert

© 2022 - 2024 — McMap. All rights reserved.