Source code for pint.definitions

"""
    pint.definitions
    ~~~~~~~~~~~~~~~~

    Functions and classes related to unit definitions.

    :copyright: 2016 by Pint Authors, see AUTHORS for more details.
    :license: BSD, see LICENSE for more details.
"""

from .converters import OffsetConverter, ScaleConverter
from .errors import DefinitionSyntaxError
from .util import ParserHelper, UnitsContainer, _is_dim


class _NotNumeric(Exception):
    def __init__(self, value):
        self.value = value


def numeric_parse(s):
    ph = ParserHelper.from_string(s)

    if len(ph):
        raise _NotNumeric(s)

    return ph.scale


[docs]class Definition: """Base class for definitions. Parameters ---------- name : str Canonical name of the unit/prefix/etc. symbol : str or None A short name or symbol for the definition. aliases : iterable of str Other names for the unit/prefix/etc. converter : callable an instance of Converter. """ def __init__(self, name, symbol, aliases, converter): self._name = name self._symbol = symbol self._aliases = aliases self._converter = converter @property def is_multiplicative(self): return self._converter.is_multiplicative
[docs] @classmethod def from_string(cls, definition): """Parse a definition Parameters ---------- definition : Returns ------- """ name, definition = definition.split("=", 1) name = name.strip() result = [res.strip() for res in definition.split("=")] # @alias name = alias1 = alias2 = ... if name.startswith("@alias "): name = name[len("@alias ") :].lstrip() return AliasDefinition(name, tuple(result)) value, aliases = result[0], tuple([x for x in result[1:] if x != ""]) symbol, aliases = (aliases[0], aliases[1:]) if aliases else (None, aliases) if symbol == "_": symbol = None aliases = tuple([x for x in aliases if x != "_"]) if name.startswith("["): return DimensionDefinition(name, symbol, aliases, value) elif name.endswith("-"): name = name.rstrip("-") return PrefixDefinition(name, symbol, aliases, value) else: return UnitDefinition(name, symbol, aliases, value)
@property def name(self): return self._name @property def symbol(self): return self._symbol or self._name @property def has_symbol(self): return bool(self._symbol) @property def aliases(self): return self._aliases def add_aliases(self, *alias): alias = tuple(a for a in alias if a not in self._aliases) self._aliases = self._aliases + alias @property def converter(self): return self._converter def __str__(self): return self.name
[docs]class PrefixDefinition(Definition): """Definition of a prefix.""" def __init__(self, name, symbol, aliases, converter): if isinstance(converter, str): try: converter = ScaleConverter(numeric_parse(converter)) except _NotNumeric as ex: raise ValueError( f"Prefix definition ('{name}') must contain only numbers, not {ex.value}" ) aliases = tuple(alias.strip("-") for alias in aliases) if symbol: symbol = symbol.strip("-") super().__init__(name, symbol, aliases, converter)
[docs]class UnitDefinition(Definition): """Definition of a unit. Parameters ---------- reference : UnitsContainer Reference units. is_base : bool Indicates if it is a base unit. """ def __init__(self, name, symbol, aliases, converter, reference=None, is_base=False): self.reference = reference self.is_base = is_base if isinstance(converter, str): if ";" in converter: [converter, modifiers] = converter.split(";", 2) try: modifiers = dict( (key.strip(), numeric_parse(value)) for key, value in ( part.split(":") for part in modifiers.split(";") ) ) except _NotNumeric as ex: raise ValueError( f"Unit definition ('{name}') must contain only numbers in modifier, not {ex.value}" ) else: modifiers = {} converter = ParserHelper.from_string(converter) if not any(_is_dim(key) for key in converter.keys()): self.is_base = False elif all(_is_dim(key) for key in converter.keys()): self.is_base = True else: raise DefinitionSyntaxError( "Cannot mix dimensions and units in the same definition. " "Base units must be referenced only to dimensions. " "Derived units must be referenced only to units." ) self.reference = UnitsContainer(converter) if modifiers.get("offset", 0.0) != 0.0: converter = OffsetConverter(converter.scale, modifiers["offset"]) else: converter = ScaleConverter(converter.scale) super().__init__(name, symbol, aliases, converter)
[docs]class DimensionDefinition(Definition): """Definition of a dimension.""" def __init__(self, name, symbol, aliases, converter, reference=None, is_base=False): self.reference = reference self.is_base = is_base if isinstance(converter, str): converter = ParserHelper.from_string(converter) if not converter: self.is_base = True elif all(_is_dim(key) for key in converter.keys()): self.is_base = False else: raise DefinitionSyntaxError( "Base dimensions must be referenced to None. " "Derived dimensions must only be referenced " "to dimensions." ) self.reference = UnitsContainer(converter) super().__init__(name, symbol, aliases, converter=None)
[docs]class AliasDefinition(Definition): """Additional alias(es) for an already existing unit""" def __init__(self, name, aliases): super().__init__(name=name, symbol=None, aliases=aliases, converter=None)