Skip to content

Commit 6035d60

Browse files
committed
dev:minimal WoRMS compatibility: handle remote deletion
1 parent 1c53911 commit 6035d60

File tree

2 files changed

+48
-8
lines changed

2 files changed

+48
-8
lines changed

py/API_operations/TaxoManager.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -567,25 +567,28 @@ def pull_updates(self) -> Dict[str, Any]:
567567
nbr_rows = len(updates)
568568
nbr_updates = nbr_inserts = 0
569569

570-
to_rename: Dict[ClassifIDT, ClassifIDT] = {}
570+
to_delete: List[ClassifIDT] = []
571571

572572
for a_json_taxon in updates:
573573
# Convert non-str fields
574574
json_taxon_id = int(a_json_taxon["id"])
575575
lastupdate_datetime = datetime.datetime.strptime(
576576
a_json_taxon["lastupdate_datetime"], "%Y-%m-%d %H:%M:%S"
577577
)
578-
# Store rename intentions
579-
if a_json_taxon["rename_to"]:
580-
to_rename[json_taxon_id] = int(a_json_taxon["rename_to"])
578+
must_delete = a_json_taxon["taxostatus"] == "X"
581579
# Read taxon from DB
582580
taxon = self.session.query(Taxonomy).get(json_taxon_id)
583581
if taxon is not None:
582+
if must_delete:
583+
to_delete.append(json_taxon_id)
584+
continue
584585
# The taxon is already present
585586
if taxon.lastupdate_datetime == lastupdate_datetime:
586587
continue # already up to date
587588
nbr_updates += 1
588589
else:
590+
if must_delete:
591+
continue # Should not happen if timestamps are OK
589592
# The taxon is not present, create it
590593
nbr_inserts += 1
591594
taxon = Taxonomy()
@@ -595,10 +598,9 @@ def pull_updates(self) -> Dict[str, Any]:
595598
for a_col in self.UpdatableCols:
596599
setattr(taxon, a_col, a_json_taxon[a_col])
597600
taxon.lastupdate_datetime = lastupdate_datetime
598-
self.session.commit()
599-
# Manage rename_to
600-
if len(to_rename) > 0:
601-
TaxonomyBO.do_renames(self.session, to_rename)
601+
if len(to_delete) > 0:
602+
TaxonomyBO.do_deletes(self.session, to_delete)
603+
self.session.commit()
602604

603605
# if gvp('updatestat') == 'Y':
604606
# msg = DoSyncStatUpdate()

py/BO/Taxonomy.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,44 @@ def do_renames(session: Session, to_rename: Dict[ClassifIDT, ClassifIDT]):
419419
# !!!! ECOPART LINK !!!!
420420
# appli.part.prj.GlobalTaxoCompute()
421421

422+
@classmethod
423+
def do_deletes(cls, session: Session, to_delete: List[ClassifIDT]) -> None:
424+
#### Direct Foreign Keys to `taxonomy.id`
425+
# 1. **`obj_head.classif_id`**: Points to `taxonomy.id`. This represents the current classification of an object.
426+
# 2. **`objectsclassifhisto.classif_id`**: Points to `taxonomy.id` (with `ondelete="CASCADE"`). This stores the history of classifications for an object.
427+
# 3. **`taxo_change_log.to_id`**: Points to `taxonomy.id` (with `ondelete="CASCADE"`). This logs the target taxon in a mass classification change.
428+
# 4. **`taxo_change_log.from_id`**: Points to `taxonomy.id` (with `ondelete="CASCADE"`). This logs the original taxon in a mass classification change.
429+
# 5. **`prediction.classif_id`**: Points to `taxonomy.id` (with `ondelete="CASCADE"`). This stores the predicted taxon for an object.
430+
# 6. **`prediction_histo.classif_id`**: Points to `taxonomy.id` (with `ondelete="CASCADE"`). This stores the history of predicted taxa.
431+
#### Other Implicit Relations
432+
# * **`taxonomy.parent_id`**: Although not explicitly defined with a `ForeignKey` constraint in the SQLAlchemy model (it is a simple `INTEGER` column), it logically points to `taxonomy.id` to represent the taxonomic tree structure.
433+
# * **`taxonomy.rename_to`**: Similarly, this `INTEGER` column is used to store an "advised" target taxon for mass category changes, logically referring to another `taxonomy.id`.
434+
# * **`taxo_recast.transforms`**: This `JSONB` column stores mapping in the form `{from:to}`, where both values are taxonomic IDs, though they are not enforced by database-level foreign key constraints.
435+
436+
# We want to protect 1. and 2.
437+
logger.info("Taxo delete, list: %s", to_delete)
438+
sql = text("SELECT DISTINCT objid FROM obj_head WHERE classif_id = ANY (:een)")
439+
res: Result = session.execute(sql, {"een": list(to_delete)})
440+
prevent_obj_head = {an_id for an_id, in res}
441+
if len(prevent_obj_head) > 0:
442+
logger.error("Unsafe deletion due to objects %s", prevent_obj_head)
443+
sql = text("SELECT DISTINCT objid FROM objectsclassifhisto WHERE classif_id = ANY (:een)")
444+
res: Result = session.execute(sql, {"een": list(to_delete)})
445+
prevent_obj_histo = {an_id for an_id, in res}
446+
if len(prevent_obj_histo) > 0:
447+
logger.error("Unsafe deletion due to objects history %s", prevent_obj_histo)
448+
# assert len(prevent_obj_head) == 0 and len(prevent_obj_histo) == 0, "Cannot achieve safe deletion"
449+
logger.info("deleting categories")
450+
session.execute(text("alter table taxonomy disable trigger all"))
451+
try:
452+
for a_taxon in to_delete:
453+
logger.info("deleting category %s", a_taxon)
454+
taxon = session.query(Taxonomy).get(a_taxon)
455+
session.delete(taxon)
456+
finally:
457+
session.execute(text("alter table taxonomy enable trigger all"))
458+
459+
422460

423461
class TaxonBOSet(object):
424462
"""

0 commit comments

Comments
 (0)