From 9bb57552d831c1dcdc270300d45582f42920b661 Mon Sep 17 00:00:00 2001 From: blakeNaccarato Date: Fri, 26 Jan 2024 14:52:50 -0800 Subject: [PATCH] Generate local dev configs --- .tools/scripts/local_dev_configs.py | 88 +++++++++++++++++++++++++++ .tools/scripts/local_pyrightconfig.py | 46 -------------- .vscode/settings.json | 2 +- .vscode/tasks.json | 4 +- 4 files changed, 91 insertions(+), 49 deletions(-) create mode 100644 .tools/scripts/local_dev_configs.py delete mode 100644 .tools/scripts/local_pyrightconfig.py diff --git a/.tools/scripts/local_dev_configs.py b/.tools/scripts/local_dev_configs.py new file mode 100644 index 00000000..a5e02635 --- /dev/null +++ b/.tools/scripts/local_dev_configs.py @@ -0,0 +1,88 @@ +"""Make local dev configs.""" + +from collections.abc import Iterable, Mapping, Sequence +from datetime import date, time +from json import dumps +from pathlib import Path +from re import sub +from shlex import join, split +from tomllib import loads +from typing import TypeAlias + +PYPROJECT = Path("pyproject.toml") +"""Input project configuration file.""" +PYRIGHTCONFIG = Path("pyrightconfig.json") +"""Resulting pyright configuration file.""" +PYTEST = Path("pytest.ini") +"""Resulting pytest configuration file.""" + +Leaf: TypeAlias = int | float | bool | date | time | str +"""Leaf node.""" +Node: TypeAlias = Leaf | Sequence["Node"] | Mapping[str, "Node"] +"""General nde.""" + + +def main(): + """Generate modified local dev configs to shadow `pyproject.toml`. + + Duplicate pyright and pytest configuration from `pyproject.toml` to + `pyrightconfig.json` and `pytest.ini`, respectively. These files shadow the + configuration in `pyproject.toml`, which drives CI or if shadow configs are not + present. Shadow configs are in `.gitignore` to facilitate local-only shadowing. + + Local pyright configuration includes the editable local `boilercore` dependency to + facilitate refactoring and runing on the latest uncommitted code of that dependency. + Concurrent test runs are disabled in the local pytest configuration which slows down + the usual local, granular test workflow. + """ + config = loads(PYPROJECT.read_text("utf-8")) + # Write pyrightconfig.json + pyright = config["tool"]["pyright"] + data = dumps(add_pyright_includes(pyright, [Path("../boilercore/src")]), indent=2) + PYRIGHTCONFIG.write_text(encoding="utf-8", data=f"{data}\n") + # Write pytest.ini + pytest = config["tool"]["pytest"]["ini_options"] + pytest["addopts"] = disable_concurrent_tests(pytest["addopts"]) + PYTEST.write_text( + encoding="utf-8", + data="\n".join(["[pytest]", *[f"{k} = {v}" for k, v in pytest.items()], ""]), + ) + + +def add_pyright_includes( + config: dict[str, Node], others: Iterable[Path | str] +) -> dict[str, Node]: + """Include additional paths in pyright configuration. + + Args: + config: Pyright configuration. + others: Local paths to add to includes. + + Returns: + Modified pyright configuration. + """ + includes = config.pop("include", []) + if not isinstance(includes, Sequence): + raise TypeError("Expected a sequence of includes.") + return { + "include": [*includes, *[str(Path(incl).as_posix()) for incl in others]], + **config, + } + + +def disable_concurrent_tests(addopts: str) -> str: + """Normalize `addopts` string and disable concurrent pytest tests. + + Normalizes `addopts` to a space-separated one-line string. + + Args: + addopts: Pytest `addopts` value. + + Returns: + Modified `addopts` value. + """ + return sub(pattern=r"-n\s*[^\s]+", repl="-n 0", string=join(split(addopts))) + + +if __name__ == "__main__": + main() diff --git a/.tools/scripts/local_pyrightconfig.py b/.tools/scripts/local_pyrightconfig.py deleted file mode 100644 index 15fb7a2a..00000000 --- a/.tools/scripts/local_pyrightconfig.py +++ /dev/null @@ -1,46 +0,0 @@ -"""Make a local `pyrightconfig.json` that includes the specified paths.""" - -from collections.abc import Iterable -from json import dumps -from pathlib import Path -from tomllib import loads - -PYPROJECT = Path("pyproject.toml") -"""Default `pyproject.toml`.""" -PYRIGHTCONFIG = Path("pyrightconfig.json") -"""Default `pyrightconfig.json`.""" -OTHERS = [Path("../boilercore/src")] -"""Default local paths to append.""" - - -def make_local_pyrightconfig( - pyproject: Path | str = PYPROJECT, - pyrightconfig: Path = PYRIGHTCONFIG, - others: Iterable[Path | str] = OTHERS, -): - """Make a local `pyrightconfig.json` that includes the specified paths. - - Duplicates config from `pyproject.toml` to `pyrightconfig.json` and appends - specified paths to `include`. Enables type checking and refactoring across project - boundaries, for instance with editable local dependencies during development of - related projects in a multi-repo workflow. - - Args: - pyproject: Source `pyproject.toml`. Search project root by default. - pyrightconfig: Destination `pyrightconfig.json`. Search project root by default. - others: Local paths to append. Defaults to `[Path("../boilercore/src")]`. - """ - config = loads(Path(pyproject).read_text("utf-8"))["tool"]["pyright"] - config = { - "include": [ - *config.pop("include", []), - *[str(Path(other).as_posix()) for other in others], - ], - **config, - } - data = f"{dumps(indent=2, obj=config)}\n" - Path(pyrightconfig).write_text(encoding="utf-8", data=data) - - -if __name__ == "__main__": - make_local_pyrightconfig() diff --git a/.vscode/settings.json b/.vscode/settings.json index 58759ac0..be84b7bb 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -83,7 +83,7 @@ }, "notebook.codeActionsOnSave": { "notebook.source.fixAll": "explicit", - "notebook.source.organizeImports": "explicit" + "source.organizeImports": "explicit" }, //! Extensions //* GitHub Actions diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3eabddf3..289572a2 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -71,10 +71,10 @@ "problemMatcher": [] }, { - "label": "proj: local pyrightconfig", + "label": "proj: local dev configs (Pyrightconfig, pytest.ini)", "type": "shell", "options": { "shell": { "executable": "pwsh", "args": ["-Command"] } }, - "command": "python .tools/scripts/local_pyrightconfig.py", + "command": "python .tools/scripts/local_dev_configs.py", "icon": { "id": "graph" }, "problemMatcher": [] },