Package: wintrymodels
WINTRY INTEGRATION FOR SQLMODELThese functionalities were originaly part ofwintry, but
were moved to a separate package so it is an opt-in.wintryis really extensible, so, more features like this will be added in the future.What is thiswintry-models is just a way of ease the use of SQLModel with wintry. It is exploiting wintry's
Dependency Injection System to provide Request Bound SqlAlchemy Session, so changes to entities
can be tracked across multiple functions, and dependencies got declared in a declarative way.It is doable to acomplish the same with pure FastAPI, but is not trivial, besides, the integration
with "Controllers" is a real Nightmare. This way, we promote a separation of concerns and a way to
easily integrate an ORM into a web framework (like happens .NET).UsageRegister the DataContext service and wire up the engine creationfromwintryimportApp,scopedfromwintrymodels.dbcontextimportDataContext,AsyncSQLEngineContext,add_data_contextfromsqlmodel.ext.asyncio.sessionimportAsyncSession# Declare the AppDbContext@scopedclassAppDbContext(AsyncSession,DataContext):def__init__(self):super().__init__(AsyncSQLEngineContext.get_client(),expire_on_commit=False)app=App()# Register the Context engineadd_data_context(app,AsyncSQLEngineContext,"sqlite:///file.db")We can now access SQLModel funcionalities as usual and have dependency injection, so we can do as follows:fromwintryimportcontroller,getfromsqlmodelimportselect,SQLModelclassUser(SQLModel):id:int|None=Noneusername:str@controllerclassHeroController(object):context:AppDbContext@get("/users")asyncdefget_users(self)->list[User]:returnawaitself.context.exec(select(User))A simple UnitOfWork implementationAs we can register ourAppDbContextas a scoped dependency, we can share it among some
classes, so we can implement a UnitOfWork as followsfromwintryimportscoped,controller,post,Pathfromwintrymodels.dbcontextimportDataContext,AsyncSQLEngineContextfromsqlmodel.ext.asyncio.sessionimportAsyncSessionfromsqlmodelimportSQLModelfromcontextlibimportasynccontextmanager# Declare the AppDbContext@scopedclassAppDbContext(AsyncSession,DataContext):def__init__(self):super().__init__(AsyncSQLEngineContext.get_client(),expire_on_commit=False)# Create two modelsclassHero(SQLModel):id:int|None=Nonename:strclassCity(SQLModel):id:int|None=Nonecity_name:str# Create Repositories for each Model@scopedclassHeroRepository(object):def__init__(self,context:AppDbContext):self.context=contextasyncdefsave(self,hero:Hero):self.context.add(hero)asyncdefget_by_id(self,hero_id:int)->Hero|None:returnawaitself.context.get(Hero,hero_id)@scopedclassCityRepository(object):def__init__(self,context:AppDbContext):self.context=contextasyncdefsave(self,city:City):self.context.add(city)asyncdefget_by_id(self,city_id:int)->City|None:returnawaitself.context.get(City,city_id)@scopedclassUnitOfWork(object):def__init__(self,context:AppDbContext):self.context=contextasyncdefstart(self):awaitself.context.begin()asyncdefcommit(self):awaitself.context.commit()asyncdefrollback(self):awaitself.context.rollback()# alternatively run this block as a transaction@asynccontextmanagerasyncdeftransaction(self):try:awaitself.start()yieldexceptExceptionase:awaitself.rollback()raiseefinally:awaitself.commit()# This can be part of our service layer, for example@scopedclassChangeHeroNameService:def__init__(self,repository:HeroRepository,uow:UnitOfWork):self.repository=repositoryself.uow=uowasyncdefchange_hero_name(self,hero_id:int):asyncwithself.uow.transaction:hero=awaitself.repository.get_by_id(hero_id)ifheroisnotNone:hero.name="New Hero Name"# As we run in a transaction, the changes to hero# will be committed at block end# Now wire up in a controller@controllerclassHeroController:hero_service:ChangeHeroNameService@post("/{hero_id}")asyncdefchange_hero_name(self,hero_id:int=Path()):awaitself.hero_service.change_hero_name(hero_id)