Skip to content

Commit f2043e7

Browse files
Merge pull request #304 from amplify-education/AT-14951-migrate_to_ruff
AT-14951: migrate from pylint/isort/black to ruff
2 parents 62e8e85 + 1da8f01 commit f2043e7

77 files changed

Lines changed: 621 additions & 1369 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.pre-commit-config.yaml

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,17 @@ repos:
2626
hooks:
2727
- id: mdformat
2828
name: Format markdown
29-
- repo: https://github.com/psf/black
30-
rev: 22.8.0
29+
- repo: https://github.com/astral-sh/ruff-pre-commit
30+
rev: v0.15.21
3131
hooks:
32-
- id: black
33-
language_version: python3.8
32+
- id: ruff-check
33+
args: [--fix]
34+
- id: ruff-format
3435
- repo: https://github.com/pre-commit/mirrors-mypy
3536
rev: v0.991 # Use the sha / tag you want to point at
3637
hooks:
3738
- id: mypy
38-
- repo: local
39-
hooks:
40-
- id: pylint
41-
name: pylint (Python Linting)
42-
entry: pylint
43-
language: system
44-
types: [python]
45-
files: ^(hcl2|test)/
46-
args: [--rcfile=pylintrc, --output-format=colorized, --score=no]
39+
# Pinned so the hook's own venv can build typed-ast, which has no
40+
# wheel for Python 3.12+; this only affects mypy's own interpreter,
41+
# not the --python-version it type-checks against.
42+
language_version: python3.11

CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,10 @@ Always run round-trip full test suite after any modification.
193193

194194
## Pre-commit Checks
195195

196-
Hooks are defined in `.pre-commit-config.yaml` (includes black, mypy, pylint, and others). All changed files must pass these checks before committing. When writing or modifying code:
196+
Hooks are defined in `.pre-commit-config.yaml` (includes ruff, mypy, and others). All changed files must pass these checks before committing. When writing or modifying code:
197197

