Package: ash-dal
Ash DALThe library provides a standardized way to connect to DB, and Base DAO class implementationInstallationPyPipipinstallash-dal# ORpoetryaddash-dalFrom githubIn order to install Ash DAL directly from GitHub repository, run:pipinstallgit+https://github.com/meetash/ash-dal.git@main# ORpoetryaddgit+https://github.com/meetash/ash-dal.git@mainUsageDatabase classThere are two options: sync or async database connection.Synchronous databasefromash_dalimportDatabase,URLfromash_dal.utilsimportprepare_ssl_contextfrommodelsimportUserssl_context=prepare_ssl_context(ssl_root_dir='/tmp/certs',client_cert_path='client-cert.pem',client_key_path='client-key.pem',server_ca_path='server-ca.pem')db_url=URL.create(drivername="mysql+pymysql",username="my_db_user",password="S3cret",host="127.0.0.1",port=3306,database="my_db",)read_replica_db_url=URL.create(drivername="mysql+pymysql",username="my_db_user",password="S3cret",host="127.0.0.1",port=3307,database="my_db",)DATABASE=Database(db_url=db_url,read_replica_url=read_replica_db_url,ssl_context=ssl_context,read_replica_ssl_context=ssl_context)defget_users(db:Database):withdb.sessionassession:users=session.scalars(User)returnusersAsynchronous databasefromash_dalimportAsyncDatabase,URLfromash_dal.utilsimportprepare_ssl_contextfrommodelsimportUserssl_context=prepare_ssl_context(ssl_root_dir='/tmp/certs',client_cert_path='client-cert.pem',client_key_path='client-key.pem',server_ca_path='server-ca.pem')db_url=URL.create(drivername="mysql+aiomysql",username="my_db_user",password="S3cret",host="127.0.0.1",port=3306,database="my_db",)read_replica_db_url=URL.create(drivername="mysql+aiomysql",username="my_db_user",password="S3cret",host="127.0.0.1",port=3307,database="my_db",)DATABASE=AsyncDatabase(db_url=db_url,read_replica_url=read_replica_db_url,ssl_context=ssl_context,read_replica_ssl_context=ssl_context)asyncdefasync_get_users(db:AsyncDatabase):asyncwithdb.sessionassession:users=awaitsession.scalars(User)returnusersDAO Base classLike you can use sync/async Database classes, there are also two variations of DAO Base classSynchronous DAO Base classfromdataclassesimportdataclassfromsqlalchemyimportStringfromsqlalchemy.ormimportDeclarativeBase,Mapped,mapped_columnfromash_dalimportBaseDAO,Database,URLclassBase(DeclarativeBase):passclassExampleORMModel(Base):__tablename__="example_table"id:Mapped[int]=mapped_column(autoincrement=True,primary_key=True)first_name:Mapped[str]=mapped_column(String(64))last_name:Mapped[str]=mapped_column(String(64))age:Mapped[int]@dataclass()classExampleEntity:id:intfirst_name:strlast_name:strage:intclassExampleDAO(BaseDAO[ExampleEntity]):__entity__=ExampleEntity__model__=ExampleORMModelif__name__=='__main__':db=Database(db_url=URL.create(drivername="mysql+pymysql",username="my_db_user",password="S3cret",host="127.0.0.1",port=3306,database="my_db",))dao=ExampleDAO(database=db)entity=dao.get_by_pk(pk='some-primary-key')Asynchronous DAO Base classfromdataclassesimportdataclassfromsqlalchemyimportStringfromsqlalchemy.ormimportDeclarativeBase,Mapped,mapped_columnfromash_dalimportAsyncBaseDAO,AsyncDatabase,URLimportasyncioclassBase(DeclarativeBase):passclassExampleORMModel(Base):__tablename__="example_table"id:Mapped[int]=mapped_column(autoincrement=True,primary_key=True)first_name:Mapped[str]=mapped_column(String(64))last_name:Mapped[str]=mapped_column(String(64))age:Mapped[int]@dataclass()classExampleEntity:id:intfirst_name:strlast_name:strage:intclassExampleDAO(AsyncBaseDAO[ExampleEntity]):__entity__=ExampleEntity__model__=ExampleORMModelif__name__=='__main__':db=AsyncDatabase(db_url=URL.create(drivername="mysql+aiomysql",username="my_db_user",password="S3cret",host="127.0.0.1",port=3306,database="my_db",))dao=ExampleDAO(database=db)entity=asyncio.run(dao.get_by_pk(pk='some-primary-key'))BaseDAO methodsThe main goal of BaseDAO class is to provide CRUD methods that could be useful while building a basic CRUD API.BaseDAOclass provides the following default methods:Data fetching methodsBaseDAO.get_by_pk(pk)- Using this method you can fetch an entity by its primary key.entity=dao.get_by_pk(pk='AWABCD1234')BaseDAO.all- Using this method you can fetch all entities from the database. It's might be useful for fetching
data from small tables where you don't actually need pagination (configs etc)entities=dao.get_all()BaseDAO.get_page(page_index, [page_size])- Fetch a page with entities by page index. If the index is out of range,
an empty page will be returned. Ifpage_sizeis not passed - the default page size (20) will be applied.page=dao.get_page(page_index=2,page_size=10)forentityinpage:# Do some stuff with entity...BaseDAO.paginate([specification, page_size])- An iterator that returns pages with entities. A specification
can be applied to fetch filtered data.forpageindao.paginate(specification={'status':'notified'},page_size=15):# Do some stuff with page...BaseDAO.filter(specification)- Fetch entities from database by specification. It's might be useful for fetching
filtered data from small tables where you don't actually need pagination (configs etc)entities=dao.filter(specification={'labId':2})Data manipulation methodsBaseDAO.create(data)- Create an entity in database based on passed data. Returns back an entitydata={'foo':'bar'}entity=dao.create(data=data)BaseDAO.bulk_create(data)- Create multiple entities within one query. Unlike the previous method
this one doesn't return anything.data=[{'foo':'bar'},{'foo':'beer'}]dao.bulk_create(data=data)BaseDAO.update(specification, update_data)- Patch entity(ies) by specification.update_data={'foo':'bar'}is_updated=dao.update(specification={'foo':'beer'},update_data=update_data)BaseDAO.delete(specification)- Remove entity(ies) by specification.is_removed=dao.delete(specification={'id':'some-id'},)Pagination strategiesBy default the BaseDAO class uses the simplest pagination strategy that is based on SQL offset & limit mechanisms.
The library provides one more strategy out of the box that is called deferred join pagination.
You can learn more about ithere.If you want to change the pagination strategy for you DAO class, you can do it by re-defining
the__paginator_factory__field inside your DAO class:fromash_dalimportBaseDAO,DeferredJoinPaginatorfromash_dal.utilsimportDeferredJoinPaginatorFactory,AsyncDeferredJoinPaginatorclassExampleEntity:...classExampleORMModel:id:int...classExampleDAO(BaseDAO[ExampleEntity]):__entity__=ExampleEntity__model__=ExampleORMModel__paginator_factory__=DeferredJoinPaginatorFactory(paginator_class=DeferredJoinPaginator,pk_field=ExampleORMModel.id,)# OR asyncclassExampleAsyncDAO(BaseDAO[ExampleEntity]):__entity__=ExampleEntity__model__=ExampleORMModel__paginator_factory__=DeferredJoinPaginatorFactory(paginator_class=AsyncDeferredJoinPaginator,pk_field=ExampleORMModel.id,)Custom pagination strategyYou can also define your own pagination strategy. Be aware that your paginator class should implement IPaginator or
IAsyncPaginator interfaces:fromash_dal.utils.paginator.interfaceimportIPaginator,IAsyncPaginatorfromash_dalimportPaginatorPageimporttypingastclassExampleORM:...classMyPaginator(IPaginator[ExampleORM]):defget_page(self,page_index:int)->PaginatorPage[ExampleORM]:# Do page fetching...defpaginate(self)->t.Iterator[PaginatorPage[ExampleORM]]:# Do pagination...@propertydefsize(self)->int:# Get pages countreturn10# or async paginatorclassMyAsyncPaginator(IAsyncPaginator[ExampleORM]):asyncdefget_page(self,page_index:int)->PaginatorPage[ExampleORM]:# Do page fetching asynchronously...asyncdefpaginate(self)->t.AsyncIterator[PaginatorPage[ExampleORM]]:# Do pagination asynchronously...@propertyasyncdefsize(self)->int:# Get pages count asynchronouslyreturn10