|
| 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. |
0 commit comments