Package: pymotyc
PyMotycStatically typed asynchronous MongoDB collections withPydanticmodels andMotorengine.Motyc stands forMOngodbTYpedCollections, and also is diminutive for the word 'motocycle' in Russian,
which, of course, also has a motor!Main idea of the library is to use Pydantic models to statically type MongoDB collections
to automate parsing documents from collection to models instances (and vise versa) and also
enable static type checking for saved and retrieved objects, which brings handy IDE support
for attributes access etc.DocumentationSeeREADME.md on GitHub.Exampleimportasynciofrommotor.motor_asyncioimportAsyncIOMotorClientfrompydanticimportBaseModelimportpymotycclassEmployee(BaseModel):name:strage:intclassWarehouse:employees:pymotyc.Collection[Employee]asyncdefmain():motor=AsyncIOMotorClient("mongodb://127.0.0.1:27017")awaitpymotyc.Engine().bind(motor=motor,databases=[Warehouse])awaitWarehouse.employees.collection.drop()vasya=awaitWarehouse.employees.save(Employee(name='Vasya Pupkin',age=42))# IDE already knows there, that vasya is of Employee typeassertisinstance(vasya,Employee)employees=awaitWarehouse.employees.find()assertemployees==[Employee(name='Vasya Pupkin',age=42)]if__name__=="__main__":asyncio.run(main())FeaturesDeclarative MongoDB databases with statically typed (by Pydantic models) collections
withsave,find,find_one,update,delete_onebuilt-in methods and a bunch of utility methods
to deal with raw Motor collections.One collection can be typed with Discriminated Union to hold different documents corresponded
to several models. Retrieved documents will be converted to correct model classes instances
thanks to Pydantic.Automation of routine procedures for collections management, like indexes creation.Flexible identity management of documents in the collection:MongoDB's_idfield injection to model, even if it has no one (detached id),id field of typebson.ObjectId(withalias='_id', limitation of Pydantic), which represents MongoDB's _id field
(the model should be properly configured or inherited frompymotyc.WithIdtrait),auto-generation of identity with callable provided (uuid in str representation by default, actually this is the most convenient method),client-managed identity field, index is created to guaranty identity uniques.pymotyc.MotycModelbase class (andpymotyc.WithIdtrait) to hold relation of the model instance with
collection it was retrieved from, this allows to modify model instance and callsavedirectly on
instance itself.Direct access to Motor's collection, which allows to rely on original MongoDB API and then use typed
collection utility methods to parse retrieved documents to model instances.ExperimentalAnother part of PyMotyc is so-calledrefactorable queries. The idea is when you type the query likeawaitcollection.find({'foo':'bar'})you have no relation between key 'foo' in the dict and model's (with which collection is typed) fieldfoo,
so when you rename field IDE have no idea, in which queries this field is used.PyMotyc allows to use model's fields as keys in queries in built-in typed collection methods.To enable this feature one should useinject_motyc_fields=TrueinEngine.bindmethod,
so query in example above can be re-written like this:...awaitpymotyc.Engine().bind(motor=motor,databases=[Warehouse],inject_motyc_fields=True)...employees=awaitWarehouse.employees.find({Employee.age:42})assertemployees==[Employee(name='Vasya Pupkin',age=42)]Now one can rename model's fieldagewith refactor feature of the IDE and IDE automatically
will rename keys in correspondent queries! Also, one can click onagefield and see all usages in queries.For this feature PyMotyc uses dirty trick, that's why feature should be considered as experimental.Using the fact, that Pydantic already initialized all its internal structures during model class creation,
PyMotyc replaces model's class attributes withpymotyc.MotycFieldclass instances, which holds
all necessary field's info like name alias etc, and then parses queries, searching for MotycField in them.PyMotyc also have simple query builder. One can useMotycFields in logical expressions, as well as use
methods likeregexpdirectly on them. This will form MotycQuery as result, which then will be converted
to MongoDB query by PyMotyc. To use models fields with injectedMotycFields in logical expression
they should be cast toMotycFieldexplicitly to calm down the IDE. One can usepymotyc.Mhelper for this.employees=awaitWarehouse.employees.find(M(Employee.name).regex('Vasya')&M(Employee.age)==42)assertemployees==[Employee(name='Vasya Pupkin',age=42)]