param 2.1.0a2__tar.gz → 2.1.1__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.
Files changed (68) hide show
  1. {param-2.1.0a2 → param-2.1.1}/PKG-INFO +44 -11
  2. {param-2.1.0a2 → param-2.1.1}/param/_utils.py +1 -1
  3. {param-2.1.0a2 → param-2.1.1}/param/_version.py +2 -2
  4. {param-2.1.0a2 → param-2.1.1}/param/parameterized.py +17 -13
  5. {param-2.1.0a2 → param-2.1.1}/param/parameters.py +1 -1
  6. {param-2.1.0a2 → param-2.1.1}/param/reactive.py +141 -13
  7. {param-2.1.0a2 → param-2.1.1}/pyproject.toml +2 -1
  8. {param-2.1.0a2 → param-2.1.1}/tests/testdeprecations.py +3 -3
  9. {param-2.1.0a2 → param-2.1.1}/tests/testparameterizedobject.py +20 -0
  10. {param-2.1.0a2 → param-2.1.1}/tests/testreactive.py +282 -23
  11. {param-2.1.0a2 → param-2.1.1}/tests/testrefs.py +48 -1
  12. {param-2.1.0a2 → param-2.1.1}/tests/testsignatures.py +3 -3
  13. {param-2.1.0a2 → param-2.1.1}/tests/testwatch.py +2 -2
  14. {param-2.1.0a2 → param-2.1.1}/tests/utils.py +63 -0
  15. {param-2.1.0a2 → param-2.1.1}/.gitignore +0 -0
  16. {param-2.1.0a2 → param-2.1.1}/LICENSE.txt +0 -0
  17. {param-2.1.0a2 → param-2.1.1}/README.md +0 -0
  18. {param-2.1.0a2 → param-2.1.1}/numbergen/__init__.py +0 -0
  19. {param-2.1.0a2 → param-2.1.1}/param/__init__.py +0 -0
  20. {param-2.1.0a2 → param-2.1.1}/param/depends.py +0 -0
  21. {param-2.1.0a2 → param-2.1.1}/param/display.py +0 -0
  22. {param-2.1.0a2 → param-2.1.1}/param/ipython.py +0 -0
  23. {param-2.1.0a2 → param-2.1.1}/param/serializer.py +0 -0
  24. {param-2.1.0a2 → param-2.1.1}/param/version.py +0 -0
  25. {param-2.1.0a2 → param-2.1.1}/tests/__init__.py +0 -0
  26. {param-2.1.0a2 → param-2.1.1}/tests/conftest.py +0 -0
  27. {param-2.1.0a2 → param-2.1.1}/tests/testaddparameter.py +0 -0
  28. {param-2.1.0a2 → param-2.1.1}/tests/testbind.py +0 -0
  29. {param-2.1.0a2 → param-2.1.1}/tests/testbooleanparam.py +0 -0
  30. {param-2.1.0a2 → param-2.1.1}/tests/testbytesparam.py +0 -0
  31. {param-2.1.0a2 → param-2.1.1}/tests/testcalendardateparam.py +0 -0
  32. {param-2.1.0a2 → param-2.1.1}/tests/testcalendardaterangeparam.py +0 -0
  33. {param-2.1.0a2 → param-2.1.1}/tests/testcallable.py +0 -0
  34. {param-2.1.0a2 → param-2.1.1}/tests/testclassselector.py +0 -0
  35. {param-2.1.0a2 → param-2.1.1}/tests/testcolorparameter.py +0 -0
  36. {param-2.1.0a2 → param-2.1.1}/tests/testcomparator.py +0 -0
  37. {param-2.1.0a2 → param-2.1.1}/tests/testcompositeparams.py +0 -0
  38. {param-2.1.0a2 → param-2.1.1}/tests/testcustomparam.py +0 -0
  39. {param-2.1.0a2 → param-2.1.1}/tests/testdateparam.py +0 -0
  40. {param-2.1.0a2 → param-2.1.1}/tests/testdaterangeparam.py +0 -0
  41. {param-2.1.0a2 → param-2.1.1}/tests/testdefaults.py +0 -0
  42. {param-2.1.0a2 → param-2.1.1}/tests/testdynamicparams.py +0 -0
  43. {param-2.1.0a2 → param-2.1.1}/tests/testfiledeserialization.py +0 -0
  44. {param-2.1.0a2 → param-2.1.1}/tests/testfileselector.py +0 -0
  45. {param-2.1.0a2 → param-2.1.1}/tests/testipythonmagic.py +0 -0
  46. {param-2.1.0a2 → param-2.1.1}/tests/testjsonserialization.py +0 -0
  47. {param-2.1.0a2 → param-2.1.1}/tests/testlist.py +0 -0
  48. {param-2.1.0a2 → param-2.1.1}/tests/testlistselector.py +0 -0
  49. {param-2.1.0a2 → param-2.1.1}/tests/testmultifileselector.py +0 -0
  50. {param-2.1.0a2 → param-2.1.1}/tests/testnumbergen.py +0 -0
  51. {param-2.1.0a2 → param-2.1.1}/tests/testnumberparameter.py +0 -0
  52. {param-2.1.0a2 → param-2.1.1}/tests/testnumpy.py +0 -0
  53. {param-2.1.0a2 → param-2.1.1}/tests/testobjectselector.py +0 -0
  54. {param-2.1.0a2 → param-2.1.1}/tests/testpandas.py +0 -0
  55. {param-2.1.0a2 → param-2.1.1}/tests/testparamdepends.py +0 -0
  56. {param-2.1.0a2 → param-2.1.1}/tests/testparameterizedrepr.py +0 -0
  57. {param-2.1.0a2 → param-2.1.1}/tests/testparamoutput.py +0 -0
  58. {param-2.1.0a2 → param-2.1.1}/tests/testparamunion.py +0 -0
  59. {param-2.1.0a2 → param-2.1.1}/tests/testpathparam.py +0 -0
  60. {param-2.1.0a2 → param-2.1.1}/tests/testpickle.py +0 -0
  61. {param-2.1.0a2 → param-2.1.1}/tests/testrangeparameter.py +0 -0
  62. {param-2.1.0a2 → param-2.1.1}/tests/testreprhtml.py +0 -0
  63. {param-2.1.0a2 → param-2.1.1}/tests/testselector.py +0 -0
  64. {param-2.1.0a2 → param-2.1.1}/tests/teststringparam.py +0 -0
  65. {param-2.1.0a2 → param-2.1.1}/tests/testtimedependent.py +0 -0
  66. {param-2.1.0a2 → param-2.1.1}/tests/testtupleparam.py +0 -0
  67. {param-2.1.0a2 → param-2.1.1}/tests/testutils.py +0 -0
  68. {param-2.1.0a2 → param-2.1.1}/tests/testversion.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.3
