diff --git a/README.md b/README.md index 60463f0..77d061a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This is a modern Cookiecutter template that can be used to initiate a Python pro - [Poetry](https://python-poetry.org/) for dependency management - CI/CD with [GitHub Actions](https://github.com/features/actions) - Pre-commit hooks with [pre-commit](https://pre-commit.com/) -- Code quality with [ruff](https://github.com/charliermarsh/ruff), [mypy](https://mypy.readthedocs.io/en/stable/), [deptry](https://github.com/fpgmaas/deptry/) and [prettier](https://prettier.io/) +- Code quality with [ruff](https://github.com/charliermarsh/ruff), [mypy](https://mypy.readthedocs.io/en/stable/) or [pyright](https://github.com/microsoft/pyright), [deptry](https://github.com/fpgmaas/deptry/) and [prettier](https://prettier.io/) - Publishing to [PyPI](https://pypi.org) or [Artifactory](https://jfrog.com/artifactory) by creating a new release on GitHub - Testing and coverage with [pytest](https://docs.pytest.org/en/7.1.x/) and [codecov](https://about.codecov.io/) - Documentation with [MkDocs](https://www.mkdocs.org/) diff --git a/cookiecutter.json b/cookiecutter.json index e5f2b21..8adb8b4 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -7,6 +7,7 @@ "project_description": "This is a template repository for Python projects that use Poetry for their dependency management.", "include_github_actions": ["y", "n"], "publish_to": ["pypi", "artifactory", "none"], + "typechecking": ["mypy", "pyright"], "deptry": ["y", "n"], "mkdocs": ["y", "n"], "codecov": ["y", "n"], diff --git a/docs/features/linting.md b/docs/features/linting.md index b0ae670..0552db4 100644 --- a/docs/features/linting.md +++ b/docs/features/linting.md @@ -64,9 +64,13 @@ ignore = [ "tests/*" = ["S101"] ``` -# mypy +# Typechecking -[mypy](https://mypy.readthedocs.io/en/stable/) is used for static type checking, and it's configuration and can be edited in `pyproject.toml`. +Two typechecking options are available, `mypy` or `pyright`. + +## mypy + +[mypy](https://mypy.readthedocs.io/en/stable/) can be used for static type checking, and its configuration and can be edited in `pyproject.toml`. ```toml [tool.mypy] @@ -84,9 +88,21 @@ exclude = [ ] ``` +## pyright + +[pyright](https://github.com/microsoft/pyright) can be used for static type checking, and its configuration and can be edited in `pyproject.toml`: + +```toml +[tool.pyright] +include = ['{{cookiecutter.project_slug}}'] +typeCheckingMode = "strict" +venvPath = "." +venv = ".venv" +``` + # deptry -[deptry](https://github.com/fpgmaas/deptry) is used to check the code for dependency issues, and it's configuration and can be edited in `pyproject.toml`. +[deptry](https://github.com/fpgmaas/deptry) is used to check the code for dependency issues, and its configuration and can be edited in `pyproject.toml`. ```toml [tool.mypy] diff --git a/docs/features/makefile.md b/docs/features/makefile.md index 959457d..8856074 100644 --- a/docs/features/makefile.md +++ b/docs/features/makefile.md @@ -7,7 +7,7 @@ available: ``` install Install the poetry environment and install the pre-commit hooks -check Lint and check code by running ruff, mypy and deptry. +check Lint and check code by running ruff, mypy/pyright and deptry. test Test the code with pytest build Build wheel file using poetry clean-build clean build artifacts diff --git a/docs/index.md b/docs/index.md index 8a3ab17..b8ae4f9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ This is a modern Cookiecutter template that can be used to initiate a Python pro - [Poetry](https://python-poetry.org/) for dependency management - CI/CD with [GitHub Actions](https://github.com/features/actions) - Pre-commit hooks with [pre-commit](https://pre-commit.com/) -- Code quality with [ruff](https://github.com/charliermarsh/ruff), [mypy](https://mypy.readthedocs.io/en/stable/), [deptry](https://github.com/fpgmaas/deptry/) and [prettier](https://prettier.io/) +- Code quality with [ruff](https://github.com/charliermarsh/ruff), [mypy](https://mypy.readthedocs.io/en/stable/) or [pyright](https://github.com/microsoft/pyright), [deptry](https://github.com/fpgmaas/deptry/) and [prettier](https://prettier.io/) - Publishing to [PyPI](https://pypi.org) or [Artifactory](https://jfrog.com/artifactory) by creating a new release on GitHub - Testing and coverage with [pytest](https://docs.pytest.org/en/7.1.x/) and [codecov](https://about.codecov.io/) - Documentation with [MkDocs](https://www.mkdocs.org/) diff --git a/tests/test_cookiecutter.py b/tests/test_cookiecutter.py index 7c6b6c3..a8b9f8f 100644 --- a/tests/test_cookiecutter.py +++ b/tests/test_cookiecutter.py @@ -76,7 +76,8 @@ def test_dont_publish(cookies, tmp_path): assert result.exit_code == 0 assert is_valid_yaml(result.project_path / ".github" / "workflows" / "on-release-main.yml") assert not file_contains_text( - f"{result.project_path}/.github/workflows/on-release-main.yml", "make build-and-publish" + f"{result.project_path}/.github/workflows/on-release-main.yml", + "make build-and-publish", ) @@ -86,7 +87,10 @@ def test_mkdocs(cookies, tmp_path): assert result.exit_code == 0 assert is_valid_yaml(result.project_path / ".github" / "workflows" / "main.yml") assert is_valid_yaml(result.project_path / ".github" / "workflows" / "on-release-main.yml") - assert file_contains_text(f"{result.project_path}/.github/workflows/on-release-main.yml", "mkdocs gh-deploy") + assert file_contains_text( + f"{result.project_path}/.github/workflows/on-release-main.yml", + "mkdocs gh-deploy", + ) assert file_contains_text(f"{result.project_path}/Makefile", "docs:") assert os.path.isdir(f"{result.project_path}/docs") @@ -98,7 +102,8 @@ def test_not_mkdocs(cookies, tmp_path): assert is_valid_yaml(result.project_path / ".github" / "workflows" / "main.yml") assert is_valid_yaml(result.project_path / ".github" / "workflows" / "on-release-main.yml") assert not file_contains_text( - f"{result.project_path}/.github/workflows/on-release-main.yml", "mkdocs gh-deploy" + f"{result.project_path}/.github/workflows/on-release-main.yml", + "mkdocs gh-deploy", ) assert not file_contains_text(f"{result.project_path}/Makefile", "docs:") assert not os.path.isdir(f"{result.project_path}/docs") @@ -153,3 +158,37 @@ def test_remove_release_workflow(cookies, tmp_path): result = cookies.bake(extra_context={"publish_to": "none", "mkdocs": "n"}) assert result.exit_code == 0 assert not os.path.isfile(f"{result.project_path}/.github/workflows/on-release-main.yml") + + +def test_pyright(cookies, tmp_path): + with run_within_dir(tmp_path): + result = cookies.bake(extra_context={"typechecking": "pyright"}) + assert result.exit_code == 0 + # check the toml file + assert file_contains_text(f"{result.project_path}/pyproject.toml", "[tool.pyright]") + assert file_contains_text(f"{result.project_path}/pyproject.toml", "pyright =") + assert not file_contains_text(f"{result.project_path}/pyproject.toml", "[tool.mypy]") + assert not file_contains_text(f"{result.project_path}/pyproject.toml", "mypy =") + # check the make file + assert file_contains_text(f"{result.project_path}/Makefile", "pyright") + assert not file_contains_text(f"{result.project_path}/Makefile", "mypy") + # check the tox file + assert file_contains_text(f"{result.project_path}/tox.ini", "pyright") + assert not file_contains_text(f"{result.project_path}/tox.ini", "mypy") + + +def test_mypy(cookies, tmp_path): + with run_within_dir(tmp_path): + result = cookies.bake(extra_context={"typechecking": "mypy"}) + assert result.exit_code == 0 + # check the toml file + assert file_contains_text(f"{result.project_path}/pyproject.toml", "[tool.mypy]") + assert file_contains_text(f"{result.project_path}/pyproject.toml", "mypy =") + assert not file_contains_text(f"{result.project_path}/pyproject.toml", "[tool.pyright]") + assert not file_contains_text(f"{result.project_path}/pyproject.toml", "pyright =") + # check the make file + assert file_contains_text(f"{result.project_path}/Makefile", "mypy") + assert not file_contains_text(f"{result.project_path}/Makefile", "pyright") + # check the tox file + assert file_contains_text(f"{result.project_path}/tox.ini", "mypy") + assert not file_contains_text(f"{result.project_path}/tox.ini", "pyright") diff --git a/{{cookiecutter.project_name}}/.github/workflows/main.yml b/{{cookiecutter.project_name}}/.github/workflows/main.yml index 5dd2e55..cfcb02c 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/main.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/main.yml @@ -47,7 +47,7 @@ jobs: run: poetry run pytest tests --cov --cov-config=pyproject.toml --cov-report=xml - name: Check typing - run: poetry run mypy + run: poetry run {{ cookiecutter.typechecking }} {% if cookiecutter.codecov == "y" %} - name: Upload coverage reports to Codecov with GitHub Action on Python 3.11 uses: codecov/codecov-action@v4 diff --git a/{{cookiecutter.project_name}}/Makefile b/{{cookiecutter.project_name}}/Makefile index 02d67f3..42cbab9 100644 --- a/{{cookiecutter.project_name}}/Makefile +++ b/{{cookiecutter.project_name}}/Makefile @@ -11,8 +11,8 @@ check: ## Run code quality tools. @poetry check --lock @echo "🚀 Linting code: Running pre-commit" @poetry run pre-commit run -a - @echo "🚀 Static type checking: Running mypy" - @poetry run mypy + @echo "🚀 Static type checking: Running {{ cookiecutter.typechecking }}" + @poetry run {{ cookiecutter.typechecking }} {%- if cookiecutter.deptry == 'y' %} @echo "🚀 Checking for obsolete dependencies: Running deptry" @poetry run deptry . diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index f0c981a..7283173 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -21,7 +21,12 @@ pytest-cov = "^4.0.0" {% if cookiecutter.deptry == 'y' -%} deptry = "^0.16.2" {% endif -%} +{% if cookiecutter.typechecking == 'mypy' -%} mypy = "^1.5.1" +{% endif -%} +{% if cookiecutter.typechecking == 'pyright' -%} +pyright = "^1.1.382" +{% endif -%} pre-commit = "^3.4.0" tox = "^4.11.1" @@ -36,6 +41,7 @@ mkdocstrings = {extras = ["python"], version = "^0.26.1"} requires = ["poetry-core>=1.0.0"] build-backend = "poetry.core.masonry.api" +{% if cookiecutter.typechecking == 'mypy' -%} [tool.mypy] files = ["{{cookiecutter.project_slug}}"] disallow_untyped_defs = "True" @@ -45,6 +51,15 @@ check_untyped_defs = "True" warn_return_any = "True" warn_unused_ignores = "True" show_error_codes = "True" +{%- endif %} + +{% if cookiecutter.typechecking == 'pyright' -%} +[tool.pyright] +include = ["{{cookiecutter.project_slug}}"] +typeCheckingMode = "strict" +venvPath = "." +venv = ".venv" +{%- endif %} [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/{{cookiecutter.project_name}}/tox.ini b/{{cookiecutter.project_name}}/tox.ini index a44a21b..f694ef7 100644 --- a/{{cookiecutter.project_name}}/tox.ini +++ b/{{cookiecutter.project_name}}/tox.ini @@ -15,4 +15,4 @@ allowlist_externals = poetry commands = poetry install -v pytest --doctest-modules tests --cov --cov-config=pyproject.toml --cov-report=xml - mypy + {{cookiecutter.typechecking}}