Skip to content

Commit 279c8d6

Browse files
committed
schema - do not use title as class name for schema
avoid collisions
1 parent 50c3f11 commit 279c8d6

File tree

5 files changed

+75
-13
lines changed

5 files changed

+75
-13
lines changed

src/aiopenapi3/base.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,6 @@ def __getstate__(self):
366366

367367
def _get_identity(self, prefix="XLS", name=None):
368368
if self._identity is None:
369-
if name is None:
370-
name = self.title
371369
if name:
372370
n = re.sub(r"\W", "_", name, flags=re.ASCII)
373371
else:

src/aiopenapi3/openapi.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,9 @@ def is_schema(v: tuple[str, "SchemaType"]) -> bool:
495495
for byid in map(lambda x: x.definitions, documents):
496496
assert byid is not None and isinstance(byid, dict)
497497
for name, schema in filter(is_schema, byid.items()):
498-
byname[schema._get_identity(name=name)] = schema
498+
n = schema._get_identity(name=name)
499+
# assert byname.get(n, None) in [None, schema]
500+
byname[n] = schema
499501

500502
# PathItems
501503
for path, obj in (self.paths or dict()).items():
@@ -508,6 +510,7 @@ def is_schema(v: tuple[str, "SchemaType"]) -> bool:
508510
if isinstance(response, (v20.paths.Response)):
509511
if isinstance(response.schema_, (v20.Schema, v31.Schema)):
510512
name = response.schema_._get_identity("PI", f"{path}.{m}.{r}")
513+
# assert byname.get(name, None) in [None, response.schema_]
511514
byname[name] = response.schema_
512515
else:
513516
raise TypeError(f"{type(response)} at {path}")
@@ -517,7 +520,9 @@ def is_schema(v: tuple[str, "SchemaType"]) -> bool:
517520
assert byid is not None and isinstance(byid, dict)
518521
for name, response in filter(is_schema, byid.items()):
519522
assert response.schema_
520-
byname[response.schema_._get_identity(name=name)] = response.schema_
523+
n = response.schema_._get_identity(name=name)
524+
# assert byname.get(name, None) in [None, response.schema_]
525+
byname[n] = response.schema_
521526

522527
elif isinstance(self._root, (v30.Root, v31.Root)):
523528
# Schema
@@ -528,7 +533,9 @@ def is_schema(v: tuple[str, "SchemaType"]) -> bool:
528533
for byid in map(lambda x: x.schemas, components):
529534
assert byid is not None and isinstance(byid, dict)
530535
for name, schema in filter(is_schema, byid.items()):
531-
byname[schema._get_identity(name=name)] = schema
536+
n = schema._get_identity(name=name)
537+
# assert byname.get(n, None) in [None, schema]
538+
byname[n] = schema
532539

533540
# PathItems
534541
for path, obj in (self.paths or dict()).items():
@@ -541,24 +548,28 @@ def is_schema(v: tuple[str, "SchemaType"]) -> bool:
541548
schema = parameter.schema_._target
542549
else:
543550
schema = parameter.schema_
544-
assert schema is not None
551+
# assert schema is not None
545552
name = schema._get_identity("I2", f"{path}.{m}.{parameter.name}")
553+
# assert byname.get(name, None) in [None, schema]
546554
byname[name] = schema
547555
else:
548556
for key, mto in parameter.content.items():
549557
if isinstance(mto.schema_, ReferenceBase):
550558
schema = mto.schema_._target
551559
else:
552560
schema = mto.schema_
553-
assert schema is not None
561+
# assert schema is not None
554562
name = schema._get_identity("I2", f"{path}.{m}.{parameter.name}.{key}")
563+
# assert byname.get(name, None) in [None, schema]
555564
byname[name] = schema
556565

557566
if op.requestBody:
558567
for mt, mto in op.requestBody.content.items():
559568
if mto.schema_ is None:
560569
continue
561-
byname[mto.schema_._get_identity("B")] = mto.schema_
570+
n = mto.schema_._get_identity("B")
571+
# assert byname.get(n, None) in [None, getattr(mto.schema_, "_target", mto.schema_)]
572+
byname[n] = mto.schema_
562573

