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"""
@@ -410,11 +463,18 @@ def _property_from_ref(
410
463
ref_path = parse_reference_path (data .ref )
411
464
if isinstance (ref_path , ParseError ):
412
465
return PropertyError (data = data , detail = ref_path .detail ), schemas
466
+
413
467
existing = schemas .classes_by_reference .get (ref_path )
414
- if not existing :
468
+ if not existing or not existing . data :
415
469
return PropertyError (data = data , detail = "Could not find reference in parsed models or enums" ), schemas
416
470
417
- prop = attr .evolve (existing , required = required , name = name )
471
+ if isinstance (existing .data , RecursiveReferenceInterupt ):
472
+ return (
473
+ LazySelfReferenceProperty (required = required , name = name , nullable = False , default = None , owner = existing ),
474
+ schemas ,
475
+ )
476
+
477
+ prop = attr .evolve (existing .data , required = required , name = name )
418
478
if parent :
419
479
prop = attr .evolve (prop , nullable = parent .nullable )
420
480
if isinstance (prop , EnumProperty ):
@@ -550,28 +610,44 @@ def build_schemas(
550
610
to_process : Iterable [Tuple [str , Union [oai .Reference , oai .Schema ]]] = components .items ()
551
611
still_making_progress = True
552
612
errors : List [PropertyError ] = []
553
-
613
+ recursive_references_waiting_reprocess : Dict [str , Union [oai .Reference , oai .Schema ]] = dict ()
614
+ visited : Set [_ReferencePath ] = set ()
615
+ depth = 0
554
616
# References could have forward References so keep going as long as we are making progress
555
617
while still_making_progress :
556
618
still_making_progress = False
557
619
errors = []
558
620
next_round = []
621
+
559
622
# Only accumulate errors from the last round, since we might fix some along the way
560
623
for name , data in to_process :
561
624
ref_path = parse_reference_path (f"#/components/schemas/{ name } " )
562
625
if isinstance (ref_path , ParseError ):
563
626
schemas .errors .append (PropertyError (detail = ref_path .detail , data = data ))
564
627
continue
565
628
566
- schemas_or_err = update_schemas_with (ref_path = ref_path , data = data , schemas = schemas , config = config )
629
+ visited .add (ref_path )
630
+ schemas_or_err = update_schemas_with (
631
+ ref_path = ref_path , data = data , schemas = schemas , visited = visited , config = config
632
+ )
567
633
if isinstance (schemas_or_err , PropertyError ):
568
- next_round .append ((name , data ))
569
- errors .append (schemas_or_err )
570
- continue
634
+ if isinstance (schemas_or_err , RecursiveReferenceInterupt ):
635
+ up_schemas = schemas_or_err .schemas
636
+ assert isinstance (up_schemas , Schemas ) # TODO fix typedef in RecursiveReferenceInterupt
637
+ schemas_or_err = up_schemas
638
+ recursive_references_waiting_reprocess [name ] = data
639
+ else :
640
+ next_round .append ((name , data ))
641
+ errors .append (schemas_or_err )
642
+ continue
571
643
572
644
schemas = schemas_or_err
573
645
still_making_progress = True
646
+ depth += 1
574
647
to_process = next_round
575
648
649
+ if len (recursive_references_waiting_reprocess .keys ()):
650
+ schemas = build_schemas (components = recursive_references_waiting_reprocess , schemas = schemas , config = config )
651
+
576
652
schemas .errors .extend (errors )
577
653
return schemas
0 commit comments