# 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.0

### 5.0.0 (2026-4-3)

**BREAKING CHANGES** — `ErDiagram.configs` renamed to `ErDiagram.entities`. See [Migration Guide](./migration.md) for details.

- feat:
  - **ORM relationship auto-discovery**: new `pydantic_resolve/integration` module generates `Relationship` + `DataLoader` from SQLAlchemy, Django, Tortoise ORM model definitions, eliminating hand-written loaders
    - `pydantic_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_meta` for field projection (`load_only` / `only`)
  - Per-mapping `filters` and `default_filter` for query filtering
  - DTO required-field validation against ORM scalar fields at setup time
  - **`Mapping` dataclass** (`integration.mapping`): unified ORM-to-DTO mapping descriptor
  - **`ErDiagram.add_relationship()`**: merge ORM-generated entities into existing ErDiagram, with duplicate detection by relationship/query/mutation name
- 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**: `fastmcp` moved from core dependencies to `[project.optional-dependencies]` under `mcp` group. Install via `pip install pydantic-resolve[mcp]`. Core functionality (Resolver, GraphQL, ERD) no longer pulls in `fastmcp`.
  - MCP imports in `pydantic_resolve/__init__.py` now use `try/except` for graceful degradation when `fastmcp` is not installed.
- fix:
  - Remove misleading `config_global_resolver` calls from MCP module docstring examples (`__init__.py`, `server.py`). MCP internally uses `config_resolver` via `GraphQLHandler` for 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 uses `Field(exclude=True)` instead of `...`, so fields like `id` that are auto-added for `AutoLoad` resolution are excluded from `model_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](./migration.md) for details.

- feat:
  - **`Relationship` parameter renames**: `field` → `fk`, `target_kls` → `target`
  - **`Relationship.name` replaces `default_field_name`**: each Relationship must declare a unique `name`, used as GraphQL field name and AutoLoad lookup key
  - **`Relationship.fk_fn` replaces `field_fn`**
  - **`Relationship.fk_none_default` / `fk_none_default_factory` replace `field_none_default` / `field_none_default_factory`**
  - **`AutoLoad` replaces `LoadBy`**: AutoLoad no longer requires FK field name; uses `origin` parameter to match by relationship name, defaults to field name
  - **Remove `MultipleRelationship` and `Link`**: multiple relationships to the same target are now separate `Relationship` entries with independent `name`
  - **Remove deprecated `Resolver` parameters**: `loader_filters` and `global_loader_filter` (deprecated since v1.9.3) are removed; use `loader_params` and `global_loader_param`
  - **`_resolve_ref` searches registered entities**: when module attribute lookup fails, falls back to searching registered entity list, resolving same-module class ordering issues

- refactor:
  - `ResponseBuilder` removes `RelationshipInfo` wrapper, uses `Relationship` directly
  - `DefineSubset` modifier logic extracted into `_apply_config_modifiers_to_field`
  - `DefineSubset` auto-adds missing AutoLoad FK fields with `exclude=True`
  - SDL / Introspection generators unified on `rel.name` and `rel.target`, all `MultipleRelationship` branches 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**: `Relationship` fields with `loader=None` are now hidden from GraphQL SDL and introspection, preventing runtime errors when querying unresolvable fields
  - Applies to both `SDLBuilder` and `IntrospectionGenerator`
- test:
  - Add `TestHideRelationshipsWithoutLoader` test class in `tests/graphql/test_sdl_builder.py`

### v3.2.2 (2026-3-23)
- fix:
  - **GraphQL datetime serialization**: Changed `model_dump(by_alias=True)` to `model_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.py` for datetime, date, time, Decimal serialization

### v3.2.1 (2026-3-22)
- fix:
  - **GraphQL introspection now includes `Relationship.target_kls` types**: `IntrospectionGenerator._collect_all_types` now collects types from `Relationship.target_kls` when the target type is not explicitly registered in `er_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.py` for introspection/SDL consistency
  - Add `tests/graphql/test_forward_ref_resolution.py` for string reference resolution in `__relationships__`


### v3.2.0 (2026-3-19)
- feat:
  - **DataLoader context injection**: Class-type DataLoaders can now declare a `_context` attribute to access Resolver's global context
  - Early validation: Raises `LoaderContextNotProvidedError` if a DataLoader requires context but Resolver doesn't provide one
  - Useful for permission filtering scenarios where `user_id` needs to be passed to loaders
  - Example:
    ```python
    class UserLoader(DataLoader):
        _context: dict  # Declare context requirement

        async def batch_load_fn(self, keys):
            user_id = self._context.get('user_id')
            # Use user_id for permission filtering
            ...

    # Resolver automatically injects context
    resolver = Resolver(context={'user_id': 123})
    ```

## v3.1

### v3.1.1 (2026-3-18)
- feat:
    - **Auto-add missing AutoLoad FK fields in DefineSubset**: When using `AutoLoad` annotation in `DefineSubset`, the referenced FK field (e.g., `user_id` in `AutoLoad('user_id')`) is now automatically added with `exclude=True` if not explicitly defined in the subset
    - **Early validation for invalid FK references**: If `AutoLoad` references a field that doesn't exist in the parent class, a `ValueError` is raised at class definition time instead of during `resolve()`

### 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]` to `METADATA_CACHE[id(resolver_class)][root_class]`, isolating caches for different resolver configurations (created via `config_resolver`)
  - **Pre-analysis in ResponseBuilder**: Dynamic response models are now pre-analyzed immediately after creation in `ResponseBuilder._create_model()`, avoiding repeated analysis in `Resolver.resolve()`
  - **Concurrent query execution in GraphQL executor**: Moved `query_method` execution 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_kls` types**: Pydantic types referenced in relationships are automatically collected and generated as GraphQL types, even if not explicitly registered in `er_diagram.configs`
  - Supports `Relationship` with `list[T]` generics and `load_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_name` post-processing with Pydantic `PlainSerializer` for 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`
  - `FieldSelection` now 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:
  - `GraphQLHandler` now creates diagram-specific resolver internally using `config_resolver`, removing `resolver_class` parameter
  - ensures `AutoLoad` annotations work without requiring `config_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_extra` mechanism instead of monkey-patching
    - Collects all nested Pydantic types at decoration time via `_collect_nested_types`
    - Sets `json_schema_extra` on 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`

### v2.5.0alpha1

- feat:
  - **NEW**: `@serialization` decorator for recursive JSON schema processing
    - No-parameter decorator, use `@serialization` directly
    - Example:
      ```python
      @serialization
      class Person(BaseModel):
          name: str = ''
          address: Address | None = None

      schema = Person.model_json_schema(mode='serialization')
      # All nested models (Address) will have required fields set correctly
      ```

### 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.md`
      - `docs/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
   

### v2.4.6 (2026-1-28)

- feat:
    - `_resolve_ref` now supports module path syntax `'module.path:ClassName'` for lazy import, solving circular import issues in ER diagrams
    - Use `target='path.to.module:ClassName'` or `list['path.to.module:ClassName']` to reference classes across modules without direct imports
- chore:
    - add pytest-benchmark


### v2.4.5 (2026-1-26)

- fix:
    - ensure `model_config` correctly handles inheritance by using `model.model_fields` instead of `kls.model_fields` to capture all fields from child classes

### v2.4.4 (2026-1-19)

- fix:
    - fix `is_compatible_type` to properly handle PEP 604 union types (`User | None`) by using identity check instead of string representation

### v2.4.3 (2026-1-3)

- fix:
    - fix dict_keys in generating openapi.json

### v2.4.2 (2026-1-3) - yanked
- fix:
    - model_config should convert all fields into required status (for serialization)
- enhancement:
    - friendly error message in er diagram module

### v2.4.1 (2025-12-26)

- feat:
    - SubsetConfig's fields now support "all" to select all fields


### v2.4.0 (2025-12-26)

```python
class NewSub(DefineSubset):
    __subset__ = SubsetConfig(
        kls=Parent,
        fields=['id', 'name', 'age'],
        exclude_fields=['name'],  # Field(exclude=True)
        expose_as=[('name', 'custom_name')],
        send_to=[
            ('age', 'age_collector'),
            ('age', ('a', 'b')) 
        ],
    )
