diff --git a/.coveragerc b/.coveragerc index c1323f88..a20640db 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,12 +1,14 @@ [run] branch = true +# tox measures `coverage=hcl2`, so hcl2/cli/* only came into scope when the CLI moved +# under the package in 8.2.0. Omitting it keeps the gate on library code, as before. omit = hcl2/lark_parser.py hcl2/version.py hcl2/__main__.py hcl2/__init__.py hcl2/rules/__init__.py - cli/__init__.py + hcl2/cli/* [report] show_missing = true diff --git a/CHANGELOG.md b/CHANGELOG.md index 69a3666b..b8fda71d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## \[Unreleased\] +### Changed + +- **Breaking for direct `cli.*` imports.** The CLI modules moved from a top-level `cli` package + to `hcl2.cli`, so installing python-hcl2 no longer claims the generic top-level `cli` name. + Since 8.0 the distribution installed `cli/` into `site-packages`, where it collided with + projects that have a top-level `cli` package of their own and made their `cli.*` imports + resolve to the wrong module. The `hcl2tojson`, `jsontohcl2`, and `hq` commands and + `python -m hcl2` are unaffected; only code importing `cli.hcl_to_json`, `cli.json_to_hcl`, + `cli.hq`, or `cli.helpers` needs to add the `hcl2.` prefix. No compatibility shim ships, + because a shim would still occupy the colliding name. +- The redundant `cli/py.typed` marker is gone; `hcl2/py.typed` already covers `hcl2.cli`. + ### Added - Python 3.14 is now tested and declared as supported. No source changes were needed; the full diff --git a/CLAUDE.md b/CLAUDE.md index 7ce75b76..5084697f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,10 +26,10 @@ The **Direct** pipeline (`parse_to_tree` → `transform` → `to_lark` → `reco | `hcl2/walk.py` | Generic tree-walking primitives for the LarkElement IR tree | | `hcl2/utils.py` | `SerializationOptions`, `SerializationContext`, string helpers | | `hcl2/const.py` | Constants: `IS_BLOCK`, `COMMENTS_KEY`, `INLINE_COMMENTS_KEY` | -| `cli/helpers.py` | File/directory/stdin conversion helpers | -| `cli/hcl_to_json.py` | `hcl2tojson` entry point | -| `cli/json_to_hcl.py` | `jsontohcl2` entry point | -| `cli/hq.py` | `hq` CLI entry point — query dispatch, formatting, optional operator | +| `hcl2/cli/helpers.py` | File/directory/stdin conversion helpers | +| `hcl2/cli/hcl_to_json.py` | `hcl2tojson` entry point | +| `hcl2/cli/json_to_hcl.py` | `jsontohcl2` entry point | +| `hcl2/cli/hq.py` | `hq` CLI entry point — query dispatch, formatting, optional operator | | `hcl2/query/__init__.py` | Public query API exports | | `hcl2/query/_base.py` | `NodeView` base class, view registry, `view_for()` factory | | `hcl2/query/body.py` | `DocumentView`, `BodyView` facades for top-level and body queries | @@ -48,7 +48,7 @@ The **Direct** pipeline (`parse_to_tree` → `transform` → `to_lark` → `reco | `hcl2/query/safe_eval.py` | AST-validated Python expression eval for hybrid/eval modes | | `hcl2/query/introspect.py` | `--describe` and `--schema` output generation | -`hcl2/__main__.py` is a thin wrapper that imports `cli.hcl_to_json:main`. +`hcl2/__main__.py` is a thin wrapper that imports `hcl2.cli.hcl_to_json:main`. ### Rules (one class per grammar rule) @@ -179,7 +179,7 @@ python -m unittest discover -s test -p "test_*.py" -v **Unit tests** (`test/unit/`): instantiate rule objects directly (no parsing). - `rules/` — one file per rules module -- `cli/` — one file per CLI module +- `cli/` — one file per module in `hcl2/cli/` - `test_*.py` — tests for corresponding files from `hcl2/` directory Use concrete stubs when testing ABCs (e.g., `StubExpression(ExpressionRule)`). diff --git a/cli/py.typed b/cli/py.typed deleted file mode 100644 index e69de29b..00000000 diff --git a/docs/06_migrating_to_v8.md b/docs/06_migrating_to_v8.md index 0c4703bf..b3ce260a 100644 --- a/docs/06_migrating_to_v8.md +++ b/docs/06_migrating_to_v8.md @@ -178,7 +178,9 @@ Reverse: dict -> from_dict() -> reconstruct() -> HCL text ## CLI changes -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. +The `hcl2tojson` entry point moved from `hcl2.__main__:main` to `hcl2.cli.hcl_to_json:main`. A shim keeps `python -m hcl2` working, but direct imports from `hcl2.__main__` should be updated. + +In 8.0 through 8.1.x these modules lived in a top-level `cli` package; 8.2.0 moved them under `hcl2.cli` so the distribution no longer claims the generic `cli` name. Only direct `import cli.*` is affected — the `hcl2tojson`, `jsontohcl2`, and `hq` commands are unchanged. Two new CLI tools ship with v8: diff --git a/hcl2/__main__.py b/hcl2/__main__.py index 9beb4a75..c07b250c 100644 --- a/hcl2/__main__.py +++ b/hcl2/__main__.py @@ -1,6 +1,6 @@ """Allow ``python -m hcl2`` to run the hcl2tojson command.""" -from cli.hcl_to_json import main +from hcl2.cli.hcl_to_json import main if __name__ == "__main__": main() diff --git a/cli/__init__.py b/hcl2/cli/__init__.py similarity index 100% rename from cli/__init__.py rename to hcl2/cli/__init__.py diff --git a/cli/hcl_to_json.py b/hcl2/cli/hcl_to_json.py similarity index 99% rename from cli/hcl_to_json.py rename to hcl2/cli/hcl_to_json.py index 87dae289..6c8b7d98 100644 --- a/cli/hcl_to_json.py +++ b/hcl2/cli/hcl_to_json.py @@ -6,7 +6,11 @@ import sys from typing import IO, List, Optional, TextIO -from cli.helpers import ( +from hcl2 import load +from hcl2.utils import SerializationOptions +from hcl2.version import __version__ + +from .helpers import ( EXIT_IO_ERROR, EXIT_PARSE_ERROR, EXIT_PARTIAL, @@ -20,9 +24,6 @@ _expand_file_args, _install_sigpipe_handler, ) -from hcl2 import load -from hcl2.utils import SerializationOptions -from hcl2.version import __version__ _HCL_EXTENSIONS = {".tf", ".hcl"} diff --git a/cli/helpers.py b/hcl2/cli/helpers.py similarity index 100% rename from cli/helpers.py rename to hcl2/cli/helpers.py diff --git a/cli/hq.py b/hcl2/cli/hq.py similarity index 100% rename from cli/hq.py rename to hcl2/cli/hq.py diff --git a/cli/json_to_hcl.py b/hcl2/cli/json_to_hcl.py similarity index 99% rename from cli/json_to_hcl.py rename to hcl2/cli/json_to_hcl.py index d14daed5..9684bfc6 100644 --- a/cli/json_to_hcl.py +++ b/hcl2/cli/json_to_hcl.py @@ -9,7 +9,14 @@ from typing import TextIO import hcl2 -from cli.helpers import ( +from hcl2 import dump +from hcl2.deserializer import DeserializerOptions +from hcl2.formatter import FormatterOptions +from hcl2.query.diff import diff_dicts, format_diff_json, format_diff_text +from hcl2.utils import SerializationOptions +from hcl2.version import __version__ + +from .helpers import ( EXIT_DIFF, EXIT_IO_ERROR, EXIT_PARSE_ERROR, @@ -22,12 +29,6 @@ _expand_file_args, _install_sigpipe_handler, ) -from hcl2 import dump -from hcl2.deserializer import DeserializerOptions -from hcl2.formatter import FormatterOptions -from hcl2.query.diff import diff_dicts, format_diff_json, format_diff_text -from hcl2.utils import SerializationOptions -from hcl2.version import __version__ def _json_to_hcl( diff --git a/pyproject.toml b/pyproject.toml index cc4c243a..6541b5d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,12 +40,12 @@ content-type = "text/markdown" Homepage = "https://github.com/amplify-education/python-hcl2" [project.scripts] -hcl2tojson = "cli.hcl_to_json:main" -jsontohcl2 = "cli.json_to_hcl:main" -hq = "cli.hq:main" +hcl2tojson = "hcl2.cli.hcl_to_json:main" +jsontohcl2 = "hcl2.cli.json_to_hcl:main" +hq = "hcl2.cli.hq:main" [tool.setuptools] -packages = ["hcl2", "hcl2.rules", "hcl2.query", "cli"] +packages = ["hcl2", "hcl2.rules", "hcl2.query", "hcl2.cli"] zip-safe = false include-package-data = true diff --git a/test/integration/test_cli_subprocess.py b/test/integration/test_cli_subprocess.py index ce4b1226..403bcd3b 100644 --- a/test/integration/test_cli_subprocess.py +++ b/test/integration/test_cli_subprocess.py @@ -26,8 +26,8 @@ JSON_RESERIALIZED_DIR = INTEGRATION_DIR / "json_reserialized" PROJECT_ROOT = INTEGRATION_DIR.parent.parent -_HCL2TOJSON = [sys.executable, "-c", "from cli.hcl_to_json import main; main()"] -_JSONTOHCL2 = [sys.executable, "-c", "from cli.json_to_hcl import main; main()"] +_HCL2TOJSON = [sys.executable, "-c", "from hcl2.cli.hcl_to_json import main; main()"] +_JSONTOHCL2 = [sys.executable, "-c", "from hcl2.cli.json_to_hcl import main; main()"] _TIMEOUT = 30 diff --git a/test/unit/cli/test_hcl_to_json.py b/test/unit/cli/test_hcl_to_json.py index 3b41e606..78fa5dbb 100644 --- a/test/unit/cli/test_hcl_to_json.py +++ b/test/unit/cli/test_hcl_to_json.py @@ -6,8 +6,8 @@ from unittest import TestCase from unittest.mock import patch -from cli.hcl_to_json import main -from cli.helpers import EXIT_IO_ERROR, EXIT_PARSE_ERROR, EXIT_PARTIAL +from hcl2.cli.hcl_to_json import main +from hcl2.cli.helpers import EXIT_IO_ERROR, EXIT_PARSE_ERROR, EXIT_PARTIAL SIMPLE_HCL = "x = 1\n" SIMPLE_JSON_DICT = {"x": 1} diff --git a/test/unit/cli/test_helpers.py b/test/unit/cli/test_helpers.py index 8ba078f2..f77cf38d 100644 --- a/test/unit/cli/test_helpers.py +++ b/test/unit/cli/test_helpers.py @@ -6,7 +6,7 @@ from unittest import TestCase from unittest.mock import patch -from cli.helpers import ( +from hcl2.cli.helpers import ( _collect_files, _convert_directory, _convert_multiple_files, diff --git a/test/unit/cli/test_hq.py b/test/unit/cli/test_hq.py index 3ec9142e..6ff263a1 100644 --- a/test/unit/cli/test_hq.py +++ b/test/unit/cli/test_hq.py @@ -6,7 +6,7 @@ from unittest import TestCase from unittest.mock import patch -from cli.hq import ( +from hcl2.cli.hq import ( EXIT_IO_ERROR, EXIT_NO_RESULTS, EXIT_PARSE_ERROR, diff --git a/test/unit/cli/test_json_to_hcl.py b/test/unit/cli/test_json_to_hcl.py index aebdbc28..32c0b2e8 100644 --- a/test/unit/cli/test_json_to_hcl.py +++ b/test/unit/cli/test_json_to_hcl.py @@ -6,8 +6,8 @@ from unittest import TestCase from unittest.mock import patch -from cli.helpers import EXIT_DIFF, EXIT_IO_ERROR, EXIT_PARSE_ERROR, EXIT_PARTIAL -from cli.json_to_hcl import main +from hcl2.cli.helpers import EXIT_DIFF, EXIT_IO_ERROR, EXIT_PARSE_ERROR, EXIT_PARTIAL +from hcl2.cli.json_to_hcl import main SIMPLE_JSON_DICT = {"x": 1} SIMPLE_JSON = json.dumps(SIMPLE_JSON_DICT) @@ -753,7 +753,7 @@ def test_structure_error_exits_2(self): stderr = StringIO() with patch("sys.argv", ["jsontohcl2", path]): - with patch("cli.json_to_hcl.dump", side_effect=TypeError("bad structure")): + with patch("hcl2.cli.json_to_hcl.dump", side_effect=TypeError("bad structure")): with patch("sys.stderr", stderr): with self.assertRaises(SystemExit) as cm: main()