Skip to content

Commit 8c35460

Browse files
✨ Don't create separate class for schemas consisting solely of single anyOf/oneOf/allOf.
1 parent 298be83 commit 8c35460

File tree

2 files changed

+60
-3
lines changed

2 files changed

+60
-3
lines changed

src/lapidary/render/model/metamodel.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import dataclasses as dc
44
import itertools
55
import operator
6-
from collections.abc import Callable, Iterable, Set
6+
from collections.abc import Callable, Container, Iterable, Set
77
from typing import Any, Self
88

99
from openapi_pydantic.v3.v3_1 import schema as schema31
@@ -93,6 +93,22 @@ class MetaModel:
9393
all_of: list[MetaModel] | None = None
9494

9595
def normalize_model(self) -> MetaModel | None:
96+
# if this doesn't have any assertions and only a single sub-schema, return that sub-schema
97+
if len(self.any_of or ()) + len(self.one_of or ()) + len(self.all_of or ()) == 1:
98+
if self.any_of:
99+
candidate = self.any_of[0]
100+
elif self.one_of:
101+
candidate = self.one_of[0]
102+
elif self.all_of:
103+
candidate = self.all_of[0]
104+
else:
105+
raise ValueError
106+
107+
if self._only_constraints() == MetaModel(stack=self.stack) or self._only_constraints() == MetaModel(
108+
stack=self.stack, type_=candidate.type_
109+
):
110+
return candidate
111+
96112
if self.type_ is None:
97113
self.type_ = _all_types()
98114

@@ -117,7 +133,7 @@ def normalize_model(self) -> MetaModel | None:
117133
if nsub is None:
118134
# ignore bottom types
119135
continue
120-
if dc.replace(sub, description=None) == dc.replace(nsub, stack=sub.stack, description=None):
136+
if sub._comparable() == nsub._comparable():
121137
# no change
122138
nsub = sub
123139
items.append(nsub)
@@ -376,7 +392,7 @@ def _as_object_anno(self, root_package: str) -> python.AnnotatedType:
376392
else:
377393
return resolve_type_name(root_package, self.stack)
378394

379-
def _has_annotations(self) -> bool:
395+
def _has_annotations(self, excluding: Container[str] = ()) -> bool:
380396
return (
381397
any(
382398
getattr(self, key) is not None
@@ -396,13 +412,18 @@ def _has_annotations(self) -> bool:
396412
'one_of',
397413
'all_of',
398414
)
415+
if key not in excluding
399416
)
400417
or self.additional_props is not True
401418
or bool(self.properties)
402419
or bool(self.props_required)
403420
or self.type_ != _all_types()
404421
)
405422

423+
def _comparable(self) -> Self:
424+
"""Return a copy without anotations, useful for comparing."""
425+
return dc.replace(self, description=None, title=None, stack=Stack())
426+
406427

407428
def _as_class_field(anno: python.AnnotatedType, name: str, required: bool) -> python.AnnotatedVariable:
408429
python_name = names.maybe_mangle_name(name)

tests/process/test_normalize.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from openapi_pydantic.v3.v3_0 import DataType
2+
3+
from lapidary.render.model.metamodel import MetaModel
4+
from lapidary.render.model.stack import Stack
5+
6+
7+
def test_normalize_single_anyof():
8+
schema = MetaModel(
9+
stack=Stack.from_str('#/components/schemas/obj'),
10+
any_of=[
11+
MetaModel(
12+
stack=Stack.from_str('#/components/schemas/obj/anyOf/0'),
13+
type_={DataType.OBJECT},
14+
properties={
15+
'id': MetaModel(
16+
stack=Stack.from_str('#/components/schemas/obj/anyOf/0/properties/id'), type_={DataType.STRING}
17+
)
18+
},
19+
)
20+
],
21+
)
22+
23+
schema = schema.normalize_model()
24+
25+
expected = MetaModel(
26+
stack=Stack.from_str('#/components/schemas/obj/anyOf/0'),
27+
type_={DataType.OBJECT},
28+
properties={
29+
'id': MetaModel(
30+
stack=Stack.from_str('#/components/schemas/obj/anyOf/0/properties/id'),
31+
type_={DataType.STRING},
32+
)
33+
},
34+
)
35+
36+
assert schema == expected

0 commit comments

Comments
 (0)