rune-runtime 2.2.2__tar.gz → 3.0.0__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.
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.gitignore +3 -0
- {rune_runtime-2.2.2/src/rune_runtime.egg-info → rune_runtime-3.0.0}/PKG-INFO +1 -1
- rune_runtime-3.0.0/benchmarks/benchmark_list_references.py +93 -0
- rune_runtime-3.0.0/docs/object-identity.md +114 -0
- rune_runtime-3.0.0/src/rune/runtime/base_data_class.py +697 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/cow.py +69 -9
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/extensions/cached_json_rune_object_deserializer.py +20 -16
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/metadata.py +176 -71
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/object_builder.py +3 -0
- rune_runtime-3.0.0/src/rune/runtime/reference_list.py +199 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/utils.py +74 -26
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/version.py +3 -3
- {rune_runtime-2.2.2 → rune_runtime-3.0.0/src/rune_runtime.egg-info}/PKG-INFO +1 -1
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune_runtime.egg-info/SOURCES.txt +10 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune_runtime.egg-info/scm_file_list.json +10 -0
- rune_runtime-3.0.0/src/rune_runtime.egg-info/scm_version.json +8 -0
- rune_runtime-3.0.0/test/bugs/test_choice_serialization.py +94 -0
- rune_runtime-3.0.0/test/bugs/test_extra_meta.py +59 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_basic_types_with_meta.py +185 -11
- rune_runtime-3.0.0/test/test_complex_types_with_meta.py +190 -0
- rune_runtime-3.0.0/test/test_cow_metadata.py +246 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_keys_and_references.py +194 -2
- rune_runtime-3.0.0/test/test_list_cardinality_serialise.py +723 -0
- rune_runtime-3.0.0/test/test_number_precision.py +211 -0
- rune_runtime-3.0.0/test/test_temporal_copy.py +172 -0
- rune_runtime-3.0.0/test/test_top_level_metadata.py +95 -0
- rune_runtime-3.0.0/test/test_utils.py +257 -0
- rune_runtime-2.2.2/src/rune/runtime/base_data_class.py +0 -455
- rune_runtime-2.2.2/src/rune_runtime.egg-info/scm_version.json +0 -8
- rune_runtime-2.2.2/test/test_complex_types_with_meta.py +0 -69
- rune_runtime-2.2.2/test/test_utils.py +0 -135
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/CODE_OF_CONDUCT.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/ISSUE_TEMPLATE/Bug_report.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/ISSUE_TEMPLATE/Feature_request.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/ISSUE_TEMPLATE/Support_question.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/ISSUE_TEMPLATE/meeting-minutes.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/workflows/build-python-package.yml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/workflows/check-license-headers.yml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/workflows/cve-scanning-python.yml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/workflows/license-scanning-python.yml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/workflows/release.yml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.github/workflows/run-tests.yml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.pre-commit-config.yaml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/.vscode/settings.json +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/CONTRIBUTING.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/EASYCLA_CHANGEME.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/LICENSE +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/LICENSE.spdx +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/MAINTAINERS.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/NOTICE +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/README.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/build_wheel.sh +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/dev_clean_setup.sh +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/docs/home.md +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/netlify.toml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/pyproject.toml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/renovate.json +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/safety-policy.yml +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/setup.cfg +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/license/apply_headers.sh +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/license/header.txt +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/__init__.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/conditions.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/extensions/__init__.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/func_proxy.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/native_registry.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/object_registry.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune/runtime/py.typed +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune_runtime.egg-info/dependency_links.txt +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune_runtime.egg-info/requires.txt +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/src/rune_runtime.egg-info/top_level.txt +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/__init__.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/cdm/EUR-Vanilla-account.json +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/cdm/test_create_irs.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/cdm/test_trade_creation.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/cdm/test_validation.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/functions/specs/cdm/_bundle.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/functions/specs/cdm/base/math/Abs.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/functions/specs/cdm/base/math/RoundToNearest.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/functions/specs/cdm/base/math/RoundingModeEnum.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/functions/specs/test_native_registry.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/functions/specs/test_simple.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/local_data/ird-ex01-vanilla-swap-versioned.json +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/run_runtime_tests.sh +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/serializer-round-trip/test_basic.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/serializer-round-trip/test_conditions.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/serializer-round-trip/test_enumtype.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/serializer-round-trip/test_extension.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/serializer-round-trip/test_metakey.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/serializer-round-trip/test_metalocation.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/serializer-round-trip/test_metascheme.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_cached_json_rune_object_deserializer.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_cow.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_deep_keys_and_references.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_deferred_ref_deserialization.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_func_proxy.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_list_cardinality_with_meta.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_namespace_prefix.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_object_builder.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_operations.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_parametrized_basic_types.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_rune_parent.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_rune_str.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_subtype_identity.py +0 -0
- {rune_runtime-2.2.2 → rune_runtime-3.0.0}/test/test_zoned_date_time.py +0 -0
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
'''Compare reference-list overhead using the same models and installed dependencies.
|
|
5
|
+
|
|
6
|
+
Run with the project interpreter. --source selects an extracted revision's src
|
|
7
|
+
directory; omit it to measure the current checkout. Timings exclude model schema
|
|
8
|
+
construction and report the best of --repeat runs independently for each round.
|
|
9
|
+
'''
|
|
10
|
+
import argparse
|
|
11
|
+
import cProfile
|
|
12
|
+
import gc
|
|
13
|
+
import json
|
|
14
|
+
import platform
|
|
15
|
+
import sys
|
|
16
|
+
import timeit
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Annotated
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> None:
|
|
22
|
+
'''Measure public read, write and validation operations on large model graphs.'''
|
|
23
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
24
|
+
parser.add_argument('--source', type=Path)
|
|
25
|
+
parser.add_argument('--label', default='working-tree')
|
|
26
|
+
parser.add_argument('--size', type=int, default=2000)
|
|
27
|
+
parser.add_argument('--repeat', type=int, default=5)
|
|
28
|
+
parser.add_argument('--rounds', type=int, default=2)
|
|
29
|
+
parser.add_argument('--profile', type=Path)
|
|
30
|
+
args = parser.parse_args()
|
|
31
|
+
source = args.source or Path(__file__).resolve().parents[1] / 'src'
|
|
32
|
+
sys.path.insert(0, str(source.resolve()))
|
|
33
|
+
|
|
34
|
+
import pydantic
|
|
35
|
+
from rune.runtime.base_data_class import BaseDataClass
|
|
36
|
+
from rune.runtime.metadata import BaseReference
|
|
37
|
+
|
|
38
|
+
class Party(BaseDataClass):
|
|
39
|
+
_ALLOWED_METADATA = {'@key', '@key:external'}
|
|
40
|
+
name: str
|
|
41
|
+
|
|
42
|
+
class PlainBook(BaseDataClass):
|
|
43
|
+
parties: Annotated[list[Party], Party.serializer(), Party.validator(('@key:external',))]
|
|
44
|
+
items: Annotated[list[Party], Party.serializer(), Party.validator()]
|
|
45
|
+
|
|
46
|
+
class ReferenceBook(BaseDataClass):
|
|
47
|
+
parties: Annotated[list[Party], Party.serializer(), Party.validator(('@key:external',))]
|
|
48
|
+
items: Annotated[list[Party | BaseReference], Party.serializer(), Party.validator(('@ref:external',))]
|
|
49
|
+
_KEY_REF_CONSTRAINTS = {'items': {'@ref:external'}}
|
|
50
|
+
|
|
51
|
+
data = {
|
|
52
|
+
'parties': [{'@key:external': f'p{i}', 'name': f'Party {i}'} for i in range(args.size)],
|
|
53
|
+
'items': [{'name': f'Inline {i}'} for i in range(args.size)],
|
|
54
|
+
}
|
|
55
|
+
cases = [('no-references', PlainBook, data),
|
|
56
|
+
('reference-capable-inline', ReferenceBook, data),
|
|
57
|
+
('references', ReferenceBook, data | {
|
|
58
|
+
'items': [{'@ref:external': f'p{i}'} for i in range(args.size)],
|
|
59
|
+
})]
|
|
60
|
+
result = {'label': args.label, 'python': platform.python_version(),
|
|
61
|
+
'pydantic': pydantic.__version__, 'source': str(source.resolve()),
|
|
62
|
+
'size': args.size, 'repeat': args.repeat, 'rounds': args.rounds, 'cases': {}}
|
|
63
|
+
for name, model_type, payload in cases:
|
|
64
|
+
encoded = json.dumps(payload)
|
|
65
|
+
model = model_type.rune_deserialize(encoded, validate_model=False)
|
|
66
|
+
operations = {
|
|
67
|
+
'read': lambda: model_type.rune_deserialize(encoded, validate_model=False),
|
|
68
|
+
'write': lambda: model.rune_serialize(validate_model=False),
|
|
69
|
+
'validate': model.validate_model,
|
|
70
|
+
}
|
|
71
|
+
measurements = {}
|
|
72
|
+
for operation, call in operations.items():
|
|
73
|
+
try:
|
|
74
|
+
call()
|
|
75
|
+
except Exception as error:
|
|
76
|
+
measurements[operation] = {'error': f'{type(error).__name__}: {error}'}
|
|
77
|
+
continue
|
|
78
|
+
timings = []
|
|
79
|
+
for _ in range(args.rounds):
|
|
80
|
+
gc.collect()
|
|
81
|
+
timings.append(min(timeit.repeat(call, number=1, repeat=args.repeat)))
|
|
82
|
+
measurements[operation] = {'seconds': timings}
|
|
83
|
+
if args.profile:
|
|
84
|
+
args.profile.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
profile = cProfile.Profile()
|
|
86
|
+
profile.runcall(call)
|
|
87
|
+
profile.dump_stats(str(args.profile / f'{args.label}-{name}-{operation}.prof'))
|
|
88
|
+
result['cases'][name] = measurements
|
|
89
|
+
print(json.dumps(result, indent=2))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == '__main__':
|
|
93
|
+
main()
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Object identity and references
|
|
2
|
+
|
|
3
|
+
Rune objects have an identity independently of the keys used to reference them.
|
|
4
|
+
`as-key` requests the identity of its operand. Allocating a missing key records
|
|
5
|
+
that identity; ordinary data writes are governed by ownership and copy-on-write
|
|
6
|
+
(COW) isolation.
|
|
7
|
+
|
|
8
|
+
## Principles
|
|
9
|
+
|
|
10
|
+
1. Every object has an identity, even when it has no key.
|
|
11
|
+
2. Distinct domain objects have distinct identities. For a given key type and
|
|
12
|
+
resolution scope, a key identifies at most one target.
|
|
13
|
+
3. Keys represent identity. Multiple keys of different types can identify the
|
|
14
|
+
same object.
|
|
15
|
+
4. Creating a key preserves the object's identity and ordinary data. It records
|
|
16
|
+
an identifier for the existing object.
|
|
17
|
+
5. `as-key` requests the identity of its operand; it does not create a new target.
|
|
18
|
+
6. A reference into the caller's graph grants read access and identity access.
|
|
19
|
+
Ordinary data writes require ownership.
|
|
20
|
+
|
|
21
|
+
## Identity and keys
|
|
22
|
+
|
|
23
|
+
Every object has an identity, including an object with no serialized key. Multiple
|
|
24
|
+
keys can identify the same object, using different key types and resolution
|
|
25
|
+
scopes. Allocating an additional key preserves the object's identity and ordinary
|
|
26
|
+
data.
|
|
27
|
+
|
|
28
|
+
Key uniqueness is evaluated using both the key type and the resolution scope.
|
|
29
|
+
The same key text can occur under different types or in different scopes without
|
|
30
|
+
implying that the targets share an identity.
|
|
31
|
+
|
|
32
|
+
Key allocation can change the object's key metadata and register the key in a
|
|
33
|
+
lookup table. This is intentional identity bookkeeping. A serialized object can
|
|
34
|
+
therefore gain an `@key` without a change to its ordinary fields. Key allocation
|
|
35
|
+
remains subject to the model's metadata permissions; having an identity does not
|
|
36
|
+
make every type eligible for every kind of serialized key.
|
|
37
|
+
|
|
38
|
+
The key identifies the existing target. Creating a key must not construct a
|
|
39
|
+
different target solely to hold that key. A reference to a newly keyed object and
|
|
40
|
+
a reference to an already keyed object have the same identity semantics.
|
|
41
|
+
|
|
42
|
+
## Key selection for as-key
|
|
43
|
+
|
|
44
|
+
Generated code implements `as-key` with `Reference(target)`. With no explicit key
|
|
45
|
+
or key type, selection follows this order:
|
|
46
|
+
|
|
47
|
+
1. Use the existing external key, if present.
|
|
48
|
+
2. Otherwise reuse the existing internal key.
|
|
49
|
+
3. Otherwise allocate an internal key for the existing target.
|
|
50
|
+
|
|
51
|
+
| Existing keys | Serialized reference | Key allocation |
|
|
52
|
+
| --- | --- | --- |
|
|
53
|
+
| External `p1` | `{"@ref:external": "p1"}` | None |
|
|
54
|
+
| Internal `i1` | `{"@ref": "i1"}` | None |
|
|
55
|
+
| External `p1` and internal `i1` | `{"@ref:external": "p1"}` | None |
|
|
56
|
+
| No external or internal key | `{"@ref": "<allocated key>"}` | Internal key |
|
|
57
|
+
|
|
58
|
+
An explicit `key_type=KeyType.INTERNAL` selects an internal reference even when
|
|
59
|
+
an external key exists. Supplying an explicit key selects that key using the
|
|
60
|
+
constructor's existing key-type rules.
|
|
61
|
+
|
|
62
|
+
Local keys are represented by `@key:scoped`. They require the appropriate
|
|
63
|
+
resolution scope, so automatic selection currently excludes them. Creating a
|
|
64
|
+
scoped reference remains explicit. Automatic selection would need a rule for
|
|
65
|
+
checking the receiving scope.
|
|
66
|
+
|
|
67
|
+
## Reference edges and key registries
|
|
68
|
+
|
|
69
|
+
A reference occurrence records its key and key type on the receiving field or
|
|
70
|
+
list entry. It exposes the target object without making that target a containment
|
|
71
|
+
child of the receiver. The same target can occur both inline and by reference.
|
|
72
|
+
|
|
73
|
+
The target holds its own key metadata. Key registries map keys to targets and are
|
|
74
|
+
normally reached through the containment parent chain, with separate handling for
|
|
75
|
+
scoped keys. An empty registry does not establish that the target has no key;
|
|
76
|
+
key selection reads the target's metadata.
|
|
77
|
+
|
|
78
|
+
Serialization writes a reference occurrence as its reference key, without
|
|
79
|
+
embedding the target's data. The target's containment graph or external context
|
|
80
|
+
provides the corresponding keyed object for resolution.
|
|
81
|
+
|
|
82
|
+
## Ownership and COW
|
|
83
|
+
|
|
84
|
+
Allocating an identity key alone does not require COW detachment. It may extend
|
|
85
|
+
the existing target's key metadata and registry while leaving ordinary data
|
|
86
|
+
unchanged. This exception is specific to identity keys and registration; it does
|
|
87
|
+
not grant unrestricted access to other metadata or mutable values.
|
|
88
|
+
|
|
89
|
+
The agreed access rule for generated functions is:
|
|
90
|
+
|
|
91
|
+
> A reference into the caller's graph grants read access and identity access;
|
|
92
|
+
> data writes require ownership.
|
|
93
|
+
|
|
94
|
+
**Enforcing this access rule is follow-up work.** Current reference access can
|
|
95
|
+
expose mutable targets; the external-key selection fix does not generally prevent
|
|
96
|
+
data writes through borrowed references.
|
|
97
|
+
|
|
98
|
+
The follow-up must protect nested objects, mutable collections and mutating
|
|
99
|
+
methods reached through a borrowed reference. Replacing a reference field on an
|
|
100
|
+
owned receiver is distinct from modifying the referenced target. References to
|
|
101
|
+
contained targets within a graph managed by COW must retain the supported
|
|
102
|
+
behavior of editing the detached graph while preserving its internal references.
|
|
103
|
+
|
|
104
|
+
## Code and regression coverage
|
|
105
|
+
|
|
106
|
+
Key selection and registration are implemented in
|
|
107
|
+
[metadata.py](../src/rune/runtime/metadata.py). COW wrappers are implemented in
|
|
108
|
+
[cow.py](../src/rune/runtime/cow.py).
|
|
109
|
+
|
|
110
|
+
[test_keys_and_references.py](../test/test_keys_and_references.py) includes both
|
|
111
|
+
generated-function reproductions from [issue #73](https://github.com/finos/rune-python-runtime/issues/73),
|
|
112
|
+
along with coverage for existing keys and explicit internal selection.
|
|
113
|
+
[test_list_cardinality_serialise.py](../test/test_list_cardinality_serialise.py)
|
|
114
|
+
covers reference occurrences and the list form of `as-key`.
|