2
2
  Name: param
3
- Version: 2.1.0a2
3
+ Version: 2.1.1
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,34 @@ 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: param[doc]; extra == 'all'
31
- Requires-Dist: param[lint]; extra == 'all'
32
- Requires-Dist: param[tests-full]; extra == 'all'
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: sphinx-remove-toctrees; extra == 'all'
51
+ Requires-Dist: tables; extra == 'all'
52
+ Requires-Dist: xlrd; extra == 'all'
33
53
  Provides-Extra: doc
54
+ Requires-Dist: aiohttp; extra == 'doc'
34
55
  Requires-Dist: nbsite==0.8.4; extra == 'doc'
35
- Requires-Dist: param[examples]; extra == 'doc'
56
+ Requires-Dist: pandas; extra == 'doc'
57
+ Requires-Dist: panel; extra == 'doc'
36
58
  Requires-Dist: sphinx-remove-toctrees; extra == 'doc'
37
59
  Provides-Extra: examples
38
60
  Requires-Dist: aiohttp; extra == 'examples'
@@ -52,22 +74,33 @@ Requires-Dist: pyarrow; extra == 'tests-deser'
52
74
  Requires-Dist: tables; extra == 'tests-deser'
53
75
  Requires-Dist: xlrd; extra == 'tests-deser'
54
76
  Provides-Extra: tests-examples
