param 2.0.2rc1__tar.gz → 2.1.0a2__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.0.2rc1 → param-2.1.0a2}/PKG-INFO +3 -3
- {param-2.0.2rc1 → param-2.1.0a2}/README.md +1 -1
- {param-2.0.2rc1 → param-2.1.0a2}/param/__init__.py +3 -2
- {param-2.0.2rc1 → param-2.1.0a2}/param/_utils.py +51 -1
- {param-2.0.2rc1 → param-2.1.0a2}/param/_version.py +2 -2
- {param-2.0.2rc1 → param-2.1.0a2}/param/depends.py +26 -2
- {param-2.0.2rc1 → param-2.1.0a2}/param/ipython.py +28 -5
- {param-2.0.2rc1 → param-2.1.0a2}/param/parameterized.py +73 -40
- {param-2.0.2rc1 → param-2.1.0a2}/param/parameters.py +8 -7
- {param-2.0.2rc1 → param-2.1.0a2}/param/reactive.py +142 -43
- {param-2.0.2rc1 → param-2.1.0a2}/pyproject.toml +1 -1
- param-2.1.0a2/tests/conftest.py +13 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testfiledeserialization.py +2 -2
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testparamdepends.py +126 -55
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testparameterizedobject.py +13 -19
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testreactive.py +183 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testrefs.py +138 -7
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testwatch.py +14 -1
- param-2.0.2rc1/tests/conftest.py +0 -30
- {param-2.0.2rc1 → param-2.1.0a2}/.gitignore +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/LICENSE.txt +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/numbergen/__init__.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/param/display.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/param/serializer.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/param/version.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/__init__.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testaddparameter.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testbind.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testbooleanparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testbytesparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testcalendardateparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testcalendardaterangeparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testcallable.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testclassselector.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testcolorparameter.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testcomparator.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testcompositeparams.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testcustomparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testdateparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testdaterangeparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testdefaults.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testdeprecations.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testdynamicparams.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testfileselector.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testipythonmagic.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testjsonserialization.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testlist.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testlistselector.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testmultifileselector.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testnumbergen.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testnumberparameter.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testnumpy.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testobjectselector.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testpandas.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testparameterizedrepr.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testparamoutput.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testparamunion.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testpathparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testpickle.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testrangeparameter.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testreprhtml.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testselector.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testsignatures.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/teststringparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testtimedependent.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testtupleparam.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testutils.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/testversion.py +0 -0
- {param-2.0.2rc1 → param-2.1.0a2}/tests/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: param
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0a2
|
|
4
4
|
Summary: Make your Python code clearer and more reliable by declaring Parameters.
|
|
5
5
|
Project-URL: Homepage, https://param.holoviz.org/
|
|
6
6
|
Project-URL: Tracker, https://github.com/holoviz/param/issues
|
|
@@ -54,9 +54,9 @@ Requires-Dist: xlrd; extra == 'tests-deser'
|
|
|
54
54
|
Provides-Extra: tests-examples
|
|
55
55
|
Requires-Dist: nbval; extra == 'tests-examples'
|
|
56
56
|
Requires-Dist: param[examples]; extra == 'tests-examples'
|
|
57
|
-
Requires-Dist: pytest; extra == 'tests-examples'
|
|
58
57
|
Requires-Dist: pytest-asyncio; extra == 'tests-examples'
|
|
59
58
|
Requires-Dist: pytest-xdist; extra == 'tests-examples'
|
|
59
|
+
Requires-Dist: pytest<8.1; extra == 'tests-examples'
|
|
60
60
|
Provides-Extra: tests-full
|
|
61
61
|
Requires-Dist: cloudpickle; extra == 'tests-full'
|
|
62
62
|
Requires-Dist: gmpy; extra == 'tests-full'
|
|
@@ -74,7 +74,7 @@ Description-Content-Type: text/markdown
|
|
|
74
74
|
|
|
75
75
|
| | |
|
|
76
76
|
| --- | --- |
|
|
77
|
-
| Build Status | [](https://github.com/holoviz/param/actions/workflows/test.yaml)
|
|
78
78
|
| Coverage | [](https://codecov.io/gh/holoviz/param) ||
|
|
79
79
|
| Latest dev release | [](https://github.com/holoviz/param/tags) [](https://holoviz-dev.github.io/param/) |
|
|
80
80
|
| Latest release | [](https://github.com/holoviz/param/releases) [](https://pypi.python.org/pypi/param) [](https://anaconda.org/pyviz/param) [](https://anaconda.org/conda-forge/param) [](https://anaconda.org/anaconda/param) |
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
| | |
|
|
4
4
|
| --- | --- |
|
|
5
|
-
| Build Status | [](https://github.com/holoviz/param/actions/workflows/test.yaml)
|
|
6
6
|
| Coverage | [](https://codecov.io/gh/holoviz/param) ||
|
|
7
7
|
| Latest dev release | [](https://github.com/holoviz/param/tags) [](https://holoviz-dev.github.io/param/) |
|
|
8
8
|
| Latest release | [](https://github.com/holoviz/param/releases) [](https://pypi.python.org/pypi/param) [](https://anaconda.org/pyviz/param) [](https://anaconda.org/conda-forge/param) [](https://anaconda.org/anaconda/param) |
|
|
@@ -3,8 +3,8 @@ import os
|
|
|
3
3
|
from . import version # noqa: api import
|
|
4
4
|
from .depends import depends # noqa: api import
|
|
5
5
|
from .parameterized import ( # noqa: api import
|
|
6
|
-
Parameterized, Parameter, String, ParameterizedFunction,
|
|
7
|
-
Undefined, get_logger
|
|
6
|
+
Parameterized, Parameter, Skip, String, ParameterizedFunction,
|
|
7
|
+
ParamOverrides, Undefined, get_logger
|
|
8
8
|
)
|
|
9
9
|
from .parameterized import (batch_watch, output, script_repr, # noqa: api import
|
|
10
10
|
discard_events, edit_constant)
|
|
@@ -161,6 +161,7 @@ __all__ = (
|
|
|
161
161
|
'Selector',
|
|
162
162
|
'SelectorBase',
|
|
163
163
|
'Series',
|
|
164
|
+
'Skip',
|
|
164
165
|
'String',
|
|
165
166
|
'Time',
|
|
166
167
|
'Tuple',
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import collections
|
|
3
|
+
import contextvars
|
|
2
4
|
import datetime as dt
|
|
3
5
|
import inspect
|
|
4
6
|
import functools
|
|
@@ -21,6 +23,8 @@ DEFAULT_SIGNATURE = inspect.Signature([
|
|
|
21
23
|
inspect.Parameter('params', inspect.Parameter.VAR_KEYWORD),
|
|
22
24
|
])
|
|
23
25
|
|
|
26
|
+
MUTABLE_TYPES = (abc.MutableSequence, abc.MutableSet, abc.MutableMapping)
|
|
27
|
+
|
|
24
28
|
class ParamWarning(Warning):
|
|
25
29
|
"""Base Param Warning"""
|
|
26
30
|
|
|
@@ -46,6 +50,10 @@ class ParamFutureWarning(ParamWarning, FutureWarning):
|
|
|
46
50
|
Always displayed.
|
|
47
51
|
"""
|
|
48
52
|
|
|
53
|
+
class Skip(Exception):
|
|
54
|
+
"""
|
|
55
|
+
Exception that allows skipping an update when resolving a reference.
|
|
56
|
+
"""
|
|
49
57
|
|
|
50
58
|
def _deprecated(extra_msg="", warning_cat=ParamDeprecationWarning):
|
|
51
59
|
def decorator(func):
|
|
@@ -184,7 +192,7 @@ def _validate_error_prefix(parameter, attribute=None):
|
|
|
184
192
|
|
|
185
193
|
def _is_mutable_container(value):
|
|
186
194
|
"""True for mutable containers, which typically need special handling when being copied"""
|
|
187
|
-
return issubclass(type(value),
|
|
195
|
+
return issubclass(type(value), MUTABLE_TYPES)
|
|
188
196
|
|
|
189
197
|
|
|
190
198
|
def _dict_update(dictionary, **kwargs):
|
|
@@ -219,6 +227,34 @@ def iscoroutinefunction(function):
|
|
|
219
227
|
inspect.iscoroutinefunction(function)
|
|
220
228
|
)
|
|
221
229
|
|
|
230
|
+
async def _to_thread(func, /, *args, **kwargs):
|
|
231
|
+
"""
|
|
232
|
+
Polyfill for asyncio.to_thread in Python < 3.9
|
|
233
|
+
"""
|
|
234
|
+
loop = asyncio.get_running_loop()
|
|
235
|
+
ctx = contextvars.copy_context()
|
|
236
|
+
func_call = functools.partial(ctx.run, func, *args, **kwargs)
|
|
237
|
+
return await loop.run_in_executor(None, func_call)
|
|
238
|
+
|
|
239
|
+
async def _to_async_gen(sync_gen):
|
|
240
|
+
done = object()
|
|
241
|
+
|
|
242
|
+
def safe_next():
|
|
243
|
+
# Converts StopIteration to a sentinel value to avoid:
|
|
244
|
+
# TypeError: StopIteration interacts badly with generators and cannot be raised into a Future
|
|
245
|
+
try:
|
|
246
|
+
return next(sync_gen)
|
|
247
|
+
except StopIteration:
|
|
248
|
+
return done
|
|
249
|
+
|
|
250
|
+
while True:
|
|
251
|
+
if sys.version_info >= (3, 9):
|
|
252
|
+
value = await asyncio.to_thread(safe_next)
|
|
253
|
+
else:
|
|
254
|
+
value = await _to_thread(safe_next)
|
|
255
|
+
if value is done:
|
|
256
|
+
break
|
|
257
|
+
yield value
|
|
222
258
|
|
|
223
259
|
def flatten(line):
|
|
224
260
|
"""
|
|
@@ -552,3 +588,17 @@ def _in_ipython():
|
|
|
552
588
|
return True
|
|
553
589
|
except NameError:
|
|
554
590
|
return False
|
|
591
|
+
|
|
592
|
+
_running_tasks = set()
|
|
593
|
+
|
|
594
|
+
def async_executor(func):
|
|
595
|
+
try:
|
|
596
|
+
event_loop = asyncio.get_running_loop()
|
|
597
|
+
except RuntimeError:
|
|
598
|
+
event_loop = asyncio.new_event_loop()
|
|
599
|
+
if event_loop.is_running():
|
|
600
|
+
task = asyncio.ensure_future(func())
|
|
601
|
+
_running_tasks.add(task)
|
|
602
|
+
task.add_done_callback(_running_tasks.discard)
|
|
603
|
+
else:
|
|
604
|
+
event_loop.run_until_complete(func())
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
|
|
1
3
|
from collections import defaultdict
|
|
2
4
|
from functools import wraps
|
|
3
5
|
|
|
@@ -31,7 +33,17 @@ def depends(func, *dependencies, watch=False, on_init=False, **kw):
|
|
|
31
33
|
{key: transform_reference(arg) for key, arg in kw.items()}
|
|
32
34
|
)
|
|
33
35
|
|
|
34
|
-
if
|
|
36
|
+
if inspect.isgeneratorfunction(func):
|
|
37
|
+
@wraps(func)
|
|
38
|
+
def _depends(*args, **kw):
|
|
39
|
+
for val in func(*args, **kw):
|
|
40
|
+
yield val
|
|
41
|
+
elif inspect.isasyncgenfunction(func):
|
|
42
|
+
@wraps(func)
|
|
43
|
+
async def _depends(*args, **kw):
|
|
44
|
+
async for val in func(*args, **kw):
|
|
45
|
+
yield val
|
|
46
|
+
elif iscoroutinefunction(func):
|
|
35
47
|
@wraps(func)
|
|
36
48
|
async def _depends(*args, **kw):
|
|
37
49
|
return await func(*args, **kw)
|
|
@@ -72,7 +84,19 @@ def depends(func, *dependencies, watch=False, on_init=False, **kw):
|
|
|
72
84
|
'parameters by name.')
|
|
73
85
|
|
|
74
86
|
if not string_specs and watch: # string_specs case handled elsewhere (later), in Parameterized.__init__
|
|
75
|
-
if
|
|
87
|
+
if inspect.isgeneratorfunction(func):
|
|
88
|
+
def cb(*events):
|
|
89
|
+
args = (getattr(dep.owner, dep.name) for dep in dependencies)
|
|
90
|
+
dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()}
|
|
91
|
+
for val in func(*args, **dep_kwargs):
|
|
92
|
+
yield val
|
|
93
|
+
elif inspect.isasyncgenfunction(func):
|
|
94
|
+
async def cb(*events):
|
|
95
|
+
args = (getattr(dep.owner, dep.name) for dep in dependencies)
|
|
96
|
+
dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()}
|
|
97
|
+
async for val in func(*args, **dep_kwargs):
|
|
98
|
+
yield val
|
|
99
|
+
elif iscoroutinefunction(func):
|
|
76
100
|
async def cb(*events):
|
|
77
101
|
args = (getattr(dep.owner, dep.name) for dep in dependencies)
|
|
78
102
|
dep_kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw.items()}
|
|
@@ -25,7 +25,7 @@ import uuid
|
|
|
25
25
|
import param
|
|
26
26
|
|
|
27
27
|
from param.display import register_display_accessor
|
|
28
|
-
|
|
28
|
+
from param._utils import async_executor
|
|
29
29
|
|
|
30
30
|
# Whether to generate warnings when misformatted docstrings are found
|
|
31
31
|
WARN_MISFORMATTED_DOCSTRINGS = False
|
|
@@ -364,23 +364,46 @@ class IPythonDisplay:
|
|
|
364
364
|
|
|
365
365
|
def __call__(self):
|
|
366
366
|
from param.depends import depends
|
|
367
|
-
from param.parameterized import resolve_ref
|
|
367
|
+
from param.parameterized import Undefined, resolve_ref
|
|
368
368
|
from param.reactive import rx
|
|
369
369
|
|
|
370
|
+
handle = None
|
|
370
371
|
if isinstance(self._reactive, rx):
|
|
371
372
|
cb = self._reactive._callback
|
|
372
373
|
@depends(*self._reactive._params, watch=True)
|
|
373
374
|
def update_handle(*args, **kwargs):
|
|
374
|
-
handle
|
|
375
|
+
if handle is not None:
|
|
376
|
+
handle.update(cb())
|
|
375
377
|
else:
|
|
376
378
|
cb = self._reactive
|
|
377
379
|
@depends(*resolve_ref(cb), watch=True)
|
|
378
380
|
def update_handle(*args, **kwargs):
|
|
379
|
-
handle
|
|
381
|
+
if handle is not None:
|
|
382
|
+
handle.update(cb())
|
|
380
383
|
try:
|
|
381
|
-
|
|
384
|
+
obj = cb()
|
|
385
|
+
if obj is Undefined:
|
|
386
|
+
obj = None
|
|
387
|
+
handle = display(obj, display_id=uuid.uuid4().hex) # noqa
|
|
382
388
|
except TypeError:
|
|
383
389
|
raise NotImplementedError
|
|
384
390
|
|
|
391
|
+
def ipython_async_executor(func):
|
|
392
|
+
event_loop = None
|
|
393
|
+
try:
|
|
394
|
+
ip = get_ipython() # noqa
|
|
395
|
+
if ip.kernel:
|
|
396
|
+
# We are in Jupyter and can piggyback the tornado IOLoop
|
|
397
|
+
from tornado.ioloop import IOLoop
|
|
398
|
+
ioloop = IOLoop.current()
|
|
399
|
+
event_loop = ioloop.asyncio_loop # type: ignore
|
|
400
|
+
if event_loop.is_running():
|
|
401
|
+
ioloop.add_callback(func)
|
|
402
|
+
else:
|
|
403
|
+
event_loop.run_until_complete(func())
|
|
404
|
+
return
|
|
405
|
+
except (NameError, AttributeError):
|
|
406
|
+
pass
|
|
407
|
+
async_executor(func)
|
|
385
408
|
|
|
386
409
|
register_display_accessor('_ipython_display_', IPythonDisplay)
|
|
@@ -42,6 +42,7 @@ from ._utils import (
|
|
|
42
42
|
DEFAULT_SIGNATURE,
|
|
43
43
|
ParamDeprecationWarning as _ParamDeprecationWarning,
|
|
44
44
|
ParamFutureWarning as _ParamFutureWarning,
|
|
45
|
+
Skip,
|
|
45
46
|
_deprecated,
|
|
46
47
|
_deprecate_positional_args,
|
|
47
48
|
_dict_update,
|
|
@@ -49,6 +50,7 @@ from ._utils import (
|
|
|
49
50
|
_is_auto_name,
|
|
50
51
|
_is_mutable_container,
|
|
51
52
|
_recursive_repr,
|
|
53
|
+
_to_async_gen,
|
|
52
54
|
_validate_error_prefix,
|
|
53
55
|
accept_arguments,
|
|
54
56
|
iscoroutinefunction,
|
|
@@ -61,11 +63,12 @@ from ._utils import (
|
|
|
61
63
|
if _in_ipython():
|
|
62
64
|
# In case the optional ipython module is unavailable
|
|
63
65
|
try:
|
|
64
|
-
from .ipython import ParamPager
|
|
66
|
+
from .ipython import ParamPager, ipython_async_executor as async_executor
|
|
65
67
|
param_pager = ParamPager(metaclass=True) # Generates param description
|
|
66
68
|
except ImportError:
|
|
67
|
-
|
|
69
|
+
from ._utils import async_executor
|
|
68
70
|
else:
|
|
71
|
+
from ._utils import async_executor
|
|
69
72
|
param_pager = None
|
|
70
73
|
|
|
71
74
|
|
|
@@ -176,8 +179,11 @@ def resolve_value(value):
|
|
|
176
179
|
resolve_value(value.step)
|
|
177
180
|
)
|
|
178
181
|
value = transform_reference(value)
|
|
179
|
-
|
|
182
|
+
is_gen = inspect.isgeneratorfunction(value)
|
|
183
|
+
if hasattr(value, '_dinfo') or iscoroutinefunction(value) or is_gen:
|
|
180
184
|
value = eval_function_with_deps(value)
|
|
185
|
+
if is_gen:
|
|
186
|
+
value = _to_async_gen(value)
|
|
181
187
|
elif isinstance(value, Parameter):
|
|
182
188
|
value = getattr(value.owner, value.name)
|
|
183
189
|
return value
|
|
@@ -356,11 +362,6 @@ def discard_events(parameterized):
|
|
|
356
362
|
parameterized.param._events = events
|
|
357
363
|
|
|
358
364
|
|
|
359
|
-
# External components can register an async executor which will run
|
|
360
|
-
# async functions
|
|
361
|
-
async_executor = None
|
|
362
|
-
|
|
363
|
-
|
|
364
365
|
def classlist(class_):
|
|
365
366
|
"""
|
|
366
367
|
Return a list of the class hierarchy above (and including) the given class.
|
|
@@ -1469,12 +1470,16 @@ class Parameter(_ParameterBase):
|
|
|
1469
1470
|
"""
|
|
1470
1471
|
name = self.name
|
|
1471
1472
|
if obj is not None and self.allow_refs and obj._param__private.initialized and name not in obj._param__private.syncing:
|
|
1472
|
-
ref, deps, val,
|
|
1473
|
+
ref, deps, val, is_async = obj.param._resolve_ref(self, val)
|
|
1473
1474
|
refs = obj._param__private.refs
|
|
1474
1475
|
if ref is not None:
|
|
1475
1476
|
self.owner.param._update_ref(name, ref)
|
|
1476
1477
|
elif name in refs:
|
|
1477
1478
|
del refs[name]
|
|
1479
|
+
if name in obj._param__private.async_refs:
|
|
1480
|
+
obj._param__private.async_refs.pop(name).cancel()
|
|
1481
|
+
if is_async or val is Undefined:
|
|
1482
|
+
return
|
|
1478
1483
|
|
|
1479
1484
|
# Deprecated Number set_hook called here to avoid duplicating setter
|
|
1480
1485
|
if hasattr(self, 'set_hook'):
|
|
@@ -1752,16 +1757,17 @@ class _ParametersRestorer:
|
|
|
1752
1757
|
Context-manager to handle the reset of parameter values after an update.
|
|
1753
1758
|
"""
|
|
1754
1759
|
|
|
1755
|
-
def __init__(self, *, parameters, restore):
|
|
1760
|
+
def __init__(self, *, parameters, restore, refs=None):
|
|
1756
1761
|
self._parameters = parameters
|
|
1757
1762
|
self._restore = restore
|
|
1763
|
+
self._refs = {} if refs is None else refs
|
|
1758
1764
|
|
|
1759
1765
|
def __enter__(self):
|
|
1760
1766
|
return self._restore
|
|
1761
1767
|
|
|
1762
1768
|
def __exit__(self, exc_type, exc_value, exc_tb):
|
|
1763
1769
|
try:
|
|
1764
|
-
self._parameters._update(self._restore)
|
|
1770
|
+
self._parameters._update(dict(self._restore, **self._refs))
|
|
1765
1771
|
finally:
|
|
1766
1772
|
self._restore = {}
|
|
1767
1773
|
|
|
@@ -1963,7 +1969,7 @@ class Parameters:
|
|
|
1963
1969
|
if ref is not None:
|
|
1964
1970
|
refs[name] = ref
|
|
1965
1971
|
deps[name] = ref_deps
|
|
1966
|
-
if not is_async:
|
|
1972
|
+
if not is_async and not (resolved is Undefined or resolved is Skip):
|
|
1967
1973
|
setattr(self, name, resolved)
|
|
1968
1974
|
return refs, deps
|
|
1969
1975
|
|
|
@@ -1985,7 +1991,10 @@ class Parameters:
|
|
|
1985
1991
|
))
|
|
1986
1992
|
|
|
1987
1993
|
def _update_ref(self_, name, ref):
|
|
1988
|
-
|
|
1994
|
+
param_private = self_.self._param__private
|
|
1995
|
+
if name in param_private.async_refs:
|
|
1996
|
+
param_private.async_refs.pop(name).cancel()
|
|
1997
|
+
for _, watcher in param_private.ref_watchers:
|
|
1989
1998
|
dep_obj = watcher.cls if watcher.inst is None else watcher.inst
|
|
1990
1999
|
dep_obj.param.unwatch(watcher)
|
|
1991
2000
|
self_.self._param__private.ref_watchers = []
|
|
@@ -1999,12 +2008,18 @@ class Parameters:
|
|
|
1999
2008
|
for pname, ref in self_.self._param__private.refs.items():
|
|
2000
2009
|
# Skip updating value if dependency has not changed
|
|
2001
2010
|
deps = resolve_ref(ref, self_[pname].nested_refs)
|
|
2002
|
-
|
|
2011
|
+
is_gen = inspect.isgeneratorfunction(ref)
|
|
2012
|
+
is_async = iscoroutinefunction(ref) or is_gen
|
|
2003
2013
|
if not any((dep.owner is e.obj and dep.name == e.name) for dep in deps for e in events) and not is_async:
|
|
2004
2014
|
continue
|
|
2005
2015
|
|
|
2006
|
-
|
|
2007
|
-
|
|
2016
|
+
try:
|
|
2017
|
+
new_val = resolve_value(ref)
|
|
2018
|
+
except Skip:
|
|
2019
|
+
new_val = Undefined
|
|
2020
|
+
if new_val is Skip or new_val is Undefined:
|
|
2021
|
+
continue
|
|
2022
|
+
elif is_async:
|
|
2008
2023
|
async_executor(partial(self_._async_ref, pname, new_val))
|
|
2009
2024
|
continue
|
|
2010
2025
|
|
|
@@ -2015,12 +2030,16 @@ class Parameters:
|
|
|
2015
2030
|
self_.update(updates)
|
|
2016
2031
|
|
|
2017
2032
|
def _resolve_ref(self_, pobj, value):
|
|
2018
|
-
|
|
2033
|
+
is_gen = inspect.isgeneratorfunction(value)
|
|
2034
|
+
is_async = iscoroutinefunction(value) or is_gen
|
|
2019
2035
|
deps = resolve_ref(value, recursive=pobj.nested_refs)
|
|
2020
|
-
if not deps
|
|
2036
|
+
if not (deps or is_async or is_gen):
|
|
2021
2037
|
return None, None, value, False
|
|
2022
2038
|
ref = value
|
|
2023
|
-
|
|
2039
|
+
try:
|
|
2040
|
+
value = resolve_value(value)
|
|
2041
|
+
except Skip:
|
|
2042
|
+
value = Undefined
|
|
2024
2043
|
if is_async:
|
|
2025
2044
|
async_executor(partial(self_._async_ref, pobj.name, value))
|
|
2026
2045
|
value = None
|
|
@@ -2030,9 +2049,13 @@ class Parameters:
|
|
|
2030
2049
|
if not self_.self._param__private.initialized:
|
|
2031
2050
|
async_executor(partial(self_._async_ref, pname, awaitable))
|
|
2032
2051
|
return
|
|
2033
|
-
|
|
2052
|
+
|
|
2053
|
+
current_task = asyncio.current_task()
|
|
2054
|
+
running_task = self_.self._param__private.async_refs.get(pname)
|
|
2055
|
+
if running_task is None:
|
|
2056
|
+
self_.self._param__private.async_refs[pname] = current_task
|
|
2057
|
+
elif current_task is not running_task:
|
|
2034
2058
|
self_.self._param__private.async_refs[pname].cancel()
|
|
2035
|
-
self_.self._param__private.async_refs[pname] = asyncio.current_task()
|
|
2036
2059
|
try:
|
|
2037
2060
|
if isinstance(awaitable, types.AsyncGeneratorType):
|
|
2038
2061
|
async for new_obj in awaitable:
|
|
@@ -2040,11 +2063,14 @@ class Parameters:
|
|
|
2040
2063
|
self_.update({pname: new_obj})
|
|
2041
2064
|
else:
|
|
2042
2065
|
with _syncing(self_.self, (pname,)):
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2066
|
+
try:
|
|
2067
|
+
self_.update({pname: await awaitable})
|
|
2068
|
+
except Skip:
|
|
2069
|
+
pass
|
|
2046
2070
|
finally:
|
|
2047
|
-
|
|
2071
|
+
# Ensure we clean up but only if the task matches the currrent task
|
|
2072
|
+
if self_.self._param__private.async_refs.get(pname) is current_task:
|
|
2073
|
+
del self_.self._param__private.async_refs[pname]
|
|
2048
2074
|
|
|
2049
2075
|
@classmethod
|
|
2050
2076
|
def _changed(cls, event):
|
|
@@ -2266,7 +2292,7 @@ class Parameters:
|
|
|
2266
2292
|
|
|
2267
2293
|
# Bothmethods
|
|
2268
2294
|
|
|
2269
|
-
def update(self_,
|
|
2295
|
+
def update(self_, arg=Undefined, /, **kwargs):
|
|
2270
2296
|
"""
|
|
2271
2297
|
For the given dictionary or iterable or set of param=value
|
|
2272
2298
|
keyword arguments, sets the corresponding parameter of this
|
|
@@ -2275,22 +2301,26 @@ class Parameters:
|
|
|
2275
2301
|
May also be used as a context manager to temporarily set and
|
|
2276
2302
|
then reset parameter values.
|
|
2277
2303
|
"""
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2304
|
+
refs = {}
|
|
2305
|
+
if self_.self is not None:
|
|
2306
|
+
private = self_.self._param__private
|
|
2307
|
+
params = list(kwargs if arg is Undefined else dict(arg, **kwargs))
|
|
2308
|
+
for pname in params:
|
|
2309
|
+
if pname in refs:
|
|
2310
|
+
continue
|
|
2311
|
+
elif pname in private.refs:
|
|
2312
|
+
refs[pname] = private.refs[pname]
|
|
2313
|
+
elif pname in private.async_refs:
|
|
2314
|
+
refs[pname] = private.async_refs[pname]
|
|
2315
|
+
restore = dict(self_._update(arg, **kwargs))
|
|
2316
|
+
return _ParametersRestorer(parameters=self_, restore=restore, refs=refs)
|
|
2317
|
+
|
|
2318
|
+
def _update(self_, arg=Undefined, /, **kwargs):
|
|
2282
2319
|
BATCH_WATCH = self_._BATCH_WATCH
|
|
2283
2320
|
self_._BATCH_WATCH = True
|
|
2284
2321
|
self_or_cls = self_.self_or_cls
|
|
2285
|
-
if
|
|
2286
|
-
|
|
2287
|
-
kwargs = args[0]
|
|
2288
|
-
else:
|
|
2289
|
-
self_._BATCH_WATCH = False
|
|
2290
|
-
raise ValueError(
|
|
2291
|
-
f"{self_.cls.__name__}.param.update accepts *either* an iterable "
|
|
2292
|
-
"or key=value pairs, not both."
|
|
2293
|
-
)
|
|
2322
|
+
if arg is not Undefined:
|
|
2323
|
+
kwargs = dict(arg, **kwargs)
|
|
2294
2324
|
|
|
2295
2325
|
trigger_params = [
|
|
2296
2326
|
k for k in kwargs
|
|
@@ -2468,7 +2498,10 @@ class Parameters:
|
|
|
2468
2498
|
watcher.fn)
|
|
2469
2499
|
async_executor(partial(watcher.fn, *args, **kwargs))
|
|
2470
2500
|
else:
|
|
2471
|
-
|
|
2501
|
+
try:
|
|
2502
|
+
watcher.fn(*args, **kwargs)
|
|
2503
|
+
except Skip:
|
|
2504
|
+
pass
|
|
2472
2505
|
|
|
2473
2506
|
def _call_watcher(self_, watcher, event):
|
|
2474
2507
|
"""
|
|
@@ -16,15 +16,16 @@ parameter types (e.g. Number), and also imports the definition of
|
|
|
16
16
|
Parameters and Parameterized classes.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
import
|
|
20
|
-
import os.path
|
|
21
|
-
import sys
|
|
19
|
+
import collections
|
|
22
20
|
import copy
|
|
23
|
-
import glob
|
|
24
|
-
import re
|
|
25
21
|
import datetime as dt
|
|
26
|
-
import
|
|
22
|
+
import glob
|
|
23
|
+
import inspect
|
|
24
|
+
import numbers
|
|
25
|
+
import os.path
|
|
27
26
|
import pathlib
|
|
27
|
+
import re
|
|
28
|
+
import sys
|
|
28
29
|
import typing
|
|
29
30
|
import warnings
|
|
30
31
|
|
|
@@ -803,7 +804,7 @@ class Number(Dynamic):
|
|
|
803
804
|
)
|
|
804
805
|
|
|
805
806
|
def _validate_value(self, val, allow_None):
|
|
806
|
-
if (allow_None and val is None) or callable(val):
|
|
807
|
+
if (allow_None and val is None) or (callable(val) and not inspect.isgeneratorfunction(val)):
|
|
807
808
|
return
|
|
808
809
|
|
|
809
810
|
if not _is_number(val):
|