python-hcl2 8.1.0__tar.gz → 8.1.2__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 (91) hide show
  1. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/CHANGELOG.md +12 -0
  2. {python_hcl2-8.1.0/python_hcl2.egg-info → python_hcl2-8.1.2}/PKG-INFO +7 -6
  3. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/README.md +6 -5
  4. python_hcl2-8.1.2/docs/06_migrating_to_v8.md +205 -0
  5. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/deserializer.py +10 -1
  6. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/hcl2.lark +8 -2
  7. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/reconstructor.py +3 -1
  8. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/literal_rules.py +20 -0
  9. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/strings.py +2 -1
  10. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/tokens.py +3 -0
  11. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/transformer.py +23 -3
  12. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/version.py +3 -3
  13. {python_hcl2-8.1.0 → python_hcl2-8.1.2/python_hcl2.egg-info}/PKG-INFO +7 -6
  14. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/SOURCES.txt +1 -1
  15. python_hcl2-8.1.0/.github/workflows/security.yml +0 -23
  16. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.codacy.yml +0 -0
  17. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.coveragerc +0 -0
  18. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/CODEOWNERS +0 -0
  19. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/ISSUE_TEMPLATE/hcl2-parsing-error.md +0 -0
  20. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/workflows/codeql-analysis.yml +0 -0
  21. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/workflows/dependencies_check.yml +0 -0
  22. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/workflows/pr_check.yml +0 -0
  23. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/workflows/publish.yml +0 -0
  24. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.gitignore +0 -0
  25. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.pre-commit-config.yaml +0 -0
  26. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.yamllint.yml +0 -0
  27. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/CLAUDE.md +0 -0
  28. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/LICENSE +0 -0
  29. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/MANIFEST.in +0 -0
  30. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/bin/check_deps.py +0 -0
  31. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/bin/terraform_test +0 -0
  32. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/__init__.py +0 -0
  33. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/hcl_to_json.py +0 -0
  34. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/helpers.py +0 -0
  35. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/hq.py +0 -0
  36. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/json_to_hcl.py +0 -0
  37. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/01_getting_started.md +0 -0
  38. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/02_querying.md +0 -0
  39. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/03_advanced_api.md +0 -0
  40. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/04_hq.md +0 -0
  41. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/05_hq_examples.md +0 -0
  42. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/__init__.py +0 -0
  43. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/__main__.py +0 -0
  44. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/api.py +0 -0
  45. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/builder.py +0 -0
  46. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/const.py +0 -0
  47. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/formatter.py +0 -0
  48. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/parser.py +0 -0
  49. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/postlexer.py +0 -0
  50. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/__init__.py +0 -0
  51. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/_base.py +0 -0
  52. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/attributes.py +0 -0
  53. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/blocks.py +0 -0
  54. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/body.py +0 -0
  55. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/builtins.py +0 -0
  56. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/containers.py +0 -0
  57. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/diff.py +0 -0
  58. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/expressions.py +0 -0
  59. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/for_exprs.py +0 -0
  60. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/functions.py +0 -0
  61. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/introspect.py +0 -0
  62. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/path.py +0 -0
  63. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/pipeline.py +0 -0
  64. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/predicate.py +0 -0
  65. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/resolver.py +0 -0
  66. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/safe_eval.py +0 -0
  67. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/__init__.py +0 -0
  68. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/abstract.py +0 -0
  69. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/base.py +0 -0
  70. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/containers.py +0 -0
  71. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/directives.py +0 -0
  72. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/expressions.py +0 -0
  73. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/for_expressions.py +0 -0
  74. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/functions.py +0 -0
  75. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/indexing.py +0 -0
  76. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/whitespace.py +0 -0
  77. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/utils.py +0 -0
  78. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/walk.py +0 -0
  79. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/mypy.ini +0 -0
  80. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/pylintrc +0 -0
  81. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/pyproject.toml +0 -0
  82. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/dependency_links.txt +0 -0
  83. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/entry_points.txt +0 -0
  84. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/not-zip-safe +0 -0
  85. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/requires.txt +0 -0
  86. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/top_level.txt +0 -0
  87. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/reports/.gitignore +0 -0
  88. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/requirements.txt +0 -0
  89. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/setup.cfg +0 -0
  90. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/test-requirements.txt +0 -0
  91. {python_hcl2-8.1.0 → python_hcl2-8.1.2}/tox.ini +0 -0
