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
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
¶
Construct and return the final object. May raise on incomplete state.
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 ¶
Like :meth:build, but captures any exception as an Err
instead of raising.
BaseConfiguration
dataclass
¶
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:
BaseEntity
dataclass
¶
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 ¶
Bases: MineProductivityError
Raised when a :class:~mineproductivity.core.builder.BaseBuilder is asked
to build from incomplete or inconsistent state.
ConfigurationError ¶
DuplicateError ¶
MineProductivityError ¶
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 ¶
Bases: MineProductivityError
Raised when a lookup (e.g. :meth:~mineproductivity.core.repository.BaseRepository.get)
finds no matching object.
SerializationError ¶
ValidationError ¶
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
BaseIdentifier
dataclass
¶
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
¶
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:
Maybe
dataclass
¶
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
BaseMetadata
dataclass
¶
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
¶
Persist a new entity.
Raises:
| Type | Description |
|---|---|
DuplicateError
|
If an entity with the same id already exists. |
get
abstractmethod
¶
find
abstractmethod
¶
Return :meth:Maybe.some the entity, or :meth:Maybe.nothing
if no entity with entity_id exists.
remove
abstractmethod
¶
list
abstractmethod
¶
Return all stored entities, optionally filtered by specification.
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
¶
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
value
property
¶
The success value.
Raises:
| Type | Description |
|---|---|
Exception
|
The stored error, if this is an |
error
property
¶
The stored error.
Raises:
| Type | Description |
|---|---|
ValueError
|
If this is an |
err
classmethod
¶
Construct a failed result wrapping error.
A plain string is wrapped in
:class:~mineproductivity.core.exceptions.MineProductivityError
for convenience.
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.
DataclassSerializer ¶
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:
AndSpecification
dataclass
¶
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
¶
Return whether candidate satisfies this specification.
NotSpecification
dataclass
¶
OrSpecification
dataclass
¶
Bases: BaseSpecification[T]
A specification satisfied when either wrapped specification is satisfied.
PredicateSpecification
dataclass
¶
Bases: BaseSpecification[T]
Adapts any plain callable into a :class:BaseSpecification.
Useful for one-off specifications that do not warrant a dedicated class.
Examples:
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.
CompositeValidator ¶
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
¶
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
raise_if_invalid ¶
Raise :class:~mineproductivity.core.exceptions.ValidationError
if :attr:is_valid is False.
BaseValueObject
dataclass
¶
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:
- :meth:
_normalize-- coerce or defensively copy mutable inputs (e.g. wrap alistargument in atuple, or freeze adictinto aMappingProxyType) before validation runs. - :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 ¶
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: |
replace ¶
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
¶
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
to_dict ¶
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. |