@@ -311,6 +311,10 @@ def package(self) -> Optional[str]:
311311 def meta_get (self , name : str , clazz : Type [T ], default : T ) -> T :
312312 return default
313313
314+ @property
315+ def is_complex (self ) -> bool :
316+ return False
317+
314318 # noinspection PyUnusedLocal
315319 @staticmethod
316320 def from_json (js : Json , _ : type = object , ** kwargs : object ) -> Kind :
@@ -775,15 +779,16 @@ def check_valid(self, obj: JsonElement, **kwargs: bool) -> ValidationResult:
775779 raise AttributeError (f"TransformKind { self .fqn } is not allowed to be supplied." )
776780
777781 def resolve (self , model : Dict [str , Kind ]) -> None :
778- source = model .get (self .runtime_kind )
779- destination = model .get (self .destination_fqn )
780- if source and destination and isinstance (source , SimpleKind ) and isinstance (destination , SimpleKind ):
781- source .resolve (model )
782- destination .resolve (model )
783- self .source_kind = source
784- self .destination_kind = destination
785- else :
786- raise AttributeError (f"Underlying kind not known: { self .destination_fqn } " )
782+ if self .source_kind is None or self .destination_kind is None :
783+ source = model .get (self .runtime_kind )
784+ destination = model .get (self .destination_fqn )
785+ if source and destination and isinstance (source , SimpleKind ) and isinstance (destination , SimpleKind ):
786+ source .resolve (model )
787+ destination .resolve (model )
788+ self .source_kind = source
789+ self .destination_kind = destination
790+ else :
791+ raise AttributeError (f"Underlying kind not known: { self .destination_fqn } " )
787792
788793 def as_json (self , ** kwargs : bool ) -> Json :
789794 return {
@@ -927,6 +932,10 @@ def __init__(
927932 self .__property_by_path : List [ResolvedPropertyPath ] = []
928933 self .__synthetic_props : List [ResolvedPropertyPath ] = []
929934
935+ @property
936+ def is_complex (self ) -> bool :
937+ return True
938+
930939 def as_json (self , ** kwargs : bool ) -> Json :
931940 result : Json = {"fqn" : self .fqn , "aggregate_root" : self .aggregate_root }
932941 if kwargs .get ("with_metadata" , True ):
@@ -1251,7 +1260,6 @@ def walk_element(
12511260 def resolve_properties (
12521261 complex_kind : ComplexKind , model : Dict [str , Kind ]
12531262 ) -> Tuple [List [ResolvedPropertyPath ], Dict [PropertyPath , ComplexKind ]]:
1254- visited : Dict [str , PropertyPath ] = {}
12551263 result : List [ResolvedPropertyPath ] = []
12561264 owner_lookup : Dict [PropertyPath , ComplexKind ] = {}
12571265
@@ -1260,16 +1268,17 @@ def path_for(
12601268 prop : Property ,
12611269 kind : Kind ,
12621270 path : PropertyPath ,
1271+ visited_kinds : Dict [str , Set [str ]],
12631272 array : bool = False ,
12641273 add_prop_to_path : bool = True ,
12651274 ) -> None :
12661275 prop_name = f"{ prop .name } []" if array else prop .name
1267- # Detect object cycles: remember the path when we have visited this property.
1268- # More complex cycles can be detected that way - leave it simple for now.
1276+ # Detect object cycles: remember the kinds we already visited for this property chain.
12691277 key = f"{ prop_name } :{ prop .kind } "
1270- if key in visited and prop_name in visited [key ].path :
1271- return
1272- visited [key ] = path
1278+ if kind .is_complex :
1279+ if kind .fqn in visited_kinds [key ]:
1280+ return
1281+ visited_kinds [key ].add (kind .fqn )
12731282 relative = path .child (prop_name ) if add_prop_to_path else path
12741283 # make sure the kind is resolved
12751284 kind .resolve (model )
@@ -1280,7 +1289,7 @@ def path_for(
12801289 if name := relative .last_part :
12811290 result .append (ResolvedPropertyPath (relative , Property (name , kind .fqn ), kind ))
12821291 owner_lookup [relative ] = owner
1283- path_for (owner , prop , kind .inner , path , True )
1292+ path_for (owner , prop , kind .inner , path , visited_kinds , True )
12841293 elif isinstance (kind , DictionaryKind ):
12851294 child = relative .child (None )
12861295 if name := relative .last_part :
@@ -1290,17 +1299,28 @@ def path_for(
12901299 value = kind .value_kind
12911300 result .append (ResolvedPropertyPath (child , Property ("any" , value .fqn ), value ))
12921301 owner_lookup [child ] = owner
1293- path_for (owner , prop , kind .value_kind , child , add_prop_to_path = False )
1302+ path_for (owner , prop , kind .value_kind , child , visited_kinds , add_prop_to_path = False )
12941303 elif isinstance (kind , ComplexKind ):
12951304 if name := relative .last_part :
12961305 result .append (ResolvedPropertyPath (relative , Property (name , kind .fqn ), kind ))
12971306 owner_lookup [relative ] = owner
1298- for_complex_kind (owner , kind , relative )
1307+ for_complex_kind (owner , kind , relative , visited_kinds )
12991308
1300- def for_complex_kind (owner : ComplexKind , current : ComplexKind , relative : PropertyPath ) -> None :
1301- for cpx in list (current .resolved_bases ().values ()) + [current ]:
1302- for prop in cpx .properties :
1303- path_for (owner , prop , cpx .__resolved_props [prop .name ][1 ], relative )
1309+ def for_complex_kind (
1310+ owner : ComplexKind ,
1311+ current : ComplexKind ,
1312+ relative : PropertyPath ,
1313+ visited_kinds : Optional [Dict [str , Set [str ]]] = None ,
1314+ ) -> None :
1315+ current .resolve (model )
1316+ bases = current .kind_hierarchy () | {current .fqn }
1317+ for cpx_fqn in bases :
1318+ if isinstance (cpx := model .get (cpx_fqn ), ComplexKind ):
1319+ cpx .resolve (model )
1320+ for prop in cpx .properties :
1321+ path_for (
1322+ owner , prop , cpx .__resolved_props [prop .name ][1 ], relative , visited_kinds or defaultdict (set )
1323+ )
13041324
13051325 for_complex_kind (complex_kind , complex_kind , PropertyPath ([], "" ))
13061326 return result , owner_lookup
0 commit comments