holoviz / holoviz/param

Add `raw` wrapper to assign ref-like values without resolving

Open
#1,090 1 comment 0 reactions 0 assignees View on GitHub
API type-feature
Dominant language
Python
Stars
521
Forks
86
Avg merge
1d 13h
Merged PRs (30d)
35

Description

## **Summary**
When assigning values to Parameters, Param automatically resolves *ref-like objects* (e.g. Parameters, reactive expressions, async generators). Sometimes, however, users need to **store these ref-like values themselves** — for forwarding, serialization, inspection, or deferred evaluation — rather than immediately resolving them.
This issue proposes adding a minimal wrapper, **`raw`**, that marks a value to be stored as-is. Alternative names considered: **`constant`**, **`const`**, or **`literal`**.

---

## **Problem**

Param’s assignment model is designed for convenience — ref-like values are automatically resolved when assigned to a Parameter.
However, there are valid use cases where the *reference object* itself should be stored, not the resolved value:

* Keeping a `Parameter` or reactive object to be connected later.
* Holding an expression or generator for deferred computation.
* Serializing configurations that intentionally contain unresolved refs.
* Preserving symbolic or lazy evaluation constructs for inspection.

---

## **Proposed API**

```python
import param

# Store a ref-like value *as itself*, skipping resolution:
obj.some_param = param.Raw(ref_like)
# or the convenience function:
obj.some_param = param.raw(ref_like)
```

Alternative names under consideration:

* `param.const(value)`
* `param.constant(value)`
* `param.literal(value)`

All would convey the same semantics: assign *without resolving*.

---

## **Semantics**

* `raw(x)` is a **transient assignment wrapper**.
* During assignment, if a value is an instance of `Raw`, Param:

* Unwraps it (`x = x.value`), and
* **Skips** any resolution logic that would normally occur.
* The stored value becomes the *underlying ref-like object*, not the wrapper.
* Reading the value simply returns whatever was stored.

---

## **Examples**

### Keep a reactive or param reference

```python
class C(param.Parameterized):
value = param.Parameter()

ref_like = another_obj.param.some_param

c = C()
c.value = param.raw(ref_like)
assert c.value is ref_like
```

### Keep a reactive expression or async generator

```python
expr = rx.map(source, lambda v: v * 2)
c.value = param.raw(expr) # store expression, not resolved value

async def stream(): yield 1
c.value = param.raw(stream()) # store generator, not consumed
```

### Containers are stored verbatim

```python
c.value = param.raw([rx.expr1, param.Parameter(), async_gen])
```

---

## **Implementation sketch**

```python
class Raw:
__slots__ = ("value",)
def __init__(self, value): self.value = value
def __repr__(self): return f"Raw({self.value!r})"

def raw(value):
return Raw(value)
```

In `Parameter.__set__` (or equivalent):

```python
if isinstance(new, Raw):
new = new.value
skip_ref_resolution = True

if not skip_ref_resolution:
new = maybe_resolve(new) # existing logic
```

### Typing (optional)

```python
from typing import Generic, TypeVar
T = TypeVar("T")

class Raw(Generic[T]):
value: T
```

---

## **Backward compatibility**

* Fully backward compatible: no change unless users explicitly use `Raw`.
* The wrapper is transient and does not appear in serialized state.
* Stored value is the inner object.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.