param 2.3.3b0__tar.gz → 2.4.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.
- {param-2.3.3b0 → param-2.4.0}/PKG-INFO +2 -1
- {param-2.3.3b0 → param-2.4.0}/param/__init__.py +5 -6
- {param-2.3.3b0 → param-2.4.0}/param/_utils.py +91 -67
- {param-2.3.3b0 → param-2.4.0}/param/_version.py +2 -2
- param-2.4.0/param/depends.py +238 -0
- {param-2.3.3b0 → param-2.4.0}/param/display.py +2 -2
- {param-2.3.3b0 → param-2.4.0}/param/ipython.py +29 -17
- {param-2.3.3b0 → param-2.4.0}/param/parameterized.py +945 -530
- {param-2.3.3b0 → param-2.4.0}/param/parameters.py +2032 -617
- param-2.4.0/param/py.typed +0 -0
- {param-2.3.3b0 → param-2.4.0}/param/reactive.py +223 -95
- {param-2.3.3b0 → param-2.4.0}/param/serializer.py +94 -47
- {param-2.3.3b0 → param-2.4.0}/pyproject.toml +54 -2
- param-2.4.0/tests/assert_types.py +426 -0
- param-2.4.0/tests/pyrightconfig-bare.json +1 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testimports.py +1 -7
- {param-2.3.3b0 → param-2.4.0}/tests/testparameterizedobject.py +45 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testpickle.py +2 -1
- {param-2.3.3b0 → param-2.4.0}/tests/testreactive.py +318 -130
- {param-2.3.3b0 → param-2.4.0}/tests/testsignatures.py +29 -11
- {param-2.3.3b0 → param-2.4.0}/tests/utils.py +1 -2
- param-2.3.3b0/param/depends.py +0 -154
- {param-2.3.3b0 → param-2.4.0}/.gitignore +0 -0
- {param-2.3.3b0 → param-2.4.0}/LICENSE.txt +0 -0
- {param-2.3.3b0 → param-2.4.0}/README.md +0 -0
- {param-2.3.3b0 → param-2.4.0}/numbergen/__init__.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/param/version.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/__init__.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/conftest.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testaddparameter.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testbind.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testbooleanparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testbytesparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testcalendardateparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testcalendardaterangeparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testcallable.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testclassselector.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testcolorparameter.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testcomparator.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testcompositeparams.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testcustomparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testdateparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testdaterangeparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testdefaultfactory.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testdefaults.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testdeprecations.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testdynamicparams.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testfiledeserialization.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testfileselector.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testipythonmagic.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testjsonserialization.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testlist.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testlistselector.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testmultifileselector.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testnumbergen.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testnumberparameter.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testnumpy.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testobjectselector.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testpandas.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testparamdepends.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testparameter.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testparameterizedrepr.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testparamoutput.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testparamunion.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testpathparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testrangeparameter.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testrefs.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testreprhtml.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testselector.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/teststringparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testtimedependent.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testtupleparam.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testutils.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testversion.py +0 -0
- {param-2.3.3b0 → param-2.4.0}/tests/testwatch.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: param
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Declarative parameters for robust Python classes and a rich API for reactive programming
|
|
5
5
|
Project-URL: Homepage, https://param.holoviz.org/
|
|
6
6
|
Project-URL: Tracker, https://github.com/holoviz/param/issues
|
|
@@ -25,6 +25,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
25
25
|
Classifier: Programming Language :: Python :: 3.14
|
|
26
26
|
Classifier: Topic :: Scientific/Engineering
|
|
27
27
|
Classifier: Topic :: Software Development :: Libraries
|
|
28
|
+
Classifier: Typing :: Typed
|
|
28
29
|
Requires-Python: >=3.10
|
|
29
30
|
Provides-Extra: all
|
|
30
31
|
Requires-Dist: aiohttp; extra == 'all'
|
|
@@ -112,10 +112,11 @@ from ._utils import (
|
|
|
112
112
|
try:
|
|
113
113
|
# For performance reasons on imports, avoid importing setuptools_scm
|
|
114
114
|
# if not in a .git folder
|
|
115
|
+
__version__ : str
|
|
115
116
|
if os.path.exists(os.path.join(os.path.dirname(__file__), "..", ".git")):
|
|
116
117
|
# If setuptools_scm is installed (e.g. in a development environment with
|
|
117
118
|
# an editable install), then use it to determine the version dynamically.
|
|
118
|
-
from setuptools_scm import get_version
|
|
119
|
+
from setuptools_scm import get_version # type: ignore[unresolved-import,import-untyped,reportMissingImports]
|
|
119
120
|
|
|
120
121
|
# This will fail with LookupError if the package is not installed in
|
|
121
122
|
# editable mode or if Git is not installed.
|
|
@@ -127,7 +128,7 @@ except (ImportError, LookupError, FileNotFoundError):
|
|
|
127
128
|
try:
|
|
128
129
|
# __version__ was added in _version in setuptools-scm 7.0.0, we rely on
|
|
129
130
|
# the hopefully stable version variable.
|
|
130
|
-
from ._version import version as __version__
|
|
131
|
+
from ._version import version as __version__ # type: ignore[unresolved-import,no-redef] # pyright: ignore[reportMissingImports]
|
|
131
132
|
except (ModuleNotFoundError, ImportError):
|
|
132
133
|
# Either _version doesn't exist (ModuleNotFoundError) or version isn't
|
|
133
134
|
# in _version (ImportError). ModuleNotFoundError is a subclass of
|
|
@@ -137,7 +138,7 @@ except (ImportError, LookupError, FileNotFoundError):
|
|
|
137
138
|
from importlib.metadata import version as mversion, PackageNotFoundError
|
|
138
139
|
|
|
139
140
|
try:
|
|
140
|
-
__version__ = mversion("param")
|
|
141
|
+
__version__ = str(mversion("param"))
|
|
141
142
|
except PackageNotFoundError:
|
|
142
143
|
# The user is probably trying to run this without having installed
|
|
143
144
|
# the package.
|
|
@@ -145,8 +146,7 @@ except (ImportError, LookupError, FileNotFoundError):
|
|
|
145
146
|
|
|
146
147
|
#: Top-level object to allow messaging not tied to a particular
|
|
147
148
|
#: Parameterized object, as in 'param.main.warning("Invalid option")'.
|
|
148
|
-
main=Parameterized(name="main")
|
|
149
|
-
|
|
149
|
+
main = Parameterized(name="main")
|
|
150
150
|
|
|
151
151
|
# A global random seed (integer or rational) available for controlling
|
|
152
152
|
# the behaviour of Parameterized objects with random state.
|
|
@@ -227,7 +227,6 @@ __all__ = (
|
|
|
227
227
|
'resolve_path',
|
|
228
228
|
'rx',
|
|
229
229
|
'script_repr',
|
|
230
|
-
'serializer',
|
|
231
230
|
'shared_parameters',
|
|
232
231
|
'version',
|
|
233
232
|
)
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import collections
|
|
4
3
|
import datetime as dt
|
|
5
4
|
import functools
|
|
6
5
|
import inspect
|
|
@@ -8,20 +7,20 @@ import numbers
|
|
|
8
7
|
import os
|
|
9
8
|
import re
|
|
10
9
|
import traceback
|
|
10
|
+
import typing as t
|
|
11
11
|
import warnings
|
|
12
12
|
from collections import OrderedDict, abc, defaultdict
|
|
13
13
|
from contextlib import contextmanager
|
|
14
|
-
from numbers import Real
|
|
15
14
|
from textwrap import dedent
|
|
16
15
|
from threading import get_ident
|
|
17
|
-
from typing import TYPE_CHECKING, Callable, TypeVar
|
|
18
16
|
|
|
19
|
-
if TYPE_CHECKING:
|
|
20
|
-
from
|
|
17
|
+
if t.TYPE_CHECKING:
|
|
18
|
+
from param.parameterized import Parameter
|
|
21
19
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
_P = t.ParamSpec("_P")
|
|
21
|
+
_R = t.TypeVar("_R")
|
|
22
|
+
_T = t.TypeVar("_T")
|
|
23
|
+
_CallableT = t.TypeVar("_CallableT", bound=abc.Callable)
|
|
25
24
|
|
|
26
25
|
DEFAULT_SIGNATURE = inspect.Signature([
|
|
27
26
|
inspect.Parameter('self', inspect.Parameter.POSITIONAL_OR_KEYWORD),
|
|
@@ -99,8 +98,8 @@ class Skip(Exception):
|
|
|
99
98
|
"""
|
|
100
99
|
|
|
101
100
|
|
|
102
|
-
def _deprecated(extra_msg="", warning_cat=ParamDeprecationWarning):
|
|
103
|
-
def decorator(func):
|
|
101
|
+
def _deprecated(extra_msg: str = "", warning_cat: type[Warning] = ParamDeprecationWarning):
|
|
102
|
+
def decorator(func: abc.Callable[..., t.Any]) -> abc.Callable[..., t.Any]:
|
|
104
103
|
"""Mark a function or method as deprecated.
|
|
105
104
|
|
|
106
105
|
This internal decorator issues a warning when the decorated function
|
|
@@ -119,7 +118,8 @@ def _deprecated(extra_msg="", warning_cat=ParamDeprecationWarning):
|
|
|
119
118
|
"""
|
|
120
119
|
@functools.wraps(func)
|
|
121
120
|
def inner(*args, **kwargs):
|
|
122
|
-
|
|
121
|
+
func_name = getattr(func, "__name__", repr(func))
|
|
122
|
+
msg = f"{func_name!r} has been deprecated and will be removed in a future version."
|
|
123
123
|
if extra_msg:
|
|
124
124
|
em = dedent(extra_msg)
|
|
125
125
|
em = em.strip().replace('\n', ' ')
|
|
@@ -152,8 +152,9 @@ def _recursive_repr(fillvalue='...'):
|
|
|
152
152
|
return decorating_function
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
def _is_auto_name(class_name, instance_name):
|
|
156
|
-
|
|
155
|
+
def _is_auto_name(class_name: str, instance_name: str) -> bool:
|
|
156
|
+
pattern = rf'^{re.escape(class_name)}[0-9]{{5}}$'
|
|
157
|
+
return bool(re.match(pattern, instance_name))
|
|
157
158
|
|
|
158
159
|
|
|
159
160
|
def _find_pname(pclass):
|
|
@@ -163,12 +164,14 @@ def _find_pname(pclass):
|
|
|
163
164
|
"""
|
|
164
165
|
stack = traceback.extract_stack()
|
|
165
166
|
for frame in stack:
|
|
166
|
-
|
|
167
|
+
if frame.line is None:
|
|
168
|
+
continue
|
|
169
|
+
match = re.match(rf"^(\S+)\s*=\s*(param|pm)\.{pclass}\(", frame.line)
|
|
167
170
|
if match:
|
|
168
171
|
return match.group(1)
|
|
169
172
|
|
|
170
173
|
|
|
171
|
-
def _validate_error_prefix(parameter, attribute=None):
|
|
174
|
+
def _validate_error_prefix(parameter: Parameter, attribute: str | None = None) -> str:
|
|
172
175
|
"""
|
|
173
176
|
Generate an error prefix suitable for Parameters when they raise a validation
|
|
174
177
|
error.
|
|
@@ -279,11 +282,11 @@ def flatten(line):
|
|
|
279
282
|
|
|
280
283
|
|
|
281
284
|
def accept_arguments(
|
|
282
|
-
f: Callable[Concatenate[
|
|
283
|
-
) -> Callable[
|
|
285
|
+
f: abc.Callable[t.Concatenate[_CallableT, _P], _R]
|
|
286
|
+
) -> abc.Callable[..., abc.Callable[[_CallableT], _R]]:
|
|
284
287
|
"""Decorate a decorator to accept arguments."""
|
|
285
288
|
@functools.wraps(f)
|
|
286
|
-
def _f(*args:
|
|
289
|
+
def _f(*args: _P.args, **kwargs: _P.kwargs) -> abc.Callable[[_CallableT], _R]:
|
|
287
290
|
return lambda actual_f: f(actual_f, *args, **kwargs)
|
|
288
291
|
return _f
|
|
289
292
|
|
|
@@ -308,10 +311,10 @@ def _hashable(x):
|
|
|
308
311
|
part of the object has changed. Does not (currently) recursively
|
|
309
312
|
replace mutable subobjects.
|
|
310
313
|
"""
|
|
311
|
-
if isinstance(x,
|
|
314
|
+
if isinstance(x, abc.MutableSequence):
|
|
312
315
|
return tuple(x)
|
|
313
|
-
elif isinstance(x,
|
|
314
|
-
return tuple([(k,v) for k,v in x.items()])
|
|
316
|
+
elif isinstance(x, abc.MutableMapping):
|
|
317
|
+
return tuple([(k, v) for k, v in x.items()])
|
|
315
318
|
else:
|
|
316
319
|
return x
|
|
317
320
|
|
|
@@ -351,39 +354,52 @@ def _named_objs(objlist, namesdict=None):
|
|
|
351
354
|
|
|
352
355
|
def _get_min_max_value(min, max, value=None, step=None):
|
|
353
356
|
"""Return min, max, value given input values with possible None."""
|
|
354
|
-
|
|
357
|
+
fmin = float(min) if min is not None else None
|
|
358
|
+
fmax = float(max) if max is not None else None
|
|
359
|
+
|
|
355
360
|
if value is None:
|
|
356
|
-
if
|
|
357
|
-
raise ValueError(
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
361
|
+
if fmin is None or fmax is None:
|
|
362
|
+
raise ValueError(f"unable to infer range, value from: ({min}, {max}, {value})")
|
|
363
|
+
fvalue = (fmin + fmax) / 2.0
|
|
364
|
+
else:
|
|
365
|
+
fvalue = float(value)
|
|
366
|
+
if fmin is None or fmax is None:
|
|
367
|
+
if fvalue == 0.0:
|
|
368
|
+
low, high = 0.0, 1.0
|
|
369
|
+
elif fvalue > 0.0:
|
|
370
|
+
low, high = -fvalue, 3.0 * fvalue
|
|
371
|
+
else:
|
|
372
|
+
low, high = 3.0 * fvalue, -fvalue
|
|
373
|
+
if fmin is None:
|
|
374
|
+
fmin = low
|
|
375
|
+
if fmax is None:
|
|
376
|
+
fmax = high
|
|
377
|
+
|
|
378
|
+
# Safety: ensure bounds exist
|
|
379
|
+
if fmin is None or fmax is None:
|
|
380
|
+
raise RuntimeError("internal error: bounds not resolved")
|
|
381
|
+
|
|
382
|
+
# Normalize so fmin <= fmax
|
|
383
|
+
if fmin > fmax:
|
|
384
|
+
fmin, fmax = fmax, fmin
|
|
385
|
+
|
|
386
|
+
# Snap to step if requested
|
|
380
387
|
if step is not None:
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
388
|
+
fstep = abs(float(step))
|
|
389
|
+
if fstep == 0.0:
|
|
390
|
+
raise ValueError("step must be non-zero")
|
|
391
|
+
ticks = round((fvalue - fmin) / fstep) # nearest tick; use math.floor for always-down
|
|
392
|
+
fvalue = fmin + ticks * fstep
|
|
393
|
+
# Clamp after snapping
|
|
394
|
+
if fvalue < fmin:
|
|
395
|
+
fvalue = fmin
|
|
396
|
+
if fvalue > fmax:
|
|
397
|
+
fvalue = fmax
|
|
398
|
+
|
|
399
|
+
if not (fmin <= fvalue <= fmax):
|
|
400
|
+
raise ValueError(f"value must be between min and max (min={fmin}, value={fvalue}, max={fmax})")
|
|
401
|
+
|
|
402
|
+
return fmin, fmax, fvalue
|
|
387
403
|
|
|
388
404
|
|
|
389
405
|
def _deserialize_from_path(ext_to_routine, path, type_name):
|
|
@@ -425,13 +441,10 @@ def _is_number(obj):
|
|
|
425
441
|
def _is_abstract(class_: type) -> bool:
|
|
426
442
|
if inspect.isabstract(class_):
|
|
427
443
|
return True
|
|
428
|
-
|
|
429
|
-
return class_.abstract
|
|
430
|
-
except AttributeError:
|
|
431
|
-
return False
|
|
444
|
+
return bool(getattr(class_, "abstract", False))
|
|
432
445
|
|
|
433
446
|
|
|
434
|
-
def descendents(class_: type, concrete: bool = False) -> list[type]:
|
|
447
|
+
def descendents(class_: type[_T], concrete: bool = False) -> list[type[_T]]:
|
|
435
448
|
"""
|
|
436
449
|
Return a list of all descendent classes of a given class.
|
|
437
450
|
|
|
@@ -470,7 +483,7 @@ def descendents(class_: type, concrete: bool = False) -> list[type]:
|
|
|
470
483
|
if not isinstance(class_, type):
|
|
471
484
|
raise TypeError(f"descendents expected a class object, not {type(class_).__name__}")
|
|
472
485
|
q = [class_]
|
|
473
|
-
out = []
|
|
486
|
+
out: list[type[_T]] = []
|
|
474
487
|
while len(q):
|
|
475
488
|
x = q.pop(0)
|
|
476
489
|
out.insert(0, x)
|
|
@@ -487,7 +500,7 @@ def descendents(class_: type, concrete: bool = False) -> list[type]:
|
|
|
487
500
|
|
|
488
501
|
|
|
489
502
|
# Could be a method of ClassSelector.
|
|
490
|
-
def concrete_descendents(parentclass: type) -> dict[str, type]:
|
|
503
|
+
def concrete_descendents(parentclass: type[_T]) -> dict[str, type[_T]]:
|
|
491
504
|
"""
|
|
492
505
|
Return a dictionary containing all subclasses of the specified
|
|
493
506
|
parentclass, including the parentclass (prefer :func:`descendents`).
|
|
@@ -580,12 +593,13 @@ def exceptions_summarized():
|
|
|
580
593
|
except Exception:
|
|
581
594
|
import sys
|
|
582
595
|
etype, value, tb = sys.exc_info()
|
|
583
|
-
|
|
596
|
+
if etype is not None:
|
|
597
|
+
print(f"{etype.__name__}: {value}", file=sys.stderr)
|
|
584
598
|
|
|
585
599
|
|
|
586
600
|
def _in_ipython():
|
|
587
601
|
try:
|
|
588
|
-
get_ipython
|
|
602
|
+
get_ipython() # type: ignore[name-defined,ty:unresolved-reference] # pyright: ignore[reportUndefinedVariable]
|
|
589
603
|
return True
|
|
590
604
|
except NameError:
|
|
591
605
|
return False
|
|
@@ -605,21 +619,27 @@ def async_executor(func):
|
|
|
605
619
|
else:
|
|
606
620
|
event_loop.run_until_complete(func())
|
|
607
621
|
|
|
622
|
+
@t.runtime_checkable
|
|
623
|
+
class _HasTypes(t.Protocol):
|
|
624
|
+
@classmethod
|
|
625
|
+
def types(cls) -> abc.Iterable[type]: ...
|
|
626
|
+
|
|
608
627
|
class _GeneratorIsMeta(type):
|
|
609
|
-
def __instancecheck__(cls, inst):
|
|
628
|
+
def __instancecheck__(cls: type[_HasTypes], inst):
|
|
610
629
|
return isinstance(inst, tuple(cls.types()))
|
|
611
630
|
|
|
612
|
-
def __subclasscheck__(cls, sub):
|
|
631
|
+
def __subclasscheck__(cls: type[_HasTypes], sub: type) -> bool:
|
|
613
632
|
return issubclass(sub, tuple(cls.types()))
|
|
614
633
|
|
|
615
|
-
def __iter__(cls):
|
|
634
|
+
def __iter__(cls: type[_HasTypes]) -> abc.Iterator[type]:
|
|
616
635
|
yield from cls.types()
|
|
617
636
|
|
|
618
637
|
class _GeneratorIs(metaclass=_GeneratorIsMeta):
|
|
619
638
|
@classmethod
|
|
620
|
-
def __iter__(cls):
|
|
639
|
+
def __iter__(cls: type[_HasTypes]) -> abc.Iterator[type]:
|
|
621
640
|
yield from cls.types()
|
|
622
641
|
|
|
642
|
+
|
|
623
643
|
def gen_types(gen_func):
|
|
624
644
|
"""Decorate a generator function to support type checking.
|
|
625
645
|
|
|
@@ -656,8 +676,12 @@ def _find_stack_level() -> int:
|
|
|
656
676
|
import numbergen
|
|
657
677
|
import param
|
|
658
678
|
|
|
659
|
-
|
|
660
|
-
|
|
679
|
+
numbergen_file = getattr(numbergen, "__file__", None)
|
|
680
|
+
param_file = getattr(param, "__file__", None)
|
|
681
|
+
if numbergen_file is None or param_file is None:
|
|
682
|
+
return 2
|
|
683
|
+
ng_dir = os.path.dirname(numbergen_file)
|
|
684
|
+
param_dir = os.path.dirname(param_file)
|
|
661
685
|
|
|
662
686
|
# https://stackoverflow.com/questions/17407119/python-inspect-stack-is-slow
|
|
663
687
|
frame = inspect.currentframe()
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '2.
|
|
22
|
-
__version_tuple__ = version_tuple = (2,
|
|
21
|
+
__version__ = version = '2.4.0'
|
|
22
|
+
__version_tuple__ = version_tuple = (2, 4, 0)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import typing as t
|
|
5
|
+
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from functools import wraps
|
|
8
|
+
|
|
9
|
+
from .parameterized import (
|
|
10
|
+
Event, Parameter, Parameterized, ParameterizedMetaclass, transform_reference,
|
|
11
|
+
)
|
|
12
|
+
from ._utils import iscoroutinefunction
|
|
13
|
+
|
|
14
|
+
if t.TYPE_CHECKING:
|
|
15
|
+
from collections.abc import AsyncGenerator, Callable, Generator
|
|
16
|
+
|
|
17
|
+
_Y = t.TypeVar("_Y")
|
|
18
|
+
_T = t.TypeVar("_T")
|
|
19
|
+
|
|
20
|
+
_P = t.ParamSpec("_P")
|
|
21
|
+
_FullP = t.ParamSpec("_FullP")
|
|
22
|
+
_R = t.TypeVar("_R", covariant=True)
|
|
23
|
+
_S = t.TypeVar("_S")
|
|
24
|
+
Dependency = Parameter | str
|
|
25
|
+
|
|
26
|
+
class DependencyInfo(t.TypedDict):
|
|
27
|
+
dependencies: tuple[Dependency, ...]
|
|
28
|
+
kw: dict[str, Dependency]
|
|
29
|
+
watch: bool
|
|
30
|
+
on_init: bool
|
|
31
|
+
|
|
32
|
+
class _DepsFn(t.Protocol[_FullP, _R]):
|
|
33
|
+
_dinfo: DependencyInfo
|
|
34
|
+
def __call__(self, *args: _FullP.args, **kwargs: _FullP.kwargs) -> _R: ...
|
|
35
|
+
|
|
36
|
+
class DependsFunc(t.Protocol[_P, _R]):
|
|
37
|
+
_dinfo: DependencyInfo
|
|
38
|
+
def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R: ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@t.overload
|
|
42
|
+
def depends(
|
|
43
|
+
func: Callable[t.Concatenate[_S, _P], _R], /, *dependencies: Dependency, watch: bool = False, on_init: bool = False, **kw: Dependency
|
|
44
|
+
) -> DependsFunc[_P, _R]:
|
|
45
|
+
...
|
|
46
|
+
|
|
47
|
+
@t.overload
|
|
48
|
+
def depends(
|
|
49
|
+
*dependencies: str, watch: bool = False, on_init: bool = False
|
|
50
|
+
) -> Callable[[Callable[t.Concatenate[_S, _P], _R]], DependsFunc[_P, _R]]:
|
|
51
|
+
...
|
|
52
|
+
|
|
53
|
+
@t.overload
|
|
54
|
+
def depends(
|
|
55
|
+
*dependencies: Parameter, watch: bool = False, on_init: bool = False, **kw: Parameter
|
|
56
|
+
) -> Callable[[Callable[t.Concatenate[_S, _P], _R]], DependsFunc[_P, _R]]:
|
|
57
|
+
...
|
|
58
|
+
|
|
59
|
+
def depends(
|
|
60
|
+
*dependencies: Dependency | Callable[t.Concatenate[_S, _P], _R], watch: bool = False, on_init: bool = False, **kw: Dependency
|
|
61
|
+
) -> DependsFunc[_P, _R] | Callable[[Callable[t.Concatenate[_S, _P], _R]], DependsFunc[_P, _R]]:
|
|
62
|
+
"""
|
|
63
|
+
Annotates a function or :class:`Parameterized` method to express its dependencies.
|
|
64
|
+
|
|
65
|
+
The specified dependencies can be either be :class:`Parameter` instances or if a
|
|
66
|
+
method is supplied they can be defined as strings referring to Parameters
|
|
67
|
+
of the class, or Parameters of subobjects (Parameterized objects that are
|
|
68
|
+
values of this object's parameters). Dependencies can either be on
|
|
69
|
+
Parameter values, or on other metadata about the Parameter.
|
|
70
|
+
|
|
71
|
+
Parameters
|
|
72
|
+
----------
|
|
73
|
+
watch : bool, optional
|
|
74
|
+
Whether to invoke the function/method when the dependency is updated,
|
|
75
|
+
by default ``False``.
|
|
76
|
+
on_init : bool, optional
|
|
77
|
+
Whether to invoke the function/method when the instance is created,
|
|
78
|
+
by default ``False``.
|
|
79
|
+
|
|
80
|
+
"""
|
|
81
|
+
if dependencies and callable(dependencies[0]) and not isinstance(dependencies[0], (str, Parameter)):
|
|
82
|
+
func = t.cast("Callable[t.Concatenate[_S, _P], _R]", dependencies[0])
|
|
83
|
+
deps = t.cast("tuple[Dependency, ...]", dependencies[1:])
|
|
84
|
+
return t.cast("DependsFunc[_P, _R]", _depends_impl(func, *deps, watch=watch, on_init=on_init, **kw))
|
|
85
|
+
|
|
86
|
+
deps = t.cast("tuple[Dependency, ...]", dependencies)
|
|
87
|
+
|
|
88
|
+
def _decorator(func: Callable[t.Concatenate[_S, _P], _R]) -> DependsFunc[_P, _R]:
|
|
89
|
+
return t.cast("DependsFunc[_P, _R]", _depends_impl(func, *deps, watch=watch, on_init=on_init, **kw))
|
|
90
|
+
|
|
91
|
+
return _decorator
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _depends_impl(
|
|
95
|
+
func: Callable[_FullP, _R], /, *dependencies: Dependency, watch: bool = False, on_init: bool = False, **kw: Dependency
|
|
96
|
+
) -> _DepsFn[_FullP, _R]:
|
|
97
|
+
dependencies, kw = (
|
|
98
|
+
tuple(transform_reference(arg) for arg in dependencies),
|
|
99
|
+
{key: transform_reference(arg) for key, arg in kw.items()}
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if inspect.isgeneratorfunction(func):
|
|
103
|
+
func_gen = t.cast("Callable[_FullP, Generator[t.Any, t.Any, t.Any]]", func)
|
|
104
|
+
@wraps(func)
|
|
105
|
+
def _depends_gen(*args, **kw):
|
|
106
|
+
for val in func_gen(*args, **kw):
|
|
107
|
+
yield val
|
|
108
|
+
_depends = t.cast("Callable[_FullP, _R]", _depends_gen)
|
|
109
|
+
elif inspect.isasyncgenfunction(func):
|
|
110
|
+
func_agen = t.cast("Callable[_FullP, AsyncGenerator[t.Any, t.Any]]", func)
|
|
111
|
+
@wraps(func)
|
|
112
|
+
async def _depends_async_gen(*args, **kw):
|
|
113
|
+
async for val in func_agen(*args, **kw):
|
|
114
|
+
yield val
|
|
115
|
+
_depends = t.cast("Callable[_FullP, _R]", _depends_async_gen)
|
|
116
|
+
elif iscoroutinefunction(func):
|
|
117
|
+
F = t.cast("Callable[_FullP, t.Awaitable[_R]]", func)
|
|
118
|
+
@wraps(func)
|
|
119
|
+
async def _depends_coro(*args, **kw):
|
|
120
|
+
return await F(*args, **kw)
|
|
121
|
+
_depends = t.cast("Callable[_FullP, _R]", _depends_coro)
|
|
122
|
+
else:
|
|
123
|
+
@wraps(func)
|
|
124
|
+
def _depends_sync(*args, **kw):
|
|
125
|
+
return func(*args, **kw)
|
|
126
|
+
_depends = t.cast("Callable[_FullP, _R]", _depends_sync)
|
|
127
|
+
|
|
128
|
+
deps = list(dependencies)+list(kw.values())
|
|
129
|
+
string_specs = False
|
|
130
|
+
for dep in deps:
|
|
131
|
+
if isinstance(dep, str):
|
|
132
|
+
string_specs = True
|
|
133
|
+
elif hasattr(dep, '_dinfo'):
|
|
134
|
+
pass
|
|
135
|
+
elif not isinstance(dep, Parameter):
|
|
136
|
+
raise ValueError('The depends decorator only accepts string '
|
|
137
|
+
'types referencing a parameter or parameter '
|
|
138
|
+
'instances, found %s type instead.' %
|
|
139
|
+
type(dep).__name__)
|
|
140
|
+
elif not (isinstance(dep.owner, Parameterized) or
|
|
141
|
+
(isinstance(dep.owner, ParameterizedMetaclass))):
|
|
142
|
+
owner = 'None' if dep.owner is None else '%s class' % type(dep.owner).__name__
|
|
143
|
+
raise ValueError('Parameters supplied to the depends decorator, '
|
|
144
|
+
'must be bound to a Parameterized class or '
|
|
145
|
+
'instance, not %s.' % owner)
|
|
146
|
+
|
|
147
|
+
if (any(isinstance(dep, Parameter) for dep in deps) and
|
|
148
|
+
any(isinstance(dep, str) for dep in deps)):
|
|
149
|
+
raise ValueError('Dependencies must either be defined as strings '
|
|
150
|
+
'referencing parameters on the class defining '
|
|
151
|
+
'the decorated method or as parameter instances. '
|
|
152
|
+
'Mixing of string specs and parameter instances '
|
|
153
|
+
'is not supported.')
|
|
154
|
+
elif string_specs and kw:
|
|
155
|
+
raise AssertionError('Supplying keywords to the decorated method '
|
|
156
|
+
'or function is not supported when referencing '
|
|
157
|
+
'parameters by name.')
|
|
158
|
+
|
|
159
|
+
_dinfo = t.cast("DependencyInfo", dict(getattr(func, '_dinfo', {})))
|
|
160
|
+
_dinfo.update({'dependencies': dependencies,
|
|
161
|
+
'kw': kw, 'watch': watch, 'on_init': on_init})
|
|
162
|
+
|
|
163
|
+
typed_depends = t.cast("_DepsFn[_FullP, _R]", _depends)
|
|
164
|
+
typed_depends._dinfo = _dinfo
|
|
165
|
+
|
|
166
|
+
if string_specs or not watch:
|
|
167
|
+
# string_specs case handled elsewhere (later), in Parameterized.__init__
|
|
168
|
+
return typed_depends
|
|
169
|
+
param_args = [dep for dep in dependencies if isinstance(dep, Parameter)]
|
|
170
|
+
param_kwargs = {n: dep for n, dep in kw.items() if isinstance(dep, Parameter)}
|
|
171
|
+
param_deps = list(param_args) + list(param_kwargs.values())
|
|
172
|
+
|
|
173
|
+
def _dep_owner_name(dep: Parameter) -> tuple[Parameterized, str] | None:
|
|
174
|
+
owner = dep.owner
|
|
175
|
+
name = dep.name
|
|
176
|
+
if owner is None or name is None:
|
|
177
|
+
return None
|
|
178
|
+
return owner, name
|
|
179
|
+
|
|
180
|
+
def _resolve_args() -> tuple[t.Any, ...]:
|
|
181
|
+
args: list[t.Any] = []
|
|
182
|
+
for dep in param_args:
|
|
183
|
+
owner_name = _dep_owner_name(dep)
|
|
184
|
+
if owner_name is None:
|
|
185
|
+
continue
|
|
186
|
+
owner, name = owner_name
|
|
187
|
+
args.append(getattr(owner, name))
|
|
188
|
+
return tuple(args)
|
|
189
|
+
|
|
190
|
+
def _resolve_kwargs() -> dict[str, t.Any]:
|
|
191
|
+
dep_kwargs: dict[str, t.Any] = {}
|
|
192
|
+
for key, dep in param_kwargs.items():
|
|
193
|
+
owner_name = _dep_owner_name(dep)
|
|
194
|
+
if owner_name is None:
|
|
195
|
+
continue
|
|
196
|
+
owner, name = owner_name
|
|
197
|
+
dep_kwargs[key] = getattr(owner, name)
|
|
198
|
+
return dep_kwargs
|
|
199
|
+
|
|
200
|
+
if inspect.isgeneratorfunction(func):
|
|
201
|
+
def cb_gen(*events: Event):
|
|
202
|
+
args = _resolve_args()
|
|
203
|
+
dep_kwargs = _resolve_kwargs()
|
|
204
|
+
func_gen = t.cast("Callable[_FullP, Generator[t.Any, t.Any, t.Any]]", func)
|
|
205
|
+
for val in func_gen(*args, **dep_kwargs):
|
|
206
|
+
yield val
|
|
207
|
+
cb = cb_gen
|
|
208
|
+
elif inspect.isasyncgenfunction(func):
|
|
209
|
+
async def cb_async_gen(*events: Event):
|
|
210
|
+
args = _resolve_args()
|
|
211
|
+
dep_kwargs = _resolve_kwargs()
|
|
212
|
+
func_agen = t.cast("Callable[_FullP, AsyncGenerator[t.Any, t.Any]]", func)
|
|
213
|
+
async for val in func_agen(*args, **dep_kwargs):
|
|
214
|
+
yield val
|
|
215
|
+
cb = cb_async_gen
|
|
216
|
+
elif iscoroutinefunction(func):
|
|
217
|
+
async def cb_coro(*events: Event):
|
|
218
|
+
args = _resolve_args()
|
|
219
|
+
dep_kwargs = _resolve_kwargs()
|
|
220
|
+
func_coro = t.cast("Callable[_FullP, t.Awaitable[t.Any]]", func)
|
|
221
|
+
await func_coro(*args, **dep_kwargs)
|
|
222
|
+
cb = cb_coro
|
|
223
|
+
else:
|
|
224
|
+
def cb_sync(*events: Event):
|
|
225
|
+
args = _resolve_args()
|
|
226
|
+
dep_kwargs = _resolve_kwargs()
|
|
227
|
+
return func(*args, **dep_kwargs)
|
|
228
|
+
cb = cb_sync
|
|
229
|
+
|
|
230
|
+
grouped = defaultdict(list)
|
|
231
|
+
for dep in param_deps:
|
|
232
|
+
grouped[id(dep.owner)].append(dep)
|
|
233
|
+
for group in grouped.values():
|
|
234
|
+
if group[0].owner is None:
|
|
235
|
+
continue
|
|
236
|
+
group[0].owner.param.watch(cb, [dep.name for dep in group])
|
|
237
|
+
|
|
238
|
+
return typed_depends
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import weakref
|
|
2
2
|
|
|
3
|
-
_display_accessors = {}
|
|
4
|
-
_reactive_display_objs = weakref.WeakSet()
|
|
3
|
+
_display_accessors: dict[str, object] = {}
|
|
4
|
+
_reactive_display_objs: weakref.WeakSet[object] = weakref.WeakSet()
|
|
5
5
|
|
|
6
6
|
def register_display_accessor(name, accessor, force=False):
|
|
7
7
|
if name in _display_accessors and not force:
|