Package: pydantic-sql-bridge
Pydantic SQL Bridge 🌉SQL and Pydantic models, simplified. Get the benefits of developing with Pydantic while harnessing all the power your
SQL database has to offer. You can read and write to most SQL databases like SQLite, PostgreSQL, MSSQL, and
MySQL.Pydantic-SQL-bridge generates Pydantic models for your database tables and the queries you write using those tables. It
allows you to write type-safe Python and use query results in FastAPI apps without having to repeat your SQL schema in
Python.Pydantic-SQL-bridge can also translate your Pydantic models into SQL code, allowing you to easily spin up a new
database. It will grow with your database usage, like when you start writing optimized queries.InstallationPydantic-SQL-bridge is available on PyPI.pipinstallpydantic-sql-bridgeHow to useThere are two options for using Pydantic-SQL-bridge: SQL first, or Pydantic first.SQL firstUse this if are generating your Pydantic models based on your database, for instance if someone else is maintaining the
database. The primary way is to derive the models from the database directly, like so:frompydantic_sql_bridge.read_writeimportcursorfrompydantic_sql_bridge.sql_firstimportcreate_models_from_dbwithcursor('local','sqlite')asc,open('models.py','w+')ashandle:handle.write('# GENERATED FILE')handle.write(create_models_from_db(c))Pydantic-SQL-bridge will generate a Python file that you can write to a location of your choosing. You can check this
into your repo and get all the benefits of developing with Pydantic. If you have a repository of SQL statements that
define your database schema, you can also read use that to generate the models.frompydantic_sql_bridge.sql_firstimportcreate_models_from_sqlwithopen('table_definitions.sql','r')ashandle:sql=handle.read().split('\n\n')withopen('models.py')ashandle:handle.write(create_models_from_sql(sql))By default, Pydantic-SQL-bridge will generate models for all your tables. Support for views and arbitrary select queries
is planned.The name of the generated model will be the name of your table, with 'Row' added to the end of it. This is how
Pydantic-SQL-bridge knows which table to query when loading data.ExampleWe set up a SQL table for portfolios and associated benchmark data.CREATETABLEPortfolio(sedolNCHAR(7)PRIMARYKEY,clusterNVARCHAR(50),n_investedBIGINT)CREATETABLEBenchmark(sedolNCHAR(7),nameNVARCHAR(50),n_availableBIGINT,is_reitBIT,CONSTRAINTFK_SedolFOREIGNKEY(sedol)REFERENCESportfolio(sedol))For this schema, Pydantic-SQL-bridge generates the following Python file.# models.pyfrompydanticimportBaseModelfromtypingimportAnnotatedfrompydantic_sql_bridge.utilsimportAnnotationsclassPortfolioRow(BaseModel):sedol:Annotated[str,Annotations.PRIMARY_KEY]cluster:strn_invested:intclassBenchmarkRow(BaseModel):sedol:strname:strn_available:intis_reit:boolYou can then write to and query from the database as follows.frompydantic_sql_bridge.read_writeimportcursor,get_where,writefrommodelsimportBenchmarkRow,PortfolioRowwithcursor('localhost',':memory:')asc:write(c,[BenchmarkRow(sedol='AAAAAAA',name='Test',n_available=14,is_reit=False)],compare_on=('sedol',),should_insert=True,should_update=True,should_delete=False)benchmark=get_where(c,BenchmarkRow)eu_retail_portfolio=get_where(c,PortfolioRow,cluster='Europe Retail')Pydantic firstUse this if you are setting up a new database.To set up a database according to our Pydantic models, we importcursorandsetup_database.frompydanticimportBaseModelfrompydantic_sql_bridge.read_writeimportcursorfrompydantic_sql_bridge.pydantic_firstimportsetup_databaseclassUser(BaseModel):id:intname='Jane Doe'classCheckingAccount(BaseModel):user:Userbalance:floatwithcursor('local','sqlite')asc:setup_database(c,[User,CheckingAccount])c.connection.commit()If you prefer to generate SQL to set up your database (for instance, if you are deploying the database separately, or
you want to make manual adjustments), we can usegenerate_sql. Since we are not connecting to a database directly,
we'll also have to tell Pydantic-SQL-bridge whatDatabaseTypeyou are using.frompydanticimportBaseModelfrompydantic_sql_bridge.pydantic_firstimportgenerate_sqlfrompydantic_sql_bridge.utilsimportDatabaseTypeclassUser(BaseModel):id:intname='Jane Doe'classCheckingAccount(BaseModel):user:Userbalance:floatsql=generate_sql([User,CheckingAccount],database_type=DatabaseType.SQLITE)withopen('table_definitions.sql','w+')ashandle:handle.write(sql)Notes and remarksWhy can't I control my database using just Pydantic-SQL-bridge?SQL is a much older technology than Python (and certainly Pydantic!), and is much more widespread. Pretty much every
programming language has a way of talking with SQL databases, and databases tend to outlive their associated
applications. SQL skills are one of the few things you can invest in for an almost guaranteed benefit, wherever your
software journey takes you. Trying to control the database from Python is rather putting the cart before the horse.It doesn't help that Python "things" areobjects, which you can nest (like when you
haveFooas an attribute ofSpam), and which
can "do stuff" ( like when you callmodel.model_dump()).  "Things" in SQL databases arerelations, which you cannot
nest, and which cannot do stuff (they are "just data"), so you cannot easily translate between those two worlds. There
do exist packages (called Object-Relational-Mappers or ORM's) that try to let you do this, such
asSQLAlchemy. If you go that way, you need to rely on the ORM's maintainers to implement
support for the database features you need, rather than just using the database however you want. And the skills you
learn are not transferable: if your next project is in C#, you cannot use SQLAlchemy.Pydantic-SQL-bridge's solution is to start from SQL and adapt our Python code around it. Of course we help you get
started using just Python, but these are training wheels. If you need something different from your database, you have
the chance to learn some SQL, and we will help you make sense of it on the Python end.Nested modelsPydantic-SQL-bridge does not support directly writing nested models to and reading them from your database: it
encourages you to work more directly with the database and the capabilities it has to offer. It does offer a utility for
nesting and unnesting models, to more easily translate between your application's models and the ones generated by
Pydantic-SQL-bridge.frompydanticimportBaseModelfromtypingimportClassVarfrompydantic_sql_bridge.read_writeimportcursor,get_where,writefrompydantic_sql_bridge.utilsimporttransformclassFirst(BaseModel):id:intname:strclassSecond(BaseModel):id:intscore:floatclassNested(BaseModel):id:intfirst:Firstsecond:SecondclassFlat(BaseModel):query_name:ClassVar[str]='example'id:intfirst_id:intfirst_name:strsecond_id:intsecond_score:floattargets=[Nested(id=0,first=First(id=0,name='alice'),second=Second(id=1,score=-5.21)),Nested(id=1,first=First(id=1,name='bob'),second=Second(id=2,score=348.7))]withcursor('localhost',':memory:')asc:write(c,[transform(target,Flat)fortargetintargets])query_result=get_where(c,Flat)targets:list[Nested]=[transform(r,Nested)forrinquery_result]