oprattr 0.6.0__tar.gz → 0.7.0__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.
@@ -1,5 +1,12 @@
1
1
  ## NEXT
2
2
 
3
+ ## v0.7.0
4
+
5
+ - Add `oprattr.Object` and `oprattr.Quantity` to the public namespace
6
+ - Rename modules that should not be part of the public namespace
7
+ - Define the `OperationError` exception class
8
+ - Change default behavior when a metadata attribute does not implement an operation
9
+
3
10
  ## v0.6.0
4
11
 
5
12
  - Add `methods` module
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oprattr
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Add your description here
5
5
  Author-email: Matthew Young <myoung.space.science@gmail.com>
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oprattr"
3
- version = "0.6.0"
3
+ version = "0.7.0"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -3,17 +3,27 @@ import functools
3
3
 
4
4
  import numpy
5
5
 
6
- from . import abstract
6
+ from ._abstract import (
7
+ Quantity,
8
+ Object,
9
+ )
7
10
  from . import methods
8
11
  from . import mixins
9
- from . import typeface
10
- from ._operations import equality
11
-
12
+ from . import _typeface
13
+ from ._operations import (
14
+ unary,
15
+ equality,
16
+ ordering,
17
+ additive,
18
+ multiplicative,
19
+ MetadataTypeError,
20
+ MetadataValueError,
21
+ )
12
22
 
13
- T = typeface.TypeVar('T')
14
23
 
24
+ T = _typeface.TypeVar('T')
15
25
 
16
- class Operand(abstract.Object[T], mixins.Numpy):
26
+ class Operand(Object[T], mixins.Numpy):
17
27
  """A concrete implementation of a real-valued object."""
18
28
 
19
29
  __abs__ = methods.__abs__