@@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
9
9
 
10
10
  - Nothing yet.
11
11
 
12
+ ## \[8.1.2\] - 2026-04-10
13
+
14
+ ### Fixed
15
+
16
+ - `true`, `false`, and `null` now serialize to native JSON types instead of strings. ([#293](https://github.com/amplify-education/python-hcl2/issues/293))
17
+
18
+ ## \[8.1.1\] - 2026-04-07
19
+
20
+ ### Added
21
+
22
+ - v7-to-v8 migration guide and absolute GitHub links in README docs table. ([#287](https://github.com/amplify-education/python-hcl2/pull/287))
23
+
12
24
  ## \[8.1.0\] - 2026-04-07
13
25
 
14
26
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hcl2
3
- Version: 8.1.0
3
+ Version: 8.1.2
4
4
  Summary: A parser for HCL2
5
5
  Author-email: Amplify Education <github@amplify.com>
6
6
  License: MIT
@@ -105,11 +105,12 @@ hcl_string = hcl2.dumps(doc.build())
105
105
 
106
106
  | Guide | Contents |
107
107
  |---|---|
108
- | [Getting Started](docs/01_getting_started.md) | Installation, load/dump, options, CLI converters |
109
- | [Querying HCL (Python)](docs/02_querying.md) | DocumentView, BlockView, tree walking, view hierarchy |
110
- | [Advanced API](docs/03_advanced_api.md) | Pipeline stages, Builder |
111
- | [hq Reference](docs/04_hq.md) | `hq` CLI — structural queries, hybrid/eval, introspection |
112
- | [hq Examples](docs/05_hq_examples.md) | Real-world queries for discovery, compliance, extraction |
108
+ | [Getting Started](https://github.com/amplify-education/python-hcl2/blob/main/docs/01_getting_started.md) | Installation, load/dump, options, CLI converters |
109
+ | [Querying HCL (Python)](https://github.com/amplify-education/python-hcl2/blob/main/docs/02_querying.md) | DocumentView, BlockView, tree walking, view hierarchy |
110
+ | [Advanced API](https://github.com/amplify-education/python-hcl2/blob/main/docs/03_advanced_api.md) | Pipeline stages, Builder |
111
+ | [hq Reference](https://github.com/amplify-education/python-hcl2/blob/main/docs/04_hq.md) | `hq` CLI — structural queries, hybrid/eval, introspection |
112
+ | [hq Examples](https://github.com/amplify-education/python-hcl2/blob/main/docs/05_hq_examples.md) | Real-world queries for discovery, compliance, extraction |
113
+ | [Migrating to v8](https://github.com/amplify-education/python-hcl2/blob/main/docs/06_migrating_to_v8.md) | Breaking changes, updated API patterns, v7-compat options |
113
114
 
114
115
  ### CLI Tools
115
116
 
@@ -78,11 +78,12 @@ hcl_string = hcl2.dumps(doc.build())
78
78
 
79
79
  | Guide | Contents |
80
80
  |---|---|
81
- | [Getting Started](docs/01_getting_started.md) | Installation, load/dump, options, CLI converters |
82
- | [Querying HCL (Python)](docs/02_querying.md) | DocumentView, BlockView, tree walking, view hierarchy |
83
- | [Advanced API](docs/03_advanced_api.md) | Pipeline stages, Builder |
84
- | [hq Reference](docs/04_hq.md) | `hq` CLI — structural queries, hybrid/eval, introspection |
85
- | [hq Examples](docs/05_hq_examples.md) | Real-world queries for discovery, compliance, extraction |
81
+ | [Getting Started](https://github.com/amplify-education/python-hcl2/blob/main/docs/01_getting_started.md) | Installation, load/dump, options, CLI converters |
82
+ | [Querying HCL (Python)](https://github.com/amplify-education/python-hcl2/blob/main/docs/02_querying.md) | DocumentView, BlockView, tree walking, view hierarchy |
83
+ | [Advanced API](https://github.com/amplify-education/python-hcl2/blob/main/docs/03_advanced_api.md) | Pipeline stages, Builder |
84
+ | [hq Reference](https://github.com/amplify-education/python-hcl2/blob/main/docs/04_hq.md) | `hq` CLI — structural queries, hybrid/eval, introspection |
85
+ | [hq Examples](https://github.com/amplify-education/python-hcl2/blob/main/docs/05_hq_examples.md) | Real-world queries for discovery, compliance, extraction |
86
+ | [Migrating to v8](https://github.com/amplify-education/python-hcl2/blob/main/docs/06_migrating_to_v8.md) | Breaking changes, updated API patterns, v7-compat options |
86
87
 
87
88
  ### CLI Tools
88
89
 
@@ -0,0 +1,205 @@
1
+ # Migrating to v8
2
+
3
+ This guide covers breaking changes when upgrading from python-hcl2 v7 to v8. Changes are ordered by likelihood of impact — if you only use `load()`/`loads()` to read HCL files, focus on the first three sections.
4
+
5
+ ## String values now include HCL quotes
6
+
7
+ **Impact: high** — silently changes output without raising errors.
8
+
9
+ In v7, `load()` stripped the surrounding double-quotes from HCL string values. In v8, quotes are preserved by default to enable lossless round-trips.
10
+
11
+ ```python
12
+ # Given: name = "hello"
13
+
14
+ # v7
15
+ data["name"] # 'hello'
16
+
17
+ # v8 (default)
18
+ data["name"] # '"hello"'
19
+ ```
20
+
21
+ To restore v7 behavior:
22
+
23
+ ```python
24
+ import hcl2
25
+ from hcl2 import SerializationOptions
26
+
27
+ data = hcl2.load(f, serialization_options=SerializationOptions(strip_string_quotes=True))
28
+ ```
29
+
30
+ > **Note:** `strip_string_quotes=True` is one-way — dicts produced with it cannot round-trip back to HCL via `dumps()` because the quotes needed to distinguish strings from identifiers are gone.
31
+
32
+ ## New metadata keys in output dicts
33
+
34
+ **Impact: high** — code that iterates keys or does exact-match assertions will break.
35
+
36
+ v8 adds two new key categories to output dicts by default:
37
+
38
+ | Key | Default | Purpose |
39
+ |---|---|---|
40
+ | `__is_block__` | on (`explicit_blocks=True`) | Distinguishes HCL blocks from plain objects |
41
+ | `__comments__`, `__inline_comments__` | on (`with_comments=True`) | Preserves HCL comments |
42
+
43
+ To suppress them:
44
+
45
+ ```python
46
+ opts = SerializationOptions(explicit_blocks=False, with_comments=False)
47
+ data = hcl2.load(f, serialization_options=opts)
48
+ ```
49
+
50
+ > **Note:** `explicit_blocks=False` disables round-trip support via `dumps()` — the deserializer needs `__is_block__` markers to reconstruct blocks correctly.
51
+
52
+ The v7 metadata keys `__start_line__` and `__end_line__` are still available but remain opt-in:
53
+
54
+ ```python
55
+ opts = SerializationOptions(with_meta=True)
56
+ ```
57
+
58
+ ## `load()` / `loads()` signature changed
59
+
60
+ **Impact: high** — calls using `with_meta` will raise `TypeError`.
61
+
62
+ The `with_meta` positional/keyword parameter has been replaced by a `SerializationOptions` object:
63
+
64
+ ```python
65
+ # v7
66
+ data = hcl2.load(f, with_meta=True)
67
+ data = hcl2.loads(text, with_meta=True)
68
+
69
+ # v8
70
+ from hcl2 import SerializationOptions
71
+ data = hcl2.load(f, serialization_options=SerializationOptions(with_meta=True))
72
+ data = hcl2.loads(text, serialization_options=SerializationOptions(with_meta=True))
73
+ ```
74
+
75
+ All parameters on `load()`/`loads()` are now keyword-only.
76
+
77
+ ## `reverse_transform()` and `writes()` removed
78
+
79
+ **Impact: medium** — calls will raise `ImportError` / `AttributeError`.
80
+
81
+ The v7 two-step dict-to-HCL workflow has been replaced by `dump()`/`dumps()`:
82
+
83
+ ```python
84
+ # v7
85
+ ast = hcl2.reverse_transform(data)
86
+ text = hcl2.writes(ast)
87
+
88
+ # v8
89
+ text = hcl2.dumps(data)
90
+
91
+ # or to a file:
92
+ with open("output.tf", "w") as f:
93
+ hcl2.dump(data, f)
94
+ ```
95
+
96
+ `dumps()` accepts optional `deserializer_options` and `formatter_options` for controlling the output:
97
+
98
+ ```python
99
+ from hcl2 import DeserializerOptions, FormatterOptions
100
+
101
+ text = hcl2.dumps(
102
+ data,
103
+ deserializer_options=DeserializerOptions(object_elements_colon=True),
104
+ formatter_options=FormatterOptions(indent_length=4),
105
+ )
106
+ ```
107
+
108
+ ## `parse()` / `parses()` return type changed
109
+
110
+ **Impact: medium** — code accessing Lark tree internals will break.
111
+
112
+ These functions now return a typed `StartRule` (a `LarkElement` node) instead of a raw `lark.Tree`:
113
+
114
+ ```python
115
+ # v7
116
+ tree = hcl2.parses(text) # -> lark.Tree
117
+ tree.data # 'start'
118
+ tree.children # [lark.Tree, ...]
119
+
120
+ # v8
121
+ tree = hcl2.parses(text) # -> StartRule
122
+ tree.body # typed BodyRule accessor
123
+ ```
124
+
125
+ If you need the raw Lark tree, use the new explicit functions:
126
+
127
+ ```python
128
+ lark_tree = hcl2.parses_to_tree(text) # -> lark.Tree (raw)
129
+ rule_tree = hcl2.transform(lark_tree) # -> StartRule (typed)
130
+ ```
131
+
132
+ ## `transform()` signature and return type changed
133
+
134
+ **Impact: medium** — same cause as above.
135
+
136
+ ```python
137
+ # v7
138
+ data = hcl2.transform(ast, with_meta=True) # -> dict
139
+
140
+ # v8
141
+ rule_tree = hcl2.transform(lark_tree, discard_comments=False) # -> StartRule
142
+ data = hcl2.serialize(rule_tree, serialization_options=opts) # -> dict
143
+ ```
144
+
145
+ In v8, `transform()` produces a typed IR tree. To get a dict, follow it with `serialize()`.
146
+
147
+ ## `DictTransformer` and `reconstruction_parser` removed
148
+
149
+ **Impact: low** — only affects code importing internals.
150
+
151
+ | v7 import | v8 replacement |
152
+ |---|---|
153
+ | `from hcl2.transformer import DictTransformer` | Use `hcl2.transform()` + `hcl2.serialize()` |
154
+ | `from hcl2.parser import reconstruction_parser` | Use `hcl2.parser.parser()` (single parser) |
155
+ | `from hcl2.reconstructor import HCLReverseTransformer` | Use `hcl2.from_dict()` + `hcl2.reconstruct()` |
156
+
157
+ ## New pipeline stages
158
+
159
+ v8 exposes the full bidirectional pipeline as composable functions:
160
+
161
+ ```
162
+ Forward: HCL text -> parses_to_tree() -> transform() -> serialize() -> dict
163
+ Reverse: dict -> from_dict() -> reconstruct() -> HCL text
164
+ ```
165
+
166
+ | Function | Input | Output |
167
+ |---|---|---|
168
+ | `parses_to_tree(text)` | HCL string | raw `lark.Tree` |
169
+ | `transform(lark_tree)` | `lark.Tree` | `StartRule` |
170
+ | `serialize(tree)` | `StartRule` | `dict` |
171
+ | `from_dict(data)` | `dict` | `StartRule` |
172
+ | `from_json(text)` | JSON string | `StartRule` |
173
+ | `reconstruct(tree)` | `StartRule` | HCL string |
174
+
175
+ ## CLI changes
176
+
177
+ The `hcl2tojson` entry point moved from `hcl2.__main__:main` to `cli.hcl_to_json:main`. A shim keeps `python -m hcl2` working, but direct imports from `hcl2.__main__` should be updated.
178
+
179
+ Two new CLI tools ship with v8:
180
+
181
+ - **`jsontohcl2`** — convert JSON back to HCL2, with diff/dry-run support
182
+ - **`hq`** — structural query tool for HCL files (jq-like syntax)
183
+
184
+ ## Python 3.7 no longer supported
185
+
186
+ The minimum Python version is now **3.8**.
187
+
188
+ ## Quick reference: v7-compatible defaults
189
+
190
+ If you want v8 to behave as closely to v7 as possible:
191
+
192
+ ```python
193
+ import hcl2
194
+ from hcl2 import SerializationOptions
195
+
196
+ V7_COMPAT = SerializationOptions(
197
+ strip_string_quotes=True,
198
+ explicit_blocks=False,
199
+ with_comments=False,
200
+ )
201
+
202
+ data = hcl2.load(f, serialization_options=V7_COMPAT)
203
+ ```
204
+
205
+ This restores the v7 dict shape but disables round-trip support and comment preservation.
@@ -30,6 +30,7 @@ from hcl2.rules.literal_rules import (
30
30
  IdentifierRule,
31
31
  IntLitRule,
32
32
  FloatLitRule,
33
+ LiteralValueRule,
33
34
  )
34
35
  from hcl2.rules.strings import (
35
36
  StringRule,
@@ -55,6 +56,9 @@ from hcl2.rules.tokens import (
55
56
  HEREDOC_TRIM_TEMPLATE,
56
57
  HEREDOC_TEMPLATE,
57
58
  COLON,
59
+ TRUE,
60
+ FALSE,
61
+ NULL,
58
62
  )
59
63
  from hcl2.transformer import RuleTransformer
60
64
  from hcl2.utils import HEREDOC_TRIM_PATTERN, HEREDOC_PATTERN
@@ -152,7 +156,12 @@ class BaseDeserializer(LarkElementTreeDeserializer):
152
156
  def _deserialize_text(self, value: Any) -> LarkRule:
153
157
  # bool must be checked before int since bool is a subclass of int
154
158
  if isinstance(value, bool):
155
- return self._deserialize_identifier(str(value).lower())
159
+ if value:
160
+ return LiteralValueRule([TRUE()])
161
+ return LiteralValueRule([FALSE()])
162
+
163
+ if value is None:
164
+ return LiteralValueRule([NULL()])
156
165
 
157
166
  if isinstance(value, float):
158
167
  return FloatLitRule([FloatLiteral(value)])
@@ -14,6 +14,10 @@ ELSE : "else"
14
14
  ENDIF : "endif"
15
15
  ENDFOR : "endfor"
16
16
 
17
+ // Literal value keywords
18
+ NULL : "null"
19
+ TRUE : "true"
20
+ FALSE : "false"
17
21
 
18
22
  // Literals
19
23
  NAME : /[a-zA-Z_][a-zA-Z0-9_-]*/
@@ -94,7 +98,7 @@ start : body
94
98
  // Body and basic constructs
95
99
  body : (new_line_or_comment? (attribute | block))* new_line_or_comment?
96
100
  attribute : _attribute_name EQ expression
97
- _attribute_name : identifier | keyword
101
+ _attribute_name : identifier | keyword | literal_value
98
102
  block : identifier (identifier | string)* new_line_or_comment? LBRACE body RBRACE
99
103
 
100
104
  // Whitespace and comments
@@ -103,6 +107,7 @@ new_line_or_comment: ( NL_OR_COMMENT )+
103
107
  // Basic literals and identifiers
104
108
  identifier : NAME
105
109
  keyword: IN | FOR | IF | FOR_EACH | ELSE | ENDIF | ENDFOR
110
+ literal_value: TRUE | FALSE | NULL
106
111
  int_lit: INT_LITERAL
107
112
  float_lit: FLOAT_LITERAL
108
113
  string: DBLQUOTE string_part* DBLQUOTE
@@ -189,6 +194,7 @@ expr_term : LPAR new_line_or_comment? expression new_line_or_comment? RPAR
189
194
  | tuple
190
195
  | object
191
196
  | identifier
197
+ | literal_value
192
198
  | function_call
193
199
  | heredoc_template
194
200
  | heredoc_template_trim
@@ -223,7 +229,7 @@ full_splat_expr_term : expr_term full_splat
223
229
  ?index : braces_index | short_index
224
230
  braces_index : LSQB new_line_or_comment? expression new_line_or_comment? RSQB
225
231
  short_index : DOT INT_LITERAL
226
- get_attr : DOT identifier
232
+ get_attr : DOT (identifier | literal_value)
227
233
  attr_splat : ATTR_SPLAT (get_attr | index)*
228
234
  full_splat : FULL_SPLAT_START (get_attr | index)*
229
235
 
@@ -16,7 +16,7 @@ from hcl2.rules.directives import (
16
16
  TemplateEndforRule,
17
17
  )
18
18
  from hcl2.rules.for_expressions import ForIntroRule, ForTupleExprRule, ForObjectExprRule
19
- from hcl2.rules.literal_rules import IdentifierRule
19
+ from hcl2.rules.literal_rules import IdentifierRule, LiteralValueRule
20
20
  from hcl2.rules.strings import StringRule
21
21
  from hcl2.rules.expressions import (
22
22
  ExprTermRule,
@@ -228,9 +228,11 @@ class HCLReconstructor:
228
228
  if rule_name in [
229
229
  StringRule.lark_name(),
230
230
  IdentifierRule.lark_name(),
231
+ LiteralValueRule.lark_name(),
231
232
  ] and self._last_rule_name in [
232
233
  StringRule.lark_name(),
233
234
  IdentifierRule.lark_name(),
235
+ LiteralValueRule.lark_name(),
234
236
  ]:
235
237
  return True
236
238
 
@@ -33,6 +33,26 @@ class KeywordRule(TokenRule):
33
33
  return "keyword"
34
34
 
35
35
 
36
+ class LiteralValueRule(TokenRule):
37
+ """Rule for HCL2 literal value keywords (true, false, null)."""
38
+
39
+ _SERIALIZE_MAP = {"true": True, "false": False, "null": None}
40
+
41
+ @staticmethod
42
+ def lark_name() -> str:
43
+ """Return the grammar rule name."""
44
+ return "literal_value"
45
+
46
+ def serialize(
47
+ self, options=SerializationOptions(), context=SerializationContext()
48
+ ) -> Any:
49
+ """Serialize to Python True, False, or None."""
50
+ value = self.token.value
51
+ if context.inside_dollar_string:
52
+ return str(value)
53
+ return self._SERIALIZE_MAP.get(str(value), str(value))
54
+
55
+
36
56
  class IdentifierRule(TokenRule):
37
57
  """Rule for HCL2 identifiers."""
38
58
 
@@ -48,7 +48,8 @@ class InterpolationRule(LarkRule):
48
48
  self, options=SerializationOptions(), context=SerializationContext()
49
49
  ) -> Any:
50
50
  """Serialize to ${expression} string."""
51
- return to_dollar_string(self.expression.serialize(options, context))
51
+ with context.modify(inside_dollar_string=True):
52
+ return to_dollar_string(self.expression.serialize(options, context))
52
53
 
53
54
 
54
55
  class StringPartRule(LarkRule):
@@ -125,6 +125,9 @@ FOR_OBJECT_ARROW = StaticStringToken[("FOR_OBJECT_ARROW", "=>")] # type: ignore
125
125
  ELSE = StaticStringToken[("ELSE", "else")] # type: ignore
126
126
  ENDIF = StaticStringToken[("ENDIF", "endif")] # type: ignore
127
127
  ENDFOR = StaticStringToken[("ENDFOR", "endfor")] # type: ignore
128
+ TRUE = StaticStringToken[("TRUE", "true")] # type: ignore
129
+ FALSE = StaticStringToken[("FALSE", "false")] # type: ignore
130
+ NULL = StaticStringToken[("NULL", "null")] # type: ignore
128
131
 
129
132
  # pylint: enable=invalid-name
130
133
 
@@ -48,6 +48,7 @@ from hcl2.rules.literal_rules import (
48
48
  IdentifierRule,
49
49
  BinaryOperatorRule,
50
50
  KeywordRule,
51
+ LiteralValueRule,
51
52
  )
52
53
  from hcl2.rules.strings import (
53
54
  InterpolationRule,
@@ -133,8 +134,9 @@ class RuleTransformer(Transformer):
133
134
 
134
135
  @v_args(meta=True)
135
136
  def attribute(self, meta: Meta, args) -> AttributeRule:
136
- # _attribute_name is flattened, so args[0] may be KeywordRule or IdentifierRule
137
- if isinstance(args[0], KeywordRule):
137
+ # _attribute_name is flattened, so args[0] may be KeywordRule,
138
+ # LiteralValueRule, or IdentifierRule
139
+ if isinstance(args[0], (KeywordRule, LiteralValueRule)):
138
140
  args[0] = IdentifierRule([NAME(args[0].token.value)], meta)
139
141
  return AttributeRule(args, meta)
140
142
 
@@ -154,6 +156,10 @@ class RuleTransformer(Transformer):
154
156
  def keyword(self, meta: Meta, args) -> KeywordRule:
155
157
  return KeywordRule(args, meta)
156
158
 
159
+ @v_args(meta=True)
160
+ def literal_value(self, meta: Meta, args) -> LiteralValueRule:
161
+ return LiteralValueRule(args, meta)
162
+
157
163
  @v_args(meta=True)
158
164
  def int_lit(self, meta: Meta, args) -> IntLitRule:
159
165
  return IntLitRule(args, meta)
@@ -333,8 +339,18 @@ class RuleTransformer(Transformer):
333
339
  if isinstance(expr, ExprTermRule) and len(expr.children) == 5:
334
340
  inner = expr.children[2] # position 2 in [None, None, inner, None, None]
335
341
  if isinstance(
336
- inner, (IdentifierRule, StringRule, IntLitRule, FloatLitRule)
342
+ inner,
343
+ (
344
+ IdentifierRule,
345
+ StringRule,
346
+ IntLitRule,
347
+ FloatLitRule,
348
+ LiteralValueRule,
349
+ ),
337
350
  ):
351
+ # Convert literal_value to identifier for dict key compatibility
352
+ if isinstance(inner, LiteralValueRule):
353
+ inner = IdentifierRule([NAME(inner.token.value)], meta)
338
354
  return ObjectElemKeyRule([inner], meta)
339
355
  # Any other expression (parenthesized or bare)
340
356
  return ObjectElemKeyExpressionRule([expr], meta)
@@ -361,6 +377,10 @@ class RuleTransformer(Transformer):
361
377
 
362
378
  @v_args(meta=True)
363
379
  def get_attr(self, meta: Meta, args) -> GetAttrRule:
380
+ # Convert literal_value (true/false/null) to identifier in attr access
381
+ if len(args) >= 2 and isinstance(args[1], LiteralValueRule):
382
+ args = list(args)
383
+ args[1] = IdentifierRule([NAME(args[1].token.value)], meta)
364
384
  return GetAttrRule(args, meta)
365
385
 
366
386
  @v_args(meta=True)
@@ -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 = '8.1.0'
22
- __version_tuple__ = version_tuple = (8, 1, 0)
21
+ __version__ = version = '8.1.2'
22
+ __version_tuple__ = version_tuple = (8, 1, 2)
23
23
 
24
- __commit_id__ = commit_id = 'g33590fcbd'
24
+ __commit_id__ = commit_id = 'ga602753ce'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-hcl2
3
- Version: 8.1.0
3
+ Version: 8.1.2
4
4
  Summary: A parser for HCL2
5
5
  Author-email: Amplify Education <github@amplify.com>
6
6
  License: MIT
@@ -105,11 +105,12 @@ hcl_string = hcl2.dumps(doc.build())
105
105
 
106
106
  | Guide | Contents |
107
107
  |---|---|
108
- | [Getting Started](docs/01_getting_started.md) | Installation, load/dump, options, CLI converters |
109
- | [Querying HCL (Python)](docs/02_querying.md) | DocumentView, BlockView, tree walking, view hierarchy |
110
- | [Advanced API](docs/03_advanced_api.md) | Pipeline stages, Builder |
111
- | [hq Reference](docs/04_hq.md) | `hq` CLI — structural queries, hybrid/eval, introspection |
112
- | [hq Examples](docs/05_hq_examples.md) | Real-world queries for discovery, compliance, extraction |
108
+ | [Getting Started](https://github.com/amplify-education/python-hcl2/blob/main/docs/01_getting_started.md) | Installation, load/dump, options, CLI converters |
109
+ | [Querying HCL (Python)](https://github.com/amplify-education/python-hcl2/blob/main/docs/02_querying.md) | DocumentView, BlockView, tree walking, view hierarchy |
110
+ | [Advanced API](https://github.com/amplify-education/python-hcl2/blob/main/docs/03_advanced_api.md) | Pipeline stages, Builder |
111
+ | [hq Reference](https://github.com/amplify-education/python-hcl2/blob/main/docs/04_hq.md) | `hq` CLI — structural queries, hybrid/eval, introspection |
112
+ | [hq Examples](https://github.com/amplify-education/python-hcl2/blob/main/docs/05_hq_examples.md) | Real-world queries for discovery, compliance, extraction |
113
+ | [Migrating to v8](https://github.com/amplify-education/python-hcl2/blob/main/docs/06_migrating_to_v8.md) | Breaking changes, updated API patterns, v7-compat options |
113
114
 
114
115
  ### CLI Tools
115
116
 
@@ -20,7 +20,6 @@ tox.ini
20
20
  .github/workflows/dependencies_check.yml
21
21
  .github/workflows/pr_check.yml
22
22
  .github/workflows/publish.yml
23
- .github/workflows/security.yml
24
23
  bin/check_deps.py
25
24
  bin/terraform_test
26
25
  cli/__init__.py
@@ -33,6 +32,7 @@ docs/02_querying.md
33
32
  docs/03_advanced_api.md
34
33
  docs/04_hq.md
35
34
  docs/05_hq_examples.md
35
+ docs/06_migrating_to_v8.md
36
36
  hcl2/__init__.py
37
37
  hcl2/__main__.py
38
38
  hcl2/api.py
@@ -1,23 +0,0 @@
1
- ---
2
- name: Security Review
3
-
4
- permissions:
5
- pull-requests: write # Needed for leaving PR comments
6
- contents: read
7
-
8
- on:
9
- pull_request:
10
-
11
- jobs:
12
- security:
13
- runs-on: github-hosted-static-ip
14
- steps:
15
- - uses: actions/checkout@v4
16
- with:
17
- ref: ${{ github.event.pull_request.head.sha || github.sha }}
18
- fetch-depth: 2
19
-
20
- - uses: anthropics/claude-code-security-review@main
21
- with:
22
- comment-pr: true
23
- claude-api-key: ${{ secrets.ANTHROPIC_CLAUDE_API_KEY }}
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
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
File without changes
File without changes
File without changes
File without changes