rune-runtime 2.2.0__tar.gz → 2.2.1__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 (90) hide show
  1. {rune_runtime-2.2.0/src/rune_runtime.egg-info → rune_runtime-2.2.1}/PKG-INFO +1 -1
  2. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/base_data_class.py +25 -7
  3. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/version.py +3 -3
  4. {rune_runtime-2.2.0 → rune_runtime-2.2.1/src/rune_runtime.egg-info}/PKG-INFO +1 -1
  5. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune_runtime.egg-info/SOURCES.txt +1 -0
  6. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune_runtime.egg-info/scm_file_list.json +1 -0
  7. rune_runtime-2.2.1/src/rune_runtime.egg-info/scm_version.json +8 -0
  8. rune_runtime-2.2.1/test/cdm/test_validation.py +46 -0
  9. rune_runtime-2.2.1/test/local_data/ird-ex01-vanilla-swap-versioned.json +307 -0
  10. rune_runtime-2.2.1/test/test_deep_keys_and_references.py +247 -0
  11. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_deferred_ref_deserialization.py +30 -3
  12. rune_runtime-2.2.0/src/rune_runtime.egg-info/scm_version.json +0 -8
  13. rune_runtime-2.2.0/test/cdm/test_validation.py +0 -23
  14. rune_runtime-2.2.0/test/test_deep_keys_and_references.py +0 -124
  15. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/CODE_OF_CONDUCT.md +0 -0
  16. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/ISSUE_TEMPLATE/Bug_report.md +0 -0
  17. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/ISSUE_TEMPLATE/Feature_request.md +0 -0
  18. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/ISSUE_TEMPLATE/Support_question.md +0 -0
  19. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/ISSUE_TEMPLATE/meeting-minutes.md +0 -0
  20. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/workflows/build-python-package.yml +0 -0
  21. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/workflows/cve-scanning-python.yml +0 -0
  22. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/workflows/license-scanning-python.yml +0 -0
  23. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/workflows/release.yml +0 -0
  24. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.github/workflows/run-tests.yml +0 -0
  25. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.gitignore +0 -0
  26. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/.vscode/settings.json +0 -0
  27. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/CONTRIBUTING.md +0 -0
  28. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/EASYCLA_CHANGEME.md +0 -0
  29. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/LICENSE +0 -0
  30. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/LICENSE.spdx +0 -0
  31. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/MAINTAINERS.md +0 -0
  32. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/NOTICE +0 -0
  33. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/README.md +0 -0
  34. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/build_wheel.sh +0 -0
  35. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/dev_clean_setup.sh +0 -0
  36. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/docs/home.md +0 -0
  37. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/netlify.toml +0 -0
  38. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/pyproject.toml +0 -0
  39. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/renovate.json +0 -0
  40. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/safety-policy.yml +0 -0
  41. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/setup.cfg +0 -0
  42. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/__init__.py +0 -0
  43. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/conditions.py +0 -0
  44. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/cow.py +0 -0
  45. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/extensions/__init__.py +0 -0
  46. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/extensions/cached_json_rune_object_deserializer.py +0 -0
  47. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/func_proxy.py +0 -0
  48. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/metadata.py +0 -0
  49. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/native_registry.py +0 -0
  50. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/object_builder.py +0 -0
  51. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/object_registry.py +0 -0
  52. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/py.typed +0 -0
  53. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune/runtime/utils.py +0 -0
  54. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune_runtime.egg-info/dependency_links.txt +0 -0
  55. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune_runtime.egg-info/requires.txt +0 -0
  56. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/src/rune_runtime.egg-info/top_level.txt +0 -0
  57. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/__init__.py +0 -0
  58. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/cdm/EUR-Vanilla-account.json +0 -0
  59. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/cdm/test_create_irs.py +0 -0
  60. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/cdm/test_trade_creation.py +0 -0
  61. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/functions/specs/cdm/_bundle.py +0 -0
  62. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/functions/specs/cdm/base/math/Abs.py +0 -0
  63. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/functions/specs/cdm/base/math/RoundToNearest.py +0 -0
  64. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/functions/specs/cdm/base/math/RoundingModeEnum.py +0 -0
  65. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/functions/specs/test_native_registry.py +0 -0
  66. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/functions/specs/test_simple.py +0 -0
  67. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/run_runtime_tests.sh +0 -0
  68. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/serializer-round-trip/test_basic.py +0 -0
  69. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/serializer-round-trip/test_conditions.py +0 -0
  70. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/serializer-round-trip/test_enumtype.py +0 -0
  71. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/serializer-round-trip/test_extension.py +0 -0
  72. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/serializer-round-trip/test_metakey.py +0 -0
  73. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/serializer-round-trip/test_metalocation.py +0 -0
  74. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/serializer-round-trip/test_metascheme.py +0 -0
  75. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_basic_types_with_meta.py +0 -0
  76. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_cached_json_rune_object_deserializer.py +0 -0
  77. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_complex_types_with_meta.py +0 -0
  78. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_cow.py +0 -0
  79. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_func_proxy.py +0 -0
  80. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_keys_and_references.py +0 -0
  81. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_list_cardinality_with_meta.py +0 -0
  82. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_namespace_prefix.py +0 -0
  83. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_object_builder.py +0 -0
  84. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_operations.py +0 -0
  85. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_parametrized_basic_types.py +0 -0
  86. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_rune_parent.py +0 -0
  87. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_rune_str.py +0 -0
  88. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_subtype_identity.py +0 -0
  89. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_utils.py +0 -0
  90. {rune_runtime-2.2.0 → rune_runtime-2.2.1}/test/test_zoned_date_time.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rune-runtime
