Package: pydantic-resolve
If you are using pydantic v2, please use pydantic2-resolve instead.A small yet powerful tool to extend your pydantic schema, and then resolve alldescendantsautomatically.It's also the key to the realm of composition-oriented-development-pattern (wip)What is composable pattarn?https://github.com/allmonday/composable-development-patternChange LogsdiscordInstallpipinstallpydantic-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 examples: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, 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, L63context: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.pyyou may need it if there has some reuseable transforming params.ensure_subset(target_model)target_model:classit will raise exception if fields of decorated class has field not existed inbase_class.this provide a validation to ensure your schema's field is a subset of targe schema.reference:test_2_ensure_subset.pymodel_config(hidden_fields: list[str], default_required: bool) (new in v1.9.1)hidden_fields: list the field names you don't want to expose.default_required: if True, fields with default values will also in schema['required']reference:test_schema_config.pyRun FastAPI examplepoetryshellcdexamples
uvicornfastapi_demo.main:app# http://localhost:8000/docs#/default/get_tasks_tasks_getUnittestpoetryrunpython-munittest# orpoetryrunpytest# orpoetryruntoxCoveragepoetryruncoveragerun-mpytest
poetryruncoveragereport-m