Coverage for lib/datamodel/foreignkey.py: 100%
37 statements
« prev ^ index » next coverage.py v7.10.1, created at 2025-07-28 07:25 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2025-07-28 07:25 +0000
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
4# Hermes : Change Data Capture (CDC) tool from any source(s) to any target
5# Copyright (C) 2024 INSA Strasbourg
6#
7# This file is part of Hermes.
8#
9# Hermes is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# Hermes is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with Hermes. If not, see <https://www.gnu.org/licenses/>.
22from typing import TYPE_CHECKING
24if TYPE_CHECKING: # pragma: no cover
25 # Only for type hints, won't import at runtime
26 from lib.datamodel.dataobject import DataObject
27 from lib.datamodel.datasource import Datasource
30class HermesCircularForeignkeysRefsError(Exception):
31 """Raised when the some circular foreign keys references are found"""
34class ForeignKey:
35 """Handle foreign keys, and allow to retrieve foreign objects references"""
37 def __init__(
38 self,
39 from_obj: str,
40 from_attr: str,
41 to_obj: str,
42 to_attr: str,
43 ):
44 """Setup a new ForeignKey"""
45 self._from_obj: str = from_obj
46 self._from_attr: str = from_attr
47 self._to_obj: str = to_obj
48 self._to_attr: str = to_attr
50 self._repr: str = (
51 f"<ForeignKey({self._from_obj}.{self._from_attr}"
52 f" -> {self._to_obj}.{self._to_attr})>"
53 )
54 self._hash: int = hash(self._repr)
56 def __repr__(self) -> str:
57 return self._repr
59 def __hash__(self) -> int:
60 return self._hash
62 def __eq__(self, other: "ForeignKey") -> bool:
63 return hash(self) == hash(other)
65 @staticmethod
66 def checkForCircularForeignKeysRefs(
67 allfkeys: dict[str, list["ForeignKey"]],
68 fkeys: list["ForeignKey"],
69 _alreadyMet: list["ForeignKey"] | None = None,
70 ):
71 """Will check recursively for circular references in foreign keys,
72 and raise HermesCircularForeignkeysRefsError if any is found"""
73 if _alreadyMet is None:
74 _alreadyMet = []
76 for fkey in fkeys:
77 if fkey in _alreadyMet:
78 errmsg = (
79 f"Circular foreign keys references found in {_alreadyMet}."
80 " Unable to continue."
81 )
82 __hermes__.logger.critical(errmsg)
83 raise HermesCircularForeignkeysRefsError(errmsg)
84 _alreadyMet.append(fkey)
85 ForeignKey.checkForCircularForeignKeysRefs(
86 allfkeys, allfkeys[fkey._to_obj], _alreadyMet
87 )
89 @staticmethod
90 def fetchParentObjs(ds: "Datasource", obj: "DataObject") -> list["DataObject"]:
91 """Returns a list of parent objects of specified obj from specified
92 Datasource ds"""
93 res: list["DataObject"] = []
94 objlist = ds[obj.getType()]
95 for fkey in objlist.FOREIGNKEYS:
96 parent = ds[fkey._to_obj].get(getattr(obj, fkey._from_attr))
97 if parent is not None:
98 res.append(parent)
99 res.extend(ForeignKey.fetchParentObjs(ds, parent))
100 return res