-
-
Notifications
You must be signed in to change notification settings - Fork 665
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
How to create a time Field for a model like django DateTimeField #370
Comments
There are a few ways to do this, either via Pydantic or with SQLAlchemy. SQLAlchemy is the way to go since implementing With Pydantic default factories or validatorsWith Pydantic you could use default factories: from datetime import datetime
from pydantic import BaseModel, Field
class Model(BaseModel):
created_at: datetime = Field(default_factory=datetime.utcnow)
m1 = Model()
m2 = Model()
print(f'{m1.created_at} != {m2.created_at}')
#> 2022-05-19 10:49:22.053624 != 2022-05-19 10:49:22.053641 Another approach would be validators: from datetime import datetime
from pydantic import BaseModel, validator
class Model(BaseModel):
created_at: datetime = None
@validator('ts', pre=True, always=True)
def set_created_at_now(cls, v):
return v or datetime.now() Having an automatic update time is a bit tougher... I think it should be possible with validators, but I can think of a few cases where the incorrect time can be set. Doing this in SQLAlchemy is easy using from sqlalchemy.sql import func
time_created = Column(DateTime(timezone=True), server_default=func.now())
time_updated = Column(DateTime(timezone=True), onupdate=func.now()) The above column definitions would mean that if Since you can directly use SQLAlchemy Columns in an SQLModel Field with the keyword argument from sqlmodel import Field, SQLModel
from sqlalchemy.sql import func
from sqlalchemy import Column, DateTime
class Model(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
foo: Optional[str]
created_at: Optional[datetime] = Field(
sa_column=Column(DateTime(timezone=True), server_default=func.now())
)
updated_at: Optional[datetime] = Field(
sa_column=Column(DateTime(timezone=True), onupdate=func.now())
) Gave it a quick test and it seems to work correctly, when an instance of |
Here is my very rough example code: import time
from datetime import datetime
from typing import Optional
from sqlalchemy import Column, DateTime, func
from sqlmodel import Field, Session, SQLModel, create_engine, select
class Model(SQLModel, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
foo: Optional[str]
created_at: Optional[datetime] = Field(
sa_column=Column(DateTime(timezone=True), server_default=func.now())
)
updated_at: Optional[datetime] = Field(
sa_column=Column(DateTime(timezone=True), onupdate=func.now())
)
sqlite_file_name = "database.db"
sqlite_url = f"sqlite:///{sqlite_file_name}"
engine = create_engine(sqlite_url)
def create_db_and_tables():
SQLModel.metadata.create_all(engine)
def create_entry():
with Session(engine) as session:
m = Model()
print(f"created {m=}")
session.add(m)
session.commit()
session.refresh(m)
print(f"added and refreshed {m=}")
def update_entry():
with Session(engine) as session:
s = select(Model)
m = session.exec(s).first()
print(f"\nfound {m=}")
m.foo = "bar"
print(f"modified {m=}")
session.add(m)
session.commit()
session.refresh(m)
print(f"refreshed {m=}")
def main():
create_db_and_tables()
create_entry()
time.sleep(5)
update_entry()
if __name__ == "__main__":
main() Running it outputs: created m=Model(id=None, foo=None, created_at=None, updated_at=None)
added and refreshed m=Model(foo=None, created_at=datetime.datetime(2022, 6, 29, 8, 14, 53), updated_at=None, id=1)
found m=Model(foo=None, created_at=datetime.datetime(2022, 6, 29, 8, 14, 53), updated_at=None, id=1)
modified m=Model(foo='bar', created_at=datetime.datetime(2022, 6, 29, 8, 14, 53), updated_at=None, id=1)
refreshed m=Model(foo='bar', created_at=datetime.datetime(2022, 6, 29, 8, 14, 53), updated_at=datetime.datetime(2022, 6, 29, 8, 14, 58), id=1) So |
@RobertRosca This example is very detailed! thanks a lot! |
Thanks! Happy it helped 😄 I wrote up an explanation and created PR #372 to add this to the documentation, since it does come up pretty often. |
@RobertRosca This is really helpful but there is an issue with |
So when using the example code I showed with Alembic instead of I haven't tested it with Alembic, but their docs show that the from alembic import op
from sqlalchemy import Column, TIMESTAMP, func
# specify "DEFAULT NOW" along with the column add
op.add_column('account',
Column('timestamp', TIMESTAMP, server_default=func.now())
) I guess there could be some issue with interactions between Alembic, SQLAlchemy, and SQLModel causing the default to be a constant instead of the now function, but I haven't had time to look. Since this should be supported by Alembic then if this isn't working correctly it's probably an issue in SQLModel, so I'd say open an issue here with some example code. I'll try and have a look some time this week as well, but maybe somebody else would be able to help before I can 😄 |
Thanks a lot for the help here @RobertRosca ! 🍰 And thanks @regainOWO for coming back to close the issue. ☕ |
I don't understand... this definitely does not work for me.
migration script: def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('users', sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('users', 'created_at')
# ### end Alembic commands ### I just added this line to created_at: Optional[datetime.datetime] = Field(
sa_column=Column(DateTime(timezone=True), server_default=func.now())
) |
I figured out this works: created_at: datetime.datetime = Field(
default_factory=datetime.datetime.utcnow,
nullable=False,
) In def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
print("Adding column created_at")
op.add_column("users", sa.Column("created_at", sa.DateTime(), nullable=True))
# Get the users.created_at column
user = sa.sql.table(
"users",
sa.sql.column("created_at", sa.DateTime()),
)
# Update the users.created_at column
op.execute(user.update().values(created_at=sa.func.now()))
with op.batch_alter_table("users", schema=None) as batch_op:
# Set the users.created_at column to not null
batch_op.alter_column("created_at", nullable=False)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table("users", schema=None) as batch_op:
batch_op.drop_column("created_at") |
I also implemented updated_at: Optional[datetime.datetime] = Field(
sa_column=Column(DateTime(), onupdate=func.now())
) Alembic migration: def upgrade() -> None:
op.add_column("users", sa.Column("updated_at", sa.DateTime(), nullable=True))
def downgrade() -> None:
op.drop_column("users", "updated_at") Or in case you are using batch operation: def upgrade() -> None:
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.add_column(sa.Column('updated_at', sa.DateTime(), nullable=True))
def downgrade() -> None:
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.drop_column('updated_at') |
First Check
Commit to Help
Example Code
Description
when i create a Image_Save_Record instance and commit it to db, the create_time field value should be that system time current.
when i change the Image_Save_Record instance which i created before and commit it to db, the update_time field value should be that system time current.
Is there sqlmodel have option to do the same like this?
Operating System
Windows
Operating System Details
No response
SQLModel Version
0.0.6
Python Version
3.7.9
Additional Context
No response
The text was updated successfully, but these errors were encountered: