rune-runtime 2.2.1__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.
Files changed (107) hide show
  1. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/CODE_OF_CONDUCT.md +4 -0
  2. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/ISSUE_TEMPLATE/Bug_report.md +4 -0
  3. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/ISSUE_TEMPLATE/Feature_request.md +4 -0
  4. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/ISSUE_TEMPLATE/Support_question.md +4 -0
  5. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/ISSUE_TEMPLATE/meeting-minutes.md +4 -0
  6. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/workflows/build-python-package.yml +4 -0
  7. rune_runtime-3.0.0/.github/workflows/check-license-headers.yml +39 -0
  8. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/workflows/cve-scanning-python.yml +4 -0
  9. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/workflows/license-scanning-python.yml +4 -0
  10. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/workflows/release.yml +4 -0
  11. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.github/workflows/run-tests.yml +8 -0
  12. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.gitignore +3 -0
  13. rune_runtime-3.0.0/.pre-commit-config.yaml +25 -0
  14. {rune_runtime-2.2.1/src/rune_runtime.egg-info → rune_runtime-3.0.0}/PKG-INFO +17 -1
  15. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/README.md +14 -0
  16. rune_runtime-3.0.0/benchmarks/benchmark_list_references.py +93 -0
  17. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/build_wheel.sh +4 -0
  18. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/dev_clean_setup.sh +6 -0
  19. rune_runtime-3.0.0/docs/object-identity.md +114 -0
  20. rune_runtime-3.0.0/netlify.toml +6 -0
  21. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/pyproject.toml +7 -1
  22. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/safety-policy.yml +4 -0
  23. rune_runtime-3.0.0/src/license/apply_headers.sh +6 -0
  24. rune_runtime-3.0.0/src/license/header.txt +2 -0
  25. rune_runtime-3.0.0/src/rune/runtime/base_data_class.py +697 -0
  26. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/cow.py +69 -9
  27. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/extensions/cached_json_rune_object_deserializer.py +20 -16
  28. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/metadata.py +195 -89
  29. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/object_builder.py +3 -0
  30. rune_runtime-3.0.0/src/rune/runtime/reference_list.py +199 -0
  31. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/utils.py +74 -26
  32. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/version.py +3 -3
  33. {rune_runtime-2.2.1 → rune_runtime-3.0.0/src/rune_runtime.egg-info}/PKG-INFO +17 -1
  34. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune_runtime.egg-info/SOURCES.txt +14 -0
  35. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune_runtime.egg-info/requires.txt +2 -0
  36. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune_runtime.egg-info/scm_file_list.json +14 -0
  37. rune_runtime-3.0.0/src/rune_runtime.egg-info/scm_version.json +8 -0
  38. rune_runtime-3.0.0/src/rune_runtime.egg-info/top_level.txt +2 -0
  39. rune_runtime-3.0.0/test/bugs/test_choice_serialization.py +94 -0
  40. rune_runtime-3.0.0/test/bugs/test_extra_meta.py +59 -0
  41. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_basic_types_with_meta.py +186 -12
  42. rune_runtime-3.0.0/test/test_complex_types_with_meta.py +190 -0
  43. rune_runtime-3.0.0/test/test_cow_metadata.py +246 -0
  44. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_keys_and_references.py +251 -5
  45. rune_runtime-3.0.0/test/test_list_cardinality_serialise.py +723 -0
  46. rune_runtime-3.0.0/test/test_number_precision.py +211 -0
  47. rune_runtime-3.0.0/test/test_temporal_copy.py +172 -0
  48. rune_runtime-3.0.0/test/test_top_level_metadata.py +95 -0
  49. rune_runtime-3.0.0/test/test_utils.py +257 -0
  50. rune_runtime-2.2.1/netlify.toml +0 -2
  51. rune_runtime-2.2.1/src/rune/runtime/base_data_class.py +0 -455
  52. rune_runtime-2.2.1/src/rune_runtime.egg-info/scm_version.json +0 -8
  53. rune_runtime-2.2.1/src/rune_runtime.egg-info/top_level.txt +0 -1
  54. rune_runtime-2.2.1/test/test_complex_types_with_meta.py +0 -69
  55. rune_runtime-2.2.1/test/test_utils.py +0 -135
  56. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/.vscode/settings.json +0 -0
  57. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/CONTRIBUTING.md +0 -0
  58. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/EASYCLA_CHANGEME.md +0 -0
  59. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/LICENSE +0 -0
  60. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/LICENSE.spdx +0 -0
  61. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/MAINTAINERS.md +0 -0
  62. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/NOTICE +0 -0
  63. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/docs/home.md +0 -0
  64. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/renovate.json +0 -0
  65. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/setup.cfg +0 -0
  66. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/__init__.py +0 -0
  67. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/conditions.py +0 -0
  68. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/extensions/__init__.py +0 -0
  69. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/func_proxy.py +0 -0
  70. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/native_registry.py +0 -0
  71. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/object_registry.py +0 -0
  72. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune/runtime/py.typed +0 -0
  73. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/src/rune_runtime.egg-info/dependency_links.txt +0 -0
  74. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/__init__.py +0 -0
  75. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/cdm/EUR-Vanilla-account.json +0 -0
  76. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/cdm/test_create_irs.py +0 -0
  77. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/cdm/test_trade_creation.py +0 -0
  78. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/cdm/test_validation.py +0 -0
  79. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/functions/specs/cdm/_bundle.py +0 -0
  80. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/functions/specs/cdm/base/math/Abs.py +0 -0
  81. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/functions/specs/cdm/base/math/RoundToNearest.py +0 -0
  82. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/functions/specs/cdm/base/math/RoundingModeEnum.py +0 -0
  83. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/functions/specs/test_native_registry.py +0 -0
  84. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/functions/specs/test_simple.py +0 -0
  85. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/local_data/ird-ex01-vanilla-swap-versioned.json +0 -0
  86. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/run_runtime_tests.sh +0 -0
  87. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/serializer-round-trip/test_basic.py +0 -0
  88. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/serializer-round-trip/test_conditions.py +0 -0
  89. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/serializer-round-trip/test_enumtype.py +0 -0
  90. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/serializer-round-trip/test_extension.py +0 -0
  91. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/serializer-round-trip/test_metakey.py +0 -0
  92. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/serializer-round-trip/test_metalocation.py +0 -0
  93. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/serializer-round-trip/test_metascheme.py +0 -0
  94. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_cached_json_rune_object_deserializer.py +0 -0
  95. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_cow.py +0 -0
  96. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_deep_keys_and_references.py +0 -0
  97. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_deferred_ref_deserialization.py +0 -0
  98. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_func_proxy.py +0 -0
  99. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_list_cardinality_with_meta.py +0 -0
  100. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_namespace_prefix.py +0 -0
  101. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_object_builder.py +0 -0
  102. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_operations.py +0 -0
  103. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_parametrized_basic_types.py +0 -0
  104. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_rune_parent.py +0 -0
  105. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_rune_str.py +0 -0
  106. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_subtype_identity.py +0 -0
  107. {rune_runtime-2.2.1 → rune_runtime-3.0.0}/test/test_zoned_date_time.py +0 -0
