Add `raw` wrapper to assign ref-like values without resolving
- 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
Assessment
This issue has not been assessed yet.