GeneralManager 0.9.0__py3-none-any.whl → 0.9.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- general_manager/measurement/measurement.py +126 -51
- {generalmanager-0.9.0.dist-info → generalmanager-0.9.1.dist-info}/METADATA +1 -1
- {generalmanager-0.9.0.dist-info → generalmanager-0.9.1.dist-info}/RECORD +6 -6
- {generalmanager-0.9.0.dist-info → generalmanager-0.9.1.dist-info}/WHEEL +0 -0
- {generalmanager-0.9.0.dist-info → generalmanager-0.9.1.dist-info}/licenses/LICENSE +0 -0
- {generalmanager-0.9.0.dist-info → generalmanager-0.9.1.dist-info}/top_level.txt +0 -0
@@ -4,7 +4,7 @@ from typing import Any, Callable
|
|
4
4
|
import pint
|
5
5
|
from decimal import Decimal, getcontext, InvalidOperation
|
6
6
|
from operator import eq, ne, lt, le, gt, ge
|
7
|
-
|
7
|
+
from pint.facets.plain import PlainQuantity
|
8
8
|
|
9
9
|
# Set precision for Decimal
|
10
10
|
getcontext().prec = 28
|
@@ -21,6 +21,16 @@ for currency in currency_units:
|
|
21
21
|
|
22
22
|
class Measurement:
|
23
23
|
def __init__(self, value: Decimal | float | int | str, unit: str):
|
24
|
+
"""
|
25
|
+
Initialize a Measurement instance with a numeric value and unit.
|
26
|
+
|
27
|
+
Parameters:
|
28
|
+
value (Decimal | float | int | str): The numeric value to be associated with the unit. Can be a Decimal, float, int, or a string convertible to Decimal.
|
29
|
+
unit (str): The unit of measurement as a string.
|
30
|
+
|
31
|
+
Raises:
|
32
|
+
TypeError: If the value cannot be converted to a Decimal.
|
33
|
+
"""
|
24
34
|
if not isinstance(value, (Decimal, float, int)):
|
25
35
|
try:
|
26
36
|
value = Decimal(str(value))
|
@@ -28,22 +38,37 @@ class Measurement:
|
|
28
38
|
raise TypeError("Value must be a Decimal, float, int or compatible.")
|
29
39
|
if not isinstance(value, Decimal):
|
30
40
|
value = Decimal(str(value))
|
31
|
-
self.__quantity = self.formatDecimal(value)
|
41
|
+
self.__quantity = ureg.Quantity(self.formatDecimal(value), unit)
|
32
42
|
|
33
43
|
def __getstate__(self):
|
44
|
+
"""
|
45
|
+
Return a serializable state dictionary containing the magnitude and unit of the measurement.
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
dict: A dictionary with 'magnitude' as a string and 'unit' as a string, suitable for pickling or other serialization.
|
49
|
+
"""
|
34
50
|
state = {
|
35
|
-
"magnitude": str(self.
|
36
|
-
"unit": str(self.
|
51
|
+
"magnitude": str(self.magnitude),
|
52
|
+
"unit": str(self.unit),
|
37
53
|
}
|
38
54
|
return state
|
39
55
|
|
40
56
|
def __setstate__(self, state):
|
57
|
+
"""
|
58
|
+
Restore the Measurement object from a serialized state dictionary.
|
59
|
+
|
60
|
+
Parameters:
|
61
|
+
state (dict): A dictionary containing 'magnitude' as a string and 'unit' as a string.
|
62
|
+
"""
|
41
63
|
value = Decimal(state["magnitude"])
|
42
64
|
unit = state["unit"]
|
43
|
-
self.__quantity = self.formatDecimal(value)
|
65
|
+
self.__quantity = ureg.Quantity(self.formatDecimal(value), unit)
|
44
66
|
|
45
67
|
@property
|
46
|
-
def quantity(self) ->
|
68
|
+
def quantity(self) -> PlainQuantity:
|
69
|
+
"""
|
70
|
+
Return the internal quantity as a `PlainQuantity` object from the `pint` library.
|
71
|
+
"""
|
47
72
|
return self.__quantity
|
48
73
|
|
49
74
|
@property
|
@@ -57,18 +82,24 @@ class Measurement:
|
|
57
82
|
@classmethod
|
58
83
|
def from_string(cls, value: str) -> Measurement:
|
59
84
|
"""
|
60
|
-
|
85
|
+
Parse a string of the form 'value unit' and return a Measurement instance.
|
61
86
|
|
62
|
-
|
63
|
-
value:
|
87
|
+
Parameters:
|
88
|
+
value (str): String containing a numeric value and a unit, separated by a space (e.g., '10.5 kg').
|
64
89
|
|
65
90
|
Returns:
|
66
|
-
|
91
|
+
Measurement: The corresponding Measurement object.
|
67
92
|
|
68
93
|
Raises:
|
69
94
|
ValueError: If the input string does not contain exactly two parts separated by a space.
|
70
95
|
"""
|
71
96
|
splitted = value.split(" ")
|
97
|
+
if len(splitted) == 1:
|
98
|
+
# If only one part, assume it's a dimensionless value
|
99
|
+
try:
|
100
|
+
return cls(Decimal(splitted[0]), "dimensionless")
|
101
|
+
except InvalidOperation:
|
102
|
+
raise ValueError("Invalid value for dimensionless measurement.")
|
72
103
|
if len(splitted) != 2:
|
73
104
|
raise ValueError("String must be in the format 'value unit'.")
|
74
105
|
value, unit = splitted
|
@@ -86,12 +117,27 @@ class Measurement:
|
|
86
117
|
return value
|
87
118
|
|
88
119
|
def to(self, target_unit: str, exchange_rate: float | None = None):
|
120
|
+
"""
|
121
|
+
Convert the measurement to a specified target unit, handling both currency and physical unit conversions.
|
122
|
+
|
123
|
+
For currency units, an explicit exchange rate must be provided if converting between different currencies; otherwise, the original measurement is returned. For physical units, standard unit conversion is performed using the underlying unit registry.
|
124
|
+
|
125
|
+
Parameters:
|
126
|
+
target_unit (str): The unit to convert the measurement to.
|
127
|
+
exchange_rate (float, optional): The exchange rate to use for currency conversion. Required if converting between different currencies.
|
128
|
+
|
129
|
+
Returns:
|
130
|
+
Measurement: A new Measurement instance in the target unit.
|
131
|
+
|
132
|
+
Raises:
|
133
|
+
ValueError: If converting between different currencies without providing an exchange rate.
|
134
|
+
"""
|
89
135
|
if self.is_currency():
|
90
|
-
if self.
|
136
|
+
if self.unit == ureg(target_unit):
|
91
137
|
return self # Same currency, no conversion needed
|
92
138
|
elif exchange_rate is not None:
|
93
139
|
# Convert using the provided exchange rate
|
94
|
-
value = self.
|
140
|
+
value = self.magnitude * Decimal(str(exchange_rate))
|
95
141
|
return Measurement(value, target_unit)
|
96
142
|
else:
|
97
143
|
raise ValueError(
|
@@ -106,14 +152,25 @@ class Measurement:
|
|
106
152
|
|
107
153
|
def is_currency(self):
|
108
154
|
# Check if the unit is a defined currency
|
109
|
-
|
155
|
+
"""
|
156
|
+
Return True if the measurement's unit is one of the defined currency units.
|
157
|
+
"""
|
158
|
+
return str(self.unit) in currency_units
|
110
159
|
|
111
160
|
def __add__(self, other: Any) -> Measurement:
|
161
|
+
"""
|
162
|
+
Add two Measurement instances, supporting both currency and physical units.
|
163
|
+
|
164
|
+
Addition is allowed only if both operands are currencies of the same unit or both are physical units with compatible dimensions. Raises a TypeError if operands are of different types (currency vs. physical unit) or not Measurement instances, and raises a ValueError if units are incompatible.
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
Measurement: A new Measurement representing the sum.
|
168
|
+
"""
|
112
169
|
if not isinstance(other, Measurement):
|
113
170
|
raise TypeError("Addition is only allowed between Measurement instances.")
|
114
171
|
if self.is_currency() and other.is_currency():
|
115
172
|
# Both are currencies
|
116
|
-
if self.
|
173
|
+
if self.unit != other.unit:
|
117
174
|
raise ValueError(
|
118
175
|
"Addition between different currencies is not allowed."
|
119
176
|
)
|
@@ -139,34 +196,49 @@ class Measurement:
|
|
139
196
|
)
|
140
197
|
|
141
198
|
def __sub__(self, other: Any) -> Measurement:
|
199
|
+
"""
|
200
|
+
Subtracts another Measurement from this one, enforcing unit compatibility.
|
201
|
+
|
202
|
+
Subtraction is allowed only between two currencies of the same unit or two physical units with compatible dimensions. Raises a TypeError if the operand is not a Measurement or if attempting to subtract a currency from a physical unit (or vice versa). Raises a ValueError if subtracting different currencies or incompatible physical units.
|
203
|
+
|
204
|
+
Returns:
|
205
|
+
Measurement: The result of the subtraction as a new Measurement instance.
|
206
|
+
"""
|
142
207
|
if not isinstance(other, Measurement):
|
143
208
|
raise TypeError(
|
144
209
|
"Subtraction is only allowed between Measurement instances."
|
145
210
|
)
|
146
211
|
if self.is_currency() and other.is_currency():
|
147
212
|
# Both are currencies
|
148
|
-
if self.
|
213
|
+
if self.unit != other.unit:
|
149
214
|
raise ValueError(
|
150
215
|
"Subtraction between different currencies is not allowed."
|
151
216
|
)
|
152
217
|
result_quantity = self.quantity - other.quantity
|
153
|
-
return Measurement(
|
154
|
-
Decimal(str(result_quantity.magnitude)), str(self.quantity.units)
|
155
|
-
)
|
218
|
+
return Measurement(Decimal(str(result_quantity.magnitude)), str(self.unit))
|
156
219
|
elif not self.is_currency() and not other.is_currency():
|
157
220
|
# Both are physical units
|
158
221
|
if self.quantity.dimensionality != other.quantity.dimensionality:
|
159
222
|
raise ValueError("Units are not compatible for subtraction.")
|
160
223
|
result_quantity = self.quantity - other.quantity
|
161
|
-
return Measurement(
|
162
|
-
Decimal(str(result_quantity.magnitude)), str(self.quantity.units)
|
163
|
-
)
|
224
|
+
return Measurement(Decimal(str(result_quantity.magnitude)), str(self.unit))
|
164
225
|
else:
|
165
226
|
raise TypeError(
|
166
227
|
"Subtraction between currency and physical unit is not allowed."
|
167
228
|
)
|
168
229
|
|
169
230
|
def __mul__(self, other: Any) -> Measurement:
|
231
|
+
"""
|
232
|
+
Multiply this measurement by another measurement or a numeric value.
|
233
|
+
|
234
|
+
Multiplication between two currency measurements is not allowed. If multiplied by another measurement, returns a new Measurement with the combined units. If multiplied by a numeric value, returns a new Measurement with the same unit and scaled magnitude.
|
235
|
+
|
236
|
+
Returns:
|
237
|
+
Measurement: The result of the multiplication as a new Measurement instance.
|
238
|
+
|
239
|
+
Raises:
|
240
|
+
TypeError: If attempting to multiply two currency measurements or if the operand is not a Measurement or numeric value.
|
241
|
+
"""
|
170
242
|
if isinstance(other, Measurement):
|
171
243
|
if self.is_currency() or other.is_currency():
|
172
244
|
raise TypeError(
|
@@ -180,15 +252,21 @@ class Measurement:
|
|
180
252
|
if not isinstance(other, Decimal):
|
181
253
|
other = Decimal(str(other))
|
182
254
|
result_quantity = self.quantity * other
|
183
|
-
return Measurement(
|
184
|
-
Decimal(str(result_quantity.magnitude)), str(self.quantity.units)
|
185
|
-
)
|
255
|
+
return Measurement(Decimal(str(result_quantity.magnitude)), str(self.unit))
|
186
256
|
else:
|
187
257
|
raise TypeError(
|
188
258
|
"Multiplication is only allowed with Measurement or numeric values."
|
189
259
|
)
|
190
260
|
|
191
261
|
def __truediv__(self, other: Any) -> Measurement:
|
262
|
+
"""
|
263
|
+
Divide this measurement by another measurement or a numeric value.
|
264
|
+
|
265
|
+
If dividing by another `Measurement`, both must not be currencies. Returns a new `Measurement` with the resulting value and unit. If dividing by a numeric value, returns a new `Measurement` with the same unit and divided magnitude.
|
266
|
+
|
267
|
+
Raises:
|
268
|
+
TypeError: If dividing two currency measurements, or if the operand is not a `Measurement` or numeric value.
|
269
|
+
"""
|
192
270
|
if isinstance(other, Measurement):
|
193
271
|
if self.is_currency() and other.is_currency():
|
194
272
|
raise TypeError("Division between two currency amounts is not allowed.")
|
@@ -200,41 +278,38 @@ class Measurement:
|
|
200
278
|
if not isinstance(other, Decimal):
|
201
279
|
other = Decimal(str(other))
|
202
280
|
result_quantity = self.quantity / other
|
203
|
-
return Measurement(
|
204
|
-
Decimal(str(result_quantity.magnitude)), str(self.quantity.units)
|
205
|
-
)
|
281
|
+
return Measurement(Decimal(str(result_quantity.magnitude)), str(self.unit))
|
206
282
|
else:
|
207
283
|
raise TypeError(
|
208
284
|
"Division is only allowed with Measurement or numeric values."
|
209
285
|
)
|
210
286
|
|
211
287
|
def __str__(self):
|
212
|
-
|
213
|
-
|
214
|
-
|
288
|
+
"""
|
289
|
+
Return a string representation of the measurement, including the unit unless it is dimensionless.
|
290
|
+
"""
|
291
|
+
if not str(self.unit) == "dimensionless":
|
292
|
+
return f"{self.magnitude} {self.unit}"
|
293
|
+
return f"{self.magnitude}"
|
215
294
|
|
216
295
|
def __repr__(self):
|
217
|
-
|
296
|
+
"""
|
297
|
+
Return a string representation of the Measurement instance for debugging, showing its magnitude and unit.
|
298
|
+
"""
|
299
|
+
return f"Measurement({self.magnitude}, '{self.unit}')"
|
218
300
|
|
219
301
|
def _compare(self, other: Any, operation: Callable[..., bool]) -> bool:
|
220
302
|
"""
|
221
|
-
|
303
|
+
Compare this Measurement to another using a specified comparison operation.
|
222
304
|
|
223
|
-
If `other` is a string, it is parsed into a Measurement. Raises a TypeError if
|
224
|
-
`other` is not a Measurement instance. Converts `other` to this instance's unit
|
225
|
-
before applying the comparison. Raises a ValueError if the measurements have
|
226
|
-
incompatible dimensions.
|
305
|
+
If `other` is a string, it is parsed into a Measurement. The comparison is performed after converting `other` to this instance's unit. Raises a TypeError if `other` is not a Measurement or a valid string, and a ValueError if the measurements have incompatible dimensions.
|
227
306
|
|
228
|
-
|
229
|
-
other: The object to compare
|
230
|
-
operation: A callable that takes two magnitudes and returns a boolean
|
307
|
+
Parameters:
|
308
|
+
other: The object to compare, either a Measurement or a string in the format "value unit".
|
309
|
+
operation: A callable that takes two magnitudes and returns a boolean.
|
231
310
|
|
232
311
|
Returns:
|
233
|
-
The result of the comparison
|
234
|
-
|
235
|
-
Raises:
|
236
|
-
TypeError: If `other` is not a Measurement instance or a valid string representation.
|
237
|
-
ValueError: If the measurements have different dimensions and cannot be compared.
|
312
|
+
bool: The result of the comparison.
|
238
313
|
"""
|
239
314
|
if isinstance(other, str):
|
240
315
|
other = Measurement.from_string(other)
|
@@ -244,9 +319,9 @@ class Measurement:
|
|
244
319
|
raise TypeError("Comparison is only allowed between Measurement instances.")
|
245
320
|
try:
|
246
321
|
# Convert `other` to the same units as `self`
|
247
|
-
other_converted: pint.Quantity = other.quantity.to(self.
|
322
|
+
other_converted: pint.Quantity = other.quantity.to(self.unit) # type: ignore
|
248
323
|
# Apply the comparison operation
|
249
|
-
return operation(self.
|
324
|
+
return operation(self.magnitude, other_converted.magnitude)
|
250
325
|
except pint.DimensionalityError:
|
251
326
|
raise ValueError("Cannot compare measurements with different dimensions.")
|
252
327
|
|
@@ -273,16 +348,16 @@ class Measurement:
|
|
273
348
|
|
274
349
|
def __ge__(self, other: Any) -> bool:
|
275
350
|
"""
|
276
|
-
|
351
|
+
Return True if this measurement is greater than or equal to another measurement.
|
277
352
|
|
278
|
-
|
353
|
+
The comparison is performed after converting the other operand to the same unit as this measurement. Raises a TypeError if the other object is not a Measurement instance or a compatible string, or a ValueError if the units are incompatible.
|
279
354
|
"""
|
280
355
|
return self._compare(other, ge)
|
281
356
|
|
282
357
|
def __hash__(self) -> int:
|
283
358
|
"""
|
284
|
-
|
359
|
+
Return a hash value derived from the measurement's magnitude and unit.
|
285
360
|
|
286
|
-
|
361
|
+
Enables use of Measurement instances in hash-based collections such as sets and dictionaries.
|
287
362
|
"""
|
288
|
-
return hash((self.
|
363
|
+
return hash((self.magnitude, str(self.unit)))
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: GeneralManager
|
3
|
-
Version: 0.9.
|
3
|
+
Version: 0.9.1
|
4
4
|
Summary: Modular Django-based data management framework with ORM, GraphQL, fine-grained permissions, rule validation, calculations and caching.
|
5
5
|
Author-email: Tim Kleindick <tkleindick@yahoo.de>
|
6
6
|
License-Expression: MIT
|
@@ -37,7 +37,7 @@ general_manager/manager/groupManager.py,sha256=8dpZUfm7aFL4lraUWv4qbbDRClQZaYxw4
|
|
37
37
|
general_manager/manager/input.py,sha256=-pJXGJ-g2-OxZfl4Buj3mQkf05fN4p8MsR2Lh9BQcEo,3208
|
38
38
|
general_manager/manager/meta.py,sha256=IN5Xzz4lcUBe2umqvBz84qoyjkzKubNaMwfuYFQjFGU,5631
|
39
39
|
general_manager/measurement/__init__.py,sha256=X97meFujBldE5v0WMF7SmKeGpC5R0JTczfLo_Lq1Xek,84
|
40
|
-
general_manager/measurement/measurement.py,sha256=
|
40
|
+
general_manager/measurement/measurement.py,sha256=0jVU6eZmEwDs0CW_W_RpUxaWDPd9J4uQXWPpxMkfJWQ,16079
|
41
41
|
general_manager/measurement/measurementField.py,sha256=iq9Hqe6ZGX8CxXm4nIqTAWTRkQVptzpqE9ExX-jFyNs,5928
|
42
42
|
general_manager/permission/__init__.py,sha256=5UlDERN60Vn8obGVkT-cOM8kHjzmoxgK5w5FgTCDhGE,59
|
43
43
|
general_manager/permission/basePermission.py,sha256=14iKo6qVmaUdg1sAz-gSZyNtpVKAAapIhutVAMDf93c,6056
|
@@ -49,8 +49,8 @@ general_manager/rule/__init__.py,sha256=4Har5cfPD1fmOsilTDod-ZUz3Com-tkl58jz7yY4
|
|
49
49
|
general_manager/rule/handler.py,sha256=z8SFHTIZ0LbLh3fV56Mud0V4_OvWkqJjlHvFqau7Qfk,7334
|
50
50
|
general_manager/rule/rule.py,sha256=3FVCKGL7BTVoStdgOTdWQwuoVRIxAIAilV4VOzouDpc,10759
|
51
51
|
general_manager/utils/testing.py,sha256=R6l-9PVAgxeVywvynkzSR6xXcHCu4z2UzRqzHDVrBUY,5591
|
52
|
-
generalmanager-0.9.
|
53
|
-
generalmanager-0.9.
|
54
|
-
generalmanager-0.9.
|
55
|
-
generalmanager-0.9.
|
56
|
-
generalmanager-0.9.
|
52
|
+
generalmanager-0.9.1.dist-info/licenses/LICENSE,sha256=YGFm0ieb4KpkMRRt2qnWue6uFh0cUMtobwEBkHwajhc,1450
|
53
|
+
generalmanager-0.9.1.dist-info/METADATA,sha256=KlP-5CSEpz1tbI4ec2SzBKT8aSCBWhiUboTS5zgLzoQ,6205
|
54
|
+
generalmanager-0.9.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
55
|
+
generalmanager-0.9.1.dist-info/top_level.txt,sha256=sTDtExP9ga-YP3h3h42yivUY-A2Q23C2nw6LNKOho4I,16
|
56
|
+
generalmanager-0.9.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|