简体   繁体   中英

How to apply Column defaults before a commit in sqlalchemy

I have a declarative-base model:

class User(Base):
    id = Column(Integer, primary_key=True)
    money = Column(Integer, default=100)

and then I run

>>> u = User()
>>> u.money
None

How can I populate the defaults using sqlalchemy without writing anything to the database?

The column default only applies to INSERT and UPDATE statements, and thus isn't being applied until you .flush() the session.

To see the same value on new instances before you flush, you need to apply the default when a new instance is being created; in the __init__ method of the User object:

class User(Base):
    __tablename__ = 'users'

    def __init__(self, **kwargs):
        if 'money' not in kwargs:
             kwargs['money'] = self.__table__.c.money.default.arg
        super(User, self).__init__(**kwargs)

    id = Column(Integer, primary_key=True)
    money = Column(Integer, default=100)

If no money attribute is being set, we add one directly based on the default configured for the column.

Note that the defaults are SQL expressions , not Python values, so you may have to map those to Python objects first. For example, a boolean field will have a default 'false' or 'true' string value, not a False or True Python boolean object.

I have discovered a mechanism for populating some defaults automatically when an instance is created. I'm migrating from an in-house ORM that set defaults at instance creation, so I needed to preserve this behaviour and I didn't want to touch every model definition.

BaseModel = declarative_base()

class Base(BaseModel):
    __abstract__ = True

    def __init__(self, **kwargs):
        for attr in self.__mapper__.column_attrs:
            if attr.key in kwargs:
                continue

            # TODO: Support more than one value in columns?
            assert len(attr.columns) == 1
            col = attr.columns[0]

            if col.default and not callable(col.default.arg):
                kwargs[attr.key] = col.default.arg

        super(Base, self).__init__(**kwargs)

The major limitation is that only non-callable defaults are supported. Callable defaults and those that use a database query require an execution context that isn't available at instance creation time. Also, I have not discovered a case where len(ColumnProperty.columns) != 1 but that isn't supported either.

Since I need the default values not necessarily in constructor, but sometimes from other places, I came up with a function, that I added to my base Object class:

Base = declarative_base()
class ItemBase(Base):
    __abstract__ = True
    def _ensure_defaults(self):
        for column in self.__table__.c:
            if getattr(self, column.name) is None and column.default is not None and column.default.is_scalar:
                setattr(self, column.name, column.default.arg)

Which obviously does not work for callables. I could not figure out how to call it, and maybe it's for the better (feel like hacking the think already).

With this I can do something like:

class User(ItemBase):
    # ... inheritance, columns and stuff
    def __init__(self):
        self._ensure_defaults()

I can also call the method not in __init__ , but also from other methods, in case I only need to rely on the values in special circumstances (which I ended up removing later, since it was too confusing). Still in some cases this might be more flexible than the solution of @JamesEmerton.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM