Skip to content

mineproductivity.core

Auto-generated API reference for the Core package, rendered from the package's own docstrings. For the narrative overview, dependency rules, and extension guide, see Packages -> Core.

core

mineproductivity.core -- universal, domain-agnostic framework primitives.

This package defines the foundational building blocks every other mineproductivity package is built on: entities, value objects, identifiers, metadata, specifications, repositories, factories, builders, validators, serializers, versioned objects, configuration, and the Result/Maybe error-handling types.

core has zero dependencies on any other mineproductivity package and zero knowledge of the mining domain -- see README.md for the full set of architectural rules this package must satisfy.

Everything documented here is part of the public API and can be imported directly from mineproductivity.core, without reaching into internal modules::

from mineproductivity.core import BaseEntity, Result, BaseRepository

JSONPrimitive

JSONPrimitive = str | int | float | bool | None

A single JSON scalar value.

JSONValue

JSONValue = JSONPrimitive | list[JSONValue] | dict[str, JSONValue]

Any value representable in JSON: a primitive, or a list/dict of :data:JSONValue. Defined with PEP 695 type syntax so the recursive reference resolves lazily without manual forward-reference quoting.

BaseBuilder

Bases: ABC, Generic[T]

Fluent, step-by-step construction of an object of type T.

Prefer :class:BaseBuilder over :class:~mineproductivity.core.factory.BaseFactory when construction has many optional steps/parameters that read better as chained method calls than as a single call with many keyword arguments.

Examples:

>>> class GreetingBuilder(BaseBuilder[str]):
...     def __init__(self) -> None:
...         self._name = "world"
...     def with_name(self, name: str) -> Self:
...         self._name = name
...         return self
...     def build(self) -> str:
...         return f"Hello, {self._name}!"
>>> GreetingBuilder().with_name("MineProductivity").build()
'Hello, MineProductivity!'

build abstractmethod

build()

Construct and return the final object. May raise on incomplete state.

reset

reset()

Return the builder to its initial state so it can be reused.

The default implementation is a no-op returning self; override it if the builder accumulates mutable state that must be cleared between builds.

build_result

build_result()

Like :meth:build, but captures any exception as an Err instead of raising.

BaseConfiguration dataclass

BaseConfiguration()

Bases: BaseValueObject

Base class for immutable, validated configuration objects.

BaseConfiguration only defines the shape of configuration (immutable, self-validating, constructible from a plain mapping); it has no opinion on where configuration values come from (environment variables, files, a remote config service). Sourcing configuration is the responsibility of the mineproductivity.config package, which depends on core and produces instances of BaseConfiguration subclasses -- core itself performs no I/O.

Examples:

>>> from dataclasses import dataclass
>>> @dataclass(frozen=True, slots=True)
... class RetrySettings(BaseConfiguration):
...     max_attempts: int = 3
>>> RetrySettings.from_mapping({"max_attempts": 5}).max_attempts
5

from_mapping classmethod

from_mapping(data)

Construct an instance from a plain mapping of field values.

to_mapping

to_mapping()

Return this configuration's fields as a plain dict.

BaseEntity dataclass

BaseEntity(id)

Bases: Generic[TId]

Base class for objects whose identity, not their attributes, defines equality.

Two entities of the same concrete type with the same :attr:id are considered equal even if every other field differs; two entities with different ids are never equal, even if every other field is identical. This mirrors the standard Domain-Driven Design definition of an Entity, as distinct from :class:~mineproductivity.core.value_object.BaseValueObject, whose equality is defined entirely by attribute values.

BaseEntity is immutable (a frozen dataclass): representing a state change means producing a new instance -- typically via a dataclasses.replace-style helper in a subclass -- rather than mutating fields in place. This is consistent with the platform's event-first architecture, in which entity state is a projection of an immutable event stream, not a mutable record.

Type parameter TId — the type of this entity's identity. Any hashable type is acceptable, including a raw str/int/uuid.UUID or a dedicated :class:~mineproductivity.core.identifier.BaseIdentifier subclass (recommended for anything beyond trivial examples, since it prevents accidentally comparing/mixing ids from different entity types).

