oprattr 0.1.0__tar.gz → 0.3.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.

Potentially problematic release.


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

@@ -0,0 +1,14 @@
1
+ ## NEXT
2
+
3
+ ## v0.3.0
4
+
5
+ - Incorporate `numerical` package
6
+ - Add `typeface` module
7
+
8
+ ## v0.2.0
9
+
10
+ - Rename `_types` submodule to `abstract`
11
+
12
+ ## v0.1.0
13
+
14
+ - Hello world!
@@ -0,0 +1,16 @@
1
+ # Developer Notes
2
+
3
+ This document contains notes for those who wish to contribute to `oprattr` by modifying the code base. In order to develop `oprattr`, you should fork the respository, edit your forked copy, and submit a pull request for integration with the original repository.
4
+
5
+ Note that this is a living document and is subject to change without notice.
6
+
7
+ ## Version Numbers
8
+
9
+ When incrementing the version number to X.Y.Z, please do the following
10
+ * create a new subsection in `CHANGELOG.md`, below **NEXT**, with the title
11
+ formatted as vX.Y.Z (YYYY-MM-DD)
12
+ * update the version number in `pyproject.toml`
13
+ * commit with the message "Increment version to X.Y.Z"
14
+ * create a tag named "vX.Y.Z" with the message "version X.Y.Z"
15
+ * push and follow tags
16
+
oprattr-0.3.0/LICENSE ADDED
@@ -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
+
@@ -1,9 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oprattr
3
- Version: 0.1.0
3
+ Version: 0.3.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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oprattr"
3
- version = "0.1.0"
3
+ version = "0.3.0"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  authors = [
@@ -8,6 +8,7 @@ authors = [
8
8
  ]
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
11
+ "numerical",
11
12
  "numpy>=2.2.1",
12
13
  "scipy>=1.15.0",
13
14
  ]
@@ -21,3 +22,6 @@ dev = [
21
22
  "ipython>=8.31.0",
22
23
  "pytest>=8.3.4",
23
24
  ]
25
+
26
+ [tool.uv.sources]
27
+ numerical = { git = "https://github.com/myoung-space-science/numerical", rev = "v0.2.0" }
@@ -1,12 +1,13 @@
1
+ import collections.abc
1
2
  import functools
2
3
  import numbers
3
- import typing
4
4
 
5
5
  import numpy
6
6
 
7
+ from . import abstract
7
8
  from . import mixins
8
9
  from . import operators
