From 730e37f12c6ee80910417cdc2145b595f511c64d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 29 Oct 2025 11:23:49 +0100 Subject: [PATCH 1/5] Add raw wrapper to assign ref-like values without resolving --- param/__init__.py | 3 +- param/parameterized.py | 67 ++++++++++++++++++++- tests/testrefs.py | 129 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 195 insertions(+), 4 deletions(-) diff --git a/param/__init__.py b/param/__init__.py index 2cc84c1fc..1d230cb4b 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -48,7 +48,7 @@ Parameterized, Parameter, Skip, String, ParameterizedFunction, ParamOverrides, Undefined, get_logger, ParameterizedABC, ) -from .parameterized import (output, script_repr, +from .parameterized import (output, script_repr, raw, discard_events, edit_constant) from .parameterized import shared_parameters from .parameterized import logging_level @@ -224,6 +224,7 @@ 'param_union', 'parameterized_class', 'random_seed', + 'raw', 'resolve_path', 'rx', 'script_repr', diff --git a/param/parameterized.py b/param/parameterized.py index dec6498a4..bd45b5484 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -162,6 +162,60 @@ def get_logger(name: Optional[str] = None)->"logging.Logger": # Hook to apply to depends and bind arguments to turn them into valid parameters _reference_transforms = [] +class Raw: + """ + Wrapper type used to assign ref-like objects to Parameters *without* + triggering automatic resolution. + + Normally, when a ref-like value (e.g. a Parameter, reactive expression, + async generator, etc.) is assigned to a Parameter attribute, Param + resolves it to its underlying value. Wrapping the object in ``Raw`` + signals that the value should instead be stored as-is. + + Example + ------- + >>> obj.some_param = param.Raw(other.param.value) + >>> assert obj.some_param is other.param.value + + Notes + ----- + - ``Raw`` is only meaningful at assignment time; the wrapper is + unwrapped and not stored. + - The stored value is the inner object itself, not the ``Raw`` instance. + - This allows safe serialization, forwarding, or deferred resolution of + ref-like values. + """ + + __slots__ = ["value"] + + def __init__(self, value): self.value = value + def __repr__(self): return f"Raw({self.value!r})" + + +def raw(value: Any): + """ + Mark a value to be assigned *as-is*, skipping Param’s automatic + resolution of ref-like objects. + + This allows storing a Parameter, reactive expression, or other + ref-like value directly, without evaluating or resolving it at + assignment time. + + Examples + -------- + >>> c = MyComponent() + >>> c.target = param.raw(other.param.value) + >>> assert c.target is other.param.value + + Notes + ----- + - The wrapper is unwrapped during assignment and not stored. + - The stored value is the inner object itself. + - Useful when serializing, forwarding, or deferring resolution of + ref-like values. + """ + return Raw(value) + def register_reference_transform(transform): """ Append a transform to extract potential parameter dependencies @@ -170,7 +224,6 @@ def register_reference_transform(transform): Parameters ---------- transform: Callable[Any, Any] - """ return _reference_transforms.append(transform) @@ -182,6 +235,8 @@ def transform_reference(arg): that are not simple Parameters or functions with dependency definitions. """ + if isinstance(arg, Raw): + return arg.value for transform in _reference_transforms: if isinstance(arg, Parameter) or hasattr(arg, '_dinfo'): break @@ -207,7 +262,9 @@ def eval_function_with_deps(function): def resolve_value(value, recursive=True): """Resolve the current value of a dynamic reference.""" - if not recursive: + if isinstance(value, Raw): + return value.value + elif not recursive: pass elif isinstance(value, (list, tuple)): return type(value)(resolve_value(v) for v in value) @@ -231,7 +288,9 @@ def resolve_value(value, recursive=True): def resolve_ref(reference, recursive=False): """Resolve all parameters a dynamic reference depends on.""" - if recursive: + if isinstance(reference, Raw): + return [] + elif recursive: if isinstance(reference, (list, tuple, set)): return [r for v in reference for r in resolve_ref(v, recursive)] elif isinstance(reference, dict): @@ -2442,6 +2501,8 @@ def _sync_refs(self_, *events): self_.update(updates) def _resolve_ref(self_, pobj, value): + if isinstance(value, Raw): + return None, None, value.value, False is_gen = inspect.isgeneratorfunction(value) is_async = iscoroutinefunction(value) or is_gen deps = resolve_ref(value, recursive=pobj.nested_refs) diff --git a/tests/testrefs.py b/tests/testrefs.py index e3786df9e..8cbdbf5f8 100644 --- a/tests/testrefs.py +++ b/tests/testrefs.py @@ -39,8 +39,12 @@ class Parameters(param.Parameterized): string_list = param.List(default=[], item_type=str, allow_refs=True, nested_refs=True) + list = param.List(default=[], allow_refs=True, nested_refs=True) + no_refs = param.Parameter(allow_refs=False) + allows_ref = param.Parameter(allow_refs=True) + @param.depends('string') def formatted_string(self): if self.string.endswith('?'): @@ -363,3 +367,128 @@ def test_resolve_ref_recursive_slice(): refs = resolve_ref(nested, recursive=True) assert len(refs) == 1 assert refs[0] is p.param.string + +def test_raw_parameter_ref_not_resolved_errors(): + p = Parameters(string='base') + + with pytest.raises(ValueError): + Parameters(string=param.raw(p.param.string)) + +def test_raw_plain_value_unchanged(): + p = Parameters(allows_ref=param.raw('literal')) + assert p.allows_ref == 'literal' + +def test_raw_is_transient_unwrapped(): + p0 = Parameters() + r = p0.param.string + p = Parameters(allows_ref=param.raw(r)) + assert p.allows_ref is r + +def test_raw_nested_list_parameter_ref_preserved(): + p_src = Parameters(string='alpha') + p = Parameters(list=param.raw([p_src.param.string, 'other'])) + # With raw, nested refs are preserved (not resolved) + assert isinstance(p.list, list) + assert p.list[0] is p_src.param.string + assert p.list[1] == 'other' + + # Changing the source has no effect — we stored the ref object, not a live link + p_src.string = 'beta' + assert p.list[0] is p_src.param.string + +def test_raw_nested_list_mixed_refs_preserved(): + s = rx('x') + expr = s + '!' + p_src = Parameters(string='y') + p = Parameters(list=param.raw([expr, p_src.param.string, 'z'])) + + assert p.list[0] is expr + assert p.list[1] is p_src.param.string + assert p.list[2] == 'z' + + s.rx.value = 'xx' + p_src.string = 'yy' + # Nothing auto-updates because we stored verbatim objects + assert p.list[0] is expr + assert p.list[1] is p_src.param.string + +def test_raw_nested_dict_value_parameter_ref_preserved(): + p_src = Parameters(string='keyed') + p = Parameters(dictionary=param.raw({'k': p_src.param.string, 'n': 1})) + # Values kept as-is + assert p.dictionary['k'] is p_src.param.string + assert p.dictionary['n'] == 1 + # Changing source does not propagate + p_src.string = 'changed' + assert p.dictionary['k'] is p_src.param.string + +def test_raw_nested_dict_deep_structure_preserved(): + p_src = Parameters(string='deep') + expr = (rx('a') + rx('b')) + nested = { + 'level1': { + 'list': [p_src.param.string, expr, {'leaf': p_src.param.string}] + } + } + p = Parameters(dictionary=param.raw(nested)) + + got = p.dictionary + assert got['level1']['list'][0] is p_src.param.string + assert got['level1']['list'][1] is expr + assert got['level1']['list'][2]['leaf'] is p_src.param.string + +def test_raw_nested_refs_do_not_resolve_even_when_param_has_nested_refs_true(): + p_src = Parameters(string='s') + obj = Parameters( + dictionary=param.raw({'inner': [p_src.param.string]}), + list=param.raw([p_src.param.string, 'x']) + ) + assert obj.dictionary['inner'][0] is p_src.param.string + assert obj.list[0] is p_src.param.string + +def test_raw_survives_param_update_context_and_reassignments(): + p_src = Parameters(string='A') + p = Parameters(allows_ref=param.raw(p_src.param.string)) + assert p.allows_ref is p_src.param.string + + with p.param.update(allows_ref=param.raw(p_src.param.string)): + assert p.allows_ref is p_src.param.string + p_src.string = 'B' + assert p.allows_ref is p_src.param.string + + p.allows_ref = p_src.param.string + assert p.allows_ref == 'B' + p_src.string = 'C' + assert p.allows_ref == 'C' + +def test_raw_stores_callables_or_generators_without_consuming(): + started = {'gen': False, 'async': False} + + def gen(): + started['gen'] = True + yield 'x' + + async def agen(): + started['async'] = True + if False: # pragma: no cover (keep as async generator) + yield None + + p = Parameters() + p.allows_ref = param.raw(gen) # store the generator *function* itself + assert p.allows_ref is gen + assert started['gen'] is False # not invoked + + p.allows_ref = param.raw(agen) # store async generator *function* itself + assert p.allows_ref is agen + assert started['async'] is False # not invoked + +def test_resolve_ref_hides_inner_when_given_raw_directly(): + p_src = Parameters() + refs = resolve_ref(param.raw(p_src.param.string)) + assert len(refs) == 0 + +def test_resolve_ref_recursive_on_container_from_raw(): + p_src = Parameters() + nested = param.raw([{'k': (p_src.param.string,)}]) + refs = resolve_ref(nested, recursive=True) + assert len(refs) == 0 From c27db885b90ac0ba90d5c05cd2ce4e9a26e32b62 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 29 Oct 2025 11:26:41 +0100 Subject: [PATCH 2/5] Apply suggestions from code review --- tests/testrefs.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/testrefs.py b/tests/testrefs.py index 8cbdbf5f8..1fb50e464 100644 --- a/tests/testrefs.py +++ b/tests/testrefs.py @@ -458,7 +458,7 @@ def test_raw_survives_param_update_context_and_reassignments(): p.allows_ref = p_src.param.string assert p.allows_ref == 'B' - p_src.string = 'C' + p_src.allows_ref = 'C' assert p.allows_ref == 'C' def test_raw_stores_callables_or_generators_without_consuming(): @@ -470,17 +470,17 @@ def gen(): async def agen(): started['async'] = True - if False: # pragma: no cover (keep as async generator) + if False: yield None p = Parameters() - p.allows_ref = param.raw(gen) # store the generator *function* itself + p.allows_ref = param.raw(gen) assert p.allows_ref is gen - assert started['gen'] is False # not invoked + assert started['gen'] is False - p.allows_ref = param.raw(agen) # store async generator *function* itself + p.allows_ref = param.raw(agen) assert p.allows_ref is agen - assert started['async'] is False # not invoked + assert started['async'] is False def test_resolve_ref_hides_inner_when_given_raw_directly(): p_src = Parameters() From 1cc60eb2b0a9446eee2230a4979f425b9691ac3d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 29 Oct 2025 11:39:45 +0100 Subject: [PATCH 3/5] Fix test --- tests/testrefs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/testrefs.py b/tests/testrefs.py index 1fb50e464..da3ab4cc2 100644 --- a/tests/testrefs.py +++ b/tests/testrefs.py @@ -458,7 +458,7 @@ def test_raw_survives_param_update_context_and_reassignments(): p.allows_ref = p_src.param.string assert p.allows_ref == 'B' - p_src.allows_ref = 'C' + p_src.string = 'C' assert p.allows_ref == 'C' def test_raw_stores_callables_or_generators_without_consuming(): From b74271b23e23aae4d66a58ec8d6f7ed9f37c9abb Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Nov 2025 12:42:31 +0100 Subject: [PATCH 4/5] Rename to LiteralRef and add docs and tests --- doc/user_guide/References.ipynb | 86 +++++++++++++++++++++++++++++++++ param/__init__.py | 3 +- param/parameterized.py | 47 +++++------------- tests/testrefs.py | 67 ++++++++++++++----------- 4 files changed, 137 insertions(+), 66 deletions(-) diff --git a/doc/user_guide/References.ipynb b/doc/user_guide/References.ipynb index e45c842c8..ad5bee9a4 100644 --- a/doc/user_guide/References.ipynb +++ b/doc/user_guide/References.ipynb @@ -265,6 +265,92 @@ ":::" ] }, + { + "cell_type": "markdown", + "id": "ae3232e6-91dd-4a09-8301-b5a79ae42f8f", + "metadata": {}, + "source": [ + "## Assigning refs literally with `LiteralRef`\n", + "\n", + "Sometimes you don’t want Param to resolve a ref-like value on assignment—you want to store the ref object itself.\n", + "\n", + "Use `LiteralRef` to assign a ref as-is (verbatim), so you can forward it, serialize it, or resolve it later under different conditions." + ] + }, + { + "cell_type": "markdown", + "id": "15985908-d821-41a6-a4e0-41a8a49d9aae", + "metadata": {}, + "source": [ + "\n", + "By default, assigning a ref-like value resolves and links it:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b2c80606-f60f-4534-b2da-608efd2c9ab8", + "metadata": {}, + "outputs": [], + "source": [ + "class Example(param.Parameterized):\n", + " value = param.Parameter(allow_refs=True)\n", + "\n", + "src = Example(value=\"A\")\n", + "dst = Example()\n", + "\n", + "dst.value = src.param.value\n", + "assert dst.value == \"A\"\n", + "src.value = \"B\"\n", + "assert dst.value == \"B\" # still linked" + ] + }, + { + "cell_type": "markdown", + "id": "3942b3d3-3d0f-4f99-bb9e-b79ee991b635", + "metadata": {}, + "source": [ + "i.e. the `dst.value` tracks the `src.value`. Wrapping the reference in the `LiteralRef` skips the resolution:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf3fae7b-7dc6-47b5-9ed9-78cd5799ff52", + "metadata": {}, + "outputs": [], + "source": [ + "dst.value = param.parameterized.LiteralRef(src.param.value)\n", + "\n", + "dst.value" + ] + }, + { + "cell_type": "markdown", + "id": "e7174aad-4751-436e-8b68-36557597fccc", + "metadata": {}, + "source": [ + "i.e. Param unwraps the `LiteralRef` object but does not resolve the `Parameter` reference. `dst` now holds the actual `Parameter` object." + ] + }, + { + "cell_type": "markdown", + "id": "481223e8-750e-41b4-8376-63381b2012c6", + "metadata": {}, + "source": [ + "#### Key points\n", + "\n", + "- The wrapper is transient: it's unwrapped at assignment time; the stored value is the inner ref object.\n", + "- No subscription, no evaluation, no resolution occurs during assignment.\n", + "\n", + "#### When to use LiteralRef\n", + "\n", + "- You're wiring a graph of components and need to pass a ref downstream without linking it yet.\n", + "- You're building a config/serialization format that stores refs (not their current values).\n", + "- You have reactive/async producers you don't want to start/subscribe/consume during assignment.\n", + "- You want to preserve intent (“this is a ref”) instead of collapsing it to a concrete value." + ] + }, { "cell_type": "markdown", "id": "6ffd34f8-211c-4945-95b8-e87ec712e028", diff --git a/param/__init__.py b/param/__init__.py index 1d230cb4b..2cc84c1fc 100644 --- a/param/__init__.py +++ b/param/__init__.py @@ -48,7 +48,7 @@ Parameterized, Parameter, Skip, String, ParameterizedFunction, ParamOverrides, Undefined, get_logger, ParameterizedABC, ) -from .parameterized import (output, script_repr, raw, +from .parameterized import (output, script_repr, discard_events, edit_constant) from .parameterized import shared_parameters from .parameterized import logging_level @@ -224,7 +224,6 @@ 'param_union', 'parameterized_class', 'random_seed', - 'raw', 'resolve_path', 'rx', 'script_repr', diff --git a/param/parameterized.py b/param/parameterized.py index bd45b5484..966643f93 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -162,26 +162,26 @@ def get_logger(name: Optional[str] = None)->"logging.Logger": # Hook to apply to depends and bind arguments to turn them into valid parameters _reference_transforms = [] -class Raw: +class LiteralRef: """ Wrapper type used to assign ref-like objects to Parameters *without* triggering automatic resolution. Normally, when a ref-like value (e.g. a Parameter, reactive expression, async generator, etc.) is assigned to a Parameter attribute, Param - resolves it to its underlying value. Wrapping the object in ``Raw`` + resolves it to its underlying value. Wrapping the object in ``LiteralRef`` signals that the value should instead be stored as-is. Example ------- - >>> obj.some_param = param.Raw(other.param.value) + >>> obj.some_param = param.LiteralRef(other.param.value) >>> assert obj.some_param is other.param.value Notes ----- - - ``Raw`` is only meaningful at assignment time; the wrapper is + - ``LiteralRef`` is only meaningful at assignment time; the wrapper is unwrapped and not stored. - - The stored value is the inner object itself, not the ``Raw`` instance. + - The stored value is the inner object itself, not the ``LiteralRef`` instance. - This allows safe serialization, forwarding, or deferred resolution of ref-like values. """ @@ -189,33 +189,9 @@ class Raw: __slots__ = ["value"] def __init__(self, value): self.value = value - def __repr__(self): return f"Raw({self.value!r})" + def __repr__(self): return f"LiteralRef({self.value!r})" -def raw(value: Any): - """ - Mark a value to be assigned *as-is*, skipping Param’s automatic - resolution of ref-like objects. - - This allows storing a Parameter, reactive expression, or other - ref-like value directly, without evaluating or resolving it at - assignment time. - - Examples - -------- - >>> c = MyComponent() - >>> c.target = param.raw(other.param.value) - >>> assert c.target is other.param.value - - Notes - ----- - - The wrapper is unwrapped during assignment and not stored. - - The stored value is the inner object itself. - - Useful when serializing, forwarding, or deferring resolution of - ref-like values. - """ - return Raw(value) - def register_reference_transform(transform): """ Append a transform to extract potential parameter dependencies @@ -235,7 +211,7 @@ def transform_reference(arg): that are not simple Parameters or functions with dependency definitions. """ - if isinstance(arg, Raw): + if isinstance(arg, LiteralRef): return arg.value for transform in _reference_transforms: if isinstance(arg, Parameter) or hasattr(arg, '_dinfo'): @@ -262,7 +238,7 @@ def eval_function_with_deps(function): def resolve_value(value, recursive=True): """Resolve the current value of a dynamic reference.""" - if isinstance(value, Raw): + if isinstance(value, LiteralRef): return value.value elif not recursive: pass @@ -288,7 +264,7 @@ def resolve_value(value, recursive=True): def resolve_ref(reference, recursive=False): """Resolve all parameters a dynamic reference depends on.""" - if isinstance(reference, Raw): + if isinstance(reference, LiteralRef): return [] elif recursive: if isinstance(reference, (list, tuple, set)): @@ -2415,6 +2391,7 @@ def _setup_params(self_, **params): # contain a reference and warn the user that the # behavior may change in future. if name not in self_.cls._param__private.explicit_no_refs: + print(pobj) try: ref, _, resolved, _ = self_._resolve_ref(pobj, val) except Exception: @@ -2501,12 +2478,12 @@ def _sync_refs(self_, *events): self_.update(updates) def _resolve_ref(self_, pobj, value): - if isinstance(value, Raw): + if isinstance(value, LiteralRef): return None, None, value.value, False is_gen = inspect.isgeneratorfunction(value) is_async = iscoroutinefunction(value) or is_gen deps = resolve_ref(value, recursive=pobj.nested_refs) - if not (deps or is_async or is_gen): + if not (deps or is_async or is_gen or pobj.nested_refs): return None, None, value, False ref = value try: diff --git a/tests/testrefs.py b/tests/testrefs.py index da3ab4cc2..86577e2b2 100644 --- a/tests/testrefs.py +++ b/tests/testrefs.py @@ -5,7 +5,7 @@ import param import pytest -from param.parameterized import Skip, resolve_ref +from param.parameterized import LiteralRef, Skip, resolve_ref from param.reactive import bind, rx @@ -368,26 +368,26 @@ def test_resolve_ref_recursive_slice(): assert len(refs) == 1 assert refs[0] is p.param.string -def test_raw_parameter_ref_not_resolved_errors(): +def test_literal_ref_parameter_ref_not_resolved_errors(): p = Parameters(string='base') with pytest.raises(ValueError): - Parameters(string=param.raw(p.param.string)) + Parameters(string=LiteralRef(p.param.string)) -def test_raw_plain_value_unchanged(): - p = Parameters(allows_ref=param.raw('literal')) +def test_literal_ref_plain_value_unchanged(): + p = Parameters(allows_ref=LiteralRef('literal')) assert p.allows_ref == 'literal' -def test_raw_is_transient_unwrapped(): +def test_literal_ref_is_transient_unwrapped(): p0 = Parameters() r = p0.param.string - p = Parameters(allows_ref=param.raw(r)) + p = Parameters(allows_ref=LiteralRef(r)) assert p.allows_ref is r -def test_raw_nested_list_parameter_ref_preserved(): +def test_literal_ref_nested_list_parameter_ref_preserved(): p_src = Parameters(string='alpha') - p = Parameters(list=param.raw([p_src.param.string, 'other'])) - # With raw, nested refs are preserved (not resolved) + p = Parameters(list=LiteralRef([p_src.param.string, 'other'])) + # With literal_ref, nested refs are preserved (not resolved) assert isinstance(p.list, list) assert p.list[0] is p_src.param.string assert p.list[1] == 'other' @@ -396,11 +396,11 @@ def test_raw_nested_list_parameter_ref_preserved(): p_src.string = 'beta' assert p.list[0] is p_src.param.string -def test_raw_nested_list_mixed_refs_preserved(): +def test_literal_ref_nested_list_mixed_refs_preserved(): s = rx('x') expr = s + '!' p_src = Parameters(string='y') - p = Parameters(list=param.raw([expr, p_src.param.string, 'z'])) + p = Parameters(list=LiteralRef([expr, p_src.param.string, 'z'])) assert p.list[0] is expr assert p.list[1] is p_src.param.string @@ -412,9 +412,9 @@ def test_raw_nested_list_mixed_refs_preserved(): assert p.list[0] is expr assert p.list[1] is p_src.param.string -def test_raw_nested_dict_value_parameter_ref_preserved(): +def test_literal_ref_nested_dict_value_parameter_ref_preserved(): p_src = Parameters(string='keyed') - p = Parameters(dictionary=param.raw({'k': p_src.param.string, 'n': 1})) + p = Parameters(dictionary=LiteralRef({'k': p_src.param.string, 'n': 1})) # Values kept as-is assert p.dictionary['k'] is p_src.param.string assert p.dictionary['n'] == 1 @@ -422,7 +422,7 @@ def test_raw_nested_dict_value_parameter_ref_preserved(): p_src.string = 'changed' assert p.dictionary['k'] is p_src.param.string -def test_raw_nested_dict_deep_structure_preserved(): +def test_literal_ref_nested_dict_deep_structure_preserved(): p_src = Parameters(string='deep') expr = (rx('a') + rx('b')) nested = { @@ -430,28 +430,37 @@ def test_raw_nested_dict_deep_structure_preserved(): 'list': [p_src.param.string, expr, {'leaf': p_src.param.string}] } } - p = Parameters(dictionary=param.raw(nested)) + p = Parameters(dictionary=LiteralRef(nested)) got = p.dictionary assert got['level1']['list'][0] is p_src.param.string assert got['level1']['list'][1] is expr assert got['level1']['list'][2]['leaf'] is p_src.param.string -def test_raw_nested_refs_do_not_resolve_even_when_param_has_nested_refs_true(): +def test_literal_ref_nested_refs_do_not_resolve_even_when_param_has_nested_refs_true(): p_src = Parameters(string='s') obj = Parameters( - dictionary=param.raw({'inner': [p_src.param.string]}), - list=param.raw([p_src.param.string, 'x']) + dictionary=LiteralRef({'inner': [p_src.param.string]}), + list=LiteralRef([p_src.param.string, 'x']) ) assert obj.dictionary['inner'][0] is p_src.param.string assert obj.list[0] is p_src.param.string -def test_raw_survives_param_update_context_and_reassignments(): +def test_literal_ref_inner_nested_refs_do_not_resolve_even_when_param_has_nested_refs_true(): + p_src = Parameters(string='s') + obj = Parameters( + dictionary={'inner': [LiteralRef(p_src.param.string)]}, + list=[LiteralRef(p_src.param.string), 'x'] + ) + assert obj.dictionary['inner'][0] is p_src.param.string + assert obj.list[0] is p_src.param.string + +def test_literal_ref_survives_param_update_context_and_reassignments(): p_src = Parameters(string='A') - p = Parameters(allows_ref=param.raw(p_src.param.string)) + p = Parameters(allows_ref=LiteralRef(p_src.param.string)) assert p.allows_ref is p_src.param.string - with p.param.update(allows_ref=param.raw(p_src.param.string)): + with p.param.update(allows_ref=LiteralRef(p_src.param.string)): assert p.allows_ref is p_src.param.string p_src.string = 'B' assert p.allows_ref is p_src.param.string @@ -461,7 +470,7 @@ def test_raw_survives_param_update_context_and_reassignments(): p_src.string = 'C' assert p.allows_ref == 'C' -def test_raw_stores_callables_or_generators_without_consuming(): +def test_literal_ref_stores_callables_or_generators_without_consuming(): started = {'gen': False, 'async': False} def gen(): @@ -474,21 +483,21 @@ async def agen(): yield None p = Parameters() - p.allows_ref = param.raw(gen) + p.allows_ref = LiteralRef(gen) assert p.allows_ref is gen assert started['gen'] is False - p.allows_ref = param.raw(agen) + p.allows_ref = LiteralRef(agen) assert p.allows_ref is agen assert started['async'] is False -def test_resolve_ref_hides_inner_when_given_raw_directly(): +def test_resolve_ref_hides_inner_when_given_literal_ref_directly(): p_src = Parameters() - refs = resolve_ref(param.raw(p_src.param.string)) + refs = resolve_ref(LiteralRef(p_src.param.string)) assert len(refs) == 0 -def test_resolve_ref_recursive_on_container_from_raw(): +def test_resolve_ref_recursive_on_container_from_literal_ref(): p_src = Parameters() - nested = param.raw([{'k': (p_src.param.string,)}]) + nested = LiteralRef([{'k': (p_src.param.string,)}]) refs = resolve_ref(nested, recursive=True) assert len(refs) == 0 From 741f9700382c14a6daab51d27a929289b90ebad9 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Wed, 5 Nov 2025 12:45:05 +0100 Subject: [PATCH 5/5] Remove stray print --- param/parameterized.py | 1 - 1 file changed, 1 deletion(-) diff --git a/param/parameterized.py b/param/parameterized.py index 966643f93..0a3f62414 100644 --- a/param/parameterized.py +++ b/param/parameterized.py @@ -2391,7 +2391,6 @@ def _setup_params(self_, **params): # contain a reference and warn the user that the # behavior may change in future. if name not in self_.cls._param__private.explicit_no_refs: - print(pobj) try: ref, _, resolved, _ = self_._resolve_ref(pobj, val) except Exception: