oprattr 0.2.0__py3-none-any.whl → 0.4.0__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.

Potentially problematic release.


This version of oprattr might be problematic. Click here for more details.

oprattr/__init__.py CHANGED
@@ -1,12 +1,13 @@
1
+ import collections.abc
1
2
  import functools
2
3
  import numbers
3
- import typing
4
4
 
5
+ from numerical import operators
5
6
  import numpy
6
7
 
7
- from . import mixins
8
- from . import operators
9
8
  from . import abstract
9
+ from . import mixins
10
+ from . import typeface
10
11
  from ._operations import (
11
12
  unary,
12
13
  equality,
@@ -16,7 +17,7 @@ from ._operations import (
16
17
  )
17
18
 
18
19
 
19
- T = typing.TypeVar('T')
20
+ T = typeface.TypeVar('T')
20
21
 
21
22
 
22
23
  class Operand(abstract.Object[T], mixins.Numpy):
@@ -114,12 +115,21 @@ class Operand(abstract.Object[T], mixins.Numpy):
114
115
 
115
116
  def __rpow__(self, other):
116
117
  """Called for other ** self."""
117
- return super().__rpow__(other)
118
+ return NotImplemented
118
119
 
119
120
  def __array__(self, *args, **kwargs):
120
121
  """Called for numpy.array(self)."""
121
122
  return numpy.array(self._data, *args, **kwargs)
122
123
 
124
+ def _apply_ufunc(self, ufunc, method, *args, **kwargs):
125
+ if ufunc in (numpy.equal, numpy.not_equal):
126
+ # NOTE: We are probably here because the left operand is a
127
+ # `numpy.ndarray`, which would otherwise take control and return the
128
+ # pure `numpy` result.
129
+ f = getattr(ufunc, method)
130
+ return equality(f, *args)
131
+ return super()._apply_ufunc(ufunc, method, *args, **kwargs)
132
+
123
133
 
124
134
  @Operand.implementation(numpy.array_equal)
125
135
  def array_equal(
@@ -155,7 +165,7 @@ def gradient(x: Operand[T], *args, **kwargs):
155
165
  return type(x)(data, **meta)
156
166
 
157
167
 
158
- def wrapnumpy(f: typing.Callable):
168
+ def wrapnumpy(f: collections.abc.Callable):
159
169
  """Implement a numpy function for objects with metadata."""
160
170
  @functools.wraps(f)
161
171
  def method(x: Operand[T], **kwargs):
oprattr/_operations.py CHANGED
@@ -1,7 +1,6 @@
1
- import typing
1
+ from numerical import operators
2
2
 
3
- from . import operators
4
- from .abstract import Object
3
+ from .abstract import Quantity
5
4
 
6
5
 
7
6
  class MetadataError(TypeError):
@@ -11,8 +10,8 @@ class MetadataError(TypeError):
11
10
  self,
12
11
  f: operators.Operator,
13
12
  *args,
14
- error: typing.Optional[str]=None,
15
- key: typing.Optional[str]=None,
13
+ error: str | None = None,
14
+ key: str | None = None,
16
15
  ) -> None:
17
16
  super().__init__(*args)
18
17
  self._f = f
@@ -33,8 +32,8 @@ class MetadataError(TypeError):
33
32
  def _build_error_message(
34
33
  f: operators.Operator,
35
34
  *types: type,
36
- error: typing.Optional[str]=None,
37
- key: typing.Optional[str]=None,
35
+ error: str | None = None,
36
+ key: str | None = None,
38
37
  ) -> str:
39
38
  """Helper for `_raise_metadata_exception`.
40
39
 
@@ -50,9 +49,9 @@ def _build_error_message(
50
49
  if len(types) == 2:
51
50
  a, b = types
52
51
  endstr = "because {} has metadata"
53
- if issubclass(a, Object):
52
+ if issubclass(a, Quantity):
54
53
  return f"{errmsg} between {a} and {b} {endstr.format(str(a))}"
55
- if issubclass(b, Object):
54
+ if issubclass(b, Quantity):
56
55
  return f"{errmsg} between {a} and {b} {endstr.format(str(b))}"
57
56
  if errstr == 'type':
58
57
  if key is None:
@@ -71,7 +70,7 @@ def _build_error_message(
71
70
 
72
71
  def unary(f: operators.Operator, a):
73
72
  """Compute the unary operation f(a)."""
74
- if isinstance(a, Object):
73
+ if isinstance(a, Quantity):
75
74
  meta = {}
76
75
  for key, value in a._meta.items():
77
76
  try:
@@ -86,32 +85,42 @@ def unary(f: operators.Operator, a):
86
85
 
87
86
  def equality(f: operators.Operator, a, b):
88
87
  """Compute the equality operation f(a, b)."""
89
- if isinstance(a, Object) and isinstance(b, Object):
88
+ x = a._data if isinstance(a, Quantity) else a
89
+ y = b._data if isinstance(b, Quantity) else b
90
+ fxy = f(x, y)
91
+ try:
92
+ iter(fxy)
93
+ except TypeError:
94
+ r = bool(fxy)
95
+ else:
96
+ r = all(fxy)
97
+ isne = f(1, 2)
98
+ if isinstance(a, Quantity) and isinstance(b, Quantity):
90
99
  if a._meta != b._meta:
91
- return f is operators.ne
92
- return f(a._data, b._data)
93
- if isinstance(a, Object):
100
+ return isne
101
+ return r
102
+ if isinstance(a, Quantity):
94
103
  if not a._meta:
95
- return f(a._data, b)
96
- return f is operators.ne
97
- if isinstance(b, Object):
104
+ return r
105
+ return isne
106
+ if isinstance(b, Quantity):
98
107
  if not b._meta:
99
- return f(a, b._data)
100
- return f is operators.ne
101
- return f(a, b)
108
+ return r
109
+ return isne
110
+ return r
102
111
 
103
112
 
104
113
  def ordering(f: operators.Operator, a, b):
105
114
  """Compute the ordering operation f(a, b)."""
106
- if isinstance(a, Object) and isinstance(b, Object):
115
+ if isinstance(a, Quantity) and isinstance(b, Quantity):
107
116
  if a._meta == b._meta:
108
117
  return f(a._data, b._data)
109
118
  raise MetadataError(f, a, b, error='unequal') from None
110
- if isinstance(a, Object):
119
+ if isinstance(a, Quantity):
111
120
  if not a._meta:
112
121
  return f(a._data, b)
113
122
  raise MetadataError(f, a, b, error='non-empty') from None
114
- if isinstance(b, Object):
123
+ if isinstance(b, Quantity):
115
124
  if not b._meta:
116
125
  return f(a, b._data)
117
126
  raise MetadataError(f, a, b, error='non-empty') from None
@@ -120,15 +129,15 @@ def ordering(f: operators.Operator, a, b):
120
129
 
121
130
  def additive(f: operators.Operator, a, b):
122
131
  """Compute the additive operation f(a, b)."""
123
- if isinstance(a, Object) and isinstance(b, Object):
132
+ if isinstance(a, Quantity) and isinstance(b, Quantity):
124
133
  if a._meta == b._meta:
125
134
  return type(a)(f(a._data, b._data), **a._meta)
126
135
  raise MetadataError(f, a, b, error='unequal') from None
127
- if isinstance(a, Object):
136
+ if isinstance(a, Quantity):
128
137
  if not a._meta:
129
138
  return type(a)(f(a._data, b))
130
139
  raise MetadataError(f, a, b, error='non-empty') from None
131
- if isinstance(b, Object):
140
+ if isinstance(b, Quantity):
132
141
  if not b._meta:
133
142
  return type(b)(f(a, b._data))
134
143
  raise MetadataError(f, a, b, error='non-empty') from None
@@ -137,7 +146,7 @@ def additive(f: operators.Operator, a, b):
137
146
 
138
147
  def multiplicative(f: operators.Operator, a, b):
139
148
  """Compute the multiplicative operation f(a, b)."""
140
- if isinstance(a, Object) and isinstance(b, Object):
149
+ if isinstance(a, Quantity) and isinstance(b, Quantity):
141
150
  keys = set(a._meta) & set(b._meta)
142
151
  meta = {}
143
152
  for key in keys:
@@ -154,7 +163,7 @@ def multiplicative(f: operators.Operator, a, b):
154
163
  if key not in keys:
155
164
  meta[key] = value
156
165
  return type(a)(f(a._data, b._data), **meta)
157
- if isinstance(a, Object):
166
+ if isinstance(a, Quantity):
158
167
  meta = {}
159
168
  for key, value in a._meta.items():
160
169
  try:
@@ -164,7 +173,7 @@ def multiplicative(f: operators.Operator, a, b):
164
173
  else:
165
174
  meta[key] = v
166
175
  return type(a)(f(a._data, b), **meta)
167
- if isinstance(b, Object):
176
+ if isinstance(b, Quantity):
168
177
  meta = {}
169
178
  for key, value in b._meta.items():
170
179
  try:
oprattr/abstract.py CHANGED
@@ -1,108 +1,13 @@
1
- import abc
1
+ import collections.abc
2
2
  import numbers
3
- import typing
4
3
 
4
+ import numerical
5
5
  import numpy.typing
6
6
 
7
+ from . import typeface
7
8
 
8
- @typing.runtime_checkable
9
- class Real(typing.Protocol):
10
- """Abstract protocol for real-valued objects."""
11
9
 
12
- @abc.abstractmethod
13
- def __abs__(self):
14
- return NotImplemented
15
-
16
- @abc.abstractmethod
17
- def __pos__(self):
18
- return NotImplemented
19
-
20
- @abc.abstractmethod
21
- def __neg__(self):
22
- return NotImplemented
23
-
24
- @abc.abstractmethod
25
- def __eq__(self, other):
26
- return False
27
-
28
- @abc.abstractmethod
29
- def __ne__(self, other):
30
- return True
31
-
32
- @abc.abstractmethod
33
- def __le__(self, other):
34
- return NotImplemented
35
-
36
- @abc.abstractmethod
37
- def __lt__(self, other):
38
- return NotImplemented
39
-
40
- @abc.abstractmethod
41
- def __ge__(self, other):
42
- return NotImplemented
43
-
44
- @abc.abstractmethod
45
- def __gt__(self, other):
46
- return NotImplemented
47
-
48
- @abc.abstractmethod
49
- def __add__(self, other):
50
- return NotImplemented
51
-
52
- @abc.abstractmethod
53
- def __radd__(self, other):
54
- return NotImplemented
55
-
56
- @abc.abstractmethod
57
- def __sub__(self, other):
58
- return NotImplemented
59
-
60
- @abc.abstractmethod
61
- def __rsub__(self, other):
62
- return NotImplemented
63
-
64
- @abc.abstractmethod
65
- def __mul__(self, other):
66
- return NotImplemented
67
-
68
- @abc.abstractmethod
69
- def __rmul__(self, other):
70
- return NotImplemented
71
-
72
- @abc.abstractmethod
73
- def __truediv__(self, other):
74
- return NotImplemented
75
-
76
- @abc.abstractmethod
77
- def __rtruediv__(self, other):
78
- return NotImplemented
79
-
80
- @abc.abstractmethod
81
- def __floordiv__(self, other):
82
- return NotImplemented
83
-
84
- @abc.abstractmethod
85
- def __rfloordiv__(self, other):
86
- return NotImplemented
87
-
88
- @abc.abstractmethod
89
- def __mod__(self, other):
90
- return NotImplemented
91
-
92
- @abc.abstractmethod
93
- def __rmod__(self, other):
94
- return NotImplemented
95
-
96
- @abc.abstractmethod
97
- def __pow__(self, other):
98
- return NotImplemented
99
-
100
- @abc.abstractmethod
101
- def __rpow__(self, other):
102
- return NotImplemented
103
-
104
-
105
- DataType = typing.TypeVar(
10
+ DataType = typeface.TypeVar(
106
11
  'DataType',
107
12
  int,
108
13
  float,
@@ -113,7 +18,14 @@ DataType = typing.TypeVar(
113
18
  )
114
19
 
115
20
 
116
- class Object(Real, typing.Generic[DataType]):
21
+ @typeface.runtime_checkable
22
+ class Quantity(numerical.Quantity[DataType], typeface.Protocol):
23
+ """Protocol for numerical objects with metadata."""
24
+
25
+ _meta: collections.abc.Mapping[str, typeface.Any]
26
+
27
+
28
+ class Object(numerical.Real, typeface.Generic[DataType]):
117
29
  """A real-valued object with metadata attributes."""
118
30
 
119
31
  def __init__(
@@ -121,7 +33,7 @@ class Object(Real, typing.Generic[DataType]):
121
33
  __data: DataType,
122
34
  **metadata,
123
35
  ) -> None:
124
- if not isinstance(__data, Real):
36
+ if not isinstance(__data, numerical.Real):
125
37
  raise TypeError("Data input to Object must be real-valued")
126
38
  self._data = __data
127
39
  self._meta = metadata
oprattr/mixins.py CHANGED
@@ -1,12 +1,13 @@
1
+ import collections.abc
1
2
  import numbers
2
- import typing
3
3
 
4
4
  import numpy
5
5
 
6
6
  from . import abstract
7
+ from . import typeface
7
8
 
8
9
 
9
- T = typing.TypeVar('T')
10
+ T = typeface.TypeVar('T')
10
11
 
11
12
 
12
13
  class Real:
@@ -82,7 +83,7 @@ class Real:
82
83
  return self
83
84
 
84
85
 
85
- UserFunction = typing.Callable[..., T]
86
+ UserFunction = collections.abc.Callable[..., T]
86
87
 
87
88
 
88
89
  class Numpy:
@@ -127,7 +128,7 @@ class Numpy:
127
128
  numpy.ndarray,
128
129
  numbers.Number,
129
130
  list,
130
- abstract.Object,
131
+ abstract.Quantity,
131
132
  }
132
133
 
133
134
  def __array_ufunc__(self, ufunc, method, *args, **kwargs):
@@ -162,7 +163,7 @@ class Numpy:
162
163
  return NotImplemented
163
164
  if out:
164
165
  kwargs['out'] = tuple(
165
- x._data if isinstance(x, abstract.Object)
166
+ x._data if isinstance(x, abstract.Quantity)
166
167
  else x for x in out
167
168
  )
168
169
  if self._implements(ufunc):
@@ -285,7 +286,7 @@ class Numpy:
285
286
  types = self._get_numpy_types(types)
286
287
  return array.__array_function__(func, types, args, kwargs)
287
288
 
288
- def _get_numpy_array(self) -> typing.Optional[numpy.typing.NDArray]:
289
+ def _get_numpy_array(self) -> numpy.typing.NDArray | None:
289
290
  """Convert the data interface to an array for `numpy` mixin methods.
290
291
 
291
292
  Notes
@@ -324,7 +325,7 @@ class Numpy:
324
325
  `arg` if `arg` is an instance of the base object class; otherwise, it
325
326
  will return the unmodified argument.
326
327
  """
327
- if isinstance(arg, abstract.Object):
328
+ if isinstance(arg, abstract.Quantity):
328
329
  return arg._data
329
330
  return arg
330
331
 
@@ -344,7 +345,7 @@ class Numpy:
344
345
  )
345
346
 
346
347
  @classmethod
347
- def _implements(cls, operation: typing.Callable):
348
+ def _implements(cls, operation: collections.abc.Callable):
348
349
  """True if this class defines a custom implementation for `operation`.
349
350
 
350
351
  This is a helper methods that gracefully handles the case in which a
@@ -356,11 +357,11 @@ class Numpy:
356
357
  return False
357
358
  return result
358
359
 
359
- _FUNCTIONS: typing.Dict[str, typing.Callable]=None
360
+ _FUNCTIONS: dict[str, collections.abc.Callable]=None
360
361
  """Internal collection of custom `numpy` function implementations."""
361
362
 
362
363
  @classmethod
363
- def implementation(cls, numpy_function: typing.Callable, /):
364
+ def implementation(cls, numpy_function: collections.abc.Callable, /):
364
365
  """Register a custom implementation of this `numpy` function.
365
366
 
366
367
  Parameters
@@ -424,7 +425,7 @@ class Numpy:
424
425
  @classmethod
425
426
  def implement(
426
427
  cls,
427
- numpy_function: typing.Callable,
428
+ numpy_function: collections.abc.Callable,
428
429
  user_function: UserFunction,
429
430
  /,
430
431
  ) -> None:
oprattr/typeface.py ADDED
@@ -0,0 +1,42 @@
1
+ """
2
+ Support for type annotations.
3
+
4
+ This module provides a single interface to type annotations, including those
5
+ that are not defined by the operative Python version and those that this package
6
+ prefers to use from future versions.
7
+
8
+ Examples
9
+ --------
10
+ * Suppose `BestType` is available in the `typing` module starting with Python
11
+ version 3.X and is available in the `typing_extensions` module for earlier
12
+ versions. If the user is running with Python version <3.X, this module will
13
+ import `BestType` from `typing_extensions`. Otherwise, it will import
14
+ `BestType` from `typing`.
15
+ * Support `UpdatedType` is available in the `typing` module for the user's
16
+ version of Python, but this package wishes to take advantage of updates since
17
+ that version. This module will automatically import the version from
18
+ `typing_extensions`.
19
+ """
20
+
21
+ import typing
22
+ import typing_extensions
23
+
24
+
25
+ __all__ = ()
26
+
27
+ EXTENDED = [
28
+ 'Protocol',
29
+ ]
30
+
31
+ def __getattr__(name: str) -> type:
32
+ """Get a built-in type annotation."""
33
+ if name in EXTENDED:
34
+ return getattr(typing_extensions, name)
35
+ try:
36
+ attr = getattr(typing, name)
37
+ except AttributeError:
38
+ attr = getattr(typing_extensions, name)
39
+ return attr
40
+
41
+
42
+
oprattr/typeface.pyi ADDED
@@ -0,0 +1,5 @@
1
+ """
2
+ Type help for our custom type-annotation interface.
3
+ """
4
+ from typing_extensions import *
5
+ from typing import *
@@ -1,9 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oprattr
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: Add your description here
5
5
  Author-email: Matthew Young <myoung.space.science@gmail.com>
6
+ License-File: LICENSE
6
7
  Requires-Python: >=3.10
8
+ Requires-Dist: numerical
7
9
  Requires-Dist: numpy>=2.2.1
8
10
  Requires-Dist: scipy>=1.15.0
9
11
  Description-Content-Type: text/markdown
@@ -0,0 +1,11 @@
1
+ oprattr/__init__.py,sha256=NPLnNea_NmyxQUX7DLqaC7YB67XATQ2gEhkPmBGpRpo,6044
2
+ oprattr/_operations.py,sha256=UruyOQGERwfiKgJTjJkt_C00BBVFvJMXes2tFxEkXJI,5985
3
+ oprattr/abstract.py,sha256=I3AYc8F79IMgH2esaKSvCTFelV_jFxWRuvXDuTZ8E94,1407
4
+ oprattr/mixins.py,sha256=_qqhReZu9Ta83irVihUrhggcObEj4lyp-3_S9K2hEog,16875
5
+ oprattr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ oprattr/typeface.py,sha256=FPGAdTZUmS_jd56PcZ5tCcUE_rXNubINBpVMQLRenvg,1214
7
+ oprattr/typeface.pyi,sha256=6gVdlDXtwl6Qyv07JWuRhM78VTGzds0pJ2KZUAAGcXs,113
8
+ oprattr-0.4.0.dist-info/METADATA,sha256=uCu_KrutswZW3e1YyOGuPa_ywL3dO818GJpBmRBFfwI,375
9
+ oprattr-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ oprattr-0.4.0.dist-info/licenses/LICENSE,sha256=m2oXG0JDq5RzaKTS57TvGyNq5cWcV4_nfmLZjzLdYTg,1513
11
+ oprattr-0.4.0.dist-info/RECORD,,
@@ -0,0 +1,32 @@
1
+
2
+
3
+ BSD 3-Clause License
4
+
5
+ Copyright (c) 2025, Matt Young
6
+ All rights reserved.
7
+
8
+ Redistribution and use in source and binary forms, with or without
9
+ modification, are permitted provided that the following conditions are met:
10
+
11
+ * Redistributions of source code must retain the above copyright notice, this
12
+ list of conditions and the following disclaimer.
13
+
14
+ * Redistributions in binary form must reproduce the above copyright notice,
15
+ this list of conditions and the following disclaimer in the documentation
16
+ and/or other materials provided with the distribution.
17
+
18
+ * Neither the name of the copyright holder nor the names of its
19
+ contributors may be used to endorse or promote products derived from
20
+ this software without specific prior written permission.
21
+
22
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32
+
oprattr/operators.py DELETED
@@ -1,41 +0,0 @@
1
- """
2
- A namespace for operators used by this package's `Object` class.
3
- """
4
-
5
- import builtins
6
- import operator
7
- import typing
8
-
9
-
10
- class Operator:
11
- """Base class for enhanced operators."""
12
- def __init__(self, __f: typing.Callable, operation: str):
13
- self._f = __f
14
- self._operation = operation
15
-
16
- def __repr__(self):
17
- """Called for repr(self)."""
18
- return self._operation
19
-
20
- def __call__(self, *args, **kwds):
21
- """Called for self(*args, **kwds)."""
22
- return self._f(*args, **kwds)
23
-
24
-
25
- eq = Operator(operator.eq, r'a == b')
26
- ne = Operator(operator.ne, r'a != b')
27
- lt = Operator(operator.lt, r'a < b')
28
- le = Operator(operator.le, r'a <= b')
29
- gt = Operator(operator.gt, r'a > b')
30
- ge = Operator(operator.ge, r'a >= b')
31
- abs = Operator(builtins.abs, r'abs(a)')
32
- pos = Operator(operator.pos, r'+a')
33
- neg = Operator(operator.neg, r'-a')
34
- add = Operator(operator.add, r'a + b')
35
- sub = Operator(operator.sub, r'a - b')
36
- mul = Operator(operator.mul, r'a * b')
37
- truediv = Operator(operator.truediv, r'a / b')
38
- floordiv = Operator(operator.floordiv, r'a // b')
39
- mod = Operator(operator.mod, r'a % b')
40
- pow = Operator(builtins.pow, r'a ** b')
41
-
@@ -1,9 +0,0 @@
1
- oprattr/__init__.py,sha256=RSJjoTQWRT-C_OYYKHKlzHVQgvnA7yM1aDTs1k4XtjM,5557
2
- oprattr/_operations.py,sha256=DiWIXLaTFQfI_IKHB89DqEgDLgxzeWpyY1CrbxLtQqI,5831
3
- oprattr/abstract.py,sha256=TzTt9GkQ5KZc5kqajdLnn8EcvxzMj3SALSI5iJrB6Vk,3182
4
- oprattr/mixins.py,sha256=KLgCsqIbE3WglZ3psrdRGb-agmVwBEqrASIpkGkDL54,16807
5
- oprattr/operators.py,sha256=skqQpIezGSDbsmB2h-UNnxG_7aDGT6PsnvUkondpwOg,1154
6
- oprattr/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- oprattr-0.2.0.dist-info/METADATA,sha256=PwniUyhE28lVnnRoAcZCNAEgVF50AdxbqqND9Jdil_s,328
8
- oprattr-0.2.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
- oprattr-0.2.0.dist-info/RECORD,,