yaml-reference 2.6.2__tar.gz → 2.7.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 (25) hide show
  1. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/PKG-INFO +64 -11
  2. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/README.md +63 -10
  3. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/tests/unit/test_flatten.py +15 -0
  4. yaml_reference-2.7.0/tests/unit/test_ignore.py +184 -0
  5. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/tests/unit/test_merge.py +15 -0
  6. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/tests/unit/test_reference.py +51 -0
  7. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/yaml_reference/__init__.py +100 -1
  8. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/.github/copilot-instructions.md +0 -0
  9. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/.github/workflows/pytests-pr.yml +0 -0
  10. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/.github/workflows/release.yml +0 -0
  11. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/.github/workflows/spectests-pr.yml +0 -0
  12. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/.gitignore +0 -0
  13. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/.pre-commit-config.yaml +0 -0
  14. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/.python-version +0 -0
  15. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/.vscode/settings.json +0 -0
  16. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/.zed/settings.json +0 -0
  17. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/GitVersion.yml +0 -0
  18. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/LICENSE +0 -0
  19. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/Makefile +0 -0
  20. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/pyproject.toml +0 -0
  21. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/scripts/spec-test.sh +0 -0
  22. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/scripts/update-readme-badge.sh +0 -0
  23. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/tests/unit/conftest.py +0 -0
  24. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/uv.lock +0 -0
  25. {yaml_reference-2.6.2 → yaml_reference-2.7.0}/yaml_reference/cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yaml-reference
3
- Version: 2.6.2
3
+ Version: 2.7.0
4
4
  Summary: Extension package built on top of `ruamel.yaml` to support cross-file references in YAML files using tags `!reference` and `!reference-all`.
5
5
  Project-URL: Repository, https://github.com/dsillman2000/yaml-reference.git
6
6
  Author-email: David Sillman <dsillman2000@gmail.com>
@@ -26,7 +26,7 @@ Description-Content-Type: text/markdown
26
26
 
27
27
  # yaml-reference
28
28
 
29
- Using `ruamel.yaml`, support cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, and `!merge`.
29
+ Using `ruamel.yaml`, yaml-reference supports cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore`.
30
30
 
31
31
  Install the package from PyPI with:
32
32
 
@@ -40,9 +40,9 @@ uv add yaml-reference
40
40
  ```
41
41
 
42
42
  ## Spec