563574
for r, response in op.responses.items():
564575
if isinstance(response, ReferenceBase):
@@ -569,6 +580,9 @@ def is_schema(v: tuple[str, "SchemaType"]) -> bool:
569580
if mto.schema_ is None:
570581
continue
571582
name = mto.schema_._get_identity("I2", f"{path}.{m}.{r}.{mt}")
583+
# assert (v := byname.get(name, None)) in [None, mto.schema_] or type(v) != type(
584+
# mto.schema_
585+
# ), (name, v, mto.schema_)
572586
byname[name] = mto.schema_
573587
else:
574588
raise TypeError(f"{type(response)} at {path}")
@@ -581,7 +595,9 @@ def is_schema(v: tuple[str, "SchemaType"]) -> bool:
581595
for mt, mto in response.content.items():
582596
if mto.schema_ is None:
583597
continue
584-
byname[mto.schema_._get_identity("R")] = mto.schema_
598+
n = mto.schema_._get_identity("R")
599+
# assert byname.get(n, None) in [None, mto.schema_]
600+
byname[n] = mto.schema_
585601

586602
byname = self.plugins.init.schemas(initialized=self._root, schemas=byname).schemas
587603
return byname
@@ -605,9 +621,11 @@ def _init_schema_types(self, only_required: bool) -> None:
605621
for i in todo | data:
606622
b = byid[i]
607623
name = b._get_identity("X")
608-
types[name] = b.get_type()
609-
for idx, j in enumerate(b._model_types):
610-
types[f"{name}.c{idx}"] = j
624+
t = b.get_type()
625+
# assert (v := byname.get(name, None)) in [None, b], (name, b, v)
626+
types[name] = t
627+
for j in b._model_types:
628+
types[j.__name__] = j
611629

612630
# print(f"{len(types)}")
613631
for name, schema in types.items():

tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -561,3 +561,8 @@ def with_schema_oneOf_mixed():
561561
@pytest.fixture
562562
def with_schema_anyOf():
563563
yield _get_parsed_yaml("schema-anyOf.yaml")
564+
565+
566+
@pytest.fixture
567+
def with_schema_title_name_collision():
568+
yield _get_parsed_yaml("schema-title-name-collision.yaml")
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
openapi: 3.1.0
2+
3+
info:
4+
title: title collision
5+
version: 2.2.7
6+
7+
servers:
8+
- url: /
9+
10+
paths: {}
11+
12+
13+
components:
14+
schemas:
15+
B:
16+
type: object
17+
title: B
18+
properties:
19+
a:
20+
title: A
21+
type: string
22+
A:
23+
type: object
24+
properties:
25+
id:
26+
type: integer
27+
28+
C:
29+
$ref: '#/components/schemas/A'

tests/schema_test.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ def test_schema_without_properties(httpx_mock):
5050
assert result.example == "it worked"
5151

5252
# the schema without properties did get its own named type defined
53-
assert type(result.no_properties).__name__ == "has_no_properties"
53+
# assert type(result.no_properties).__name__ == "has_no_properties"
54+
5455
# and it has no fields
5556
assert len(type(result.no_properties).model_fields) == 0
5657

@@ -790,3 +791,14 @@ def test_schema_date_types(with_schema_date_types):
790791
v = String.model_validate(str(ts))
791792
assert isinstance(v.root, datetime)
792793
assert v.model_dump_json()[1:-1] == now.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
794+
795+
796+
def test_schema_title_name_collision(with_schema_title_name_collision):
797+
api = OpenAPI("/", with_schema_title_name_collision)
798+
C = api.components.schemas["C"].get_type()
799+
C.model_validate({"a": 1})
800+
801+
802+
def test_schema_discriminated_union_extends(with_schema_discriminated_union_extends):
803+
# AssertionError: A.c0
804+
api = OpenAPI("/", with_schema_discriminated_union_extends)

0 commit comments

Comments
 (0)