1
1
from enum import Enum , auto
2
- from typing import Dict , Union
2
+ from typing import Dict , List , Union
3
3
4
4
from labelbox import utils
5
5
from labelbox .exceptions import InvalidAttributeError
@@ -239,22 +239,31 @@ class EntityMeta(type):
239
239
of the Entity class object so they can be referenced for example like:
240
240
Entity.Project.
241
241
"""
242
- mappings : Dict [str , "Entity" ] = {}
242
+ # Maps Entity name to Relationships for all currently defined Entities
243
+ relationship_mappings : Dict [str , List [Relationship ]] = {}
243
244
244
245
def __init__ (cls , clsname , superclasses , attributedict ):
245
246
super ().__init__ (clsname , superclasses , attributedict )
246
247
cls .validate_cached_relationships ()
247
248
if clsname != "Entity" :
248
249
setattr (Entity , clsname , cls )
249
- EntityMeta .mappings [utils .snake_case (
250
+ EntityMeta .relationship_mappings [utils .snake_case (
250
251
cls .__name__ )] = cls .relationships ()
251
252
252
253
@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 ] ):
254
255
raise TypeError (
255
256
"Cannot cache a relationship to an Entity with its own cached relationship(s). "
256
257
f"`{ first } ` caches `{ middle } ` which caches `{ last } `" )
257
258
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
+
258
267
def validate_cached_relationships (cls ):
259
268
"""
260
269
Graphql doesn't allow for infinite nesting in queries.
@@ -270,23 +279,28 @@ def validate_cached_relationships(cls):
270
279
A two way check is necessary because checks are performed as classes are being defined.
271
280
As opposed to after all objects have been created.
272
281
"""
282
+ # All cached relationships
273
283
cached_rels = [r for r in cls .relationships () if r .cache ]
284
+
274
285
# Check if any cached entities have their own cached fields
275
286
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 )
278
288
if nested :
279
289
cls .raise_for_nested_cache (utils .snake_case (cls .__name__ ),
280
- rel .name , nested )
290
+ rel .name , list ( nested . keys ()) )
281
291
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.
284
294
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 :
290
304
cls .raise_for_nested_cache (
291
305
utils .snake_case (entity_name ),
292
306
utils .snake_case (cls .__name__ ),
0 commit comments