43
- ![Spec Status](https://img.shields.io/badge/spec%20v0.2.6--4-passing-brightgreen?link=https%3A%2F%2Fgithub.com%2Fdsillman2000%2Fyaml-reference-specs%2Ftree%2Fv0.2.6-4)
43
+ ![Spec Status](https://img.shields.io/badge/spec%20v0.2.8--1-passing-brightgreen?link=https%3A%2F%2Fgithub.com%2Fdsillman2000%2Fyaml-reference-specs%2Ftree%2Fv0.2.8-1)
44
44
 
45
- This Python library implements the YAML specification for cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, and `!merge` as defined in the [yaml-reference-specs project](https://github.com/dsillman2000/yaml-reference-specs).
45
+ This Python library implements the YAML specification for cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore` as defined in the [yaml-reference-specs project](https://github.com/dsillman2000/yaml-reference-specs).
46
46
 
47
47
  ## Example
48
48
 
@@ -50,15 +50,13 @@ This Python library implements the YAML specification for cross-file references
50
50
  # root.yaml
51
51
  version: "3.1"
52
52
  services:
53
- - !reference
54
- path: "services/website.yaml"
53
+ - !reference "services/website.yaml"
55
54
 
56
55
  - !reference
57
56
  path: "services/database.yaml"
58
57
 
59
58
  networkConfigs:
60
- !reference-all
61
- glob: "networks/*.yaml"
59
+ !reference-all "networks/*.yaml"
62
60
 
63
61
  tags: !flatten
64
62
  - !reference { path: "common/tags.yaml" }
@@ -69,6 +67,14 @@ config: !merge
69
67
  - !reference { path: "config/defaults.yaml" }
70
68
  - !reference { path: "config/overrides.yaml" }
71
69
 
70
+ .anchors: !ignore
71
+ commonTags: &commonTags
72
+ - common:http
73
+ - common:security
74
+ dbDefaults: &dbDefaults
75
+ host: localhost
76
+ port: 5432
77
+
72
78
  ```
73
79
 
74
80
  Supposing there are `services/website.yaml` and `services/database.yaml` files in the same directory as `root.yaml`, and a `networks` directory with YAML files, the above will be expanded to account for the referenced files with the following Python code:
@@ -99,6 +105,48 @@ print(data["networkConfigs"])
99
105
  data = parse_yaml_with_references("root.yaml", allow_paths=["/allowed/path"])
100
106
  ```
101
107
 
108
+ For `!reference` and `!reference-all`, both mapping and scalar shorthand forms are supported. These are equivalent:
109
+
110
+ ```yaml
111
+ # Scalar shorthand
112
+ service: !reference "services/api.yaml"
113
+
114
+ # Mapping form
115
+ service: !reference { path: "services/api.yaml" }
116
+
117
+ # Scalar shorthand
118
+ networks: !reference-all "networks/*.yaml"
119
+
120
+ # Mapping form
121
+ networks: !reference-all { glob: "networks/*.yaml" }
122
+ ```
123
+
124
+ Use the mapping form when you need optional arguments such as `anchor`; use the scalar shorthand when you only need `path` or `glob`.
125
+
126
+ ### The `!ignore` Tag
127
+
128
+ The `!ignore` tag marks YAML content that should be parsed but omitted from the final resolved output. The most common use case is a hidden section of reusable anchors that should remain available for aliases elsewhere in the document without being emitted in the resolved result.
129
+
130
+ ```yaml
131
+ .anchors: !ignore
132
+ commonLabels: &commonLabels
133
+ app: payments
134
+ team: platform
135
+ defaultResources: &defaultResources
136
+ requests:
137
+ cpu: "100m"
138
+ memory: "128Mi"
139
+
140
+ service:
141
+ metadata:
142
+ labels: *commonLabels
143
+ resources: *defaultResources
144
+ ```
145
+
146
+ When loaded with `load_yaml_with_references`, the `.anchors` key is removed entirely, but the anchors it defined remain usable by aliases elsewhere in the document.
147
+
148
+ Ignored items are also pruned before `!flatten` and `!merge` are evaluated, so an ignored sequence entry inside either tag is simply omitted from the flattened or merged result.
149
+
102
150
  ### The `!merge` Tag
103
151
 
104
152
  The `!merge` tag combines multiple YAML mappings (dictionaries) into a single mapping. This is useful for composing configuration from multiple sources or applying overrides. When you use `!merge`, you provide a sequence of mappings that will be merged together, with later mappings overriding keys from earlier ones.
@@ -177,20 +225,25 @@ Note that the `app_name` and `cache_settings` fields from `config.yaml` are not
177
225
 
178
226
  ### VSCode squigglies
179
227
 
180
- To get rid of red squigglies in VSCode when using the `!reference`, `!reference-all`, `!flatten`, and `!merge` tags, you can add the following to your `settings.json` file:
228
+ To get rid of red squigglies in VSCode when using the `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore` tags, you can add the following to your `settings.json` file:
181
229
 
182
230
  ```json
183
231
  "yaml.customTags": [
232
+ "!reference scalar",
184
233
  "!reference mapping",
234
+ "!reference-all scalar",
185
235
  "!reference-all mapping",
186
236
  "!flatten sequence",
187
- "!merge sequence"
237
+ "!merge sequence",
238
+ "!ignore scalar",
239
+ "!ignore sequence",
240
+ "!ignore mapping"
188
241
  ]
189
242
  ```
190
243
 
191
244
  ## CLI interface
192
245
 
193
- There is a CLI interface for this package which can be used to read a YAML file which contains `!reference` tags and dump its contents as pretty-printed JSON with references expanded. This is useful for generating a single file for deployment or other purposes. Note that the keys of mappings will be sorted alphabetically. This CLI interface is used to test the contract of this package against the `yaml-reference-specs` project.
246
+ There is a CLI interface for this package which can be used to read a YAML file which contains composition tags such as `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore`, and dump its contents as pretty-printed JSON with references expanded and ignored content removed. This is useful for generating a single file for deployment or other purposes. Note that the keys of mappings will be sorted alphabetically. This CLI interface is used to test the contract of this package against the `yaml-reference-specs` project.
194
247
 
195
248
  ```bash
196
249
  $ yaml-reference-cli -h
@@ -1,6 +1,6 @@
1
1
  # yaml-reference
2
2
 
3
- Using `ruamel.yaml`, support cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, and `!merge`.
3
+ Using `ruamel.yaml`, yaml-reference supports cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore`.
4
4
 
5
5
  Install the package from PyPI with:
6
6
 
@@ -14,9 +14,9 @@ uv add yaml-reference
14
14
  ```
15
15
 
16
16
  ## Spec
17
- ![Spec Status](https://img.shields.io/badge/spec%20v0.2.6--4-passing-brightgreen?link=https%3A%2F%2Fgithub.com%2Fdsillman2000%2Fyaml-reference-specs%2Ftree%2Fv0.2.6-4)
17
+ ![Spec Status](https://img.shields.io/badge/spec%20v0.2.8--1-passing-brightgreen?link=https%3A%2F%2Fgithub.com%2Fdsillman2000%2Fyaml-reference-specs%2Ftree%2Fv0.2.8-1)
18
18
 
19
- This Python library implements the YAML specification for cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, and `!merge` as defined in the [yaml-reference-specs project](https://github.com/dsillman2000/yaml-reference-specs).
19
+ This Python library implements the YAML specification for cross-file references and YAML composition in YAML files using tags `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore` as defined in the [yaml-reference-specs project](https://github.com/dsillman2000/yaml-reference-specs).
20
20
 
21
21
  ## Example
22
22
 
@@ -24,15 +24,13 @@ This Python library implements the YAML specification for cross-file references
24
24
  # root.yaml
25
25
  version: "3.1"
26
26
  services:
27
- - !reference
28
- path: "services/website.yaml"
27
+ - !reference "services/website.yaml"
29
28
 
30
29
  - !reference
31
30
  path: "services/database.yaml"
32
31
 
33
32
  networkConfigs:
34
- !reference-all
35
- glob: "networks/*.yaml"
33
+ !reference-all "networks/*.yaml"
36
34
 
37
35
  tags: !flatten
38
36
  - !reference { path: "common/tags.yaml" }
@@ -43,6 +41,14 @@ config: !merge
43
41
  - !reference { path: "config/defaults.yaml" }
44
42
  - !reference { path: "config/overrides.yaml" }
45
43
 
44
+ .anchors: !ignore
45
+ commonTags: &commonTags
46
+ - common:http
47
+ - common:security
48
+ dbDefaults: &dbDefaults
49
+ host: localhost
50
+ port: 5432
51
+
46
52
  ```
47
53
 
48
54
  Supposing there are `services/website.yaml` and `services/database.yaml` files in the same directory as `root.yaml`, and a `networks` directory with YAML files, the above will be expanded to account for the referenced files with the following Python code:
@@ -73,6 +79,48 @@ print(data["networkConfigs"])
73
79
  data = parse_yaml_with_references("root.yaml", allow_paths=["/allowed/path"])
74
80
  ```
75
81
 
82
+ For `!reference` and `!reference-all`, both mapping and scalar shorthand forms are supported. These are equivalent:
83
+
84
+ ```yaml
85
+ # Scalar shorthand
86
+ service: !reference "services/api.yaml"
87
+
88
+ # Mapping form
89
+ service: !reference { path: "services/api.yaml" }
90
+
91
+ # Scalar shorthand
92
+ networks: !reference-all "networks/*.yaml"
93
+
94
+ # Mapping form
95
+ networks: !reference-all { glob: "networks/*.yaml" }
96
+ ```
97
+
98
+ Use the mapping form when you need optional arguments such as `anchor`; use the scalar shorthand when you only need `path` or `glob`.
99
+
100
+ ### The `!ignore` Tag
101
+
102
+ The `!ignore` tag marks YAML content that should be parsed but omitted from the final resolved output. The most common use case is a hidden section of reusable anchors that should remain available for aliases elsewhere in the document without being emitted in the resolved result.
103
+
104
+ ```yaml
105
+ .anchors: !ignore
106
+ commonLabels: &commonLabels
107
+ app: payments
108
+ team: platform
109
+ defaultResources: &defaultResources
110
+ requests:
111
+ cpu: "100m"
112
+ memory: "128Mi"
113
+
114
+ service:
115
+ metadata:
116
+ labels: *commonLabels
117
+ resources: *defaultResources
118
+ ```
119
+
120
+ When loaded with `load_yaml_with_references`, the `.anchors` key is removed entirely, but the anchors it defined remain usable by aliases elsewhere in the document.
121
+
122
+ Ignored items are also pruned before `!flatten` and `!merge` are evaluated, so an ignored sequence entry inside either tag is simply omitted from the flattened or merged result.
123
+
76
124
  ### The `!merge` Tag
77
125
 
78
126
  The `!merge` tag combines multiple YAML mappings (dictionaries) into a single mapping. This is useful for composing configuration from multiple sources or applying overrides. When you use `!merge`, you provide a sequence of mappings that will be merged together, with later mappings overriding keys from earlier ones.
@@ -151,20 +199,25 @@ Note that the `app_name` and `cache_settings` fields from `config.yaml` are not
151
199
 
152
200
  ### VSCode squigglies
153
201
 
154
- To get rid of red squigglies in VSCode when using the `!reference`, `!reference-all`, `!flatten`, and `!merge` tags, you can add the following to your `settings.json` file:
202
+ To get rid of red squigglies in VSCode when using the `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore` tags, you can add the following to your `settings.json` file:
155
203
 
156
204
  ```json
157
205
  "yaml.customTags": [
206
+ "!reference scalar",
158
207
  "!reference mapping",
208
+ "!reference-all scalar",
159
209
  "!reference-all mapping",
160
210
  "!flatten sequence",
161
- "!merge sequence"
211
+ "!merge sequence",
212
+ "!ignore scalar",
213
+ "!ignore sequence",
214
+ "!ignore mapping"
162
215
  ]
163
216
  ```
164
217
 
165
218
  ## CLI interface
166
219
 
167
- There is a CLI interface for this package which can be used to read a YAML file which contains `!reference` tags and dump its contents as pretty-printed JSON with references expanded. This is useful for generating a single file for deployment or other purposes. Note that the keys of mappings will be sorted alphabetically. This CLI interface is used to test the contract of this package against the `yaml-reference-specs` project.
220
+ There is a CLI interface for this package which can be used to read a YAML file which contains composition tags such as `!reference`, `!reference-all`, `!flatten`, `!merge`, and `!ignore`, and dump its contents as pretty-printed JSON with references expanded and ignored content removed. This is useful for generating a single file for deployment or other purposes. Note that the keys of mappings will be sorted alphabetically. This CLI interface is used to test the contract of this package against the `yaml-reference-specs` project.
168
221
 
169
222
  ```bash
170
223
  $ yaml-reference-cli -h
@@ -197,6 +197,21 @@ def test_flatten_with_scalars(stage_files):
197
197
  assert data["data"] == [1, 2, 3, 4, 5, 6, 7]
198
198
 
199
199
 
200
+ def test_flatten_ignores_ignored_sequence_items(stage_files):
201
+ """Test that !ignore items inside a !flatten sequence are omitted from the flattened result."""
202
+ files = {
203
+ "test.yml": """
204
+ data: !flatten
205
+ - [1, 2]
206
+ - !ignore [99, 100]
207
+ - [3, 4]
208
+ """,
209
+ }
210
+ stg = stage_files(files)
211
+ data = load_yaml_with_references(stg / "test.yml")
212
+ assert data["data"] == [1, 2, 3, 4]
213
+
214
+
200
215
  def test_flatten_mixed_objects_references(stage_files):
201
216
  """Test flattening a sequence of objects, references, and reference-all tags."""
202
217
  files = {
@@ -0,0 +1,184 @@
1
+ from yaml_reference import (
2
+ Ignore,
3
+ load_yaml_with_references,
4
+ parse_yaml_with_references,
5
+ prune_ignores,
6
+ )
7
+
8
+
9
+ def test_ignore_parse_produces_ignore_object(stage_files):
10
+ """Test that !ignore tags are parsed into Ignore objects by parse_yaml_with_references."""
11
+ files = {
12
+ "test.yml": "key: !ignore some_value",
13
+ }
14
+ stg = stage_files(files)
15
+ data = parse_yaml_with_references(stg / "test.yml")
16
+ assert isinstance(data["key"], Ignore)
17
+
18
+
19
+ def test_ignore_dict_value_removed(stage_files):
20
+ """Test that a dict value tagged with !ignore is removed from the output."""
21
+ files = {
22
+ "test.yml": """\
23
+ keep: hello
24
+ drop: !ignore world
25
+ """,
26
+ }
27
+ stg = stage_files(files)
28
+ data = load_yaml_with_references(stg / "test.yml")
29
+ assert "keep" in data
30
+ assert data["keep"] == "hello"
31
+ assert "drop" not in data
32
+
33
+
34
+ def test_ignore_list_item_removed(stage_files):
35
+ """Test that a list item tagged with !ignore is removed from the output."""
36
+ files = {
37
+ "test.yml": """\
38
+ items:
39
+ - one
40
+ - !ignore two
41
+ - three
42
+ """,
43
+ }
44
+ stg = stage_files(files)
45
+ data = load_yaml_with_references(stg / "test.yml")
46
+ assert data["items"] == ["one", "three"]
47
+
48
+
49
+ def test_ignore_standalone_value_becomes_none(stage_files):
50
+ """Test that a standalone (non-list, non-dict) !ignore value is replaced with None."""
51
+ files = {
52
+ "test.yml": "!ignore standalone",
53
+ }
54
+ stg = stage_files(files)
55
+ data = load_yaml_with_references(stg / "test.yml")
56
+ assert data is None
57
+
58
+
59
+ def test_ignore_multiple_items_in_list(stage_files):
60
+ """Test that multiple !ignore items in a list are all removed."""
61
+ files = {
62
+ "test.yml": """\
63
+ items:
64
+ - !ignore a
65
+ - b
66
+ - !ignore c
67
+ - d
68
+ """,
69
+ }
70
+ stg = stage_files(files)
71
+ data = load_yaml_with_references(stg / "test.yml")
72
+ assert data["items"] == ["b", "d"]
73
+
74
+
75
+ def test_ignore_multiple_keys_in_dict(stage_files):
76
+ """Test that multiple !ignore values in a dict are all removed."""
77
+ files = {
78
+ "test.yml": """\
79
+ a: !ignore 1
80
+ b: 2
81
+ c: !ignore 3
82
+ d: 4
83
+ """,
84
+ }
85
+ stg = stage_files(files)
86
+ data = load_yaml_with_references(stg / "test.yml")
87
+ assert data == {"b": 2, "d": 4}
88
+
89
+
90
+ def test_ignore_mapping_value(stage_files):
91
+ """Test that a mapping tagged with !ignore is removed."""
92
+ files = {
93
+ "test.yml": """\
94
+ keep:
95
+ x: 1
96
+ drop: !ignore
97
+ y: 2
98
+ z: 3
99
+ """,
100
+ }
101
+ stg = stage_files(files)
102
+ data = load_yaml_with_references(stg / "test.yml")
103
+ assert "keep" in data
104
+ assert "drop" not in data
105
+
106
+
107
+ def test_ignore_sequence_value(stage_files):
108
+ """Test that a sequence tagged with !ignore is removed when used as a dict value."""
109
+ files = {
110
+ "test.yml": """\
111
+ keep: [1, 2]
112
+ drop: !ignore [3, 4]
113
+ """,
114
+ }
115
+ stg = stage_files(files)
116
+ data = load_yaml_with_references(stg / "test.yml")
117
+ assert data == {"keep": [1, 2]}
118
+
119
+
120
+ def test_ignore_does_not_affect_non_tagged_content(stage_files):
121
+ """Test that !ignore only affects tagged content and leaves everything else intact."""
122
+ files = {
123
+ "test.yml": """\
124
+ name: yaml-reference
125
+ version: 1.0
126
+ description: !ignore internal note
127
+ tags:
128
+ - alpha
129
+ - !ignore beta
130
+ - gamma
131
+ """,
132
+ }
133
+ stg = stage_files(files)
134
+ data = load_yaml_with_references(stg / "test.yml")
135
+ assert data["name"] == "yaml-reference"
136
+ assert data["version"] == 1.0
137
+ assert "description" not in data
138
+ assert data["tags"] == ["alpha", "gamma"]
139
+
140
+
141
+ def test_ignore_in_referenced_file(stage_files):
142
+ """Test that !ignore tags in a referenced file are pruned during resolution."""
143
+ files = {
144
+ "root.yml": "contents: !reference { path: ./inner.yml }",
145
+ "inner.yml": """\
146
+ public: visible
147
+ private: !ignore hidden
148
+ """,
149
+ }
150
+ stg = stage_files(files)
151
+ data = load_yaml_with_references(stg / "root.yml")
152
+ assert data["contents"] == {"public": "visible"}
153
+ assert "private" not in data["contents"]
154
+
155
+
156
+ def test_prune_ignores_standalone(stage_files):
157
+ """Test prune_ignores() directly on a structure containing Ignore objects."""
158
+ data = {
159
+ "a": Ignore("should be removed"),
160
+ "b": 42,
161
+ "c": [1, Ignore("also removed"), 3],
162
+ }
163
+ result = prune_ignores(data)
164
+ assert "a" not in result
165
+ assert result["b"] == 42
166
+ assert result["c"] == [1, 3]
167
+
168
+
169
+ def test_ignore_preserves_none_values(stage_files):
170
+ """Test that existing null/None values in YAML are preserved (not confused with ignored values)."""
171
+ files = {
172
+ "test.yml": """\
173
+ present: ~
174
+ also_present: null
175
+ dropped: !ignore something
176
+ """,
177
+ }
178
+ stg = stage_files(files)
179
+ data = load_yaml_with_references(stg / "test.yml")
180
+ assert "present" in data
181
+ assert data["present"] is None
182
+ assert "also_present" in data
183
+ assert data["also_present"] is None
184
+ assert "dropped" not in data
@@ -124,6 +124,21 @@ result: !merge
124
124
  assert data["result"] == {"a": 1, "b": 2, "inner": {"x": 2, "y": 1}}
125
125
 
126
126
 
127
+ def test_merge_ignores_ignored_sequence_items(stage_files):
128
+ """Test that !ignore items inside a !merge sequence are omitted before merging."""
129
+ files = {
130
+ "test.yml": """
131
+ result: !merge
132
+ - {a: 1}
133
+ - !ignore {ignored: true}
134
+ - {b: 2}
135
+ """,
136
+ }
137
+ stg = stage_files(files)
138
+ data = load_yaml_with_references(stg / "test.yml")
139
+ assert data["result"] == {"a": 1, "b": 2}
140
+
141
+
127
142
  def test_flatten_and_merge(stage_files):
128
143
  """Test flattening and merging together."""
129
144
 
@@ -46,6 +46,17 @@ def test_reference_load(stage_files):
46
46
  assert data["contents"]["another_inner"]["another_inner2"]["leaf"] == "leaf_value"
47
47
 
48
48
 
49
+ def test_reference_load_shorthand(stage_files):
50
+ files = {
51
+ "test.yml": "hello: world\ncontents: !reference ./inner.yml",
52
+ "inner.yml": "inner: inner_value\ninner_list:\n - inner_list_value_1\n - inner_list_value_2",
53
+ }
54
+ stg = stage_files(files)
55
+ data = load_yaml_with_references(stg / "test.yml")
56
+ assert data["hello"] == "world"
57
+ assert data["contents"]["inner"] == "inner_value"
58
+
59
+
49
60
  def test_reference_all_load(stage_files):
50
61
  files = {
51
62
  "test.yml": "hello: world\ncontents: !reference-all { glob: ./chapters/*.yml }",
@@ -76,6 +87,22 @@ def test_reference_all_load(stage_files):
76
87
  assert {"chapter2_summary": 2} in data["inner"]["open"]
77
88
 
78
89
 
90
+ def test_reference_all_load_shorthand(stage_files):
91
+ files = {
92
+ "test.yml": "hello: world\ncontents: !reference-all ./chapters/*.yml",
93
+ "chapters/chapter1.yml": "chapter_value: 1\n",
94
+ "chapters/chapter2.yml": "chapter_value: 2\n",
95
+ "chapters/chapter3.yml": "chapter_value: 3\n",
96
+ }
97
+ stg = stage_files(files)
98
+ data = load_yaml_with_references(stg / "test.yml")
99
+ assert data["hello"] == "world"
100
+ assert len(data["contents"]) == 3
101
+ assert {"chapter_value": 1} in data["contents"]
102
+ assert {"chapter_value": 2} in data["contents"]
103
+ assert {"chapter_value": 3} in data["contents"]
104
+
105
+
79
106
  def test_parse_references(stage_files):
80
107
  files = {
81
108
  "test.yml": "inner: !reference { path: next/open.yml }\n",
@@ -102,6 +129,30 @@ def test_parse_references(stage_files):
102
129
  assert data["open"].location == str((stg / "next/open.yml").absolute())
103
130
 
104
131
 
132
+ def test_parse_reference_shorthand(stage_files):
133
+ files = {
134
+ "test.yml": "inner: !reference next/open.yml\n",
135
+ }
136
+ stg = stage_files(files)
137
+ data = parse_yaml_with_references(stg / "test.yml")
138
+ assert isinstance(data["inner"], Reference)
139
+ assert data["inner"].path == "next/open.yml"
140
+ assert data["inner"].anchor is None
141
+ assert data["inner"].location == str((stg / "test.yml").absolute())
142
+
143
+
144
+ def test_parse_reference_all_shorthand(stage_files):
145
+ files = {
146
+ "test.yml": "inner: !reference-all next/*.yml\n",
147
+ }
148
+ stg = stage_files(files)
149
+ data = parse_yaml_with_references(stg / "test.yml")
150
+ assert isinstance(data["inner"], ReferenceAll)
151
+ assert data["inner"].glob == "next/*.yml"
152
+ assert data["inner"].anchor is None
153
+ assert data["inner"].location == str((stg / "test.yml").absolute())
154
+
155
+
105
156
  def test_disallow_absolute_path_references(stage_files):
106
157
  """Test that absolute path references are disallowed."""
107
158
  actual_file = Path("/tmp/file.yml")
@@ -44,6 +44,8 @@ class Reference:
44
44
 
45
45
  @classmethod
46
46
  def from_yaml(cls, constructor, node):
47
+ if node.id == "scalar":
48
+ return cls(constructor.construct_scalar(node))
47
49
  mapping = constructor.construct_mapping(node)
48
50
  path = mapping["path"]
49
51
  anchor = mapping.get("anchor")
@@ -86,12 +88,51 @@ class ReferenceAll:
86
88
 
87
89
  @classmethod
88
90
  def from_yaml(cls, constructor, node):
91
+ if node.id == "scalar":
92
+ return cls(constructor.construct_scalar(node))
89
93
  mapping = constructor.construct_mapping(node)
90
94
  glob = mapping["glob"]
91
95
  anchor = mapping.get("anchor")
92
96
  return cls(glob, anchor)
93
97
 
94
98
 
99
+ class Ignore:
100
+ """Represents an ignore marker for YAML content.
101
+
102
+ This class is used as a marker for YAML content that should be ignored and is registered
103
+ with ruamel.yaml to handle the `!ignore` tag. Any content marked with `!ignore` will be
104
+ parsed but effectively discarded in the final output.
105
+
106
+ Args:
107
+ content (Any): The YAML content associated with this tag that should be ignored.
108
+ """
109
+
110
+ content: Any
111
+ yaml_tag = "!ignore"
112
+
113
+ def __init__(self, content: Any):
114
+ self.content = content
115
+
116
+ def __repr__(self):
117
+ return f"Ignore(content={repr(self.content)})"
118
+
119
+ @classmethod
120
+ def from_yaml(cls, constructor, node):
121
+ # Construct the underlying content based on the node type instead of
122
+ # calling construct_object(node), which can recurse back into this
123
+ # method or yield partially constructed placeholders with ruamel.yaml.
124
+ if node.id == "scalar":
125
+ content = constructor.construct_scalar(node)
126
+ elif node.id == "sequence":
127
+ content = constructor.construct_sequence(node)
128
+ elif node.id == "mapping":
129
+ content = constructor.construct_mapping(node)
130
+ else:
131
+ # Fallback for any unexpected node types.
132
+ content = constructor.construct_object(node)
133
+ return cls(content)
134
+
135
+
95
136
  class Flatten:
96
137
  """Represents a flattening operation for nested sequences.
97
138
 
@@ -354,6 +395,7 @@ def parse_yaml_with_references(
354
395
  yaml.register_class(ReferenceAll)
355
396
  yaml.register_class(Flatten)
356
397
  yaml.register_class(Merge)
398
+ yaml.register_class(Ignore)
357
399
 
358
400
  if not anchor:
359
401
  with path.open("r") as f:
@@ -374,6 +416,12 @@ def _recursively_attribute_location_to_references(data: Any, base_path: Path):
374
416
  for item in data.sequence
375
417
  ]
376
418
  )
419
+ if isinstance(data, Ignore):
420
+ return Ignore(
421
+ content=_recursively_attribute_location_to_references(
422
+ data.content, base_path
423
+ )
424
+ )
377
425
  if isinstance(data, Merge):
378
426
  return Merge(
379
427
  sequence=[
@@ -476,6 +524,13 @@ def _recursively_resolve_references(
476
524
  ]
477
525
  )
478
526
 
527
+ if isinstance(data, Ignore):
528
+ return Ignore(
529
+ content=_recursively_resolve_references(
530
+ data.content, allow_paths=allow_paths, visited_paths=visited_paths
531
+ )
532
+ )
533
+
479
534
  if isinstance(data, Merge):
480
535
  return Merge(
481
536
  sequence=[
@@ -596,6 +651,43 @@ def merge_mappings(data: Any) -> Any:
596
651
  return data
597
652
 
598
653
 
654
+ def prune_ignores(data: Any) -> Any:
655
+ """
656
+ Given an object which may contain Ignore(...) objects which was parsed from a YAML document containing !ignore
657
+ tags, return the object with all Ignore(...) objects removed. If an Ignore(...) object is found in a list, it is
658
+ removed from the list. If an Ignore(...) object is found as a value in a dict, the key-value pair is removed from
659
+ the dict. If an Ignore(...) object is found as a value which is not in a list or dict, it is replaced with None.
660
+ """
661
+ if isinstance(data, Ignore):
662
+ return None
663
+ if isinstance(data, Flatten):
664
+ return Flatten(
665
+ sequence=[
666
+ prune_ignores(item)
667
+ for item in data.sequence
668
+ if not isinstance(item, Ignore)
669
+ ]
670
+ )
671
+ if isinstance(data, Merge):
672
+ return Merge(
673
+ sequence=[
674
+ prune_ignores(item)
675
+ for item in data.sequence
676
+ if not isinstance(item, Ignore)
677
+ ]
678
+ )
679
+ if isinstance(data, list):
680
+ return [prune_ignores(item) for item in data if not isinstance(item, Ignore)]
681
+ elif isinstance(data, dict):
682
+ return {
683
+ key: prune_ignores(value)
684
+ for key, value in data.items()
685
+ if not isinstance(value, Ignore)
686
+ }
687
+ else:
688
+ return data
689
+
690
+
599
691
  def load_yaml_with_references(
600
692
  file_path: PathLike, allow_paths: Sequence[PathLike] = []
601
693
  ) -> Any:
@@ -633,7 +725,12 @@ def load_yaml_with_references(
633
725
  allow_paths=allow_paths, # type: ignore
634
726
  visited_paths=visited_paths,
635
727
  )
636
- flattened = flatten_sequences(resolved)
728
+ # Prune ignores after full resolution so that Ignore wrappers introduced by
729
+ # referenced files propagate up to their parent containers, allowing keys and
730
+ # list items whose resolved value is !ignore to be dropped entirely rather
731
+ # than replaced with null.
732
+ pruned = prune_ignores(resolved)
733
+ flattened = flatten_sequences(pruned)
637
734
  merged = merge_mappings(flattened)
638
735
  return merged
639
736
 
@@ -645,4 +742,6 @@ __all__ = [
645
742
  "Flatten",
646
743
  "merge_mappings",
647
744
  "Merge",
745
+ "prune_ignores",
746
+ "Ignore",
648
747
  ]
File without changes
File without changes
File without changes