Module peprock.models
General purpose model classes.
Sub-modules
peprock.models.measurement
-
Generic measurement model …
peprock.models.metric_prefix
-
Metric prefix model …
peprock.models.unit
-
Unit of measurement model …
Classes
class Measurement (magnitude: _MagnitudeT,
prefix: MetricPrefix = <MetricPrefix.NONE: 0>,
unit: Unit | str | None = None)-
Expand source code
@dataclasses.dataclass(frozen=True) class Measurement(typing.Generic[_MagnitudeT]): """Measurement model supporting conversion and arithmetic operations.""" magnitude: _MagnitudeT prefix: MetricPrefix = MetricPrefix.NONE unit: Unit | str | None = None @functools.cached_property def _unit_symbol(self: Self) -> str: match self.unit: case None | Unit.one: return "" case Unit(): return self.unit.symbol case _: return self.unit def __format__(self: Self, format_spec: str) -> str: """Format measurement and return str.""" if self.prefix.symbol or (self.unit and self.unit is not Unit.one): return ( f"{self.magnitude:{format_spec}} " f"{self.prefix.symbol}{self._unit_symbol}" ) return format(self.magnitude, format_spec) @functools.cached_property def _str(self: Self) -> str: return format(self) def __str__(self: Self) -> str: """Return str(self).""" return self._str @classmethod @functools.cache def _init_field_names(cls: type[Self]) -> tuple[str, ...]: return tuple(field.name for field in dataclasses.fields(cls) if field.init) def replace(self: Self, **changes: typing.Any) -> Self: """Return a new object replacing specified fields with new values.""" return type(self)( **{ field_name: ( changes[field_name] if field_name in changes else getattr(self, field_name) ) for field_name in self._init_field_names() }, ) @typing.overload def _apply_operator( self: Self, __other: Measurement[_MagnitudeS], __operator: collections.abc.Callable[ [_MagnitudeT | float, _MagnitudeS | float], _T, ], /, *, wrap_in_measurement: typing.Literal[False] = False, ) -> _T: ... @typing.overload def _apply_operator( self: Self, __other: object, __operator: collections.abc.Callable[[_MagnitudeT | float, object], _T], /, *, wrap_in_measurement: typing.Literal[False] = False, ) -> _T: ... @typing.overload def _apply_operator( self: Self, __other: Measurement[_MagnitudeS], __operator: collections.abc.Callable[ [_MagnitudeT | float, _MagnitudeS | float], _T, ], /, *, wrap_in_measurement: typing.Literal[True], ) -> Self: ... def _apply_operator( # noqa: PLR0911 self, __other, __operator, /, *, wrap_in_measurement=False, ): if isinstance(__other, Measurement) and self.unit == __other.unit: if (diff := self.prefix - __other.prefix) == 0: magnitude = __operator( self.magnitude, __other.magnitude, ) if wrap_in_measurement: return self.replace( magnitude=magnitude, ) return magnitude if diff < 0: magnitude = __operator( self.magnitude, __other.prefix.convert(__other.magnitude, to=self.prefix), ) if wrap_in_measurement: return self.replace( magnitude=magnitude, ) return magnitude magnitude = __operator( self.prefix.convert(self.magnitude, to=__other.prefix), __other.magnitude, ) if wrap_in_measurement: return self.replace( magnitude=magnitude, prefix=__other.prefix, ) return magnitude return NotImplemented def __lt__(self: Self, other: Measurement) -> bool: """Return self < other.""" return self._apply_operator(other, operator.lt) def __le__(self: Self, other: Measurement) -> bool: """Return self <= other.""" return self._apply_operator(other, operator.le) def __eq__(self: Self, other: object) -> bool: """Return self == other.""" return self._apply_operator(other, operator.eq) def __ne__(self: Self, other: object) -> bool: """Return self != other.""" return self._apply_operator(other, operator.ne) def __gt__(self: Self, other: Measurement) -> bool: """Return self > other.""" return self._apply_operator(other, operator.gt) def __ge__(self: Self, other: Measurement) -> bool: """Return self >= other.""" return self._apply_operator(other, operator.ge) @functools.cached_property def _hash(self: Self) -> int: return hash((self.prefix.convert(self.magnitude), self.unit)) def __hash__(self: Self) -> int: """Return hash(self).""" return self._hash def __abs__(self: Self) -> Self: """Return abs(self).""" return self.replace( magnitude=abs(self.magnitude), ) @typing.overload def __add__( self: Measurement[int], other: Measurement[int], ) -> Measurement[int] | Measurement[float]: ... @typing.overload def __add__( self: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], other: Measurement[float], ) -> Measurement[float]: ... @typing.overload def __add__( self: Measurement[float], other: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], ) -> Measurement[float]: ... @typing.overload def __add__( self: Measurement[int] | Measurement[decimal.Decimal], other: Measurement[decimal.Decimal], ) -> Measurement[decimal.Decimal]: ... @typing.overload def __add__( self: Measurement[decimal.Decimal], other: Measurement[int] | Measurement[decimal.Decimal], ) -> Measurement[decimal.Decimal]: ... @typing.overload def __add__( self: Measurement[int] | Measurement[fractions.Fraction], other: Measurement[fractions.Fraction], ) -> Measurement[fractions.Fraction]: ... @typing.overload def __add__( self: Measurement[fractions.Fraction], other: Measurement[int] | Measurement[fractions.Fraction], ) -> Measurement[fractions.Fraction]: ... def __add__(self, other): """Return self + other.""" return self._apply_operator(other, operator.add, wrap_in_measurement=True) @typing.overload def __floordiv__( self: Measurement[int] | Measurement[fractions.Fraction], other: int | fractions.Fraction, ) -> Measurement[int]: ... @typing.overload def __floordiv__( self: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], other: float, ) -> Measurement[float]: ... @typing.overload def __floordiv__( self: Measurement[float], other: float | fractions.Fraction, ) -> Measurement[float]: ... @typing.overload def __floordiv__( self: Measurement[int] | Measurement[decimal.Decimal], other: decimal.Decimal, ) -> Measurement[decimal.Decimal]: ... @typing.overload def __floordiv__( self: Measurement[decimal.Decimal], other: int | decimal.Decimal, ) -> Measurement[decimal.Decimal]: ... @typing.overload def __floordiv__( self: Measurement[int] | Measurement[fractions.Fraction], other: Measurement[int] | Measurement[fractions.Fraction], ) -> int: ... @typing.overload def __floordiv__( self: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], other: Measurement[float], ) -> float: ... @typing.overload def __floordiv__( self: Measurement[float], other: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], ) -> float: ... @typing.overload def __floordiv__( self: Measurement[int] | Measurement[decimal.Decimal], other: Measurement[decimal.Decimal], ) -> decimal.Decimal: ... @typing.overload def __floordiv__( self: Measurement[decimal.Decimal], other: Measurement[int] | Measurement[decimal.Decimal], ) -> decimal.Decimal: ... def __floordiv__(self, other): """Return self // other.""" match other: case int() | float() | decimal.Decimal() | fractions.Fraction(): return self.replace( magnitude=self.magnitude // other, ) return self._apply_operator(other, operator.floordiv) @typing.overload def __mod__( self: Measurement[int], other: Measurement[int], ) -> Measurement[int]: ... @typing.overload def __mod__( self: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], other: Measurement[float], ) -> Measurement[float]: ... @typing.overload def __mod__( self: Measurement[float], other: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], ) -> Measurement[float]: ... @typing.overload def __mod__( self: Measurement[int] | Measurement[decimal.Decimal], other: Measurement[decimal.Decimal], ) -> Measurement[decimal.Decimal]: ... @typing.overload def __mod__( self: Measurement[decimal.Decimal], other: Measurement[int] | Measurement[decimal.Decimal], ) -> Measurement[decimal.Decimal]: ... @typing.overload def __mod__( self: Measurement[int] | Measurement[fractions.Fraction], other: Measurement[fractions.Fraction], ) -> Measurement[fractions.Fraction]: ... @typing.overload def __mod__( self: Measurement[fractions.Fraction], other: Measurement[int] | Measurement[fractions.Fraction], ) -> Measurement[fractions.Fraction]: ... def __mod__(self, other): """Return self % other.""" return self._apply_operator(other, operator.mod, wrap_in_measurement=True) @typing.overload def __mul__( self: Measurement[int], other: int, ) -> Measurement[int]: ... @typing.overload def __mul__( self: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], other: float, ) -> Measurement[float]: ... @typing.overload def __mul__( self: Measurement[float], other: float | fractions.Fraction, ) -> Measurement[float]: ... @typing.overload def __mul__( self: Measurement[int] | Measurement[decimal.Decimal], other: decimal.Decimal, ) -> Measurement[decimal.Decimal]: ... @typing.overload def __mul__( self: Measurement[decimal.Decimal], other: int | decimal.Decimal, ) -> Measurement[decimal.Decimal]: ... @typing.overload def __mul__( self: Measurement[int] | Measurement[fractions.Fraction], other: fractions.Fraction, ) -> Measurement[fractions.Fraction]: ... @typing.overload def __mul__( self: Measurement[fractions.Fraction], other: int | fractions.Fraction, ) -> Measurement[fractions.Fraction]: ... def __mul__(self, other): """Return self * other.""" match other: case int() | float() | decimal.Decimal() | fractions.Fraction(): return self.replace( magnitude=self.magnitude * other, ) return NotImplemented @typing.overload def __rmul__( self: Measurement[int], other: int, ) -> Measurement[int]: ... @typing.overload def __rmul__( self: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], other: float, ) -> Measurement[float]: ... @typing.overload def __rmul__( self: Measurement[float], other: float | fractions.Fraction, ) -> Measurement[float]: ... @typing.overload def __rmul__( self: Measurement[int] | Measurement[decimal.Decimal], other: decimal.Decimal, ) -> Measurement[decimal.Decimal]: ... @typing.overload def __rmul__( self: Measurement[decimal.Decimal], other: int | decimal.Decimal, ) -> Measurement[decimal.Decimal]: ... @typing.overload def __rmul__( self: Measurement[int] | Measurement[fractions.Fraction], other: fractions.Fraction, ) -> Measurement[fractions.Fraction]: ... @typing.overload def __rmul__( self: Measurement[fractions.Fraction], other: int | fractions.Fraction, ) -> Measurement[fractions.Fraction]: ... def __rmul__(self, other): """Return other * self.""" return self.__mul__(other) def __neg__(self: Self) -> Self: """Return -self.""" return self.replace( magnitude=-self.magnitude, ) def __pos__(self: Self) -> Self: """Return +self.""" return self.replace( magnitude=+self.magnitude, ) @typing.overload def __sub__( self: Measurement[int], other: Measurement[int], ) -> Measurement[int] | Measurement[float]: ... @typing.overload def __sub__( self: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], other: Measurement[float], ) -> Measurement[float]: ... @typing.overload def __sub__( self: Measurement[float], other: Measurement[int] | Measurement[float] | Measurement[fractions.Fraction], ) -> Measurement[float]: ... @typing.overload def __sub__( self: Measurement[int] | Measurement[decimal.Decimal], other: Measurement[decimal.Decimal], ) -> Measurement[decimal.Decimal]: ... @typing.overload def __sub__( self: Measurement[decimal.Decimal], other: Measurement[int] | Measurement[decimal.Decimal], ) -> Measurement[decimal.Decimal]: ... @typing.overload def __sub__( self: Measurement[int] | Measurement[fractions.Fraction], other: Measurement[fractions.Fraction], ) -> Measurement[fractions.Fraction]: ... @typing.overload def __sub__( self: Measurement[fractions.Fraction], other: Measurement[int] | Measurement[fractions.Fraction], ) -> Measurement[fractions.Fraction]: ... def __sub__(self, other): """Return self - other.""" return self._apply_operator(other, operator.sub, wrap_in_measurement=True) @typing.overload def __truediv__( self: Measurement[int] | Measurement[float], other: float, ) -> Measurement[float]: ... @typing.overload def __truediv__( self: Measurement[float], other: fractions.Fraction, ) -> Measurement[float]: ... @typing.overload def __truediv__( self: Measurement[fractions.Fraction], other: float, ) -> Measurement[float]: ... @typing.overload def __truediv__( self: Measurement[int] | Measurement[decimal.Decimal], other: decimal.Decimal, ) -> Measurement[decimal.Decimal]: ... @typing.overload def __truediv__( self: Measurement[decimal.Decimal], other: int | decimal.Decimal, ) -> Measurement[decimal.Decimal]: ... @typing.overload def __truediv__( self: Measurement[int] | Measurement[fractions.Fraction], other: fractions.Fraction, ) -> Measurement[fractions.Fraction]: ... @typing.overload def __truediv__( self: Measurement[fractions.Fraction], other: int | fractions.Fraction, ) -> Measurement[fractions.Fraction]: ... @typing.overload def __truediv__( self: Measurement[int] | Measurement[float], other: Measurement[int] | Measurement[float], ) -> float: ... @typing.overload def __truediv__( self: Measurement[float], other: Measurement[fractions.Fraction], ) -> float: ... @typing.overload def __truediv__( self: Measurement[fractions.Fraction], other: Measurement[float], ) -> float: ... @typing.overload def __truediv__( self: Measurement[int] | Measurement[decimal.Decimal], other: Measurement[decimal.Decimal], ) -> decimal.Decimal: ... @typing.overload def __truediv__( self: Measurement[decimal.Decimal], other: Measurement[int] | Measurement[decimal.Decimal], ) -> decimal.Decimal: ... @typing.overload def __truediv__( self: Measurement[int] | Measurement[fractions.Fraction], other: Measurement[fractions.Fraction], ) -> fractions.Fraction: ... @typing.overload def __truediv__( self: Measurement[fractions.Fraction], other: Measurement[int] | Measurement[fractions.Fraction], ) -> fractions.Fraction: ... def __truediv__(self, other): """Return self / other.""" match other: case int() | float() | decimal.Decimal() | fractions.Fraction(): return self.replace( magnitude=self.magnitude / other, ) return self._apply_operator(other, operator.truediv) def __bool__(self: Self) -> bool: """Return True if magnitude is nonzero; otherwise return False.""" return bool(self.magnitude) def __int__(self: Self) -> int: """Return int(self).""" return int( ( self.magnitude if self.prefix is MetricPrefix.NONE else self.prefix.convert(self.magnitude) ), ) def __float__(self: Self) -> float: """Return float(self).""" return float( ( self.magnitude if self.prefix is MetricPrefix.NONE else self.prefix.convert(self.magnitude) ), ) @typing.overload def __round__(self: Self) -> Measurement[int]: ... @typing.overload def __round__(self: Self, ndigits: int, /) -> Self: ... def __round__(self, ndigits=None, /): """Return round(self).""" return self.replace( magnitude=round(self.magnitude, ndigits), )
Measurement model supporting conversion and arithmetic operations.
Ancestors
- typing.Generic
Instance variables
var magnitude : ~_MagnitudeT
-
The type of the None singleton.
var prefix : MetricPrefix
-
The type of the None singleton.
var unit : Unit | str | None
-
The type of the None singleton.
Methods
def replace(self: Self, **changes: typing.Any) ‑> Self
-
Expand source code
def replace(self: Self, **changes: typing.Any) -> Self: """Return a new object replacing specified fields with new values.""" return type(self)( **{ field_name: ( changes[field_name] if field_name in changes else getattr(self, field_name) ) for field_name in self._init_field_names() }, )
Return a new object replacing specified fields with new values.
class MetricPrefix (*args, **kwds)
-
Expand source code
class MetricPrefix(enum.IntEnum): """MetricPrefix IntEnum with symbol and conversion support.""" quetta = 30 ronna = 27 yotta = 24 zetta = 21 exa = 18 peta = 15 tera = 12 giga = 9 mega = 6 kilo = 3 hecto = 2 deca = 1 NONE = 0 deci = -1 centi = -2 milli = -3 micro = -6 nano = -9 pico = -12 femto = -15 atto = -18 zepto = -21 yocto = -24 ronto = -27 quecto = -30 @classmethod def from_symbol(cls: type[MetricPrefix], symbol: str, /) -> MetricPrefix: """Return MetricPrefix by symbol.""" return cls._by_symbol()[symbol] @functools.cached_property def symbol(self: MetricPrefix) -> str: """Metric prefix symbol, e.g. G for giga.""" return self._symbols()[self] def __str__(self: MetricPrefix) -> str: """Return symbol.""" return self.symbol @typing.overload def to( self: MetricPrefix, __other: MetricPrefix | int, /, *, number_type: type[int] = int, ) -> int | float: ... @typing.overload def to( self: MetricPrefix, __other: MetricPrefix | int, /, *, number_type: type[ComplexT], ) -> ComplexT: ... def to( self: MetricPrefix, __other: MetricPrefix | int, /, *, number_type: type[int | ComplexT] = int, ) -> int | ComplexT: """Calculate conversion factor between self and other.""" return number_type(_BASE) ** (self - __other) @typing.overload def convert( self: MetricPrefix, __value: int, /, to: MetricPrefix = NONE, # type: ignore[assignment] ) -> int | float: ... @typing.overload def convert( self: MetricPrefix, __value: ComplexT, /, to: MetricPrefix = NONE, # type: ignore[assignment] ) -> ComplexT: ... def convert( self, __value, /, to=NONE, ): """Convert value from metric prefix self to to.""" if self is to: return __value return __value * self.to(to, number_type=type(__value)) @staticmethod @functools.cache def _symbols() -> types.MappingProxyType[MetricPrefix, str]: return types.MappingProxyType( { MetricPrefix.quetta: "Q", MetricPrefix.ronna: "R", MetricPrefix.yotta: "Y", MetricPrefix.zetta: "Z", MetricPrefix.exa: "E", MetricPrefix.peta: "P", MetricPrefix.tera: "T", MetricPrefix.giga: "G", MetricPrefix.mega: "M", MetricPrefix.kilo: "k", MetricPrefix.hecto: "h", MetricPrefix.deca: "da", MetricPrefix.NONE: "", MetricPrefix.deci: "d", MetricPrefix.centi: "c", MetricPrefix.milli: "m", MetricPrefix.micro: "μ", MetricPrefix.nano: "n", MetricPrefix.pico: "p", MetricPrefix.femto: "f", MetricPrefix.atto: "a", MetricPrefix.zepto: "z", MetricPrefix.yocto: "y", MetricPrefix.ronto: "r", MetricPrefix.quecto: "q", }, ) @classmethod @functools.cache def _by_symbol( cls: type[MetricPrefix], ) -> types.MappingProxyType[str, MetricPrefix]: return types.MappingProxyType( {symbol: metric_prefix for metric_prefix, symbol in cls._symbols().items()}, )
MetricPrefix IntEnum with symbol and conversion support.
Ancestors
- enum.IntEnum
- builtins.int
- enum.ReprEnum
- enum.Enum
Class variables
var NONE
-
The type of the None singleton.
var atto
-
The type of the None singleton.
var centi
-
The type of the None singleton.
var deca
-
The type of the None singleton.
var deci
-
The type of the None singleton.
var exa
-
The type of the None singleton.
var femto
-
The type of the None singleton.
var giga
-
The type of the None singleton.
var hecto
-
The type of the None singleton.
var kilo
-
The type of the None singleton.
var mega
-
The type of the None singleton.
var micro
-
The type of the None singleton.
var milli
-
The type of the None singleton.
var nano
-
The type of the None singleton.
var peta
-
The type of the None singleton.
var pico
-
The type of the None singleton.
var quecto
-
The type of the None singleton.
var quetta
-
The type of the None singleton.
var ronna
-
The type of the None singleton.
var ronto
-
The type of the None singleton.
var tera
-
The type of the None singleton.
var yocto
-
The type of the None singleton.
var yotta
-
The type of the None singleton.
var zepto
-
The type of the None singleton.
var zetta
-
The type of the None singleton.
Static methods
def from_symbol(symbol: str, /) ‑> MetricPrefix
-
Return MetricPrefix by symbol.
Instance variables
var symbol : str
-
Expand source code
@functools.cached_property def symbol(self: MetricPrefix) -> str: """Metric prefix symbol, e.g. G for giga.""" return self._symbols()[self]
Metric prefix symbol, e.g. G for giga.
Methods
def convert(self, _MetricPrefix__value, /, to=0)
-
Expand source code
def convert( self, __value, /, to=NONE, ): """Convert value from metric prefix self to to.""" if self is to: return __value return __value * self.to(to, number_type=type(__value))
Convert value from metric prefix self to to.
def to(self: MetricPrefix,
_MetricPrefix__other: MetricPrefix | int,
/,
*,
number_type: type[int | ComplexT] = builtins.int) ‑> int | ComplexT-
Expand source code
def to( self: MetricPrefix, __other: MetricPrefix | int, /, *, number_type: type[int | ComplexT] = int, ) -> int | ComplexT: """Calculate conversion factor between self and other.""" return number_type(_BASE) ** (self - __other)
Calculate conversion factor between self and other.
class Unit (*args, **kwds)
-
Expand source code
class Unit(enum.Enum): """Unit Enum with symbol.""" # metric units, see https://en.wikipedia.org/wiki/List_of_metric_units one = "1" # unit of a quantity of dimension one second = "s" # unit of time metre = "m" # unit of length gram = "g" # unit of mass (actually kilogram in SI) ampere = "A" # unit of electric current kelvin = "K" # unit of thermodynamic temperature mole = "mol" # unit of amount of substance candela = "cd" # unit of luminous intensity hertz = "Hz" # equal to one reciprocal second radian = "rad" # equal to one steradian = "sr" # equal to one newton = "N" # equal to one kilogram-metre per second squared pascal = "Pa" # equal to one newton per square metre joule = "J" # equal to one newton-metre watt = "W" # equal to one joule per second coulomb = "C" # equal to one ampere second volt = "V" # equal to one joule per coulomb weber = "Wb" # equal to one volt-second tesla = "T" # equal to one weber per square metre farad = "F" # equal to one coulomb per volt ohm = "Ω" # equal to one volt per ampere siemens = "S" # equal to one ampere per volt henry = "H" # equal to one volt-second per ampere # degree Celsius (°C) is equal to one kelvin lumen = "lm" # equal to one candela-steradian lux = "lx" # equal to one lumen per square metre becquerel = "Bq" # equal to one reciprocal second gray = "Gy" # equal to one joule per kilogram sievert = "Sv" # equal to one joule per kilogram katal = "kat" # equal to one mole per second @functools.cached_property def symbol(self: Unit) -> str: """Get the unit symbol.""" return self.value
Unit Enum with symbol.
Ancestors
- enum.Enum
Class variables
var ampere
-
The type of the None singleton.
var becquerel
-
The type of the None singleton.
var candela
-
The type of the None singleton.
var coulomb
-
The type of the None singleton.
var farad
-
The type of the None singleton.
var gram
-
The type of the None singleton.
var gray
-
The type of the None singleton.
var henry
-
The type of the None singleton.
var hertz
-
The type of the None singleton.
var joule
-
The type of the None singleton.
var katal
-
The type of the None singleton.
var kelvin
-
The type of the None singleton.
var lumen
-
The type of the None singleton.
var lux
-
The type of the None singleton.
var metre
-
The type of the None singleton.
var mole
-
The type of the None singleton.
var newton
-
The type of the None singleton.
var ohm
-
The type of the None singleton.
var one
-
The type of the None singleton.
var pascal
-
The type of the None singleton.
var radian
-
The type of the None singleton.
var second
-
The type of the None singleton.
var siemens
-
The type of the None singleton.
var sievert
-
The type of the None singleton.
var steradian
-
The type of the None singleton.
var tesla
-
The type of the None singleton.
var volt
-
The type of the None singleton.
var watt
-
The type of the None singleton.
var weber
-
The type of the None singleton.
Instance variables
var symbol : str
-
Expand source code
@functools.cached_property def symbol(self: Unit) -> str: """Get the unit symbol.""" return self.value
Get the unit symbol.