uarray 0.9.2__cp313-cp313-win32.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.
- uarray/.coveragerc +9 -0
- uarray/__init__.py +118 -0
- uarray/_backend.py +800 -0
- uarray/_typing.pyi +65 -0
- uarray/_uarray.cp313-win32.pyd +0 -0
- uarray/_uarray.pyi +131 -0
- uarray/_version.py +16 -0
- uarray/conftest.py +11 -0
- uarray/py.typed +0 -0
- uarray/pytest.ini +3 -0
- uarray/tests/__init__.py +0 -0
- uarray/tests/example_helpers.py +46 -0
- uarray/tests/test_uarray.py +580 -0
- uarray-0.9.2.dist-info/LICENSE +29 -0
- uarray-0.9.2.dist-info/METADATA +88 -0
- uarray-0.9.2.dist-info/RECORD +18 -0
- uarray-0.9.2.dist-info/WHEEL +5 -0
- uarray-0.9.2.dist-info/top_level.txt +1 -0
uarray/_backend.py
ADDED
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import types
|
|
4
|
+
import inspect
|
|
5
|
+
import functools
|
|
6
|
+
from . import _uarray
|
|
7
|
+
import copyreg
|
|
8
|
+
import pickle
|
|
9
|
+
import contextlib
|
|
10
|
+
import warnings
|
|
11
|
+
|
|
12
|
+
from collections.abc import Callable, Generator, Iterable
|
|
13
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar, Literal, overload, no_type_check
|
|
14
|
+
|
|
15
|
+
from ._uarray import (
|
|
16
|
+
BackendNotImplementedError,
|
|
17
|
+
_Function,
|
|
18
|
+
_SkipBackendContext,
|
|
19
|
+
_SetBackendContext,
|
|
20
|
+
_BackendState,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from typing_extensions import ParamSpec
|
|
25
|
+
from ._typing import (
|
|
26
|
+
_SupportsUA,
|
|
27
|
+
_PartialDispatchable,
|
|
28
|
+
_ReplacerFunc,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
_P = ParamSpec("_P")
|
|
32
|
+
|
|
33
|
+
_T = TypeVar("_T")
|
|
34
|
+
_T2 = TypeVar("_T2")
|
|
35
|
+
_Self = TypeVar("_Self")
|
|
36
|
+
_TT = TypeVar("_TT", bound=type)
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"set_backend",
|
|
40
|
+
"set_global_backend",
|
|
41
|
+
"skip_backend",
|
|
42
|
+
"register_backend",
|
|
43
|
+
"determine_backend",
|
|
44
|
+
"determine_backend_multi",
|
|
45
|
+
"clear_backends",
|
|
46
|
+
"create_multimethod",
|
|
47
|
+
"generate_multimethod",
|
|
48
|
+
"_Function",
|
|
49
|
+
"BackendNotImplementedError",
|
|
50
|
+
"Dispatchable",
|
|
51
|
+
"wrap_single_convertor",
|
|
52
|
+
"wrap_single_convertor_instance",
|
|
53
|
+
"all_of_type",
|
|
54
|
+
"mark_as",
|
|
55
|
+
"set_state",
|
|
56
|
+
"get_state",
|
|
57
|
+
"reset_state",
|
|
58
|
+
"_BackendState",
|
|
59
|
+
"_SkipBackendContext",
|
|
60
|
+
"_SetBackendContext",
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@no_type_check
|
|
65
|
+
def unpickle_function(
|
|
66
|
+
mod_name: str,
|
|
67
|
+
qname: str,
|
|
68
|
+
self_: object,
|
|
69
|
+
) -> Callable[..., Any]:
|
|
70
|
+
import importlib
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
module = importlib.import_module(mod_name)
|
|
74
|
+
qname = qname.split(".")
|
|
75
|
+
func = module
|
|
76
|
+
for q in qname:
|
|
77
|
+
func = getattr(func, q)
|
|
78
|
+
|
|
79
|
+
if self_ is not None:
|
|
80
|
+
func = types.MethodType(func, self_)
|
|
81
|
+
|
|
82
|
+
return func
|
|
83
|
+
except (ImportError, AttributeError) as e:
|
|
84
|
+
from pickle import UnpicklingError
|
|
85
|
+
|
|
86
|
+
raise UnpicklingError from e
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@no_type_check
|
|
90
|
+
def pickle_function(
|
|
91
|
+
func: Callable[..., Any],
|
|
92
|
+
) -> tuple[
|
|
93
|
+
Callable[[str, str, object], Callable[..., Any]],
|
|
94
|
+
tuple[str, str, Any | None],
|
|
95
|
+
]:
|
|
96
|
+
mod_name = getattr(func, "__module__", None)
|
|
97
|
+
qname = getattr(func, "__qualname__", None)
|
|
98
|
+
self_ = getattr(func, "__self__", None)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
test = unpickle_function(mod_name, qname, self_)
|
|
102
|
+
except pickle.UnpicklingError:
|
|
103
|
+
test = None
|
|
104
|
+
|
|
105
|
+
if test is not func:
|
|
106
|
+
raise pickle.PicklingError(
|
|
107
|
+
"Can't pickle {}: it's not the same object as {}".format(func, test)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
return unpickle_function, (mod_name, qname, self_)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def pickle_state(
|
|
114
|
+
state: _BackendState,
|
|
115
|
+
) -> tuple[
|
|
116
|
+
Callable[[dict[str, Any], dict[str, Any], bool], _BackendState],
|
|
117
|
+
tuple[dict[str, Any], dict[str, Any], bool],
|
|
118
|
+
]:
|
|
119
|
+
return _uarray._BackendState._unpickle, state._pickle()
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def pickle_set_backend_context(
|
|
123
|
+
ctx: _SetBackendContext,
|
|
124
|
+
) -> tuple[type[_SetBackendContext], tuple[_SupportsUA, bool, bool],]:
|
|
125
|
+
return _SetBackendContext, ctx._pickle()
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def pickle_skip_backend_context(
|
|
129
|
+
ctx: _SkipBackendContext,
|
|
130
|
+
) -> tuple[type[_SkipBackendContext], tuple[_SupportsUA],]:
|
|
131
|
+
return _SkipBackendContext, ctx._pickle()
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# TODO: Remove the `if` block once python/typeshed#7415
|
|
135
|
+
# has been integrated into mypy
|
|
136
|
+
if not TYPE_CHECKING:
|
|
137
|
+
copyreg.pickle(_Function, pickle_function)
|
|
138
|
+
copyreg.pickle(_uarray._BackendState, pickle_state)
|
|
139
|
+
copyreg.pickle(_SetBackendContext, pickle_set_backend_context)
|
|
140
|
+
copyreg.pickle(_SkipBackendContext, pickle_skip_backend_context)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_state() -> _BackendState:
|
|
144
|
+
"""
|
|
145
|
+
Returns an opaque object containing the current state of all the backends.
|
|
146
|
+
|
|
147
|
+
Can be used for synchronization between threads/processes.
|
|
148
|
+
|
|
149
|
+
See Also
|
|
150
|
+
--------
|
|
151
|
+
set_state
|
|
152
|
+
Sets the state returned by this function.
|
|
153
|
+
"""
|
|
154
|
+
return _uarray.get_state()
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@contextlib.contextmanager
|
|
158
|
+
def reset_state() -> Generator[None, None, None]:
|
|
159
|
+
"""
|
|
160
|
+
Returns a context manager that resets all state once exited.
|
|
161
|
+
|
|
162
|
+
See Also
|
|
163
|
+
--------
|
|
164
|
+
set_state
|
|
165
|
+
Context manager that sets the backend state.
|
|
166
|
+
get_state
|
|
167
|
+
Gets a state to be set by this context manager.
|
|
168
|
+
"""
|
|
169
|
+
with set_state(get_state()):
|
|
170
|
+
yield
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@contextlib.contextmanager
|
|
174
|
+
def set_state(state: _BackendState) -> Generator[None, None, None]:
|
|
175
|
+
"""
|
|
176
|
+
A context manager that sets the state of the backends to one returned by :obj:`get_state`.
|
|
177
|
+
|
|
178
|
+
See Also
|
|
179
|
+
--------
|
|
180
|
+
get_state
|
|
181
|
+
Gets a state to be set by this context manager.
|
|
182
|
+
"""
|
|
183
|
+
old_state = get_state()
|
|
184
|
+
_uarray.set_state(state)
|
|
185
|
+
try:
|
|
186
|
+
yield
|
|
187
|
+
finally:
|
|
188
|
+
_uarray.set_state(old_state, True)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def create_multimethod(
|
|
192
|
+
*args: Any,
|
|
193
|
+
**kwargs: Any,
|
|
194
|
+
) -> Callable[[Callable[_P, tuple[Dispatchable[Any, Any], ...]]], _Function[_P]]:
|
|
195
|
+
"""
|
|
196
|
+
Creates a decorator for generating multimethods.
|
|
197
|
+
|
|
198
|
+
This function creates a decorator that can be used with an argument
|
|
199
|
+
extractor in order to generate a multimethod. Other than for the
|
|
200
|
+
argument extractor, all arguments are passed on to
|
|
201
|
+
:obj:`generate_multimethod`.
|
|
202
|
+
|
|
203
|
+
See Also
|
|
204
|
+
--------
|
|
205
|
+
generate_multimethod
|
|
206
|
+
Generates a multimethod.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
def wrapper(a):
|
|
210
|
+
return generate_multimethod(a, *args, **kwargs)
|
|
211
|
+
|
|
212
|
+
return wrapper
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def generate_multimethod(
|
|
216
|
+
argument_extractor: Callable[_P, tuple[Dispatchable[Any, Any], ...]],
|
|
217
|
+
argument_replacer: _ReplacerFunc,
|
|
218
|
+
domain: str,
|
|
219
|
+
default: None | Callable[..., Any] = None,
|
|
220
|
+
) -> _Function[_P]:
|
|
221
|
+
"""
|
|
222
|
+
Generates a multimethod.
|
|
223
|
+
|
|
224
|
+
Parameters
|
|
225
|
+
----------
|
|
226
|
+
argument_extractor : ArgumentExtractorType
|
|
227
|
+
A callable which extracts the dispatchable arguments. Extracted arguments
|
|
228
|
+
should be marked by the :obj:`Dispatchable` class. It has the same signature
|
|
229
|
+
as the desired multimethod.
|
|
230
|
+
argument_replacer : ArgumentReplacerType
|
|
231
|
+
A callable with the signature (args, kwargs, dispatchables), which should also
|
|
232
|
+
return an (args, kwargs) pair with the dispatchables replaced inside the args/kwargs.
|
|
233
|
+
domain : str
|
|
234
|
+
A string value indicating the domain of this multimethod.
|
|
235
|
+
default: Optional[Callable], optional
|
|
236
|
+
The default implementation of this multimethod, where ``None`` (the default) specifies
|
|
237
|
+
there is no default implementation.
|
|
238
|
+
|
|
239
|
+
Examples
|
|
240
|
+
--------
|
|
241
|
+
In this example, ``a`` is to be dispatched over, so we return it, while marking it as an ``int``.
|
|
242
|
+
The trailing comma is needed because the args have to be returned as an iterable.
|
|
243
|
+
|
|
244
|
+
>>> def override_me(a, b):
|
|
245
|
+
... return Dispatchable(a, int),
|
|
246
|
+
|
|
247
|
+
Next, we define the argument replacer that replaces the dispatchables inside args/kwargs with the
|
|
248
|
+
supplied ones.
|
|
249
|
+
|
|
250
|
+
>>> def override_replacer(args, kwargs, dispatchables):
|
|
251
|
+
... return (dispatchables[0], args[1]), {}
|
|
252
|
+
|
|
253
|
+
Next, we define the multimethod.
|
|
254
|
+
|
|
255
|
+
>>> overridden_me = generate_multimethod(
|
|
256
|
+
... override_me, override_replacer, "ua_examples"
|
|
257
|
+
... )
|
|
258
|
+
|
|
259
|
+
Notice that there's no default implementation, unless you supply one.
|
|
260
|
+
|
|
261
|
+
>>> overridden_me(1, "a")
|
|
262
|
+
Traceback (most recent call last):
|
|
263
|
+
...
|
|
264
|
+
uarray.BackendNotImplementedError: ...
|
|
265
|
+
|
|
266
|
+
>>> overridden_me2 = generate_multimethod(
|
|
267
|
+
... override_me, override_replacer, "ua_examples", default=lambda x, y: (x, y)
|
|
268
|
+
... )
|
|
269
|
+
>>> overridden_me2(1, "a")
|
|
270
|
+
(1, 'a')
|
|
271
|
+
|
|
272
|
+
See Also
|
|
273
|
+
--------
|
|
274
|
+
uarray
|
|
275
|
+
See the module documentation for how to override the method by creating backends.
|
|
276
|
+
"""
|
|
277
|
+
kw_defaults, arg_defaults, opts = get_defaults(argument_extractor)
|
|
278
|
+
ua_func = _Function(
|
|
279
|
+
argument_extractor,
|
|
280
|
+
argument_replacer,
|
|
281
|
+
domain,
|
|
282
|
+
arg_defaults,
|
|
283
|
+
kw_defaults,
|
|
284
|
+
default,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
return functools.update_wrapper(ua_func, argument_extractor) # type: ignore[return-value]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
def set_backend(
|
|
291
|
+
backend: _SupportsUA,
|
|
292
|
+
coerce: bool = False,
|
|
293
|
+
only: bool = False,
|
|
294
|
+
) -> _SetBackendContext:
|
|
295
|
+
"""
|
|
296
|
+
A context manager that sets the preferred backend.
|
|
297
|
+
|
|
298
|
+
Parameters
|
|
299
|
+
----------
|
|
300
|
+
backend
|
|
301
|
+
The backend to set.
|
|
302
|
+
coerce
|
|
303
|
+
Whether or not to coerce to a specific backend's types. Implies ``only``.
|
|
304
|
+
only
|
|
305
|
+
Whether or not this should be the last backend to try.
|
|
306
|
+
|
|
307
|
+
See Also
|
|
308
|
+
--------
|
|
309
|
+
skip_backend: A context manager that allows skipping of backends.
|
|
310
|
+
set_global_backend: Set a single, global backend for a domain.
|
|
311
|
+
"""
|
|
312
|
+
# Deprecated: 2022-08-17, To be removed: 2023-08-17
|
|
313
|
+
# See gh-237 and https://discuss.scientific-python.org/t/requirements-and-discussion-of-a-type-dispatcher-for-the-ecosystem/157/40
|
|
314
|
+
warnings.warn("uarray.skip_backend is deprecated, please migrate to scoped backends.", category=DeprecationWarning)
|
|
315
|
+
try:
|
|
316
|
+
return backend.__ua_cache__["set", coerce, only]
|
|
317
|
+
except AttributeError:
|
|
318
|
+
backend.__ua_cache__ = {} # type: ignore[misc]
|
|
319
|
+
except KeyError:
|
|
320
|
+
pass
|
|
321
|
+
|
|
322
|
+
ctx = _SetBackendContext(backend, coerce, only)
|
|
323
|
+
backend.__ua_cache__["set", coerce, only] = ctx
|
|
324
|
+
return ctx
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def skip_backend(backend: _SupportsUA) -> _SkipBackendContext:
|
|
328
|
+
"""
|
|
329
|
+
A context manager that allows one to skip a given backend from processing
|
|
330
|
+
entirely. This allows one to use another backend's code in a library that
|
|
331
|
+
is also a consumer of the same backend.
|
|
332
|
+
|
|
333
|
+
Parameters
|
|
334
|
+
----------
|
|
335
|
+
backend
|
|
336
|
+
The backend to skip.
|
|
337
|
+
|
|
338
|
+
See Also
|
|
339
|
+
--------
|
|
340
|
+
set_backend: A context manager that allows setting of backends.
|
|
341
|
+
set_global_backend: Set a single, global backend for a domain.
|
|
342
|
+
"""
|
|
343
|
+
try:
|
|
344
|
+
return backend.__ua_cache__["skip"]
|
|
345
|
+
except AttributeError:
|
|
346
|
+
backend.__ua_cache__ = {} # type: ignore[misc]
|
|
347
|
+
except KeyError:
|
|
348
|
+
pass
|
|
349
|
+
|
|
350
|
+
ctx = _SkipBackendContext(backend)
|
|
351
|
+
backend.__ua_cache__["skip"] = ctx
|
|
352
|
+
return ctx
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def get_defaults(
|
|
356
|
+
f: Callable[..., Any],
|
|
357
|
+
) -> tuple[dict[str, Any], tuple[Any, ...], set[str]]:
|
|
358
|
+
sig = inspect.signature(f)
|
|
359
|
+
kw_defaults = {}
|
|
360
|
+
arg_defaults = []
|
|
361
|
+
opts = set()
|
|
362
|
+
for k, v in sig.parameters.items():
|
|
363
|
+
if v.default is not inspect.Parameter.empty:
|
|
364
|
+
kw_defaults[k] = v.default
|
|
365
|
+
if v.kind in (
|
|
366
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
367
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
368
|
+
):
|
|
369
|
+
arg_defaults.append(v.default)
|
|
370
|
+
opts.add(k)
|
|
371
|
+
|
|
372
|
+
return kw_defaults, tuple(arg_defaults), opts
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def set_global_backend(
|
|
376
|
+
backend: _SupportsUA,
|
|
377
|
+
coerce: bool = False,
|
|
378
|
+
only: bool = False,
|
|
379
|
+
*,
|
|
380
|
+
try_last: bool = False,
|
|
381
|
+
) -> None:
|
|
382
|
+
"""
|
|
383
|
+
This utility method replaces the default backend for permanent use. It
|
|
384
|
+
will be tried in the list of backends automatically, unless the
|
|
385
|
+
``only`` flag is set on a backend. This will be the first tried
|
|
386
|
+
backend outside the :obj:`set_backend` context manager.
|
|
387
|
+
|
|
388
|
+
Note that this method is not thread-safe.
|
|
389
|
+
|
|
390
|
+
.. warning::
|
|
391
|
+
We caution library authors against using this function in
|
|
392
|
+
their code. We do *not* support this use-case. This function
|
|
393
|
+
is meant to be used only by users themselves, or by a reference
|
|
394
|
+
implementation, if one exists.
|
|
395
|
+
|
|
396
|
+
Parameters
|
|
397
|
+
----------
|
|
398
|
+
backend
|
|
399
|
+
The backend to register.
|
|
400
|
+
coerce : bool
|
|
401
|
+
Whether to coerce input types when trying this backend.
|
|
402
|
+
only : bool
|
|
403
|
+
If ``True``, no more backends will be tried if this fails.
|
|
404
|
+
Implied by ``coerce=True``.
|
|
405
|
+
try_last : bool
|
|
406
|
+
If ``True``, the global backend is tried after registered backends.
|
|
407
|
+
|
|
408
|
+
See Also
|
|
409
|
+
--------
|
|
410
|
+
set_backend: A context manager that allows setting of backends.
|
|
411
|
+
skip_backend: A context manager that allows skipping of backends.
|
|
412
|
+
"""
|
|
413
|
+
_uarray.set_global_backend(backend, coerce, only, try_last)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def register_backend(backend: _SupportsUA) -> None:
|
|
417
|
+
"""
|
|
418
|
+
This utility method sets registers backend for permanent use. It
|
|
419
|
+
will be tried in the list of backends automatically, unless the
|
|
420
|
+
``only`` flag is set on a backend.
|
|
421
|
+
|
|
422
|
+
Note that this method is not thread-safe.
|
|
423
|
+
|
|
424
|
+
Parameters
|
|
425
|
+
----------
|
|
426
|
+
backend
|
|
427
|
+
The backend to register.
|
|
428
|
+
"""
|
|
429
|
+
_uarray.register_backend(backend)
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def clear_backends(
|
|
433
|
+
domain: None | str,
|
|
434
|
+
registered: bool = True,
|
|
435
|
+
globals: bool = False,
|
|
436
|
+
) -> None:
|
|
437
|
+
"""
|
|
438
|
+
This utility method clears registered backends.
|
|
439
|
+
|
|
440
|
+
.. warning::
|
|
441
|
+
We caution library authors against using this function in
|
|
442
|
+
their code. We do *not* support this use-case. This function
|
|
443
|
+
is meant to be used only by users themselves.
|
|
444
|
+
|
|
445
|
+
.. warning::
|
|
446
|
+
Do NOT use this method inside a multimethod call, or the
|
|
447
|
+
program is likely to crash.
|
|
448
|
+
|
|
449
|
+
Parameters
|
|
450
|
+
----------
|
|
451
|
+
domain : Optional[str]
|
|
452
|
+
The domain for which to de-register backends. ``None`` means
|
|
453
|
+
de-register for all domains.
|
|
454
|
+
registered : bool
|
|
455
|
+
Whether or not to clear registered backends. See :obj:`register_backend`.
|
|
456
|
+
globals : bool
|
|
457
|
+
Whether or not to clear global backends. See :obj:`set_global_backend`.
|
|
458
|
+
|
|
459
|
+
See Also
|
|
460
|
+
--------
|
|
461
|
+
register_backend : Register a backend globally.
|
|
462
|
+
set_global_backend : Set a global backend.
|
|
463
|
+
"""
|
|
464
|
+
_uarray.clear_backends(domain, registered, globals)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class Dispatchable(Generic[_T, _TT]):
|
|
468
|
+
"""
|
|
469
|
+
A utility class which marks an argument with a specific dispatch type.
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
Attributes
|
|
473
|
+
----------
|
|
474
|
+
value
|
|
475
|
+
The value of the Dispatchable.
|
|
476
|
+
|
|
477
|
+
type
|
|
478
|
+
The type of the Dispatchable.
|
|
479
|
+
|
|
480
|
+
Examples
|
|
481
|
+
--------
|
|
482
|
+
>>> x = Dispatchable(1, str)
|
|
483
|
+
>>> x
|
|
484
|
+
<Dispatchable: type=<class 'str'>, value=1>
|
|
485
|
+
|
|
486
|
+
See Also
|
|
487
|
+
--------
|
|
488
|
+
all_of_type
|
|
489
|
+
Marks all unmarked parameters of a function.
|
|
490
|
+
|
|
491
|
+
mark_as
|
|
492
|
+
Allows one to create a utility function to mark as a given type.
|
|
493
|
+
"""
|
|
494
|
+
|
|
495
|
+
def __init__(
|
|
496
|
+
self,
|
|
497
|
+
value: _T,
|
|
498
|
+
dispatch_type: _TT,
|
|
499
|
+
coercible: bool = True,
|
|
500
|
+
) -> None:
|
|
501
|
+
self.value = value
|
|
502
|
+
self.type = dispatch_type
|
|
503
|
+
self.coercible = coercible
|
|
504
|
+
|
|
505
|
+
@overload
|
|
506
|
+
def __getitem__(self, index: Literal[0]) -> _TT:
|
|
507
|
+
...
|
|
508
|
+
|
|
509
|
+
@overload
|
|
510
|
+
def __getitem__(self, index: Literal[1]) -> _T:
|
|
511
|
+
...
|
|
512
|
+
|
|
513
|
+
def __getitem__(self, index: Literal[0, 1]) -> _TT | _T:
|
|
514
|
+
return (self.type, self.value)[index]
|
|
515
|
+
|
|
516
|
+
def __str__(self) -> str:
|
|
517
|
+
return "<{0}: type={1!r}, value={2!r}>".format(
|
|
518
|
+
type(self).__name__, self.type, self.value
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
__repr__ = __str__
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def mark_as(dispatch_type: _TT) -> _PartialDispatchable[_TT]:
|
|
525
|
+
"""
|
|
526
|
+
Creates a utility function to mark something as a specific type.
|
|
527
|
+
|
|
528
|
+
Examples
|
|
529
|
+
--------
|
|
530
|
+
>>> mark_int = mark_as(int)
|
|
531
|
+
>>> mark_int(1)
|
|
532
|
+
<Dispatchable: type=<class 'int'>, value=1>
|
|
533
|
+
"""
|
|
534
|
+
# Pretend a more specific `functools.partial` sub-type is returned
|
|
535
|
+
return functools.partial( # type: ignore[return-value]
|
|
536
|
+
Dispatchable, dispatch_type=dispatch_type,
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def all_of_type(
|
|
541
|
+
arg_type: _TT,
|
|
542
|
+
) -> Callable[
|
|
543
|
+
[Callable[_P, Iterable[_T]]],
|
|
544
|
+
Callable[_P, tuple[Dispatchable[_T, _TT], ...]],
|
|
545
|
+
]:
|
|
546
|
+
"""
|
|
547
|
+
Marks all unmarked arguments as a given type.
|
|
548
|
+
|
|
549
|
+
Examples
|
|
550
|
+
--------
|
|
551
|
+
>>> @all_of_type(str)
|
|
552
|
+
... def f(a, b):
|
|
553
|
+
... return a, Dispatchable(b, int)
|
|
554
|
+
>>> f('a', 1)
|
|
555
|
+
(<Dispatchable: type=<class 'str'>, value='a'>, <Dispatchable: type=<class 'int'>, value=1>)
|
|
556
|
+
"""
|
|
557
|
+
|
|
558
|
+
def outer(func):
|
|
559
|
+
@functools.wraps(func)
|
|
560
|
+
def inner(*args, **kwargs):
|
|
561
|
+
extracted_args = func(*args, **kwargs)
|
|
562
|
+
return tuple(
|
|
563
|
+
Dispatchable(arg, arg_type)
|
|
564
|
+
if not isinstance(arg, Dispatchable)
|
|
565
|
+
else arg
|
|
566
|
+
for arg in extracted_args
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
return inner
|
|
570
|
+
|
|
571
|
+
return outer
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def wrap_single_convertor(
|
|
575
|
+
convert_single: Callable[[_T, _TT, bool], _T2],
|
|
576
|
+
) -> Callable[[Iterable[Dispatchable[_T, _TT]], bool], list[_T2]]:
|
|
577
|
+
"""
|
|
578
|
+
Wraps a ``__ua_convert__`` defined for a single element to all elements.
|
|
579
|
+
If any of them return ``NotImplemented``, the operation is assumed to be
|
|
580
|
+
undefined.
|
|
581
|
+
|
|
582
|
+
Accepts a signature of (value, type, coerce).
|
|
583
|
+
"""
|
|
584
|
+
|
|
585
|
+
@functools.wraps(convert_single)
|
|
586
|
+
def __ua_convert__(dispatchables, coerce):
|
|
587
|
+
converted = []
|
|
588
|
+
for d in dispatchables:
|
|
589
|
+
c = convert_single(d.value, d.type, coerce and d.coercible)
|
|
590
|
+
|
|
591
|
+
if c is NotImplemented:
|
|
592
|
+
return NotImplemented
|
|
593
|
+
|
|
594
|
+
converted.append(c)
|
|
595
|
+
|
|
596
|
+
return converted
|
|
597
|
+
|
|
598
|
+
return __ua_convert__
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def wrap_single_convertor_instance(
|
|
602
|
+
convert_single: Callable[[_Self, _T, _TT, bool], _T2],
|
|
603
|
+
) -> Callable[[_Self, Iterable[Dispatchable[_T, _TT]], bool], list[_T2]]:
|
|
604
|
+
"""
|
|
605
|
+
Wraps a ``__ua_convert__`` defined for a single element to all elements.
|
|
606
|
+
If any of them return ``NotImplemented``, the operation is assumed to be
|
|
607
|
+
undefined.
|
|
608
|
+
|
|
609
|
+
Accepts a signature of (value, type, coerce).
|
|
610
|
+
"""
|
|
611
|
+
|
|
612
|
+
@functools.wraps(convert_single)
|
|
613
|
+
def __ua_convert__(self, dispatchables, coerce):
|
|
614
|
+
converted = []
|
|
615
|
+
for d in dispatchables:
|
|
616
|
+
c = convert_single(self, d.value, d.type, coerce and d.coercible)
|
|
617
|
+
|
|
618
|
+
if c is NotImplemented:
|
|
619
|
+
return NotImplemented
|
|
620
|
+
|
|
621
|
+
converted.append(c)
|
|
622
|
+
|
|
623
|
+
return converted
|
|
624
|
+
|
|
625
|
+
return __ua_convert__
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def determine_backend(
|
|
629
|
+
value: object,
|
|
630
|
+
dispatch_type: type[Any],
|
|
631
|
+
*,
|
|
632
|
+
domain: str,
|
|
633
|
+
only: bool = True,
|
|
634
|
+
coerce: bool = False,
|
|
635
|
+
) -> _SetBackendContext:
|
|
636
|
+
"""Set the backend to the first active backend that supports ``value``
|
|
637
|
+
|
|
638
|
+
This is useful for functions that call multimethods without any dispatchable
|
|
639
|
+
arguments. You can use :func:`determine_backend` to ensure the same backend
|
|
640
|
+
is used everywhere in a block of multimethod calls.
|
|
641
|
+
|
|
642
|
+
Parameters
|
|
643
|
+
----------
|
|
644
|
+
value
|
|
645
|
+
The value being tested
|
|
646
|
+
dispatch_type
|
|
647
|
+
The dispatch type associated with ``value``, aka
|
|
648
|
+
":ref:`marking <MarkingGlossary>`".
|
|
649
|
+
domain: string
|
|
650
|
+
The domain to query for backends and set.
|
|
651
|
+
coerce: bool
|
|
652
|
+
Whether or not to allow coercion to the backend's types. Implies ``only``.
|
|
653
|
+
only: bool
|
|
654
|
+
Whether or not this should be the last backend to try.
|
|
655
|
+
|
|
656
|
+
See Also
|
|
657
|
+
--------
|
|
658
|
+
set_backend: For when you know which backend to set
|
|
659
|
+
|
|
660
|
+
Notes
|
|
661
|
+
-----
|
|
662
|
+
|
|
663
|
+
Support is determined by the ``__ua_convert__`` protocol. Backends not
|
|
664
|
+
supporting the type must return ``NotImplemented`` from their
|
|
665
|
+
``__ua_convert__`` if they don't support input of that type.
|
|
666
|
+
|
|
667
|
+
Examples
|
|
668
|
+
--------
|
|
669
|
+
|
|
670
|
+
Suppose we have two backends ``BackendA`` and ``BackendB`` each supporting
|
|
671
|
+
different types, ``TypeA`` and ``TypeB``. Neither supporting the other type:
|
|
672
|
+
|
|
673
|
+
>>> with ua.set_backend(ex.BackendA):
|
|
674
|
+
... ex.call_multimethod(ex.TypeB(), ex.TypeB())
|
|
675
|
+
Traceback (most recent call last):
|
|
676
|
+
...
|
|
677
|
+
uarray.BackendNotImplementedError: ...
|
|
678
|
+
|
|
679
|
+
Now consider a multimethod that creates a new object of ``TypeA``, or
|
|
680
|
+
``TypeB`` depending on the active backend.
|
|
681
|
+
|
|
682
|
+
>>> with ua.set_backend(ex.BackendA), ua.set_backend(ex.BackendB):
|
|
683
|
+
... res = ex.creation_multimethod()
|
|
684
|
+
... ex.call_multimethod(res, ex.TypeA())
|
|
685
|
+
Traceback (most recent call last):
|
|
686
|
+
...
|
|
687
|
+
uarray.BackendNotImplementedError: ...
|
|
688
|
+
|
|
689
|
+
``res`` is an object of ``TypeB`` because ``BackendB`` is set in the
|
|
690
|
+
innermost with statement. So, ``call_multimethod`` fails since the types
|
|
691
|
+
don't match.
|
|
692
|
+
|
|
693
|
+
Instead, we need to first find a backend suitable for all of our objects.
|
|
694
|
+
|
|
695
|
+
>>> with ua.set_backend(ex.BackendA), ua.set_backend(ex.BackendB):
|
|
696
|
+
... x = ex.TypeA()
|
|
697
|
+
... with ua.determine_backend(x, "mark", domain="ua_examples"):
|
|
698
|
+
... res = ex.creation_multimethod()
|
|
699
|
+
... ex.call_multimethod(res, x)
|
|
700
|
+
TypeA
|
|
701
|
+
|
|
702
|
+
"""
|
|
703
|
+
dispatchables = (Dispatchable(value, dispatch_type, coerce),)
|
|
704
|
+
backend = _uarray.determine_backend(domain, dispatchables, coerce)
|
|
705
|
+
|
|
706
|
+
return set_backend(backend, coerce=coerce, only=only)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
def determine_backend_multi(
|
|
710
|
+
dispatchables: Iterable[Any],
|
|
711
|
+
*,
|
|
712
|
+
domain: str,
|
|
713
|
+
only: bool = True,
|
|
714
|
+
coerce: bool = False,
|
|
715
|
+
**kwargs: type[Any],
|
|
716
|
+
) -> _SetBackendContext:
|
|
717
|
+
"""Set a backend supporting all ``dispatchables``
|
|
718
|
+
|
|
719
|
+
This is useful for functions that call multimethods without any dispatchable
|
|
720
|
+
arguments. You can use :func:`determine_backend_multi` to ensure the same
|
|
721
|
+
backend is used everywhere in a block of multimethod calls involving
|
|
722
|
+
multiple arrays.
|
|
723
|
+
|
|
724
|
+
Parameters
|
|
725
|
+
----------
|
|
726
|
+
dispatchables: Sequence[Union[uarray.Dispatchable, Any]]
|
|
727
|
+
The dispatchables that must be supported
|
|
728
|
+
domain: string
|
|
729
|
+
The domain to query for backends and set.
|
|
730
|
+
coerce: bool
|
|
731
|
+
Whether or not to allow coercion to the backend's types. Implies ``only``.
|
|
732
|
+
only: bool
|
|
733
|
+
Whether or not this should be the last backend to try.
|
|
734
|
+
dispatch_type: Optional[Any]
|
|
735
|
+
The default dispatch type associated with ``dispatchables``, aka
|
|
736
|
+
":ref:`marking <MarkingGlossary>`".
|
|
737
|
+
|
|
738
|
+
See Also
|
|
739
|
+
--------
|
|
740
|
+
determine_backend: For a single dispatch value
|
|
741
|
+
set_backend: For when you know which backend to set
|
|
742
|
+
|
|
743
|
+
Notes
|
|
744
|
+
-----
|
|
745
|
+
|
|
746
|
+
Support is determined by the ``__ua_convert__`` protocol. Backends not
|
|
747
|
+
supporting the type must return ``NotImplemented`` from their
|
|
748
|
+
``__ua_convert__`` if they don't support input of that type.
|
|
749
|
+
|
|
750
|
+
Examples
|
|
751
|
+
--------
|
|
752
|
+
|
|
753
|
+
:func:`determine_backend` allows the backend to be set from a single
|
|
754
|
+
object. :func:`determine_backend_multi` allows multiple objects to be
|
|
755
|
+
checked simultaneously for support in the backend. Suppose we have a
|
|
756
|
+
``BackendAB`` which supports ``TypeA`` and ``TypeB`` in the same call,
|
|
757
|
+
and a ``BackendBC`` that doesn't support ``TypeA``.
|
|
758
|
+
|
|
759
|
+
>>> with ua.set_backend(ex.BackendAB), ua.set_backend(ex.BackendBC):
|
|
760
|
+
... a, b = ex.TypeA(), ex.TypeB()
|
|
761
|
+
... with ua.determine_backend_multi(
|
|
762
|
+
... [ua.Dispatchable(a, "mark"), ua.Dispatchable(b, "mark")],
|
|
763
|
+
... domain="ua_examples"
|
|
764
|
+
... ):
|
|
765
|
+
... res = ex.creation_multimethod()
|
|
766
|
+
... ex.call_multimethod(res, a, b)
|
|
767
|
+
TypeA
|
|
768
|
+
|
|
769
|
+
This won't call ``BackendBC`` because it doesn't support ``TypeA``.
|
|
770
|
+
|
|
771
|
+
We can also use leave out the ``ua.Dispatchable`` if we specify the
|
|
772
|
+
default ``dispatch_type`` for the ``dispatchables`` argument.
|
|
773
|
+
|
|
774
|
+
>>> with ua.set_backend(ex.BackendAB), ua.set_backend(ex.BackendBC):
|
|
775
|
+
... a, b = ex.TypeA(), ex.TypeB()
|
|
776
|
+
... with ua.determine_backend_multi(
|
|
777
|
+
... [a, b], dispatch_type="mark", domain="ua_examples"
|
|
778
|
+
... ):
|
|
779
|
+
... res = ex.creation_multimethod()
|
|
780
|
+
... ex.call_multimethod(res, a, b)
|
|
781
|
+
TypeA
|
|
782
|
+
|
|
783
|
+
"""
|
|
784
|
+
if "dispatch_type" in kwargs:
|
|
785
|
+
disp_type = kwargs.pop("dispatch_type")
|
|
786
|
+
dispatchables = tuple(
|
|
787
|
+
d if isinstance(d, Dispatchable) else Dispatchable(d, disp_type)
|
|
788
|
+
for d in dispatchables
|
|
789
|
+
)
|
|
790
|
+
else:
|
|
791
|
+
dispatchables = tuple(dispatchables)
|
|
792
|
+
if not all(isinstance(d, Dispatchable) for d in dispatchables):
|
|
793
|
+
raise TypeError("dispatchables must be instances of uarray.Dispatchable")
|
|
794
|
+
|
|
795
|
+
if len(kwargs) != 0:
|
|
796
|
+
raise TypeError("Received unexpected keyword arguments: {}".format(kwargs))
|
|
797
|
+
|
|
798
|
+
backend = _uarray.determine_backend(domain, dispatchables, coerce)
|
|
799
|
+
|
|
800
|
+
return set_backend(backend, coerce=coerce, only=only)
|