9
- from . import _types
10
+ from . import typeface
10
11
  from ._operations import (
11
12
  unary,
12
13
  equality,
@@ -16,10 +17,10 @@ from ._operations import (
16
17
  )
17
18
 
18
19
 
19
- T = typing.TypeVar('T')
20
+ T = typeface.TypeVar('T')
20
21
 
21
22
 
22
- class Operand(_types.Object[T], mixins.Numpy):
23
+ class Operand(abstract.Object[T], mixins.Numpy):
23
24
  """A concrete implementation of a real-valued object."""
24
25
 
25
26
  def __abs__(self):
@@ -114,7 +115,7 @@ class Operand(_types.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)."""
@@ -155,7 +156,7 @@ def gradient(x: Operand[T], *args, **kwargs):
155
156
  return type(x)(data, **meta)
156
157
 
157
158
 
158
- def wrapnumpy(f: typing.Callable):
159
+ def wrapnumpy(f: collections.abc.Callable):
159
160
  """Implement a numpy function for objects with metadata."""
160
161
  @functools.wraps(f)
161
162
  def method(x: Operand[T], **kwargs):
@@ -1,7 +1,5 @@
1
- import typing
2
-
3
1
  from . import operators
4
- from ._types import Object
2
+ from .abstract import Quantity
5
3
 
6
4
 
7
5
  class MetadataError(TypeError):
@@ -11,8 +9,8 @@ class MetadataError(TypeError):
11
9
  self,
12
10
  f: operators.Operator,
13
11
  *args,
14
- error: typing.Optional[str]=None,
15
- key: typing.Optional[str]=None,
12
+ error: str | None = None,
13
+ key: str | None = None,
16
14
  ) -> None:
17
15
  super().__init__(*args)
18
16
  self._f = f
@@ -33,8 +31,8 @@ class MetadataError(TypeError):
33
31
  def _build_error_message(
34
32
  f: operators.Operator,
35
33
  *types: type,
36
- error: typing.Optional[str]=None,
37
- key: typing.Optional[str]=None,
34
+ error: str | None = None,
35
+ key: str | None = None,
38
36
  ) -> str:
39
37
  """Helper for `_raise_metadata_exception`.
40
38
 
@@ -50,9 +48,9 @@ def _build_error_message(
50
48
  if len(types) == 2:
51
49
  a, b = types
52
50
  endstr = "because {} has metadata"
53
- if issubclass(a, Object):
51
+ if issubclass(a, Quantity):
54
52
  return f"{errmsg} between {a} and {b} {endstr.format(str(a))}"
55
- if issubclass(b, Object):
53
+ if issubclass(b, Quantity):
56
54
  return f"{errmsg} between {a} and {b} {endstr.format(str(b))}"
57
55
  if errstr == 'type':
58
56
  if key is None:
@@ -71,7 +69,7 @@ def _build_error_message(
71
69
 
72
70
  def unary(f: operators.Operator, a):
73
71
  """Compute the unary operation f(a)."""
74
- if isinstance(a, Object):
72
+ if isinstance(a, Quantity):
75
73
  meta = {}
76
74
  for key, value in a._meta.items():
77
75
  try:
@@ -86,15 +84,15 @@ def unary(f: operators.Operator, a):
86
84
 
87
85
  def equality(f: operators.Operator, a, b):
88
86
  """Compute the equality operation f(a, b)."""
89
- if isinstance(a, Object) and isinstance(b, Object):
87
+ if isinstance(a, Quantity) and isinstance(b, Quantity):
90
88
  if a._meta != b._meta:
91
89
  return f is operators.ne
92
90
  return f(a._data, b._data)
93
- if isinstance(a, Object):
91
+ if isinstance(a, Quantity):
94
92
  if not a._meta:
95
93
  return f(a._data, b)
96
94
  return f is operators.ne
97
- if isinstance(b, Object):
95
+ if isinstance(b, Quantity):
98
96
  if not b._meta:
99
97
  return f(a, b._data)
100
98
  return f is operators.ne
@@ -103,15 +101,15 @@ def equality(f: operators.Operator, a, b):
103
101
 
104
102
  def ordering(f: operators.Operator, a, b):
105
103
  """Compute the ordering operation f(a, b)."""
106
- if isinstance(a, Object) and isinstance(b, Object):
104
+ if isinstance(a, Quantity) and isinstance(b, Quantity):
107
105
  if a._meta == b._meta:
108
106
  return f(a._data, b._data)
109
107
  raise MetadataError(f, a, b, error='unequal') from None
110
- if isinstance(a, Object):
108
+ if isinstance(a, Quantity):
111
109
  if not a._meta:
112
110
  return f(a._data, b)
113
111
  raise MetadataError(f, a, b, error='non-empty') from None
114
- if isinstance(b, Object):
112
+ if isinstance(b, Quantity):
115
113
  if not b._meta:
116
114
  return f(a, b._data)
117
115
  raise MetadataError(f, a, b, error='non-empty') from None
@@ -120,15 +118,15 @@ def ordering(f: operators.Operator, a, b):
120
118
 
121
119
  def additive(f: operators.Operator, a, b):
122
120
  """Compute the additive operation f(a, b)."""
123
- if isinstance(a, Object) and isinstance(b, Object):
121
+ if isinstance(a, Quantity) and isinstance(b, Quantity):
124
122
  if a._meta == b._meta:
125
123
  return type(a)(f(a._data, b._data), **a._meta)
126
124
  raise MetadataError(f, a, b, error='unequal') from None
127
- if isinstance(a, Object):
125
+ if isinstance(a, Quantity):
128
126
  if not a._meta:
129
127
  return type(a)(f(a._data, b))
130
128
  raise MetadataError(f, a, b, error='non-empty') from None
131
- if isinstance(b, Object):
129
+ if isinstance(b, Quantity):
132
130
  if not b._meta:
133
131
  return type(b)(f(a, b._data))
134
132
  raise MetadataError(f, a, b, error='non-empty') from None
@@ -137,7 +135,7 @@ def additive(f: operators.Operator, a, b):
137
135
 
138
136
  def multiplicative(f: operators.Operator, a, b):
139
137
  """Compute the multiplicative operation f(a, b)."""
140
- if isinstance(a, Object) and isinstance(b, Object):
138
+ if isinstance(a, Quantity) and isinstance(b, Quantity):
141
139
  keys = set(a._meta) & set(b._meta)
142
140
  meta = {}
143
141
  for key in keys:
@@ -154,7 +152,7 @@ def multiplicative(f: operators.Operator, a, b):
154
152
  if key not in keys:
155
153
  meta[key] = value
156
154
  return type(a)(f(a._data, b._data), **meta)
157
- if isinstance(a, Object):
155
+ if isinstance(a, Quantity):
158
156
  meta = {}
159
157
  for key, value in a._meta.items():
160
158
  try:
@@ -164,7 +162,7 @@ def multiplicative(f: operators.Operator, a, b):
164
162
  else:
165
163
  meta[key] = v
166
164
  return type(a)(f(a._data, b), **meta)
167
- if isinstance(b, Object):
165
+ if isinstance(b, Quantity):
168
166
  meta = {}
169
167
  for key, value in b._meta.items():
170
168
  try:
@@ -0,0 +1,56 @@
1
+ import collections.abc
2
+ import numbers
3
+
4
+ import numerical
5
+ import numpy.typing
6
+
7
+ from . import typeface
8
+
9
+
10
+ DataType = typeface.TypeVar(
11
+ 'DataType',
12
+ int,
13
+ float,
14
+ numbers.Number,
15
+ numpy.number,
16
+ numpy.typing.ArrayLike,
17
+ numpy.typing.NDArray,
18
+ )
19
+
20
+
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]):
29
+ """A real-valued object with metadata attributes."""
30
+
31
+ def __init__(
32
+ self,
33
+ __data: DataType,
34
+ **metadata,
35
+ ) -> None:
36
+ if not isinstance(__data, numerical.Real):
37
+ raise TypeError("Data input to Object must be real-valued")
38
+ self._data = __data
39
+ self._meta = metadata
40
+
41
+ def __repr__(self):
42
+ """Called for repr(self)."""
43
+ try:
44
+ datastr = numpy.array2string(
45
+ self._data,
46
+ separator=", ",
47
+ threshold=6,
48
+ edgeitems=2,
49
+ prefix=f"{self.__class__.__qualname__}(",
50
+ suffix=")"
51
+ )
52
+ except Exception:
53
+ datastr = str(self._data)
54
+ metastr = "metadata={" + ", ".join(f"{k!r}" for k in self._meta) + "}"
55
+ return f"{self.__class__.__qualname__}({datastr}, {metastr})"
56
+
@@ -1,15 +1,89 @@
1
+ import collections.abc
1
2
  import numbers
2
- import typing
3
3
 
4
4
  import numpy
5
5
 
6
- from . import _types
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
- UserFunction = typing.Callable[..., T]
13
+ class Real:
14
+ """Mixin for adding basic real-valued operator support."""
15
+
16
+ def __abs__(self):
17
+ return self
18
+
19
+ def __pos__(self):
20
+ return self
21
+
22
+ def __neg__(self):
23
+ return self
24
+
25
+ def __eq__(self, other):
26
+ return False
27
+
28
+ def __ne__(self, other):
29
+ return not (self == other)
30
+
31
+ def __lt__(self, other):
32
+ return False
33
+
34
+ def __le__(self, other):
35
+ return (self < other) and (self == other)
36
+
37
+ def __gt__(self, other):
38
+ return not (self <= other)
39
+
40
+ def __ge__(self, other):
41
+ return not (self < other)
42
+
43
+ def __add__(self, other):
44
+ return self
45
+
46
+ def __radd__(self, other):
47
+ return self
48
+
49
+ def __sub__(self, other):
50
+ return self
51
+
52
+ def __rsub__(self, other):
53
+ return self
54
+
55
+ def __mul__(self, other):
56
+ return self
57
+
58
+ def __rmul__(self, other):
59
+ return self
60
+
61
+ def __truediv__(self, other):
62
+ return self
63
+
64
+ def __rtruediv__(self, other):
65
+ return self
66
+
67
+ def __floordiv__(self, other):
68
+ return self
69
+
70
+ def __rfloordiv__(self, other):
71
+ return self
72
+
73
+ def __mod__(self, other):
74
+ return self
75
+
76
+ def __rmod__(self, other):
77
+ return self
78
+
79
+ def __pow__(self, other):
80
+ return self
81
+
82
+ def __rpow__(self, other):
83
+ return self
84
+
85
+
86
+ UserFunction = collections.abc.Callable[..., T]
13
87
 
14
88
 
15
89
  class Numpy:
@@ -54,7 +128,7 @@ class Numpy:
54
128
  numpy.ndarray,
55
129
  numbers.Number,
56
130
  list,
57
- _types.Object,
131
+ abstract.Quantity,
58
132
  }
59
133
 
60
134
  def __array_ufunc__(self, ufunc, method, *args, **kwargs):
@@ -89,7 +163,7 @@ class Numpy:
89
163
  return NotImplemented
90
164
  if out:
91
165
  kwargs['out'] = tuple(
92
- x._data if isinstance(x, _types.Object)
166
+ x._data if isinstance(x, abstract.Quantity)
93
167
  else x for x in out
94
168
  )
95
169
  if self._implements(ufunc):
@@ -134,7 +208,7 @@ class Numpy:
134
208
 
135
209
  _FUNCTION_TYPES = {
136
210
  numpy.ndarray,
137
- _types.Object,
211
+ abstract.Object,
138
212
  } | set(numpy.ScalarType)
139
213
 
140
214
  def __array_function__(self, func, types, args, kwargs):
@@ -212,7 +286,7 @@ class Numpy:
212
286
  types = self._get_numpy_types(types)
213
287
  return array.__array_function__(func, types, args, kwargs)
214
288
 
215
- def _get_numpy_array(self) -> typing.Optional[numpy.typing.NDArray]:
289
+ def _get_numpy_array(self) -> numpy.typing.NDArray | None:
216
290
  """Convert the data interface to an array for `numpy` mixin methods.
217
291
 
218
292
  Notes
@@ -251,7 +325,7 @@ class Numpy:
251
325
  `arg` if `arg` is an instance of the base object class; otherwise, it
252
326
  will return the unmodified argument.
253
327
  """