77
+ Requires-Dist: aiohttp; extra == 'tests-examples'
55
78
  Requires-Dist: nbval; extra == 'tests-examples'
56
- Requires-Dist: param[examples]; extra == 'tests-examples'
79
+ Requires-Dist: pandas; extra == 'tests-examples'
80
+ Requires-Dist: panel; extra == 'tests-examples'
81
+ Requires-Dist: pytest; extra == 'tests-examples'
57
82
  Requires-Dist: pytest-asyncio; extra == 'tests-examples'
58
83
  Requires-Dist: pytest-xdist; extra == 'tests-examples'
59
- Requires-Dist: pytest<8.1; extra == 'tests-examples'
60
84
  Provides-Extra: tests-full
85
+ Requires-Dist: aiohttp; extra == 'tests-full'
61
86
  Requires-Dist: cloudpickle; extra == 'tests-full'
87
+ Requires-Dist: coverage[toml]; extra == 'tests-full'
62
88
  Requires-Dist: gmpy; extra == 'tests-full'
63
89
  Requires-Dist: ipython; extra == 'tests-full'
64
90
  Requires-Dist: jsonschema; extra == 'tests-full'
91
+ Requires-Dist: nbval; extra == 'tests-full'
65
92
  Requires-Dist: nest-asyncio; extra == 'tests-full'
66
93
  Requires-Dist: numpy; extra == 'tests-full'
94
+ Requires-Dist: odfpy; extra == 'tests-full'
95
+ Requires-Dist: openpyxl; extra == 'tests-full'
67
96
  Requires-Dist: pandas; extra == 'tests-full'
68
- Requires-Dist: param[tests-deser]; extra == 'tests-full'
69
- Requires-Dist: param[tests-examples]; extra == 'tests-full'
70
- Requires-Dist: param[tests]; extra == 'tests-full'
97
+ Requires-Dist: panel; extra == 'tests-full'
98
+ Requires-Dist: pyarrow; extra == 'tests-full'
99
+ Requires-Dist: pytest; extra == 'tests-full'
100
+ Requires-Dist: pytest-asyncio; extra == 'tests-full'
101
+ Requires-Dist: pytest-xdist; extra == 'tests-full'
102
+ Requires-Dist: tables; extra == 'tests-full'
103
+ Requires-Dist: xlrd; extra == 'tests-full'
71
104
  Description-Content-Type: text/markdown
72
105
 
73
106
  <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
- ParamPendingDeprecationWarning,
101
+ ParamDeprecationWarning,
102
102
  stacklevel=2,
103
103
  )
104
104
 
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.1.0a2'
16
- __version_tuple__ = version_tuple = (2, 1, 0)
15
+ __version__ = version = '2.1.1'
16
+ __version_tuple__ = version_tuple = (2, 1, 1)
@@ -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 isinstance(value, (list, tuple)):
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())
@@ -194,11 +196,11 @@ def resolve_ref(reference, recursive=False):
194
196
  """
195
197
  if recursive:
196
198
  if isinstance(reference, (list, tuple, set)):
197
- return [r for v in reference for r in resolve_ref(v)]
199
+ return [r for v in reference for r in resolve_ref(v, recursive)]
198
200
  elif isinstance(reference, dict):
199
- return [r for kv in reference.items() for o in kv for r in resolve_ref(o)]
201
+ return [r for kv in reference.items() for o in kv for r in resolve_ref(o, recursive)]
200
202
  elif isinstance(reference, slice):
201
- return [r for v in (reference.start, reference.stop, reference.step) for r in resolve_ref(v)]
203
+ return [r for v in (reference.start, reference.stop, reference.step) for r in resolve_ref(v, recursive)]
202
204
  reference = transform_reference(reference)
203
205
  if hasattr(reference, '_dinfo'):
204
206
  dinfo = getattr(reference, '_dinfo', {})
@@ -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 and name not in obj._param__private.syncing:
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
- deps = resolve_ref(ref, self_[pname].nested_refs)
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:
@@ -2100,7 +2104,7 @@ class Parameters:
2100
2104
 
2101
2105
  dict_[key] = new_object
2102
2106
 
2103
- if isinstance(new_object, Parameterized):
2107
+ if isinstance(new_object, Parameterized) and deepcopy:
2104
2108
  global object_count
2105
2109
  object_count += 1
2106
2110
  # Writes over name given to the original object;
@@ -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=FutureWarning)
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=FutureWarning)
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
 
@@ -2150,7 +2150,7 @@ class MultiFileSelector(ListSelector):
2150
2150
 
2151
2151
  class ClassSelector(SelectorBase):
2152
2152
  """
