TypedUnit 0.0.4__tar.gz → 0.0.5__tar.gz

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.
Files changed (43) hide show
  1. {typedunit-0.0.4 → typedunit-0.0.5}/PKG-INFO +1 -1
  2. {typedunit-0.0.4 → typedunit-0.0.5}/TypedUnit/_version.py +3 -3
  3. typedunit-0.0.5/TypedUnit/helper.py +67 -0
  4. typedunit-0.0.5/TypedUnit/units.py +206 -0
  5. {typedunit-0.0.4 → typedunit-0.0.5}/TypedUnit.egg-info/PKG-INFO +1 -1
  6. typedunit-0.0.4/TypedUnit/helper.py +0 -20
  7. typedunit-0.0.4/TypedUnit/units.py +0 -96
  8. {typedunit-0.0.4 → typedunit-0.0.5}/.flake8 +0 -0
  9. {typedunit-0.0.4 → typedunit-0.0.5}/.github/dependabot.yml +0 -0
  10. {typedunit-0.0.4 → typedunit-0.0.5}/.github/workflows/deploy_PyPi.yml +0 -0
  11. {typedunit-0.0.4 → typedunit-0.0.5}/.github/workflows/deploy_anaconda.yml +0 -0
  12. {typedunit-0.0.4 → typedunit-0.0.5}/.github/workflows/deploy_coverage.yml +0 -0
  13. {typedunit-0.0.4 → typedunit-0.0.5}/.github/workflows/deploy_documentation.yml +0 -0
  14. {typedunit-0.0.4 → typedunit-0.0.5}/.gitignore +0 -0
  15. {typedunit-0.0.4 → typedunit-0.0.5}/LICENSE +0 -0
  16. {typedunit-0.0.4 → typedunit-0.0.5}/README.rst +0 -0
  17. {typedunit-0.0.4 → typedunit-0.0.5}/TypedUnit/__init__.py +0 -0
  18. {typedunit-0.0.4 → typedunit-0.0.5}/TypedUnit/directories.py +0 -0
  19. {typedunit-0.0.4 → typedunit-0.0.5}/TypedUnit/utils.py +0 -0
  20. {typedunit-0.0.4 → typedunit-0.0.5}/TypedUnit.egg-info/SOURCES.txt +0 -0
  21. {typedunit-0.0.4 → typedunit-0.0.5}/TypedUnit.egg-info/dependency_links.txt +0 -0
  22. {typedunit-0.0.4 → typedunit-0.0.5}/TypedUnit.egg-info/requires.txt +0 -0
  23. {typedunit-0.0.4 → typedunit-0.0.5}/TypedUnit.egg-info/top_level.txt +0 -0
  24. {typedunit-0.0.4 → typedunit-0.0.5}/docs/Makefile +0 -0
  25. {typedunit-0.0.4 → typedunit-0.0.5}/docs/examples/README.rst +0 -0
  26. {typedunit-0.0.4 → typedunit-0.0.5}/docs/examples/example_0.py +0 -0
  27. {typedunit-0.0.4 → typedunit-0.0.5}/docs/examples/example_1.py +0 -0
  28. {typedunit-0.0.4 → typedunit-0.0.5}/docs/images/logo.png +0 -0
  29. {typedunit-0.0.4 → typedunit-0.0.5}/docs/make.bat +0 -0
  30. {typedunit-0.0.4 → typedunit-0.0.5}/docs/source/_static/default.css +0 -0
  31. {typedunit-0.0.4 → typedunit-0.0.5}/docs/source/_static/thumbnail.png +0 -0
  32. {typedunit-0.0.4 → typedunit-0.0.5}/docs/source/code.rst +0 -0
  33. {typedunit-0.0.4 → typedunit-0.0.5}/docs/source/conf.py +0 -0
  34. {typedunit-0.0.4 → typedunit-0.0.5}/docs/source/gallery/index.rst +0 -0
  35. {typedunit-0.0.4 → typedunit-0.0.5}/docs/source/index.rst +0 -0
  36. {typedunit-0.0.4 → typedunit-0.0.5}/docs/source/references.rst +0 -0
  37. {typedunit-0.0.4 → typedunit-0.0.5}/docs/source/sg_execution_times.rst +0 -0
  38. {typedunit-0.0.4 → typedunit-0.0.5}/docs/source/theory.rst +0 -0
  39. {typedunit-0.0.4 → typedunit-0.0.5}/meta.yaml +0 -0
  40. {typedunit-0.0.4 → typedunit-0.0.5}/pyproject.toml +0 -0
  41. {typedunit-0.0.4 → typedunit-0.0.5}/setup.cfg +0 -0
  42. {typedunit-0.0.4 → typedunit-0.0.5}/tests/__init__.py +0 -0
  43. {typedunit-0.0.4 → typedunit-0.0.5}/tests/test_0.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypedUnit
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: A package wrapping pint for handling typed units in Python.
5
5
  Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
