diff --git a/mex/common/identity/__init__.py b/mex/common/identity/__init__.py index e69de29b..727d7b34 100644 --- a/mex/common/identity/__init__.py +++ b/mex/common/identity/__init__.py @@ -0,0 +1,14 @@ +from mex.common.identity.base import BaseProvider +from mex.common.identity.dummy import DummyIdentityProvider +from mex.common.identity.models import Identity +from mex.common.identity.types import IdentityProvider +from mex.common.identity.query import assign_identity,fetch_identity + +__all__ = ( + "assign_identity", + "BaseProvider", + "DummyIdentityProvider", + "fetch_identity", + "Identity", + "IdentityProvider", +) diff --git a/mex/common/identity/base.py b/mex/common/identity/base.py index 1a2a4127..77eccff3 100644 --- a/mex/common/identity/base.py +++ b/mex/common/identity/base.py @@ -1,17 +1,9 @@ from abc import ABCMeta, abstractmethod -from enum import Enum from mex.common.identity.models import Identity from mex.common.types import Identifier, PrimarySourceID -class IdentityProvider(Enum): - """Choice of available identity providers.""" - - BACKEND = "backend" - DUMMY = "dummy" - - class BaseProvider(metaclass=ABCMeta): """Base class to define the interface of identity providers.""" @@ -31,6 +23,6 @@ def fetch( had_primary_source: Identifier | None = None, identifier_in_primary_source: str | None = None, stable_target_id: Identifier | None = None, - ) -> Identity | None: # pragma: no cover - """Find an Identity instance from the database if it can be found.""" + ) -> list[Identity]: # pragma: no cover + """Find Identity instances matching the given filters.""" ... diff --git a/mex/common/identity/dummy.py b/mex/common/identity/dummy.py index 723485dd..336c4016 100644 --- a/mex/common/identity/dummy.py +++ b/mex/common/identity/dummy.py @@ -1,6 +1,7 @@ from typing import Iterable from mex.common.connector import BaseConnector +from mex.common.exceptions import MExError from mex.common.identity.base import BaseProvider from mex.common.identity.models import Identity from mex.common.types import Identifier, PrimarySourceID @@ -25,18 +26,20 @@ def assign( Returns: Newly created or updated Identity instance """ - identity = self.fetch( + identities = self.fetch( had_primary_source=had_primary_source, identifier_in_primary_source=identifier_in_primary_source, ) - if not identity: - identity = Identity( - hadPrimarySource=had_primary_source, - identifierInPrimarySource=identifier_in_primary_source, - stableTargetId=Identifier.generate(), - identifier=Identifier.generate(), - ) - self.dummy_identity_db.append(identity) + if identities: + return identities[0] + + identity = Identity( + hadPrimarySource=had_primary_source, + identifierInPrimarySource=identifier_in_primary_source, + stableTargetId=Identifier.generate(), + identifier=Identifier.generate(), + ) + self.dummy_identity_db.append(identity) return identity def fetch( @@ -45,8 +48,8 @@ def fetch( had_primary_source: Identifier | None = None, identifier_in_primary_source: str | None = None, stable_target_id: Identifier | None = None, - ) -> Identity | None: - """Find an Identity instance from the database if it can be found. + ) -> list[ Identity] : + """Find Identity instances in the dummy database. Args: had_primary_source: Stable target ID of primary source @@ -54,9 +57,9 @@ def fetch( stable_target_id: Stable target ID of the entity Returns: - Optional Identity instance + List of Identity instances """ - identities: Iterable[Identity] = self.dummy_identity_db + identities = iter(self.dummy_identity_db) if had_primary_source: identities = filter( @@ -72,9 +75,7 @@ def fetch( lambda i: i.stableTargetId == stable_target_id, identities ) - if identities := list(identities): - return identities[0] - return None + return list(identities) def close(self) -> None: """Trash the dummy identity database.""" diff --git a/mex/common/identity/query.py b/mex/common/identity/query.py index ec9433f7..af5f97a6 100644 --- a/mex/common/identity/query.py +++ b/mex/common/identity/query.py @@ -19,10 +19,9 @@ def assign_identity( def fetch_identity( - *, - had_primary_source: PrimarySourceID | None = None, - identifier_in_primary_source: str | None = None, - stable_target_id: Identifier | None = None, + had_primary_source: PrimarySourceID , + identifier_in_primary_source: str , + stable_target_id: Identifier|None ) -> Identity | None: """Find an Identity instance from the database if it can be found.""" settings = BaseSettings.get() diff --git a/mex/common/ldap/extract.py b/mex/common/ldap/extract.py index 198dd3f7..731c7642 100644 --- a/mex/common/ldap/extract.py +++ b/mex/common/ldap/extract.py @@ -1,7 +1,7 @@ from collections import defaultdict from typing import Hashable, Iterable, cast -from mex.common.identity.query import fetch_identity +from mex.common.identity import fetch_identity from mex.common.ldap.models.person import LDAPPerson, LDAPPersonWithQuery from mex.common.models import ExtractedPrimarySource from mex.common.types import Identifier diff --git a/mex/common/models/extracted_data.py b/mex/common/models/extracted_data.py index 31593a60..a85493fc 100644 --- a/mex/common/models/extracted_data.py +++ b/mex/common/models/extracted_data.py @@ -3,7 +3,7 @@ from pydantic import Field, root_validator from mex.common.models.base import MExModel -from mex.common.types import Identifier, PrimarySourceID +from mex.common.types import PrimarySourceID MEX_PRIMARY_SOURCE_STABLE_TARGET_ID = PrimarySourceID("00000000000000") MEX_PRIMARY_SOURCE_IDENTIFIER_IN_PRIMARY_SOURCE = "mex" @@ -114,7 +114,7 @@ def set_identifiers(cls, values: dict[str, Any]) -> dict[str, Any]: Values with identifier and provenance attributes """ # break import cycle, sigh - from mex.common.identity.query import assign_identity + from mex.common.identity import assign_identity # validate ID in primary source and primary source ID if identifier_in_primary_source := values.get("identifierInPrimarySource"): @@ -127,10 +127,7 @@ def set_identifiers(cls, values: dict[str, Any]) -> dict[str, Any]: else: raise ValueError("Missing value for `hadPrimarySource`.") - identity = assign_identity( - had_primary_source=had_primary_source, - identifier_in_primary_source=identifier_in_primary_source, - ) + identity = assign_identity(had_primary_source, identifier_in_primary_source) # In case an identity was already found and the identifier provided to the # constructor do not match we raise an error because it should not be diff --git a/mex/common/settings.py b/mex/common/settings.py index 4fec3b31..5c9df2aa 100644 --- a/mex/common/settings.py +++ b/mex/common/settings.py @@ -9,7 +9,7 @@ from pydantic.env_settings import DotenvType, env_file_sentinel from pydantic.typing import StrPath -from mex.common.identity.types import IdentityProvider +from mex.common.identity import IdentityProvider from mex.common.sinks import Sink from mex.common.transform import MExEncoder from mex.common.types import AssetsPath diff --git a/tests/identity/test_dummy.py b/tests/identity/test_dummy.py new file mode 100644 index 00000000..dce8a822 --- /dev/null +++ b/tests/identity/test_dummy.py @@ -0,0 +1,55 @@ +import pytest +from pytest import MonkeyPatch + +from mex.common.exceptions import MExError +from mex.common.identity import assign_identity, fetch_identity,DummyIdentityProvider +from mex.common.settings import BaseSettings +from mex.common.testing import Joker +from mex.common.types import Identifier,PrimarySourceID + + +def test_assign_identity() -> None: + had_primary_source = PrimarySourceID("0000000000") + identifier_in_primary_source = "thing-1" + + new_identity = assign_identity( had_primary_source, identifier_in_primary_source ) + + assert new_identity.dict() == dict( + hadPrimarySource=had_primary_source, + identifierInPrimarySource=identifier_in_primary_source, + stableTargetId=Joker(), + identifier=Joker(), + ) + + found_identity = assign_identity(had_primary_source, identifier_in_primary_source) + + assert found_identity.dict() == dict( + hadPrimarySource=had_primary_source, + identifierInPrimarySource=identifier_in_primary_source, + stableTargetId=new_identity.stableTargetId, + identifier=new_identity.identifier + ) + + +def test_fetch_identity() -> None: + had_primary_source = PrimarySourceID("0000000000") + identifier_in_primary_source = "thing-1" + + identity = fetch_identity(had_primary_source,identifier_in_primary_source) + assert identity is None + + identity = assign_identity(had_primary_source,identifier_in_primary_source) + + # + assert fetch_identity( + had_primary_source=identity.hadPrimarySource, + identifier_in_primary_source=identity.identifierInPrimarySource, + stable_target_id=identity.stableTargetId, + ) == identity + + +def test_fetch_identity_nothing_found() -> None: + assert fetch_identity( + had_primary_source=PrimarySourceID.generate(), + identifier_in_primary_source="thing-xyz", + ) is None diff --git a/tests/identity/test_query.py b/tests/identity/test_query.py index 8af8c1f5..3453b5cc 100644 --- a/tests/identity/test_query.py +++ b/tests/identity/test_query.py @@ -2,39 +2,10 @@ from pytest import MonkeyPatch from mex.common.exceptions import MExError -from mex.common.identity.query import assign_identity, fetch_identity +from mex.common.identity import assign_identity, fetch_identity, IdentityProvider from mex.common.settings import BaseSettings from mex.common.testing import Joker -from mex.common.types.identifier import Identifier - - -def test_assign_identity() -> None: - system_a_id = Identifier.generate() - old_target_id = Identifier.generate() - new_target_id = Identifier.generate() - identity_after_insert = assign_identity( - system_a_id, "thing-1", old_target_id, "type-x" - ) - - assert identity_after_insert.dict() == dict( - hadPrimarySource=system_a_id, - identifierInPrimarySource="thing-1", - stableTargetId=old_target_id, - entityType="type-x", - identifier=Joker(), - ) - - identity_after_update = assign_identity( - system_a_id, "thing-1", new_target_id, "type-x" - ) - - assert identity_after_update.dict() == dict( - hadPrimarySource=system_a_id, - identifierInPrimarySource="thing-1", - stableTargetId=new_target_id, - entityType="type-x", - identifier=Joker(), - ) +from mex.common.types import Identifier, PrimarySourceID def test_unsupported_providers(monkeypatch: MonkeyPatch) -> None: @@ -42,36 +13,33 @@ def test_unsupported_providers(monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr(BaseSettings, "__setattr__", object.__setattr__) monkeypatch.setattr(settings, "identity_provider", "bogus-provider") + had_primary_source = PrimarySourceID("0000000000") + identifier_in_primary_source = "thing-1" + with pytest.raises(MExError, match="Cannot fetch identity from bogus-provider."): - fetch_identity() + fetch_identity(had_primary_source, identifier_in_primary_source) with pytest.raises(MExError, match="Cannot assign identity to bogus-provider."): - assign_identity( - Identifier.generate(), "thing-1", Identifier.generate(), "type-x" - ) + assign_identity(had_primary_source, identifier_in_primary_source) -def test_fetch_identity() -> None: - system_a_id = Identifier.generate() - entity_1 = Identifier.generate() +def test_under_specified_fetch()->None: + with pytest.raises(MExError, match="Need either `stable_target_id` or both"): + fetch_identity() + with pytest.raises(MExError, match="Need either `stable_target_id` or both"): + fetch_identity(had_primary_source=PrimarySourceID("0000000000")) + with pytest.raises(MExError, match="Need either `stable_target_id` or both"): + fetch_identity(identifier_in_primary_source="thing-1") - result = fetch_identity() - assert result is None - identity = assign_identity(system_a_id, "thing-1", entity_1, "type-x") +def test_dummy_provider() -> None: + settings = BaseSettings.get() + assert settings.identity_provider == IdentityProvider.DUMMY - result = fetch_identity( - had_primary_source=identity.hadPrimarySource, - identifier_in_primary_source=identity.identifierInPrimarySource, - stable_target_id=identity.stableTargetId, - ) - assert result == identity + had_primary_source = PrimarySourceID("0000000000") + identifier_in_primary_source = "thing-1" + assigned = assign_identity(had_primary_source, identifier_in_primary_source) + fetched = fetch_identity(had_primary_source, identifier_in_primary_source) -def test_fetch_identity_nothing_found() -> None: - result = fetch_identity( - had_primary_source=Identifier.generate(), - identifier_in_primary_source=Identifier.generate(), - stable_target_id=Identifier.generate(), - ) - assert result is None + assert assigned == fetched diff --git a/tests/models/test_extracted_data.py b/tests/models/test_extracted_data.py index 2332f202..a9e86308 100644 --- a/tests/models/test_extracted_data.py +++ b/tests/models/test_extracted_data.py @@ -4,7 +4,7 @@ import pytest from pydantic import ValidationError -from mex.common.identity.query import fetch_identity +from mex.common.identity import fetch_identity from mex.common.models import BaseModel, ExtractedData from mex.common.types import Identifier