diff --git a/poetry.lock b/poetry.lock index d3fa300..acc3790 100644 --- a/poetry.lock +++ b/poetry.lock @@ -769,6 +769,24 @@ files = [ [package.dependencies] tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +[[package]] +name = "pyright" +version = "1.1.336" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.336-py3-none-any.whl", hash = "sha256:8f6a8f365730c8d6c1af840d937371fd5cf0137b6e1827b8b066bc0bb7327aa6"}, + {file = "pyright-1.1.336.tar.gz", hash = "sha256:f92d6d6845e4175833ea60dee5b1ef4d5d66663438fdaedccc1c3ba0f8efa3e3"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" + +[package.extras] +all = ["twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] + [[package]] name = "pytest" version = "7.4.3" @@ -1275,4 +1293,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "8b322516c10931326405cc905735ccc9831a1e37d05774f341950ab2e4f791c9" +content-hash = "17c4ca2e97668439249d40ded43e9a1cc30edb4c37f3f12aacaf2c7b5e6c52d9" diff --git a/pyproject.toml b/pyproject.toml index c50fc0f..5ed5c67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,10 @@ tox = "^4.6.4" coverage = {extras = ["toml"], version = "^7.2.7"} requests = "^2.31.0" + +[tool.poetry.group.type.dependencies] +pyright = "^1.1.336" + [tool.black] extend-exclude = """(\ reps/templates)\ @@ -39,6 +43,11 @@ testpaths = ["test"] [tool.ruff] exclude = ["reps/templates"] +[tool.pyright] +include = ["reps"] +ignore = ["reps/templates"] +reportUnknownParameterType = "error" + [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/reps/console.py b/reps/console.py index 62bbdd5..e21108e 100644 --- a/reps/console.py +++ b/reps/console.py @@ -1,6 +1,7 @@ import sys from argparse import ArgumentParser from pathlib import Path +from typing import Any, List from cookiecutter.main import cookiecutter @@ -12,7 +13,7 @@ def available_templates(): return [path.name for path in TEMPLATE_DIR.iterdir() if path.is_dir()] -def command_new(name, template, **cookiecutter_args): +def command_new(name: str, template: str, **cookiecutter_args: Any): if template not in available_templates(): print(f"template '{template}' not found!") return 1 @@ -23,17 +24,17 @@ def command_new(name, template, **cookiecutter_args): # specified, ensure we don't error out when the project already exists. cookiecutter_args.setdefault("overwrite_if_exists", False) - template = TEMPLATE_DIR / template + template = str(TEMPLATE_DIR / template) cookiecutter_args.setdefault("extra_context", {}).setdefault("project_name", name) # Generate the project. cookiecutter( - str(template), + template, **cookiecutter_args, ) -def run(args=sys.argv[1:]): +def run(args: List[str] = sys.argv[1:]): parser = ArgumentParser() parser.add_argument( "name", nargs="?", default=None, help="Name of the project to create." diff --git a/reps/hooks.py b/reps/hooks.py index 77f9322..4e25e14 100644 --- a/reps/hooks.py +++ b/reps/hooks.py @@ -1,32 +1,34 @@ import subprocess from collections import defaultdict from pathlib import Path +from typing import Any, Callable, Generator, List from ruamel.yaml import YAML from halo import Halo from reps.console import command_new +from reps.types import HookFn, Items HOOKS = defaultdict(list) -def hook(name): - def wrapper(func): +def hook(name: str) -> Callable[[HookFn], HookFn]: + def wrapper(func: HookFn) -> HookFn: HOOKS[name].append(func) return func return wrapper -def run_hooks(group, items): +def run_hooks(group: str, items: Items): for hook_fn in HOOKS[group]: with Halo(f"running {hook_fn.__name__}") as spinner: hook_fn(items) spinner.succeed(f"{hook_fn.__name__}") -def run(cmd, **kwargs): +def run(cmd: List[str], **kwargs: Any): kwargs.setdefault("check", True) kwargs.setdefault("text", True) kwargs.setdefault("stdout", subprocess.PIPE) @@ -40,7 +42,7 @@ def run(cmd, **kwargs): @hook("pre-gen-py") -def base_init(items): +def base_init(items: Items): """Generate the 'base' template first.""" if "_copy_without_render" in items: del items["_copy_without_render"] @@ -57,7 +59,7 @@ def base_init(items): @hook("post-gen-py") -def merge_pre_commit(items): +def merge_pre_commit(items: Items): """Update the base pre-commit config with Python-specific tools.""" yaml = YAML() @@ -88,7 +90,7 @@ def merge_pre_commit(items): @hook("post-gen-py") -def add_poetry_dependencies(items): +def add_poetry_dependencies(items: Items): # Build constraints to ensure we don't try to add versions # that are incompatible with the minimum Python. min_python = items["min_python_version"] @@ -96,7 +98,7 @@ def add_poetry_dependencies(items): constraints["coverage"] = {"3.7": "coverage@<7.3.0"} constraints["tox"] = {"3.7": "tox@<4.9.0"} - def build_specifiers(*packages): + def build_specifiers(*packages: str) -> Generator[str, None, None]: for p in packages: yield constraints[p].get(min_python, p) @@ -111,15 +113,12 @@ def build_specifiers(*packages): + list(build_specifiers("sphinx<7", "sphinx-autobuild", "sphinx-book-theme")) ) - run( - ["poetry", "add", "--group=type"] - + list(build_specifiers("pyright")) - ) + run(["poetry", "add", "--group=type"] + list(build_specifiers("pyright"))) @hook("post-gen-py") @hook("post-gen-base") -def git_init(items): +def git_init(items: Items): run(["git", "init"]) run(["git", "checkout", "-b", "main"]) run( @@ -135,16 +134,16 @@ def git_init(items): @hook("post-gen-py") @hook("post-gen-base") -def pre_commit_autoupdate(items): +def pre_commit_autoupdate(items: Items): run(["pre-commit", "autoupdate"]) @hook("post-gen-py") @hook("post-gen-base") -def lock_taskgraph_requirements(items): +def lock_taskgraph_requirements(items: Items): run(["pip-compile", "requirements.in", "--generate-hashes"], cwd="taskcluster") @hook("post-gen-base") -def taskgraph_init(items): +def taskgraph_init(items: Items): run(["taskgraph", "init"]) diff --git a/reps/types.py b/reps/types.py new file mode 100644 index 0000000..d195a59 --- /dev/null +++ b/reps/types.py @@ -0,0 +1,5 @@ +from typing import Any, Callable, Dict + + +Items = Dict[str, Any] +HookFn = Callable[[Items], None] diff --git a/taskcluster/ci/test/kind.yml b/taskcluster/ci/test/kind.yml index 8a787d3..2e2ed1c 100644 --- a/taskcluster/ci/test/kind.yml +++ b/taskcluster/ci/test/kind.yml @@ -34,3 +34,12 @@ tasks: command: >- poetry install --only test && poetry run tox --parallel + + type-check: + description: "Run pyright type checking against code base" + worker: + max-run-time: 300 + run: + command: >- + poetry install --only main --only type && + poetry run pyright