1
+ import copy
1
2
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
3
4
4
5
import attr
5
6
14
15
from .schemas import Schemas
15
16
16
17
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
+
17
95
@attr .s (auto_attribs = True , frozen = True )
18
96
class NoneProperty (Property ):
19
97
""" A property that is always None (used for empty schemas) """
@@ -435,14 +513,19 @@ def _property_from_data(
435
513
""" Generate a Property from the OpenAPI dictionary representation of it """
436
514
name = utils .remove_string_escapes (name )
437
515
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
+
438
519
reference = Reference .from_ref (data .ref )
439
520
existing = schemas .enums .get (reference .class_name ) or schemas .models .get (reference .class_name )
440
521
if existing :
441
522
return (
442
523
attr .evolve (existing , required = required , name = name ),
443
524
schemas ,
444
525
)
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
+
446
529
if data .enum :
447
530
return build_enum_property (
448
531
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
513
596
)
514
597
else :
515
598
prop , schemas = build_model_property (data = data , name = name , schemas = schemas , required = True , parent_name = None )
599
+
516
600
if isinstance (prop , PropertyError ):
517
601
return prop
518
602
else :
@@ -596,6 +680,7 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
596
680
errors : List [PropertyError ] = []
597
681
references_by_name : Dict [str , oai .Reference ] = dict ()
598
682
references_to_process : List [Tuple [str , oai .Reference ]] = list ()
683
+ LazyReferencePropertyProxy .flush_internal_references () # Cleanup side effects
599
684
600
685
# References could have forward References so keep going as long as we are making progress
601
686
while processing :
@@ -620,11 +705,17 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
620
705
621
706
to_process = next_round
622
707
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 )
625
710
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 )
628
713
629
714
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
+ )
630
721
return schemas
0 commit comments