metricengine.units

class metricengine.units.Unit[source]

Bases: object

Base unit class.

class metricengine.units.Dimensionless[source]

Bases: Unit

Unit for dimensionless values.

class metricengine.units.Ratio[source]

Bases: Unit

Unit for ratio values.

class metricengine.units.Percent[source]

Bases: Ratio

Unit for percentage values (inherits from Ratio).

class metricengine.units.Money[source]

Bases: Unit

Unit for monetary values.

code: str = 'USD'
metricengine.units.currency_unit(code)[source]

Create a Money unit class with a specific currency code.

Return type:

type[Money]

metricengine.units.USD

alias of CurrencyMoney

metricengine.units.EUR

alias of CurrencyMoney

metricengine.units.GBP

alias of CurrencyMoney

metricengine.units.ZAR

alias of CurrencyMoney

class metricengine.units.NewUnit(category, code)[source]

Bases: object

Generic unit with category and code dimensions.

A unit represents a measurement dimension with both a category (the type of measurement) and a specific code (the particular unit within that category). Units are immutable and hashable, making them suitable for use as dictionary keys in conversion registries.

category

The category of measurement (e.g., “Money”, “Quantity”, “Percent”)

code

The specific unit code within the category (e.g., “USD”, “kg”, “ratio”)

Examples

>>> usd = NewUnit("Money", "USD")
>>> str(usd)
'Money[USD]'
>>> kg = NewUnit("Quantity", "kg")
>>> str(kg)
'Quantity[kg]'
>>> ratio = NewUnit("Percent", "ratio")
>>> str(ratio)
'Percent[ratio]'
category: str
code: str
metricengine.units.MoneyUnit(code)[source]

Create a Money unit with the specified currency code.

Parameters:

code (str) – Currency code (e.g., “USD”, “GBP”, “EUR”)

Return type:

NewUnit

Returns:

NewUnit with “Money” category and the specified code

Example

>>> usd = MoneyUnit("USD")
>>> str(usd)
'Money[USD]'
metricengine.units.Qty(code)[source]

Create a Quantity unit with the specified unit code.

Parameters:

code (str) – Quantity unit code (e.g., “kg”, “L”, “m”)

Return type:

NewUnit

Returns:

NewUnit with “Quantity” category and the specified code

Example

>>> kg = Qty("kg")
>>> str(kg)
'Quantity[kg]'
metricengine.units.Pct(code='ratio')[source]

Create a Percent unit with the specified code.

Parameters:

code (str) – Percent unit code, defaults to “ratio”

Return type:

NewUnit

Returns:

NewUnit with “Percent” category and the specified code

Example

>>> ratio = Pct()
>>> str(ratio)
'Percent[ratio]'
>>> bp = Pct("bp")
>>> str(bp)
'Percent[bp]'
class metricengine.units.ConversionContext(at=None, meta=<factory>)[source]

Bases: object

Context information for unit conversions.

Provides metadata and timing information that conversion functions can use to perform dynamic rate lookups or apply business rules. This allows conversion functions to access external data sources like exchange rate APIs or historical rate databases.

at

Optional timestamp or date string for rate lookups

meta

Dictionary of additional metadata (rates, tenant info, etc.)

Examples

>>> # Simple context with timestamp
>>> ctx = ConversionContext(at="2025-09-06T10:30:00Z")
>>>
>>> # Context with metadata
>>> ctx = ConversionContext(
...     at="2025-09-06",
...     meta={"rate": "0.79", "source": "ECB"}
... )
at: Optional[str] = None
meta: dict[str, str]
class metricengine.units.Conversion(src, dst, fn)[source]

Bases: object

Represents a registered conversion between two units.

Contains the source unit, destination unit, and the function that performs the actual conversion calculation. Conversion functions receive the value to convert and a context object that can provide additional information like exchange rates.

src

Source unit for the conversion

dst

Destination unit for the conversion

fn

Function that performs the conversion, taking (Decimal, ConversionContext) -> Decimal

Examples

>>> usd = MoneyUnit("USD")
>>> gbp = MoneyUnit("GBP")
>>>
>>> def usd_to_gbp(value: Decimal, ctx: ConversionContext) -> Decimal:
...     return value * Decimal("0.79")
>>>
>>> conversion = Conversion(usd, gbp, usd_to_gbp)
src: NewUnit
dst: NewUnit
fn: Callable[[Decimal, ConversionContext], Decimal]
metricengine.units.register_conversion(src, dst)[source]

Decorator for registering conversion functions between units.

The decorated function must accept a Decimal value and ConversionContext, and return a Decimal result.

Parameters:
  • src (NewUnit) – Source unit for the conversion

  • dst (NewUnit) – Destination unit for the conversion

Returns:

Decorator function that registers the conversion

Raises:

ValueError – If the function signature is invalid

Example

>>> usd = MoneyUnit("USD")
>>> gbp = MoneyUnit("GBP")
>>>
>>> @register_conversion(usd, gbp)
... def usd_to_gbp(value: Decimal, ctx: ConversionContext) -> Decimal:
...     # Simple fixed rate for example
...     return value * Decimal("0.79")
metricengine.units.get_conversion(src, dst)[source]

Get a registered conversion between two units.

Parameters:
Return type:

Conversion

Returns:

Conversion object containing the conversion function

Raises:

KeyError – If no conversion is registered for the unit pair

metricengine.units.list_conversions()[source]

Get a copy of all registered conversions.

Return type:

dict[tuple[NewUnit, NewUnit], Conversion]

Returns:

Dictionary mapping unit pairs to their conversions

class metricengine.units.ConversionPolicy(strict=True, allow_paths=True)[source]

Bases: object

Policy configuration for unit conversions.

Controls the behavior of the conversion system, including whether to raise errors on missing conversions and whether to allow multi-hop conversion paths through intermediate units.

strict

If True, raise KeyError on missing conversions; if False, return original value

allow_paths

If True, enable multi-hop conversions; if False, only direct conversions

Examples

>>> # Strict policy (default) - raises on missing conversions
>>> strict_policy = ConversionPolicy(strict=True, allow_paths=True)
>>>
>>> # Permissive policy - returns original value on missing conversions
>>> permissive_policy = ConversionPolicy(strict=False, allow_paths=True)
>>>
>>> # Direct-only policy - no multi-hop conversions
>>> direct_only = ConversionPolicy(strict=True, allow_paths=False)
strict: bool = True
allow_paths: bool = True
metricengine.units.use_conversions(policy)[source]

Context manager for scoped conversion policy.

Temporarily sets the conversion policy for the duration of the context. The previous policy is restored when the context exits.

Parameters:

policy (ConversionPolicy) – ConversionPolicy to use within the context

Example

>>> permissive_policy = ConversionPolicy(strict=False, allow_paths=True)
>>> with use_conversions(permissive_policy):
...     # Conversions within this block use permissive policy
...     result = convert_decimal(value, usd, gbp)
metricengine.units.get_current_conversion_policy()[source]

Get the current conversion policy from context.

Return type:

ConversionPolicy

Returns:

Current ConversionPolicy in effect

metricengine.units.convert_decimal(value, src, dst, *, at=None, meta=None)[source]

Convert a decimal value from one unit to another.

This function performs unit-to-unit conversion using the registered conversion functions. It handles same-unit conversions by returning the original value unchanged, and supports multi-hop conversions through intermediate units when no direct conversion exists.

The behavior is controlled by the current ConversionPolicy: - strict=True: Raises KeyError on missing conversions - strict=False: Returns original value on missing conversions - allow_paths=True: Enables multi-hop conversions - allow_paths=False: Only allows direct conversions

Parameters:
  • value (Decimal) – The decimal value to convert

  • src (NewUnit) – Source unit

  • dst (NewUnit) – Destination unit

  • at (Optional[str]) – Optional timestamp for rate lookups

  • meta (Optional[dict[str, str]]) – Optional metadata dictionary for conversion context

Return type:

Decimal

Returns:

Converted decimal value, or original value if conversion fails and strict=False

Raises:
  • KeyError – If no conversion path exists and strict=True

  • ValueError – If conversion function raises an exception and strict=True

Example

>>> usd = MoneyUnit("USD")
>>> gbp = MoneyUnit("GBP")
>>>
>>> # Strict mode (default) - raises on missing conversion
>>> result = convert_decimal(Decimal("100"), usd, gbp)
>>>
>>> # Permissive mode - returns original on missing conversion
>>> policy = ConversionPolicy(strict=False)
>>> with use_conversions(policy):
...     result = convert_decimal(Decimal("100"), usd, gbp)
...     # Returns Decimal("100") if no conversion exists

Base Unit Class

class metricengine.units.Unit[source]

Bases: object

Base unit class.

Built-in Units

class metricengine.units.Percent[source]

Bases: Ratio

Unit for percentage values (inherits from Ratio).