Changelog
- Major (X.0.0): Major new features or breaking changes
- Minor (x.Y.0): New features, backward compatible
- Patch (x.y.Z): Bug fixes and minor improvements
5.10
5.10.5 (2026-6-30)
- fix:
-
copy_dataloader_klscopies inherit the parent loader's annotated fields (#302):get_class_field_annotationsinpydantic_resolve/utils/types.pynow walkscls.__mro__and merges__annotations__from every class in the chain, instead of reading onlycls.__dict__['__annotations__']. Without this fix,copy_dataloader_kls('Foo', ParentLoader)produces aclass NewLoader(ParentLoader): passwhose own__dict__['__annotations__']is empty, so_create_loader_instancecould no longer see fields likeuser_id: int/permission: strdeclared on the parent — thesetattrinjection loop ran zero times and the loader instance later raisedAttributeError: 'Foo' object has no attribute 'user_id'from insidebatch_load_fn. Direct subclasses ofDataLoader(the common case) were unaffected because their own__dict__carried the annotations.Bug history — the
cls.__dict__.get('__annotations__')lookup was a latent bug frome637541(2023-04-06, "global filter"), present for 3+ years. It was masked untilba8762e(2026-05-14, "Replace type() factory with inheritance in empty loaders andcopy_dataloader_kls") changedcopy_dataloader_klsfromtype(name, loader_kls.__bases__, dict(loader_kls.__dict__))to the standardclass NewLoader(loader_kls): pass. The oldtype()factory shallow-copied the parent's__dict__into the new class — including__annotations__— so the lookup accidentally worked. The refactor correctly fixed the original state-isolation bug (#265/#266: copies sharing mutable class attributes via shallow__dict__), but in doing so dropped the__annotations__copy and activated this latent issue. Symptom window: ~6 weeks (2026-05-14 → 2026-06-30).Why not
cls.__annotations__.keys()— Python's attribute lookup returns the first__annotations__found in the MRO, not a merge. Soclass Child(Parent): extra: strwould yield only{'extra': str}and drop the parent's fields. The explicitreversed(cls.__mro__)walk merges annotations across all levels (subclass overrides win on key collision).Side effects — (1)
analysis.py's_is_loader_context_requireduses the same function, so copies of_context-aware loaders are now correctly recognized as needing context injection (previously silently skipped). (2) Walking the full MRO also surfacesaiodataloader.DataLoader's own annotated fields (batch,cache,max_batch_size); all of these have class-level defaults, so_create_loader_instance's existingif has_default and field not in param_config: continuebranch skips them unchanged. (3) Two existing tests intests/utils/test_types.pyandtests/utils/test_utils.pyhad been encoding the old "only-this-class" behavior with explicit comments; both are updated to assert the merged-MRO semantics, and a newtest_copy_dataloader_kls_inherits_parent_annotationsregression covers the reported scenario.
5.10.4 (2026-6-25)
- fix:
- Compose JSON serialization for UUID-like values:
use_case.composenow serializes projectedBaseModelresults in Pydantic JSON mode, recursively serializesdictpayloads, and applies JSON-mode fallback serialization for scalar values such asuuid.UUID. This fixes compose responses that previously leaked raw Python objects for UUID fields, especially inside nested dictionaries.
5.10.3 (2026-6-25)
- fix:
- Resolver instance state isolation (#289):
object_level_collect_alias_map_storeis now reset at the start of everyresolve()call instead of accumulating across trees; a new_in_resolveguard withtry/finallyraises a clearRuntimeErrorwhen the sameResolverinstance is shared across concurrentresolve()calls (previously surfaced as a crypticKeyError: 'resolve_params');_make_nodes/_collect_childrenno longer eagerly evaluateclass_util.get_kls_full_name(kls)on every cache hit (dict.get(k, expensive_default)was paying the default argument on every child node — 50 leaves produced 55 calls instead of 4). Documented the one-Resolver-per-concurrent-call contract inResolver's class docstring anddocs/api_resolver.md/docs/fastapi_integration.md(EN + ZH). Profile.Timer.timesetno longer grows unboundedly (#290):Timer.end()now usesself.timeset.pop(id)instead ofself.timeset[id]. Previously every debug-moderesolve()pair added one entry totimesetthat was never removed, leaking memory proportional to the number of timed method calls in any long-livedResolver(debug=True)process.Resolver(context={})rejected up-front (#291): empty dict and non-dictcontextvalues are now rejected in__init__withValueErrorandTypeErrorrespectively, instead of being silently coerced toNonevia theif context else Nonefalsy check — which previously surfaced later as the misleadingAttributeError: context is missingfrom the unrelatedhas_contextcheck.Resolver()andResolver(context=None)still produceself.context = None._validate_exposeno longer false-positives on inherited__pydantic_resolve_expose__(#292):_prepare_class_contextnow reads__pydantic_resolve_expose__/__pydantic_resolve_collect__fromkls.__dict__instead of viagetattr, so a subclass that doesn't override the attribute no longer re-registers the parent's aliases intoexpose_setand triggers a spuriousValueError: Expose alias name conflictswhen both classes appear in the same resolve tree. Same change applied tocollect_dictfor consistency._clone_collectorreverts tocopy.deepcopy(#293): the hand-written clone introduced in5.9.0(commitbfba588) bypassed__init__viacls.__new__(cls)and hard-codednew.val = [], silently dropping any attribute a subclass set in__init__(key_fn,n, etc.) and breaking every collector whosevalis a dict/set/custom aggregator.copy.deepcopyhandles allICollectorimplementations transparently. Micro-benchmark: 2.89 us/call (deepcopy) vs 0.13 us/call (hand-written) — 22x ratio but absolute cost is dwarfed by IO in any realisticpost_*workload, and end-to-end benchmarks show 0% impact.
5.10.2 (2026-6-24)
- fix:
- UseCase compose:
BaseModelarg types now register asINPUT_OBJECTend-to-end. The arg-side_collect_reachable_typespass isis_input=True, so apayload: CreateTaskInputparameter produces anINPUT_OBJECTtype withinputFieldspopulated (previously only the method-arg type ref claimedINPUT_OBJECTwhile the schema itself had no such type).FieldInfo.default_valueis now populated from each pydantic field's default and rendered intoinputFields[i].defaultValueas a GraphQL literal (10/null/"hi"), matching how method-arg defaults are serialized. describe_compose_methodexpandsINPUT_OBJECTtypes referenced by args:_collect_reachable_sdl_typesnow walksINPUT_OBJECTentries (and recurses their fields), and a new_render_struct_type_sdl(keyword="input")branch emitsinput X { ... }blocks. Previously the method SDL referenced an input type without defining it.- Same
BaseModelas both return and arg no longer produces a spec-violating schema: GraphQL forbids a type from serving as bothOBJECTandINPUT_OBJECT.build_compose_schemanow runs in two phases (all returns first, then all args) and renames the arg-side registration to{Name}Inputwhen the class name is already taken by anOBJECT._build_type_refconsults the registry so arg / input-field type refs pick up the renamed leaf. Tooling that consumes introspection (GraphiQL,graphql-core) sees a valid schema instead ofarg.kind=INPUT_OBJECTpointing at a type defined asOBJECT.
5.10.1 (2026-6-24)
- fix:
- Raise on
post_default_handler+default_handlerfield conflict: previously, when a class declared both apost_default_handlermethod (reserved finalizer, no field auto-binding) and adefault_handlerfield, the method's return value was silently discarded while the field sat at its declared default — thepost_<field>naming convention misled users into expecting the method to populate the field._scan_and_validate_methodsnow detects the conflict at metadata-build time and raisesValueErrorwith clear remediation. Backward-compatible: the finalizer behavior is preserved when only the method exists.
5.10.0 (2026-6-19)
Introduces a GraphQL surface for UseCase services — a way to compose multiple UseCaseService methods in a single GraphQL query, plus the schema/introspection machinery that goes with it. MCP is one consumer of this surface (the AI-agent packaging), but the underlying Python APIs (UseCaseResources.compose, compose_introspect, build_compose_schema) work directly from FastAPI or any HTTP framework. See demo/use_case/app_compose.py for a non-MCP HTTP example.
The classic 4-tool UseCase MCP server (create_use_case_mcp_server / list_services / describe_service / call_use_case) is removed; the GraphQL compose MCP server (create_use_case_graphql_mcp_server) is now the single MCP entry point. graphql-core's role in this codebase narrows to query parsing — type representation, schema construction, and introspection execution all switch to the existing ErDiagram type infrastructure (TypeMapper + TypeInfo registry).
- feat:
- UseCase GraphQL compose (
UseCaseResources.compose(query, context)): execute a fixed 3-level GraphQL query — root → Service → Method → DTO field selection — and return a nested{service: {method: result}}dict. Each@querymethod runs concurrently viaasyncio.gather;@mutationmethods run serially in declaration order. Field selection projects each method's return DTO viabuild_subset_modelbefore serialization.Resolver/AutoLoadon returned DTOs fire normally — business methods are responsible for resolving their own outputs. - UseCase GraphQL introspection (
compose_introspect(app, query)): GraphiQL-compatible__schema/__type(name: "...")/__typenamequeries against the app's cached schema registry. Standard 5 built-in directives (skip/include/deprecated/specifiedBy/oneOf) reported. Walks a hand-built{type_name: TypeInfo}registry instead of going through graphql-core. build_compose_schema(app): walks UseCaseService classes once at registration time, producing the{type_name: TypeInfo}registry (services as{Service}QueryOBJECT, every reachable DTO + Enum, plus the 5 standard GraphQL scalars). Cached onUseCaseResources.compose_schemaso introspection and SDL rendering don't rebuild per call.FromContextmarker + per-appcontext_extractor:Annotated[int, FromContext()]parameters are server-injected (e.g. user identity extracted from the Authorization header), not settable from query args. Same service method works unchanged across FastAPI (Depends(get_current_user)) and MCP (context_extractor).create_use_case_graphql_mcp_server(apps): MCP packaging of the above. 4-layer progressive disclosure —list_apps→describe_compose_schema(service + method listing) →describe_compose_method(args / returns / focused SDL showing every reachable DTO and Enum) →compose_query(data execution). Pure wrapper around the Python APIs; no MCP-specific business logic.-
Spec-compliant SDL rendering: type descriptions above
type X {, field descriptions above each field, both as block strings ("""..."""). Enum types referenced by a method's return DTO appear in SDL alongside OBJECT types. Seemethod_sdl(registry, service, method). -
refactor:
- graphql-core scope narrowed to query parsing only. Removed
pydantic_resolve/graphql/type_converter.py(236 lines) — itspydantic_to_graphql_typeis fully replaced byTypeMapper.map_to_graphql_typefrom the ErDiagram path. The two paths now share one type system instead of running parallel implementations. - Classic MCP server removed (pre-release on a feature branch, no breaking change for downstream users). Drops
pydantic_resolve/use_case/server.py(480 lines) anddemo/use_case/mcp_server.py+mcp_server_with_context.py. introspector.pyremoved (646 lines): never called from the production path;mcp_server+compose_schemaalready cover the same surface via different code paths.-
Shared utilities consolidated:
TypeMapper.collect_referenced_types(annotation, *, include_enums=True)— single canonical walker for "find every BaseModel/Enum reachable from this annotation".TypeCollector.collect_nested_pydantic_typesandutils/openapi._collect_nested_typesbecome thin wrappers.utils/field_metadata.iter_fields_with_marker(model_cls, marker_cls)— centralizes theExposeAs/SendTo/AutoLoadfield-scan pattern.GraphQLTypeInfo.leaf_nameproperty — walks NON_NULL / LIST wrappers to the named leaf.business.iter_use_case_methods(cls, *, enable_mutation=True)— collapses thegetattr + kind-filterpattern duplicated across compose_schema and mcp_server.context.is_from_context_annotation— consolidates 4 inline copies.
-
fix:
- Root Query service fields point at the right OBJECT: previously degraded to a scalar fallback (
String/Int) because TypeMapper didn't recognizeUseCaseServicesubclasses. Now correctly rendersNON_NULL(OBJECT({Service}Query)). - GraphQL duplicate fields rejected instead of silently merged (affects
QueryParser). -
defaultValueusesjson.dumpsinstead ofreprso booleans serialize astrue/false(notTrue/False) and strings use double quotes per GraphQL spec. -
docs:
docs/use_case_mcp_service.md+.zh.mdrewritten for the compose server (4-layer walkthrough, FromContext + context_extractor pattern, SDL examples).docs/api_use_case_mcp.md+.zh.mddocument onlycreate_use_case_graphql_mcp_server.demo/use_case/services.pyenriched with class docstrings +Field(description=...)+ aTaskStatusEnum so the SDL output is meaningful end-to-end.
5.8
5.8.0 (2026-6-8)
- feat:
- BFS execution mode for Resolver: replace recursive DFS traversal with level-by-level BFS. All
resolve_*methods at the same level run concurrently viaasyncio.gather, maximizing DataLoader batch sizes. Two-phase design: Phase A resolves top-down, Phase B executespost_*bottom-up - perf:
-
DataLoader batch efficiency: same-level resolves now share a single gather call, significantly increasing DataLoader batch coalescing. SQLite P50 comparison (DFS → BFS):
Medium (20 users, 10 sprints, 200 tasks)
Scenario DFS (ms) BFS (ms) Delta Q1: 1-level 5.57 3.71 -33.4% Q2: 2-level 6.28 4.42 -29.6% Q3: 3-level linear 5.90 4.08 -30.8% Q4: wide parallel 8.32 5.96 -28.4% Large (50 users, 20 sprints, 1000 tasks)
Scenario DFS (ms) BFS (ms) Delta Q1: 1-level 25.15 15.53 -38.3% Q2: 2-level 26.57 16.88 -36.5% Q3: 3-level linear 13.04 8.37 -35.8% Q4: wide parallel 19.35 12.92 -33.2% XLarge (200 users, 50 sprints, 2500 tasks)
Scenario DFS (ms) BFS (ms) Delta Q1: 1-level 95.62 63.25 -33.9% Q2: 2-level 97.75 66.94 -31.5% Q3: 3-level linear 77.77 31.11 -60.0% Q4: wide parallel 107.49 73.18 -31.9% -
fix:
post_default_handlerordering: now runs after all namedpost_*methods complete at the same level, ensuring consistent data visibilityexpose_to_descendanttiming: child ancestor context is now built after all resolves at a level finish, soresolve_methods that set expose fields are visible to children- Debug/profile mode: restore timing data collection in BFS traversal
- refactor:
- Remove DFS recursive traversal and ContextVar infrastructure (~379 lines)
- Replace bare tuple with
_ResolveJobdataclass for resolve job representation - Extract
_traverseinto_phase_a_resolve,_phase_b_prepare_collectors,_phase_b_execute_postsfor readability - docs:
- Redesign landing page with Clean Architecture emphasis
- Improve quick start, post-processing, UseCase MCP, and Voyager guides
- Update README with before/after comparison and advanced examples
5.9
5.9.0 (2026-6-9)
- perf:
-
BFS resolver hot path optimization: reduce per-node overhead across all BFS phases. Benchmark comparison (5.8.0 → 5.8.1, pytest-benchmark, mean time):
Scenario 5.8.0 (ms) 5.8.1 (ms) Delta very_large_dataset 53.75 45.68 -15.0% large_dataset_simple_objects 29.02 22.56 -22.3% large_dataset_with_post 20.28 17.19 -15.2% expose_three_levels 25.10 21.72 -13.5% deep_nesting_standard 14.92 13.85 -7.2% dataloader 27.67 27.41 -0.9% -
Optimizations applied:
setattr→object.__setattr__to bypass pydantic validation on already-validated valuescopy.deepcopy→ lightweight_clone_collectorfor collector cloning in Phase Bisinstancefast path intry_parse_data_to_target_field_typeto skip redundantvalidate_python- Cache
kls_pathfrom metadata instead of repeated f-string construction - Inline metadata lookups, eliminating per-node function call overhead and intermediate allocation
5.7
5.7.0 (2026-5-21)
- feat:
- UseCase MCP selection projection for
call_use_case:call_use_casenow supports response field selection so clients can request focused subsets of nested result data instead of always receiving the full payload - Selection metadata in service introspection: UseCase MCP introspection now exposes selection-aware response information, helping MCP clients discover which fields can be projected
- docs:
- Update English and Chinese UseCase MCP docs with selection projection examples and guidance
- test:
- Add coverage for selection projection behavior across UseCase MCP responses
5.6
5.6.3 (2026-5-21)
- fix:
- UseCase MCP fallback type-hint resolution: unresolved return forward references no longer break runtime parameter coercion or
FromContextinjection incall_use_case, so execution now stays consistent withdescribe_service - refactor:
- Shared type-hint resolution helpers: move use_case fallback annotation resolution into
utils/types.pyand reuse it across introspection, runtime coercion, and return annotation extraction
5.6.2 (2026-5-20)
- fix:
- UseCase MCP type coercion for
call_use_case: arguments received from MCP clients (JSON-native types likeintpassed wherestris expected) are now coerced to the correct Python type using PydanticTypeAdapterbefore being passed to the underlying method
5.6.1 (2026-5-20)
- fix:
- UseCase MCP FromContext parameters are described as optional: parameters annotated with
Annotated[..., FromContext()]now appear as optional in generateddescribe_servicesignatures and parameter metadata, so MCP clients do not need to provide values that are injected from context
5.6.0 (2026-5-9)
This release introduces breaking changes to the UseCaseService API. Methods must now be decorated with @query or @mutation instead of using @classmethod.
- feat:
@query/@mutationdecorators for UseCaseService: UseCaseService methods now use@queryand@mutationdecorators (frompydantic_resolve) instead of@classmethod. The decorators reuse the same implementation as GraphQL, automatically converting methods to classmethods and setting metadataenable_mutationapp-level control: NewUseCaseAppConfig(enable_mutation=False)option to hide mutation methods from MCP tools. When disabled,list_servicesexcludes mutations from count,describe_serviceomits mutation methods, andcall_use_caseblocks mutation callskindfield indescribe_service: Each method indescribe_serviceresponse now includes akindfield ("query"or"mutation") for AI agents to distinguish read vs write operations- break:
- UseCaseService methods require
@query/@mutation: Undecorated async classmethods are no longer automatically discovered byBusinessMeta. All existing UseCaseService subclasses must add@queryor@mutationdecorators __use_case_methods__structure changed: value type changed fromclassmethoddescriptor todictwith keysmethod,kind,description
5.5
5.5.0 (2026-5-9)
This release introduces breaking changes to the AutoLoad API. See Migration Guide — v5.4 to v5.5 for upgrade instructions.
- feat:
- Implicit AutoLoad: fields whose names match a relationship
namein the ER Diagram are automatically resolved without requiringAnnotated[..., AutoLoad()]annotation. ExplicitAutoLoad(origin=...)is still needed when the field name differs from the relationship name - Standalone
AutoLoadfunction:AutoLoadis now a module-level function (from pydantic_resolve import AutoLoad) instead of a factory created viadiagram.create_auto_load(). No diagram binding required - Ambiguity detection for external ErDiagram: when multiple
ErDiagraminstances register conflicting relationships (same name, different FK) for the same entity class,DefineSubsetraisesValueErrorat class-definition time instead of silently using the last registration - break:
ErDiagram.create_auto_load()removed: replaced by standaloneAutoLoad()function. See Migration Guide for detailsLoaderInfo._er_configs_mapremoved: no longer used; relationship lookup now goes through MRO + global registry- refactor:
ErLoaderPreGenerator.prepare()restructured into Phase 1 (explicit AutoLoad) and Phase 2 (implicit matching by field name), consistent withDefineSubsetFK injection logicDefineSubsetFK auto-injection unified via_collect_relationship_candidates_from_mro+_select_relationship, removing direct dependency onLoaderInfo._er_configs_mapResponseBuilderuses implicit AutoLoad for dynamic GraphQL response models, removingAnnotated[..., AutoLoad()]wrappingErDiagramregistration changed from overwrite (kls -> Entity) to append (kls -> [Entity]) to support ambiguity detection
5.4
5.4.0 (2026-5-8)
- feat:
- UseCase MCP server: multi-app management MCP server that exposes UseCaseService methods to AI agents via progressive disclosure (list_apps → list_services → describe_service → call_use_case)
- UseCase
FromContextannotation: methods can declare parameters annotated withFromContextto receive values from request context, withcontext_extractorsupport for async extraction get_return_annotation()utility: extract return type annotation from methods (handles classmethod,__future__annotations, string fallback) for convenient FastAPIresponse_modelusage- refactor:
- Update TypeMapper and SDLBuilder to use new mapping methods and improve type resolution
- Remove unused method for collecting types from method parameters
- Consolidate type checking in
introspector.pyto use shared utilities fromutils/types.py(_is_list,_is_optional,get_core_types), eliminating duplicate type-handling code - Simplify
_collect_dto_typeswithget_core_typesfor one-pass type unwrapping
5.3
5.3.1 (2026-4-22)
- bug:
- fix pagination in many to many relationship
5.3.0 (2026-4-22)
- feat:
- GraphQL limit/offset pagination:
GraphQLHandler(enable_pagination=True)enables server-side pagination for one-to-many relationships. Requirespage_loaderandorder_byconfigured onRelationship. Supports multi-level nested pagination with independent limit/offset per field Resolver(resolved_hooks=...)dependency injection: core Resolver now accepts a list of post-resolve hooks via constructor, decoupling GraphQL-specific pagination logic from the core resolution engine- refactor:
- Extract
inject_nested_paginationinto standalonegraphql/pagination/injector.pymodule, injected viaresolved_hooksinstead of hardcoded in coreResolver - Consolidate pagination hidden field names into
constant.py(GRAPHQL_PAGINATION_FIELD_PREFIX,GRAPHQL_PAGINATION_TREE_FIELD)
5.2
5.2.0 (2026-4-18)
- feat:
Resolver(split_loader_by_type=True): create separate DataLoader instances per request_type so lightweight views (e.g.TaskCardwith 2 columns) don't query all columns needed by heavyweight views (e.g.TaskDetailwith 20 columns). Incompatible withloader_instances. See Resolver API for details.- refactor:
- Improved
validate_and_create_loader_instancereadability with unified split/non-split logic - Deterministic
_query_metaoutput order
5.1
5.1.0 (2026-4-13)
- feat:
- Request context support for GraphQL:
handler.execute()accepts an optionalcontextdict, passed into@query/@mutationmethods via_contextparameter and forwarded to the internalResolver(context=...)for DataLoader context injection _contextparameter in@query/@mutationmethods: declare_context: dictin method signature to receive request-scoped data (e.g.user_idfrom JWT); the parameter is hidden from GraphQL schema — clients cannot see or set it_contextexcluded from SDL and introspection:SDLBuilderandIntrospectionGeneratorskip_contextwhen generating query/mutation field argumentsAppConfig.context_extractorfor MCP: optional callback(Context) -> dict | Awaitable[dict]that extracts request-scoped context from FastMCP HTTP request (e.g. user identity from Authorization header), passed ascontext=tohandler.execute()- RBAC/ABAC demo: new
demo/rbac/showcasing multi-level permission queries with DataLoader batching, FK-based ancestor tracing, ABAC condition evaluation, and mail group permission inheritance - refactor:
- Entity classes updated for context handling in GraphQL API
5.0
5.0.1 (2026-4-11)
- fix:
- fix minor type issues
5.0.0 (2026-4-3)
BREAKING CHANGES — ErDiagram.configs renamed to ErDiagram.entities. See Migration Guide for details.
- feat:
- ORM relationship auto-discovery: new
pydantic_resolve/integrationmodule generatesRelationship+DataLoaderfrom SQLAlchemy, Django, Tortoise ORM model definitions, eliminating hand-written loaderspydantic_resolve.integration.sqlalchemy(pip install pydantic-resolve[sqlalchemy])pydantic_resolve.integration.django(pip install pydantic-resolve[django])pydantic_resolve.integration.tortoise(pip install pydantic-resolve[tortoise])
- Each ORM adapter supports: Many-to-One, One-to-Many, One-to-One, Reverse One-to-One, Many-to-Many
- Generated loaders leverage
_query_metafor field projection (load_only/only) - Per-mapping
filtersanddefault_filterfor query filtering - DTO required-field validation against ORM scalar fields at setup time
Mappingdescriptor (integration.mapping): unified ORM-to-DTO mapping descriptorErDiagram.add_relationship(): merge ORM-generated entities into existing ErDiagram, with duplicate detection by relationship/query/mutation name- Built-in GraphiQL page helper: export
get_graphiql_html()frompydantic_resolve.graphqland addGraphQLHandler.get_graphiql_html()for serving a ready-to-use GraphiQL IDE alongside the GraphQL endpoint - MCP full-schema tool: multi-app MCP servers now expose
get_full_schema(app_name, response_type='sdl'|'introspection')to fetch the entire schema in one call - ORM-first GraphQL response validation: dynamic response models now preserve entity
from_attributes=Trueor enable it viaenable_from_attribute_in_type_adapter, and relationship fields use a syntheticvalidation_aliasto avoid premature lazy-loading of ORM relationships beforeAutoLoadruns - Forward-ref module path syntax:
base_entity()now resolves relationship targets written as'package.module:ClassName'andlist['package.module:ClassName'], reducing same-module ordering constraints - break:
ErDiagram.configs→ErDiagram.entities: parameter renamed; all internal consumers updated
v4.1
v4.1.0 (2026-4-2)
- feat:
- MCP dependencies are now optional:
fastmcpmoved from core dependencies to[project.optional-dependencies]undermcpgroup. Install viapip install pydantic-resolve[mcp]. Core functionality (Resolver, GraphQL, ERD) no longer pulls infastmcp. - MCP imports in
pydantic_resolve/__init__.pynow usetry/exceptfor graceful degradation whenfastmcpis not installed. - fix:
- Remove misleading
config_global_resolvercalls from MCP module docstring examples (__init__.py,server.py). MCP internally usesconfig_resolverviaGraphQLHandlerfor proper isolation.
v4.0
v4.0.1 (2026-4-1)
- fix:
- Auto-added FK fields no longer leak into GraphQL response:
ResponseBuilder._add_fk_fields()now usesField(exclude=True)instead of..., so fields likeidthat are auto-added forAutoLoadresolution are excluded frommodel_dump()serialization while remaining accessible as attributes - Support scalar target relationships in GraphQL:
_build_relationship_field()now handles scalar targets (e.g.,str,int) via_is_scalar_relationship()check, skipping recursive model building for non-BaseModel targets - Allow scalar relationship fields without sub-selections:
_add_relationship_fields()now permits scalar relationship fields to be included even when no sub-fields are selected
v4.0.0 (2026-3-29)
BREAKING CHANGES — ER Diagram API overhaul, simplified Relationship definition. See Migration Guide for details.
- feat:
Relationshipparameter renames:field→fk,target_kls→targetRelationship.namereplacesdefault_field_name: each Relationship must declare a uniquename, used as GraphQL field name and AutoLoad lookup keyRelationship.fk_fnreplacesfield_fnRelationship.fk_none_default/fk_none_default_factoryreplacefield_none_default/field_none_default_factoryAutoLoadreplacesLoadBy: AutoLoad no longer requires FK field name; usesoriginparameter to match by relationship name, defaults to field name- Remove
MultipleRelationshipandLink: multiple relationships to the same target are now separateRelationshipentries with independentname - Remove deprecated
Resolverparameters:loader_filtersandglobal_loader_filter(deprecated since v1.9.3) are removed; useloader_paramsandglobal_loader_param -
_resolve_refsearches registered entities: when module attribute lookup fails, falls back to searching registered entity list, resolving same-module class ordering issues -
refactor:
ResponseBuilderremovesRelationshipInfowrapper, usesRelationshipdirectlyDefineSubsetmodifier logic extracted into_apply_config_modifiers_to_fieldDefineSubsetauto-adds missing AutoLoad FK fields withexclude=True- SDL / Introspection generators unified on
rel.nameandrel.target, allMultipleRelationshipbranches removed - Removed
__pydantic_resolve_relationships__attribute name; use__relationships__only - Added type compatibility check in
ErLoaderPreGenerator.prepare()for early mismatch detection
v3.3
3.3.0 (2026-3-27)
- feat:
- migrate from mcp to fastmcp ver 3
v3.2
v3.2.3 (2026-3-24)
- feat:
- GraphQL hides relationship fields without loaders:
Relationshipfields withloader=Noneare now hidden from GraphQL SDL and introspection, preventing runtime errors when querying unresolvable fields - Applies to both
SDLBuilderandIntrospectionGenerator - test:
- Add
TestHideRelationshipsWithoutLoadertest class intests/graphql/test_sdl_builder.py
v3.2.2 (2026-3-23)
- fix:
- GraphQL datetime serialization: Changed
model_dump(by_alias=True)tomodel_dump(mode='json', by_alias=True)in executor to ensure datetime, date, time, Decimal, and other non-JSON types are properly serialized to JSON-compatible formats - Before:
TypeError: Object of type datetime is not JSON serializable - After: datetime fields are automatically serialized to ISO-8601 strings
- test:
- Add
tests/graphql/test_datetime_support.pyfor datetime, date, time, Decimal serialization
v3.2.1 (2026-3-22)
- fix:
- GraphQL introspection now includes
Relationship.target_klstypes:IntrospectionGenerator._collect_all_typesnow collects types fromRelationship.target_klswhen the target type is not explicitly registered iner_diagram.configs, ensuring consistency with SDL generation - Before: Introspection types list was missing types referenced in relationships, causing field type references to point to undefined types
- After: Both SDL and introspection include the same types, field type references are always valid
- test:
- Add
tests/graphql/test_missing_target_type.pyfor introspection/SDL consistency - Add
tests/graphql/test_forward_ref_resolution.pyfor string reference resolution in__relationships__
v3.2.0 (2026-3-19)
- feat:
- DataLoader context injection: Class-type DataLoaders can now declare a
_contextattribute to access Resolver's global context - Early validation: Raises
LoaderContextNotProvidedErrorif a DataLoader requires context but Resolver doesn't provide one - Useful for permission filtering scenarios where
user_idneeds to be passed to loaders - Example:
v3.1
v3.1.1 (2026-3-18)
- feat:
- Auto-add missing AutoLoad FK fields in DefineSubset: When using
AutoLoadannotation inDefineSubset, the referenced FK field (e.g.,user_idinAutoLoad('user_id')) is now automatically added withexclude=Trueif not explicitly defined in the subset - Early validation for invalid FK references: If
AutoLoadreferences a field that doesn't exist in the parent class, aValueErroris raised at class definition time instead of duringresolve()
- Auto-add missing AutoLoad FK fields in DefineSubset: When using
v3.1.0 (2026-3-16)
- feature:
- add MCP support based on ER diagram, add query/mutation decorator
v3.0
v3.0.7 (2026-3-5)
- perf:
- Two-level METADATA_CACHE with resolver_class isolation: Cache structure changed from
METADATA_CACHE[root_class]toMETADATA_CACHE[id(resolver_class)][root_class], isolating caches for different resolver configurations (created viaconfig_resolver) - Pre-analysis in ResponseBuilder: Dynamic response models are now pre-analyzed immediately after creation in
ResponseBuilder._create_model(), avoiding repeated analysis inResolver.resolve() - Concurrent query execution in GraphQL executor: Moved
query_methodexecution from Phase 1 (serial) to Phase 2 (concurrent), enabling parallel I/O operations for multiple root queries - Before: Phase 1 executes query_methods serially → Phase 2 resolves concurrently
- After: Phase 1 builds models only → Phase 2 executes (query_method + transform + resolve) concurrently
v3.0.6 (2026-3-3)
- feat:
- GraphQL schema now includes
Relationship.target_klstypes: Pydantic types referenced in relationships are automatically collected and generated as GraphQL types, even if not explicitly registered iner_diagram.configs - Supports
Relationshipwithlist[T]generics andload_many=True
v3.0.5 (2026-3-2)
- feat:
- Enum support for GraphQL: Full enum type support across SDL generation, introspection, query execution, and mutation input
- Enum fields now serialize to enum name (e.g.,
"ADMIN") conforming to GraphQL convention - Enum default values in introspection formatted correctly (e.g.,
"USER"instead of"UserRole.USER") - Mutation arguments accept enum names and convert to Python enum members
- refactor:
- Enum serialization optimization: Replaced recursive
_convert_enum_to_namepost-processing with PydanticPlainSerializerfor better performance - Extracted
_add_enum_definitions()helper in SDL generator to reduce code duplication - Fixed
_format_default_value()return type and moved Enum import to module level in introspection generator - Added enum handling in
_map_python_type_to_gql_for_input()for input types
v3.0.4 (2026-3-1)
- feat:
- add LRU cache for GraphQL response model generation in
ResponseBuilder FieldSelectionnow implements__hash__and__eq__to support caching (arguments excluded from comparison)- same query structure with different arguments will hit cache, improving performance
- cache size: 256 entries with LRU eviction
v3.0.3 (2026-3-1)
- refactor:
GraphQLHandlernow creates diagram-specific resolver internally usingconfig_resolver, removingresolver_classparameter- ensures
AutoLoadannotations work without requiringconfig_global_resolver()to be called - convert all relative imports to absolute imports in
pydantic_resolve/directory
v3.0.2 (2026-3-1)
- fix:
- fix introspection for scalar return types (bool, int, float, str) in mutations
v3.0.1 (2026-2-28)
- refactor:
- graphql interface
v3.0.0 (2026-2-27)
- add support for auto-generating graphql interface for ERD.
v2.5
v2.5.0 (2026-2-21)
- stable release
v2.5.0alpha2
- refactor:
- serialization decorator: Use
json_schema_extramechanism instead of monkey-patching- Collects all nested Pydantic types at decoration time via
_collect_nested_types - Sets
json_schema_extraon root class and all nested types automatically - Respects existing configurations (skips types that already have
json_schema_extra) - Removes ~100 lines of code (
_process_schema,_process_nested_type,_process_reference) - File:
pydantic_resolve/utils/openapi.py
- Collects all nested Pydantic types at decoration time via
v2.5.0alpha1
- feat:
- NEW:
@serializationdecorator for recursive JSON schema processing- No-parameter decorator, use
@serializationdirectly - Example:
- No-parameter decorator, use
v2.5.0alpha
- test:
-
Add edge case tests for Pydantic model resolution and collector handling
- Empty classes, missing fields, collector validation, expose conflicts
- Self-reference and circular reference handling
- Inheritance chain testing
- File:
tests/analysis/test_analysis_edge_cases.py
-
refactor:
-
loader management: Enhance loader classes and validation with new architecture
- Improved loader instance creation and validation
- Better error messages for missing or invalid loader configurations
- File:
pydantic_resolve/loader_manager.py
-
ContextVar optimization: Optimize ancestor and collector management
- Use single dict-based ContextVar instead of multiple ContextVars
- Reduces context variable overhead from N+1 to 1 per category
- Pre-create parent ContextVar to avoid repeated creation
- File:
pydantic_resolve/resolver.py
-
doc:
-
Add Entity-First architecture discussion
- Comprehensive documentation on Entity-First design pattern
- Examples of data assembly with automatic resolver
- GraphQL-inspired concepts for FastAPI + Pydantic
- Files:
docs/fastapi-pydantic-architecture-outline.mddocs/fastapi-pydantic-architecture-outline.en.md
-
Update README with detailed Pydantic response schemas and examples
- Clarify ORM and Pydantic schema relationship
v2.4
v2.4.7 (2026-1-29)
- refactor:
- use modern type annotation
- logger
- analysis.py for better readibility
- improve doc, add more details about ErDiagram