Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 39 additions & 2 deletions src/adaptix/_internal/morphing/union_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,20 @@
from ..special_cases_optimization import as_is_stub
from ..type_tools import BaseNormType, is_subclass_soft, strip_tags
from ..type_tools.normalize_type import NoneType
from ..utils import Omitted
from .concrete_provider import none_loader
from .json_schema.definitions import JSONSchema
from .json_schema.request_cls import JSONSchemaRequest
from .json_schema.schema_model import JSONSchemaType
from .load_error import LoadError, TypeLoadError, UnionLoadError
from .provider_template import DumperProvider, LoaderProvider
from .provider_template import DumperProvider, JSONSchemaProvider, LoaderProvider
from .request_cls import DebugTrailRequest, DumperRequest, LoaderRequest
from .sentinel_provider import check_is_sentinel
from .utils import try_normalize_type


@for_predicate(Union)
class UnionProvider(LoaderProvider, DumperProvider):
class UnionProvider(LoaderProvider, DumperProvider, JSONSchemaProvider):
def _get_loc_stacks_to_request(
self,
mediator: Mediator,
Expand Down Expand Up @@ -280,3 +284,36 @@ def union_dumper(data):
return dumper_class_dispatcher.dispatch(type(data))(data)

return union_dumper

def provide_json_schema(self, mediator: Mediator, request: JSONSchemaRequest) -> JSONSchema:
norm = try_normalize_type(request.last_loc.type)
loc_stacks = self._get_loc_stacks_to_request(
mediator=mediator,
request=request,
norm=norm,
target="json schema",
)
json_schemas = mediator.mandatory_provide_by_iterable(
[
request.with_loc_stack(loc_stack) for loc_stack in loc_stacks
],
lambda: "Cannot create json schema for union. Dumpers for some union cases cannot be created",
)
return self._join_json_schema(json_schemas)

def _join_json_schema(self, json_schemas: Sequence[JSONSchema]) -> JSONSchema:
types: list[JSONSchemaType] = []

for schema in json_schemas:
current_type = schema.type
if isinstance(current_type, JSONSchemaType):
types.append(current_type)
elif isinstance(current_type, Omitted):
continue
else:
types.extend(current_type)

any_of_schemas = [JSONSchema(type=type_) for type_ in types]

return JSONSchema(any_of=any_of_schemas)
Comment on lines +304 to +318
Copy link
Member

@zhPavel zhPavel Oct 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why only type from subschema is used? I think that entire subschema has to be inside anyOf field


56 changes: 46 additions & 10 deletions tests/integration/morphing/test_generic_model_312.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from decimal import Decimal
from typing import Optional

from tests_helpers.morphing import JSONSchemaFork, JSONSchemaOptItem, assert_morphing

from adaptix import Retort


Expand All @@ -11,15 +13,49 @@ class MinMax[NumberT]:
max_value: Optional[NumberT]


def test_loading():
retort = Retort()

assert retort.load({"min_value": 1, "max_value": 2}, MinMax[int]) == MinMax(1, 2)
assert retort.load({"min_value": "1", "max_value": "2"}, MinMax[Decimal]) == MinMax(Decimal(1), Decimal(2))


def test_dumping():
def test_generic_model_312():
retort = Retort()

assert retort.dump(MinMax(1, 2), MinMax[int]) == {"min_value": 1, "max_value": 2}
assert retort.dump(MinMax(Decimal(1), Decimal(2)), MinMax[Decimal]) == {"min_value": "1", "max_value": "2"}
assert_morphing(
retort=retort,
tp=MinMax[int],
data={"min_value": 1, "max_value": 2},
loaded=MinMax(1, 2),
dumped={"min_value": 1, "max_value": 2},
json_schema={
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {},
"additionalProperties": JSONSchemaOptItem(input=True),
"properties": {
"min_value": {"anyOf": [{"type": "integer"}, {"type": "null"}]},
"max_value": {"anyOf": [{"type": "integer"}, {"type": "null"}]},
},
"required": JSONSchemaFork(
input=["min_value", "max_value"],
output=["min_value", "max_value"],
),
"type": "object",
},
)

assert_morphing(
retort=retort,
tp=MinMax[Decimal],
data={"min_value": "1", "max_value": "2"},
loaded=MinMax(Decimal(1), Decimal(2)),
dumped={"min_value": "1", "max_value": "2"},
json_schema={
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": {},
"additionalProperties": JSONSchemaOptItem(input=True),
"properties": {
"min_value": {"anyOf": [{"type": "string"}, {"type": "null"}]},
"max_value": {"anyOf": [{"type": "string"}, {"type": "null"}]},
},
"required": JSONSchemaFork(
input=["min_value", "max_value"],
output=["min_value", "max_value"],
),
"type": "object",
},
)
Loading