@@ -1,3 +1,7 @@
1
+ <!--
2
+ Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ SPDX-License-Identifier: Apache-2.0
4
+ -->
1
5
  # Code of Conduct for {project name}
2
6
 
3
7
  Please see the [Community Code of Conduct](https://www.finos.org/code-of-conduct).
@@ -1,3 +1,7 @@
1
+ <!--
2
+ Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ SPDX-License-Identifier: Apache-2.0
4
+ -->
1
5
  ---
2
6
  name: 🐛 Bug Report
3
7
  about: If something isn't working as expected 🤔.
@@ -1,3 +1,7 @@
1
+ <!--
2
+ Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ SPDX-License-Identifier: Apache-2.0
4
+ -->
1
5
  ---
2
6
  name: 🚀 Feature Request
3
7
  about: I have a suggestion (and may want to implement it 🙂)!
@@ -1,3 +1,7 @@
1
+ <!--
2
+ Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ SPDX-License-Identifier: Apache-2.0
4
+ -->
1
5
  ---
2
6
  name: 🤗 Support Question
3
7
  about: If you have a question about configuration, usage, etc. 💬
@@ -1,3 +1,7 @@
1
+ <!--
2
+ Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ SPDX-License-Identifier: Apache-2.0
4
+ -->
1
5
  ---
2
6
  name: "\U0001F91D {project name} Meeting Minutes"
3
7
  about: To track {project name} meeting agenda and attendance
@@ -1,3 +1,7 @@
1
+ ##
2
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ ## SPDX-License-Identifier: Apache-2.0
4
+ ##
1
5
  name: Build and Release Wheel
2
6
 
3
7
  on:
@@ -0,0 +1,39 @@
1
+ ##
2
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ ## SPDX-License-Identifier: Apache-2.0
4
+ ##
5
+ name: Check License Headers
6
+
7
+ on:
8
+ push:
9
+ branches:
10
+ - main
11
+ - develop
12
+ pull_request:
13
+ branches:
14
+ - main
15
+ - develop
16
+
17
+ jobs:
18
+ license-headers:
19
+ name: Verify license headers
20
+ runs-on: ubuntu-latest
21
+
22
+ steps:
23
+ - name: Checkout repository
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Set up Python 3.11
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: '3.11'
30
+
31
+ - name: Install licenseheaders
32
+ run: pip install licenseheaders
33
+
34
+ - name: Check license headers
35
+ run: |
36
+ src/license/apply_headers.sh
37
+ git diff --exit-code || \
38
+ (echo "One or more files are missing a license header. Run 'src/license/apply_headers.sh' locally to fix." && \
39
+ exit 1)
@@ -1,3 +1,7 @@
1
+ ##
2
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ ## SPDX-License-Identifier: Apache-2.0
4
+ ##
1
5
  name: CVE Scanning for Python
2
6
 
3
7
  on:
@@ -1,3 +1,7 @@
1
+ ##
2
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ ## SPDX-License-Identifier: Apache-2.0
4
+ ##
1
5
  name: License Scanning for Python
2
6
 
3
7
  on:
@@ -1,3 +1,7 @@
1
+ ##
2
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ ## SPDX-License-Identifier: Apache-2.0
4
+ ##
1
5
  name: Publish to PyPi
2
6
 
3
7
  on:
@@ -1,3 +1,7 @@
1
+ ##
2
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ ## SPDX-License-Identifier: Apache-2.0
4
+ ##
1
5
  name: Run Unit Tests
2
6
 
3
7
  on:
@@ -5,6 +9,10 @@ on:
5
9
  branches:
6
10
  - main
7
11
  - develop
12
+ pull_request:
13
+ branches:
14
+ - main
15
+ - develop
8
16
 
9
17
  jobs:
10
18
  test:
@@ -28,3 +28,6 @@ rune.runtime-*.whl
28
28
  src/rune/runtime/version.py
29
29
  /.coverage
30
30
  src/rune_runtime.egg-info
31
+
32
+ # local scratch files
33
+ /.tmp/
@@ -0,0 +1,25 @@
1
+ ##
2
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ ## SPDX-License-Identifier: Apache-2.0
4
+ ##
5
+ repos:
6
+ - repo: local
7
+ hooks:
8
+ - id: license-headers-dirs
9
+ name: Add/check license headers (src, test, .github)
10
+ entry: licenseheaders
11
+ args: [-t, src/license/header.txt, --dir, src, --dir, test, --dir, .github]
12
+ language: python
13
+ additional_dependencies: [licenseheaders>=0.8.8]
14
+ types_or: [python, yaml, shell, toml]
15
+ pass_filenames: false
16
+ - id: license-headers-files
17
+ name: Add/check license headers (root files)
18
+ entry: licenseheaders
19
+ args: [-t, src/license/header.txt, --additional-extensions, python=.toml,
20
+ -f, build_wheel.sh, dev_clean_setup.sh, netlify.toml, pyproject.toml,
21
+ safety-policy.yml, .pre-commit-config.yaml]
22
+ language: python
23
+ additional_dependencies: [licenseheaders>=0.8.8]
24
+ types_or: [python, yaml, shell, toml]
25
+ pass_filenames: false
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rune-runtime
3
- Version: 2.2.1
3
+ Version: 3.0.0
4
4
  Summary: rune-runtime: the Rune DSL runtime for Python
5
5
  Author: Daniel Schwartz, Plamen Neykov, Others (See AUTHORS)
6
6
  License-Expression: Apache-2.0
@@ -30,6 +30,8 @@ Requires-Dist: pytest>=9.0.2; extra == "dev"
30
30
  Requires-Dist: pytest-cov>=7.0.0; extra == "dev"
31
31
  Requires-Dist: pytest-mock>=3.14.1; extra == "dev"
32
32
  Requires-Dist: types-python-dateutil>=2.9.0.20250809; extra == "dev"
33
+ Requires-Dist: licenseheaders>=0.8.8; extra == "dev"
34
+ Requires-Dist: pre-commit>=3.0.0; extra == "dev"
33
35
  Dynamic: license-file
34
36
 
35
37
  [![FINOS - Incubating](https://cdn.jsdelivr.net/gh/finos/contrib-toolbox@master/images/badge-incubating.svg)](https://community.finos.org/docs/governance/Software-Projects/stages/incubating)
@@ -59,6 +61,20 @@ Use [dev_clean_setup.sh](https://github.com/Cloudrisk/rune-python-runtime/blob/m
59
61
  ```sh
60
62
  ./dev_clean_setup.sh
61
63
  ```
64
+
65
+ This script installs all dev dependencies and registers the pre-commit hooks, including the license-header check. Every `.py` file under `src/` and `test/` must carry the Apache 2.0 SPDX header:
66
+
67
+ ```python
68
+ # Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
69
+ # SPDX-License-Identifier: Apache-2.0
70
+ ```
71
+
72
+ To add missing headers to all files at any time, run:
73
+
74
+ ```sh
75
+ src/license/apply_headers.sh
76
+ ```
77
+
62
78
  ### 3. Build
63
79
  Use [build_wheel.sh](https://github.com/Cloudrisk/rune-python-runtime/blob/main/build_wheel.sh) to build the package
64
80
  ```sh
@@ -25,6 +25,20 @@ Use [dev_clean_setup.sh](https://github.com/Cloudrisk/rune-python-runtime/blob/m
25
25
  ```sh
26
26
  ./dev_clean_setup.sh
27
27
  ```
28
+
29
+ This script installs all dev dependencies and registers the pre-commit hooks, including the license-header check. Every `.py` file under `src/` and `test/` must carry the Apache 2.0 SPDX header:
30
+
31
+ ```python
32
+ # Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
33
+ # SPDX-License-Identifier: Apache-2.0
34
+ ```
35
+
36
+ To add missing headers to all files at any time, run:
37
+
38
+ ```sh
39
+ src/license/apply_headers.sh
40
+ ```
41
+
28
42
  ### 3. Build
29
43
  Use [build_wheel.sh](https://github.com/Cloudrisk/rune-python-runtime/blob/main/build_wheel.sh) to build the package
30
44
  ```sh
@@ -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()
@@ -1,4 +1,8 @@
1
1
  #!/bin/bash
2
+ ##
3
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
4
+ ## SPDX-License-Identifier: Apache-2.0
5
+ ##
2
6
 
3
7
  function error
4
8
  {
@@ -1,4 +1,8 @@
1
1
  #!/bin/bash
2
+ ##
3
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
4
+ ## SPDX-License-Identifier: Apache-2.0
5
+ ##
2
6
 
3
7
  function error
4
8
  {
@@ -34,3 +38,5 @@ ${PY_EXE} -m venv --clear .pydevenv || error
34
38
 
35
39
 
36
40
  python -m pip install -e .[dev] --config-settings editable_mode=compat || error
41
+
42
+ pre-commit install || error
@@ -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`.
@@ -0,0 +1,6 @@
1
+ #
2
+ # Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
5
+ [build]
6
+ ignore = "/bin/false"
@@ -1,3 +1,7 @@
1
+ #
2
+ # Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ # SPDX-License-Identifier: Apache-2.0
4
+ #
1
5
  [build-system]
2
6
  requires = ["setuptools>=64", "setuptools_scm>=8", "wheel>=0.45.1", "pytest-runner>=6.0.1"]
3
7
  build-backend = "setuptools.build_meta"
@@ -15,7 +19,9 @@ optional-dependencies.dev = [
15
19
  "pytest>=9.0.2",
16
20
  "pytest-cov>=7.0.0",
17
21
  "pytest-mock>=3.14.1",
18
- "types-python-dateutil>=2.9.0.20250809"
22
+ "types-python-dateutil>=2.9.0.20250809",
23
+ "licenseheaders>=0.8.8",
24
+ "pre-commit>=3.0.0"
19
25
  ]
20
26
  description = "rune-runtime: the Rune DSL runtime for Python"
21
27
  readme = "README.md"
@@ -1,3 +1,7 @@
1
+ ##
2
+ ## Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
3
+ ## SPDX-License-Identifier: Apache-2.0
4
+ ##
1
5
  security:
2
6
  ignore-cvss-severity-below: 6
3
7
  ignore-cvss-unknown-severity: False
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+ # Applies the Apache 2.0 SPDX license header to all tracked source files.
3
+ set -e
4
+ TMPL="$(dirname "$0")/header.txt"
5
+ licenseheaders -t "$TMPL" --dir src --dir test --dir .github
6
+ licenseheaders -t "$TMPL" --additional-extensions python=.toml -f build_wheel.sh dev_clean_setup.sh netlify.toml pyproject.toml safety-policy.yml .pre-commit-config.yaml
@@ -0,0 +1,2 @@
1
+ Copyright (c) 2023-2026 CLOUDRISK Limited and FT Advisory LLC
2
+ SPDX-License-Identifier: Apache-2.0