Closed jymchng closed 1 year ago
I have created a minimum viable codes to illustrate this problem:
from typing import List, Optional, Any
from sqlmodel import Field, SQLModel as _SQLModel, Relationship
from slugify import slugify
from datetime import date, datetime
from pydantic import validator
from sqlalchemy.orm import declared_attr
class SQLModel(_SQLModel):
@declared_attr # type: ignore
def __tablename__(cls) -> str:
return cls.__name__
class Config:
use_enum_values = True
class BaseIDModel(SQLModel):
id: Optional[int] = Field(
primary_key=True,
index=True,
nullable=False,
)
class ProjectBase(SQLModel):
is_active: bool = Field(default=True, nullable=False)
name: str = Field(nullable=False)
publish_date: date = Field(
default_factory=datetime.now().date, nullable=False)
repr_name: Optional[str] = Field(default=None, nullable=False)
approved: bool = Field(default=False, nullable=False)
featured: bool = Field(default=False, nullable=False)
# post_init
@validator("repr_name", pre=True, always=True)
def slugify_name(cls, v, values) -> str:
if v is None:
print(f"{v=}, {values=}")
return slugify(values["name"], separator="_")
raise ValueError(
f"`repr_name` cannot be passed in as a parameter to the `Request`.")
class Project(BaseIDModel, ProjectBase, table=True):
pass
class IProjectCreate(ProjectBase):
pass
if __name__ == '__main__':
Project.from_orm(IProjectCreate(name='Hello A'))
The Output is:
v=None, values={'is_active': True, 'name': 'Hello A', 'publish_date': datetime.date(2023, 2, 18)}
v=None, values={'is_active': True, 'publish_date': datetime.date(2023, 2, 18)}
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-4-8a711b7fe15b> in <module>
----> 1 Project.from_orm(IProjectCreate(name='Hello A'))
~\AppData\Roaming\Python\Python38\site-packages\sqlmodel\main.py in from_orm(cls, obj, update)
549 # If table, create the new instance normally to make SQLAlchemy create
550 # the _sa_instance_state attribute
--> 551 m = cls()
552 values, fields_set, validation_error = validate_model(cls, obj)
553 if validation_error:
<string> in __init__(__pydantic_self__, **data)
~\AppData\Roaming\Python\Python38\site-packages\sqlalchemy\orm\state.py in _initialize_instance(*mixed, **kwargs)
480 except:
481 with util.safe_reraise():
--> 482 manager.dispatch.init_failure(self, args, kwargs)
483
484 def get_history(self, key, passive):
~\AppData\Roaming\Python\Python38\site-packages\sqlalchemy\util\langhelpers.py in __exit__(self, type_, value, traceback)
68 self._exc_info = None # remove potential circular references
69 if not self.warn_only:
---> 70 compat.raise_(
71 exc_value,
72 with_traceback=exc_tb,
~\AppData\Roaming\Python\Python38\site-packages\sqlalchemy\util\compat.py in raise_(***failed resolving arguments***)
206
207 try:
--> 208 raise exception
209 finally:
210 # credit to
~\AppData\Roaming\Python\Python38\site-packages\sqlalchemy\orm\state.py in _initialize_instance(*mixed, **kwargs)
477
478 try:
--> 479 return manager.original_init(*mixed[1:], **kwargs)
480 except:
481 with util.safe_reraise():
~\AppData\Roaming\Python\Python38\site-packages\sqlmodel\main.py in __init__(__pydantic_self__, **data)
496 # Uses something other than `self` the first arg to allow "self" as a
497 # settable attribute
--> 498 values, fields_set, validation_error = validate_model(
499 __pydantic_self__.__class__, data
500 )
~\AppData\Roaming\Python\Python38\site-packages\pydantic\main.cp38-win_amd64.pyd in pydantic.main.validate_model()
~\AppData\Roaming\Python\Python38\site-packages\pydantic\fields.cp38-win_amd64.pyd in pydantic.fields.ModelField.validate()
~\AppData\Roaming\Python\Python38\site-packages\pydantic\fields.cp38-win_amd64.pyd in pydantic.fields.ModelField._apply_validators()
~\AppData\Roaming\Python\Python38\site-packages\pydantic\class_validators.cp38-win_amd64.pyd in pydantic.class_validators._generic_validator_cls.lambda5()
<ipython-input-2-8fd04dce0748> in slugify_name(cls, v, values)
31 if v is None:
32 print(f"{v=}, {values=}")
---> 33 return slugify(values["name"], separator="_")
34 raise ValueError(
35 f"`repr_name` cannot be passed in as a parameter to the `Request`.")
KeyError: 'name'
Opened a discussion in sqlmodel
: https://github.com/tiangolo/sqlmodel/discussions/556
Hello @jymchng I see that you got the solution here adding the validation into pydantic schema instead of SQLModel object. I think this validation can be used as a reference and do similar on create.
Please check this commit it adds validation on IHereoCreate schema. Also if you ask why schemas use the "I" letter at the beginning this is an internal convention of our team in order to be able to differentiate Models from schemas and as also we use typescript we see schemas similar to interfaces in typescript which we already use "I" convention
Hi @jonra1993, thank you for working on the commit, yeah, I managed to figure the separation of responsibilities between models and schemas - hence, figured that the right place to do validations is on the schemas.
Thank you for including an example on the IHeroCreate
schema for future references.
Let's supposed I have the following model and
create
schema.As you can see, I am trying to post-process the
name
field by 'slugify' it.The
ICreateProject
schema is as follows:As I try to create a project using the API, with the following request:
The error log I got is this:
Somehow, the
from_orm
method does not have the fieldname
.