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.
@@ -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) * ureg.Quantity(1, unit)
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.quantity.magnitude),
36
- "unit": str(self.quantity.units),
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) * ureg.Quantity(1, unit)
65
+ self.__quantity = ureg.Quantity(self.formatDecimal(value), unit)
44
66
 
45
67
  @property
46
- def quantity(self) -> pint.Quantity:
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
- Creates a Measurement instance from a string in the format 'value unit'.
85
+ Parse a string of the form 'value unit' and return a Measurement instance.
61
86
 
62
- Args:
63
- value: A string containing a numeric value and a unit separated by a space (e.g., '10.5 kg').
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
- A Measurement instance representing the parsed value and unit.
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.quantity.units == ureg(target_unit):
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.quantity.magnitude * Decimal(str(exchange_rate))
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
- return str(self.quantity.units) in currency_units
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.quantity.units != other.quantity.units:
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.quantity.units != other.quantity.units:
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
- if not str(self.quantity.units) == "dimensionless":
213
- return f"{self.quantity.magnitude} {self.quantity.units}"
214
- return f"{self.quantity.magnitude}"
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
- return f"Measurement({self.quantity.magnitude}, '{self.quantity.units}')"
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
- Compares this Measurement with another using the specified comparison operation.
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
- Args:
229
- other: The object to compare with, either a Measurement or a string in the format "value unit".
230
- operation: A callable that takes two magnitudes and returns a boolean result.
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 operation.
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.quantity.units) # type: ignore
322
+ other_converted: pint.Quantity = other.quantity.to(self.unit) # type: ignore
248
323
  # Apply the comparison operation
249
- return operation(self.quantity.magnitude, other_converted.magnitude)
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
- Returns True if this measurement is greater than or equal to another measurement.
351
+ Return True if this measurement is greater than or equal to another measurement.
277
352
 
278
- Comparison is performed after converting the other operand to the same unit. Raises a TypeError if the other object is not a Measurement instance or a compatible string, or a ValueError if the units are not compatible.
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
- Returns a hash value based on the magnitude and unit of the measurement.
359
+ Return a hash value derived from the measurement's magnitude and unit.
285
360
 
286
- This enables Measurement instances to be used in hash-based collections such as sets and dictionaries.
361
+ Enables use of Measurement instances in hash-based collections such as sets and dictionaries.
287
362
  """
288
- return hash((self.quantity.magnitude, str(self.quantity.units)))
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.0
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=e_FjHieeJbBtjXGCO9J7vRPw6KCkMrOxwWjaD0m8ee4,11777
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.0.dist-info/licenses/LICENSE,sha256=YGFm0ieb4KpkMRRt2qnWue6uFh0cUMtobwEBkHwajhc,1450
53
- generalmanager-0.9.0.dist-info/METADATA,sha256=jZGL1zcuGh2BUBCCpafvSZH2qIVWtcdk4FltvRj-06M,6205
54
- generalmanager-0.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
- generalmanager-0.9.0.dist-info/top_level.txt,sha256=sTDtExP9ga-YP3h3h42yivUY-A2Q23C2nw6LNKOho4I,16
56
- generalmanager-0.9.0.dist-info/RECORD,,
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,,