Note that __repr__ is intentionally not overridden here: each concrete subclass gets the standard dataclass-generated repr showing every field (useful for debugging), while equality and hashing use identity only. Every subclass must repeat eq=False in its own @dataclass decorator, since eq defaults to True and would otherwise silently replace the identity-based __eq__/__hash__ defined below (Python only skips generating a dunder method when the subclass itself defines it, not when an ancestor does).

Examples:

>>> from dataclasses import dataclass
>>> @dataclass(frozen=True, slots=True, eq=False)
... class Truck(BaseEntity[str]):
...     model: str
>>> Truck(id="T-1", model="CAT 793") == Truck(id="T-1", model="different")
True
>>> Truck(id="T-1", model="CAT 793") == Truck(id="T-2", model="CAT 793")
False

BuilderError

BuilderError(message, *, details=None)

Bases: MineProductivityError

Raised when a :class:~mineproductivity.core.builder.BaseBuilder is asked to build from incomplete or inconsistent state.

ConfigurationError

ConfigurationError(message, *, details=None)

Bases: MineProductivityError

Raised when configuration data is missing, malformed, or invalid.

DuplicateError

DuplicateError(message, *, details=None)

Bases: MineProductivityError

Raised when an operation would violate a uniqueness constraint.

MineProductivityError

MineProductivityError(message, *, details=None)

Bases: Exception

Root of the mineproductivity exception hierarchy.

Parameters:

Name Type Description Default
message str

A human-readable description of the error.

required
details Mapping[str, Any] | None

Optional structured context (e.g. the offending field name, id, or value) that callers can inspect programmatically instead of parsing the message string.

None

NotFoundError

NotFoundError(message, *, details=None)

Bases: MineProductivityError

Raised when a lookup (e.g. :meth:~mineproductivity.core.repository.BaseRepository.get) finds no matching object.

SerializationError

SerializationError(message, *, details=None)

Bases: MineProductivityError

Raised when an object cannot be serialized or deserialized.

ValidationError

ValidationError(message, *, details=None)

Bases: MineProductivityError

Raised when an object or candidate value fails to satisfy an invariant.

BaseFactory

Bases: ABC, Generic[T]

Encapsulates the construction of an object of type T.

Prefer a factory over a constructor or classmethod when construction requires coordinating multiple inputs, choosing between alternative implementations, or enforcing invariants that depend on more than a single value object's own fields.

Examples:

>>> class GreetingFactory(BaseFactory[str]):
...     def create(self, **kwargs: object) -> str:
...         name = kwargs.get("name", "world")
...         return f"Hello, {name}!"
>>> GreetingFactory().create(name="MineProductivity")
'Hello, MineProductivity!'
>>> GreetingFactory().create_result(name="core").is_ok
True

create abstractmethod

create(**kwargs)

Construct and return a new instance of T.

create_result

create_result(**kwargs)

Like :meth:create, but captures any exception as an Err instead of raising.

BaseIdentifier dataclass

BaseIdentifier(value)

Bases: BaseValueObject, Generic[TIdValue]

A value object that wraps a single raw identity value.

Identifiers are themselves value objects: two identifiers wrapping equal raw values are equal, regardless of which entity they identify. Subclass this to create domain-specific identifier types (e.g. TruckId, ShiftId) instead of passing raw strings/UUIDs/ints around, so a type checker can catch identifier mix-ups such as passing a TruckId where a ShiftId is expected.

Examples:

>>> from dataclasses import dataclass
>>> @dataclass(frozen=True, slots=True)
... class TruckId(BaseIdentifier[str]):
...     pass
>>> TruckId("T-1") == TruckId("T-1")
True
>>> str(TruckId("T-1"))
'T-1'

UUIDIdentifier dataclass

UUIDIdentifier(value)

Bases: BaseIdentifier[UUID]