6
6
  License: MIT License
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.0.4'
32
- __version_tuple__ = version_tuple = (0, 0, 4)
31
+ __version__ = version = '0.0.5'
32
+ __version_tuple__ = version_tuple = (0, 0, 5)
33
33
 
34
- __commit_id__ = commit_id = 'g833e1e15d'
34
+ __commit_id__ = commit_id = 'g036d03e92'
@@ -0,0 +1,67 @@
1
+ from TypedUnit.units import BaseUnit
2
+ from typing import get_type_hints, get_origin, get_args, Union
3
+ import inspect
4
+
5
+ def _get_unit_type(hint):
6
+ """Extract BaseUnit type from hint, handling Optional and List."""
7
+ # Direct BaseUnit subclass
8
+ if inspect.isclass(hint) and issubclass(hint, BaseUnit):
9
+ return hint, False, False # (unit_type, is_optional, is_list)
10
+
11
+ origin = get_origin(hint)
12
+ args = get_args(hint)
13
+
14
+ if origin is Union:
15
+ # Handle Optional[BaseUnit] (Union[BaseUnit, None])
16
+ unit_types = [arg for arg in args if arg is not type(None)]
17
+ if len(unit_types) == 1 and inspect.isclass(unit_types[0]) and issubclass(unit_types[0], BaseUnit):
18
+ return unit_types[0], True, False # Optional BaseUnit
19
+
20
+ elif origin in (list, tuple):
21
+ # Handle List[BaseUnit] or Tuple[BaseUnit]
22
+ if len(args) == 1 and inspect.isclass(args[0]) and issubclass(args[0], BaseUnit):
23
+ return args[0], False, True # List of BaseUnit
24
+
25
+ return None, False, False
26
+
27
+ def validate_units(function):
28
+ def wrapper(*args, **kwargs):
29
+ hints = get_type_hints(function)
30
+ sig = inspect.signature(function)
31
+
32
+ # Bind arguments to get parameter names
33
+ bound_args = sig.bind(*args, **kwargs)
34
+ bound_args.apply_defaults()
35
+
36
+ for param_name, value in bound_args.arguments.items():
37
+ if param_name not in hints:
38
+ continue
39
+
40
+ hint = hints[param_name]
41
+ unit_type, is_optional, is_list = _get_unit_type(hint)
42
+
43
+ if unit_type is None:
44
+ continue # No unit validation needed
45
+
46
+ # Handle None values for Optional types
47
+ if value is None and is_optional:
48
+ continue
49
+ elif value is None and not is_optional:
50
+ raise ValueError(f"Parameter '{param_name}' cannot be None")
51
+
52
+ # Handle List/Tuple types
53
+ if is_list:
54
+ for i, item in enumerate(value):
55
+ try:
56
+ unit_type.check(item)
57
+ except (AssertionError, ValueError) as e:
58
+ raise ValueError(f"Parameter '{param_name}[{i}]': {e}")
59
+ else:
60
+ # Handle single values
61
+ try:
62
+ unit_type.check(value)
63
+ except (AssertionError, ValueError) as e:
64
+ raise ValueError(f"Parameter '{param_name}': {e}")
65
+
66
+ return function(*args, **kwargs)
67
+ return wrapper
@@ -0,0 +1,206 @@
1
+ # Initialize a unit registry
2
+ from pint import Quantity, UnitRegistry
3
+
4
+ ureg = UnitRegistry()
5
+
6
+ ureg.define("photoelectron = [Float64]") # Define a custom unit 'photoelectron'
7
+ ureg.define("event = [Int64]") # Define a custom unit 'events'
8
+ ureg.define("sqrt_hertz = Hz**0.5")
9
+ ureg.define("bit_bins = ![Int64]")
10
+
11
+ class UnitMeta(type):
12
+ """
13
+ Metaclass that makes isinstance(x, SomeUnit) return True
14
+ when x is a pint.Quantity with the right dimensionality.
15
+ """
16
+ def __instancecheck__(cls, obj):
17
+ if not isinstance(obj, Quantity):
18
+ return False
19
+ expected = getattr(cls, "_expected_dim", None)
20
+ if expected is None:
21
+ return True
22
+ # Use pint's dimensionality check; accepts strings like "energy"
23
+ return obj.check(expected)
24
+
25
+ def __truediv__(cls, other):
26
+ if not (isinstance(other, UnitMeta) and issubclass(other, BaseUnit)):
27
+ return NotImplemented
28
+ num = cls.__name__
29
+ den = other.__name__
30
+ name = f"{num}Per{den}"
31
+ expected = f"{getattr(cls, '_expected_dim')}/{getattr(other, '_expected_dim')}"
32
+ # Create a new subclass that uses the same metaclass
33
+ return UnitMeta(name, (BaseUnit,), {"_expected_dim": expected})
34
+
35
+ # (Optional) multiplication, powers, etc., if you ever want them.
36
+ def __mul__(cls, other):
37
+ if not (isinstance(other, UnitMeta) and issubclass(other, BaseUnit)):
38
+ return NotImplemented
39
+ a = cls.__name__
40
+ b = other.__name__
41
+ name = f"{a}Times{b}"
42
+ expected = f"({getattr(cls, '_expected_dim')})*({getattr(other, '_expected_dim')})"
43
+ return UnitMeta(name, (BaseUnit,), {"_expected_dim": expected})
44
+
45
+ def __pow__(cls, power):
46
+ if not isinstance(power, int):
47
+ return NotImplemented
48
+ name = f"{cls.__name__}Pow{power}"
49
+ expected = f"({getattr(cls, '_expected_dim')})**{power}"
50
+ return UnitMeta(name, (BaseUnit,), {"_expected_dim": expected})
51
+
52
+ class BaseUnit(metaclass=UnitMeta):
53
+ _expected_dim: str | None = None
54
+
55
+ @classmethod
56
+ def check(cls, value):
57
+ if cls._expected_dim is None:
58
+ return value
59
+ assert isinstance(value, Quantity), f"Expected a pint Quantity instance, got {type(value)}"
60
+ assert value.check(cls._expected_dim), f"Value units {value.dimensionality} do not match {cls.__name__} units"
61
+ return value
62
+
63
+ class Energy(BaseUnit):
64
+ """Quantity specifically for energy units."""
65
+ _expected_dim = "joule"
66
+
67
+ class Time(BaseUnit):
68
+ """Quantity specifically for time units."""
69
+ _expected_dim = "second"
70
+
71
+ class Power(BaseUnit):
72
+ """Quantity specifically for power units."""
73
+ _expected_dim = "watt"
74
+
75
+ class Voltage(BaseUnit):
76
+ """Quantity specifically for voltage units."""
77
+ _expected_dim = "volt"
78
+
79
+ class Current(BaseUnit):
80
+ """Quantity specifically for current units."""
81
+ _expected_dim = "ampere"
82
+
83
+ class Resistance(BaseUnit):
84
+ """Quantity specifically for resistance units."""
85
+ _expected_dim = "ohm"
86
+
87
+ class Temperature(BaseUnit):
88
+ """Quantity specifically for temperature units."""
89
+ _expected_dim = "kelvin"
90
+
91
+ class Length(BaseUnit):
92
+ """Quantity specifically for length units."""
93
+ _expected_dim = "meter"
94
+
95
+ class Volume(BaseUnit):
96
+ """Quantity specifically for volume units."""
97
+ _expected_dim = "liter"
98
+
99
+ class FlowRate(BaseUnit):
100
+ """Quantity specifically for flow rate units."""
101
+ _expected_dim = "liter / second"
102
+
103
+ class Frequency(BaseUnit):
104
+ """Quantity specifically for frequency units."""
105
+ _expected_dim = "hertz"
106
+
107
+ class Angle(BaseUnit):
108
+ """Quantity specifically for angle units."""
109
+ _expected_dim = "degree"
110
+
111
+ class Mass(BaseUnit):
112
+ """Quantity specifically for mass units."""
113
+ _expected_dim = "kilogram"
114
+
115
+ class RefractiveIndex(BaseUnit):
116
+ """Quantity specifically for refractive index units."""
117
+ _expected_dim = "refractive_index_units"
118
+
119
+ class Dimensionless(BaseUnit):
120
+ """Quantity specifically for dimensionless units."""
121
+ _expected_dim = ""
122
+
123
+ class Responsitivity(BaseUnit):
124
+ """Quantity specifically for responsitivity units."""
125
+ _expected_dim = "ampere / watt"
126
+
127
+ class Viscosity(BaseUnit):
128
+ """Quantity specifically for viscosity units."""
129
+ _expected_dim = "pascal * second"
130
+
131
+ class Area(BaseUnit):
132
+ """Quantity specifically for viscosity units."""
133
+ _expected_dim = "meter * meter"
134
+
135
+ class ElectricField(BaseUnit):
136
+ """Quantity specifically for electric field units."""
137
+ _expected_dim = "volt / meter"
138
+
139
+ class Velocity(BaseUnit):
140
+ """Quantity specifically for velocity units."""
141
+ _expected_dim = "meter / second"
142
+
143
+ class Ohm(BaseUnit):
144
+ """Quantity specifically for resistance units."""
145
+ _expected_dim = "ohm"
146
+
147
+ class Concentration(BaseUnit):
148
+ """Quantity specifically for concentration units."""
149
+ _expected_dim = "particle / liter"
150
+
151
+ class Particle(BaseUnit):
152
+ """Quantity specifically for particle units."""
153
+ _expected_dim = "particle"
154
+
155
+ class ParticleFlux(BaseUnit):
156
+ """Quantity specifically for particle flux units."""
157
+ _expected_dim = "particle / second"
158
+
159
+ class Any(BaseUnit):
160
+ """Quantity that can have any unit."""
161
+ _expected_dim = None # Accept any dimensionality
162
+
163
+ class AnyUnit(BaseUnit):
164
+ """Quantity that can have any unit."""
165
+ _expected_dim = None # Accept any dimensionality
166
+
167
+ ureg.AU = ureg.dimensionless
168
+
169
+
170
+
171
+
172
+
173
+
174
+
175
+
176
+
177
+
178
+
179
+ # from pydantic.dataclasses import dataclass
180
+
181
+ # config_dict = dict(
182
+ # arbitrary_types_allowed=True,
183
+ # kw_only=True,
184
+ # slots=True,
185
+ # extra='forbid'
186
+ # )
187
+
188
+
189
+
190
+
191
+ # @dataclass(config=config_dict)
192
+ # class Sphere():
193
+ # name: str
194
+ # refractive_index: RefractiveIndex
195
+ # diameter: Length
196
+ # particle_count: Particle
197
+
198
+
199
+ # test = Sphere(
200
+ # name="Test Sphere",
201
+ # refractive_index=1 * ureg.refractive_index_units,
202
+ # diameter=1 * ureg.meter,
203
+ # particle_count=1 * 5e9 * ureg.particle / ureg.milliliter
204
+ # )
205
+
206
+ # print(test)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: TypedUnit
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: A package wrapping pint for handling typed units in Python.
5
5
  Author-email: Martin Poinsinet de Sivry-Houle <martin.poinsinet.de.sivry@gmail.com>