2153
- Parameter allowing selection of either a subclass or an instance of a given set of classes.
2153
+ Parameter allowing selection of either a subclass or an instance of a class or tuple of classes.
2154
2154
  By default, requires an instance, but if is_instance=False, accepts a class instead.
2155
2155
  Both class and instance values respect the instantiate slot, though it matters only
2156
2156
  for is_instance=True.
@@ -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 ._utils import iscoroutinefunction, full_groupby
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
- Simple wrapper to allow updating literal values easily.
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
- Wrapper to allow streaming from generator functions.
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
  """
@@ -157,7 +211,7 @@ class reactive_ops:
157
211
 
158
212
  def buffer(self, n):
159
213
  """
160
- Collects the last n items that were emmitted.
214
+ Collects the last n items that were emitted.
161
215
  """
162
216
  items = []
163
217
  def collect(new, n):
@@ -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
- def apply(vs, *args, **kwargs):
208
- return [func(v, *args, **kwargs) for v in vs]
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,8 +829,11 @@ 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
- for ref in resolve_ref(arg):
836
+ for ref in resolve_ref(arg, recursive=True):
740
837
  if ref not in ps:
741
838
  ps.append(ref)
742
839
 
@@ -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
- params[0].owner.param._watch(self._invalidate_obj, [p.name for p in params], precedence=-1)
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
@@ -829,7 +954,10 @@ class rx:
829
954
  def _callback(self):
830
955
  params = self._params
831
956
  def evaluate(*args, **kwargs):
832
- return self._transform_output(self._current)
957
+ out = self._current
958
+ if self._method:
959
+ out = getattr(out, self._method)
960
+ return self._transform_output(out)
833
961
  if params:
834
962
  return bind(evaluate, *params)
835
963
  return evaluate
@@ -57,7 +57,7 @@ tests-deser = [
57
57
  "tables",
58
58
  ]
59
59
  tests-examples = [
60
- "pytest <8.1", # https://github.com/computationalmodelling/nbval/issues/202
60
+ "pytest",
61
61
  "pytest-asyncio",
62
62
  "pytest-xdist",
63
63
  "nbval",
@@ -210,6 +210,7 @@ python = [
210
210
  "3.9",
211
211
  "3.10",
212
212
  "3.11",
213
+ "3.12",
213
214
  ]
214
215
 
215
216
  [tool.hatch.envs.tests_examples.scripts]
@@ -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.ParamPendingDeprecationWarning):
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(FutureWarning):
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(FutureWarning):
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):
@@ -833,6 +833,26 @@ class TestParameterized(unittest.TestCase):
833
833
  assert t.inst == 'foo'
834
834
  assert t.notinst == 1
835
835
 
836
+ def test_constant_readonly_parameterized(self):
837
+ class ParamClass(param.Parameterized):
838
+ x = param.Number()
839
+
840
+ pc1 = ParamClass(name="FOO1")
841
+ pc2 = ParamClass(name="FOO2")
842
+
843
+ class P(param.Parameterized):
844
+ ro = param.Parameter(pc1, constant=True)
845
+ const = param.Parameter(pc2, readonly=True)
846
+ ro_i = param.Parameter(pc1, constant=True, instantiate=True)
847
+ const_i = param.Parameter(pc2, readonly=True, instantiate=True)
848
+
849
+ p = P()
850
+
851
+ assert p.ro.name == 'FOO1'
852
+ assert p.const.name == 'FOO2'
853
+ assert p.ro_i.name.startswith('ParamClass0')
854
+ assert p.const_i.name == 'FOO2'
855
+
836
856
 
837
857
  class some_fn(param.ParameterizedFunction):
838
858
  __test__ = False