From bca91443b33e3d55815114b45aaaead860c9eea6 Mon Sep 17 00:00:00 2001 From: Pal Kerecsenyi Date: Tue, 1 Sep 2026 10:20:18 +0200 Subject: [PATCH] fix(load): make EP split deep copy more precise * The `deepcopy(entry)` was copying everything, including the reference to the logger in each class. This was not actually serialisable so could not be deep copied, which led to an error. * This is now fixed by only deep copying the individual parts that need copying. Some copying is needed (otherwise the restricted/public records would overwrite eachother). I tested this locally and it's working for NA61/2/3/4 records --- .../load/entities/ep_migration_entry_load.py | 13 ++----- .../rdm/records/load/entities/ep_split.py | 39 +++++++++++++------ 2 files changed, 32 insertions(+), 20 deletions(-) diff --git a/cds_migrator_kit/rdm/records/load/entities/ep_migration_entry_load.py b/cds_migrator_kit/rdm/records/load/entities/ep_migration_entry_load.py index 9ccd6889..1d04b2f9 100644 --- a/cds_migrator_kit/rdm/records/load/entities/ep_migration_entry_load.py +++ b/cds_migrator_kit/rdm/records/load/entities/ep_migration_entry_load.py @@ -131,15 +131,10 @@ def _load_split(self, entry: MigrationEntry, recid): ) if self.dry_run: - # 1. Create restricted record - restricted_records = restricted_record_load.load(restricted_entry) - restricted_record_state = restricted_record_load.build_record_state( - recid, restricted_records - ) - # 2. Create and approve EP approval request - approval_request_load.create(restricted_record_state) - # 3. Create public record - public_record_load.load(public_entry) + # We are already validating the approval request, so trying to load it + # is not necessary. There is now uow here so we can't call `approval_request_load.create` + restricted_record_load.dry_load() + public_record_load.dry_load() # We need to finalise here, or the log would only list the # records that failed. self.migration_logger.finalise_record(recid) diff --git a/cds_migrator_kit/rdm/records/load/entities/ep_split.py b/cds_migrator_kit/rdm/records/load/entities/ep_split.py index f666fa66..9d2878f8 100644 --- a/cds_migrator_kit/rdm/records/load/entities/ep_split.py +++ b/cds_migrator_kit/rdm/records/load/entities/ep_split.py @@ -6,9 +6,10 @@ # the terms of the MIT License; see LICENSE file for more details. """Build public and restricted load entries for EP approval records.""" + import re from collections import OrderedDict -from copy import deepcopy +from copy import copy, deepcopy from typing import Dict from flask import current_app @@ -35,6 +36,24 @@ def _cern_scientific_community_id(): return current_app.config["CDS_CERN_SCIENTIFIC_COMMUNITY_ID"] +def _split_copy(entry: MigrationEntry) -> MigrationEntry: + """Return a deep-ish copy of ``entry`` without copying things that can't be copied.""" + memo = {} + split = dict(entry) + + record = copy(entry["record"]) + record.body = deepcopy(entry["record"].body, memo) + split["record"] = record + + parent = copy(entry["parent"]) + parent.body = deepcopy(entry["parent"].body, memo) + parent.communities = deepcopy(entry["parent"].communities, memo) + parent.access_grants = deepcopy(entry["parent"].access_grants, memo) + split["parent"] = parent + + return split + + class MetadataEntry: """Build a load entry for the public or restricted EP approval split.""" @@ -49,7 +68,7 @@ def identifiers(self, identifiers): def build(self) -> MigrationEntry: """Return a load entry with split files and modified metadata.""" - split = deepcopy(self.entry) + split = _split_copy(self.entry) split.pop("ep_approval", None) split["versions"] = self._build_versions(split) self._apply_metadata(split) @@ -208,12 +227,12 @@ def identifiers(self, identifiers): else: kept.append(id_entry) - kept.append( - { - "identifier": self.approval_request.report_number, - "scheme": "apprn", - } - ) + apprn_entry = { + "identifier": self.approval_request.report_number, + "scheme": "apprn", + } + if apprn_entry not in kept: + kept.append(apprn_entry) if removed: self._log_removed_identifiers(removed, self._access_status) @@ -255,9 +274,7 @@ def _remove_cern_scientific_community(self, entry): # mutating in place: entry["parent"].communities is the same dict # object, no need to set it back. communities = entry["parent"].communities - ids = [ - cid for cid in communities.get("ids", []) if cid != community_id - ] + ids = [cid for cid in communities.get("ids", []) if cid != community_id] communities["ids"] = ids if communities.get("default") == community_id: communities["default"] = ids[0] if ids else None