6
6
  License: MIT License
@@ -1,20 +0,0 @@
1
- from TypedUnit.units import BaseUnit
2
-
3
- from typing import get_type_hints
4
-
5
- def validate_units(function):
6
- def wrapper(*args, **kwargs):
7
- hints = get_type_hints(function)
8
-
9
- for arg, hint in zip(args, hints.values()):
10
- if isinstance(hint, BaseUnit):
11
- hint.check(arg)
12
-
13
- for kwarg_name, kwarg_value in kwargs.items():
14
- if kwarg_name in hints:
15
- hint = hints[kwarg_name]
16
- if issubclass(hint, BaseUnit):
17
- hint.check(kwarg_value)
18
-
19
- return function(*args, **kwargs)
20
- return wrapper
@@ -1,96 +0,0 @@
1
- # Initialize a unit registry
2
- from pint import Quantity, UnitRegistry
3
-
4
- ureg = UnitRegistry()
5
-
6
- ureg.define("photoelectron = [Float64]") # Define a custom unit 'photoelectron'
7
- ureg.define("event = [Int64]") # Define a custom unit 'events'
8
- ureg.define("sqrt_hertz = Hz**0.5")
9
- ureg.define("bit_bins = ![Int64]")
10
-
11
- class UnitMeta(type):
12
- """
13
- Metaclass that makes isinstance(x, SomeUnit) return True
14
- when x is a pint.Quantity with the right dimensionality.
15
- """
16
- def __instancecheck__(cls, obj):
17
- if not isinstance(obj, Quantity):
18
- return False
19
- expected = getattr(cls, "_expected_dim", None)
20
- if expected is None:
21
- return False
22
- # Use pint's dimensionality check; accepts strings like "energy"
23
- return obj.check(expected)
24
-
25
- class BaseUnit(metaclass=UnitMeta):
26
- _expected_dim: str | None = None
27
-
28
- @classmethod
29
- def check(cls, value):
30
- assert isinstance(value, Quantity), f"Expected a pint Quantity instance, got {type(value)}"
31
- assert value.check(cls._expected_dim), f"Value units {value.dimensionality} do not match {cls.__name__} units"
32
- return value
33
-
34
-
35
- class Energy(BaseUnit):
36
- """Quantity specifically for energy units."""
37
- _expected_dim = "joule"
38
-
39
- class Time(BaseUnit):
40
- """Quantity specifically for time units."""
41
- _expected_dim = "second"
42
-
43
- class Power(BaseUnit):
44
- """Quantity specifically for power units."""
45
- _expected_dim = "watt"
46
-
47
- class Voltage(BaseUnit):
48
- """Quantity specifically for voltage units."""
49
- _expected_dim = "volt"
50
-
51
- class Current(BaseUnit):
52
- """Quantity specifically for current units."""
53
- _expected_dim = "ampere"
54
-
55
- class Resistance(BaseUnit):
56
- """Quantity specifically for resistance units."""
57
- _expected_dim = "ohm"
58
-
59
- class Temperature(BaseUnit):
60
- """Quantity specifically for temperature units."""
61
- _expected_dim = "kelvin"
62
-
63
- class Length(BaseUnit):
64
- """Quantity specifically for length units."""
65
- _expected_dim = "meter"
66
-
67
- class Volume(BaseUnit):
68
- """Quantity specifically for volume units."""
69
- _expected_dim = "liter"
70
-
71
- class FlowRate(BaseUnit):
72
- """Quantity specifically for flow rate units."""
73
- _expected_dim = "liter / second"
74
-
75
- class Frequency(BaseUnit):
76
- """Quantity specifically for frequency units."""
77
- _expected_dim = "hertz"
78
-
79
- class Angle(BaseUnit):
80
- """Quantity specifically for angle units."""
81
- _expected_dim = "degree"
82
-
83
- class Mass(BaseUnit):
84
- """Quantity specifically for mass units."""
85
- _expected_dim = "kilogram"
86
-
87
- class RefractiveIndex(BaseUnit):
88
- """Quantity specifically for refractive index units."""
89
- _expected_dim = "refractive_index_units"
90
-
91
- class Dimensionless(BaseUnit):
92
- """Quantity specifically for dimensionless units."""
93
- _expected_dim = ""
94
-
95
-
96
-
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes