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