klen / mixer

Mixer -- Is a fixtures replacement. Supported Django, Flask, SqlAlchemy and custom python objects.
Other
944 stars 95 forks source link

Support for Flask-SQLAlchemy models that have `__init__` arguments #12

Closed razzius closed 10 years ago

razzius commented 10 years ago

Consider the todo example from Flask-SQLAlchemy:

from datetime import datetime
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from mixer.backend.sqlalchemy import mixer

app = Flask(__name__)
db = SQLAlchemy(app)

class Todo(db.Model):
    __tablename__ = 'todos'
    id = db.Column('todo_id', db.Integer, primary_key=True)
    title = db.Column(db.String(60))
    text = db.Column(db.String)
    pub_date = db.Column(db.DateTime)

    def __init__(self, title, text):
        self.title = title
        self.text = text
        self.pub_date = datetime.utcnow()

If we try to use mixer with it:

mixer.blend(Todo)

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-1-8a1d24676132> in <module>()
     19         self.pub_date = datetime.utcnow()
     20
---> 21 mixer.blend(Todo)

/Users/razzi/local/venvs/thornm/lib/python2.7/site-packages/mixer/main.pyc in blend(self, scheme, **values)
    596         type_mixer = self.get_typemixer(scheme)
    597         try:
--> 598             return type_mixer.blend(**values)
    599         except Exception:
    600             if self.params.get('silence'):

/Users/razzi/local/venvs/thornm/lib/python2.7/site-packages/mixer/main.pyc in blend(self, **values)
    145                 values.append((name, value))
    146
--> 147         target = self.populate_target(values)
    148
    149         # Run registered middlewares

/Users/razzi/local/venvs/thornm/lib/python2.7/site-packages/mixer/main.pyc in populate_target(self, values)
    168     def populate_target(self, values):
    169         """ Populate target with values. """
--> 170         target = self.__scheme()
    171         for name, value in values:
    172             setattr(target, name, value)

TypeError: __init__() takes exactly 3 arguments (1 given)

mixer attempts to __init__ without any arguments, which throws an error. Would supporting a model defined this way be feasible/desirable?

One way to get around this is to make all fields have defaults in the __init__:

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50), unique=True)
    email = Column(String(120), unique=True)

    def __init__(self, name=None, email=None):
        self.name = name
        self.email = email

At the very least, putting a note in the docs that models with required __init__ parameters are unsupported might save others some confusion.

klen commented 10 years ago

I little bit dont understand what the reason for redefine __init__ method of SQLAlchemy models. But as one of solution you can easy create own mixer:

from mixer.backend.sqlalchemy import Mixer

class MyOwnMixer(Mixer):
    def populate_target(self, values):
        target = self.__scheme(**values)
        return target

mixer = MyOwnMixer()
klen commented 10 years ago

Maybe I will add this behavior as param to sqlalchemy mixer.

razzius commented 10 years ago

I do agree that using the default __init__ makes more sense — models with more than a couple fields are best constructed using named arguments rather than enforcing an arbitrary order.

Perhaps the Flask examples could be updated recommending this approach, if others feel the same way.

klen commented 10 years ago

Updated README and docs.

anthon-alindada commented 6 years ago

Hi, Is this fix still working. I've tried creating my own mixer but it does not work. I tried to put a logger inside the populate_target and its not triggering the log. I'm not sure with this but i think its not overriding the populate_target. I'm not even sure if i'm doing it right. Can someone help me with this.

Here's the error

Traceback (most recent call last): File "/usr/local/lib/python3.5/dist-packages/mixer/main.py", line 568, in blend return type_mixer.blend(**values) File "/usr/local/lib/python3.5/dist-packages/mixer/main.py", line 134, in blend target = self.populate_target(values) File "/usr/local/lib/python3.5/dist-packages/mixer/backend/sqlalchemy.py", line 254, in populate_target target = self.__scheme() TypeError: Mixer (<class 'app.users.models.User'>): __init__() missing 3 required positional arguments: 'email', 'first_name', and 'last_name'

smoll commented 6 years ago

I ran into the same issue as @anthon-alindada - it looks like the code changed since the example in the README was created. This worked for me:

class CustomTypeMixer(TypeMixer):
    def __init__(self, cls, mixer=None, factory=None, fake=True):
        super().__init__(cls, mixer=mixer, factory=factory, fake=fake)
        self.__scheme = cls  # not sure why above line isn't setting this... is this the keyword 'cls' or just a variable named 'cls'?

    def populate_target(self, values):
        """ Populate a target by values. """
        target = self.__scheme(**dict(values))
        return target

class CustomMixer(Mixer):
    type_mixer_cls = CustomTypeMixer

mixer = CustomMixer(session=session, commit=True)
organization = mixer.blend(Organization, internal_name='foo', name='Foo')