Source code for codegen_database.types.numeric

"""Numeric / decimal column types for codegen_database.

``"numeric"`` and ``"decimal"`` are synonyms for
:class:`~sqlalchemy.Numeric` (PostgreSQL ``NUMERIC``); ``"decimal"``
exists because that is the name most callers reach for when they want
an exact fixed-point amount with an explicit precision/scale.
``"integer"`` stays the minor-unit (e.g. cents) default.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Literal

from sqlalchemy import Integer, Numeric

from codegen_database.errors import CodegenDatabaseValidationError

if TYPE_CHECKING:
    from sqlalchemy.types import TypeEngine

ValueType = Literal["integer", "numeric", "decimal"]
"""Named scalar column types accepted by the ledger factories.

``"numeric"`` and ``"decimal"`` are synonyms for
:class:`~sqlalchemy.Numeric`; ``"integer"`` is the minor-unit
(e.g. cents) default.  Used to type the ``value_type`` argument of
:class:`~codegen_database.factory.ledger.LedgerTablePlugin`,
:class:`~codegen_database.plugins.snapshot.LedgerSnapshotPlugin`,
and :func:`build_value_type`.
"""


VALUE_TYPES: dict[ValueType, type[TypeEngine]] = {
    "integer": Integer,
    "numeric": Numeric,
    "decimal": Numeric,
}
"""Maps each :data:`ValueType` to its SQLAlchemy type class.

``"numeric"`` and ``"decimal"`` both resolve to
:class:`~sqlalchemy.Numeric`.  Used to type the ledger ``value`` /
snapshot ``balance`` columns.
"""


[docs] def build_value_type( value_type: ValueType, *, precision: int | None = None, scale: int | None = None, ) -> TypeEngine: """Instantiate the SQLAlchemy type for a named value column. Args: value_type: One of :data:`ValueType`. precision: Total number of digits for a ``NUMERIC`` column (the ``NUMERIC(precision, scale)`` first argument). Only valid for the ``"numeric"`` / ``"decimal"`` types. scale: Number of digits after the decimal point. Requires *precision* -- ``NUMERIC`` cannot fix a scale without a precision. Returns: A configured :class:`~sqlalchemy.types.TypeEngine` instance ready to drop into a :class:`~sqlalchemy.Column`. Raises: CodegenDatabaseValidationError: If *value_type* is unknown, if precision/scale are supplied for a non-``NUMERIC`` type, or if *scale* is given without *precision*. """ if value_type not in VALUE_TYPES: msg = ( f"Unknown value_type {value_type!r}. " f"Must be one of: {sorted(VALUE_TYPES)}" ) raise CodegenDatabaseValidationError(msg) sa_type = VALUE_TYPES[value_type] if precision is None and scale is None: return sa_type() if sa_type is not Numeric: msg = ( f"precision/scale are only valid for the 'numeric' / " f"'decimal' value types, not {value_type!r}." ) raise CodegenDatabaseValidationError(msg) if precision is None: msg = ( "scale requires precision " "(NUMERIC needs a precision to fix a scale)." ) raise CodegenDatabaseValidationError(msg) return Numeric(precision, scale)