A :class:BaseIdentifier backed by a standard library :class:uuid.UUID.

The most common concrete identifier type: generate one with :meth:generate, or parse an existing canonical string with :meth:from_string.

Examples:

>>> identifier = UUIDIdentifier.generate()
>>> identifier == UUIDIdentifier.from_string(str(identifier))
True

generate classmethod

generate()

Return a new, randomly generated identifier (UUID version 4).

from_string classmethod

from_string(value)

Parse a canonical UUID string (e.g. "12345678-1234-...") into an identifier.

Maybe dataclass

Maybe(_value=None, _has_value=False)

Bases: Generic[T]

A container that either holds a value (:meth:some) or does not (:meth:nothing).

Construct instances via :meth:some / :meth:nothing, not the constructor directly.

Examples:

>>> def find_even(values: list[int]) -> Maybe[int]:
...     for v in values:
...         if v % 2 == 0:
...             return Maybe.some(v)
...     return Maybe.nothing()
>>> find_even([1, 3, 4]).unwrap_or(-1)
4
>>> find_even([1, 3, 5]).unwrap_or(-1)
-1
>>> find_even([1, 3, 5]).is_nothing
True

is_some property

is_some

Whether this Maybe holds a value.

is_nothing property

is_nothing

Whether this Maybe is empty.

some classmethod

some(value)

Construct a Maybe holding value.

nothing classmethod

nothing()

Construct an empty Maybe.

unwrap

unwrap()

Return the held value.

Raises:

Type Description
ValueError

If this Maybe is empty.

unwrap_or

unwrap_or(default)

Return the held value, or default if empty.

unwrap_or_else

unwrap_or_else(fn)

Return the held value, or the result of calling fn() if empty.

map

map(fn)

Transform the held value, leaving an empty Maybe untouched.

and_then

and_then(fn)

Chain another Maybe-returning operation, short-circuiting on empty.

filter

filter(predicate)

Keep the value only if it satisfies predicate, else become empty.

to_result

to_result(error)

Convert to a :class:~mineproductivity.core.result.Result, using error if this Maybe is empty.

BaseMetadata dataclass

BaseMetadata(name, *, description='', tags=frozenset(), attributes=dict())

Bases: BaseValueObject

Descriptive metadata that can be attached to any domain object.

BaseMetadata deliberately knows nothing about what it describes: downstream packages (e.g. a future kpis package attaching units and provenance to a metric) compose it into their own metadata types rather than subclassing it with domain-specific fields, keeping core free of domain knowledge. This mirrors the platform-wide metadata-first principle: every object that needs describing gets a BaseMetadata-shaped companion before it gets behavior.

Attributes:

Name Type Description
name str

A short, human-readable, non-empty label.

description str

A longer free-text description. Empty by default.

tags frozenset[str]

An immutable set of free-form classification tags.

attributes Mapping[str, Any]

An immutable mapping of arbitrary additional key/value data.

Examples:

>>> meta = BaseMetadata(name="example", tags=["a", "b"], attributes={"k": 1})
>>> sorted(meta.tags)
['a', 'b']
>>> meta.attributes["k"]
1

BaseRepository

Bases: ABC, Generic[TEntity, TId]

The abstract contract for storing and retrieving :class:~mineproductivity.core.entity.BaseEntity aggregates by identity, independent of the underlying storage technology.

core defines only the contract; concrete persistence (SQL, document store, remote API) belongs in the package that needs it and depends on core.repository, never the other way around (Dependency Inversion Principle). :class:InMemoryRepository is the one concrete implementation provided here, intended for tests and examples rather than production persistence.

add abstractmethod

add(entity)

Persist a new entity.

Raises:

Type Description
DuplicateError

If an entity with the same id already exists.

get abstractmethod

get(entity_id)

Return the entity with entity_id.

Raises:

Type Description
NotFoundError

If no such entity exists.

find abstractmethod

find(entity_id)

Return :meth:Maybe.some the entity, or :meth:Maybe.nothing if no entity with entity_id exists.

remove abstractmethod

remove(entity_id)

Remove the entity with entity_id.

Raises:

Type Description
NotFoundError

If no such entity exists.

list abstractmethod

list(specification=None)

Return all stored entities, optionally filtered by specification.

InMemoryRepository

InMemoryRepository()

Bases: BaseRepository[TEntity, TId]

A dictionary-backed :class:BaseRepository, suitable for unit tests, examples, and prototypes -- not for production use.

Examples:

>>> from dataclasses import dataclass
>>> @dataclass(frozen=True, slots=True, eq=False)
... class Item(BaseEntity[str]):
...     name: str
>>> repo: InMemoryRepository[Item, str] = InMemoryRepository()
>>> repo.add(Item(id="1", name="widget"))
>>> repo.get("1").name
'widget'
>>> "1" in repo
True

Result dataclass

Result(_value=None, _error=None, _is_ok=True)

Bases: Generic[T]

The outcome of an operation that can fail, without raising.

Construct instances via :meth:ok / :meth:err, not the constructor directly -- the underscore-prefixed fields exist only so that dataclasses can provide equality, hashing, and a base repr for free; :meth:__post_init__ enforces that exactly one of a value or an error is ever present.

Examples:

>>> def divide(a: float, b: float) -> Result[float]:
...     if b == 0:
...         return Result.err("division by zero")
...     return Result.ok(a / b)
>>> divide(10, 2).unwrap()
5.0
>>> divide(10, 0).unwrap_or(0.0)
0.0
>>> divide(10, 2).map(lambda x: x * 2).unwrap()
10.0

is_ok property

is_ok

Whether this result represents success.

is_err property

is_err

Whether this result represents failure.

value property

value

The success value.

Raises:

Type Description
Exception

The stored error, if this is an Err result.

error property

error

The stored error.

Raises:

Type Description
ValueError

If this is an Ok result (which has no error).

ok classmethod

ok(value)

Construct a successful result wrapping value.

err classmethod

err(error)

Construct a failed result wrapping error.

A plain string is wrapped in :class:~mineproductivity.core.exceptions.MineProductivityError for convenience.

unwrap

unwrap()

Alias for :attr:value, for parity with Rust/returns-style APIs.

unwrap_or

unwrap_or(default)

Return the success value, or default if this is an Err.

unwrap_or_else

unwrap_or_else(fn)

Return the success value, or the result of calling fn(error).

map

map(fn)

Transform the success value, leaving an Err untouched.

map_err

map_err(fn)

Transform the error, leaving an Ok untouched.

and_then

and_then(fn)

Chain another fallible operation, short-circuiting on Err.

BaseSerializer

Bases: ABC, Generic[T]

Converts between an in-memory object of type T and a plain, JSON-safe mapping.

Serialization is deliberately kept out of the object being serialized (composition over inheritance): a domain type does not need to know how it will be serialized, only that a BaseSerializer exists that can do it, which keeps formats (JSON, a database row, a wire protocol) fully swappable without touching the domain type.

serialize abstractmethod

serialize(obj)

Convert obj into a plain mapping of primitive values.

deserialize abstractmethod

deserialize(data)

Reconstruct an instance of T from a plain mapping.

DataclassSerializer

DataclassSerializer(target_type)

Bases: BaseSerializer[T]

A ready-to-use :class:BaseSerializer for any dataclasses-based type.

Examples:

>>> from dataclasses import dataclass
>>> @dataclass(frozen=True, slots=True)
... class Point:
...     x: int
...     y: int
>>> serializer = DataclassSerializer(Point)
>>> data = serializer.serialize(Point(1, 2))
>>> serializer.deserialize(data)
Point(x=1, y=2)

SupportsFromDict

Bases: Protocol

Structural type for any type exposing a from_dict(...) classmethod.

SupportsToDict

Bases: Protocol

Structural type for any object exposing a to_dict() method.

BaseService

Bases: ABC

Marker base class for stateless application/domain services.

A "service" in Domain-Driven Design is an operation that does not naturally belong to any single :class:~mineproductivity.core.entity.BaseEntity or :class:~mineproductivity.core.value_object.BaseValueObject -- typically because it coordinates several of them (repositories, factories, other aggregates). BaseService intentionally declares no abstract methods: the shape of a service's public API is entirely use-case-specific. Subclass it to document intent and to give tooling/tests a common type to discover services by.

Examples:

>>> class TransferService(BaseService):
...     def transfer(self, amount: int) -> int:
...         return amount
>>> TransferService().transfer(5)
5

AndSpecification dataclass

AndSpecification(left, right)

Bases: BaseSpecification[T]

A specification satisfied when both wrapped specifications are satisfied.

BaseSpecification

Bases: ABC, Generic[T]

A composable predicate over candidates of type T.

A specification encapsulates a single business rule as an object, which can then be combined with &, |, and ~ to build up more complex rules without editing existing classes (Open/Closed Principle). Specifications can be reused anywhere a yes/no decision about a candidate is required -- most notably to filter :meth:~mineproductivity.core.repository.BaseRepository.list results.

Examples:

>>> class IsPositive(BaseSpecification[int]):
...     def is_satisfied_by(self, candidate: int) -> bool:
...         return candidate > 0
>>> class IsEven(BaseSpecification[int]):
...     def is_satisfied_by(self, candidate: int) -> bool:
...         return candidate % 2 == 0
>>> spec = IsPositive() & IsEven()
>>> spec.is_satisfied_by(4)
True
>>> spec.is_satisfied_by(-4)
False
>>> (~IsPositive()).is_satisfied_by(-1)
True

is_satisfied_by abstractmethod

is_satisfied_by(candidate)

Return whether candidate satisfies this specification.

NotSpecification dataclass

NotSpecification(wrapped)

Bases: BaseSpecification[T]

A specification satisfied when the wrapped specification is not.

OrSpecification dataclass

OrSpecification(left, right)

Bases: BaseSpecification[T]

A specification satisfied when either wrapped specification is satisfied.

PredicateSpecification dataclass

PredicateSpecification(predicate)

Bases: BaseSpecification[T]

Adapts any plain callable into a :class:BaseSpecification.

Useful for one-off specifications that do not warrant a dedicated class.

Examples:

>>> adult = PredicateSpecification(lambda age: age >= 18)
>>> adult.is_satisfied_by(21)
True

Comparable

Bases: Protocol

Structural type for anything supporting < comparison.

Satisfied by any object implementing __lt__, without requiring inheritance from a common base -- useful for generic sorting/ranking utilities that should accept both core types and plain built-ins (int, str, datetime, ...).

Identifiable

Bases: Protocol[TId]

Structural type for anything exposing an id attribute.

Satisfied by any :class:~mineproductivity.core.entity.BaseEntity instance, but also by any unrelated class that happens to expose an id attribute -- callers that only need "has an id" rather than "is-a Entity" should prefer this Protocol over importing :class:~mineproductivity.core.entity.BaseEntity directly.

BaseValidator

Bases: ABC, Generic[T]

A standalone object that knows how to validate a candidate of type T.

Separating validation from the object being validated follows the Single Responsibility Principle: a value object enforces its own invariants in validate()/_normalize() (see :class:~mineproductivity.core.value_object.BaseValueObject), while a BaseValidator enforces contextual business rules that may depend on external state (a database lookup, another aggregate, a configuration flag) and that may differ by use case.

validate abstractmethod

validate(candidate)

Validate candidate and return the accumulated result.

CompositeValidator

CompositeValidator(*validators)

Bases: BaseValidator[T]

Combines multiple validators into one, merging all of their errors.

Demonstrates composition over inheritance: instead of subclassing to add a rule, wrap additional :class:BaseValidator instances.

Examples:

