Skip to content

Commit 13f2489

Browse files
committed
parser/ propertoes / add LazyReferencePropertyProxy: lazy resolve reference properties
1 parent 87c5b12 commit 13f2489

File tree

1 file changed

+97
-6
lines changed

1 file changed

+97
-6
lines changed

openapi_python_client/parser/properties/__init__.py

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import copy
12
from itertools import chain
2-
from typing import Any, ClassVar, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union
3+
from typing import Any, ClassVar, Dict, Generic, Iterable, Iterator, List, Optional, Set, Tuple, TypeVar, Union, cast
34

45
import attr
56

@@ -14,6 +15,83 @@
1415
from .schemas import Schemas
1516

1617

18+
class LazyReferencePropertyProxy:
19+
20+
__GLOBAL_SCHEMAS_REF: Schemas = Schemas()
21+
__PROXIES: List[Tuple["LazyReferencePropertyProxy", oai.Reference]] = []
22+
23+
@classmethod
24+
def update_schemas(cls, schemas: Schemas) -> None:
25+
cls.__GLOBAL_SCHEMAS_REF = schemas
26+
27+
@classmethod
28+
def create(cls, name: str, required: bool, data: oai.Reference, parent_name: str) -> "LazyReferencePropertyProxy":
29+
proxy = LazyReferencePropertyProxy(name, required, data, parent_name)
30+
cls.__PROXIES.append((proxy, data))
31+
return proxy
32+
33+
@classmethod
34+
def created_proxies(cls) -> List[Tuple["LazyReferencePropertyProxy", oai.Reference]]:
35+
return cls.__PROXIES
36+
37+
@classmethod
38+
def flush_internal_references(cls) -> None:
39+
cls.__PROXIES = []
40+
cls.__GLOBAL_SCHEMAS_REF = Schemas()
41+
42+
def __init__(self, name: str, required: bool, data: oai.Reference, parent_name: str):
43+
self._name = name
44+
self._required = required
45+
self._data = data
46+
self._parent_name = parent_name
47+
self._reference: Reference = Reference.from_ref(data.ref)
48+
self._reference_to_itself: bool = self._reference.class_name == parent_name
49+
self._resolved: Union[Property, None] = None
50+
51+
def get_instance_type_string(self) -> str:
52+
return self.get_type_string(no_optional=True)
53+
54+
def get_type_string(self, no_optional: bool = False) -> str:
55+
resolved = self.resolve()
56+
if resolved:
57+
return resolved.get_type_string(no_optional)
58+
return "LazyReferencePropertyProxy"
59+
60+
def get_imports(self, *, prefix: str) -> Set[str]:
61+
resolved = self.resolve()
62+
if resolved:
63+
return resolved.get_imports(prefix=prefix)
64+
return set()
65+
66+
def __copy__(self) -> Property:
67+
resolved = cast(Property, self.resolve(False))
68+
return copy.copy(resolved)
69+
70+
def __deepcopy__(self, memo: Any) -> Property:
71+
resolved = cast(Property, self.resolve(False))
72+
return copy.deepcopy(resolved, memo)
73+
74+
def __getattr__(self, name: str) -> Any:
75+
resolved = self.resolve(False)
76+
return resolved.__getattribute__(name)
77+
78+
def resolve(self, allow_lazyness: bool = True) -> Union[Property, None]:
79+
if not self._resolved:
80+
schemas = LazyReferencePropertyProxy.__GLOBAL_SCHEMAS_REF
81+
class_name = self._reference.class_name
82+
if schemas:
83+
existing = schemas.enums.get(class_name) or schemas.models.get(class_name)
84+
if existing:
85+
self._resolved = attr.evolve(existing, required=self._required, name=self._name)
86+
87+
if self._resolved:
88+
return self._resolved
89+
elif allow_lazyness:
90+
return None
91+
else:
92+
raise RuntimeError(f"Reference {self._data} shall have been resolved.")
93+
94+
1795
@attr.s(auto_attribs=True, frozen=True)
1896
class NoneProperty(Property):
1997
""" A property that is always None (used for empty schemas) """
@@ -435,14 +513,19 @@ def _property_from_data(
435513
""" Generate a Property from the OpenAPI dictionary representation of it """
436514
name = utils.remove_string_escapes(name)
437515
if isinstance(data, oai.Reference):
516+
if not _is_local_reference(data):
517+
return PropertyError(data=data, detail="Remote reference schemas are not supported."), schemas
518+
438519
reference = Reference.from_ref(data.ref)
439520
existing = schemas.enums.get(reference.class_name) or schemas.models.get(reference.class_name)
440521
if existing:
441522
return (
442523
attr.evolve(existing, required=required, name=name),
443524
schemas,
444525
)
445-
return PropertyError(data=data, detail="Could not find reference in parsed models or enums"), schemas
526+
else:
527+
return cast(Property, LazyReferencePropertyProxy.create(name, required, data, parent_name)), schemas
528+
446529
if data.enum:
447530
return build_enum_property(
448531
data=data, name=name, required=required, schemas=schemas, enum=data.enum, parent_name=parent_name
@@ -513,6 +596,7 @@ def update_schemas_with_data(name: str, data: oai.Schema, schemas: Schemas) -> U
513596
)
514597
else:
515598
prop, schemas = build_model_property(data=data, name=name, schemas=schemas, required=True, parent_name=None)
599+
516600
if isinstance(prop, PropertyError):
517601
return prop
518602
else:
@@ -596,6 +680,7 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
596680
errors: List[PropertyError] = []
597681
references_by_name: Dict[str, oai.Reference] = dict()
598682
references_to_process: List[Tuple[str, oai.Reference]] = list()
683+
LazyReferencePropertyProxy.flush_internal_references() # Cleanup side effects
599684

600685
# References could have forward References so keep going as long as we are making progress
601686
while processing:
@@ -620,11 +705,17 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
620705

621706
to_process = next_round
622707

623-
for name, reference in references_to_process:
624-
schemas_or_err = resolve_reference_and_update_schemas(name, reference, schemas, references_by_name)
708+
for name, reference in references_to_process:
709+
schemas_or_err = resolve_reference_and_update_schemas(name, reference, schemas, references_by_name)
625710

626-
if isinstance(schemas_or_err, PropertyError):
627-
errors.append(schemas_or_err)
711+
if isinstance(schemas_or_err, PropertyError):
712+
errors.append(schemas_or_err)
628713

629714
schemas.errors.extend(errors)
715+
LazyReferencePropertyProxy.update_schemas(schemas)
716+
for reference_proxy, data in LazyReferencePropertyProxy.created_proxies():
717+
if not reference_proxy.resolve():
718+
schemas.errors.append(
719+
PropertyError(data=data, detail="Could not find reference in parsed models or enums.")
720+
)
630721
return schemas

0 commit comments

Comments
 (0)