Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Peewee calls __init__ on model unexpectedly #856

Closed
jrs65 opened this issue Feb 16, 2016 · 13 comments
Closed

Peewee calls __init__ on model unexpectedly #856

jrs65 opened this issue Feb 16, 2016 · 13 comments

Comments

@jrs65
Copy link

jrs65 commented Feb 16, 2016

We've run into some trouble when upgrading to peewee 2.8.0, when we have a custom __init__ defined on our models. This behaviour worked fine when on earlier versions (2.7.3 is the other version I tested).

import peewee as pw

db = pw.SqliteDatabase(':memory:')


class TestTable1(pw.Model):

    a = pw.IntegerField()
    b = pw.IntegerField(default=2)

    class Meta:
        database = db


class TestTable2(pw.Model):

    def __init__(self, a=2):
        pw.Model.__init__(self, a=a)

    a = pw.IntegerField()
    b = pw.IntegerField(default=2)

    class Meta:
        database = db


db.connect()

db.create_tables([TestTable1, TestTable2])

# This works fine
TestTable1.create(a=2)
t1 = TestTable1.select().get()

# This fails
TestTable2.create(a=2)
t2 = TestTable2.select().get()  # Failure with: TypeError: __init__() got an unexpected keyword argument 'b'

In the code above, the failure occurs in the final select, it seems that when the model instance is created, the code in playhouse._speedups._ModelQueryResultWrapper.process_row uses __init__ to create an instance of the row with the full results of the query passed as kwargs (i.e. a=2, b=2). However, the TestTable2.__init__ does not support b as a keyword argument.

It's not clear from the documentation how well I should expect custom __init__ methods to work. However, I would imagine that this could be fixed by making playhouse._speedups._ModelQueryResultWrapper.process_row explicitly call pw.Model.__init__ on an instance created via __new__.

@lez
Copy link

lez commented Feb 17, 2016

Try modifying your __init__ to:

def __init__(self, a=2, **kwargs):
    pw.Model.__init__(self, a=a, **kwargs)

@coleifer
Copy link
Owner

@lez is correct. Models that override __init__() should accept arbitrary kwargs and pass them to the parent constructor. Really, you should not be overriding __init__() in the first place though.

@jrs65
Copy link
Author

jrs65 commented Feb 18, 2016

Thanks. I guess my confusion came from the fact this used to work fine on earlier versions.

Might be worth a mention in the documentation about not overriding __init__ or how to do it if you must. It's quite a natural thing to want to do, and it breaks in places that are a little unexpected if you do it incorrectly. If I get time in the next few days, I'll do it myself and send in a PR.

@mellertson
Copy link

Same with me. I overrode init() and it used to work, but I believe I upgraded to a newer version of peewee and it now spits out the following error:

TypeError: init() got an unexpected keyword argument 'database_column_name'

@elemolotiv
Copy link

@coleifer so what is the right way to init the data fields of peewee.Model descendant class?

@coleifer
Copy link
Owner

so what is the right way to init the data fields of peewee.Model descendant class

A Model class represents a row of data, which it is initialized with. What you mean by "data fields" is unclear to me. You can always override the init method, but with the caveat that it your implementation must accept arbitrary keyword arguments and also call the parent constructor.

@elemolotiv
Copy link

elemolotiv commented Aug 12, 2018

thanks @coleifer!

suppose I have a Car class that inherits from peewee.Model. If I want to initialise an object of the Car class with individual values, I can just use the constructor:

car_object = Car(seats=6, cylinders=2)

So far, so good!

But now suppose I want to initialise the Car object, starting from another object, say some CarConfig object and the initialisation requires some logic, more than just assigning fields 1-to-1.

Without peewee, I would simply define my __init__() method and put all the initialisation logic there

class Car:

     def __init__(self, car_config_object):
           ... some complex initialisation logic here that uses the car_config_object ...

but I can't define the __init__() method in peewee.Model descendant classes anymore.
So where do I put all that complex initialisation logic?

@coleifer
Copy link
Owner

You can still put it in __init__() with the caveat that __init__() is going to be called not just when instantiating objects yourself, but also every time a Car instance is read from a database cursor. I think you probably could make a classmethod on your Car object and use that as a factory for complex logic?

@elemolotiv
Copy link

good idea - I think the factory method is the way to go!

@mauriciogracia
Copy link

Could someone share an example of how would that factory method look like ?

@mkmoisen
Copy link

@mauriciogracia

class Tweet:
    @classmethod
    def new(cls, *args, **kwargs):
        tweet = cls()
        # Do complicated logic
        return tweet

This is just a way to avoid overriding __init__.

@aiqc
Copy link

aiqc commented Apr 27, 2022

Since __new__ does not trigger __init__ in Python, would there be anything wrong with implicitly using __new__ instead of explicitly using Tweet.new ?

@coleifer
Copy link
Owner

coleifer commented Apr 28, 2022

I would not advise it unless you're quite familiar with all the differences between __new__ and __init__ (which I myself am not quite sure of). If you need a specialized constructor, my advice is to just write an explicit @classmethod or you can just make very cautious modifications to __init__, e.g.

class User(Model):
    username = TextField()
    def __init__(self, *args, **kwargs):
        super(User, self).__init__(*args, **kwargs)  # Ensure parent init is called correctly.
        # do complicated stuff here.

But my advice is to probably stick to a classmethod constructor if your logic is particularly complex.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants