Search code examples
python-2.7peewee

peewee: reusing dynamically created models


In my application I have a model for which I know the number of columns only at runtime. Using a function like Factory below to create the model solves this issue nicely. However, if I use it multiple times (with potentially varying fields), the creation of the foreign key ref throws an exception:

AttributeError: Foreign key: dynamictable.ref related name "dynamictable_set" 
collision with foreign key using same related_name.

The message is quite clear and when I set the related_name argument when creating the foreign key there is no error.

Questions:

  1. Why can't I use the same related_name the second time? Do I need to redefine StaticTable as well?

  2. Is there a better approach to write to multiple databases with dynamic models?

Minimal, reproducable example:

import peewee

database_proxy = peewee.Proxy()

class BaseModel(peewee.Model):
    class Meta:
        database = database_proxy

class StaticTable(BaseModel):
    foo = peewee.DoubleField()

def Factory(fields):
    class DynamicTable(BaseModel):
        ref = peewee.ForeignKeyField(StaticTable)
    for field in fields:
        peewee.DoubleField().add_to_class(DynamicTable, field)
    return DynamicTable 

def Test(fname, fields):
    db = peewee.SqliteDatabase(fname)
    database_proxy.initialize(db)
    db.create_table(StaticTable)
    dyntable = Factory(fields)
    db.create_table(dyntable)
    db.close()


Test(':memory:', ['foo', 'bar'])
Test(':memory:', ['foo', 'bar', 'extra'])

Solution

  • I think this highlights a bug in peewee where you may explicitly want to ignore any backrefs. I've opened a ticket and will resolve it.

    https://github.com/coleifer/peewee/issues/465

    In the meantime, you can silence the error by setting a dynamic related_name on the model, e.g.

    def Factory(fields):
        dynamic_name = '_'.join(fields)
        class DynamicTable(BaseModel):
            ref = peewee.ForeignKeyField(StaticTable, related_name=dynamic_name)
        for field in fields:
            peewee.DoubleField().add_to_class(DynamicTable, field)
            return DynamicTable
    

    Update: per the fix in #465, it's now possible to disable backref validation:

    def Factory(fields):
        class DynamicTable(BaseModel):
            ref = peewee.ForeignKeyField(StaticTable, related_name=dynamic_name)
            class Meta:
                validate_backrefs = False
        for field in fields:
            peewee.DoubleField().add_to_class(DynamicTable, field)
            return DynamicTable