3
- Version: 2.2.0
3
+ Version: 2.2.1
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
@@ -125,6 +125,15 @@ class BaseDataClass(BaseModel, ComplexTypeMetaDataMixin):
125
125
  return aux
126
126
  obj = handler(data)
127
127
  obj._init_rune_parent() # pylint: disable=protected-access
128
+ if isinstance(data, dict):
129
+ # Deferred annotations can bypass the field metadata validator.
130
+ # Retain target keys before resolving references to this model.
131
+ keys = {k: v for k, v in metadata.items() if k.startswith('@key')}
132
+ if keys:
133
+ # Field-level validators handle allowed metadata, including
134
+ # scoped keys that are not declared on the target class.
135
+ obj.set_meta(check_allowed=False, **keys)
136
+ obj._register_keys(keys) # pylint: disable=protected-access
128
137
  obj.resolve_references(ignore_dangling=True, recurse=False)
129
138
  # transfer refs that were established on the original before
130
139
  # re-validation wiped them
@@ -140,9 +149,12 @@ class BaseDataClass(BaseModel, ComplexTypeMetaDataMixin):
140
149
  self.__dict__[RUNE_OBJ_MAPS] = {}
141
150
 
142
151
  for prop_nm, obj in self.__dict__.items():
143
- if (isinstance(obj, BaseMetaDataMixin)
144
- and not prop_nm.startswith('__') and prop_nm not in refs):
145
- obj._set_rune_parent(self) # pylint: disable=protected-access
152
+ if prop_nm.startswith('__') or prop_nm in refs:
153
+ continue
154
+ children = obj if isinstance(obj, (MutableSequence, tuple)) else (obj,)
155
+ for child in children:
156
+ if isinstance(child, BaseMetaDataMixin):
157
+ child._set_rune_parent(self) # pylint: disable=protected-access
146
158
 
