Package: pydantic2-resolve
Change LogA small yet powerful tool to extend your pydantic schema with no pain.It is the key to the realm of composition-oriented-development-pattern (wip)[Attention] This package supports pydantic v2 only, it's incompatible with pydantic v1, if you want to play with pydantic v1, please use pydantic-resolve instead.What is composable pattarn?https://github.com/allmonday/composable-development-patternInstallpipinstallpydantic-resolveCode snippetsbasic usage, resolve your fields.importasynciofrompydanticimportBaseModelfrompydantic_resolveimportResolverasyncdefquery_age(name):print(f'query{name}')awaitasyncio.sleep(1)_map={'kikodo':21,'John':14,'老王':40,}return_map.get(name)classPerson(BaseModel):name:strage:int=0asyncdefresolve_age(self):returnawaitquery_age(self.name)is_adult:bool=Falsedefpost_is_adult(self):returnself.age>18asyncdefsimple():p=Person(name='kikodo')p=awaitResolver().resolve(p)print(p)# query kikodo# Person(name='kikodo', age=21, is_adult=True)people=[Person(name=n)fornin['kikodo','John','老王']]people=awaitResolver().resolve(people)print(people)# Oops!! the issue of N+1 query happens# query kikodo# query John# query 老王# [Person(name='kikodo', age=21, is_adult=True), Person(name='John', age=14, is_adult=False), Person(name='老王', age=40, is_adult=True)]asyncio.run(simple())optimizeN+1with dataloaderimportasynciofromtypingimportListfrompydanticimportBaseModelfrompydantic_resolveimportResolver,LoaderDependasLDasyncdefbatch_person_age_loader(names:List[str]):print(names)_map={'kikodo':21,'John':14,'老王':40,}return[_map.get(n)forninnames]classPerson(BaseModel):name:strage:int=0defresolve_age(self,loader=LD(batch_person_age_loader)):returnloader.load(self.name)is_adult:bool=Falsedefpost_is_adult(self):returnself.age>18asyncdefsimple():people=[Person(name=n)fornin['kikodo','John','老王']]people=awaitResolver().resolve(people)print(people)# query query kikodo,John,老王 (N+1 query fixed)# [Person(name='kikodo', age=21, is_adult=True), Person(name='John', age=14, is_adult=False), Person(name='老王', age=40, is_adult=True)]asyncio.run(simple())More cases:for more cases like:how to filter membershow to make post calculation after resolved?and so on..please read the following demos.cdexamples

python-mreadme_demo.0_basic
python-mreadme_demo.1_filter
python-mreadme_demo.2_post_methods
python-mreadme_demo.3_context
python-mreadme_demo.4_loader_instance
python-mreadme_demo.5_subset
python-mreadme_demo.6_mapper
python-mreadme_demo.7_singleAPIResolver(loader_filters, global_loader_filter, loader_instances, ensure_type, context)loader_filters:dictprovide extra query filters along with loader key.reference:6_sqlalchemy_loaderdepend_global_filter.pyL55, L59global_loader_filter:dictprovide global filter config for all dataloader instancesit will raise exception if some fields are duplicated with specific loader filter config inloader_filtersreference:test_33_global_loader_filter.pyL47, L49loader_instances:dictprovide pre-created loader instance, with canprimedata into loader cache.reference:test_20_loader_instance.py, L62, L63ensure_type:boolifTrue, resolve method is restricted to be annotated.reference:test_13_check_wrong_type.pyannotation_class:classif you havefrom __future__ import annotation, and pydantic raises error, use this config to update forward refsreference:test_25_parse_to_obj_for_pydantic_with_annotation.py, L39context:dictcontext can carry setting into each single resolver methods.classEarth(BaseModel):humans:List[Human]=[]defresolve_humans(self,context):return[dict(name=f'man-{i}')foriinrange(context['count'])]earth=awaitResolver(context={'count':10}).resolve(earth)LoaderDepend(loader_fn)loader_fn:subclass of DataLoader or batch_load_fn.detaildeclare dataloader dependency,pydantic-resolvewill take the care of lifecycle of dataloader.build_list(rows, keys, fn), build_object(rows, keys, fn)rows:list, query resultkeys:list, batch_load_fn:keysfn:lambda, define the way to get primary keyhelper function to generate return value required bybatch_load_fn. read the code for details.reference:test_utils.py, L32mapper(param)param:class of pydantic or dataclass, or a lambdapydantic-resolvewill trigger the fn inmapperafter inner future is resolved. it exposes an interface to change return schema even from the same dataloader.
if param is a class, it will try to automatically transform it.reference:test_16_mapper.pyensure_subset(base_class)base_class:classit will raise exception if fields of decorated class has field not existed inbase_class.reference:test_2_ensure_subset.pymodel_config(default_required: bool)default_required: if True, fields with default values will also in schema['required']use withField(exclude=True)to hide fields in schema and dumped resultreference:test_schema_config.pyRun FastAPI example:FastAPI examplepoetryshell
poetryinstallcdexamples
uvicornfastapi_demo.main:app
visithttp://localhost:8000/docs#/default/get_tasks_tasks_getUnittestpoetryrunpython-munittest# orpoetryrunpytest# orpoetryruntoxCoveragepoetryruncoveragerun-mpytest
poetryruncoveragereport-m