Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
cutoffthetop committed Oct 26, 2023
1 parent ec5cd72 commit a5129d1
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 93 deletions.
14 changes: 14 additions & 0 deletions mex/common/identity/__init__.py
Original file line number Diff line number Diff line change
@@ -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",
)
12 changes: 2 additions & 10 deletions mex/common/identity/base.py
Original file line number Diff line number Diff line change
@@ -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."""

Expand All @@ -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."""
...
33 changes: 17 additions & 16 deletions mex/common/identity/dummy.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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(
Expand All @@ -45,18 +48,18 @@ 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
identifier_in_primary_source: Identifier in the primary source
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(
Expand All @@ -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."""
Expand Down
7 changes: 3 additions & 4 deletions mex/common/identity/query.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion mex/common/ldap/extract.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
9 changes: 3 additions & 6 deletions mex/common/models/extracted_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"):
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion mex/common/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 55 additions & 0 deletions tests/identity/test_dummy.py
Original file line number Diff line number Diff line change
@@ -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
76 changes: 22 additions & 54 deletions tests/identity/test_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,76 +2,44 @@
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:
settings = BaseSettings.get()
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
2 changes: 1 addition & 1 deletion tests/models/test_extracted_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit a5129d1

Please sign in to comment.