Package: pymnesia
PymnesiaPymnesia provides with a real in memory database and ORM to be used in unit tests and in staging environment when
persistence adapters are not available yet.
This tool is likely to be used within decoupled architectures.OverviewThe current version is beta, but the project is stable and offers a wide range of features that can already be used in
your projects :Declare entities with various field types (For a complete list, please refer to the table below.)Save entities in an in memory databaseCommit and rollbackQuery stored entities using a very lightweight api and intuitive syntaxFields supportSupported for declarationSupported for queryintyes ☑yes ☑floatyes ☑yes ☑stryes ☑yes ☑boolyes ☑yes ☑datenot officiallynot officiallydatetimenot officiallynot officiallytupleyes ☑not officiallylistno ☒no ☒dictnot officiallyno ☒one to one relationyes ☑yes ☑one to many relationyes ☑yes ☑many to one relationno ☒no ☒many to many relationno ☒no ☒not officially supported: The feature should work but may not be fully functional (querying for instance) and
stability is not guarantied.Basic user documentationUntil a more detailed documentation is released, please refer to the examples below for usage.Please keep in mind that the features will usually be made available through an api import.
Core classes, functions and such should not be imported and used directly.EntitiesDeclaring a simple entityfromuuidimportUUIDfrompymnesia.api.entities.baseimportdeclarative_baseclassCarEngine(declarative_base()):__tablename__="car_engines"id:UUIDhorsepower:intDeclaring an entity with a 'one to one' relationfromuuidimportUUIDfrompymnesia.api.entities.baseimportdeclarative_baseclassCar(declarative_base()):__tablename__="cars"id:UUIDname:strengine:CarEngineDeclaring an entity with a 'one to many' relationThe relation api can be used to specify custom options (for now the reverse name and whether the relation is optional or
not).fromuuidimportUUIDfrompymnesia.api.entities.baseimportdeclarative_basefrompymnesia.api.entities.fieldsimportfield,relationclassCar(declarative_base()):__tablename__="cars"id:UUIDname:str=field(default="Peugeot 3008")engine:CarEnginedrivers:List[Driver]=relation(reverse="a_list_of_drivers")CommandSave and commitfromuuidimportuuid4frompymnesia.api.unit_of_workimportuowfrompymnesia.api.commandimporttransactionunit_of_work=uow()new_transaction=transaction(unit_of_work=unit_of_work)v12_engine=CarEngine(id=uuid4(),horsepower=400)aston_martin=CarModel(id=uuid4(),engine_id=v12_engine.id)unit_of_work.save_entity(entity=v12_engine)unit_of_work.save_entity(entity=aston_martin)new_transaction.commit()Querying the database for car models will return one car model (The output will be 400).forcarinunit_of_work.query().cars().fetch():print(car.engine.horsepower)Rollbackfromuuidimportuuid4frompymnesia.api.unit_of_workimportuowfrompymnesia.api.commandimporttransactionunit_of_work=uow()new_transaction=transaction(unit_of_work=unit_of_work)v12_engine=CarEngine(id=uuid4(),horsepower=400)unit_of_work.save_entity(entity=v12_engine)new_transaction.rollback()v8_engine=CarEngine(id=uuid4(),horsepower=300)unit_of_work.save_entity(entity=v8_engine)new_transaction.commit()Querying the database for car engines will return the v8 engine alone (The output will be 300).forengineinunit_of_work.query().car_engines().fetch():print(engine.horsepower)QueryFetchFetch allows to retrieve multiple results of a query.
To query an entity model, call query() in the unit of work instance containing your entities.
Then simply call a method using the tablename you declared for said entity.For instance, if you declare the entity below:fromuuidimportUUIDfrompymnesia.api.entities.baseimportdeclarative_baseclassAddress(declarative_base()):__tablename__="addresses"id:UUIDstreet_name:strYou will query addresses as follows:forcarinunit_of_work.query().addresses().fetch():# whatever you need to dopassFetch oneFetch allows to retrieve the first result of a query.Given you have two cars in your in memory database, fetch_one() will return the entity that was saved first.car=unit_of_work.query().addresses().fetch_one()Where Or clausesOne of the great features of Pymnesia is how you can add where or clauses to you queries.Where clauseTo add a where clause use the where method when querying an entity.'Equal' operatorThe query below will return every car engine that has a 400 horsepower:forcarinunit_of_work.query().car_engines().where({"horsepower":400}).fetch():# whatever you need to dopass'Not equal' operatorThe query below will return every car engine that doesn't have a 400 horsepower:forcarinunit_of_work.query().car_engines().where({"horsepower::not":400}).fetch():# whatever you need to dopass'Greater than' operatorThe query below will return every car engine that have horsepower greater than 400:forcarinunit_of_work.query().car_engines().where({"horsepower::gt":400}).fetch():# whatever you need to dopass'Greater than or equal to' operatorThe query below will return every car engine that have horsepower greater than or equal to 400:forcarinunit_of_work.query().car_engines().where({"horsepower::gte":400}).fetch():# whatever you need to dopass'Less than' operatorThe query below will return every car engine that have horsepower lesser than 400:forcarinunit_of_work.query().car_engines().where({"horsepower::lt":400}).fetch():# whatever you need to dopass'Less than or equal to' operatorThe query below will return every car engine that have horsepower lesser than or equal to 400:forcarinunit_of_work.query().car_engines().where({"horsepower::lte":400}).fetch():# whatever you need to dopass'Match' operatorThe query below will return every car that have a name matching the provided regex:forcarinunit_of_work.query().cars().where({"name::match":r'^Peugeot .*$'}).fetch():# whatever you need to dopass'In' operatorThe query below will return every car that have a value included in the provided list:forcarinunit_of_work.query().cars().where({"name::in":["Aston Martin Valkyrie","Porsche GT3"]}).fetch():# whatever you need to dopassRelational queriesEvery operator documented above can be used to make relational queries:forcarinunit_of_work.query().cars().where({"engine.horsepower::gt":400}).fetch():# whatever you need to dopassOr clausesYou can add one or more 'or clauses' to a query.
Every condition in a 'or clause' is evaluated as OR AND.For instance the query below:query=unit_of_work.query().cars().where({"name":"Peugeot 3008"})query.or_({"name::match":r'^Peugeot .*$',"engine.horsepower::gt":100})Is the equivalent of an SQL query:SELECT*FROMcarsJOINcar_enginesONcar_engines.id=cars.engine_idWHEREcars.name='Peugeot 3008'OR(cars.nameLIKE'^Peugeot .*$'ANDcar_engines.horsepower>100)Multiple 'or clauses' remain independent of one another:query=unit_of_work.query().cars().where({"name":"Peugeot 3008"})query.or_({"name::match":r'^Peugeot .*$',"engine.horsepower::gt":100})query.or_({"engine.horsepower::gte":200})Is the equivalent of an SQL query:SELECT*FROMcarsJOINcar_enginesONcar_engines.id=cars.engine_idWHEREcars.name='Peugeot 3008'OR(cars.nameLIKE'^Peugeot .*$'ANDcar_engines.horsepower>100)OR(car_engines.horsepower>=200)Where clause using compositionThe entities can be queried using composition rather than declarative conditions.The example below makes little sense, but this feature can be powerful to make complex queries when operator
functions
are not available to perform the requested
operation.fromtypingimportIterablefromfunctoolsimportpartialdefcar_name_func(entities_:Iterable,field:str,value:str,*args,**kwargs)->filter:returnfilter(lambdae:getattr(e,field)==value,entities_)partial_k2000_func=partial(car_name_func,field="name",value="K2000",)partial_gran_torino_func=partial(car_name_func,field="name",value="Gran Torino",)query=unit_of_work.query().cars().where_with_composition([partial_k2000_func,partial_gran_torino_func,])LimitYou can limit the number of result using the limit() method.The query below will limit the number of results to 5 car engines:forcarinunit_of_work.query().car_engines().limit(5).fetch():# whatever you need to dopassOrder byYou can order by your results by specifying a direction and a key.The query below will order the results on the field 'name' in descending order.forcarinunit_of_work.query().cars().order_by(direction="desc",key="name").fetch():# whatever you need to dopass