>>> class NotEmpty(BaseValidator[str]):
...     def validate(self, candidate: str) -> ValidationResult:
...         return (
...             ValidationResult.success()
...             if candidate
...             else ValidationResult.failure("must not be empty")
...         )
>>> class MaxLength(BaseValidator[str]):
...     def validate(self, candidate: str) -> ValidationResult:
...         return (
...             ValidationResult.success()
...             if len(candidate) <= 3
...             else ValidationResult.failure("too long")
...         )
>>> CompositeValidator(NotEmpty(), MaxLength()).validate("abcd").errors
('too long',)

ValidationResult dataclass

ValidationResult(errors=())

The outcome of running a :class:BaseValidator over a candidate.

Examples:

>>> ValidationResult.success().is_valid
True
>>> ValidationResult.failure("too short", "missing field").is_valid
False

is_valid property

is_valid

Whether validation produced zero errors.

success classmethod

success()

Construct a result with no errors.

failure classmethod

failure(*errors)

Construct a result carrying one or more error messages.

merge

merge(other)

Combine two results, accumulating errors from both.

raise_if_invalid

raise_if_invalid()

Raise :class:~mineproductivity.core.exceptions.ValidationError if :attr:is_valid is False.

BaseValueObject dataclass

BaseValueObject()

Base class for immutable objects whose equality is defined entirely by their attribute values, never by identity.

Subclasses must be decorated with @dataclasses.dataclass(frozen=True) (slots=True is recommended for memory efficiency). Equality and hashing are then derived automatically by the dataclass machinery from the declared fields -- two value objects of the same type with equal fields are equal and hash identically, by design.

Two extension hooks run automatically after __init__, in this order:

  1. :meth:_normalize -- coerce or defensively copy mutable inputs (e.g. wrap a list argument in a tuple, or freeze a dict into a MappingProxyType) before validation runs.
  2. :meth:validate -- raise an exception (conventionally :class:~mineproductivity.core.exceptions.ValidationError) if the instance violates a domain invariant.

Examples:

>>> from dataclasses import dataclass
>>> @dataclass(frozen=True, slots=True)
... class Money(BaseValueObject):
...     amount: int
...     currency: str
...
...     def validate(self) -> None:
...         if self.amount < 0:
...             raise ValueError("amount must be non-negative")
>>> Money(10, "USD") == Money(10, "USD")
True
>>> Money(10, "USD").replace(amount=20)
Money(amount=20, currency='USD')

validate

validate()

Override to enforce invariants on this value object's fields.

The default implementation does nothing (no invariants).

Raises:

Type Description
Exception

Any exception may be raised to reject invalid state; :class:~mineproductivity.core.exceptions.ValidationError is preferred for consistency with the rest of the framework.

replace

replace(**changes)

Return a new instance with the given fields replaced.

Thin, discoverable wrapper around :func:dataclasses.replace so callers do not need to import :mod:dataclasses themselves. The replacement instance goes through :meth:_normalize and :meth:validate again, exactly as if it had been constructed directly.

BaseVersionedObject dataclass

BaseVersionedObject(*, version=1)

Bases: BaseValueObject

A value object carrying an explicit, monotonically increasing version.

Because value objects are immutable, "changing" a versioned object means producing a new instance with an incremented version via :meth:next_version, rather than mutating the original in place. This is the building block for optimistic-concurrency patterns: callers persist a version alongside the object and reject writes whose expected version does not match the currently stored one.

Examples:

>>> from dataclasses import dataclass
>>> @dataclass(frozen=True, slots=True)
... class Note(BaseVersionedObject):
...     text: str
>>> note = Note(text="draft")
>>> updated = note.next_version()
>>> updated.version
2

next_version

next_version()

Return a new instance identical to this one but with version + 1.

to_dict

to_dict(obj)

Best-effort conversion of obj into a plain dict.

Prefers a to_dict() method if obj implements :class:SupportsToDict; falls back to :func:dataclasses.asdict-equivalent recursion (see :func:_asdict_recursive) for plain dataclass instances.

Raises:

Type Description
SerializationError

If neither strategy applies.