@@ -76,20 +76,33 @@ def _save_model_json(self):
7676
7777 # *** Sync ***
7878
79+ def _find_entity_json_by_uid (self , uid : int ) -> Optional [Dict [str , Any ]]:
80+ """ Finds entity JSON by UID. """
81+ if self .model_json is None :
82+ return None
83+ for entity_json in self .model_json ["entities" ]:
84+ if IdUid .from_str (entity_json ["id" ]).uid == uid :
85+ return entity_json
86+ return None
87+
7988 def _find_entity_json_by_name (self , entity_name : str ) -> Optional [Dict [str , Any ]]:
80- """ Finds the entity data by name in the model JSON file . """
89+ """ Finds entity JSON by name. """
8190 if self .model_json is None :
8291 return None
8392 for entity_json in self .model_json ["entities" ]:
8493 if entity_json ["name" ] == entity_name :
8594 return entity_json
8695 return None
8796
88- def _find_property_json_by_name (self , entity_name : str , prop_name : str ) -> Optional [Dict [str , Any ]]:
89- """ Finds the entity property data by name in the model JSON file. """
90- entity_json = self ._find_entity_json_by_name (entity_name )
91- if entity_json is None :
92- return None
97+ def _find_property_json_by_uid (self , entity_json : Dict [str , Any ], uid : int ) -> Optional [Dict [str , Any ]]:
98+ """ Finds entity property JSON by property UID. """
99+ for prop_json in entity_json ["properties" ]:
100+ if IdUid .from_str (prop_json ["id" ]).uid == uid :
101+ return prop_json
102+ return None
103+
104+ def _find_property_json_by_name (self , entity_json : Dict [str , Any ], prop_name : str ) -> Optional [Dict [str , Any ]]:
105+ """ Finds entity property JSON by property name. """
93106 for prop_json in entity_json ["properties" ]:
94107 if prop_json ["name" ] == prop_name :
95108 return prop_json
@@ -100,127 +113,126 @@ def _generate_uid() -> int:
100113 return random .getrandbits (63 ) + 1 # 0 would be invalid
101114
102115 def _validate_uid_unassigned (self , uid : int ):
103- """ Validates that the UID is not assigned for any other entity/property/index. """
104- pass # TODO
116+ """ Validates that a user supplied UID is not assigned for any other entity/property/index.
117+ Raises a ValueError if the UID is already assigned elsewhere.
118+ """
105119
106- def _sync_index_id (self , entity : _Entity , prop : Property , index : Union [Index , HnswIndex ]) -> None :
107- """ Given an index, syncs its ID/UID with the JSON file. """
120+ try :
121+ entity_json = self ._find_entity_json_by_uid (uid )
122+ if entity_json is not None :
123+ raise ValueError (f"in Entity \" { entity_json ['name' ]} \" ({ entity_json ['id' ]} )" )
124+
125+ for entity_json in self .model_json ["entities" ]:
126+ prop_json = self ._find_property_json_by_uid (entity_json , uid )
127+ if prop_json is not None :
128+ raise ValueError (f"in Property \" { entity_json ['name' ]} .{ prop_json ['name' ]} \" ({ prop_json ['id' ]} )" )
129+ for prop_json in entity_json ["properties" ]:
130+ if "indexId" in prop_json and IdUid .from_str (prop_json ["indexId" ]).uid == uid :
131+ raise ValueError (
132+ f"in Property index \" { entity_json ['name' ]} .{ prop_json ['name' ]} \" ({ prop_json ['id' ]} )" )
133+ except ValueError as error :
134+ raise ValueError (f"User supplied UID \" { uid } \" found { error } " )
135+
136+ def _validate_matching_prop (self , entity : _Entity , prop : Property , prop_json : Dict [str , Any ]):
137+ """ Validates that the given property matches the JSON property. """
138+ try :
139+ # Don't check name equality as the property could be matched by UID (rename)
140+ # if validate_name and prop.name != prop_json["name"]:
141+ # raise ValueError(f"name {prop.name} != name {prop_json['name']} (in JSON)")
142+ if prop ._ob_type != prop_json ["type" ]:
143+ raise ValueError (f"OBX type { prop ._ob_type } != OBX type { prop_json ['type' ]} (in JSON)" )
144+ elif prop ._flags != prop_json ["flags" ]:
145+ raise ValueError (f"flags { prop ._flags } != flags { prop_json ['flags' ]} (in JSON)" )
146+ elif prop .index is None and "indexId" in prop_json :
147+ raise ValueError ("property hasn't index, but index found in JSON" )
148+ elif prop .index is not None and "indexId" not in prop_json :
149+ raise ValueError ("property has index, but index not found in JSON" )
150+ except ValueError as error :
151+ raise ValueError (f"Property { entity .name } .{ prop .name } mismatches property found in JSON file: { error } " )
152+
153+ def _load_or_assign_index (self , entity : _Entity , prop : Property , prop_json : Optional [Dict [str , Any ]]):
154+ assert prop .index is not None
155+ index = prop .index
156+
157+ # Fetch index ID/UID from JSON file
108158 iduid_json = None
109- prop_json = self ._find_property_json_by_name (entity .name , prop .name )
110- if prop_json is not None and "indexId" in prop_json :
159+ if (prop_json is not None ) and ("indexId" in prop_json ):
111160 iduid_json = IdUid .from_str (prop_json ["indexId" ])
112- if iduid_json is None : # Index not present in JSON
113- if index .has_uid ():
114- self ._validate_uid_unassigned (index .uid )
115- else :
116- gen_uid = self ._generate_uid ()
117- index .iduid .uid = gen_uid
161+
162+ # User provided a UID not matching index's, make sure it's not assigned elsewhere
163+ if index .has_uid () and (iduid_json is not None ) and (index .uid != iduid_json .uid ):
164+ self ._validate_uid_unassigned (index .uid )
165+
166+ # Generate UID only if not supplied by the user, and index isn't found in JSON
167+ if not index .has_uid () and iduid_json is None :
168+ index .iduid .uid = self ._generate_uid ()
169+
170+ if (iduid_json is not None ) and (not index .has_uid () or index .iduid .uid == iduid_json .uid ): # Load
171+ index .iduid = IdUid .from_str (prop_json ["indexId" ])
172+ else : # Assign
118173 index .iduid = IdUid (self .model .last_index_iduid .id + 1 , index .uid )
119- else : # Index present in JSON
120- if index .has_uid () and index .uid != iduid_json .uid :
121- self ._validate_uid_unassigned (index .uid )
122- index .iduid = IdUid (self .model .last_index_iduid .id + 1 , index .uid ) # Assign ID
123- else : # not index.has_uid() or index.uid != iduid_json.uid
124- index .iduid = iduid_json
174+ self .model .last_index_iduid = index .iduid
125175
126- def _validate_matching_prop (self , entity : _Entity , prop : Property , prop_json : Dict [str , Any ]):
127- """ Validates that the given property matches the JSON property. """
128- assert prop .name == prop_json ["name" ], \
129- f"Property { entity .name } .{ prop .name } mismatches property found in JSON file " \
130- f"(name { prop .name } != type { prop_json ['name' ]} )" # Shouldn't happen (JSON property is got by name)
131- assert prop ._ob_type == prop_json ["type" ], \
132- f"Property { entity .name } .{ prop .name } mismatches property found in JSON file " \
133- f"(type { prop ._ob_type } != type { prop_json ['type' ]} )"
134- assert prop ._flags == prop_json ["flags" ], \
135- f"Property { entity .name } .{ prop .name } mismatches property found in JSON file " \
136- f"(flags { prop ._flags } != type { prop_json ['flags' ]} )"
137-
138- def _sync_property_id (self , entity : _Entity , prop : Property ) -> None :
139- """ Given an entity's property, syncs its ID/UID with the JSON file. """
140- prop_json = self ._find_property_json_by_name (entity .name , prop .name )
141- if prop_json is None : # Property not present in JSON
142- if prop .has_uid ():
176+ def _load_or_assign_property (self , entity : _Entity , prop : Property , entity_json : Optional [Dict [str , Any ]]):
177+ prop_json = None
178+ if prop .has_uid ():
179+ if entity_json is not None :
180+ prop_json = self ._find_property_json_by_uid (entity_json , prop .uid )
181+ if prop_json is None :
182+ # User provided a UID not matching any property (within the entity), make sure it's not assigned
183+ # elsewhere
143184 self ._validate_uid_unassigned (prop .uid )
144- else :
185+ else :
186+ if entity_json is not None :
187+ prop_json = self ._find_property_json_by_name (entity_json , prop .name )
188+
189+ if prop_json is not None : # Load
190+ # Property was matched with a JSON property (either by UID or by name), make sure they're equal
191+ self ._validate_matching_prop (entity , prop , prop_json )
192+ prop .iduid = IdUid .from_str (prop_json ["id" ])
193+ else : # Assign
194+ if not prop .has_uid ():
145195 prop .iduid .uid = self ._generate_uid ()
146- prop .iduid = IdUid (entity .last_property_iduid .id + 1 , prop .uid ) # Assign ID
147- else : # Property present in JSON
148- iduid_json = IdUid .from_str (prop_json ["id" ])
149- if prop .has_uid () and prop .uid != iduid_json .uid : # New property
150- self ._validate_uid_unassigned (prop .uid )
151- prop .iduid = IdUid (entity .last_property_iduid .id + 1 , prop .uid ) # Assign ID
152- else : # not prop.has_uid() or prop.uid == iduid_json.uid
153- self ._validate_matching_prop (entity , prop , prop_json )
154- prop .iduid = iduid_json
155-
156- def _validate_matching_entity (self , entity : _Entity , entity_json : Dict [str , Any ]):
157- """ Validates that the given entity matches the JSON entity. """
158- assert entity .name == entity_json ["name" ], \
159- f"Entity { entity .name } mismatches property found in JSON file " \
160- f"(name { entity .name } != type { entity_json ['name' ]} )" # Shouldn't happen (JSON entity is got by name)
161- assert len (entity .properties ) == len (entity_json ["properties" ]), \
162- f"Entity { entity .name } mismatches entity found in JSON file " \
163- f"({ len (entity .properties )} properties != { len (entity_json ['properties' ])} properties)"
164- # TODO check relations count
165- pass # TODO check properties' fields?
166-
167- def _sync_entity_id (self , entity : _Entity ) -> None :
168- """ Given an entity, syncs its ID/UID with the JSON file. """
169- entity_json = self ._find_entity_json_by_name (entity .name )
170- if entity_json is None : # Entity not present in JSON file
171- if entity .has_uid ():
196+ prop .iduid = IdUid (entity .last_property_iduid .id + 1 , prop .iduid .uid )
197+ entity .last_property_iduid = prop .iduid
198+
199+ if prop .index is not None :
200+ self ._load_or_assign_index (entity , prop , prop_json )
201+
202+ def _load_or_assign_entity (self , entity : _Entity ):
203+ # entity_json = None
204+ if entity .has_uid ():
205+ entity_json = self ._find_entity_json_by_uid (entity .uid )
206+ if entity_json is None :
207+ # User provided a UID not matching any entity, make sure it's not assigned elsewhere
172208 self ._validate_uid_unassigned (entity .uid )
173- else :
209+ else :
210+ entity_json = self ._find_entity_json_by_name (entity .name )
211+
212+ if entity_json is not None : # Load
213+ entity .iduid = IdUid .from_str (entity_json ["id" ])
214+ entity .last_property_iduid = IdUid .from_str (entity_json ["lastPropertyId" ])
215+ else : # Assign
216+ if not entity .has_uid ():
174217 entity .iduid .uid = self ._generate_uid ()
175- entity .iduid = IdUid (self .model .last_entity_iduid .id + 1 , entity .uid ) # Assign ID
176- else : # Entity present in JSON file
177- iduid_json = IdUid .from_str (entity_json ["id" ])
178- if entity .has_uid () and entity .uid != iduid_json .uid : # New entity
179- self ._validate_uid_unassigned (entity .uid )
180- entity .iduid = IdUid (self .model .last_entity_iduid .id + 1 , entity .uid ) # Assign ID
181- else : # not entity.has_uid() or entity.uid == iduid_json.uid
182- self ._validate_matching_entity (entity , entity_json )
183- entity .iduid = iduid_json
218+ entity .iduid = IdUid (self .model .last_entity_iduid .id + 1 , entity .iduid .uid )
219+ self .model .last_entity_iduid = entity .iduid
220+ entity .last_property_iduid = IdUid (0 , 0 )
221+
222+ # Load properties
223+ for prop in entity .properties :
224+ self ._load_or_assign_property (entity , prop , entity_json )
184225
185226 def sync (self ):
186227 """ Syncs the provided model with the model JSON file. """
187228
188- # Sync entities ID/UID
189229 if self .model_json is not None :
190230 self .model .last_entity_iduid = IdUid .from_str (self .model_json ["lastEntityId" ])
191- else :
192- self .model .last_entity_iduid = IdUid (0 , 0 )
193- for entity in self .model .entities :
194- self ._sync_entity_id (entity )
195- if entity .id > self .model .last_entity_iduid .id : # If assignment occurred, update last_entity_iduid
196- self .model .last_entity_iduid = entity .iduid
197-
198- # Sync properties ID/UID
199- for entity in self .model .entities :
200- entity_json = self ._find_entity_json_by_name (entity .name )
201- if entity_json is not None :
202- entity .last_property_iduid = IdUid .from_str (entity_json ["lastPropertyId" ])
203- else :
204- entity .last_property_iduid = IdUid (0 , 0 )
205- for prop in entity .properties :
206- self ._sync_property_id (entity , prop )
207- if prop .id > entity .last_property_iduid .id : # If assignment occurred, update last_property_iduid
208- entity .last_property_iduid = prop .iduid
209-
210- # Sync indexes ID/UID
211- if self .model_json is not None :
212231 self .model .last_index_iduid = IdUid .from_str (self .model_json ["lastIndexId" ])
213- else :
214- self .model .last_index_iduid = IdUid (0 , 0 )
215- for entity in self .model .entities :
216- for prop in entity .properties :
217- if prop .index is not None :
218- index = prop .index
219- self ._sync_index_id (entity , prop , index )
220- if index .id > self .model .last_index_iduid .id : # If assignment occurred, update last_index_iduid
221- self .model .last_index_iduid = index .iduid
232+ # self.model.last_relation_iduid =
222233
223- # TODO Sync relations ID/UID(s)
234+ for entity in self .model .entities :
235+ self ._load_or_assign_entity (entity )
224236
225237 self ._save_model_json ()
226238
0 commit comments