Skip to content

Commit 7b3b6f1

Browse files
committed
idsync: write model.json only if updated + unit test #25
1 parent d145422 commit 7b3b6f1

2 files changed

Lines changed: 150 additions & 14 deletions

File tree

objectbox/model/idsync.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,12 @@ def _validate_matching_prop(self, entity: _Entity, prop: Property, prop_json: Di
165165
except ValueError as error:
166166
raise ValueError(f"Property {entity.name}.{prop.name} mismatches property found in JSON file: {error}")
167167

168-
def _load_or_assign_index(self, entity: _Entity, prop: Property, prop_json: Optional[Dict[str, Any]]):
168+
def _load_or_assign_index(self, entity: _Entity, prop: Property, prop_json: Optional[Dict[str, Any]]) -> bool:
169169
assert prop.index is not None
170170
index = prop.index
171171

172+
write_json = False
173+
172174
# Fetch index ID/UID from JSON file
173175
iduid_json = None
174176
if (prop_json is not None) and ("indexId" in prop_json):
@@ -187,8 +189,13 @@ def _load_or_assign_index(self, entity: _Entity, prop: Property, prop_json: Opti
187189
else: # Assign new ID to new index
188190
index.iduid = IdUid(self.model.last_index_iduid.id + 1, index.uid)
189191
self.model.last_index_iduid = index.iduid
192+
write_json = True
193+
194+
return write_json
195+
196+
def _load_or_assign_property(self, entity: _Entity, prop: Property, entity_json: Optional[Dict[str, Any]]) -> bool:
197+
write_json = False
190198

191-
def _load_or_assign_property(self, entity: _Entity, prop: Property, entity_json: Optional[Dict[str, Any]]):
192199
prop_json = None
193200
if prop.has_uid():
194201
if entity_json is not None:
@@ -197,6 +204,8 @@ def _load_or_assign_property(self, entity: _Entity, prop: Property, entity_json:
197204
# User provided a UID not matching any property (within the entity), make sure it's not assigned
198205
# elsewhere
199206
self._validate_uid_unassigned(prop.uid)
207+
else:
208+
write_json = prop.name != prop_json["name"] # If renaming we shall update the JSON
200209
else:
201210
if entity_json is not None:
202211
prop_json = self._find_property_json_by_name(entity_json, prop.name)
@@ -210,20 +219,31 @@ def _load_or_assign_property(self, entity: _Entity, prop: Property, entity_json:
210219
prop.iduid.uid = self._generate_uid()
211220
prop.iduid = IdUid(entity.last_property_iduid.id + 1, prop.iduid.uid)
212221
entity.last_property_iduid = prop.iduid
222+
write_json = True
213223

214224
if prop.index is not None:
215-
self._load_or_assign_index(entity, prop, prop_json)
225+
write_json |= self._load_or_assign_index(entity, prop, prop_json)
226+
227+
return write_json
228+
229+
def _load_or_assign_entity(self, entity: _Entity) -> bool:
230+
write_json = False
216231

217-
def _load_or_assign_entity(self, entity: _Entity):
218232
# entity_json = None
219233
if entity.has_uid():
220234
entity_json = self._find_entity_json_by_uid(entity.uid)
221235
if entity_json is None:
222236
# User provided a UID not matching any entity, make sure it's not assigned elsewhere
223237
self._validate_uid_unassigned(entity.uid)
238+
else:
239+
write_json = entity.name != entity_json["name"] # If renaming we shall update the JSON
224240
else:
225241
entity_json = self._find_entity_json_by_name(entity.name)
226242

243+
# Write JSON if the number of properties differs (to handle removed property)
244+
if entity_json is not None:
245+
write_json |= len(entity.properties) != len(entity_json["properties"])
246+
227247
if entity_json is not None: # Load existing IDs from JSON
228248
entity.iduid = IdUid.from_str(entity_json["id"])
229249
entity.last_property_iduid = IdUid.from_str(entity_json["lastPropertyId"])
@@ -233,27 +253,42 @@ def _load_or_assign_entity(self, entity: _Entity):
233253
entity.iduid = IdUid(self.model.last_entity_iduid.id + 1, entity.iduid.uid)
234254
self.model.last_entity_iduid = entity.iduid
235255
entity.last_property_iduid = IdUid(0, 0)
256+
write_json = True
236257

237258
# Load properties
238259
for prop in entity.properties:
239-
self._load_or_assign_property(entity, prop, entity_json)
260+
write_json |= self._load_or_assign_property(entity, prop, entity_json)
240261

241-
def sync(self):
242-
""" Syncs the provided model with the model JSON file. """
262+
return write_json
263+
264+
def sync(self) -> bool:
265+
""" Syncs the provided model with the model JSON file.
266+
Returns True if the model JSON was written. """
243267

244268
if self.model_json is not None:
245269
self.model.last_entity_iduid = IdUid.from_str(self.model_json["lastEntityId"])
246270
self.model.last_index_iduid = IdUid.from_str(self.model_json["lastIndexId"])
247271
# self.model.last_relation_iduid =
248272

273+
write_json = False
274+
275+
# Write JSON if the number of entities differs (to handle removed entity)
276+
if self.model_json is not None:
277+
write_json |= len(self.model_json["entities"]) != len(self.model.entities)
278+
249279
for entity in self.model.entities:
250-
self._load_or_assign_entity(entity)
280+
write_json |= self._load_or_assign_entity(entity)
281+
282+
if write_json:
283+
logger.info(f"Model changed, writing model.json: {self.model_filepath}")
284+
self._save_model_json()
251285

252-
self._save_model_json()
286+
return write_json
253287

254288

255-
def sync_model(model: Model, model_filepath: str = "objectbox-model.json"):
256-
""" Syncs the provided model with the model JSON file. """
289+
def sync_model(model: Model, model_filepath: str = "objectbox-model.json") -> bool:
290+
""" Syncs the provided model with the model JSON file.
291+
Returns True if the model JSON was written. """
257292

258293
id_sync = IdSync(model, model_filepath)
259-
id_sync.sync()
294+
return id_sync.sync()

tests/test_idsync.py

Lines changed: 103 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from typing import *
12
from objectbox import *
23
from objectbox.model import *
34
from objectbox.model.entity import _Entity
@@ -18,14 +19,19 @@ def __init__(self):
1819
self.model_path = 'test.json'
1920
if os.path.exists(self.model_path):
2021
os.remove(self.model_path)
22+
self.model = None
2123
self.db_path = 'testdb'
2224
Store.remove_db_files(self.db_path)
23-
def sync(self, model):
25+
26+
def sync(self, model: Model) -> bool:
2427
self.model = model
25-
sync_model(self.model, self.model_path)
28+
return sync_model(self.model, self.model_path)
29+
2630
def json(self):
2731
return json.load(open(self.model_path))
32+
2833
def store(self):
34+
assert self.model is not None
2935
return Store(model=self.model, directory=self.db_path)
3036

3137

@@ -367,3 +373,98 @@ class EntityA:
367373
assert box.count() == 1
368374
assert not hasattr(box.get(1), "name")
369375
assert box.get(1).renamed_name == "Luca"
376+
377+
378+
def test_model_json_updates(env):
379+
""" Tests situations where the model JSON should be written/should not be written. """
380+
381+
def assert_model_json_written(value: bool, *entities: _Entity):
382+
model = Model()
383+
for entity in entities:
384+
model.entity(entity)
385+
assert env.sync(model) == value
386+
387+
# Init
388+
@Entity()
389+
class EntityA:
390+
id = Id()
391+
name = String()
392+
assert_model_json_written(True, EntityA)
393+
394+
# Add entity
395+
@Entity()
396+
class EntityB:
397+
id = Id()
398+
name = String()
399+
assert_model_json_written(True, EntityB)
400+
401+
entityb_uid = EntityB.uid
402+
403+
# Rename entity
404+
@Entity(uid=entityb_uid)
405+
class EntityC:
406+
id = Id()
407+
name = String()
408+
assert_model_json_written(True, EntityC)
409+
410+
# Noop
411+
model = Model()
412+
model.entity(EntityC)
413+
assert not env.sync(model)
414+
415+
# Add entity
416+
@Entity()
417+
class EntityD:
418+
id = Id()
419+
name = String()
420+
age = Int8()
421+
assert_model_json_written(True, EntityC, EntityD)
422+
423+
# Noop
424+
assert_model_json_written(False, EntityC, EntityD)
425+
426+
# Replace entity
427+
@Entity()
428+
class EntityE:
429+
id = Id()
430+
assert_model_json_written(True, EntityD, EntityE)
431+
432+
# Noop
433+
assert_model_json_written(False, EntityD, EntityE)
434+
435+
# Remove entity
436+
assert_model_json_written(True, EntityD)
437+
438+
# Noop
439+
assert_model_json_written(False, EntityD)
440+
441+
# Add property
442+
@Entity()
443+
class EntityD:
444+
id = Id()
445+
name = String()
446+
age = Int8()
447+
my_prop = String()
448+
assert_model_json_written(True, EntityD)
449+
450+
my_prop_uid = EntityD.get_property("my_prop").uid
451+
452+
# Rename property
453+
@Entity()
454+
class EntityD:
455+
id = Id()
456+
name = String()
457+
age = Int8()
458+
my_prop_renamed = String(uid=my_prop_uid)
459+
assert_model_json_written(True, EntityD)
460+
461+
# Noop
462+
assert_model_json_written(False, EntityD)
463+
464+
# Remove property
465+
@Entity()
466+
class EntityD:
467+
id = Id()
468+
name = String()
469+
age = Int8()
470+
assert_model_json_written(True, EntityD)

0 commit comments

Comments
 (0)