param 2.1.0a2__tar.gz → 2.1.1a0__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.1.0a2 → param-2.1.1a0}/PKG-INFO +45 -10
- {param-2.1.0a2 → param-2.1.1a0}/param/_utils.py +1 -1
- {param-2.1.0a2 → param-2.1.1a0}/param/_version.py +2 -2
- {param-2.1.0a2 → param-2.1.1a0}/param/parameterized.py +13 -9
- {param-2.1.0a2 → param-2.1.1a0}/param/reactive.py +135 -10
- {param-2.1.0a2 → param-2.1.1a0}/tests/testdeprecations.py +3 -3
- {param-2.1.0a2 → param-2.1.1a0}/tests/testreactive.py +254 -21
- {param-2.1.0a2 → param-2.1.1a0}/tests/testsignatures.py +3 -3
- {param-2.1.0a2 → param-2.1.1a0}/tests/testwatch.py +2 -2
- {param-2.1.0a2 → param-2.1.1a0}/tests/utils.py +63 -0
- {param-2.1.0a2 → param-2.1.1a0}/.gitignore +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/LICENSE.txt +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/README.md +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/numbergen/__init__.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/param/__init__.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/param/depends.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/param/display.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/param/ipython.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/param/parameters.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/param/serializer.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/param/version.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/pyproject.toml +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/__init__.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/conftest.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testaddparameter.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testbind.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testbooleanparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testbytesparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testcalendardateparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testcalendardaterangeparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testcallable.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testclassselector.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testcolorparameter.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testcomparator.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testcompositeparams.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testcustomparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testdateparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testdaterangeparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testdefaults.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testdynamicparams.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testfiledeserialization.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testfileselector.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testipythonmagic.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testjsonserialization.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testlist.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testlistselector.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testmultifileselector.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testnumbergen.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testnumberparameter.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testnumpy.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testobjectselector.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testpandas.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testparamdepends.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testparameterizedobject.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testparameterizedrepr.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testparamoutput.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testparamunion.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testpathparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testpickle.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testrangeparameter.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testrefs.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testreprhtml.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testselector.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/teststringparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testtimedependent.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testtupleparam.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testutils.py +0 -0
- {param-2.1.0a2 → param-2.1.1a0}/tests/testversion.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: param
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.1a0
|
|
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
|
|
@@ -27,12 +27,35 @@ Classifier: Topic :: Scientific/Engineering
|
|
|
27
27
|
Classifier: Topic :: Software Development :: Libraries
|
|
28
28
|
Requires-Python: >=3.8
|
|
29
29
|
Provides-Extra: all
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
32
|
-
Requires-Dist:
|
|
30
|
+
Requires-Dist: aiohttp; extra == 'all'
|
|
31
|
+
Requires-Dist: cloudpickle; extra == 'all'
|
|
32
|
+
Requires-Dist: coverage[toml]; extra == 'all'
|
|
33
|
+
Requires-Dist: flake8; extra == 'all'
|
|
34
|
+
Requires-Dist: gmpy; extra == 'all'
|
|
35
|
+
Requires-Dist: ipython; extra == 'all'
|
|
36
|
+
Requires-Dist: jsonschema; extra == 'all'
|
|
37
|
+
Requires-Dist: nbsite==0.8.4; extra == 'all'
|
|
38
|
+
Requires-Dist: nbval; extra == 'all'
|
|
39
|
+
Requires-Dist: nest-asyncio; extra == 'all'
|
|
40
|
+
Requires-Dist: numpy; extra == 'all'
|
|
41
|
+
Requires-Dist: odfpy; extra == 'all'
|
|
42
|
+
Requires-Dist: openpyxl; extra == 'all'
|
|
43
|
+
Requires-Dist: pandas; extra == 'all'
|
|
44
|
+
Requires-Dist: panel; extra == 'all'
|
|
45
|
+
Requires-Dist: pre-commit; extra == 'all'
|
|
46
|
+
Requires-Dist: pyarrow; extra == 'all'
|
|
47
|
+
Requires-Dist: pytest; extra == 'all'
|
|
48
|
+
Requires-Dist: pytest-asyncio; extra == 'all'
|
|
49
|
+
Requires-Dist: pytest-xdist; extra == 'all'
|
|
50
|
+
Requires-Dist: pytest<8.1; extra == 'all'
|
|
51
|
+
Requires-Dist: sphinx-remove-toctrees; extra == 'all'
|
|
52
|
+
Requires-Dist: tables; extra == 'all'
|
|
53
|
+
Requires-Dist: xlrd; extra == 'all'
|
|
33
54
|
Provides-Extra: doc
|
|
55
|
+
Requires-Dist: aiohttp; extra == 'doc'
|
|
34
56
|
Requires-Dist: nbsite==0.8.4; extra == 'doc'
|
|
35
|
-
Requires-Dist:
|
|
57
|
+
Requires-Dist: pandas; extra == 'doc'
|
|
58
|
+
Requires-Dist: panel; extra == 'doc'
|
|
36
59
|
Requires-Dist: sphinx-remove-toctrees; extra == 'doc'
|
|
37
60
|
Provides-Extra: examples
|
|
38
61
|
Requires-Dist: aiohttp; extra == 'examples'
|
|
@@ -52,22 +75,34 @@ Requires-Dist: pyarrow; extra == 'tests-deser'
|
|
|
52
75
|
Requires-Dist: tables; extra == 'tests-deser'
|
|
53
76
|
Requires-Dist: xlrd; extra == 'tests-deser'
|
|
54
77
|
Provides-Extra: tests-examples
|
|
78
|
+
Requires-Dist: aiohttp; extra == 'tests-examples'
|
|
55
79
|
Requires-Dist: nbval; extra == 'tests-examples'
|
|
56
|
-
Requires-Dist:
|
|
80
|
+
Requires-Dist: pandas; extra == 'tests-examples'
|
|
81
|
+
Requires-Dist: panel; extra == 'tests-examples'
|
|
57
82
|
Requires-Dist: pytest-asyncio; extra == 'tests-examples'
|
|
58
83
|
Requires-Dist: pytest-xdist; extra == 'tests-examples'
|
|
59
84
|
Requires-Dist: pytest<8.1; extra == 'tests-examples'
|
|
60
85
|
Provides-Extra: tests-full
|
|
86
|
+
Requires-Dist: aiohttp; extra == 'tests-full'
|
|
61
87
|
Requires-Dist: cloudpickle; extra == 'tests-full'
|
|
88
|
+
Requires-Dist: coverage[toml]; extra == 'tests-full'
|
|
62
89
|
Requires-Dist: gmpy; extra == 'tests-full'
|
|
63
90
|
Requires-Dist: ipython; extra == 'tests-full'
|
|
64
91
|
Requires-Dist: jsonschema; extra == 'tests-full'
|
|
92
|
+
Requires-Dist: nbval; extra == 'tests-full'
|
|
65
93
|
Requires-Dist: nest-asyncio; extra == 'tests-full'
|
|
66
94
|
Requires-Dist: numpy; extra == 'tests-full'
|
|
95
|
+
Requires-Dist: odfpy; extra == 'tests-full'
|
|
96
|
+
Requires-Dist: openpyxl; extra == 'tests-full'
|
|
67
97
|
Requires-Dist: pandas; extra == 'tests-full'
|
|
68
|
-
Requires-Dist:
|
|
69
|
-
Requires-Dist:
|
|
70
|
-
Requires-Dist:
|
|
98
|
+
Requires-Dist: panel; extra == 'tests-full'
|
|
99
|
+
Requires-Dist: pyarrow; extra == 'tests-full'
|
|
100
|
+
Requires-Dist: pytest; extra == 'tests-full'
|
|
101
|
+
Requires-Dist: pytest-asyncio; extra == 'tests-full'
|
|
102
|
+
Requires-Dist: pytest-xdist; extra == 'tests-full'
|
|
103
|
+
Requires-Dist: pytest<8.1; extra == 'tests-full'
|
|
104
|
+
Requires-Dist: tables; extra == 'tests-full'
|
|
105
|
+
Requires-Dist: xlrd; extra == 'tests-full'
|
|
71
106
|
Description-Content-Type: text/markdown
|
|
72
107
|
|
|
73
108
|
<img src="https://raw.githubusercontent.com/holoviz/param/main/doc/_static/logo_horizontal.png" width=250>
|
|
@@ -98,7 +98,7 @@ def _deprecate_positional_args(func):
|
|
|
98
98
|
f"Passing '{extra_args}' as positional argument(s) to 'param.{name}' "
|
|
99
99
|
"has been deprecated since Param 2.0.0 and will raise an error in a future version, "
|
|
100
100
|
"please pass them as keyword arguments.",
|
|
101
|
-
|
|
101
|
+
ParamDeprecationWarning,
|
|
102
102
|
stacklevel=2,
|
|
103
103
|
)
|
|
104
104
|
|
|
@@ -164,11 +164,13 @@ def eval_function_with_deps(function):
|
|
|
164
164
|
kwargs = {n: getattr(dep.owner, dep.name) for n, dep in kw_deps.items()}
|
|
165
165
|
return function(*args, **kwargs)
|
|
166
166
|
|
|
167
|
-
def resolve_value(value):
|
|
167
|
+
def resolve_value(value, recursive=True):
|
|
168
168
|
"""
|
|
169
169
|
Resolves the current value of a dynamic reference.
|
|
170
170
|
"""
|
|
171
|
-
if
|
|
171
|
+
if not recursive:
|
|
172
|
+
pass
|
|
173
|
+
elif isinstance(value, (list, tuple)):
|
|
172
174
|
return type(value)(resolve_value(v) for v in value)
|
|
173
175
|
elif isinstance(value, dict):
|
|
174
176
|
return type(value)((resolve_value(k), resolve_value(v)) for k, v in value.items())
|
|
@@ -1469,12 +1471,13 @@ class Parameter(_ParameterBase):
|
|
|
1469
1471
|
item in a list).
|
|
1470
1472
|
"""
|
|
1471
1473
|
name = self.name
|
|
1472
|
-
if obj is not None and self.allow_refs and obj._param__private.initialized
|
|
1474
|
+
if obj is not None and self.allow_refs and obj._param__private.initialized:
|
|
1475
|
+
syncing = name in obj._param__private.syncing
|
|
1473
1476
|
ref, deps, val, is_async = obj.param._resolve_ref(self, val)
|
|
1474
1477
|
refs = obj._param__private.refs
|
|
1475
1478
|
if ref is not None:
|
|
1476
1479
|
self.owner.param._update_ref(name, ref)
|
|
1477
|
-
elif name in refs:
|
|
1480
|
+
elif name in refs and not syncing:
|
|
1478
1481
|
del refs[name]
|
|
1479
1482
|
if name in obj._param__private.async_refs:
|
|
1480
1483
|
obj._param__private.async_refs.pop(name).cancel()
|
|
@@ -2007,14 +2010,15 @@ class Parameters:
|
|
|
2007
2010
|
updates = {}
|
|
2008
2011
|
for pname, ref in self_.self._param__private.refs.items():
|
|
2009
2012
|
# Skip updating value if dependency has not changed
|
|
2010
|
-
|
|
2013
|
+
recursive = self_[pname].nested_refs
|
|
2014
|
+
deps = resolve_ref(ref, recursive)
|
|
2011
2015
|
is_gen = inspect.isgeneratorfunction(ref)
|
|
2012
2016
|
is_async = iscoroutinefunction(ref) or is_gen
|
|
2013
2017
|
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:
|
|
2014
2018
|
continue
|
|
2015
2019
|
|
|
2016
2020
|
try:
|
|
2017
|
-
new_val = resolve_value(ref)
|
|
2021
|
+
new_val = resolve_value(ref, recursive)
|
|
2018
2022
|
except Skip:
|
|
2019
2023
|
new_val = Undefined
|
|
2020
2024
|
if new_val is Skip or new_val is Undefined:
|
|
@@ -2037,7 +2041,7 @@ class Parameters:
|
|
|
2037
2041
|
return None, None, value, False
|
|
2038
2042
|
ref = value
|
|
2039
2043
|
try:
|
|
2040
|
-
value = resolve_value(value)
|
|
2044
|
+
value = resolve_value(value, recursive=pobj.nested_refs)
|
|
2041
2045
|
except Skip:
|
|
2042
2046
|
value = Undefined
|
|
2043
2047
|
if is_async:
|
|
@@ -4196,13 +4200,13 @@ class Parameterized(metaclass=ParameterizedMetaclass):
|
|
|
4196
4200
|
|
|
4197
4201
|
#PARAM3_DEPRECATION
|
|
4198
4202
|
@property
|
|
4199
|
-
@_deprecated(extra_msg="Use `inst.param.watchers` instead.", warning_cat=
|
|
4203
|
+
@_deprecated(extra_msg="Use `inst.param.watchers` instead.", warning_cat=_ParamFutureWarning)
|
|
4200
4204
|
def _param_watchers(self):
|
|
4201
4205
|
return self._param__private.watchers
|
|
4202
4206
|
|
|
4203
4207
|
#PARAM3_DEPRECATION
|
|
4204
4208
|
@_param_watchers.setter
|
|
4205
|
-
@_deprecated(extra_msg="Use `inst.param.watchers = ...` instead.", warning_cat=
|
|
4209
|
+
@_deprecated(extra_msg="Use `inst.param.watchers = ...` instead.", warning_cat=_ParamFutureWarning)
|
|
4206
4210
|
def _param_watchers(self, value):
|
|
4207
4211
|
self._param__private.watchers = value
|
|
4208
4212
|
|
|
@@ -81,6 +81,7 @@ display its repr.
|
|
|
81
81
|
"""
|
|
82
82
|
from __future__ import annotations
|
|
83
83
|
|
|
84
|
+
import asyncio
|
|
84
85
|
import inspect
|
|
85
86
|
import math
|
|
86
87
|
import operator
|
|
@@ -90,19 +91,19 @@ from functools import partial
|
|
|
90
91
|
from types import FunctionType, MethodType
|
|
91
92
|
from typing import Any, Callable, Optional
|
|
92
93
|
|
|
93
|
-
from . import Event
|
|
94
94
|
from .depends import depends
|
|
95
95
|
from .display import _display_accessors, _reactive_display_objs
|
|
96
96
|
from .parameterized import (
|
|
97
97
|
Parameter, Parameterized, Skip, Undefined, eval_function_with_deps, get_method_owner,
|
|
98
98
|
register_reference_transform, resolve_ref, resolve_value, transform_reference
|
|
99
99
|
)
|
|
100
|
-
from .
|
|
100
|
+
from .parameters import Boolean, Event
|
|
101
|
+
from ._utils import _to_async_gen, iscoroutinefunction, full_groupby
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
class Wrapper(Parameterized):
|
|
104
105
|
"""
|
|
105
|
-
|
|
106
|
+
Helper class to allow updating literal values easily.
|
|
106
107
|
"""
|
|
107
108
|
|
|
108
109
|
object = Parameter(allow_refs=False)
|
|
@@ -110,20 +111,73 @@ class Wrapper(Parameterized):
|
|
|
110
111
|
|
|
111
112
|
class GenWrapper(Parameterized):
|
|
112
113
|
"""
|
|
113
|
-
|
|
114
|
+
Helper class to allow streaming from generator functions.
|
|
114
115
|
"""
|
|
115
116
|
|
|
116
117
|
object = Parameter(allow_refs=True)
|
|
117
118
|
|
|
118
119
|
|
|
119
120
|
class Trigger(Parameterized):
|
|
121
|
+
"""
|
|
122
|
+
Helper class to allow triggering an event under some condition.
|
|
123
|
+
"""
|
|
120
124
|
|
|
121
125
|
value = Event()
|
|
122
126
|
|
|
123
|
-
def __init__(self, parameters, **params):
|
|
127
|
+
def __init__(self, parameters=None, internal=False, **params):
|
|
124
128
|
super().__init__(**params)
|
|
129
|
+
self.internal = internal
|
|
125
130
|
self.parameters = parameters
|
|
126
131
|
|
|
132
|
+
class Resolver(Parameterized):
|
|
133
|
+
"""
|
|
134
|
+
Helper class to allow (recursively) resolving references.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
object = Parameter(allow_refs=True)
|
|
138
|
+
|
|
139
|
+
recursive = Boolean(default=False)
|
|
140
|
+
|
|
141
|
+
value = Parameter()
|
|
142
|
+
|
|
143
|
+
def __init__(self, **params):
|
|
144
|
+
self._watchers = []
|
|
145
|
+
super().__init__(**params)
|
|
146
|
+
|
|
147
|
+
def _resolve_value(self, *events):
|
|
148
|
+
nested = self.param.object.nested_refs
|
|
149
|
+
refs = resolve_ref(self.object, nested)
|
|
150
|
+
value = resolve_value(self.object, nested)
|
|
151
|
+
if self.recursive:
|
|
152
|
+
new_refs = [r for r in resolve_ref(value, nested) if r not in refs]
|
|
153
|
+
while new_refs:
|
|
154
|
+
refs += new_refs
|
|
155
|
+
value = resolve_value(value, nested)
|
|
156
|
+
new_refs = [r for r in resolve_ref(value, nested) if r not in refs]
|
|
157
|
+
if events:
|
|
158
|
+
self._update_refs(refs)
|
|
159
|
+
self.value = value
|
|
160
|
+
return refs
|
|
161
|
+
|
|
162
|
+
@depends('object', watch=True, on_init=True)
|
|
163
|
+
def _resolve_object(self):
|
|
164
|
+
refs = self._resolve_value()
|
|
165
|
+
self._update_refs(refs)
|
|
166
|
+
|
|
167
|
+
def _update_refs(self, refs):
|
|
168
|
+
for w in self._watchers:
|
|
169
|
+
(w.inst or w.cls).param.unwatch(w)
|
|
170
|
+
self._watchers = []
|
|
171
|
+
for _, params in full_groupby(refs, lambda x: id(x.owner)):
|
|
172
|
+
self._watchers.append(
|
|
173
|
+
params[0].owner.param.watch(self._resolve_value, [p.name for p in params])
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class NestedResolver(Resolver):
|
|
178
|
+
|
|
179
|
+
object = Parameter(allow_refs=True, nested_refs=True)
|
|
180
|
+
|
|
127
181
|
|
|
128
182
|
class reactive_ops:
|
|
129
183
|
"""
|
|
@@ -204,8 +258,17 @@ class reactive_ops:
|
|
|
204
258
|
kwargs: mapping, optional
|
|
205
259
|
A dictionary of keywords to pass to `func`.
|
|
206
260
|
"""
|
|
207
|
-
|
|
208
|
-
|
|
261
|
+
if inspect.isasyncgenfunction(func) or inspect.isgeneratorfunction(func):
|
|
262
|
+
raise TypeError(
|
|
263
|
+
"Cannot map a generator function. Only regular function "
|
|
264
|
+
"or coroutine functions are permitted."
|
|
265
|
+
)
|
|
266
|
+
if inspect.iscoroutinefunction(func):
|
|
267
|
+
async def apply(vs, *args, **kwargs):
|
|
268
|
+
return list(await asyncio.gather(*(func(v, *args, **kwargs) for v in vs)))
|
|
269
|
+
else:
|
|
270
|
+
def apply(vs, *args, **kwargs):
|
|
271
|
+
return [func(v, *args, **kwargs) for v in vs]
|
|
209
272
|
return self._as_rx()._apply_operator(apply, *args, **kwargs)
|
|
210
273
|
|
|
211
274
|
def not_(self):
|
|
@@ -235,6 +298,27 @@ class reactive_ops:
|
|
|
235
298
|
"""
|
|
236
299
|
return self._as_rx()._apply_operator(func, *args, **kwargs)
|
|
237
300
|
|
|
301
|
+
def resolve(self, nested=True, recursive=False):
|
|
302
|
+
"""
|
|
303
|
+
Resolves references held by the expression.
|
|
304
|
+
|
|
305
|
+
As an example if the expression returns a list of parameters
|
|
306
|
+
this operation will return a list of the parameter values.
|
|
307
|
+
|
|
308
|
+
Arguments
|
|
309
|
+
---------
|
|
310
|
+
nested: bool
|
|
311
|
+
Whether to resolve references contained within nested objects,
|
|
312
|
+
i.e. tuples, lists, sets and dictionaries.
|
|
313
|
+
recursive: bool
|
|
314
|
+
Whether to recursively resolve references, i.e. if a reference
|
|
315
|
+
itself returns a reference we recurse into it until no more
|
|
316
|
+
references can be resolved.
|
|
317
|
+
"""
|
|
318
|
+
resolver_type = NestedResolver if nested else Resolver
|
|
319
|
+
resolver = resolver_type(object=self._reactive, recursive=recursive)
|
|
320
|
+
return resolver.param.value.rx()
|
|
321
|
+
|
|
238
322
|
def updating(self):
|
|
239
323
|
"""
|
|
240
324
|
Returns a new expression that is True while the expression is updating.
|
|
@@ -646,19 +730,27 @@ class rx:
|
|
|
646
730
|
self._depth = depth
|
|
647
731
|
self._dirty = _current is None
|
|
648
732
|
self._dirty_obj = False
|
|
733
|
+
self._current_task = None
|
|
649
734
|
self._error_state = None
|
|
650
735
|
self._current_ = _current
|
|
651
736
|
if isinstance(obj, rx) and not prev:
|
|
652
737
|
self._prev = obj
|
|
653
738
|
else:
|
|
654
739
|
self._prev = prev
|
|
740
|
+
|
|
741
|
+
# Define special trigger parameter if operation has to be lazily evaluated
|
|
742
|
+
if operation and (iscoroutinefunction(operation['fn']) or inspect.isgeneratorfunction(operation['fn'])):
|
|
743
|
+
self._trigger = Trigger(internal=True)
|
|
744
|
+
self._current_ = Undefined
|
|
745
|
+
else:
|
|
746
|
+
self._trigger = None
|
|
655
747
|
self._root = self._compute_root()
|
|
656
748
|
self._fn_params = self._compute_fn_params()
|
|
657
749
|
self._internal_params = self._compute_params()
|
|
658
750
|
# Filter params that external objects depend on, ensuring
|
|
659
751
|
# that Trigger parameters do not cause double execution
|
|
660
752
|
self._params = [
|
|
661
|
-
p for p in self._internal_params if not isinstance(p.owner, Trigger)
|
|
753
|
+
p for p in self._internal_params if (not isinstance(p.owner, Trigger) or p.owner.internal)
|
|
662
754
|
or any (p not in self._internal_params for p in p.owner.parameters)
|
|
663
755
|
]
|
|
664
756
|
self._setup_invalidations(depth)
|
|
@@ -721,7 +813,9 @@ class rx:
|
|
|
721
813
|
return args + kwargs
|
|
722
814
|
|
|
723
815
|
def _compute_params(self) -> list[Parameter]:
|
|
724
|
-
ps = self._fn_params
|
|
816
|
+
ps = list(self._fn_params)
|
|
817
|
+
if self._trigger:
|
|
818
|
+
ps.append(self._trigger.param.value)
|
|
725
819
|
|
|
726
820
|
# Collect parameters on previous objects in chain
|
|
727
821
|
prev = self._prev
|
|
@@ -735,6 +829,9 @@ class rx:
|
|
|
735
829
|
return ps
|
|
736
830
|
|
|
737
831
|
# Accumulate dependencies in args and/or kwargs
|
|
832
|
+
for ref in resolve_ref(self._operation['fn']):
|
|
833
|
+
if ref not in ps:
|
|
834
|
+
ps.append(ref)
|
|
738
835
|
for arg in list(self._operation['args'])+list(self._operation['kwargs'].values()):
|
|
739
836
|
for ref in resolve_ref(arg):
|
|
740
837
|
if ref not in ps:
|
|
@@ -763,11 +860,15 @@ class rx:
|
|
|
763
860
|
"""
|
|
764
861
|
if self._fn is not None:
|
|
765
862
|
for _, params in full_groupby(self._fn_params, lambda x: id(x.owner)):
|
|
766
|
-
|
|
863
|
+
fps = [p.name for p in params if p in self._root._fn_params]
|
|
864
|
+
if fps:
|
|
865
|
+
params[0].owner.param._watch(self._invalidate_obj, fps, precedence=-1)
|
|
767
866
|
for _, params in full_groupby(self._internal_params, lambda x: id(x.owner)):
|
|
768
867
|
params[0].owner.param._watch(self._invalidate_current, [p.name for p in params], precedence=-1)
|
|
769
868
|
|
|
770
869
|
def _invalidate_current(self, *events):
|
|
870
|
+
if all(event.obj is self._trigger for event in events):
|
|
871
|
+
return
|
|
771
872
|
self._dirty = True
|
|
772
873
|
self._error_state = None
|
|
773
874
|
|
|
@@ -775,6 +876,26 @@ class rx:
|
|
|
775
876
|
self._root._dirty_obj = True
|
|
776
877
|
self._error_state = None
|
|
777
878
|
|
|
879
|
+
async def _resolve_async(self, obj):
|
|
880
|
+
self._current_task = task = asyncio.current_task()
|
|
881
|
+
if inspect.isasyncgen(obj):
|
|
882
|
+
async for val in obj:
|
|
883
|
+
if self._current_task is not task:
|
|
884
|
+
break
|
|
885
|
+
self._current_ = val
|
|
886
|
+
self._trigger.param.trigger('value')
|
|
887
|
+
else:
|
|
888
|
+
value = await obj
|
|
889
|
+
if self._current_task is task:
|
|
890
|
+
self._current_ = value
|
|
891
|
+
self._trigger.param.trigger('value')
|
|
892
|
+
|
|
893
|
+
def _lazy_resolve(self, obj):
|
|
894
|
+
from .parameterized import async_executor
|
|
895
|
+
if inspect.isgenerator(obj):
|
|
896
|
+
obj = _to_async_gen(obj)
|
|
897
|
+
async_executor(partial(self._resolve_async, obj))
|
|
898
|
+
|
|
778
899
|
def _resolve(self):
|
|
779
900
|
if self._error_state:
|
|
780
901
|
raise self._error_state
|
|
@@ -787,9 +908,13 @@ class rx:
|
|
|
787
908
|
operation = self._operation
|
|
788
909
|
if operation:
|
|
789
910
|
obj = self._eval_operation(obj, operation)
|
|
911
|
+
if inspect.isasyncgen(obj) or inspect.iscoroutine(obj) or inspect.isgenerator(obj):
|
|
912
|
+
self._lazy_resolve(obj)
|
|
913
|
+
obj = Skip
|
|
790
914
|
if obj is Skip:
|
|
791
915
|
raise Skip
|
|
792
916
|
except Skip:
|
|
917
|
+
self._dirty = False
|
|
793
918
|
return self._current_
|
|
794
919
|
except Exception as e:
|
|
795
920
|
self._error_state = e
|
|
@@ -22,7 +22,7 @@ def specific_filter():
|
|
|
22
22
|
class TestDeprecateParameter:
|
|
23
23
|
|
|
24
24
|
def test_deprecate_posargs_Parameter(self):
|
|
25
|
-
with pytest.raises(param._utils.
|
|
25
|
+
with pytest.raises(param._utils.ParamDeprecationWarning):
|
|
26
26
|
param.Parameter(1, 'doc')
|
|
27
27
|
|
|
28
28
|
def test_deprecate_List_class_(self):
|
|
@@ -103,11 +103,11 @@ class TestDeprecateParameterizedModule:
|
|
|
103
103
|
param.parameterized.all_equal(1, 1)
|
|
104
104
|
|
|
105
105
|
def test_deprecate_param_watchers(self):
|
|
106
|
-
with pytest.raises(
|
|
106
|
+
with pytest.raises(param._utils.ParamFutureWarning):
|
|
107
107
|
param.parameterized.Parameterized()._param_watchers
|
|
108
108
|
|
|
109
109
|
def test_deprecate_param_watchers_setter(self):
|
|
110
|
-
with pytest.raises(
|
|
110
|
+
with pytest.raises(param._utils.ParamFutureWarning):
|
|
111
111
|
param.parameterized.Parameterized()._param_watchers = {}
|
|
112
112
|
|
|
113
113
|
def test_param_error_unsafe_ops_before_initialized(self):
|
|
@@ -27,6 +27,8 @@ import pytest
|
|
|
27
27
|
from param.parameterized import Skip
|
|
28
28
|
from param.reactive import bind, rx
|
|
29
29
|
|
|
30
|
+
from .utils import async_wait_until
|
|
31
|
+
|
|
30
32
|
NUMERIC_BINARY_OPERATORS = (
|
|
31
33
|
operator.add, divmod, operator.floordiv, operator.mod, operator.mul,
|
|
32
34
|
operator.pow, operator.sub, operator.truediv,
|
|
@@ -67,6 +69,8 @@ class Parameters(param.Parameterized):
|
|
|
67
69
|
|
|
68
70
|
boolean = param.Boolean(default=False)
|
|
69
71
|
|
|
72
|
+
parameter = param.Parameter(allow_refs=False)
|
|
73
|
+
|
|
70
74
|
event = param.Event()
|
|
71
75
|
|
|
72
76
|
@param.depends('integer')
|
|
@@ -305,6 +309,19 @@ def test_reactive_map():
|
|
|
305
309
|
i.rx.value = range(1, 4)
|
|
306
310
|
assert b.rx.value == [2, 4, 6]
|
|
307
311
|
|
|
312
|
+
async def test_reactive_async_map():
|
|
313
|
+
i = rx(range(3))
|
|
314
|
+
async def mul(x):
|
|
315
|
+
await asyncio.sleep(0.05)
|
|
316
|
+
return x*2
|
|
317
|
+
b = i.rx.map(mul)
|
|
318
|
+
assert b.rx.value is param.Undefined
|
|
319
|
+
await async_wait_until(lambda: b.rx.value == [0, 2, 4])
|
|
320
|
+
i.rx.value = range(1, 4)
|
|
321
|
+
assert b.rx.value == [0, 2, 4]
|
|
322
|
+
await async_wait_until(lambda: b.rx.value == [2, 4, 6])
|
|
323
|
+
assert b.rx.value == [2, 4, 6]
|
|
324
|
+
|
|
308
325
|
def test_reactive_map_args():
|
|
309
326
|
i = rx(range(3))
|
|
310
327
|
j = rx(2)
|
|
@@ -393,11 +410,7 @@ async def test_reactive_watch_async_on_event():
|
|
|
393
410
|
items = []
|
|
394
411
|
event.rx.watch(items.append)
|
|
395
412
|
p.param.trigger('event')
|
|
396
|
-
|
|
397
|
-
await asyncio.sleep(0.05)
|
|
398
|
-
if items == [True]:
|
|
399
|
-
break
|
|
400
|
-
assert items == [True]
|
|
413
|
+
await async_wait_until(lambda: items == [True])
|
|
401
414
|
|
|
402
415
|
def test_reactive_set_value_non_root_raises():
|
|
403
416
|
rx_val = rx(1) + 1
|
|
@@ -433,6 +446,96 @@ def test_reactive_when_initial():
|
|
|
433
446
|
p.param.trigger('event')
|
|
434
447
|
assert integer.rx.value == 4
|
|
435
448
|
|
|
449
|
+
def test_reactive_resolve():
|
|
450
|
+
p = Parameters(integer=3)
|
|
451
|
+
p2 = Parameters(parameter=p.param.integer)
|
|
452
|
+
|
|
453
|
+
prx = p2.param.parameter.rx()
|
|
454
|
+
assert prx.rx.value is p.param.integer
|
|
455
|
+
|
|
456
|
+
resolved_prx = prx.rx.resolve()
|
|
457
|
+
assert resolved_prx.rx.value == 3
|
|
458
|
+
|
|
459
|
+
changes = []
|
|
460
|
+
resolved_prx.rx.watch(changes.append)
|
|
461
|
+
|
|
462
|
+
# Test changing referenced value
|
|
463
|
+
p.integer = 4
|
|
464
|
+
assert resolved_prx.rx.value == 4
|
|
465
|
+
assert changes == [4]
|
|
466
|
+
|
|
467
|
+
# Test changing reference itself
|
|
468
|
+
p2.parameter = p.param.number
|
|
469
|
+
assert resolved_prx.rx.value == 3.14
|
|
470
|
+
assert changes == [4, 3.14]
|
|
471
|
+
|
|
472
|
+
# Ensure no updates triggered when old reference is updated
|
|
473
|
+
p.integer = 5
|
|
474
|
+
assert resolved_prx.rx.value == 3.14
|
|
475
|
+
assert changes == [4, 3.14]
|
|
476
|
+
|
|
477
|
+
def test_reactive_resolve_nested():
|
|
478
|
+
p = Parameters(integer=3)
|
|
479
|
+
p2 = Parameters(parameter=[p.param.integer])
|
|
480
|
+
|
|
481
|
+
prx = p2.param.parameter.rx()
|
|
482
|
+
assert prx.rx.value == [p.param.integer]
|
|
483
|
+
|
|
484
|
+
resolved_prx = prx.rx.resolve(nested=True)
|
|
485
|
+
assert resolved_prx.rx.value == [3]
|
|
486
|
+
|
|
487
|
+
changes = []
|
|
488
|
+
resolved_prx.rx.watch(changes.append)
|
|
489
|
+
|
|
490
|
+
# Test changing referenced value
|
|
491
|
+
p.integer = 4
|
|
492
|
+
assert resolved_prx.rx.value == [4]
|
|
493
|
+
assert changes == [[4]]
|
|
494
|
+
|
|
495
|
+
# Test changing reference itself
|
|
496
|
+
p2.parameter = [p.param.number]
|
|
497
|
+
assert resolved_prx.rx.value == [3.14]
|
|
498
|
+
assert changes == [[4], [3.14]]
|
|
499
|
+
|
|
500
|
+
# Ensure no updates triggered when old reference is updated
|
|
501
|
+
p.integer = 5
|
|
502
|
+
assert resolved_prx.rx.value == [3.14]
|
|
503
|
+
assert changes == [[4], [3.14]]
|
|
504
|
+
|
|
505
|
+
def test_reactive_resolve_recursive():
|
|
506
|
+
p = Parameters(integer=3)
|
|
507
|
+
p2 = Parameters(parameter=p.param.integer)
|
|
508
|
+
p3 = Parameters(parameter=p2.param.parameter)
|
|
509
|
+
|
|
510
|
+
prx = p3.param.parameter.rx()
|
|
511
|
+
assert prx.rx.value is p2.param.parameter
|
|
512
|
+
|
|
513
|
+
resolved_prx = prx.rx.resolve(recursive=True)
|
|
514
|
+
assert resolved_prx.rx.value == 3
|
|
515
|
+
|
|
516
|
+
changes = []
|
|
517
|
+
resolved_prx.rx.watch(changes.append)
|
|
518
|
+
|
|
519
|
+
# Test changing referenced value
|
|
520
|
+
p.integer = 4
|
|
521
|
+
assert resolved_prx.rx.value == 4
|
|
522
|
+
assert changes == [4]
|
|
523
|
+
|
|
524
|
+
# Test changing recursive reference
|
|
525
|
+
p2.parameter = p.param.number
|
|
526
|
+
assert resolved_prx.rx.value == 3.14
|
|
527
|
+
assert changes == [4, 3.14]
|
|
528
|
+
|
|
529
|
+
# Ensure no updates triggered when old reference is updated
|
|
530
|
+
p.integer = 5
|
|
531
|
+
assert resolved_prx.rx.value == 3.14
|
|
532
|
+
assert changes == [4, 3.14]
|
|
533
|
+
|
|
534
|
+
# Test changing reference itself
|
|
535
|
+
p3.parameter = p.param.string
|
|
536
|
+
assert resolved_prx.rx.value == 'string'
|
|
537
|
+
assert changes == [4, 3.14, 'string']
|
|
538
|
+
|
|
436
539
|
async def test_reactive_async_func():
|
|
437
540
|
async def async_func():
|
|
438
541
|
await asyncio.sleep(0.02)
|
|
@@ -440,8 +543,17 @@ async def test_reactive_async_func():
|
|
|
440
543
|
|
|
441
544
|
async_rx = rx(async_func) + 2
|
|
442
545
|
assert async_rx.rx.value is param.Undefined
|
|
443
|
-
await
|
|
444
|
-
|
|
546
|
+
await async_wait_until(lambda: async_rx.rx.value == 4)
|
|
547
|
+
|
|
548
|
+
async def test_reactive_pipe_async_func():
|
|
549
|
+
async def async_func(value):
|
|
550
|
+
await asyncio.sleep(0.02)
|
|
551
|
+
return value+2
|
|
552
|
+
|
|
553
|
+
async_rx = rx(0).rx.pipe(async_func)
|
|
554
|
+
async_rx.rx.watch()
|
|
555
|
+
assert async_rx.rx.value is param.Undefined
|
|
556
|
+
await async_wait_until(lambda: async_rx.rx.value == 2)
|
|
445
557
|
|
|
446
558
|
async def test_reactive_gen():
|
|
447
559
|
def gen():
|
|
@@ -453,11 +565,25 @@ async def test_reactive_gen():
|
|
|
453
565
|
assert rxgen.rx.value is param.Undefined
|
|
454
566
|
await asyncio.sleep(0.04)
|
|
455
567
|
assert rxgen.rx.value == 1
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
568
|
+
await async_wait_until(lambda: rxgen.rx.value == 2)
|
|
569
|
+
|
|
570
|
+
async def test_reactive_gen_pipe():
|
|
571
|
+
def gen(val):
|
|
572
|
+
yield val+1
|
|
573
|
+
time.sleep(0.05)
|
|
574
|
+
yield val+2
|
|
575
|
+
|
|
576
|
+
rxv = rx(0)
|
|
577
|
+
rxgen = rxv.rx.pipe(gen)
|
|
578
|
+
rxgen.rx.watch()
|
|
579
|
+
assert rxgen.rx.value is param.Undefined
|
|
580
|
+
await asyncio.sleep(0.04)
|
|
581
|
+
assert rxgen.rx.value == 1
|
|
582
|
+
await async_wait_until(lambda: rxgen.rx.value == 2)
|
|
583
|
+
rxv.rx.value = 2
|
|
584
|
+
await asyncio.sleep(0.04)
|
|
585
|
+
assert rxgen.rx.value == 3
|
|
586
|
+
await async_wait_until(lambda: rxgen.rx.value == 4)
|
|
461
587
|
|
|
462
588
|
async def test_reactive_gen_with_dep():
|
|
463
589
|
def gen(i):
|
|
@@ -473,24 +599,53 @@ async def test_reactive_gen_with_dep():
|
|
|
473
599
|
irx.rx.value = 3
|
|
474
600
|
await asyncio.sleep(0.04)
|
|
475
601
|
assert rxgen.rx.value == 4
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
602
|
+
await async_wait_until(lambda: rxgen.rx.value == 5)
|
|
603
|
+
|
|
604
|
+
async def test_reactive_gen_pipe_with_dep():
|
|
605
|
+
def gen(value, i):
|
|
606
|
+
yield value+i+1
|
|
607
|
+
time.sleep(0.05)
|
|
608
|
+
yield value+i+2
|
|
609
|
+
|
|
610
|
+
irx = rx(0)
|
|
611
|
+
rxv = rx(0)
|
|
612
|
+
rxgen = rxv.rx.pipe(bind(gen, irx))
|
|
613
|
+
rxgen.rx.watch()
|
|
614
|
+
assert rxgen.rx.value is param.Undefined
|
|
615
|
+
await asyncio.sleep(0.04)
|
|
616
|
+
assert rxgen.rx.value == 1
|
|
617
|
+
irx.rx.value = 3
|
|
618
|
+
await asyncio.sleep(0.04)
|
|
619
|
+
assert rxgen.rx.value == 4
|
|
620
|
+
await async_wait_until(lambda: rxgen.rx.value == 5)
|
|
621
|
+
rxv.rx.value = 5
|
|
622
|
+
await asyncio.sleep(0.04)
|
|
623
|
+
assert rxgen.rx.value == 9
|
|
624
|
+
await async_wait_until(lambda: rxgen.rx.value == 10)
|
|
481
625
|
|
|
482
626
|
async def test_reactive_async_gen():
|
|
483
627
|
async def gen():
|
|
484
628
|
yield 1
|
|
485
|
-
await asyncio.sleep(0.
|
|
629
|
+
await asyncio.sleep(0.05)
|
|
486
630
|
yield 2
|
|
487
631
|
|
|
488
632
|
rxgen = rx(gen)
|
|
489
633
|
assert rxgen.rx.value is param.Undefined
|
|
490
|
-
await asyncio.sleep(0.
|
|
634
|
+
await asyncio.sleep(0.04)
|
|
491
635
|
assert rxgen.rx.value == 1
|
|
492
|
-
await
|
|
493
|
-
|
|
636
|
+
await async_wait_until(lambda: rxgen.rx.value == 2)
|
|
637
|
+
|
|
638
|
+
async def test_reactive_async_gen_pipe():
|
|
639
|
+
async def gen(value):
|
|
640
|
+
yield value + 1
|
|
641
|
+
await asyncio.sleep(0.05)
|
|
642
|
+
yield value + 2
|
|
643
|
+
|
|
644
|
+
rxgen = rx(0).rx.pipe(gen)
|
|
645
|
+
assert rxgen.rx.value is param.Undefined
|
|
646
|
+
await asyncio.sleep(0.04)
|
|
647
|
+
assert rxgen.rx.value == 1
|
|
648
|
+
await async_wait_until(lambda: rxgen.rx.value == 2)
|
|
494
649
|
|
|
495
650
|
async def test_reactive_async_gen_with_dep():
|
|
496
651
|
async def gen(i):
|
|
@@ -508,3 +663,81 @@ async def test_reactive_async_gen_with_dep():
|
|
|
508
663
|
irx.rx.value = 4
|
|
509
664
|
await asyncio.sleep(0.1)
|
|
510
665
|
assert rxgen.rx.value == 5
|
|
666
|
+
|
|
667
|
+
async def test_reactive_async_gen_pipe_with_dep():
|
|
668
|
+
async def gen(value, i):
|
|
669
|
+
yield value+i+1
|
|
670
|
+
await asyncio.sleep(0.05)
|
|
671
|
+
yield value+i+2
|
|
672
|
+
|
|
673
|
+
irx = rx(0)
|
|
674
|
+
rxv = rx(0)
|
|
675
|
+
rxgen = rxv.rx.pipe(bind(gen, i=irx))
|
|
676
|
+
rxgen.rx.watch()
|
|
677
|
+
assert rxgen.rx.value is param.Undefined
|
|
678
|
+
await asyncio.sleep(0.04)
|
|
679
|
+
assert rxgen.rx.value == 1
|
|
680
|
+
irx.rx.value = 3
|
|
681
|
+
await asyncio.sleep(0.04)
|
|
682
|
+
irx.rx.value = 4
|
|
683
|
+
await asyncio.sleep(0.04)
|
|
684
|
+
assert rxgen.rx.value == 5
|
|
685
|
+
rxv.rx.value = 5
|
|
686
|
+
await asyncio.sleep(0.04)
|
|
687
|
+
assert rxgen.rx.value == 10
|
|
688
|
+
await async_wait_until(lambda: rxgen.rx.value == 11)
|
|
689
|
+
|
|
690
|
+
def test_root_invalidation():
|
|
691
|
+
arx = rx('a')
|
|
692
|
+
brx = rx('b')
|
|
693
|
+
|
|
694
|
+
computed = []
|
|
695
|
+
def debug(value, info):
|
|
696
|
+
computed.append(info)
|
|
697
|
+
return value
|
|
698
|
+
|
|
699
|
+
expr = arx.title().rx.pipe(debug, '1') + brx.title().rx.pipe(debug, '2')
|
|
700
|
+
|
|
701
|
+
assert expr.rx.value == 'AB'
|
|
702
|
+
assert computed == ['1', '2']
|
|
703
|
+
|
|
704
|
+
brx.rx.value = 'c'
|
|
705
|
+
|
|
706
|
+
assert expr.rx.value == 'AC'
|
|
707
|
+
assert computed == ['1', '2', '2']
|
|
708
|
+
|
|
709
|
+
arx.rx.value = 'd'
|
|
710
|
+
assert expr.rx.value == 'DC'
|
|
711
|
+
assert computed == ['1', '2', '2', '1']
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
def test_ensure_ref_can_update_by_watcher_of_same_parameter():
|
|
715
|
+
# https://github.com/holoviz/param/pull/929
|
|
716
|
+
|
|
717
|
+
class W(param.Parameterized):
|
|
718
|
+
value = param.String()
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
class T(param.Parameterized):
|
|
722
|
+
lst = param.List(allow_refs=True, allow_None=True)
|
|
723
|
+
|
|
724
|
+
@param.depends("lst", watch=True)
|
|
725
|
+
def test(self):
|
|
726
|
+
lst = self.lst or range(5)
|
|
727
|
+
items = [W(value=str(i)) for i in lst]
|
|
728
|
+
with param.discard_events(self):
|
|
729
|
+
self.lst = param.rx(items).rx.resolve()
|
|
730
|
+
self.items = items
|
|
731
|
+
|
|
732
|
+
def transform(obj):
|
|
733
|
+
if isinstance(obj, W):
|
|
734
|
+
return obj.param.value
|
|
735
|
+
return obj
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
param.reactive.register_reference_transform(transform)
|
|
739
|
+
|
|
740
|
+
t = T()
|
|
741
|
+
t.lst = list("ABCDE")
|
|
742
|
+
t.items[1].value = "TEST"
|
|
743
|
+
assert t.lst[1] == "TEST"
|
|
@@ -97,17 +97,17 @@ def test_signature_position_keywords():
|
|
|
97
97
|
def test_signature_warning_by_position():
|
|
98
98
|
# Simple test as it's tricky to automatically test all the Parameters
|
|
99
99
|
with pytest.warns(
|
|
100
|
-
param._utils.
|
|
100
|
+
param._utils.ParamDeprecationWarning,
|
|
101
101
|
match=r"Passing 'objects' as positional argument\(s\) to 'param.Selector' has been deprecated since Param 2.0.0 and will raise an error in a future version, please pass them as keyword arguments"
|
|
102
102
|
):
|
|
103
103
|
param.Selector([0, 1]) # objects
|
|
104
104
|
with pytest.warns(
|
|
105
|
-
param._utils.
|
|
105
|
+
param._utils.ParamDeprecationWarning,
|
|
106
106
|
match=r"Passing 'class_' as positional argument\(s\) to 'param.ClassSelector' has been deprecated since Param 2.0.0 and will raise an error in a future version, please pass them as keyword arguments"
|
|
107
107
|
):
|
|
108
108
|
param.ClassSelector(int) # class_
|
|
109
109
|
with pytest.warns(
|
|
110
|
-
param._utils.
|
|
110
|
+
param._utils.ParamDeprecationWarning,
|
|
111
111
|
match=r"Passing 'bounds, softbounds' as positional argument\(s\) to 'param.Number' has been deprecated since Param 2.0.0 and will raise an error in a future version, please pass them as keyword arguments"
|
|
112
112
|
):
|
|
113
113
|
param.Number(1, (0, 2), (0, 2)) # default (OK), bounds (not OK), softbounds (not OK)
|
|
@@ -652,7 +652,7 @@ class TestWatch(unittest.TestCase):
|
|
|
652
652
|
|
|
653
653
|
obj.param.watch(lambda: '', ['a', 'b'])
|
|
654
654
|
|
|
655
|
-
with pytest.warns(
|
|
655
|
+
with pytest.warns(param._utils.ParamFutureWarning):
|
|
656
656
|
pw = obj._param_watchers
|
|
657
657
|
assert isinstance(pw, dict)
|
|
658
658
|
for pname in ('a', 'b'):
|
|
@@ -667,7 +667,7 @@ class TestWatch(unittest.TestCase):
|
|
|
667
667
|
|
|
668
668
|
obj.param.watch(accumulator, ['a', 'b'])
|
|
669
669
|
|
|
670
|
-
with pytest.warns(
|
|
670
|
+
with pytest.warns(param._utils.ParamFutureWarning):
|
|
671
671
|
pw = obj._param_watchers
|
|
672
672
|
del pw['a']
|
|
673
673
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import logging
|
|
3
|
+
import time
|
|
2
4
|
|
|
3
5
|
from contextlib import contextmanager
|
|
4
6
|
|
|
@@ -111,3 +113,64 @@ def warnings_as_excepts(match=None):
|
|
|
111
113
|
raise ValueError(f'Exception emitted {str(e)!r} does not contain {match!r}')
|
|
112
114
|
finally:
|
|
113
115
|
param.parameterized.warnings_as_exceptions = orig
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
async def async_wait_until(fn, timeout=5000, interval=100):
|
|
119
|
+
"""
|
|
120
|
+
Exercise a test function in a loop until it evaluates to True
|
|
121
|
+
or times out.
|
|
122
|
+
|
|
123
|
+
The function can either be a simple lambda that returns True or False:
|
|
124
|
+
>>> await async_wait_until(lambda: x.values() == ['x'])
|
|
125
|
+
|
|
126
|
+
Or a defined function with an assert:
|
|
127
|
+
>>> async def _()
|
|
128
|
+
>>> assert x.values() == ['x']
|
|
129
|
+
>>> await async_wait_until(_)
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
fn : callable
|
|
134
|
+
Callback
|
|
135
|
+
timeout : int, optional
|
|
136
|
+
Total timeout in milliseconds, by default 5000
|
|
137
|
+
interval : int, optional
|
|
138
|
+
Waiting interval, by default 100
|
|
139
|
+
|
|
140
|
+
Adapted from pytest-qt.
|
|
141
|
+
"""
|
|
142
|
+
# Hide this function traceback from the pytest output if the test fails
|
|
143
|
+
__tracebackhide__ = True
|
|
144
|
+
|
|
145
|
+
start = time.monotonic()
|
|
146
|
+
|
|
147
|
+
def timed_out():
|
|
148
|
+
elapsed = time.monotonic() - start
|
|
149
|
+
elapsed_ms = elapsed * 1000
|
|
150
|
+
return elapsed_ms > timeout
|
|
151
|
+
|
|
152
|
+
timeout_msg = f"async_wait_until timed out in {timeout} milliseconds"
|
|
153
|
+
|
|
154
|
+
while True:
|
|
155
|
+
try:
|
|
156
|
+
result = fn()
|
|
157
|
+
if asyncio.iscoroutine(result):
|
|
158
|
+
result = await result
|
|
159
|
+
except AssertionError as e:
|
|
160
|
+
if timed_out():
|
|
161
|
+
raise TimeoutError(timeout_msg) from e
|
|
162
|
+
else:
|
|
163
|
+
if result not in (None, True, False):
|
|
164
|
+
raise ValueError(
|
|
165
|
+
"`async_wait_until` callback must return None, True, or "
|
|
166
|
+
f"False, returned {result!r}"
|
|
167
|
+
)
|
|
168
|
+
# None is returned when the function has an assert
|
|
169
|
+
if result is None:
|
|
170
|
+
return
|
|
171
|
+
# When the function returns True or False
|
|
172
|
+
if result:
|
|
173
|
+
return
|
|
174
|
+
if timed_out():
|
|
175
|
+
raise TimeoutError(timeout_msg)
|
|
176
|
+
await asyncio.sleep(interval / 1000)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|