1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
"""
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 __future__ import annotations
from dataclasses import dataclass
from typing import Callable, Optional, Tuple, Union
from .converters import Converter
@dataclass(frozen=True)
class PreprocessedDefinition:
"""Splits a definition into the constitutive parts.
A definition is given as a string with equalities in a single line::
---------------> rhs
a = b = c = d = e
| | | -------> aliases (optional)
| | |
| | -----------> symbol (use "_" for no symbol)
| |
| ---------------> value
|
-------------------> name
"""
name: str
symbol: Optional[str]
aliases: Tuple[str, ...]
value: str
rhs_parts: Tuple[str, ...]
@classmethod
def from_string(cls, definition: str) -> PreprocessedDefinition:
name, definition = definition.split("=", 1)
name = name.strip()
rhs_parts = tuple(res.strip() for res in definition.split("="))
value, aliases = rhs_parts[0], tuple([x for x in rhs_parts[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 != "_"])
return cls(name, symbol, aliases, value, rhs_parts)
@dataclass(frozen=True)
class Definition:
"""Base class for definitions.
Parameters
----------
name : str
Canonical name of the unit/prefix/etc.
defined_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 or Converter or None
"""
name: str
defined_symbol: Optional[str]
aliases: Tuple[str, ...]
converter: Optional[Union[Callable, Converter]]
_subclasses = []
_default_subclass = None
def __init_subclass__(cls, **kwargs):
if kwargs.pop("default", False):
if cls._default_subclass is not None:
raise ValueError("There is already a registered default definition.")
Definition._default_subclass = cls
super().__init_subclass__(**kwargs)
cls._subclasses.append(cls)
def __post_init__(self):
if isinstance(self.converter, str):
raise TypeError(
"The converter parameter cannot be an instance of `str`. Use `from_string` method"
)
@property
def is_multiplicative(self) -> bool:
return self.converter.is_multiplicative
@property
def is_logarithmic(self) -> bool:
return self.converter.is_logarithmic
@classmethod
def accept_to_parse(cls, preprocessed: PreprocessedDefinition):
return False
@classmethod
def from_string(
cls, definition: Union[str, PreprocessedDefinition], non_int_type: type = float
) -> Definition:
"""Parse a definition.
Parameters
----------
definition : str or PreprocessedDefinition
non_int_type : type
Returns
-------
Definition or subclass of Definition
"""
if isinstance(definition, str):
definition = PreprocessedDefinition.from_string(definition)
for subclass in cls._subclasses:
if subclass.accept_to_parse(definition):
return subclass.from_string(definition, non_int_type)
if cls._default_subclass is None:
raise ValueError("No matching definition (and no default parser).")
return cls._default_subclass.from_string(definition, non_int_type)
@property
def symbol(self) -> str:
return self.defined_symbol or self.name
@property
def has_symbol(self) -> bool:
return bool(self.defined_symbol)
def add_aliases(self, *alias: str) -> None:
raise Exception("Cannot add aliases, definitions are inmutable.")
def __str__(self) -> str:
return self.name
|