Package: alchemista
AlchemistaTools to generate Pydantic models from SQLAlchemy models.Still experimental.InstallationAlchemista isavailable in PyPI.
To install it withpip, run:pipinstallalchemistaUsageSimply call themodel_fromfunction with a SQLAlchemy model.
EachColumnin its definition will result in an attribute of the generated model via the PydanticFieldfunction.For example, a SQLAlchemy model like the followingfromsqlalchemyimportColumn,Integer,Stringfromsqlalchemy.ormimportdeclarative_baseBase=declarative_base()classPersonDB(Base):__tablename__="people"id=Column(Integer,primary_key=True)age=Column(Integer,default=0,nullable=False,doc="Age in years")name=Column(String(128),nullable=False,doc="Full name")could have a generated Pydantic model viafromalchemistaimportmodel_fromPerson=model_from(PersonDB)and would result in a Pydantic model equivalent tofrompydanticimportBaseModel,FieldclassPerson(BaseModel):id:intage:int=Field(0,description="Age in years")name:str=Field(...,max_length=128,description="Full name")classConfig:orm_mode=TrueNote that the string length from the column definition was sufficient to add amax_lengthconstraint.
Additionally, by default, the generated model will haveorm_mode=True.
That can be customized via the__config__keyword argument.There is also anexcludekeyword argument that accepts a set of field names tonotinclude in the generated model,
and anincludekeyword argument accepts a set of field names todoinclude in the generated model.
However, they are mutually exclusive and cannot be used together.This example is available in a short executable form in theexamples/directory.Fieldarguments andinfoCurrently, the type, default value (either scalar or callable), and the description (from thedocattribute) are
extracted directly from theColumndefinition.
However, except for the type, all of them can be overridden via theinfodictionary attribute.
All other custom arguments to theFieldfunction are specified there too.
The supported keys are listed inalchemista.field.Info.Everything specified ininfois preferred from what has been extracted fromColumn.
This means that the default value and the description can beoverriddenif so desired.
Also, similarly to using Pydantic directly,defaultanddefault_factoryare mutually-exclusive,
so they cannot be used together.
Usedefault_factoryif the default value comes from calling a function (without any arguments).For example, in the case above,name=Column(String(128),nullable=False,doc="Full name",info=dict(description=None,max_length=64))would instead result inname:str=Field(...,max_length=64)fields_fromandmodel_fromThefields_fromfunction is the function that actually inspects the SQLAlchemy model and builds a dictionary
in a format that can be used to generate a Pydantic model.
Somodel_fromis just a shortcut for callingfields_fromand thenpydantic.create_model.
The model name thatmodel_fromsets isdb_model.__name__.If desired, or extra control is needed,pydantic.create_modelcan be used directly, in conjunction withfields_from.
This allows the customization of the name of the model that will be created and the specification of othercreate_modelarguments, like__base__and__validators__(model_fromcurrently only accepts__config__).For example:fromalchemistaimportfields_fromfrompydanticimportcreate_modelMyModel=create_model("MyModel",**fields_from(DBModel))transformBothfields_fromandmodel_fromhave atransformargument.
It is a callable used to transform the fields before generating a Pydantic model.
The provided transformation functions are in thefuncmodule.
By default, thetransformargument is set tofunc.unchanged, which, as the name implies, does nothing.The other provided transformation function isfunc.nonify, which makes all fields optional (if they weren't already)
and nullable and sets the default value toNone.
This is useful when some kind of "input model" is desired.
For example, the database model might have an auto-generated primary key, and some other columns with default values.
When updating an entity of this model, one wouldn't want to receive the primary key as an update candidate (which can
be solved using theexcludeargument) and probably wouldn't want the fields with default values to actually have
these values, since the update is meant to change only what the user asked to update.For example:fromsqlalchemyimportColumn,Integer,Stringfromsqlalchemy.ormimportdeclarative_basefromalchemistaimportmodel_fromfromalchemista.funcimportnonifyBase=declarative_base()classPersonDB(Base):__tablename__="people"id=Column(Integer,primary_key=True)age=Column(Integer,default=0,nullable=False,doc="Age in years")name=Column(String(128),nullable=False,doc="Full name")PersonInput=model_from(PersonDB,exclude={"id"},transform=nonify)The modelPersonInputis equivalent to the hand-written version below:fromtypingimportOptionalfrompydanticimportBaseModel,FieldclassPersonInput(BaseModel):age:Optional[int]=Field(None,description="Age in years")name:Optional[str]=Field(None,max_length=128,description="Full name")classConfig:orm_mode=TrueNote that bothageandnameweren't originally nullable (Optional, in Python) and the name was required (age
wasn't because it has a default value).
Now, both fields are nullable and their default value isNone.User-defined transformationsYou can also create your own transformation functions.
The expected signature is as follows:fromtypingimportTuplefrompydantic.fieldsimportFieldInfodeftransformation(name:str,python_type:type,field:FieldInfo)->Tuple[type,FieldInfo]:passWherenameis the name of the field currently being created,python_typeis its Python type, andfieldis its full
Pydantic field specification.
The return type is a tuple of the Python type and the field specification.
These two can be changed freely (the name can't).LicenseThis project is licensed under the terms of the MIT license.