@@ -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,41 @@ 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
+ owner_class_name = Reference .from_ref (f"#{ parent_name } " ).class_name
577
+ reference_name = _reference_name (data )
578
+ if lookup_is_reference_to_itself (reference_name , owner_class_name , lazy_references ):
527
579
return cast (Property , LazyReferencePropertyProxy .create (name , required , data , parent_name )), schemas
528
580
else :
529
581
return PropertyError (data = data , detail = "Could not find reference in parsed models or enums." ), schemas
530
582
531
583
if data .enum :
532
584
return build_enum_property (
533
- data = data , name = name , required = required , schemas = schemas , enum = data .enum , parent_name = parent_name
585
+ data = data ,
586
+ name = name ,
587
+ required = required ,
588
+ schemas = schemas ,
589
+ enum = data .enum ,
590
+ parent_name = parent_name ,
534
591
)
535
592
if data .anyOf or data .oneOf :
536
- return build_union_property (data = data , name = name , required = required , schemas = schemas , parent_name = parent_name )
593
+ return build_union_property (
594
+ data = data ,
595
+ name = name ,
596
+ required = required ,
597
+ schemas = schemas ,
598
+ parent_name = parent_name ,
599
+ lazy_references = lazy_references ,
600
+ )
537
601
if not data .type :
538
602
return NoneProperty (name = name , required = required , nullable = False , default = None ), schemas
539
603
@@ -570,9 +634,23 @@ def _property_from_data(
570
634
schemas ,
571
635
)
572
636
elif data .type == "array" :
573
- return build_list_property (data = data , name = name , required = required , schemas = schemas , parent_name = parent_name )
637
+ return build_list_property (
638
+ data = data ,
639
+ name = name ,
640
+ required = required ,
641
+ schemas = schemas ,
642
+ parent_name = parent_name ,
643
+ lazy_references = lazy_references ,
644
+ )
574
645
elif data .type == "object" :
575
- return build_model_property (data = data , name = name , schemas = schemas , required = required , parent_name = parent_name )
646
+ return build_model_property (
647
+ data = data ,
648
+ name = name ,
649
+ schemas = schemas ,
650
+ required = required ,
651
+ parent_name = parent_name ,
652
+ lazy_references = lazy_references ,
653
+ )
576
654
return PropertyError (data = data , detail = f"unknown type { data .type } " ), schemas
577
655
578
656
@@ -583,21 +661,41 @@ def property_from_data(
583
661
data : Union [oai .Reference , oai .Schema ],
584
662
schemas : Schemas ,
585
663
parent_name : str ,
664
+ lazy_references : Optional [Dict [str , oai .Reference ]] = None ,
586
665
) -> Tuple [Union [Property , PropertyError ], Schemas ]:
666
+ if lazy_references is None :
667
+ lazy_references = dict ()
668
+
587
669
try :
588
- return _property_from_data (name = name , required = required , data = data , schemas = schemas , parent_name = parent_name )
670
+ return _property_from_data (
671
+ name = name ,
672
+ required = required ,
673
+ data = data ,
674
+ schemas = schemas ,
675
+ parent_name = parent_name ,
676
+ lazy_references = lazy_references ,
677
+ )
589
678
except ValidationError :
590
679
return PropertyError (detail = "Failed to validate default value" , data = data ), schemas
591
680
592
681
593
- def update_schemas_with_data (name : str , data : oai .Schema , schemas : Schemas ) -> Union [Schemas , PropertyError ]:
682
+ def update_schemas_with_data (
683
+ name : str , data : oai .Schema , schemas : Schemas , lazy_references : Dict [str , oai .Reference ]
684
+ ) -> Union [Schemas , PropertyError ]:
594
685
prop : Union [PropertyError , ModelProperty , EnumProperty ]
595
686
if data .enum is not None :
596
687
prop , schemas = build_enum_property (
597
- data = data , name = name , required = True , schemas = schemas , enum = data .enum , parent_name = None
688
+ data = data ,
689
+ name = name ,
690
+ required = True ,
691
+ schemas = schemas ,
692
+ enum = data .enum ,
693
+ parent_name = None ,
598
694
)
599
695
else :
600
- prop , schemas = build_model_property (data = data , name = name , schemas = schemas , required = True , parent_name = None )
696
+ prop , schemas = build_model_property (
697
+ data = data , name = name , schemas = schemas , required = True , parent_name = None , lazy_references = lazy_references
698
+ )
601
699
602
700
if isinstance (prop , PropertyError ):
603
701
return prop
@@ -680,23 +778,31 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
680
778
to_process : Iterable [Tuple [str , Union [oai .Reference , oai .Schema ]]] = components .items ()
681
779
processing = True
682
780
errors : List [PropertyError ] = []
683
- references_by_name : Dict [str , oai .Reference ] = dict ()
684
- references_to_process : List [Tuple [str , oai .Reference ]] = list ()
685
781
LazyReferencePropertyProxy .flush_internal_references () # Cleanup side effects
782
+ lazy_self_references : Dict [str , oai .Reference ] = dict ()
783
+ visited : List [str ] = []
686
784
687
785
# References could have forward References so keep going as long as we are making progress
688
786
while processing :
787
+ references_by_name : Dict [str , oai .Reference ] = dict ()
788
+ references_to_process : List [Tuple [str , oai .Reference ]] = list ()
689
789
processing = False
690
790
errors = []
691
791
next_round = []
792
+
692
793
# Only accumulate errors from the last round, since we might fix some along the way
693
794
for name , data in to_process :
795
+ visited .append (name )
796
+
694
797
if isinstance (data , oai .Reference ):
695
- references_by_name [name ] = data
696
- references_to_process .append ((name , data ))
798
+ class_name = _reference_model_name (data )
799
+
800
+ if not schemas .models .get (class_name ) and not schemas .enums .get (class_name ):
801
+ references_by_name [name ] = data
802
+ references_to_process .append ((name , data ))
697
803
continue
698
804
699
- schemas_or_err = update_schemas_with_data (name , data , schemas )
805
+ schemas_or_err = update_schemas_with_data (name , data , schemas , lazy_self_references )
700
806
701
807
if isinstance (schemas_or_err , PropertyError ):
702
808
next_round .append ((name , data ))
@@ -711,7 +817,18 @@ def build_schemas(*, components: Dict[str, Union[oai.Reference, oai.Schema]]) ->
711
817
schemas_or_err = resolve_reference_and_update_schemas (name , reference , schemas , references_by_name )
712
818
713
819
if isinstance (schemas_or_err , PropertyError ):
714
- errors .append (schemas_or_err )
820
+ if _reference_name (reference ) in visited :
821
+ # It's a reference to an already visited Enum|Model; not yet resolved
822
+ # It's an indirect reference toward this Enum|Model;
823
+ # It will be lazy proxified and resolved later on
824
+ lazy_self_references [name ] = reference
825
+ else :
826
+ errors .append (schemas_or_err )
827
+
828
+ for name in lazy_self_references .keys ():
829
+ schemas_or_err = resolve_reference_and_update_schemas (
830
+ name , lazy_self_references [name ], schemas , references_by_name
831
+ )
715
832
716
833
schemas .errors .extend (errors )
717
834
LazyReferencePropertyProxy .update_schemas (schemas )
0 commit comments