11from enum import Enum , auto
2- from typing import Dict , Union
2+ from typing import Dict , List , Union
33
44from labelbox import utils
55from labelbox .exceptions import InvalidAttributeError
@@ -239,22 +239,31 @@ class EntityMeta(type):
239239 of the Entity class object so they can be referenced for example like:
240240 Entity.Project.
241241 """
242- mappings : Dict [str , "Entity" ] = {}
242+ # Maps Entity name to Relationships for all currently defined Entities
243+ relationship_mappings : Dict [str , List [Relationship ]] = {}
243244
244245 def __init__ (cls , clsname , superclasses , attributedict ):
245246 super ().__init__ (clsname , superclasses , attributedict )
246247 cls .validate_cached_relationships ()
247248 if clsname != "Entity" :
248249 setattr (Entity , clsname , cls )
249- EntityMeta .mappings [utils .snake_case (
250+ EntityMeta .relationship_mappings [utils .snake_case (
250251 cls .__name__ )] = cls .relationships ()
251252
252253 @staticmethod
253- def raise_for_nested_cache (first : str , middle : str , last : str ):
254+ def raise_for_nested_cache (first : str , middle : str , last : List [ str ] ):
254255 raise TypeError (
255256 "Cannot cache a relationship to an Entity with its own cached relationship(s). "
256257 f"`{ first } ` caches `{ middle } ` which caches `{ last } `" )
257258
259+ @staticmethod
260+ def cached_entities (entity_name : str ):
261+ """
262+ Return all cached entites for a given Entity name
263+ """
264+ cached_entities = EntityMeta .relationship_mappings .get (entity_name , [])
265+ return {entity .name : entity for entity in cached_entities if entity .cache }
266+
258267 def validate_cached_relationships (cls ):
259268 """
260269 Graphql doesn't allow for infinite nesting in queries.
@@ -270,23 +279,28 @@ def validate_cached_relationships(cls):
270279 A two way check is necessary because checks are performed as classes are being defined.
271280 As opposed to after all objects have been created.
272281 """
282+ # All cached relationships
273283 cached_rels = [r for r in cls .relationships () if r .cache ]
284+
274285 # Check if any cached entities have their own cached fields
275286 for rel in cached_rels :
276- cached_entities = EntityMeta .mappings .get (rel .name , [])
277- nested = [entity .name for entity in cached_entities if entity .cache ]
287+ nested = cls .cached_entities (rel .name )
278288 if nested :
279289 cls .raise_for_nested_cache (utils .snake_case (cls .__name__ ),
280- rel .name , nested )
290+ rel .name , list ( nested . keys ()) )
281291
282- # If this entity caches any other entity
283- # then check if any entity caches this entity
292+ # If the current Entity (cls) has any cached relationships (cached_rels)
293+ # then no other defined Entity (entities in EntityMeta.relationship_mappings) can cache this Entity.
284294 if cached_rels :
285- for entity_name , entity_relationships in EntityMeta .mappings .items (
286- ):
287- attr = {rel .name : rel for rel in entity_relationships
288- }.get (utils .snake_case (cls .__name__ ))
289- if attr and attr .cache :
295+ # For all currently defined Entities
296+ for entity_name in EntityMeta .relationship_mappings :
297+ # Get all cached ToOne relationships
298+ rels = cls .cached_entities (entity_name )
299+ # Check if the current Entity (cls) is referenced by the Entity with `entity_name`
300+ rel = rels .get (utils .snake_case (cls .__name__ ))
301+ # If rel exists and is cached then raise an exception
302+ # This means `entity_name` caches `cls` which cached items in `cached_rels`
303+ if rel and rel .cache :
290304 cls .raise_for_nested_cache (
291305 utils .snake_case (entity_name ),
292306 utils .snake_case (cls .__name__ ),
0 commit comments