@@ -72,8 +72,13 @@ def __deepcopy__(self, memo: Any) -> Property:
72
72
return copy .deepcopy (resolved , memo )
73
73
74
74
def __getattr__ (self , name : str ) -> Any :
75
- resolved = self .resolve (False )
76
- return resolved .__getattribute__ (name )
75
+ if name == "nullable" :
76
+ return not self ._required
77
+ elif name == "required" :
78
+ return self ._required
79
+ else :
80
+ resolved = self .resolve (False )
81
+ return resolved .__getattribute__ (name )
77
82
78
83
def resolve (self , allow_lazyness : bool = True ) -> Union [Property , None ]:
79
84
if not self ._resolved :
@@ -312,7 +317,13 @@ def _string_based_property(
312
317
313
318
314
319
def build_model_property (
315
- * , data : oai .Schema , name : str , schemas : Schemas , required : bool , parent_name : Optional [str ]
320
+ * ,
321
+ data : oai .Schema ,
322
+ name : str ,
323
+ schemas : Schemas ,
324
+ required : bool ,
325
+ parent_name : Optional [str ],
326
+ lazy_references : Dict [str , oai .Reference ],
316
327
) -> Tuple [Union [ModelProperty , PropertyError ], Schemas ]:
317
328
"""
318
329
A single ModelProperty from its OAI data
@@ -336,7 +347,12 @@ def build_model_property(
336
347
for key , value in (data .properties or {}).items ():
337
348
prop_required = key in required_set
338
349
prop , schemas = property_from_data (
339
- name = key , required = prop_required , data = value , schemas = schemas , parent_name = class_name
350
+ name = key ,
351
+ required = prop_required ,
352
+ data = value ,
353
+ schemas = schemas ,
354
+ parent_name = class_name ,
355
+ lazy_references = lazy_references ,
340
356
)
341
357
if isinstance (prop , PropertyError ):
342
358
return prop , schemas
@@ -362,6 +378,7 @@ def build_model_property(
362
378
data = data .additionalProperties ,
363
379
schemas = schemas ,
364
380
parent_name = class_name ,
381
+ lazy_references = lazy_references ,
365
382
)
366
383
if isinstance (additional_properties , PropertyError ):
367
384
return additional_properties , schemas
@@ -456,12 +473,23 @@ def build_enum_property(
456
473
457
474
458
475
def build_union_property (
459
- * , data : oai .Schema , name : str , required : bool , schemas : Schemas , parent_name : str
476
+ * ,
477
+ data : oai .Schema ,
478
+ name : str ,
479
+ required : bool ,
480
+ schemas : Schemas ,
481
+ parent_name : str ,
482
+ lazy_references : Dict [str , oai .Reference ],
460
483
) -> Tuple [Union [UnionProperty , PropertyError ], Schemas ]:
461
484
sub_properties : List [Property ] = []
462
485
for sub_prop_data in chain (data .anyOf , data .oneOf ):
463
486
sub_prop , schemas = property_from_data (
464
- name = name , required = required , data = sub_prop_data , schemas = schemas , parent_name = parent_name
487
+ name = name ,
488
+ required = required ,
489
+ data = sub_prop_data ,
490
+ schemas = schemas ,
491
+ parent_name = parent_name ,
492
+ lazy_references = lazy_references ,
465
493
)
466
494
if isinstance (sub_prop , PropertyError ):
467
495
return PropertyError (detail = f"Invalid property in union { name } " , data = sub_prop_data ), schemas
@@ -481,12 +509,23 @@ def build_union_property(
481
509
482
510
483
511
def build_list_property (
484
- * , data : oai .Schema , name : str , required : bool , schemas : Schemas , parent_name : str
512
+ * ,
513
+ data : oai .Schema ,
514
+ name : str ,
515
+ required : bool ,
516
+ schemas : Schemas ,
517
+ parent_name : str ,
518
+ lazy_references : Dict [str , oai .Reference ],
485
519
) -> Tuple [Union [ListProperty [Any ], PropertyError ], Schemas ]:
486
520
if data .items is None :
487
521
return PropertyError (data = data , detail = "type array must have items defined" ), schemas
488
522
inner_prop , schemas = property_from_data (
489
- name = f"{ name } _item" , required = True , data = data .items , schemas = schemas , parent_name = parent_name
523
+ name = f"{ name } _item" ,
524
+ required = True ,
525
+ data = data .items ,
526
+ schemas = schemas ,
527
+ parent_name = parent_name ,
528
+ lazy_references = lazy_references ,
490
529
)
491
530
if isinstance (inner_prop , PropertyError ):
492
531
return PropertyError (data = inner_prop .data , detail = f"invalid data in items of array { name } " ), schemas
@@ -508,6 +547,7 @@ def _property_from_data(
508
547
data : Union [oai .Reference , oai .Schema ],
509
548
schemas : Schemas ,
510
549
parent_name : str ,
550
+ lazy_references : Dict [str , oai .Reference ],
511
551
) -> Tuple [Union [Property , PropertyError ], Schemas ]:
512
552
""" Generate a Property from the OpenAPI dictionary representation of it """
513
553
name = utils .remove_string_escapes (name )
@@ -523,17 +563,40 @@ def _property_from_data(
523
563
schemas ,
524
564
)
525
565
else :
526
- if Reference .from_ref (f"#{ parent_name } " ).class_name == reference .class_name :
566
+
567
+ def lookup_is_reference_to_itself (
568
+ ref_name : str , owner_class_name : str , lazy_references : Dict [str , oai .Reference ]
569
+ ) -> bool :
570
+ if ref_name in lazy_references :
571
+ next_ref_name = _reference_name (lazy_references [ref_name ])
572
+ return lookup_is_reference_to_itself (next_ref_name , owner_class_name , lazy_references )
573
+
574
+ return ref_name .casefold () == owner_class_name .casefold ()
575
+
576
+ reference_name = _reference_name (data )
577
+ if lookup_is_reference_to_itself (reference_name , parent_name , lazy_references ):
527
578
return cast (Property , LazyReferencePropertyProxy .create (name , required , data , parent_name )), schemas
528
579
else :
529
580
return PropertyError (data = data , detail = "Could not find reference in parsed models or enums." ), schemas
530
581
531
582
if data .enum :
532
583
return build_enum_property (
533
- data = data , name = name , required = required , schemas = schemas , enum = data .enum , parent_name = parent_name
584
+ data = data ,
585
+ name = name ,
586
+ required = required ,
587
+ schemas = schemas ,
588
+ enum = data .enum ,
589
+ parent_name = parent_name ,
534
590
)
535
591
if data .anyOf or data .oneOf :
536
- return build_union_property (data = data , name = name , required = required , schemas = schemas , parent_name = parent_name )
592
+ return build_union_property (
593
+ data = data ,
594
+ name = name ,
595
+ required = required ,
596
+ schemas = schemas ,
597
+ parent_name = parent_name ,
598
+ lazy_references = lazy_references ,
599
+ )
537
600
if not data .type :
538
601
return NoneProperty (name = name , required = required , nullable = False , default = None ), schemas
539
602
@@ -570,9 +633,23 @@ def _property_from_data(
570
633
schemas ,
571
634
)
572
635
elif data .type == "array" :
573
- return build_list_property (data = data , name = name , required = required , schemas = schemas , parent_name = parent_name )
636
+ return build_list_property (
637
+ data = data ,
638
+ name = name ,
639
+ required = required ,
640
+ schemas = schemas ,
641
+ parent_name = parent_name ,
642
+ lazy_references = lazy_references ,
643
+ )
574
644
elif data .type == "object" :
575
- return build_model_property (data = data , name = name , schemas = schemas , required = required , parent_name = parent_name )
645
+ return build_model_property (
646
+ data = data ,
647
+ name = name ,
648
+ schemas = schemas ,
649
+ required = required ,
650
+ parent_name = parent_name ,
651
+ lazy_references = lazy_references ,
652
+ )
576
653
return PropertyError (data = data , detail = f"unknown type { data .type } " ), schemas
577
654
578
655
@@ -583,21 +660,41 @@ def property_from_data(
583
660
data : Union [oai .Reference , oai .Schema ],
584
661
schemas : Schemas ,
585
662
parent_name : str ,
663
+ lazy_references : Optional [Dict [str , oai .Reference ]] = None ,
586
664
) -> Tuple [Union [Property , PropertyError ], Schemas ]:
665
+ if lazy_references is None :
666
+ lazy_references = dict ()
667
+
587
668
try :
588
- return _property_from_data (name = name , required = required , data = data , schemas = schemas , parent_name = parent_name )
669
+ return _property_from_data (
670
+ name = name ,
671
+ required = required ,
672
+ data = data ,
673
+ schemas = schemas ,
674
+ parent_name = parent_name ,
675
+ lazy_references = lazy_references ,
676
+ )
589
677
except ValidationError :
590
678
return PropertyError (detail = "Failed to validate default value" , data = data ), schemas
591
679
592
680
593
- def update_schemas_with_data (name : str , data : oai .Schema , schemas : Schemas ) -> Union [Schemas , PropertyError ]:
681
+ def update_schemas_with_data (
682
+ name : str , data : oai .Schema , schemas : Schemas , lazy_references : Dict [str , oai .Reference ]
683
+ ) -> Union [Schemas , PropertyError ]:
594
684
prop : Union [PropertyError , ModelProperty , EnumProperty ]
595
685
if data .enum is not None :
596
686
prop , schemas = build_enum_property (
597
- data = data , name = name , required = True , schemas = schemas , enum = data .enum , parent_name = None
687
+ data = data ,
688
+ name = name ,
689
+ required = True ,
690
+ schemas = schemas ,
691
+ enum = data .enum ,
692
+ parent_name = None ,
598
693
)
599
694
else :
600
- prop , schemas = build_model_property (data = data , name = name , schemas = schemas , required = True , parent_name = None )
695
+ prop , schemas = build_model_property (
696
+ data = data , name = name , schemas = schemas , required = True , parent_name = None , lazy_references = lazy_references
697
+ )
601
698
602
699
if isinstance (prop , PropertyError ):
603
700
return prop
@@ -680,23 +777,31 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
680
777
to_process : Iterable [Tuple [str , Union [oai .Reference , oai .Schema ]]] = components .items ()
681
778
processing = True
682
779
errors : List [PropertyError ] = []
683
- references_by_name : Dict [str , oai .Reference ] = dict ()
684
- references_to_process : List [Tuple [str , oai .Reference ]] = list ()
685
780
LazyReferencePropertyProxy .flush_internal_references () # Cleanup side effects
781
+ lazy_self_references : Dict [str , oai .Reference ] = dict ()
782
+ visited : List [str ] = []
686
783
687
784
# References could have forward References so keep going as long as we are making progress
688
785
while processing :
786
+ references_by_name : Dict [str , oai .Reference ] = dict ()
787
+ references_to_process : List [Tuple [str , oai .Reference ]] = list ()
689
788
processing = False
690
789
errors = []
691
790
next_round = []
791
+
692
792
# Only accumulate errors from the last round, since we might fix some along the way
693
793
for name , data in to_process :
794
+ visited .append (name )
795
+
694
796
if isinstance (data , oai .Reference ):
695
- references_by_name [name ] = data
696
- references_to_process .append ((name , data ))
797
+ class_name = _reference_model_name (data )
798
+
799
+ if not schemas .models .get (class_name ) and not schemas .enums .get (class_name ):
800
+ references_by_name [name ] = data
801
+ references_to_process .append ((name , data ))
697
802
continue
698
803
699
- schemas_or_err = update_schemas_with_data (name , data , schemas )
804
+ schemas_or_err = update_schemas_with_data (name , data , schemas , lazy_self_references )
700
805
701
806
if isinstance (schemas_or_err , PropertyError ):
702
807
next_round .append ((name , data ))
@@ -711,7 +816,18 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
711
816
schemas_or_err = resolve_reference_and_update_schemas (name , reference , schemas , references_by_name )
712
817
713
818
if isinstance (schemas_or_err , PropertyError ):
714
- errors .append (schemas_or_err )
819
+ if _reference_name (reference ) in visited :
820
+ # It's a reference to an already visited Enum|Model; not yet resolved
821
+ # It's an indirect reference toward this Enum|Model;
822
+ # It will be lazy proxified and resolved later on
823
+ lazy_self_references [name ] = reference
824
+ else :
825
+ errors .append (schemas_or_err )
826
+
827
+ for name in lazy_self_references .keys ():
828
+ schemas_or_err = resolve_reference_and_update_schemas (
829
+ name , lazy_self_references [name ], schemas , references_by_name
830
+ )
715
831
716
832
schemas .errors .extend (errors )
717
833
LazyReferencePropertyProxy .update_schemas (schemas )
0 commit comments