254
- if isinstance(arg, _types.Object):
328
+ if isinstance(arg, abstract.Quantity):
255
329
  return arg._data
256
330
  return arg
257
331
 
@@ -267,11 +341,11 @@ class Numpy:
267
341
  """
268
342
  return tuple(
269
343
  ti for ti in types
270
- if not issubclass(ti, _types.Object)
344
+ if not issubclass(ti, abstract.Object)
271
345
  )
272
346
 
273
347
  @classmethod
274
- def _implements(cls, operation: typing.Callable):
348
+ def _implements(cls, operation: collections.abc.Callable):
275
349
  """True if this class defines a custom implementation for `operation`.
276
350
 
277
351
  This is a helper methods that gracefully handles the case in which a
@@ -283,11 +357,11 @@ class Numpy:
283
357
  return False
284
358
  return result
285
359
 
286
- _FUNCTIONS: typing.Dict[str, typing.Callable]=None
360
+ _FUNCTIONS: dict[str, collections.abc.Callable]=None
287
361
  """Internal collection of custom `numpy` function implementations."""
288
362
 
289
363
  @classmethod
290
- def implementation(cls, numpy_function: typing.Callable, /):
364
+ def implementation(cls, numpy_function: collections.abc.Callable, /):
291
365
  """Register a custom implementation of this `numpy` function.
292
366
 
293
367
  Parameters
@@ -351,7 +425,7 @@ class Numpy:
351
425
  @classmethod
352
426
  def implement(
353
427
  cls,
354
- numpy_function: typing.Callable,
428
+ numpy_function: collections.abc.Callable,
355
429
  user_function: UserFunction,
356
430
  /,
357
431
  ) -> None:
@@ -2,14 +2,14 @@
2
2
  A namespace for operators used by this package's `Object` class.
3
3
  """
4
4
 
5
+ import collections.abc
5
6
  import builtins
6
7
  import operator
7
- import typing
8
8
 
9
9
 
10
10
  class Operator:
11
11
  """Base class for enhanced operators."""
12
- def __init__(self, __f: typing.Callable, operation: str):
12
+ def __init__(self, __f: collections.abc.Callable, operation: str):
13
13
  self._f = __f
14
14
  self._operation = operation
15
15
 
@@ -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
+
@@ -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,6 +1,6 @@
1
1
  """
2
2
  This script generates and echos exceptions related to operations on instances of
3
- the `Object` class. It is meant as a supplement to rigorous tests.
3
+ the `Operand` class. It is meant as a supplement to rigorous tests.
4
4
  """
5
5
 
6
6
  import oprattr