147
159
  def rune_serialize(
148
160
  self,
@@ -306,11 +318,17 @@ class BaseDataClass(BaseModel, ComplexTypeMetaDataMixin):
306
318
  def resolve_references(self, ignore_dangling=False, recurse=True):
307
319
  '''resolves all attributes which are references'''
308
320
  if recurse:
321
+ resolved_refs = self._get_rune_refs_container()
309
322
  for prop_nm, obj in self.__dict__.items():
310
- if (isinstance(obj, BaseDataClass)
311
- and not prop_nm.startswith('__')):
312
- obj.resolve_references(ignore_dangling=ignore_dangling,
313
- recurse=recurse)
323
+ # Follow containment only; resolved references can point back
324
+ # to an ancestor and must not be traversed again.
325
+ if prop_nm.startswith('__') or prop_nm in resolved_refs:
326
+ continue
327
+ children = obj if isinstance(obj, (MutableSequence, tuple)) else (obj,)
328
+ for child in children:
329
+ if isinstance(child, BaseDataClass):
330
+ child.resolve_references(ignore_dangling=ignore_dangling,
331
+ recurse=recurse)
314
332
 
315
333
  refs = []
316
334
  for prop_nm, obj in self.__dict__.items():
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.2.0'
22
- __version_tuple__ = version_tuple = (2, 2, 0)
21
+ __version__ = version = '2.2.1'
22
+ __version_tuple__ = version_tuple = (2, 2, 1)
23
23
 
24
- __commit_id__ = commit_id = 'g58791175d'
24
+ __commit_id__ = commit_id = 'g05cabe482'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rune-runtime
3
- Version: 2.2.0
3
+ Version: 2.2.1
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
@@ -75,6 +75,7 @@ test/functions/specs/cdm/_bundle.py
75
75
  test/functions/specs/cdm/base/math/Abs.py
76
76
  test/functions/specs/cdm/base/math/RoundToNearest.py
77
77
  test/functions/specs/cdm/base/math/RoundingModeEnum.py
78
+ test/local_data/ird-ex01-vanilla-swap-versioned.json
78
79
  test/serializer-round-trip/test_basic.py
79
80
  test/serializer-round-trip/test_conditions.py
80
81
  test/serializer-round-trip/test_enumtype.py
@@ -50,6 +50,7 @@
50
50
  "test/functions/specs/cdm/base/math/RoundingModeEnum.py",
51
51
  "test/functions/specs/test_native_registry.py",
52
52
  "test/functions/specs/test_simple.py",
53
+ "test/local_data/ird-ex01-vanilla-swap-versioned.json",
53
54
  "test/run_runtime_tests.sh",
54
55
  "test/serializer-round-trip/test_basic.py",
55
56
  "test/serializer-round-trip/test_conditions.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.2.1",
3
+ "distance": 0,
4
+ "node": "g05cabe4820d4659485de75d071c296b38c2e4d45",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-16"
8
+ }
@@ -0,0 +1,46 @@
1
+ '''Full attribute validation - pydantic and constraints'''
2
+ from pathlib import Path
3
+ import pytest
4
+ from pydantic import ValidationError
5
+ from rune.runtime.base_data_class import BaseDataClass
6
+ try:
7
+ # pylint: disable=unused-import
8
+ # type: ignore
9
+ from cdm.base.math.NonNegativeQuantity import NonNegativeQuantity
10
+ from cdm.base.math.UnitType import UnitType
11
+ NO_SER_TEST_MOD = False
12
+ except ImportError:
13
+ NO_SER_TEST_MOD = True
14
+
15
+
16
+ @pytest.mark.skipif(NO_SER_TEST_MOD, reason='CDM package not found')
17
+ def test_bad_attrib_validation():
18
+ '''Invalid attribute assigned'''
19
+ unit = UnitType(currency='EUR')
20
+ mq = NonNegativeQuantity(value=10, unit=unit)
21
+ mq.frequency = 'Blah'
22
+ with pytest.raises(ValidationError):
23
+ mq.validate_model()
24
+
25
+
26
+ def test_vanilla_swap_list_reference_validation():
27
+ '''Validate the reported sample when local data and the CDM bundle exist.'''
28
+ sample = Path(__file__).resolve().parents[1] / 'local_data' / 'ird-ex01-vanilla-swap-versioned.json'
29
+ if not sample.is_file():
30
+ pytest.skip('Local CDM sample not found')
31
+ pytest.importorskip('finos')
32
+
33
+ model = BaseDataClass.rune_deserialize(
34
+ sample.read_text(encoding='utf-8'),
35
+ namespace_prefix='finos',
36
+ check_rune_constraints=False,
37
+ )
38
+
39
+ assert len(model.trade.counterparty) == 2
40
+ parties = {party.get_meta('@key:external'): party for party in model.trade.party}
41
+ for counterparty in model.trade.counterparty:
42
+ key = counterparty.resolve_ref_key('partyReference')
43
+ assert counterparty.partyReference is parties[key]
44
+ assert model.validate_model(check_rune_constraints=False) == []
45
+
46
+ # EOF
@@ -0,0 +1,307 @@
1
+ {
2
+ "@model" : "cdm",
3
+ "@type" : "cdm.event.common.TradeState",
4
+ "@version" : "0.0.0.master-SNAPSHOT",
5
+ "@key" : "3afe24c4",
6
+ "trade" : {
7
+ "product" : {
8
+ "taxonomy" : [ {
9
+ "source" : "ISDA",
10
+ "value" : {
11
+ "name" : {
12
+ "@data" : "InterestRate_IRSwap_FixedFloat"
13
+ }
14
+ },
15
+ "calculated" : true
16
+ } ],
17
+ "economicTerms" : {
18
+ "payout" : [ {
19
+ "@type" : "cdm.product.asset.InterestRatePayout",
20
+ "payerReceiver" : {
21
+ "payer" : "Party1",
22
+ "receiver" : "Party2"
23
+ },
24
+ "priceQuantity" : {
25
+ "quantitySchedule" : {
26
+ "@ref:scoped" : "quantity-1"
27
+ }
28
+ },
29
+ "rateSpecification" : {
30
+ "@type" : "cdm.product.asset.FloatingRateSpecification",
31
+ "rateOption" : {
32
+ "@ref:scoped" : "InterestRateIndex-1"
33
+ }
34
+ },
35
+ "dayCountFraction" : {
36
+ "@data" : "ACT/360"
37
+ },
38
+ "calculationPeriodDates" : {
39
+ "@key:external" : "floatingCalcPeriodDates",
40
+ "effectiveDate" : {
41
+ "adjustableDate" : {
42
+ "unadjustedDate" : "1994-12-14",
43
+ "dateAdjustments" : {
44
+ "businessDayConvention" : "NONE"
45
+ }
46
+ }
47
+ },
48
+ "terminationDate" : {
49
+ "adjustableDate" : {
50
+ "unadjustedDate" : "1999-12-14",
51
+ "dateAdjustments" : {
52
+ "businessDayConvention" : "MODFOLLOWING"
53
+ }
54
+ }
55
+ },
56
+ "calculationPeriodDatesAdjustments" : {
57
+ "businessDayConvention" : "MODFOLLOWING",
58
+ "businessCenters" : {
59
+ "businessCentersReference" : {
60
+ "@ref:external" : "primaryBusinessCenters"
61
+ }
62
+ }
63
+ },
64
+ "calculationPeriodFrequency" : {
65
+ "periodMultiplier" : 6,
66
+ "period" : "M",
67
+ "rollConvention" : "14"
68
+ }
69
+ },
70
+ "paymentDates" : {
71
+ "paymentFrequency" : {
72
+ "periodMultiplier" : 6,
73
+ "period" : "M"
74
+ },
75
+ "payRelativeTo" : "CalculationPeriodEndDate",
76
+ "paymentDatesAdjustments" : {
77
+ "businessDayConvention" : "MODFOLLOWING",
78
+ "businessCenters" : {
79
+ "businessCentersReference" : {
80
+ "@ref:external" : "primaryBusinessCenters"
81
+ }
82
+ }
83
+ }
84
+ },
85
+ "resetDates" : {
86
+ "@key:external" : "resetDates",
87
+ "calculationPeriodDatesReference" : {
88
+ "@ref:external" : "floatingCalcPeriodDates"
89
+ },
90
+ "resetRelativeTo" : "CalculationPeriodStartDate",
91
+ "fixingDates" : {
92
+ "periodMultiplier" : -2,
93
+ "period" : "D",
94
+ "dayType" : "Business",
95
+ "businessDayConvention" : "NONE",
96
+ "dateRelativeTo" : {
97
+ "@ref" : "a0134250",
98
+ "@ref:external" : "resetDates"
99
+ }
100
+ },
101
+ "resetFrequency" : {
102
+ "periodMultiplier" : 6,
103
+ "period" : "M"
104
+ },
105
+ "resetDatesAdjustments" : {
106
+ "businessDayConvention" : "MODFOLLOWING",
107
+ "businessCenters" : {
108
+ "businessCentersReference" : {
109
+ "@ref:external" : "primaryBusinessCenters"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }, {
115
+ "@type" : "cdm.product.asset.InterestRatePayout",
116
+ "payerReceiver" : {
117
+ "payer" : "Party2",
118
+ "receiver" : "Party1"
119
+ },
120
+ "priceQuantity" : {
121
+ "quantitySchedule" : {
122
+ "@ref:scoped" : "quantity-2"
123
+ }
124
+ },
125
+ "rateSpecification" : {
126
+ "@type" : "cdm.product.asset.FixedRateSpecification",
127
+ "rateSchedule" : {
128
+ "price" : {
129
+ "@ref:scoped" : "price-1"
130
+ }
131
+ }
132
+ },
133
+ "dayCountFraction" : {
134
+ "@data" : "30E/360"
135
+ },
136
+ "calculationPeriodDates" : {
137
+ "@key:external" : "fixedCalcPeriodDates",
138
+ "effectiveDate" : {
139
+ "adjustableDate" : {
140
+ "unadjustedDate" : "1994-12-14",
141
+ "dateAdjustments" : {
142
+ "businessDayConvention" : "NONE"
143
+ }
144
+ }
145
+ },
146
+ "terminationDate" : {
147
+ "adjustableDate" : {
148
+ "unadjustedDate" : "1999-12-14",
149
+ "dateAdjustments" : {
150
+ "businessDayConvention" : "MODFOLLOWING",
151
+ "businessCenters" : {
152
+ "businessCentersReference" : {
153
+ "@ref:external" : "primaryBusinessCenters"
154
+ }
155
+ }
156
+ }
157
+ }
158
+ },
159
+ "calculationPeriodDatesAdjustments" : {
160
+ "businessDayConvention" : "MODFOLLOWING",
161
+ "businessCenters" : {
162
+ "businessCentersReference" : {
163
+ "@ref:external" : "primaryBusinessCenters"
164
+ }
165
+ }
166
+ },
167
+ "calculationPeriodFrequency" : {
168
+ "periodMultiplier" : 1,
169
+ "period" : "Y",
170
+ "rollConvention" : "14"
171
+ }
172
+ },
173
+ "paymentDates" : {
174
+ "paymentFrequency" : {
175
+ "periodMultiplier" : 1,
176
+ "period" : "Y"
177
+ },
178
+ "payRelativeTo" : "CalculationPeriodEndDate",
179
+ "paymentDatesAdjustments" : {
180
+ "businessDayConvention" : "MODFOLLOWING",
181
+ "businessCenters" : {
182
+ "businessCentersReference" : {
183
+ "@ref:external" : "primaryBusinessCenters"
184
+ }
185
+ }
186
+ }
187
+ }
188
+ } ]
189
+ }
190
+ },
191
+ "tradeLot" : [ {
192
+ "priceQuantity" : [ {
193
+ "quantity" : {
194
+ "@key:scoped" : "quantity-1",
195
+ "value" : 50000000.00,
196
+ "unit" : {
197
+ "currency" : {
198
+ "@scheme" : "http://www.fpml.org/coding-scheme/external/iso4217",
199
+ "@data" : "EUR"
200
+ }
201
+ }
202
+ },
203
+ "observable" : {
204
+ "@key:scoped" : "observable-1",
205
+ "@data" : {
206
+ "@type" : "cdm.observable.asset.InterestRateIndex",
207
+ "@key:scoped" : "InterestRateIndex-1",
208
+ "@data" : {
209
+ "@type" : "cdm.observable.asset.FloatingRateIndex",
210
+ "identifier" : [ {
211
+ "identifier" : {
212
+ "@data" : "EUR-LIBOR-BBA"
213
+ },
214
+ "identifierType" : "Other"
215
+ } ],
216
+ "assetType" : "Other",
217
+ "assetClass" : "InterestRate",
218
+ "floatingRateIndex" : {
219
+ "@data" : "EUR-LIBOR-BBA"
220
+ },
221
+ "indexTenor" : {
222
+ "periodMultiplier" : 6,
223
+ "period" : "M"
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }, {
229
+ "price" : [ {
230
+ "@key:scoped" : "price-1",
231
+ "value" : 0.06,
232
+ "unit" : {
233
+ "currency" : {
234
+ "@scheme" : "http://www.fpml.org/coding-scheme/external/iso4217",
235
+ "@data" : "EUR"
236
+ }
237
+ },
238
+ "perUnitOf" : {
239
+ "currency" : {
240
+ "@scheme" : "http://www.fpml.org/coding-scheme/external/iso4217",
241
+ "@data" : "EUR"
242
+ }
243
+ },
244
+ "priceType" : "InterestRate"
245
+ } ],
246
+ "quantity" : {
247
+ "@key:scoped" : "quantity-2",
248
+ "value" : 50000000.00,
249
+ "unit" : {
250
+ "currency" : {
251
+ "@scheme" : "http://www.fpml.org/coding-scheme/external/iso4217",
252
+ "@data" : "EUR"
253
+ }
254
+ }
255
+ }
256
+ } ]
257
+ } ],
258
+ "counterparty" : [ {
259
+ "role" : "Party1",
260
+ "partyReference" : {
261
+ "@ref:external" : "party1"
262
+ }
263
+ }, {
264
+ "role" : "Party2",
265
+ "partyReference" : {
266
+ "@ref:external" : "party2"
267
+ }
268
+ } ],
269
+ "tradeIdentifier" : [ {
270
+ "issuerReference" : {
271
+ "@ref:external" : "party2"
272
+ },
273
+ "assignedIdentifier" : [ {
274
+ "identifier" : {
275
+ "@scheme" : "http://www.barclays.com/swaps/trade-id",
276
+ "@data" : "SW2000"
277
+ },
278
+ "version" : 1
279
+ } ]
280
+ } ],
281
+ "tradeDate" : {
282
+ "@data" : "1994-12-12"
283
+ },
284
+ "party" : [ {
285
+ "@key:external" : "party1",
286
+ "partyId" : [ {
287
+ "identifier" : {
288
+ "@scheme" : "http://www.fpml.org/coding-scheme/external/iso9362",
289
+ "@data" : "PARTYAUS33"
290
+ },
291
+ "identifierType" : "BIC"
292
+ } ],
293
+ "name" : {
294
+ "@data" : "Party A"
295
+ }
296
+ }, {
297
+ "@key:external" : "party2",
298
+ "partyId" : [ {
299
+ "identifier" : {
300
+ "@scheme" : "http://www.fpml.org/coding-scheme/external/iso9362",
301
+ "@data" : "BARCGB2L"
302
+ },
303
+ "identifierType" : "BIC"
304
+ } ]
305
+ } ]
306
+ }
307
+ }
@@ -0,0 +1,247 @@
1
+ '''test module for ref lifecycle'''
2
+ from collections import UserList
3
+ from typing import Optional, Annotated
4
+ from pydantic import Field
5
+ import pytest
6
+ from rune.runtime.metadata import Reference, KeyType, UnresolvedReference
7
+ from rune.runtime.base_data_class import BaseDataClass
8
+
9
+
10
+ class B(BaseDataClass):
11
+ '''no doc'''
12
+ fieldB: str = Field(..., description='')
13
+
14
+
15
+ class A(BaseDataClass):
16
+ '''no doc'''
17
+ b: Annotated[B, B.serializer(),
18
+ B.validator(('@key:scoped', ))] = Field(..., description='')
19
+
20
+
21
+ class Root(BaseDataClass):
22
+ '''no doc'''
23
+ typeA: Optional[Annotated[A, A.serializer(),
24
+ A.validator()]] = Field(None, description='')
25
+ bAddress: Optional[Annotated[B,
26
+ B.serializer(),
27
+ B.validator(('@ref:scoped', ))]] = Field(
28
+ None, description='')
29
+ _KEY_REF_CONSTRAINTS = {
30
+ 'bAddress': {'@ref:scoped'}
31
+ }
32
+
33
+ class DeepRef(BaseDataClass):
34
+ '''no doc'''
35
+ root: Annotated[Root, Root.serializer(),
36
+ Root.validator()] = Field(..., description='')
37
+
38
+
39
+ class ListedReferences(BaseDataClass):
40
+ '''References and their targets occur in separate list fields.'''
41
+ references: list[Annotated[Root, Root.validator()]]
42
+ targets: list[Annotated[A, A.validator()]]
43
+
44
+
45
+ class NestedListedReferences(BaseDataClass):
46
+ '''Exercise lists below both model fields and other lists.'''
47
+ groups: list[Annotated[ListedReferences, ListedReferences.validator()]]
48
+ labels: list[str] = Field(default_factory=list)
49
+
50
+
51
+ class SequenceContainer(BaseDataClass):
52
+ '''Keep sequence implementations intact during validation.'''
53
+ entries: object
54
+
55
+
56
+ class CyclicReference(BaseDataClass):
57
+ '''A resolved reference can point back to its containing object.'''
58
+ _ALLOWED_METADATA = {'@key'}
59
+ target: Annotated[BaseDataClass, BaseDataClass.validator(('@ref', ))]
60
+ _KEY_REF_CONSTRAINTS = {'target': {'@ref'}}
61
+
62
+
63
+ @pytest.fixture
64
+ def listed_reference_data():
65
+ '''A forward reference to a target in a sibling list.'''
66
+ return {
67
+ 'groups': [{
68
+ 'references': [
69
+ {'bAddress': {'@ref:scoped': 'first'}},
70
+ {'bAddress': {'@ref:scoped': 'second'}},
71
+ ],
72
+ 'targets': [
73
+ {'b': {'@key:scoped': 'first', 'fieldB': 'first target'}},
74
+ {'b': {'@key:scoped': 'second', 'fieldB': 'second target'}},
75
+ ],
76
+ }],
77
+ 'labels': ['primitive list entries are ignored'],
78
+ }
79
+
80
+
81
+ def test_list_children_share_parent_and_key_maps(listed_reference_data):
82
+ '''Keys from list children must be visible to sibling list entries.'''
83
+ model = NestedListedReferences.model_validate(listed_reference_data)
84
+ group = model.groups[0]
85
+
86
+ assert group.get_rune_parent() is model
87
+ for reference, target in zip(group.references, group.targets):
88
+ assert reference.get_rune_parent() is group
89
+ assert target.get_rune_parent() is group
90
+ key = target.b.get_meta('@key:scoped')
91
+ assert reference.get_object_by_key(key, KeyType.SCOPED) is target.b
92
+
93
+
94
+ def test_deserialize_and_validate_references_in_nested_lists(listed_reference_data):
95
+ '''Rune deserialization resolves every list entry before validation.'''
96
+ model = NestedListedReferences.rune_deserialize(listed_reference_data)
97
+ group = model.groups[0]
98
+
99
+ for reference, target in zip(group.references, group.targets):
100
+ assert reference.bAddress is target.b
101
+ assert reference.resolve_ref_key('bAddress') == target.b.get_meta('@key:scoped')
102
+ assert model.validate_model() == []
103
+
104
+ model.resolve_references()
105
+ for reference, target in zip(group.references, group.targets):
106
+ assert reference.bAddress is target.b
107
+
108
+
109
+ def test_resolve_list_references_respects_recurse_false(listed_reference_data):
110
+ '''Nonrecursive resolution leaves child references untouched.'''
111
+ model = NestedListedReferences.model_validate(listed_reference_data)
112
+
113
+ model.resolve_references(recurse=False)
114
+
115
+ assert all(isinstance(ref.bAddress, UnresolvedReference)
116
+ for ref in model.groups[0].references)
117
+
118
+
119
+ def test_resolve_list_references_respects_ignore_dangling(listed_reference_data):
120
+ '''Missing list references may be ignored or reported by the caller.'''
121
+ listed_reference_data['groups'][0]['references'][1]['bAddress'] = {
122
+ '@ref:scoped': 'missing-key'
123
+ }
124
+ model = NestedListedReferences.model_validate(listed_reference_data)
125
+
126
+ model.resolve_references(ignore_dangling=True)
127
+
128
+ group = model.groups[0]
129
+ assert group.references[0].bAddress is group.targets[0].b
130
+ assert isinstance(group.references[1].bAddress, UnresolvedReference)
131
+ with pytest.raises(KeyError, match='missing-key'):
132
+ model.resolve_references(ignore_dangling=False)
133
+
134
+
135
+ @pytest.mark.parametrize('sequence_type', [list, tuple, UserList])
136
+ def test_resolve_references_in_sequence_types(sequence_type):
137
+ '''Supported sequences provide both parent wiring and reference traversal.'''
138
+ reference = Root.model_validate({'bAddress': {'@ref:scoped': 'target'}})
139
+ target = A.model_validate({'b': {'@key:scoped': 'target', 'fieldB': 'value'}})
140
+ model = SequenceContainer(entries=sequence_type([reference, target, 'label', None]))
141
+
142
+ model.resolve_references()
143
+
144
+ assert reference.get_rune_parent() is model
145
+ assert target.get_rune_parent() is model
146
+ assert reference.bAddress is target.b
147
+
148
+
149
+ def test_resolve_references_skips_resolved_cycles_in_lists():
150
+ '''Repeated resolution must not recurse through an already-bound reference.'''
151
+ child = CyclicReference.model_validate({'target': {'@ref': 'self'}})
152
+ child.target = Reference(child)
153
+ model = SequenceContainer(entries=[child])
154
+
155
+ model.resolve_references()
156
+ model.resolve_references()
157
+
158
+ assert child.target is child
159
+
160
+
161
+ def test_ref_creation():
162
+ '''no doc'''
163
+ b = B(fieldB='some b content')
164
+ a = A(b=b)
165
+ root = Root(typeA=a, bAddress=Reference(a.b, 'aKey', KeyType.SCOPED))
166
+ # pylint: disable=no-member
167
+ assert id(root.typeA.b) == id(root.bAddress)
168
+
169
+
170
+ def test_deep_ref_creation():
171
+ '''no doc'''
172
+ b = B(fieldB='some b content')
173
+ a = A(b=b)
174
+ root = Root(typeA=a, bAddress=Reference(a.b, 'aKey2', KeyType.SCOPED))
175
+ deep_ref = DeepRef(root=root)
176
+ # pylint: disable=no-member
177
+ assert id(deep_ref.root.typeA.b) == id(deep_ref.root.bAddress)
178
+
179
+
180
+ def test_fail_wrong_key_ext():
181
+ '''no doc'''
182
+ b = B(fieldB='some b content')
183
+ a = A(b=b)
184
+ with pytest.raises(ValueError):
185
+ Root(typeA=a, bAddress=Reference(a.b, 'aKey', KeyType.EXTERNAL))
186
+
187
+
188
+ def test_fail_wrong_key_int():
189
+ '''no doc'''
190
+ b = B(fieldB='some b content')
191
+ a = A(b=b)
192
+ with pytest.raises(ValueError):
193
+ Root(typeA=a, bAddress=Reference(a.b))
194
+
195
+
196
+ def test_scoped_reference_metadata_and_type():
197
+ '''scoped refs should store key type and metadata'''
198
+ b = B(fieldB='some b content')
199
+ a = A(b=b)
200
+ root = Root(typeA=a, bAddress=Reference(a.b, 'aKeyScoped', KeyType.SCOPED))
201
+ assert b.get_meta('@key:scoped') == 'aKeyScoped'
202
+ assert root.__dict__['__rune_references']['bAddress'][1] == KeyType.SCOPED
203
+
204
+
205
+ def test_ref_prefers_scoped_over_internal():
206
+ '''scoped ref should be preferred when both tags are provided'''
207
+ rune_dict = {
208
+ "bAddress": {
209
+ "@ref": "internalKey",
210
+ "@ref:scoped": "scopedKey"
211
+ },
212
+ "typeA": {
213
+ "b": {
214
+ "@key:scoped": "scopedKey",
215
+ "fieldB": "some b content"
216
+ }
217
+ },
218
+ }
219
+ root = Root.model_validate(rune_dict)
220
+ assert root.bAddress == root.typeA.b
221
+ assert root.__dict__['__rune_references']['bAddress'][1] == KeyType.SCOPED
222
+
223
+
224
+ def test_invalid_multiple_ref_tags_raise():
225
+ '''unknown multiple ref tags should raise'''
226
+ rune_dict = {
227
+ "bAddress": {
228
+ "@ref:foo": "key1",
229
+ "@ref:bar": "key2"
230
+ }
231
+ }
232
+ with pytest.raises(ValueError):
233
+ Root.model_validate(rune_dict)
234
+
235
+
236
+ def test_scoped_key_not_visible_outside_scope(mocker):
237
+ '''scoped keys should not leak across scope instances'''
238
+ mocker.patch('rune.runtime.metadata.BaseMetaDataMixin._DEFAULT_SCOPE_TYPE',
239
+ 'test_deep_keys_and_references.Root')
240
+ b = B(fieldB='some b content')
241
+ a = A(b=b)
242
+ Root(typeA=a, bAddress=Reference(a.b, 'aKeyScoped', KeyType.SCOPED))
243
+ root2 = Root.model_validate({"bAddress": {"@ref:scoped": "aKeyScoped"}})
244
+ with pytest.raises(KeyError):
245
+ root2.resolve_references(ignore_dangling=False, recurse=False)
246
+
247
+ # EOF
@@ -16,6 +16,8 @@ because its required fields are absent.
16
16
 
17
17
  The fix is to have _deserialize_refs check for reference dicts BEFORE calling
18
18
  handler, consistent with ComplexTypeMetaDataMixin.deserialize.
19
+ Target keys must also be retained when the deferred schema bypasses the field
20
+ metadata validator, so references in sibling lists can find their targets.
19
21
 
20
22
  Note on placement: this test lives in rune-runtime even though the Phase 1/2/3
21
23
  pattern originates in the rune-python-generator's bundle generator. The runtime
@@ -29,7 +31,7 @@ import pytest
29
31
  from pydantic import Field
30
32
 
31
33
  from rune.runtime.base_data_class import BaseDataClass
32
- from rune.runtime.metadata import BaseReference, UnresolvedReference
34
+ from rune.runtime.metadata import BaseReference, KeyType, UnresolvedReference
33
35
 
34
36
 
35
37
  class Issuer(BaseDataClass):
@@ -45,7 +47,7 @@ class Contract(BaseDataClass):
45
47
  name: str = Field(..., description='Contract name')
46
48
 
47
49
  _KEY_REF_CONSTRAINTS = {
48
- 'issuerReference': {'@key', '@key:external', '@ref', '@ref:external'}
50
+ 'issuerReference': {'@key', '@key:external', '@ref', '@ref:external', '@ref:scoped'}
49
51
  }
50
52
 
51
53
 
@@ -55,7 +57,7 @@ def _apply_deferred_annotations():
55
57
  Contract.__annotations__['issuerReference'] = Annotated[
56
58
  Optional[Issuer | BaseReference],
57
59
  Issuer.serializer(),
58
- Issuer.validator(('@key', '@key:external', '@ref', '@ref:external')),
60
+ Issuer.validator(('@key', '@key:external', '@ref', '@ref:external', '@ref:scoped')),
59
61
  ]
60
62
  Contract.model_rebuild(force=True)
61
63
 
@@ -63,6 +65,31 @@ def _apply_deferred_annotations():
63
65
  _apply_deferred_annotations()
64
66
 
65
67
 
68
+ class Portfolio(BaseDataClass):
69
+ '''Bare field types reproduce schemas rebuilt from deferred annotations.'''
70
+ contracts: list[Contract]
71
+ issuers: list[Issuer]
72
+
73
+
74
+ @pytest.mark.parametrize('key_type', list(KeyType))
75
+ def test_deferred_list_targets_retain_keys_and_resolve(key_type):
76
+ '''Target keys must survive even when field metadata validators are absent.'''
77
+ data = {
78
+ 'contracts': [{
79
+ 'name': 'test-contract',
80
+ 'issuerReference': {key_type.rune_ref_tag: 'party1'},
81
+ }],
82
+ 'issuers': [{key_type.rune_key_tag: 'party1', 'partyId': ['LEI-001']}],
83
+ }
84
+
85
+ portfolio = Portfolio.rune_deserialize(data)
86
+
87
+ assert portfolio.issuers[0].get_meta(key_type.key_tag) == 'party1'
88
+ assert portfolio.contracts[0].issuerReference is portfolio.issuers[0]
89
+ assert portfolio.get_object_by_key('party1', key_type) is portfolio.issuers[0]
90
+ assert portfolio.validate_model() == []
91
+
92
+
66
93
  def test_ref_external_deserializes_to_unresolved_reference():
67
94
  '''A ref dict in a deferred field should produce an UnresolvedReference,
68
95
  not a validation error about missing required fields on the target type.'''
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "2.2.0",
3
- "distance": 0,
4
- "node": "g58791175d98fff845230d3c6657d620ccb15f2d2",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-08-10"
8
- }
@@ -1,23 +0,0 @@
1
- '''Full attribute validation - pydantic and constraints'''
2
- import pytest
3
- from pydantic import ValidationError
4
- try:
5
- # pylint: disable=unused-import
6
- # type: ignore
7
- from cdm.base.math.NonNegativeQuantity import NonNegativeQuantity
8
- from cdm.base.math.UnitType import UnitType
9
- NO_SER_TEST_MOD = False
10
- except ImportError:
11
- NO_SER_TEST_MOD = True
12
-
13
-
14
- @pytest.mark.skipif(NO_SER_TEST_MOD, reason='CDM package not found')
15
- def test_bad_attrib_validation():
16
- '''Invalid attribute assigned'''
17
- unit = UnitType(currency='EUR')
18
- mq = NonNegativeQuantity(value=10, unit=unit)
19
- mq.frequency = 'Blah'
20
- with pytest.raises(ValidationError):
21
- mq.validate_model()
22
-
23
- # EOF
@@ -1,124 +0,0 @@
1
- '''test module for ref lifecycle'''
2
- from typing import Optional, Annotated
3
- from pydantic import Field
4
- import pytest
5
- from rune.runtime.metadata import Reference, KeyType
6
- from rune.runtime.base_data_class import BaseDataClass
7
-
8
-
9
- class B(BaseDataClass):
10
- '''no doc'''
11
- fieldB: str = Field(..., description='')
12
-
13
-
14
- class A(BaseDataClass):
15
- '''no doc'''
16
- b: Annotated[B, B.serializer(),
17
- B.validator(('@key:scoped', ))] = Field(..., description='')
18
-
19
-
20
- class Root(BaseDataClass):
21
- '''no doc'''
22
- typeA: Optional[Annotated[A, A.serializer(),
23
- A.validator()]] = Field(None, description='')
24
- bAddress: Optional[Annotated[B,
25
- B.serializer(),
26
- B.validator(('@ref:scoped', ))]] = Field(
27
- None, description='')
28
- _KEY_REF_CONSTRAINTS = {
29
- 'bAddress': {'@ref:scoped'}
30
- }
31
-
32
- class DeepRef(BaseDataClass):
33
- '''no doc'''
34
- root: Annotated[Root, Root.serializer(),
35
- Root.validator()] = Field(..., description='')
36
-
37
-
38
- def test_ref_creation():
39
- '''no doc'''
40
- b = B(fieldB='some b content')
41
- a = A(b=b)
42
- root = Root(typeA=a, bAddress=Reference(a.b, 'aKey', KeyType.SCOPED))
43
- # pylint: disable=no-member
44
- assert id(root.typeA.b) == id(root.bAddress)
45
-
46
-
47
- def test_deep_ref_creation():
48
- '''no doc'''
49
- b = B(fieldB='some b content')
50
- a = A(b=b)
51
- root = Root(typeA=a, bAddress=Reference(a.b, 'aKey2', KeyType.SCOPED))
52
- deep_ref = DeepRef(root=root)
53
- # pylint: disable=no-member
54
- assert id(deep_ref.root.typeA.b) == id(deep_ref.root.bAddress)
55
-
56
-
57
- def test_fail_wrong_key_ext():
58
- '''no doc'''
59
- b = B(fieldB='some b content')
60
- a = A(b=b)
61
- with pytest.raises(ValueError):
62
- Root(typeA=a, bAddress=Reference(a.b, 'aKey', KeyType.EXTERNAL))
63
-
64
-
65
- def test_fail_wrong_key_int():
66
- '''no doc'''
67
- b = B(fieldB='some b content')
68
- a = A(b=b)
69
- with pytest.raises(ValueError):
70
- Root(typeA=a, bAddress=Reference(a.b))
71
-
72
-
73
- def test_scoped_reference_metadata_and_type():
74
- '''scoped refs should store key type and metadata'''
75
- b = B(fieldB='some b content')
76
- a = A(b=b)
77
- root = Root(typeA=a, bAddress=Reference(a.b, 'aKeyScoped', KeyType.SCOPED))
78
- assert b.get_meta('@key:scoped') == 'aKeyScoped'
79
- assert root.__dict__['__rune_references']['bAddress'][1] == KeyType.SCOPED
80
-
81
-
82
- def test_ref_prefers_scoped_over_internal():
83
- '''scoped ref should be preferred when both tags are provided'''
84
- rune_dict = {
85
- "bAddress": {
86
- "@ref": "internalKey",
87
- "@ref:scoped": "scopedKey"
88
- },
89
- "typeA": {
90
- "b": {
91
- "@key:scoped": "scopedKey",
92
- "fieldB": "some b content"
93
- }
94
- },
95
- }
96
- root = Root.model_validate(rune_dict)
97
- assert root.bAddress == root.typeA.b
98
- assert root.__dict__['__rune_references']['bAddress'][1] == KeyType.SCOPED
99
-
100
-
101
- def test_invalid_multiple_ref_tags_raise():
102
- '''unknown multiple ref tags should raise'''
103
- rune_dict = {
104
- "bAddress": {
105
- "@ref:foo": "key1",
106
- "@ref:bar": "key2"
107
- }
108
- }
109
- with pytest.raises(ValueError):
110
- Root.model_validate(rune_dict)
111
-
112
-
113
- def test_scoped_key_not_visible_outside_scope(mocker):
114
- '''scoped keys should not leak across scope instances'''
115
- mocker.patch('rune.runtime.metadata.BaseMetaDataMixin._DEFAULT_SCOPE_TYPE',
116
- 'test_deep_keys_and_references.Root')
117
- b = B(fieldB='some b content')
118
- a = A(b=b)
119
- Root(typeA=a, bAddress=Reference(a.b, 'aKeyScoped', KeyType.SCOPED))
120
- root2 = Root.model_validate({"bAddress": {"@ref:scoped": "aKeyScoped"}})
121
- with pytest.raises(KeyError):
122
- root2.resolve_references(ignore_dangling=False, recurse=False)
123
-
124
- # EOF
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes