resolve_references does not traverse list members — @ref inside list elements remain unresolved
@plamen-neykov is already working on this.
Since Sep 15, 2026.
Assessment
This issue has not been assessed yet.
Description
Bug Report
resolve_references does not traverse list members — @ref inside list elements remain unresolved
Steps to Reproduce
- Clone the repository and set up the development environment:
git clone https://github.com/regnosys/rune-python-runtime
cd rune-python-runtime
./dev_clean_setup.sh
source .pydevenv/bin/activate
- Run the following self-contained script (no external dependencies beyond the runtime itself):
import json
from typing import List
from typing_extensions import Annotated
from pydantic import Field
from rune.runtime.base_data_class import BaseDataClass
# A keyed type — instances can be registered under an external key
class Party(BaseDataClass):
_ALLOWED_METADATA = {'@key:external'}
name: str = Field(..., description='party name')
# A type whose partyReference field is a @ref to a Party
class Counterparty(BaseDataClass):
role: str = Field(..., description='role')
partyReference: Annotated[
Party,
Party.serializer(),
Party.validator(allowed_meta=('@ref:external',))
] = Field(..., description='reference to party')
_KEY_REF_CONSTRAINTS = {'partyReference': {'@ref:external'}}
# Trade holds parties (the keys) and counterparties (the refs) both as lists
class Trade(BaseDataClass):
party: List[Party] = Field(..., description='parties')
counterparty: List[Counterparty] = Field(..., description='counterparties')
data = json.dumps({
"party": [
{"@key:external": "p1", "name": "Party A"},
{"@key:external": "p2", "name": "Party B"}
],
"counterparty": [
{"role": "Party1", "partyReference": {"@ref:external": "p1"}},
{"role": "Party2", "partyReference": {"@ref:external": "p2"}}
]
})
# Step A — inspect reference type after deserialize with validate_model=False
t = Trade.rune_deserialize(data, validate_model=False)
print("counterparty[0].partyReference:", type(t.counterparty[0].partyReference).__name__)
print("counterparty[1].partyReference:", type(t.counterparty[1].partyReference).__name__)
# Step B — deserialize with validate_model=True (default)
t2 = Trade.rune_deserialize(data, validate_model=True)
Expected Result
Step A: both partyReference fields are resolved to Party instances after rune_deserialize.
Step B: deserialization completes without error.
Actual Result
Step A — references are not resolved:
counterparty[0].partyReference: UnresolvedReference
counterparty[1].partyReference: UnresolvedReference
Step B — ValidationError is raised because validate_attribs re-runs model_validate on the already-constructed model and Pydantic rejects the remaining UnresolvedReference objects:
pydantic.ValidationError: 2 validation errors for Trade
counterparty.0.partyReference
Expected either <class 'Party'> or dict but got <class 'rune.runtime.metadata.UnresolvedReference'>.
[type=Input Validation Error, input_type=UnresolvedReference]
counterparty.1.partyReference
Expected either <class 'Party'> or dict but got <class 'rune.runtime.metadata.UnresolvedReference'>.
[type=Input Validation Error, input_type=UnresolvedReference]
Root Cause
BaseDataClass.resolve_references recurses only into properties that are direct BaseDataClass instances. It does not descend into list members:
# base_data_class.py — resolve_references
if recurse:
for prop_nm, obj in self.__dict__.items():
if (isinstance(obj, BaseDataClass) # ← True for scalar fields only
and not prop_nm.startswith('__')):
obj.resolve_references(...)
# list-valued properties are silently skipped
Trade.counterparty is a list — isinstance(list, BaseDataClass) is False, so the Counterparty items inside it are never visited and their partyReference fields remain as UnresolvedReference.
Environment
rune-python-runtimeversion: 2.2.0- Python: 3.11
- OS: macOS Darwin 25.6.0
Additional Context
Proposed fix — extend the recursion in resolve_references to traverse list members:
if recurse:
for prop_nm, obj in self.__dict__.items():
if prop_nm.startswith('__'):
continue
if isinstance(obj, BaseDataClass):
obj.resolve_references(ignore_dangling=ignore_dangling,
recurse=recurse)
elif isinstance(obj, list):
for item in obj:
if isinstance(item, BaseDataClass):
item.resolve_references(ignore_dangling=ignore_dangling,
recurse=recurse)
Impact — any Rune model JSON that uses @ref inside a list-valued field is affected:
validate_model=False— reference silently remainsUnresolvedReference; downstream attribute access fails at runtimevalidate_model=True(default) —ValidationErrorraised immediately, preventing use of the deserialized object
- Dominant language
- Python
- Stars
- 0
- Forks
- 3
- Avg merge
- 5m
- Merged PRs (30d)
- 1
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from finos/rune-python-runtime
-
bug
finos/rune-python-runtime#39 · 1 assignee ·
-
Difficulty 3/5 1-2 days Newbie friendliness 62/100
finos/rune-python-runtime#27 ·
-
Difficulty 5/5 Over a week Newbie friendliness 25/100
finos/rune-python-runtime#16 ·
-
Difficulty 4/5 3-5 days Newbie friendliness 35/100
finos/rune-python-runtime#15 · 1 comment ·
All issues in finos/rune-python-runtime
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
use-agent-os/agent-os#3314 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
BasedHardware/omi#15662 · 1 comment ·
-
documentation help wanted
Difficulty 2/5 1-3 hours Newbie friendliness 90/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
AiursoftWeb/AnduinOS-2#19 ·