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.
Files changed (69) hide show
  1. {param-2.0.2rc1 → param-2.1.0a2}/PKG-INFO +3 -3
  2. {param-2.0.2rc1 → param-2.1.0a2}/README.md +1 -1
  3. {param-2.0.2rc1 → param-2.1.0a2}/param/__init__.py +3 -2
  4. {param-2.0.2rc1 → param-2.1.0a2}/param/_utils.py +51 -1
  5. {param-2.0.2rc1 → param-2.1.0a2}/param/_version.py +2 -2
  6. {param-2.0.2rc1 → param-2.1.0a2}/param/depends.py +26 -2
  7. {param-2.0.2rc1 → param-2.1.0a2}/param/ipython.py +28 -5
  8. {param-2.0.2rc1 → param-2.1.0a2}/param/parameterized.py +73 -40
  9. {param-2.0.2rc1 → param-2.1.0a2}/param/parameters.py +8 -7
  10. {param-2.0.2rc1 → param-2.1.0a2}/param/reactive.py +142 -43
  11. {param-2.0.2rc1 → param-2.1.0a2}/pyproject.toml +1 -1
  12. param-2.1.0a2/tests/conftest.py +13 -0
  13. {param-2.0.2rc1 → param-2.1.0a2}/tests/testfiledeserialization.py +2 -2
  14. {param-2.0.2rc1 → param-2.1.0a2}/tests/testparamdepends.py +126 -55
  15. {param-2.0.2rc1 → param-2.1.0a2}/tests/testparameterizedobject.py +13 -19
  16. {param-2.0.2rc1 → param-2.1.0a2}/tests/testreactive.py +183 -0
  17. {param-2.0.2rc1 → param-2.1.0a2}/tests/testrefs.py +138 -7
  18. {param-2.0.2rc1 → param-2.1.0a2}/tests/testwatch.py +14 -1
  19. param-2.0.2rc1/tests/conftest.py +0 -30
  20. {param-2.0.2rc1 → param-2.1.0a2}/.gitignore +0 -0
  21. {param-2.0.2rc1 → param-2.1.0a2}/LICENSE.txt +0 -0
  22. {param-2.0.2rc1 → param-2.1.0a2}/numbergen/__init__.py +0 -0
  23. {param-2.0.2rc1 → param-2.1.0a2}/param/display.py +0 -0
  24. {param-2.0.2rc1 → param-2.1.0a2}/param/serializer.py +0 -0
  25. {param-2.0.2rc1 → param-2.1.0a2}/param/version.py +0 -0
  26. {param-2.0.2rc1 → param-2.1.0a2}/tests/__init__.py +0 -0
  27. {param-2.0.2rc1 → param-2.1.0a2}/tests/testaddparameter.py +0 -0
  28. {param-2.0.2rc1 → param-2.1.0a2}/tests/testbind.py +0 -0
  29. {param-2.0.2rc1 → param-2.1.0a2}/tests/testbooleanparam.py +0 -0
  30. {param-2.0.2rc1 → param-2.1.0a2}/tests/testbytesparam.py +0 -0
  31. {param-2.0.2rc1 → param-2.1.0a2}/tests/testcalendardateparam.py +0 -0
  32. {param-2.0.2rc1 → param-2.1.0a2}/tests/testcalendardaterangeparam.py +0 -0
  33. {param-2.0.2rc1 → param-2.1.0a2}/tests/testcallable.py +0 -0
  34. {param-2.0.2rc1 → param-2.1.0a2}/tests/testclassselector.py +0 -0
  35. {param-2.0.2rc1 → param-2.1.0a2}/tests/testcolorparameter.py +0 -0
  36. {param-2.0.2rc1 → param-2.1.0a2}/tests/testcomparator.py +0 -0
  37. {param-2.0.2rc1 → param-2.1.0a2}/tests/testcompositeparams.py +0 -0
  38. {param-2.0.2rc1 → param-2.1.0a2}/tests/testcustomparam.py +0 -0
  39. {param-2.0.2rc1 → param-2.1.0a2}/tests/testdateparam.py +0 -0
  40. {param-2.0.2rc1 → param-2.1.0a2}/tests/testdaterangeparam.py +0 -0
  41. {param-2.0.2rc1 → param-2.1.0a2}/tests/testdefaults.py +0 -0
  42. {param-2.0.2rc1 → param-2.1.0a2}/tests/testdeprecations.py +0 -0
  43. {param-2.0.2rc1 → param-2.1.0a2}/tests/testdynamicparams.py +0 -0
  44. {param-2.0.2rc1 → param-2.1.0a2}/tests/testfileselector.py +0 -0
  45. {param-2.0.2rc1 → param-2.1.0a2}/tests/testipythonmagic.py +0 -0
  46. {param-2.0.2rc1 → param-2.1.0a2}/tests/testjsonserialization.py +0 -0
  47. {param-2.0.2rc1 → param-2.1.0a2}/tests/testlist.py +0 -0
  48. {param-2.0.2rc1 → param-2.1.0a2}/tests/testlistselector.py +0 -0
  49. {param-2.0.2rc1 → param-2.1.0a2}/tests/testmultifileselector.py +0 -0
  50. {param-2.0.2rc1 → param-2.1.0a2}/tests/testnumbergen.py +0 -0
  51. {param-2.0.2rc1 → param-2.1.0a2}/tests/testnumberparameter.py +0 -0
  52. {param-2.0.2rc1 → param-2.1.0a2}/tests/testnumpy.py +0 -0
  53. {param-2.0.2rc1 → param-2.1.0a2}/tests/testobjectselector.py +0 -0
  54. {param-2.0.2rc1 → param-2.1.0a2}/tests/testpandas.py +0 -0
  55. {param-2.0.2rc1 → param-2.1.0a2}/tests/testparameterizedrepr.py +0 -0
  56. {param-2.0.2rc1 → param-2.1.0a2}/tests/testparamoutput.py +0 -0
  57. {param-2.0.2rc1 → param-2.1.0a2}/tests/testparamunion.py +0 -0
  58. {param-2.0.2rc1 → param-2.1.0a2}/tests/testpathparam.py +0 -0
  59. {param-2.0.2rc1 → param-2.1.0a2}/tests/testpickle.py +0 -0
  60. {param-2.0.2rc1 → param-2.1.0a2}/tests/testrangeparameter.py +0 -0
  61. {param-2.0.2rc1 → param-2.1.0a2}/tests/testreprhtml.py +0 -0
  62. {param-2.0.2rc1 → param-2.1.0a2}/tests/testselector.py +0 -0
  63. {param-2.0.2rc1 → param-2.1.0a2}/tests/testsignatures.py +0 -0
  64. {param-2.0.2rc1 → param-2.1.0a2}/tests/teststringparam.py +0 -0
  65. {param-2.0.2rc1 → param-2.1.0a2}/tests/testtimedependent.py +0 -0
  66. {param-2.0.2rc1 → param-2.1.0a2}/tests/testtupleparam.py +0 -0
  67. {param-2.0.2rc1 → param-2.1.0a2}/tests/testutils.py +0 -0
  68. {param-2.0.2rc1 → param-2.1.0a2}/tests/testversion.py +0 -0
  69. {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.0.2rc1
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 | [![Linux/MacOS/Windows Build Status](https://github.com/holoviz/param/workflows/pytest/badge.svg)](https://github.com/holoviz/param/actions/workflows/test.yml)
77
+ | Build Status | [![Linux/MacOS/Windows Build Status](https://github.com/holoviz/param/workflows/tests/badge.svg)](https://github.com/holoviz/param/actions/workflows/test.yaml)
78
78
  | Coverage | [![codecov](https://codecov.io/gh/holoviz/param/branch/main/graph/badge.svg)](https://codecov.io/gh/holoviz/param) ||
79
79
  | Latest dev release | [![Github tag](https://img.shields.io/github/v/tag/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/tags) [![dev-site](https://img.shields.io/website-up-down-green-red/https/holoviz-dev.github.io/param.svg?label=dev%20website)](https://holoviz-dev.github.io/param/) |
80
80
  | Latest release | [![Github release](https://img.shields.io/github/release/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/releases) [![PyPI version](https://img.shields.io/pypi/v/param.svg?colorB=cc77dd)](https://pypi.python.org/pypi/param) [![param version](https://img.shields.io/conda/v/pyviz/param.svg?colorB=4488ff&style=flat)](https://anaconda.org/pyviz/param) [![conda-forge version](https://img.shields.io/conda/v/conda-forge/param.svg?label=conda%7Cconda-forge&colorB=4488ff)](https://anaconda.org/conda-forge/param) [![defaults version](https://img.shields.io/conda/v/anaconda/param.svg?label=conda%7Cdefaults&style=flat&colorB=4488ff)](https://anaconda.org/anaconda/param) |
@@ -2,7 +2,7 @@
2
2
 
3
3
  | | |
4
4
  | --- | --- |
5
- | Build Status | [![Linux/MacOS/Windows Build Status](https://github.com/holoviz/param/workflows/pytest/badge.svg)](https://github.com/holoviz/param/actions/workflows/test.yml)
5
+ | Build Status | [![Linux/MacOS/Windows Build Status](https://github.com/holoviz/param/workflows/tests/badge.svg)](https://github.com/holoviz/param/actions/workflows/test.yaml)
6
6
  | Coverage | [![codecov](https://codecov.io/gh/holoviz/param/branch/main/graph/badge.svg)](https://codecov.io/gh/holoviz/param) ||
7
7
  | Latest dev release | [![Github tag](https://img.shields.io/github/v/tag/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/tags) [![dev-site](https://img.shields.io/website-up-down-green-red/https/holoviz-dev.github.io/param.svg?label=dev%20website)](https://holoviz-dev.github.io/param/) |
8
8
  | Latest release | [![Github release](https://img.shields.io/github/release/holoviz/param.svg?label=tag&colorB=11ccbb)](https://github.com/holoviz/param/releases) [![PyPI version](https://img.shields.io/pypi/v/param.svg?colorB=cc77dd)](https://pypi.python.org/pypi/param) [![param version](https://img.shields.io/conda/v/pyviz/param.svg?colorB=4488ff&style=flat)](https://anaconda.org/pyviz/param) [![conda-forge version](https://img.shields.io/conda/v/conda-forge/param.svg?label=conda%7Cconda-forge&colorB=4488ff)](https://anaconda.org/conda-forge/param) [![defaults version](https://img.shields.io/conda/v/anaconda/param.svg?label=conda%7Cdefaults&style=flat&colorB=4488ff)](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, ParamOverrides,
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), (abc.MutableSequence, abc.MutableSet, abc.MutableMapping))
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())
@@ -12,5 +12,5 @@ __version__: str
12
12
  __version_tuple__: VERSION_TUPLE
13
13
  version_tuple: VERSION_TUPLE
14
14
 
15
- __version__ = version = '2.0.2rc1'
16
- __version_tuple__ = version_tuple = (2, 0, 2)
15
+ __version__ = version = '2.1.0a2'
16
+ __version_tuple__ = version_tuple = (2, 1, 0)
@@ -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 iscoroutinefunction(func):
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 iscoroutinefunction(func):
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.update(cb())
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.update(cb())
381
+ if handle is not None:
382
+ handle.update(cb())
380
383
  try:
381
- handle = display(cb(), display_id=uuid.uuid4().hex) # noqa
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
- param_pager = None
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
- if hasattr(value, '_dinfo') or iscoroutinefunction(value):
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, _ = obj.param._resolve_ref(self, 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
- for _, watcher in self_.self._param__private.ref_watchers:
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
- is_async = iscoroutinefunction(ref)
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
- new_val = resolve_value(ref)
2007
- if is_async:
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
- is_async = iscoroutinefunction(value)
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 and not is_async:
2036
+ if not (deps or is_async or is_gen):
2021
2037
  return None, None, value, False
2022
2038
  ref = value
2023
- value = resolve_value(value)
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
- if pname in self_.self._param__private.async_refs:
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
- self_.update({pname: await awaitable})
2044
- except Exception as e:
2045
- raise e
2066
+ try:
2067
+ self_.update({pname: await awaitable})
2068
+ except Skip:
2069
+ pass
2046
2070
  finally:
2047
- del self_.self._param__private.async_refs[pname]
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_, *args, **kwargs):
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
- restore = self_._update(*args, **kwargs)
2279
- return _ParametersRestorer(parameters=self_, restore=restore)
2280
-
2281
- def _update(self_, *args, **kwargs):
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 args:
2286
- if len(args) == 1 and not kwargs:
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
- watcher.fn(*args, **kwargs)
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 numbers
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 collections
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):