@@ -66,22 +76,22 @@ def array_equal(
66
76
  return numpy.array_equal(numpy.array(x), numpy.array(y), **kwargs)
67
77
 
68
78
 
79
+ class OperationError(NotImplementedError):
80
+ """A metadata attribute does not support this operation.
81
+
82
+ The default behavior when applying an operator to a metadata attribute of
83
+ `~Operand` is to copy the current value if the attribute does not define the
84
+ operation. Custom metadata attributes may raise this exception to indicate
85
+ that attempting to apply the operator is truly an error.
86
+ """
87
+
88
+
69
89
  @Operand.implementation(numpy.gradient)
70
90
  def gradient(x: Operand[T], *args, **kwargs):
71
91
  """Called for numpy.gradient(x)."""
72
- data = numpy.gradient(x._data, *args, **kwargs)
73
- meta = {}
74
- for key, value in x._meta.items():
75
- try:
76
- v = numpy.gradient(value, **kwargs)
77
- except TypeError as exc:
78
- raise TypeError(
79
- "Cannot compute numpy.gradient(x)"
80
- f" because metadata attribute {key!r}"
81
- " does not support this operation"
82
- ) from exc
83
- else:
84
- meta[key] = v
92
+ f = numpy.gradient
93
+ data = f(x._data, *args, **kwargs)
94
+ meta = _apply_to_metadata(f, x, **kwargs)
85
95
  if isinstance(data, (list, tuple)):
86
96
  r = [type(x)(array, **meta) for array in data]
87
97
  if isinstance(data, tuple):
@@ -96,22 +106,34 @@ def wrapnumpy(f: collections.abc.Callable):
96
106
  def method(x: Operand[T], **kwargs):
97
107
  """Apply a numpy function to x."""
98
108
  data = f(x._data, **kwargs)
99
- meta = {}
100
- for key, value in x._meta.items():
101
- try:
102
- v = f(value, **kwargs)
103
- except TypeError as exc:
104
- raise TypeError(
105
- f"Cannot compute numpy.{f.__qualname__}(x)"
106
- f" because metadata attribute {key!r}"
107
- " does not support this operation"
108
- ) from exc
109
- else:
110
- meta[key] = v
109
+ meta = _apply_to_metadata(f, x, **kwargs)
111
110
  return type(x)(data, **meta)
112
111
  return method
113
112
 
114
113
 
114
+ def _apply_to_metadata(
115
+ f: collections.abc.Callable,
116
+ x: Operand,
117
+ **kwargs,
118
+ ) -> dict[str, _typeface.Any]:
119
+ """Apply `f` to metadata attributes."""
120
+ processed = {}
121
+ for key, value in x._meta.items():
122
+ try:
123
+ v = f(value, **kwargs)
124
+ except TypeError:
125
+ processed[key] = value
126
+ except OperationError as exc:
127
+ raise TypeError(
128
+ f"Cannot compute numpy.{f.__qualname__}(x)"
129
+ f" because metadata attribute {key!r} of {type(x)}"
130
+ " does not support this operation"
131
+ ) from exc
132
+ else:
133
+ processed[key] = v
134
+ return processed.copy()
135
+
136
+
115
137
  _OPERAND_UFUNCS = (
116
138
  numpy.sqrt,
117
139
  numpy.sin,
@@ -133,7 +155,25 @@ _OPERAND_FUNCTIONS = (
133
155
  )
134
156
 
135
157
 
136
- for f in _OPERAND_UFUNCS + _OPERAND_FUNCTIONS:
137
- Operand.implement(f, wrapnumpy(f))
138
-
139
-
158
+ for _func in _OPERAND_UFUNCS + _OPERAND_FUNCTIONS:
159
+ Operand.implement(_func, wrapnumpy(_func))
160
+
161
+
162
+ __all__ = [
163
+ # Modules
164
+ methods,
165
+ mixins,
166
+ # Object classes
167
+ Quantity,
168
+ Object,
169
+ # Functions
170
+ unary,
171
+ equality,
172
+ ordering,
173
+ additive,
174
+ multiplicative,
175
+ # Error classes
176
+ MetadataTypeError,
177
+ MetadataValueError,
178
+ OperationError,
179
+ ]
@@ -1,22 +1,22 @@
1
1
  import numpy.typing
2
2
 
3
- from . import abstract
3
+ from . import _abstract
4
4
  from . import mixins
5
- from . import typeface
5
+ from . import _typeface
6
6
 
7
7
 
8
- T = typeface.TypeVar('T')
8
+ T = _typeface.TypeVar('T')
9
9
 
10
- class Operand(abstract.Object[T], mixins.Numpy):
10
+ class Operand(_abstract.Object[T], mixins.Numpy):
11
11
  """A concrete implementation of a real-valued object."""
12
12
 
13
- def __abs__(self) -> typeface.Self:
13
+ def __abs__(self) -> _typeface.Self:
14
14
  """Called for abs(self)."""
15
15
 
16
- def __pos__(self) -> typeface.Self:
16
+ def __pos__(self) -> _typeface.Self:
17
17
  """Called for +self."""
18
18
 
19
- def __neg__(self) -> typeface.Self:
19
+ def __neg__(self) -> _typeface.Self:
20
20
  """Called for -self."""
21
21
 
22
22
  def __eq__(self, other) -> bool:
@@ -37,43 +37,43 @@ class Operand(abstract.Object[T], mixins.Numpy):
37
37
  def __ge__(self, other) -> bool | numpy.typing.NDArray[numpy.bool]:
38
38
  """Called for self >= other."""
39
39
 
40
- def __add__(self, other) -> typeface.Self:
40
+ def __add__(self, other) -> _typeface.Self:
41
41
  """Called for self + other."""
42
42
 
43
- def __radd__(self, other) -> typeface.Self:
43
+ def __radd__(self, other) -> _typeface.Self:
44
44
  """Called for other + self."""
45
45
 
46
- def __sub__(self, other) -> typeface.Self:
46
+ def __sub__(self, other) -> _typeface.Self:
47
47
  """Called for self - other."""
48
48
 
49
- def __rsub__(self, other) -> typeface.Self:
49
+ def __rsub__(self, other) -> _typeface.Self:
50
50
  """Called for other - self."""
51
51
 
52
- def __mul__(self, other) -> typeface.Self:
52
+ def __mul__(self, other) -> _typeface.Self:
53
53
  """Called for self * other."""
54
54
 
55
- def __rmul__(self, other) -> typeface.Self:
55
+ def __rmul__(self, other) -> _typeface.Self:
56
56
  """Called for other * self."""
57
57
 
58
- def __truediv__(self, other) -> typeface.Self:
58
+ def __truediv__(self, other) -> _typeface.Self:
59
59
  """Called for self / other."""
60
60
 
61
- def __rtruediv__(self, other) -> typeface.Self:
61
+ def __rtruediv__(self, other) -> _typeface.Self:
62
62
  """Called for other / self."""
63
63
 
64
- def __floordiv__(self, other) -> typeface.Self:
64
+ def __floordiv__(self, other) -> _typeface.Self:
65
65
  """Called for self // other."""
66
66
 
67
- def __rfloordiv__(self, other) -> typeface.Self:
67
+ def __rfloordiv__(self, other) -> _typeface.Self:
68
68
  """Called for other // self."""
69
69
 
70
- def __mod__(self, other) -> typeface.Self:
70
+ def __mod__(self, other) -> _typeface.Self:
71
71
  """Called for self % other."""
72
72
 
73
- def __rmod__(self, other) -> typeface.Self:
73
+ def __rmod__(self, other) -> _typeface.Self:
74
74
  """Called for other % self."""
75
75
 
76
- def __pow__(self, other) -> typeface.Self:
76
+ def __pow__(self, other) -> _typeface.Self:
77
77
  """Called for self ** other."""
78
78
 
79
79
  def __rpow__(self, other):
@@ -4,10 +4,10 @@ import numbers
4
4
  import numerical
5
5
  import numpy.typing
6
6
 
7
- from . import typeface
7
+ from . import _typeface
8
8
 
9
9
 
10
- DataType = typeface.TypeVar(
10
+ DataType = _typeface.TypeVar(
11
11
  'DataType',
12
12
  int,
13
13
  float,
@@ -18,14 +18,14 @@ DataType = typeface.TypeVar(
18
18
  )
19
19
 
20
20
 
21
- @typeface.runtime_checkable
22
- class Quantity(numerical.Quantity[DataType], typeface.Protocol):
21
+ @_typeface.runtime_checkable
22
+ class Quantity(numerical.Quantity[DataType], _typeface.Protocol):
23
23
  """Protocol for numerical objects with metadata."""
24
24
 
25
- _meta: collections.abc.Mapping[str, typeface.Any]
25
+ _meta: collections.abc.Mapping[str, _typeface.Any]
26
26
 
27
27
 
28
- class Object(numerical.Real, typeface.Generic[DataType]):
28
+ class Object(numerical.Real, _typeface.Generic[DataType]):
29
29
  """A real-valued object with metadata attributes."""
30
30
 
31
31
  def __init__(
@@ -1,6 +1,6 @@
1
1
  from numerical import operators
2
2
 
3
- from .abstract import (
3
+ from ._abstract import (
4
4
  Quantity,
5
5
  Object,
6
6
  )
@@ -3,12 +3,11 @@ import numbers
3
3
 
4
4
  import numpy
5
5
 
6
- from . import abstract
7
- from . import typeface
6
+ from . import _abstract
7
+ from . import _typeface
8
8
 
9
9
 
10
- T = typeface.TypeVar('T')
11
-
10
+ T = _typeface.TypeVar('T')
12
11
 
13
12
  class Real:
14
13
  """Mixin for adding basic real-valued operator support."""
@@ -85,7 +84,6 @@ class Real:
85
84
 
86
85
  UserFunction = collections.abc.Callable[..., T]
87
86
 
88
-
89
87
  class Numpy:
90
88
  """Mixin for adding support for `numpy` functions to numeric objects.
91
89
 
@@ -128,7 +126,7 @@ class Numpy:
128
126
  numpy.ndarray,
129
127
  numbers.Number,
130
128
  list,
131
- abstract.Quantity,
129
+ _abstract.Quantity,
132
130
  }
133
131
 
134
132
  def __array_ufunc__(self, ufunc, method, *args, **kwargs):
@@ -163,7 +161,7 @@ class Numpy:
163
161
  return NotImplemented
164
162
  if out:
165
163
  kwargs['out'] = tuple(
166
- x._data if isinstance(x, abstract.Quantity)
164
+ x._data if isinstance(x, _abstract.Quantity)
167
165
  else x for x in out
168
166
  )
169
167
  if self._implements(ufunc):
@@ -208,7 +206,7 @@ class Numpy:
208
206
 
209
207
  _FUNCTION_TYPES = {
210
208
  numpy.ndarray,
211
- abstract.Object,
209
+ _abstract.Object,
212
210
  } | set(numpy.ScalarType)
213
211
 
214
212
  def __array_function__(self, func, types, args, kwargs):
@@ -325,7 +323,7 @@ class Numpy:
325
323
  `arg` if `arg` is an instance of the base object class; otherwise, it
326
324
  will return the unmodified argument.
327
325
  """
328
- if isinstance(arg, abstract.Quantity):
326
+ if isinstance(arg, _abstract.Quantity):
329
327
  return arg._data
330
328
  return arg
331
329
 
@@ -341,7 +339,7 @@ class Numpy:
341
339
  """
342
340
  return tuple(
343
341
  ti for ti in types
344
- if not issubclass(ti, abstract.Object)
342
+ if not issubclass(ti, _abstract.Object)
345
343
  )
346
344
 
347
345
  @classmethod
@@ -157,11 +157,6 @@ def symbol_gradient(x: Symbol, **kwargs):
157
157
  return f"numpy.gradient({x})"
158
158
 
159
159
 
160
- @Symbol.implementation(numpy.trapezoid)
161
- def symbol_trapezoid(x: Symbol, **kwargs):
162
- return f"numpy.trapezoid({x})"
163
-
164
-
165
160
  def symbolic_binary(a, op, b):
166
161
  if isinstance(a, (Symbol, str)) and isinstance(b, (Symbol, str)):
167
162
  return f"{a} {op} {b}"
@@ -502,26 +497,29 @@ def test_gradient():
502
497
  grad = numpy.gradient(v)
503
498
  for t, g in zip(that, grad):
504
499
  assert isinstance(t, oprattr.Operand)
505
- assert numpy.array_equal(t, x(g, name=nR))
500
+ assert numpy.array_equal(t, g)
501
+ assert t._meta['name'] == nR
506
502
  for axis in range(v.ndim):
507
503
  that = numpy.gradient(this, axis=axis)
508
504
  assert isinstance(that, oprattr.Operand)
509
505
  grad = numpy.gradient(v, axis=axis)
510
- assert numpy.array_equal(that, x(grad, name=nR))
506
+ assert numpy.array_equal(that, grad)
507
+ assert t._meta['name'] == nR
511
508
 
512
509
 
513
510
  def test_trapezoid():
514
- """Test `numpy.trapezoid`."""
511
+ """Test `numpy.trapezoid`, which `Symbol` does not implement."""
515
512
  nA = Symbol('A')
516
- nR = Symbol('numpy.trapezoid(A)')
517
513
  v = numpy.arange(3*4*5).reshape(3, 4, 5)
518
514
  this = x(v, name=nA)
519
515
  that = numpy.trapezoid(this)
520
516
  assert isinstance(that, oprattr.Operand)
521
- assert numpy.array_equal(that, x(numpy.trapezoid(v), name=nR))
517
+ assert numpy.array_equal(that, numpy.trapezoid(v))
518
+ assert that._meta['name'] == nA
522
519
  for axis in range(v.ndim):
523
520
  that = numpy.trapezoid(this, axis=axis)
524
521
  assert isinstance(that, oprattr.Operand)
525
522
  trap = numpy.trapezoid(v, axis=axis)
526
- assert numpy.array_equal(that, x(trap, name=nR))
523
+ assert numpy.array_equal(that, trap)
524
+ assert that._meta['name'] == nA
527
525
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes