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)
-
Measurement model supporting conversion and arithmetic operations.
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: typing.SupportsIndex, ) -> Self: ... def __round__( self, __ndigits=None, ): """Return round(self).""" return self.replace( magnitude=round(self.magnitude, __ndigits), )
Ancestors
- typing.Generic
Class variables
var magnitude : ~_MagnitudeT
var prefix : MetricPrefix
var unit : Unit | str | None
Methods
def replace(self: Self, **changes: typing.Any)
-
Return a new object replacing specified fields with new values.
class MetricPrefix (*args, **kwds)
-
MetricPrefix IntEnum with symbol and conversion support.
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()}, )
Ancestors
- enum.IntEnum
- builtins.int
- enum.ReprEnum
- enum.Enum
Class variables
var NONE
var atto
var centi
var deca
var deci
var exa
var femto
var giga
var hecto
var kilo
var mega
var micro
var milli
var nano
var peta
var pico
var quecto
var quetta
var ronna
var ronto
var tera
var yocto
var yotta
var zepto
var zetta
Static methods
def from_symbol(symbol: str, /) ‑> MetricPrefix
-
Return MetricPrefix by symbol.
Instance variables
var symbol
-
Metric prefix symbol, e.g. G for giga.
Expand source code
def __get__(self, instance, owner=None): if instance is None: return self if self.attrname is None: raise TypeError( "Cannot use cached_property instance without calling __set_name__ on it.") try: cache = instance.__dict__ except AttributeError: # not all objects have __dict__ (e.g. class defines slots) msg = ( f"No '__dict__' attribute on {type(instance).__name__!r} " f"instance to cache {self.attrname!r} property." ) raise TypeError(msg) from None val = cache.get(self.attrname, _NOT_FOUND) if val is _NOT_FOUND: val = self.func(instance) try: cache[self.attrname] = val except TypeError: msg = ( f"The '__dict__' attribute on {type(instance).__name__!r} instance " f"does not support item assignment for caching {self.attrname!r} property." ) raise TypeError(msg) from None return val
Methods
def convert(self, _MetricPrefix__value, /, to=0)
-
Convert value from metric prefix self to to.
def to(self: MetricPrefix, _MetricPrefix__other: MetricPrefix | int, /, *, number_type: type[int | ComplexT] = builtins.int)
-
Calculate conversion factor between self and other.
class Unit (*args, **kwds)
-
Unit Enum with symbol.
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
Ancestors
- enum.Enum
Class variables
var ampere
var becquerel
var candela
var coulomb
var farad
var gram
var gray
var henry
var hertz
var joule
var katal
var kelvin
var lumen
var lux
var metre
var mole
var newton
var ohm
var one
var pascal
var radian
var second
var siemens
var sievert
var steradian
var tesla
var volt
var watt
var weber
Instance variables
var symbol
-
Get the unit symbol.
Expand source code
def __get__(self, instance, owner=None): if instance is None: return self if self.attrname is None: raise TypeError( "Cannot use cached_property instance without calling __set_name__ on it.") try: cache = instance.__dict__ except AttributeError: # not all objects have __dict__ (e.g. class defines slots) msg = ( f"No '__dict__' attribute on {type(instance).__name__!r} " f"instance to cache {self.attrname!r} property." ) raise TypeError(msg) from None val = cache.get(self.attrname, _NOT_FOUND) if val is _NOT_FOUND: val = self.func(instance) try: cache[self.attrname] = val except TypeError: msg = ( f"The '__dict__' attribute on {type(instance).__name__!r} instance " f"does not support item assignment for caching {self.attrname!r} property." ) raise TypeError(msg) from None return val