From 575deedaa283aef2aa0f90386dd9313550b93c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niccol=C3=B2=20Passera?= Date: Thu, 6 Aug 2026 17:01:45 +0200 Subject: [PATCH] shared: implementazione package identity --- .gitattributes | 33 +++++ pyproject.toml | 4 + shared/__init__.py | 7 + shared/identity/__init__.py | 23 +++ shared/identity/artifact_id.py | 23 +++ shared/identity/blueprint_id.py | 23 +++ shared/identity/execution_id.py | 23 +++ shared/identity/identifier.py | 97 +++++++++++++ shared/identity/module_id.py | 23 +++ shared/identity/request_id.py | 23 +++ shared/identity/session_id.py | 23 +++ tests/shared/identity/test_artifact_id.py | 43 ++++++ tests/shared/identity/test_blueprint_id.py | 43 ++++++ tests/shared/identity/test_execution_id.py | 43 ++++++ tests/shared/identity/test_identifier.py | 158 +++++++++++++++++++++ tests/shared/identity/test_module_id.py | 43 ++++++ tests/shared/identity/test_request_id.py | 43 ++++++ tests/shared/identity/test_session_id.py | 43 ++++++ 18 files changed, 718 insertions(+) create mode 100644 .gitattributes create mode 100644 shared/__init__.py create mode 100644 shared/identity/__init__.py create mode 100644 shared/identity/artifact_id.py create mode 100644 shared/identity/blueprint_id.py create mode 100644 shared/identity/execution_id.py create mode 100644 shared/identity/identifier.py create mode 100644 shared/identity/module_id.py create mode 100644 shared/identity/request_id.py create mode 100644 shared/identity/session_id.py create mode 100644 tests/shared/identity/test_artifact_id.py create mode 100644 tests/shared/identity/test_blueprint_id.py create mode 100644 tests/shared/identity/test_execution_id.py create mode 100644 tests/shared/identity/test_identifier.py create mode 100644 tests/shared/identity/test_module_id.py create mode 100644 tests/shared/identity/test_request_id.py create mode 100644 tests/shared/identity/test_session_id.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..0220237 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,33 @@ +# Normalize all text files to LF +* text=auto eol=lf + +# Python +*.py text eol=lf + +# Markdown +*.md text eol=lf + +# YAML +*.yml text eol=lf +*.yaml text eol=lf + +# TOML +*.toml text eol=lf + +# JSON +*.json text eol=lf + +# Shell +*.sh text eol=lf + +# Windows scripts +*.bat text eol=crlf +*.cmd text eol=crlf + +# Binary files +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.pdf binary diff --git a/pyproject.toml b/pyproject.toml index 938380b..bc65f61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,10 @@ dev = [ [tool.setuptools] include-package-data = true +[tool.setuptools.packages.find] +where = ["."] +include = ["shared*", "core*", "api*", "modules*", "artifacts*"] + [tool.ruff] target-version = "py311" line-length = 88 diff --git a/shared/__init__.py b/shared/__init__.py new file mode 100644 index 0000000..955d77f --- /dev/null +++ b/shared/__init__.py @@ -0,0 +1,7 @@ +""" +SMAVS Shared Kernel. + +Provides shared components used across the framework. +""" + +__all__: list[str] = [] diff --git a/shared/identity/__init__.py b/shared/identity/__init__.py new file mode 100644 index 0000000..8984af4 --- /dev/null +++ b/shared/identity/__init__.py @@ -0,0 +1,23 @@ +""" +SMAVS Shared Kernel - Identity. + +Provides the public interface for the identity package. +""" + +from shared.identity.artifact_id import ArtifactId +from shared.identity.blueprint_id import BlueprintId +from shared.identity.execution_id import ExecutionId +from shared.identity.identifier import Identifier +from shared.identity.module_id import ModuleId +from shared.identity.request_id import RequestId +from shared.identity.session_id import SessionId + +__all__ = [ + "ArtifactId", + "BlueprintId", + "ExecutionId", + "Identifier", + "ModuleId", + "RequestId", + "SessionId", +] diff --git a/shared/identity/artifact_id.py b/shared/identity/artifact_id.py new file mode 100644 index 0000000..4078bc3 --- /dev/null +++ b/shared/identity/artifact_id.py @@ -0,0 +1,23 @@ +""" +SMAVS Shared Kernel - Artifact Identifier. + +Defines the unique identifier for an Artifact. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from shared.identity.identifier import Identifier + +__all__ = ["ArtifactId"] + + +@dataclass(frozen=True, slots=True) +class ArtifactId(Identifier): + """ + Unique identifier of an Artifact. + + This identifier is used to uniquely identify an Artifact + within the SMAVS framework. + """ diff --git a/shared/identity/blueprint_id.py b/shared/identity/blueprint_id.py new file mode 100644 index 0000000..c24a1b2 --- /dev/null +++ b/shared/identity/blueprint_id.py @@ -0,0 +1,23 @@ +""" +SMAVS Shared Kernel - Blueprint Identifier. + +Defines the unique identifier for a Blueprint. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from shared.identity.identifier import Identifier + +__all__ = ["BlueprintId"] + + +@dataclass(frozen=True, slots=True) +class BlueprintId(Identifier): + """ + Unique identifier of a Blueprint. + + This identifier is used to uniquely identify a Blueprint + within the SMAVS framework. + """ diff --git a/shared/identity/execution_id.py b/shared/identity/execution_id.py new file mode 100644 index 0000000..1904add --- /dev/null +++ b/shared/identity/execution_id.py @@ -0,0 +1,23 @@ +""" +SMAVS Shared Kernel - Execution Identifier. + +Defines the unique identifier for an Execution. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from shared.identity.identifier import Identifier + +__all__ = ["ExecutionId"] + + +@dataclass(frozen=True, slots=True) +class ExecutionId(Identifier): + """ + Unique identifier of an Execution. + + This identifier is used to uniquely identify an Execution + within the SMAVS framework. + """ diff --git a/shared/identity/identifier.py b/shared/identity/identifier.py new file mode 100644 index 0000000..0902997 --- /dev/null +++ b/shared/identity/identifier.py @@ -0,0 +1,97 @@ +""" +SMAVS Shared Kernel - Identifier + +Defines the base Value Object used to uniquely identify every +domain element inside the framework. + +All domain-specific identifiers inherit from this class. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from uuid import UUID, uuid4 + +__all__ = ["Identifier"] + + +@dataclass(frozen=True, slots=True) +class Identifier: + """ + Base immutable identifier. + + An Identifier represents the identity of a domain object. + + Internally it stores a UUID while exposing a strongly typed + Value Object to the rest of the domain. + """ + + value: UUID + + @classmethod + def new(cls) -> Identifier: + """ + Create a new identifier. + + Returns: + A newly generated identifier. + """ + return cls(uuid4()) + + @classmethod + def from_string(cls, value: str) -> Identifier: + """ + Create an identifier from its string representation. + + Args: + value: + UUID string. + + Returns: + The corresponding identifier. + + Raises: + ValueError: + If the provided string is not a valid UUID. + """ + return cls(UUID(value)) + + @classmethod + def from_uuid(cls, value: UUID) -> Identifier: + """ + Create an identifier from an existing UUID. + + Args: + value: + UUID instance. + + Returns: + The corresponding identifier. + """ + return cls(value) + + def __str__(self) -> str: + """ + Return the canonical string representation. + """ + return str(self.value) + + def __repr__(self) -> str: + """ + Return the developer representation. + """ + return f"{self.__class__.__name__}('{self.value}')" + + @property + def hex(self) -> str: + """ + Return the hexadecimal representation. + """ + return self.value.hex + + @property + def urn(self) -> str: + """ + Return the UUID URN representation. + """ + return self.value.urn diff --git a/shared/identity/module_id.py b/shared/identity/module_id.py new file mode 100644 index 0000000..ab482c5 --- /dev/null +++ b/shared/identity/module_id.py @@ -0,0 +1,23 @@ +""" +SMAVS Shared Kernel - Module Identifier. + +Defines the unique identifier for a Module. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from shared.identity.identifier import Identifier + +__all__ = ["ModuleId"] + + +@dataclass(frozen=True, slots=True) +class ModuleId(Identifier): + """ + Unique identifier of a Module. + + This identifier is used to uniquely identify a Module + within the SMAVS framework. + """ diff --git a/shared/identity/request_id.py b/shared/identity/request_id.py new file mode 100644 index 0000000..5b641d6 --- /dev/null +++ b/shared/identity/request_id.py @@ -0,0 +1,23 @@ +""" +SMAVS Shared Kernel - Request Identifier. + +Defines the unique identifier for a Request. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from shared.identity.identifier import Identifier + +__all__ = ["RequestId"] + + +@dataclass(frozen=True, slots=True) +class RequestId(Identifier): + """ + Unique identifier of a Request. + + This identifier is used to uniquely identify a Request + within the SMAVS framework. + """ diff --git a/shared/identity/session_id.py b/shared/identity/session_id.py new file mode 100644 index 0000000..d3c1e30 --- /dev/null +++ b/shared/identity/session_id.py @@ -0,0 +1,23 @@ +""" +SMAVS Shared Kernel - Session Identifier. + +Defines the unique identifier for a Session. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from shared.identity.identifier import Identifier + +__all__ = ["SessionId"] + + +@dataclass(frozen=True, slots=True) +class SessionId(Identifier): + """ + Unique identifier of a Session. + + This identifier is used to uniquely identify a Session + within the SMAVS framework. + """ diff --git a/tests/shared/identity/test_artifact_id.py b/tests/shared/identity/test_artifact_id.py new file mode 100644 index 0000000..7b29bb6 --- /dev/null +++ b/tests/shared/identity/test_artifact_id.py @@ -0,0 +1,43 @@ +""" +Tests for the ArtifactId Value Object. +""" + +from __future__ import annotations + +from shared.identity import ArtifactId, Identifier + + +def test_new_creates_module_id() -> None: + """A new ArtifactId should be created.""" + + # Arrange + + # Act + module_id = ArtifactId.new() + + # Assert + assert isinstance(module_id, ArtifactId) + + +def test_module_id_is_identifier() -> None: + """ArtifactId should inherit from Identifier.""" + + # Arrange + + # Act + module_id = ArtifactId.new() + + # Assert + assert isinstance(module_id, Identifier) + + +def test_module_id_preserves_type() -> None: + """Factory methods should preserve the concrete type.""" + + # Arrange + + # Act + module_id = ArtifactId.new() + + # Assert + assert type(module_id) is ArtifactId diff --git a/tests/shared/identity/test_blueprint_id.py b/tests/shared/identity/test_blueprint_id.py new file mode 100644 index 0000000..4d5a589 --- /dev/null +++ b/tests/shared/identity/test_blueprint_id.py @@ -0,0 +1,43 @@ +""" +Tests for the BlueprintId Value Object. +""" + +from __future__ import annotations + +from shared.identity import BlueprintId, Identifier + + +def test_new_creates_module_id() -> None: + """A new BlueprintId should be created.""" + + # Arrange + + # Act + module_id = BlueprintId.new() + + # Assert + assert isinstance(module_id, BlueprintId) + + +def test_module_id_is_identifier() -> None: + """BlueprintId should inherit from Identifier.""" + + # Arrange + + # Act + module_id = BlueprintId.new() + + # Assert + assert isinstance(module_id, Identifier) + + +def test_module_id_preserves_type() -> None: + """Factory methods should preserve the concrete type.""" + + # Arrange + + # Act + module_id = BlueprintId.new() + + # Assert + assert type(module_id) is BlueprintId diff --git a/tests/shared/identity/test_execution_id.py b/tests/shared/identity/test_execution_id.py new file mode 100644 index 0000000..3953cf3 --- /dev/null +++ b/tests/shared/identity/test_execution_id.py @@ -0,0 +1,43 @@ +""" +Tests for the ExecutionId Value Object. +""" + +from __future__ import annotations + +from shared.identity import ExecutionId, Identifier + + +def test_new_creates_module_id() -> None: + """A new ExecutionId should be created.""" + + # Arrange + + # Act + module_id = ExecutionId.new() + + # Assert + assert isinstance(module_id, ExecutionId) + + +def test_module_id_is_identifier() -> None: + """ExecutionId should inherit from Identifier.""" + + # Arrange + + # Act + module_id = ExecutionId.new() + + # Assert + assert isinstance(module_id, Identifier) + + +def test_module_id_preserves_type() -> None: + """Factory methods should preserve the concrete type.""" + + # Arrange + + # Act + module_id = ExecutionId.new() + + # Assert + assert type(module_id) is ExecutionId diff --git a/tests/shared/identity/test_identifier.py b/tests/shared/identity/test_identifier.py new file mode 100644 index 0000000..461a427 --- /dev/null +++ b/tests/shared/identity/test_identifier.py @@ -0,0 +1,158 @@ +""" +Tests for the Identifier Value Object. +""" + +from __future__ import annotations + +from dataclasses import FrozenInstanceError +from uuid import UUID + +import pytest + +from shared.identity import Identifier + + +def test_new_creates_identifier() -> None: + """A new identifier should contain a valid UUID.""" + + # Arrange + + # Act + identifier = Identifier.new() + + # Assert + assert isinstance(identifier.value, UUID) + + +def test_from_string_creates_identifier() -> None: + """An identifier can be created from a UUID string.""" + + # Arrange + value = "550e8400-e29b-41d4-a716-446655440000" + + # Act + identifier = Identifier.from_string(value) + + # Assert + assert identifier.value == UUID(value) + + +def test_from_uuid_creates_identifier() -> None: + """An identifier can be created from an existing UUID.""" + + # Arrange + value = UUID("550e8400-e29b-41d4-a716-446655440000") + + # Act + identifier = Identifier.from_uuid(value) + + # Assert + assert identifier.value == value + + +def test_from_string_raises_for_invalid_uuid() -> None: + """Creating an identifier from an invalid UUID should fail.""" + + # Arrange + value = "invalid-uuid" + + # Act / Assert + with pytest.raises(ValueError): + Identifier.from_string(value) + + +def test_identifiers_with_same_value_are_equal() -> None: + """Identifiers with the same UUID should be equal.""" + + # Arrange + value = UUID("550e8400-e29b-41d4-a716-446655440000") + + # Act + first = Identifier.from_uuid(value) + second = Identifier.from_uuid(value) + + # Assert + assert first == second + + +def test_identifiers_with_different_values_are_not_equal() -> None: + """Identifiers with different UUIDs should not be equal.""" + + # Arrange / Act + first = Identifier.new() + second = Identifier.new() + + # Assert + assert first != second + + +def test_identifier_is_hashable() -> None: + """Identifiers should be hashable.""" + + # Arrange + identifier = Identifier.new() + + # Act + values = {identifier} + + # Assert + assert identifier in values + + +def test_str_returns_uuid_string() -> None: + """The string representation should match the UUID.""" + + # Arrange + identifier = Identifier.from_string( + "550e8400-e29b-41d4-a716-446655440000", + ) + + # Act / Assert + assert str(identifier) == "550e8400-e29b-41d4-a716-446655440000" + + +def test_repr_returns_developer_representation() -> None: + """The repr should contain the class name and UUID.""" + + # Arrange + identifier = Identifier.from_string( + "550e8400-e29b-41d4-a716-446655440000", + ) + + # Act / Assert + assert repr(identifier) == "Identifier('550e8400-e29b-41d4-a716-446655440000')" + + +def test_hex_returns_uuid_hexadecimal() -> None: + """The hexadecimal representation should match the UUID.""" + + # Arrange + identifier = Identifier.from_string( + "550e8400-e29b-41d4-a716-446655440000", + ) + + # Act / Assert + assert identifier.hex == "550e8400e29b41d4a716446655440000" + + +def test_urn_returns_uuid_urn() -> None: + """The URN representation should match the UUID.""" + + # Arrange + identifier = Identifier.from_string( + "550e8400-e29b-41d4-a716-446655440000", + ) + + # Act / Assert + assert identifier.urn == "urn:uuid:550e8400-e29b-41d4-a716-446655440000" + + +def test_identifier_is_immutable() -> None: + """Identifiers must be immutable.""" + + # Arrange + identifier = Identifier.new() + + # Act / Assert + with pytest.raises(FrozenInstanceError): + object.__setattr__(identifier, "value", UUID(int=0)) diff --git a/tests/shared/identity/test_module_id.py b/tests/shared/identity/test_module_id.py new file mode 100644 index 0000000..d77ef25 --- /dev/null +++ b/tests/shared/identity/test_module_id.py @@ -0,0 +1,43 @@ +""" +Tests for the ModuleId Value Object. +""" + +from __future__ import annotations + +from shared.identity import Identifier, ModuleId + + +def test_new_creates_module_id() -> None: + """A new ModuleId should be created.""" + + # Arrange + + # Act + module_id = ModuleId.new() + + # Assert + assert isinstance(module_id, ModuleId) + + +def test_module_id_is_identifier() -> None: + """ModuleId should inherit from Identifier.""" + + # Arrange + + # Act + module_id = ModuleId.new() + + # Assert + assert isinstance(module_id, Identifier) + + +def test_module_id_preserves_type() -> None: + """Factory methods should preserve the concrete type.""" + + # Arrange + + # Act + module_id = ModuleId.new() + + # Assert + assert type(module_id) is ModuleId diff --git a/tests/shared/identity/test_request_id.py b/tests/shared/identity/test_request_id.py new file mode 100644 index 0000000..36dd980 --- /dev/null +++ b/tests/shared/identity/test_request_id.py @@ -0,0 +1,43 @@ +""" +Tests for the RequestId Value Object. +""" + +from __future__ import annotations + +from shared.identity import Identifier, RequestId + + +def test_new_creates_module_id() -> None: + """A new RequestId should be created.""" + + # Arrange + + # Act + module_id = RequestId.new() + + # Assert + assert isinstance(module_id, RequestId) + + +def test_module_id_is_identifier() -> None: + """RequestId should inherit from Identifier.""" + + # Arrange + + # Act + module_id = RequestId.new() + + # Assert + assert isinstance(module_id, Identifier) + + +def test_module_id_preserves_type() -> None: + """Factory methods should preserve the concrete type.""" + + # Arrange + + # Act + module_id = RequestId.new() + + # Assert + assert type(module_id) is RequestId diff --git a/tests/shared/identity/test_session_id.py b/tests/shared/identity/test_session_id.py new file mode 100644 index 0000000..878ca86 --- /dev/null +++ b/tests/shared/identity/test_session_id.py @@ -0,0 +1,43 @@ +""" +Tests for the SessionId Value Object. +""" + +from __future__ import annotations + +from shared.identity import Identifier, SessionId + + +def test_new_creates_module_id() -> None: + """A new SessionId should be created.""" + + # Arrange + + # Act + module_id = SessionId.new() + + # Assert + assert isinstance(module_id, SessionId) + + +def test_module_id_is_identifier() -> None: + """SessionId should inherit from Identifier.""" + + # Arrange + + # Act + module_id = SessionId.new() + + # Assert + assert isinstance(module_id, Identifier) + + +def test_module_id_preserves_type() -> None: + """Factory methods should preserve the concrete type.""" + + # Arrange + + # Act + module_id = SessionId.new() + + # Assert + assert type(module_id) is SessionId