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.
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/CHANGELOG.md +12 -0
- {python_hcl2-8.1.0/python_hcl2.egg-info → python_hcl2-8.1.2}/PKG-INFO +7 -6
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/README.md +6 -5
- python_hcl2-8.1.2/docs/06_migrating_to_v8.md +205 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/deserializer.py +10 -1
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/hcl2.lark +8 -2
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/reconstructor.py +3 -1
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/literal_rules.py +20 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/strings.py +2 -1
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/tokens.py +3 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/transformer.py +23 -3
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/version.py +3 -3
- {python_hcl2-8.1.0 → python_hcl2-8.1.2/python_hcl2.egg-info}/PKG-INFO +7 -6
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/SOURCES.txt +1 -1
- python_hcl2-8.1.0/.github/workflows/security.yml +0 -23
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.codacy.yml +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.coveragerc +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/CODEOWNERS +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/ISSUE_TEMPLATE/hcl2-parsing-error.md +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/workflows/codeql-analysis.yml +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/workflows/dependencies_check.yml +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/workflows/pr_check.yml +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.github/workflows/publish.yml +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.gitignore +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.pre-commit-config.yaml +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/.yamllint.yml +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/CLAUDE.md +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/LICENSE +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/MANIFEST.in +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/bin/check_deps.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/bin/terraform_test +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/__init__.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/hcl_to_json.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/helpers.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/hq.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/cli/json_to_hcl.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/01_getting_started.md +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/02_querying.md +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/03_advanced_api.md +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/04_hq.md +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/docs/05_hq_examples.md +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/__init__.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/__main__.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/api.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/builder.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/const.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/formatter.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/parser.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/postlexer.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/__init__.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/_base.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/attributes.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/blocks.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/body.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/builtins.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/containers.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/diff.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/expressions.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/for_exprs.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/functions.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/introspect.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/path.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/pipeline.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/predicate.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/resolver.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/query/safe_eval.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/__init__.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/abstract.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/base.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/containers.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/directives.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/expressions.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/for_expressions.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/functions.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/indexing.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/rules/whitespace.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/utils.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/hcl2/walk.py +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/mypy.ini +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/pylintrc +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/pyproject.toml +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/dependency_links.txt +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/entry_points.txt +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/not-zip-safe +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/requires.txt +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/python_hcl2.egg-info/top_level.txt +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/reports/.gitignore +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/requirements.txt +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/setup.cfg +0 -0
- {python_hcl2-8.1.0 → python_hcl2-8.1.2}/test-requirements.txt +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
137
|
-
|
|
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,
|
|
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.
|
|
22
|
-
__version_tuple__ = version_tuple = (8, 1,
|
|
21
|
+
__version__ = version = '8.1.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (8, 1, 2)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|