17
17
from ... import Config
18
18
from ... import schema as oai
19
19
from ... import utils
20
- from ..errors import ParseError , PropertyError , ValidationError
20
+ from ..errors import ParseError , PropertyError , RecursiveReferenceInterupt , ValidationError
21
21
from .converter import convert , convert_chain
22
22
from .enum_property import EnumProperty
23
23
from .model_property import ModelProperty , build_model_property
24
24
from .property import Property
25
- from .schemas import Class , Schemas , parse_reference_path , update_schemas_with
25
+ from .schemas import Class , Schemas , _Holder , _ReferencePath , parse_reference_path , update_schemas_with
26
26
27
27
28
28
@attr .s (auto_attribs = True , frozen = True )
@@ -34,6 +34,59 @@ class NoneProperty(Property):
34
34
template : ClassVar [Optional [str ]] = "none_property.py.jinja"
35
35
36
36
37
+ @attr .s (auto_attribs = True , frozen = True )
38
+ class LazySelfReferenceProperty (Property ):
39
+ """A property used to resolve recursive reference.
40
+ It proxyfy the required method call to its binded Property owner
41
+ """
42
+
43
+ owner : _Holder [Union [ModelProperty , EnumProperty , RecursiveReferenceInterupt ]]
44
+ _resolved : bool = False
45
+
46
+ def get_base_type_string (self ) -> str :
47
+ self ._ensure_resolved ()
48
+
49
+ prop = self .owner .data
50
+ assert isinstance (prop , Property )
51
+ return prop .get_base_type_string ()
52
+
53
+ def get_base_json_type_string (self ) -> str :
54
+ self ._ensure_resolved ()
55
+
56
+ prop = self .owner .data
57
+ assert isinstance (prop , Property )
58
+ return prop .get_base_json_type_string ()
59
+
60
+ def get_type_string (self , no_optional : bool = False , json : bool = False ) -> str :
61
+ self ._ensure_resolved ()
62
+
63
+ prop = self .owner .data
64
+ assert isinstance (prop , Property )
65
+ return prop .get_type_string (no_optional , json )
66
+
67
+ def get_instance_type_string (self ) -> str :
68
+ self ._ensure_resolved ()
69
+ return super ().get_instance_type_string ()
70
+
71
+ def to_string (self ) -> str :
72
+ self ._ensure_resolved ()
73
+
74
+ if not self .required :
75
+ return f"{ self .python_name } : Union[Unset, { self .get_type_string ()} ] = UNSET"
76
+ else :
77
+ return f"{ self .python_name } : { self .get_type_string ()} "
78
+
79
+ def _ensure_resolved (self ) -> None :
80
+ if self ._resolved :
81
+ return
82
+
83
+ if not isinstance (self .owner .data , Property ):
84
+ raise RuntimeError (f"LazySelfReferenceProperty { self .name } owner shall have been resolved." )
85
+ else :
86
+ object .__setattr__ (self , "_resolved" , True )
87
+ object .__setattr__ (self , "nullable" , self .owner .data .nullable )
88
+
89
+
37
90
@attr .s (auto_attribs = True , frozen = True )
38
91
class StringProperty (Property ):
39
92
"""A property of type str"""
@@ -411,11 +464,18 @@ def _property_from_ref(
411
464
ref_path = parse_reference_path (data .ref )
412
465
if isinstance (ref_path , ParseError ):
413
466
return PropertyError (data = data , detail = ref_path .detail ), schemas
467
+
414
468
existing = schemas .classes_by_reference .get (ref_path )
415
- if not existing :
469
+ if not existing or not existing . data :
416
470
return PropertyError (data = data , detail = "Could not find reference in parsed models or enums" ), schemas
417
471
418
- prop = attr .evolve (existing , required = required , name = name )
472
+ if isinstance (existing .data , RecursiveReferenceInterupt ):
473
+ return (
474
+ LazySelfReferenceProperty (required = required , name = name , nullable = False , default = None , owner = existing ),
475
+ schemas ,
476
+ )
477
+
478
+ prop = attr .evolve (existing .data , required = required , name = name )
419
479
if parent :
420
480
prop = attr .evolve (prop , nullable = parent .nullable )
421
481
if isinstance (prop , EnumProperty ):
@@ -551,28 +611,44 @@ def build_schemas(
551
611
to_process : Iterable [Tuple [str , Union [oai .Reference , oai .Schema ]]] = components .items ()
552
612
still_making_progress = True
553
613
errors : List [PropertyError ] = []
554
-
614
+ recursive_references_waiting_reprocess : Dict [str , Union [oai .Reference , oai .Schema ]] = dict ()
615
+ visited : Set [_ReferencePath ] = set ()
616
+ depth = 0
555
617
# References could have forward References so keep going as long as we are making progress
556
618
while still_making_progress :
557
619
still_making_progress = False
558
620
errors = []
559
621
next_round = []
622
+
560
623
# Only accumulate errors from the last round, since we might fix some along the way
561
624
for name , data in to_process :
562
625
ref_path = parse_reference_path (f"#/components/schemas/{ name } " )
563
626
if isinstance (ref_path , ParseError ):
564
627
schemas .errors .append (PropertyError (detail = ref_path .detail , data = data ))
565
628
continue
566
629
567
- schemas_or_err = update_schemas_with (ref_path = ref_path , data = data , schemas = schemas , config = config )
630
+ visited .add (ref_path )
631
+ schemas_or_err = update_schemas_with (
632
+ ref_path = ref_path , data = data , schemas = schemas , visited = visited , config = config
633
+ )
568
634
if isinstance (schemas_or_err , PropertyError ):
569
- next_round .append ((name , data ))
570
- errors .append (schemas_or_err )
571
- continue
635
+ if isinstance (schemas_or_err , RecursiveReferenceInterupt ):
636
+ up_schemas = schemas_or_err .schemas
637
+ assert isinstance (up_schemas , Schemas ) # TODO fix typedef in RecursiveReferenceInterupt
638
+ schemas_or_err = up_schemas
639
+ recursive_references_waiting_reprocess [name ] = data
640
+ else :
641
+ next_round .append ((name , data ))
642
+ errors .append (schemas_or_err )
643
+ continue
572
644
573
645
schemas = schemas_or_err
574
646
still_making_progress = True
647
+ depth += 1
575
648
to_process = next_round
576
649
650
+ if len (recursive_references_waiting_reprocess .keys ()):
651
+ schemas = build_schemas (components = recursive_references_waiting_reprocess , schemas = schemas , config = config )
652
+
577
653
schemas .errors .extend (errors )
578
654
return schemas
0 commit comments