From d81229bcc45d2010764b3a745f1ffcf87e25fb92 Mon Sep 17 00:00:00 2001 From: 20C Date: Mon, 20 May 2024 11:34:48 +0000 Subject: [PATCH] Support 202404 Fixes issue with automatic resolving of unique constraint errors (#85) --- CHANGELOG.md | 2 ++ CHANGELOG.yaml | 3 ++- src/peeringdb/_update.py | 17 +++++++++++------ src/peeringdb/fetch.py | 5 +++-- tests/test_sync.py | 40 +++++++++++++++++++++++++++++++++------- 5 files changed, 51 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a2bc3..1f38e4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +### Fixed +- fixes automatic solving of unique constraint errors (#85) ## 2.0.0 diff --git a/CHANGELOG.yaml b/CHANGELOG.yaml index a4cef5b..cfd6fb7 100644 --- a/CHANGELOG.yaml +++ b/CHANGELOG.yaml @@ -1,6 +1,7 @@ Unreleased: added: [] - fixed: [] + fixed: + - fixes automatic solving of unique constraint errors (#85) changed: [] deprecated: [] removed: [] diff --git a/src/peeringdb/_update.py b/src/peeringdb/_update.py index e01ef3d..5d82771 100644 --- a/src/peeringdb/_update.py +++ b/src/peeringdb/_update.py @@ -244,13 +244,18 @@ def update_one(self, res, pk: int, depth=0): :param depth: Depth of recursion :return: """ - self.fetcher.load(res.tag, 0) - row = self.fetcher.get(res.tag, pk, depth=depth) - obj, ret = self.create_obj(row, res) - if ret: + if depth != 0: + # no longer relevant, deprecation warning + self._log.warning( + "update_one: depth parameter is not used and will be removed in a future version" + ) + + row = self.fetcher.get(res.tag, pk, depth=0, force_fetch=True) + try: + obj, _ = self.create_obj(row, res) + self.copy_object(obj) + except self.backend.object_missing_error(self.backend.get_concrete(res)): obj, _ = self.create_obj(row, res) - if obj: - self.clean_obj(obj) self.backend.save(obj) def update_collision(self, res, row: dict, exc: Exception): diff --git a/src/peeringdb/fetch.py b/src/peeringdb/fetch.py index 17abc94..a1f14fa 100644 --- a/src/peeringdb/fetch.py +++ b/src/peeringdb/fetch.py @@ -152,14 +152,15 @@ def entries(self, tag: str): self.load(tag) return self.resources[tag] - def get(self, tag: str, pk: int, depth: int = 0): + def get(self, tag: str, pk: int, depth: int = 0, force_fetch: bool = False): """ Get an individual object or attempt to query :param tag: Resource tag (i.e. "net") :param pk: Primary key :param depth: Depth of related objects to fetch + :param force_fetch: Force a fetch from the API """ - if tag not in self.resources: + if tag not in self.resources or force_fetch: objs = self._get(tag, since=1, id=pk, depth=depth) if len(objs) > 0: return objs[0] diff --git a/tests/test_sync.py b/tests/test_sync.py index 7014aef..456a5a4 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -56,13 +56,6 @@ def test_single(client_empty): assert client.get(Organization, FIRST_NET) -def test_single_deep(client_empty): - client = get_pclient() - client.updater.update_one(Network, FIRST_NET, depth=2) - assert client.get(Network, FIRST_NET) - assert client.get(Organization, FIRST_NET) - - # Test sync where update would result in a duplicate field # Test data should include: swapped case; deleted case def test_nonunique(client_dup): @@ -96,6 +89,39 @@ def test_nonunique_single(client_dup): assert client.get(Network, FIRST_NET) +def test_auto_resolve_unique_conflict(client_empty): + # first load all entries + client = get_client() + rs = all_resources() + client.updater.update_all(rs) + + # then manually change name of network with id 3 + net = client.get(Network, 3) + + net_3_remote_name = net.name + + net.name = "placeholder" + client.updater.backend.save(net) + + # then manually change name of network with id 1 + # to the same name as network with id 3 creating + # a conflict + + net = client.get(Network, 1) + net.name = net_3_remote_name + client.updater.backend.save(net) + + class DummyUniqueException: + error_dict = {"name": "already exists"} + + row = client.fetcher._get("net", id=3)[0] + client.updater.update_collision(Network, row, DummyUniqueException()) + + # check if the conflict was resolved + net = client.get(Network, 1) + assert net.name != net_3_remote_name + + @pytest.mark.sync def test_auth(client_empty): with pytest.raises(ValueError):