198-
- Format Python with **black** (Python 3.8 target).
199-
- Ensure **mypy** and **pylint** pass. Pylint config is in `pylintrc`, scoped to `hcl2/` and `test/`.
198+
- Format and lint Python with **ruff** (config in `pyproject.toml`'s `[tool.ruff]`).
199+
- Ensure **mypy** passes.
200200
- End files with a newline; strip trailing whitespace (except under `test/integration/(hcl2_reconstructed|specialized)/`).
201201

202202
## Keeping Docs Current

bin/check_deps.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
"""Used by dependencies_check.yml to verify if dependencies between pyproject.yml and requirements.txt are in sync"""
1+
"""Used by dependencies_check.yml to verify if dependencies between pyproject.yml and
2+
requirements.txt are in sync"""
3+
4+
import difflib
25
import sys
36
from typing import Set
4-
import difflib
7+
58
import tomli
69

710

bin/terraform_test

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ Options:
99
PATH The directory to check. Defaults to the TERRAFORM_CONFIG environment variable
1010
1111
"""
12+
1213
import argparse
1314
import os
1415

15-
from hcl2 import load
1616
from hcl2.version import __version__
1717

18+
from hcl2 import load
19+
1820
if __name__ == "__main__":
19-
parser = argparse.ArgumentParser(
20-
description="This script recursively converts hcl2 files to json"
21-
)
21+
parser = argparse.ArgumentParser(description="This script recursively converts hcl2 files to json")
2222
parser.add_argument("PATH", nargs="?", default=None, help="The path to convert")
2323
parser.add_argument("--version", action="version", version=__version__)
2424

@@ -27,9 +27,7 @@ if __name__ == "__main__":
2727
target_dir = args.PATH if args.PATH else os.environ["TERRAFORM_CONFIG"]
2828
for curr_dir, dirs, files in os.walk(target_dir):
2929
for file_name in files:
30-
if ".terraform" not in curr_dir and (
31-
file_name.endswith(".tf") or file_name.endswith("tfvars")
32-
):
30+
if ".terraform" not in curr_dir and (file_name.endswith(".tf") or file_name.endswith("tfvars")):
3331
file_path = os.path.join(curr_dir, file_name)
3432

3533
with open(file_path, "r") as file:

cli/hcl_to_json.py

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@
66
import sys
77
from typing import IO, List, Optional, TextIO
88

9-
from hcl2 import load
10-
from hcl2.utils import SerializationOptions
119
from hcl2.version import __version__
10+
1211
from cli.helpers import (
1312
EXIT_IO_ERROR,
1413
EXIT_PARSE_ERROR,
@@ -23,6 +22,8 @@
2322
_expand_file_args,
2423
_install_sigpipe_handler,
2524
)
25+
from hcl2 import load
26+
from hcl2.utils import SerializationOptions
2627

2728
_HCL_EXTENSIONS = {".tf", ".hcl"}
2829

@@ -125,22 +126,16 @@ def _stream_ndjson( # pylint: disable=too-many-arguments,too-many-positional-ar
125126
print(file_path, file=sys.stderr, flush=True)
126127
try:
127128
if file_path == "-":
128-
data = _load_to_dict(
129-
sys.stdin, options, only=only, exclude=exclude, fields=fields
130-
)
129+
data = _load_to_dict(sys.stdin, options, only=only, exclude=exclude, fields=fields)
131130
else:
132131
with open(file_path, "r", encoding="utf-8") as f:
133-
data = _load_to_dict(
134-
f, options, only=only, exclude=exclude, fields=fields
135-
)
132+
data = _load_to_dict(f, options, only=only, exclude=exclude, fields=fields)
136133
except HCL_SKIPPABLE as exc:
137134
if skip:
138135
worst_exit = max(worst_exit, EXIT_PARTIAL)
139136
continue
140137
print(
141-
_error(
142-
str(exc), use_json=True, error_type="parse_error", file=file_path
143-
),
138+
_error(str(exc), use_json=True, error_type="parse_error", file=file_path),
144139
file=sys.stderr,
145140
)
146141
return EXIT_PARSE_ERROR
@@ -197,9 +192,7 @@ def main(): # pylint: disable=too-many-branches,too-many-statements,too-many-lo
197192
epilog=_EXAMPLES,
198193
formatter_class=argparse.RawDescriptionHelpFormatter,
199194
)
200-
parser.add_argument(
201-
"-s", dest="skip", action="store_true", help="Skip un-parsable files"
202-
)
195+
parser.add_argument("-s", dest="skip", action="store_true", help="Skip un-parsable files")
203196
parser.add_argument(
204197
"PATH",
205198
nargs="*",
@@ -382,9 +375,7 @@ def convert(in_file, out_file):
382375
if len(paths) == 1:
383376
path = paths[0]
384377
if path == "-" or os.path.isfile(path):
385-
if not _convert_single_file(
386-
path, output, convert, args.skip, HCL_SKIPPABLE, quiet=quiet
387-
):
378+
if not _convert_single_file(path, output, convert, args.skip, HCL_SKIPPABLE, quiet=quiet):
388379
sys.exit(EXIT_PARTIAL)
389380
elif os.path.isdir(path):
390381
if output is None:

cli/helpers.py

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import signal
77
import sys
88
from io import StringIO
9-
from typing import Callable, IO, List, Optional, Set, Tuple, Type
9+
from typing import IO, Callable, List, Optional, Set, Tuple, Type
1010

1111
from lark import UnexpectedCharacters, UnexpectedToken
1212

@@ -158,9 +158,7 @@ def _convert_directory( # pylint: disable=too-many-positional-arguments,too-man
158158
for current_dir, _, files in os.walk(in_path):
159159
dir_prefix = os.path.commonpath([in_path, current_dir])
160160
relative_current_dir = os.path.relpath(current_dir, dir_prefix)
161-
current_out_path = os.path.normpath(
162-
os.path.join(out_path, relative_current_dir)
163-
)
161+
current_out_path = os.path.normpath(os.path.join(out_path, relative_current_dir))
164162
if not os.path.exists(current_out_path):
165163
os.makedirs(current_out_path)
166164
for file_name in files:
@@ -226,9 +224,7 @@ def _convert_multiple_files( # pylint: disable=too-many-positional-arguments
226224
file_out_dir = os.path.dirname(file_out)
227225
if file_out_dir and not os.path.exists(file_out_dir):
228226
os.makedirs(file_out_dir)
229-
if not _convert_single_file(
230-
in_path, file_out, convert_fn, skip, skippable, quiet=quiet
231-
):
227+
if not _convert_single_file(in_path, file_out, convert_fn, skip, skippable, quiet=quiet):
232228
any_skipped = True
233229
return any_skipped
234230

cli/hq.py

Lines changed: 19 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,21 @@
88
import sys
99
from typing import Any, List, Optional, Tuple
1010

11+
from hcl2.version import __version__
12+
1113
from hcl2.query._base import NodeView
12-
from hcl2.utils import SerializationOptions
1314
from hcl2.query.body import DocumentView
1415
from hcl2.query.introspect import build_schema, describe_results
1516
from hcl2.query.path import QuerySyntaxError
1617
from hcl2.query.pipeline import classify_stage, execute_pipeline, split_pipeline
1718
from hcl2.query.resolver import resolve_path
1819
from hcl2.query.safe_eval import (
19-
UnsafeExpressionError,
2020
_SAFE_CALLABLE_NAMES,
21+
UnsafeExpressionError,
2122
safe_eval,
2223
)
23-
from hcl2.version import __version__
24+
from hcl2.utils import SerializationOptions
25+
2426
from .helpers import _expand_file_args # noqa: F401 — re-exported for tests
2527

2628
# ---------------------------------------------------------------------------
@@ -171,9 +173,7 @@ def _collect_files(path: str) -> List[str]:
171173
def _error(msg: str, use_json: bool, **extra) -> str:
172174
"""Format an error message."""
173175
if use_json:
174-
return json.dumps(
175-
{"error": extra.get("error_type", "error"), "message": msg, **extra}
176-
)
176+
return json.dumps({"error": extra.get("error_type", "error"), "message": msg, **extra})
177177
return f"Error: {msg}"
178178

179179

@@ -347,17 +347,12 @@ def format_result(self, result: Any) -> str:
347347
def format_list(self, items: list) -> str:
348348
"""Format a list result (e.g. from hybrid mode returning a list)."""
349349
if self.output_json:
350-
converted = [
351-
_convert_for_json(item, options=self.serialization_options)
352-
for item in items
353-
]
350+
converted = [_convert_for_json(item, options=self.serialization_options) for item in items]
354351
return json.dumps(converted, indent=self.json_indent, default=str)
355352
parts = []
356353
for item in items:
357354
if isinstance(item, NodeView):
358-
parts.append(
359-
item.to_hcl() if not self.output_value else str(item.to_dict())
360-
)
355+
parts.append(item.to_hcl() if not self.output_value else str(item.to_dict()))
361356
else:
362357
parts.append(str(item))
363358
if not self.output_value:
@@ -367,10 +362,7 @@ def format_list(self, items: list) -> str:
367362
def format_output(self, results: List[Any]) -> str:
368363
"""Format results for final output."""
369364
if self.output_json and len(results) > 1:
370-
items = [
371-
_convert_for_json(item, options=self.serialization_options)
372-
for item in results
373-
]
365+
items = [_convert_for_json(item, options=self.serialization_options) for item in results]
374366
return json.dumps(items, indent=self.json_indent, default=str)
375367
return "\n".join(self.format_result(r) for r in results)
376368

@@ -427,9 +419,7 @@ def emit(self, results: List[Any], file_path: str) -> None:
427419

428420
# JSON + multi — accumulate for merged output
429421
if self.config.output_json and self.multi:
430-
self._accumulator.extend(
431-
_convert_results(results, file_path, self.multi, self.config)
432-
)
422+
self._accumulator.extend(_convert_results(results, file_path, self.multi, self.config))
433423
return
434424

435425
# Single-file output (with_location or default)
@@ -458,12 +448,8 @@ def flush(self) -> None:
458448
"""Sort and emit accumulated JSON results."""
459449
if not self._accumulator:
460450
return
461-
self._accumulator.sort(
462-
key=lambda x: x.get("__file__", "") if isinstance(x, dict) else ""
463-
)
464-
print(
465-
json.dumps(self._accumulator, indent=self.config.json_indent, default=str)
466-
)
451+
self._accumulator.sort(key=lambda x: x.get("__file__", "") if isinstance(x, dict) else "")
452+
print(json.dumps(self._accumulator, indent=self.config.json_indent, default=str))
467453
self._accumulator.clear()
468454

469455

@@ -543,9 +529,7 @@ def _process_file(args_tuple):
543529
return (file_path, EXIT_SUCCESS, converted, None)
544530

545531

546-
def _run_diff(
547-
file1: str, file2: str, use_json: bool, json_indent: Optional[int]
548-
) -> int:
532+
def _run_diff(file1: str, file2: str, use_json: bool, json_indent: Optional[int]) -> int:
549533
"""Run structural diff between two HCL files.
550534
551535
Returns an exit code: 0 if files are identical, 1 if they differ.
@@ -554,9 +538,7 @@ def _run_diff(
554538
import hcl2
555539
from hcl2.query.diff import diff_dicts, format_diff_json, format_diff_text
556540

557-
opts = SerializationOptions(
558-
with_comments=False, with_meta=False, explicit_blocks=True
559-
)
541+
opts = SerializationOptions(with_comments=False, with_meta=False, explicit_blocks=True)
560542
for path in (file1, file2):
561543
if path == "-":
562544
continue
@@ -630,9 +612,7 @@ def _build_parser() -> argparse.ArgumentParser:
630612

631613
output_group = parser.add_mutually_exclusive_group()
632614
output_group.add_argument("--json", action="store_true", help="Output as JSON")
633-
output_group.add_argument(
634-
"--value", action="store_true", help="Output raw value only"
635-
)
615+
output_group.add_argument("--value", action="store_true", help="Output raw value only")
636616
output_group.add_argument(
637617
"--raw",
638618
action="store_true",
@@ -796,9 +776,7 @@ def _execute_and_emit(
796776
output_config: OutputConfig,
797777
) -> int:
798778
"""Execute queries across files and emit results. Returns an exit code."""
799-
file_paths = [
800-
fp for fa in _expand_file_args(args.FILE) for fp in _collect_files(fa)
801-
]
779+
file_paths = [fp for fa in _expand_file_args(args.FILE) for fp in _collect_files(fa)]
802780
any_results = False
803781
worst_exit = EXIT_SUCCESS
804782
multi = len(file_paths) > 1
@@ -816,14 +794,9 @@ def _execute_and_emit(
816794
with OutputSink(output_config, multi) as sink:
817795
if use_parallel:
818796
n_workers = args.jobs or min(os.cpu_count() or 1, len(file_paths))
819-
worker_args = [
820-
(fp, query, False, args.QUERY, multi, output_config)
821-
for fp in file_paths
822-
]
797+
worker_args = [(fp, query, False, args.QUERY, multi, output_config) for fp in file_paths]
823798
with multiprocessing.Pool(n_workers) as pool:
824-
for fp, exit_code, converted, error_msg in pool.imap_unordered(
825-
_process_file, worker_args
826-
):
799+
for fp, exit_code, converted, error_msg in pool.imap_unordered(_process_file, worker_args):
827800
if error_msg:
828801
etype = _EXIT_TO_ERROR_TYPE.get(exit_code, "error")
829802
print(
@@ -838,9 +811,7 @@ def _execute_and_emit(
838811
sink.emit_converted(converted)
839812
else:
840813
for file_path in file_paths:
841-
results, exit_code = _run_query_on_file(
842-
file_path, query, args.eval, use_json, args.QUERY
843-
)
814+
results, exit_code = _run_query_on_file(file_path, query, args.eval, use_json, args.QUERY)
844815
if results is None:
845816
worst_exit = max(worst_exit, exit_code)
846817
continue

0 commit comments

Comments
 (0)