```
        
- feat:
    - better subset configuration
- refactor:
    - move loader_manager into seperate module
- doc:
    - add more comments 

## v2.3

### v2.3.3

hotfix:

```shell
  File "/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 75, in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 334, in app
    content = await serialize_response(
              ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/fastapi/routing.py", line 188, in serialize_response
    return field.serialize(
           ^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/fastapi/_compat.py", line 152, in serialize
    return self._type_adapter.dump_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.12/site-packages/pydantic/type_adapter.py", line 572, in dump_python
    return self.serializer.to_python(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'mappingproxy' object cannot be converted to 'PyDict'
```

### v2.3.2
- optimization:
    - provide proper type annotation for `base_entity` method
- refactor:
    - clean up unused files

### v2.3.1
- feat:
    - add `__subset__` short name for `__pydantic_resolve_subset__`

### v2.3.0

- feat: 
    - add `SendTo` and `ExposeAs` to simplify / replace `__pydantic_resolve_collect__` and `__pydantic_resolve_expose__`, read: test_52 and test_53 for more details
    - add `__relationships__` short name for `__pydantic_resolve_relationships__`
- fix: clean print

## v2.2

### v2.2.4 (2025.12.8)

fix: ignore inherited descendant in inline relationship definition

more details in test case: `tests/er_diagram/test_er_diagram_base.py`


### v2.2.3 (2025.12.7)

feat: for ininline relationships str are allowed to represent the class name

for example, use `target='User'` if User is defined after current class

```python
class Biz(BaseModel, BaseEntity):
    __pydantic_resolve_relationships__ = [
        Relationship(fk='user_id', target='User', loader=UserLoader),
        Relationship(fk='id', target=list[Bar], fk_none_default_factory=list, loader=BarLoader),
    ]
    id: Optional[int]
    name: str
    user_id: Optional[int]

class User(BaseModel):
    ...
```

Only use this in inline mode.

### v2.2.2 (2025.12.5)

feat: add origin_kls in AutoLoad to avoid blurring during matching loader

### v2.2.1 (2025.12.5)

feat: add field_fn in er diagram, receive callable object to process key before loader.load

### v2.2.0 (2025.12.5)

feat: relationship can be defined inside class
```python
class Biz(BaseModel, BaseEntity):
    __pydantic_resolve_relationships__ = [
        Relationship(fk='user_id', target=User, loader=UserLoader),
        Relationship(fk='id', target=list[Bar], fk_none_default_factory=list, loader=BarLoader),
    ]
    id: Optional[int]
    name: str
    user_id: Optional[int]

MyResolver = config_resolver('MyResolver', er_diagram=BaseEntity.get_diagram())
# or
# config_global_resolver(er_diagram=BaseEntity.get_diagram())
```

## v2.1

### v2.1.0 (2025.11.29)
- change: 
    - inside ErDiagram, `biz` of Relationship is moved into Link, and is not Opitonal anymore
- feature:
    - add MultipleRelationship for scenarios having mulitple links between entity and target_entity.

## v2.0

### v2.0.1 (2025.11.26)
- rename `ErConfig` to `Entity` (caution: will break current code)

### v2.0.0 (2025.11.26)
- release

### v2.0.0a5 (2025.11.25)
- feat:
    - support load_many in Relationship

### v2.0.0a4 (2025.11.23)
- refactor:
    - optimize ErPreGenerator

### v2.0.0a3 (2025.11.19)
- fix:
    - DefineSubset lost resolve/post methods and `__pydantic_resolve_xxx__` configs

### v2.0.0a2 (2025.11.17)
- fix:
    - raise ValueError if er_diagram are not provided when AutoLoad are used in somewhere
    - relationship.loader is optional, but required if used in AutoLoad 

### v2.0.0a1 (2025.11.16)
- features:
    - add `er_diagram` to define application level entity relationships.
    - add `config_resolver` and `config_global_resolver` to bind Resolver with er_diagram.
    - a new version of subset: `DefineSubset`.
- enhancements:
    - caching metadata generated during analysis process
- breaks:
    - remove support of pydantic v1 and dataclass
    - 3.10 now is the minimal requirement

## v1.13

### v1.13.5 (2025.10.23)

- add annotation param in Resolver to specify the root class when resolving list of Union types.
    - refer to `tests/common/test_annotation_param.py`
- support type alias: `type U = A | B` 

### v1.13.4 (2025.10.14)

- fix memory leak due inner function closure

### v1.13.3 (2025.10.13)

- fix memory leak due to haven't properly reset contextvars.ContextVar

### v1.13.2 (2025.9.4)

- minor: @ensure_subset(base_kls) will attach kls info into target class, prepare for dependency analysis in future

### v1.13.1 (2025.8.28)

- fix: add support for UnionType such as `A | B`
- update: upgrade python version to 3.10 in ci

### v1.13.0 (2025.8.27)

feature:

- add support for resolving Union[A, B]
  - **known issue**: for pydantic v1, use it in caution, union_smart not yet works with `parse_obj_as`, see `tests/pydantic_v1/resolver/test_43_union_bad.py`
  - it fully support pydantic v2
  - prefer to return pydantic/dataclass object instead of dict

## v1.12

### v1.12.5 (2025.7.24)

feature:

- add short name `Loader` for `LoaderDepend`
- set Loader return type as DataLoader

non-functional:

- add more tests
- rename internal variable names

### v1.12.4 (2025.7.12)

update python versions in pyproject.toml

### v1.12.3 (2025.6.29)

enhancement the support to dataclass

- update model_config decorator to also support dataclass.
- return annotation now supports `Union[T, None]` for `sys.version_info > (3.7)`
- ensure_subset support dataclass

### v1.12.2 (2025.6.20)

fix:

if DataLoader fields already has default value, Resolver.loader_params can skip them and will not raise `LoaderFieldNotProvidedError` any more.

```python
class LoaderA(DataLoader):
    power: int = 2
    async def batch_load_fn(self, keys: List[int]):
        return [ k** self.power for k in keys ]

class A(BaseModel):
    val: int

    a: int = 0
    def resolve_a(self, loader=LoaderDepend(LoaderA)):
        return loader.load(self.val)

@pytest.mark.asyncio
async def test_case():
    """
    default param allow not setting loader param in Resolver
    """
    data = [A(val=n) for n in range(3)] # 0, 1, 2 => 0, 1, 4
    data = await Resolver().resolve(data)
    assert data[2].a == 4

@pytest.mark.asyncio
async def test_case_2():
    """
    default param can be overridden in Resolver
    """
    data = [A(val=n) for n in range(3)] # 0, 1, 2 => 0, 1, 4
    data = await Resolver(
        loader_params={
            LoaderA: {'power': 3}  # override default power
        }
    ).resolve(data)
    assert data[2].a == 8
```

### v1.12.1 (2025.6.1)

feature:

inside dataloader object, we can read the request return types / request fiels from `_query_meta` field.

```python
class SampleLoader(DataLoader):
    async def batch_load_fn(self, keys):
        print(self._query_meta['fields']) # => ['id', 'name']
        print(self._query_meta['request_types']) # => [ {'name': Student, 'fields': ['id', 'name'] } ]

        data = await query_students(self._query_meta['fields'], keys)
        # select id, name from xxxxx

        return build_list(data, keys, lambda d: d.id)

class Student(BaseModel):
    id: int
    name: str

class ClassRoom(BaseModel):
    id: int
    name: str

    students: List[Student] = []
    def resolve_students(self, loader=LoaderDepend(SampleLoader)):
        return loader.load(self.id)
```

### v1.12.0 (2025.5.24)

optimization:

improve the algorithm of analysising object_fields, it helps skip nodes that don't need processing.
using a back populate approach, now it can handle loop scenaio.

refer:

- `tests/common/test_analysis_object_fields.py` 1,2,3,4

```
┌────────┐
│   Kls  │
└────────┘
    │       ┌─────────────────┐
    │       │ ┌──────────┐    │
    │       ▼ ▼          │    │
    │     ┌─────┐        x(o) o
    ├─o───│ Tree│────────┘    │
    │     └─────┘─────────────┘
    │
    │
    │         ┌───────┐
    │         │       │
    │         ▼       x
    │     ┌──────┐    │
    ├─x───│ Tree2│ ───┘
    │     └──────┘
    │
    │         ┌─────────o─────────┐
    │         │                   │
    │         ▼                   │
    │     ┌───────┐           ┌───────┐
    ├──o──│ Tree31│─────x────►│Tree32 │
    │     └───────┘           └───────┘
    │
    │         ┌───────┐
    │         │       │
    │         ▼       o
    │     ┌──────┐    │
    └─x───│ Tree4│ ───┘
          └──────┘
```

## v1.11

### v1.11.10 (2025.5.12)

bug:

optimize the hit logic during analysising the class

fix a corner case of wrongly marking a class as 'no resolve'

### v1.11.9 (2025.5.7)

bug:

details in testcase: `test/common/test_long_distance_resolve.py`

before ver: 1.11.8
d, or e will be removed from the object_fields
it is due to the \_has_config function do not verify the len of
object_fields.

but in fact, if a kls is cached, it should not take party in judging
`should_treverse`, so from 1.11.9 it will return None

for self reference data structure, object fields will always be kept

details in test case `test/common/test_long_distance_resolve_recursive.py`

### v1.11.8 (2025.3.31)

bug:

- [pydantic v2] fix `adapter.validate_python` raise exception when parse from another pydantic object (which is not exactly the same with target class)

### v1.11.7 (2025.3.21)

feat:

- optimize the output of debug (profile)

### v1.11.6 (2025.3.19)

bug:

- fix debug generate multiple logging due to add handler multiple times in constructor.

### v1.11.5 (2025.3.19)

bug:

- post method should not further resolve the target, it will cause the lost of ancestor using with collector
- and this behavior also conflicts with the purpose of data adjustment and re-organization only

### v1.11.4 (2025.3.19)

feature:

- add debug field to display time consuming of each node.

### v1.11.3 (2024.12.12)

- if an object field and its descendants does not has pydantic-resolve related configs, it will be skipped during the traversal.

### v1.11.2 (2024.11.22)

- return value from post method will also be recursively traversed and resolved.
- add dataloader params in post method

### v1.11.1 (2024.11.21)

- refactor fold structure
- new documentation

### v1.11.0 (2024.10.24)

- support pydantic v2 (archive lib pydantic2-resolve)
- remove hidden_fields in model_config

## v1.10

### v1.10.8 (2024.8.27)

- collector suppor tuple for value

```python
class Item(BaseModel):
    __pydantic_resolve_collect__ = {('field_a', 'field_b'): ('collector_a', 'collector_b')}
```

this means `(Item.field_a, Item.field_b)` will be sent to both collector_a and collector_b

### v1.10.7 (2024.8.16)

- collector support tuple

```python
class Item(BaseModel):
    __pydantic_resolve_collect__ = {('field_a', 'field_b'): 'collector_name'}
```

### v1.10.6 (2024.8.12)

- collector now can collect from multiple sources

### v1.10.5 (2024.5.20)

- post method can be async function

### v1.10.4 (2024.4.11)

- new feature: reading parent node in resolve, post and post_default_handler

### v1.10.3 (2024.4.8)

- bugfix, handle TypeError from issubclass, https://github.com/allmonday/pydantic2-resolve/issues/7

### v1.10.2 (2024.3.21)

- internal optimization:
  - no more inspect.signature in runtime

### v1.10.1 (2024.3.17)

- fix collect intermediate level error
- internal refactor
  - better metadata lookup

### ~~v1.10.0 (2024.3.16)~~

- add new feature: `collector`
- internal refactor
  - optimize scan process.
  - prepare for debug feature.
  - create dataloader instance before resolving

## v1.9

### v1.9.3 (2024.03.12)

- rename loader_filter -> loader_params
- rename global_loader_filter -> global_loader_param

using old params will have warning messages.

### v1.9.2 (2024.02.03)

- bugfix for model_config
  - setting `__exclude_fields__` should not put in schema_extra.

### v1.9.1 (2024.02.02)

- add new decorator `model_config`:
  - control hidden fields (for schema() and dict())
  - built-in required in schema

### v1.9.0 (2024.01.22)

- add `global_loader_filter` for convinence. (thanks Dambre)

## v1.8

### v1.8.2 (2023.12.20)

- fix corner case of empty list input. `tests/core/test_input.py`

### v1.8.1 (2023.12.16)

- fix scan exceptions caused from pydantic `.type_` value of `List[Optional[T]]`

### v1.8.0 (2023.12.15)

- internal refactor: performance improvement
- remove Resolver.annotation_class, it will be processed automatically
- new helper function `copy_dataloader_kls` to generate a copy of DataLoader
- `pydantic_resolve.utils` provide several internal `generate_loader` functions.

## v1.7

### v1.7.2 (2023.11.16)

- fix overwriting BaseModel.Config.schema_extra in `@input`

### v1.7.1 (2023.10.19)

- fix raising error when `__pydantic_resolve_exposed__` field value is None

```python
class Bar(BaseModel):
    __pydantic_resolve_expose__ = {'num': 'bar_num'}  # expose {'bar_num': val }

    num: Optional[int]  # may be None
```

### v1.7.0 (2023.9.2)

- add a port in ancestor class to expose value of specific field to its descendants.
- resolve_field and post_field of descendant class can read those from `ancestor_context`
- DO NOT EXPOSE resolver_fields (eg Bar.kars, Kar.desc), it is empty by default.

```python
class Kar(BaseModel):
    name: str

    desc: str = ''
    def resolve_desc(self, ancestor_context):
        return f"{self.name} - {ancestor_context['bar_num']}"  # read ancestor value from 'ancestor_context'


class Bar(BaseModel):
    __pydantic_resolve_expose__ = {'num': 'bar_num'}  # expose {'bar_num': val }

    num: int

    kars: List[Kar] = []
    def resolve_kars(self):
        return [{'name': n} for n in ['a', 'b', 'c']]


class Foo(BaseModel):
    nums:List[int]
    bars: List[Bar] = []

    def resolve_bars(self):
        return [{'num': n} for n in self.nums]

```

## v1.6

### v1.6.5 (2023.8.12)

- `post_field` method and `post_default_handler` now can read context (setting in Resolver)

### v1.6.4 (2023.8.7)

- some inside refactor

### v1.6.3 (2023.7.20)

- fix dataclass exception under lazy annotation. refer to `tests/resolver/test_28_parse_to_obj_for_dataclass_with_annotation.py`
- provide extra tip on auto map fails.

> recursion dataclass type is still not supported, refer to `test/resolver/test_26_tree.py`, L81

### v1.6.2 (2023.7.12)

- fix `output` minor bug

### v1.6.1 (2023.7.11)

- fix `output` decorator, it will modify schema_json's `required` field and fill all fields

### v1.6.0 (2023.7.11)

- add `Resolver.context` param
- remove `core.resolve`
- change `post_method`, return value will be assined to target field.
- add `output` decorator
- add `post_default_handler` for spacial use.

## v1.5

### v1.5.2 (2023.7.10)

- fix pydantic annotation related minor issue

### v1.5.1 (2023.7.9)

- fix the order of auto map, which will break the resolve chain

### ~~v1.5.0 (2023.7.9)~~

- new feature. the return value from resolve_method will be automatically converted to the type of target field, which means mapper with params of type is not required any more.
- if you `fromt __future__ import annotations` at top, make sure you define schemas in global scopes.

```python
    class Book(BaseModel):
        name: str

    async def batch_load_fn(keys):
        books = [[bb for bb in BOOKS.get(k, [])] for k in keys]
        return books

    class Student(BaseModel):
        id: int
        name: str

        books: List[Book] = []
        def resolve_books(self, loader=LoaderDepend(batch_load_fn)):
            return loader.load(self.id)  # auto convert dict into Book type

    class ClassRoom(BaseModel):
        students: List[Student]


    students = [Student(id=1, name="jack"), Student(id=2, name="mike"), Student(id=3, name="wiki")]
    classroom = ClassRoom(students=students)
    classroom = await Resolver().resolve(classroom)
    assert isinstance(classroom.students[0].books[0], Book)
```

- recommend: using `mapper` with lambda only

## v1.4

### v1.4.1 (2023.7.6)

- minor optimization, iteration of object attributes.

### v1.4.0 (2023.7.6)

- support resolve through objects which intermediate items has not `resolve_` methods.

more detail: `tests/resolver/test_21_not_stop_by_idle_level.py`

```python
class C(BaseModel):
    name: str = ''

class B(BaseModel):
    name: str
    c: Optional[C] = None
    async def resolve_c(self) -> Optional[C]:
        await asyncio.sleep(1)
        return C(name='hello world')

class A(BaseModel):
    b: B

class Z(BaseModel):
    a: A
    resolve_age: int

@pytest.mark.asyncio
async def test_resolve_object():
    s = Z(a=A(b=B(name="kikodo")), resolve_age=21)  # resolve starts from B

    result = await Resolver().resolve(s)
```

## v1.3

### v1.3.2 (2023.7.4)

- add subset check decorator `ensure_subset`.

```python
    class Base(BaseModel):
        a: str
        b: int

    @util.ensure_subset(Base)
    class ChildA(BaseModel):
        a: str

    @util.ensure_subset(Base)
    class ChildB(BaseModel):
        a: str
        c: int = 0
        def resolve_c(self):
            return 21

    @util.ensure_subset(Base)
    class ChildB(BaseModel):
        a: int  # raise attribute error
```

### v1.3.1 (2023.7.3)

- support `auto-mapping` from pydantic to pydantic and fix some testcases.

more detail: `tests/resolver/test_16_mapper.py`

- test_mapper_6
- test_mapper_7

### v1.3.0 (2023.6.27)

- add `Resolver.loader_instances` param, user can create loader before Resolver and this loader will be used inside. for example: you can prime value and to avoid extra query.

```python
loader = FriendLoader()
loader.prime('tangkikodo', ['tom', 'jerry'])
loader.prime('john', ['mike', 'wallace'])
result = await Resolver(loader_instances={FriendLoader: loader}).resolve(root)
# batch_load_fn will not run.
```

more detail: `tests/resolver/test_20_loader_instance.py`

## v1.2

### v1.2.2 (2023.6.21)

- minor adjustment, `build_list` and `build_object` will return iterator instead of list.

### v1.2.1 (2023.6.19)

- fix, modify `post_fieldname` execution position, reduce the duplication.

### v1.2.0 (2023.6.18)

- add `post_fieldname` method, it will be called after the object is fully resolve as a hook, developer can run some aggregation computation. `tests/resolver/test_18_post_methods.py`

```python
class Friend(BaseModel):
    name: str

    cash: Optional[Cash] = None
    @mapper(Cash)  # auto mapping
    def resolve_cash(self, contact_loader=LoaderDepend(cash_batch_load_fn)):
        return contact_loader.load(self.name)

    has_cash: bool = False
    def post_has_cash(self):    # <----------------------
        self.has_cash = self.cash is not None

class User(BaseModel):
    name: str
    age: int

    friends: List[Friend] = []
    @mapper(lambda names: [Friend(name=name) for name in names])
    def resolve_friends(self, friend_loader=LoaderDepend(friends_batch_load_fn)):
        return friend_loader.load(self.name)

    has_cash: bool = False
    def post_has_cash(self):    # <----------------------
        self.has_cash = any([f.has_cash for f in self.friends])
```

## v1.1

### v1.1.1 (2023.6.17)

- extend @mapper decorator with target class option, this will call auto_mapping function inside.

```python
comments: List[CommentSchema]  = []
@mapper(CommentSchema)
def resolve_comments(self, loader=LoaderDepend(CommentLoader)):
    return loader.load(self.id)
```

### v1.1.0 (2023.6.16)

- add @mapper decorator, to enable custom data transform

```python
comments: List[CommentSchema]  = []
@mapper(lambda items: [CommentSchema.from_orm(item) for item in items])
def resolve_comments(self, loader=LoaderDepend(CommentLoader)):
    return loader.load(self.id)
```

## v1.0

### v1.0.0 (2023.6.11)

- support `batch_load_fn` as params for `LoaderDepend`
- add test `tests/resolver/test_15_support_batch_load_fn.py`
- `build_object`, `build_list` can be imported from `pydantic_resolve`

## v0.5

### v0.5.1 (2023.6.11)

- add helper utils for Dataloader.batch_load_fn `built_list` and `build_object`, see `examples/fastapi_demo/loader.py`
- FIX: fix potential error caused by same loader name from different module. see `tests/resolver/test_14_check_loader_name.py`

### v0.5.0 (2023.6.1)

- `Resolver.ensure_type`: True will ensure `resolve_*` methods have return annotation.
- add FastAPI integrated example `examples/fastapi_demo`

## v0.4

### v0.4.0 (2023.4.6)

- add new install option `pip install "pydantic-resolve[dataloader]" to include `aiodataloader` by default
- add new `doc/loader-cn.md`, `doc/loader-en.md` to explain the convinence of using `LoaderDepen`
- add new params in Resolver: `loader_filters` to support global filter setting for inside loaders.
- add `examples/6_sqlalchemy_loaderdepend_global_filter.md` for global filter

## v0.3

### v0.3.2 (2023.4.5)

- refact tests, group by `core` and `resolver`
- replace `unittest` with `pytest.mark.asyncio`
- little `readme.md` change, new top sample code

### v0.3.1 (2023.4.3)

- change code examples in readme.md
- add unittest for pydantic validationError
- code refactor (rename)

### v0.3.0

- add `DataloaderDependCantBeResolved`, it will raise if use `resolve` to handle schema with `Dataloader`
- add `chinese.md` and `english.md` in `doc` folder
