Skip to content

Generic

The foundational layer shared across all IRIS domains. Every domain entity, value object, and repository builds on the abstractions defined here.

Entity Relationship Diagram

Generic ERD

Overview

The module is organized in four files:

File Contents
abstracts.py Metadata, AliasSet, SerializableFlag
entities.py NamedEntity — simplest base entity
objects.py Sex, QualityCategory, contact value objects
repositories.py Repository[T] hierarchy + EventPublisher

Design rules

All objects are @define(frozen=True) (attrs). State is never mutated in place; attrs.evolve() produces a modified copy. This applies uniformly from base abstractions down to the most specific domain entity.

Metadata

A generic key-value container for provenance data — source name, version string, and an arbitrary data mapping. The mapping is frozen on construction so the object remains immutable. Use with_data(**kwargs) to add keys or merge(other) to combine two instances (the other's keys win on collision). Entities that carry provenance attach a Metadata | None field rather than scattering ad-hoc string fields.

AliasSet

An immutable set of canonical names with an optional alias mapping. Used wherever a domain concept has multiple valid identifiers — gene symbols (GeneNomenclature stores an AliasSet of HGNC symbols and aliases), transcript names, disease codes. resolve(value) returns the canonical name, falling back to the value itself when no alias entry exists.

SerializableFlag

Base class for composite Flag enums that need round-trip serialization to PostgreSQL TEXT[] or a pipe-separated VARCHAR. Concrete examples: MendelianInheritance (AD, AR, XLR, XLD…) in the genomics domain. Subclasses inherit to_list(), to_str(), from_list(), and from_str() without any additional implementation.

Repository hierarchy

Repository[T]           get(id) → T | None
├── ReadRepository[T]   + find_all() → Sequence[T]
├── WriteRepository[T]  + save(T) → T, delete(id)
└── CrudRepository[T]   (ReadRepository + WriteRepository)

Genomics repositories are read-only (Repository[T] only) because variant and gene data come from external reference databases. LIMS, HR, and marketing repositories use CrudRepository[T] because they manage mutable laboratory records.

Abstracts

iris.domain.generic.abstracts

Generic domain abstractions: Metadata container and AliasSet.

Metadata

Generic, extensible metadata container.

Stores arbitrary key-value pairs alongside an optional source and version label. The data mapping is frozen on construction so the object remains immutable; use with_data() to produce an updated copy or merge() to combine two instances (the other's keys win on collision).

get

get(key, default=None)

Return the value for key, or default if not present.

Source code in src/iris/domain/generic/abstracts.py
def get(self, key: str, default: Any = None) -> Any:
    """Return the value for key, or default if not present."""
    return self.data.get(key, default)

require

require(key)

Return the value for key, raising KeyError if absent.

Source code in src/iris/domain/generic/abstracts.py
def require(self, key: str) -> Any:
    """Return the value for key, raising KeyError if absent."""
    if key not in self.data:
        raise KeyError(f"Missing metadata key: {key!r}")
    return self.data[key]

with_data

with_data(**kwargs)

Return new Metadata with updated keys.

Source code in src/iris/domain/generic/abstracts.py
def with_data(self, **kwargs: Any) -> Metadata:
    """Return new Metadata with updated keys."""
    return evolve(self, data={**self.data, **kwargs})

merge

merge(other)

Merge two metadata objects (other overrides).

Source code in src/iris/domain/generic/abstracts.py
def merge(self, other: Metadata) -> Metadata:
    """Merge two metadata objects (other overrides)."""
    return Metadata(
        source=other.source or self.source,
        version=other.version or self.version,
        data={**self.data, **other.data},
    )

to_dict

to_dict()

Convert to mutable dict (for JSON serialization).

Source code in src/iris/domain/generic/abstracts.py
def to_dict(self) -> dict[str, Any]:
    """Convert to mutable dict (for JSON serialization)."""

    def thaw(x):
        if isinstance(x, Mapping):
            return {k: thaw(v) for k, v in x.items()}
        if isinstance(x, tuple):
            return [thaw(v) for v in x]
        return x

    return {"source": self.source, "version": self.version, "data": thaw(self.data)}

SerializableFlag

Bases: Flag

Base class for Flag enums with TEXT[] serialization.

Subclasses inherit to_list, to_str, from_list, from_str, and primitives. All methods work generically via type(self) and cls, so subclasses don't need to override anything.

Example
class MyFlag(SerializableFlag):
    A = 1 << 0
    B = 1 << 1
    AB = A | B


MyFlag.from_list(["A", "B"]) == MyFlag.AB
# True
MyFlag.AB.to_list()
# ['A', 'B']

to_list

to_list()

Return active primitive member names for Postgres TEXT[] storage.

RETURNS DESCRIPTION
list[str]

Sorted list of primitive member names.

Source code in src/iris/domain/generic/abstracts.py
def to_list(self) -> list[str]:
    """Return active primitive member names for Postgres TEXT[] storage.

    Returns:
        Sorted list of primitive member names.
    """
    return sorted(
        m.name for m in type(self) if m in self and _is_primitive(m) and m.name is not None
    )

to_str

to_str()

Return a pipe-separated string for VARCHAR storage.

RETURNS DESCRIPTION
str

Pipe-separated primitive member names (e.g. 'AD|AR').

Source code in src/iris/domain/generic/abstracts.py
def to_str(self) -> str:
    """Return a pipe-separated string for VARCHAR storage.

    Returns:
        Pipe-separated primitive member names (e.g. 'AD|AR').
    """
    return "|".join(self.to_list())

from_list classmethod

from_list(values)

Reconstruct from a list of member names.

PARAMETER DESCRIPTION
values

List of member names (e.g. ['AD', 'AR']).

TYPE: list[str]

RETURNS DESCRIPTION
SerializableFlag

Combined flag instance.

RAISES DESCRIPTION
KeyError

If any value is not a valid member name.

Source code in src/iris/domain/generic/abstracts.py
@classmethod
def from_list(cls, values: list[str]) -> SerializableFlag:
    """Reconstruct from a list of member names.

    Args:
        values: List of member names (e.g. ['AD', 'AR']).

    Returns:
        Combined flag instance.

    Raises:
        KeyError: If any value is not a valid member name.
    """
    if not values:
        return cls(0)
    result = cls(0)
    for name in values:
        result |= cls[name]
    return result

from_str classmethod

from_str(value)

Reconstruct from a pipe-separated string.

PARAMETER DESCRIPTION
value

Pipe-separated member names (e.g. 'AD|AR').

TYPE: str

RETURNS DESCRIPTION
SerializableFlag

Combined flag instance.

RAISES DESCRIPTION
KeyError

If any part is not a valid member name.

Source code in src/iris/domain/generic/abstracts.py
@classmethod
def from_str(cls, value: str) -> SerializableFlag:
    """Reconstruct from a pipe-separated string.

    Args:
        value: Pipe-separated member names (e.g. 'AD|AR').

    Returns:
        Combined flag instance.

    Raises:
        KeyError: If any part is not a valid member name.
    """
    return cls.from_list([p.strip() for p in value.split("|") if p.strip()])

primitives

primitives()

Return active primitive (non-composite) members.

RETURNS DESCRIPTION
list[Self]

List of primitive flag members.

Source code in src/iris/domain/generic/abstracts.py
def primitives(self) -> list[Self]:
    """Return active primitive (non-composite) members.

    Returns:
        List of primitive flag members.
    """
    return [m for m in type(self) if m in self and _is_primitive(m)]

AliasSet

Immutable set of canonical names with an optional alias mapping.

Canonical names are stored as a frozenset and iterated in sorted order via ordered. The aliases mapping translates alternative spellings or identifiers back to their canonical form; resolve() performs that lookup and falls back to the value itself when no alias entry exists.

resolve

resolve(value)

Return the canonical name for value, resolving aliases as needed.

Always returns the single shared string instance stored for the resolved canonical name, never the caller's input object, and never a separately-constructed object from the aliases mapping — see the note in attrs_post_init for why this matters.

Source code in src/iris/domain/generic/abstracts.py
def resolve(self, value: str) -> str:
    """Return the canonical name for value, resolving aliases as needed.

    Always returns the single shared string instance stored for the
    resolved canonical name, never the caller's input object, and never
    a separately-constructed object from the aliases mapping — see the
    note in __attrs_post_init__ for why this matters.
    """
    if value in self._canonical_lookup:
        return self._canonical_lookup[value]
    try:
        canonical = self.aliases[value]
    except KeyError:
        raise KeyError(f"Unknown name or alias: {value!r}") from None
    # `canonical` may be a different string object than the one held in
    # `_canonical_lookup`, even if equal in value (e.g. two independent
    # str(i) calls produce equal but distinct objects). Route through
    # the lookup so identity is guaranteed regardless of how the alias
    # mapping was constructed.
    return self._canonical_lookup[canonical]

Entities

iris.domain.generic.entities

Base entity class shared across all domains.

NamedEntity

Base entity with a string code identifier and a human-readable name.

All domain aggregates and entities inherit from this class. code is the stable machine-readable key (e.g. "BRCA1", "P-0042"), while name is the display label shown in reports and UIs. Both fields are required and immutable after construction.

Objects

iris.domain.generic.objects

Generic value objects shared across domains: contact info, quality scores, and enums.

Sex

Bases: Enum

Biological sex of a person.

QualityCategory

Bases: Enum

High-level technical families of quality metrics used in data measurement and pipelines.

Categories:

  • Accuracy: Closeness of an estimate to a reference or ground truth. • e.g., absolute/relative error, identification error.

  • Precision: Stability of repeated measurements under identical conditions. • e.g., coefficient of variation, replicate concordance.

  • Completeness: Degree to which required data, annotations, or evidence are present. • e.g., missingness rate, coverage breadth.

  • Robustness: Sensitivity of metrics to noise, batch effects, or perturbations. • e.g., noise-tolerance score, batch-stability index.

  • Informational: Amount and richness of supporting contextual or evidential data. • e.g., documentation depth, annotation density.

  • Confidence: Algorithm-derived certainty in a call or estimate. • e.g., posterior probability, likelihood-based quality.

  • Coherence: Internal consistency between related metrics or signals. • e.g., cross-metric concordance, structural agreement.

  • Resolution: Ability to distinguish fine-grained states or values. • e.g., discrimination index, signal-to-resolution ratio.

  • Usability: Degree to which outputs are interpretable and operationally actionable. • e.g., interpretability score, clarity index.

  • Trustworthiness: Evidence of integrity, provenance, and auditability of data. • e.g., source-provenance score, integrity checksum.

These categories align with principles from measurement theory, statistical quality assessment, and data reliability frameworks.

Address

Postal address with optional street, city, state, zip, and country fields.

Only name is required; all location fields are optional to accommodate partial addresses. Typically embedded in a ContactInfo instance rather than used standalone.

ContactInfo

Contact details aggregating email, phone, and web contacts for a named entity.

primary_email property

primary_email

Return the primary email contact, or None if none is marked primary.

primary_phone property

primary_phone

Return the primary phone contact, or None if none is marked primary.

primary_website property

primary_website

Return the primary website contact, or None if none is marked primary.

EmailContact

An email address with optional primary, verified, and format-validation flags.

The email value is normalized to lowercase and stripped of whitespace on construction; an invalid format raises ValueError. At most one EmailContact per ContactInfo may have is_primary=True.

normalize_email staticmethod

normalize_email(value)

Normalize and validate the email address.

Source code in src/iris/domain/generic/objects.py
@staticmethod
def normalize_email(value: str) -> str:
    """Normalize and validate the email address."""
    value = value.strip().lower()

    if not re.match(r"^[^@\s]+@[^@\s]+\.[^@\s]+$", value):
        raise ValueError(f"Invalid email address: {value}")

    return value

PhoneContact

A phone number with optional primary, verified, country code, and extension fields.

normalize_phone staticmethod

normalize_phone(value)

Normalize the phone number by stripping whitespace.

Source code in src/iris/domain/generic/objects.py
@staticmethod
def normalize_phone(value: str) -> str:
    """Normalize the phone number by stripping whitespace."""
    return value.strip()

WebContact

A website URL normalized to https:// with optional primary and verified flags.

normalize_url staticmethod

normalize_url(value)

Normalize and validate the website URL, adding https:// if needed.

Source code in src/iris/domain/generic/objects.py
@staticmethod
def normalize_url(value: str) -> str:
    """Normalize and validate the website URL, adding https:// if needed."""
    value = value.strip()

    if not value:
        raise ValueError("Website URL cannot be empty")

    # Permite escribir example.com y lo convierte a https://example.com
    if "://" not in value:
        value = f"https://{value}"

    parsed = urlparse(value)

    if parsed.scheme not in {"http", "https"}:
        raise ValueError(f"Website URL must use http or https: {value}")

    if not parsed.netloc:
        raise ValueError(f"Invalid website URL: {value}")

    if "." not in parsed.netloc and parsed.netloc != "localhost":
        raise ValueError(f"Invalid website domain: {value}")

    return value

QualityScore

A quality measurement with a category, pass/fail status, and optional value.

category classifies the dimension being measured (e.g. completeness, accuracy). passed is the primary signal; value carries a numeric score when available and info holds a free-text explanation for failures or borderline results.

ok classmethod

ok(value=None, info=None)

Return a passing quality score in the SYSTEM category.

Source code in src/iris/domain/generic/objects.py
@classmethod
def ok(cls, value: int | None = None, info: str | None = None) -> QualityScore:
    """Return a passing quality score in the SYSTEM category."""
    return cls(category=QualityCategory.SYSTEM, passed=True, value=value, info=info)

Repositories

iris.domain.generic.repositories

Generic repository base classes and EventPublisher port.

EventPublisher

Bases: ABC

Puerto genérico para publicar eventos de dominio.

Los eventos son objetos frozen attrs — se serializan a dict antes de publicar. El adaptador decide cómo y dónde persistirlos.

publish abstractmethod

publish(event)

Publica un evento de dominio.

PARAMETER DESCRIPTION
event

Instancia frozen attrs de un evento de dominio. Debe tener un campo occurred_at de tipo datetime.

TYPE: Any

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def publish(self, event: Any) -> None:
    """Publica un evento de dominio.

    Args:
        event: Instancia frozen attrs de un evento de dominio.
               Debe tener un campo `occurred_at` de tipo datetime.
    """

publish_many abstractmethod

publish_many(events)

Publica múltiples eventos en orden.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def publish_many(self, events: list[Any]) -> None:
    """Publica múltiples eventos en orden."""

Repository

Bases: ABC, Generic[T]

Base repository: retrieve a single entity by its string identifier.

get abstractmethod

get(identifier)

Return the entity matching identifier, or None if not found.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def get(self, identifier: str) -> T | None:
    """Return the entity matching `identifier`, or None if not found."""

ReadRepository

Bases: Repository[T]

Read-only repository with full collection access.

find_all abstractmethod

find_all()

Return all entities in the repository.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def find_all(self) -> Sequence[T]:
    """Return all entities in the repository."""

get abstractmethod

get(identifier)

Return the entity matching identifier, or None if not found.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def get(self, identifier: str) -> T | None:
    """Return the entity matching `identifier`, or None if not found."""

WriteRepository

Bases: Repository[T]

Write repository: persist and remove entities.

save abstractmethod

save(entity)

Persist entity and return the saved version.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def save(self, entity: T) -> T:
    """Persist `entity` and return the saved version."""

delete abstractmethod

delete(identifier)

Remove the entity identified by identifier.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def delete(self, identifier: str) -> None:
    """Remove the entity identified by `identifier`."""

get abstractmethod

get(identifier)

Return the entity matching identifier, or None if not found.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def get(self, identifier: str) -> T | None:
    """Return the entity matching `identifier`, or None if not found."""

CrudRepository

Bases: ReadRepository[T], WriteRepository[T]

Full CRUD repository combining read and write operations.

get abstractmethod

get(identifier)

Return the entity matching identifier, or None if not found.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def get(self, identifier: str) -> T | None:
    """Return the entity matching `identifier`, or None if not found."""

save abstractmethod

save(entity)

Persist entity and return the saved version.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def save(self, entity: T) -> T:
    """Persist `entity` and return the saved version."""

delete abstractmethod

delete(identifier)

Remove the entity identified by identifier.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def delete(self, identifier: str) -> None:
    """Remove the entity identified by `identifier`."""

find_all abstractmethod

find_all()

Return all entities in the repository.

Source code in src/iris/domain/generic/repositories.py
@abstractmethod
def find_all(self) -> Sequence[T]:
    """Return all entities in the repository."""