diff --git a/README.md b/README.md index 80ea1ce7..f055fb8c 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Starting development in My Package can be done with a single click by [opening M - 📦 Packaging and dependency management with [Poetry](https://github.com/python-poetry/poetry) - 🚚 Installing from and publishing to private package repositories and [PyPI](https://pypi.org/) - ⚡️ Task running with [Poe the Poet](https://github.com/nat-n/poethepoet) -- ✍️ Code formatting with [Black](https://github.com/psf/black) and [Ruff](https://github.com/charliermarsh/ruff) +- ✍️ Code formatting with [Ruff](https://github.com/charliermarsh/ruff) - ✅ Code linting with [Pre-commit](https://pre-commit.com/), [Mypy](https://github.com/python/mypy), and [Ruff](https://github.com/charliermarsh/ruff) - 🏷 Optionally follows the [Conventional Commits](https://www.conventionalcommits.org/) standard to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/) with [Commitizen](https://github.com/commitizen-tools/commitizen) - 💌 Verified commits with [GPG](https://gnupg.org/) @@ -32,6 +32,7 @@ Starting development in My Package can be done with a single click by [opening M ### Creating a new Python project To create a new Python project with this template: + 1. Install the latest [Cruft](https://github.com/cruft/cruft) and [Cookiecutter](https://github.com/cookiecutter/cookiecutter) in your [Python environment](https://github.com/pyenv/pyenv-virtualenv) with: ```sh pip install --upgrade cruft>=2.12.0 cookiecutter>=2.1.1 @@ -49,29 +50,30 @@ To create a new Python project with this template: ### Updating your Python project To update your Python project with the latest template: + 1. Run `cruft update` to update your project with the latest template. 2. If any of the updates failed, resolve them by inspecting the generated `.rej` files. ## 🤓 Template parameters -| Parameter | Description | -| ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `package_name`
"Spline Reticulator" | The name of the package. Will be slugified to `snake_case` for importing and `kebab-case` for installing. | -| `package_description`
"A Python package that reticulates splines." | A single-line description of the package. | -| `package_url`
"https://github.com/user/spline-reticulator" | The URL to the package's repository. | -| `author_name`
"John Smith" | The full name of the primary author of the package. | -| `author_email`
"john@example.com" | The email address of the primary author of the package. | -| `python_version`
"3.8" | The minimum Python version that the package requires. | -| `docker_image`
"python:$PYTHON_VERSION-slim" | The base Docker image to use for the Dev Container and application. The $PYTHON_VERSION build argument is equal to the `python_version` value by default, but may be overridden when building the image to test different Python versions. If CUDA support is required, you may use radixai/python-gpu:$PYTHON_VERSION-cuda11.8 ([see here for more information](https://github.com/radix-ai/python-gpu)). | -| `development_environment`
["simple", "strict"] | Whether to configure the development environment with a focus on simplicity or with a focus on strictness. In strict mode, additional [Ruff rules](https://beta.ruff.rs/docs/rules/) are added, and tools such as [Mypy](https://github.com/python/mypy) and [Pytest](https://github.com/pytest-dev/pytest) are set to strict mode. | -| `with_conventional_commits`
["0", "1"] | If "1", [Commitizen](https://github.com/commitizen-tools/commitizen) will verify that your commits follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. In return, `cz bump` may be used to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/). | -| `with_fastapi_api`
["0", "1"] | If "1", [FastAPI](https://github.com/tiangolo/fastapi) is added as a run time dependency, FastAPI API stubs and tests are added, a `poe api` command for serving the API is added, and an `app` stage that packages the API is added to the Dockerfile. Additionally, the CI workflow will push the application as a Docker image instead of publishing the Python package. | -| `with_jupyter_lab`
["0", "1"] | If "1", [JupyterLab](https://github.com/jupyterlab/jupyterlab) is added to Poetry's dev dependencies, and a `poe lab` command is added to start Jupyter Lab in the `notebooks/` directory. | -| `with_pydantic_typing`
["0", "1"] | If "1", [Pydantic](https://github.com/samuelcolvin/pydantic) is added as a run time dependency, and the [Pydantic mypy plugin](https://pydantic-docs.helpmanual.io/mypy_plugin/) is enabled and configured. | -| `with_sentry_logging`
["0", "1"] | If "1", [Sentry](https://github.com/getsentry/sentry-python) is added as a run time dependency, and a Sentry configuration stub and tests are added. | -| `with_streamlit_app`
["0", "1"] | If "1", [Streamlit](https://github.com/streamlit/streamlit) is added as a run time dependency, a Streamlit application stub is added, a `poe app` command to serve the Streamlit app is added, and an `app` stage that packages the Streamlit app is added to the Dockerfile. Additionally, the CI workflow will push the application as a Docker image instead of publishing the Python package. | -| `with_typer_cli`
["0", "1"] | If "1", [Typer](https://github.com/tiangolo/typer) is added as a run time dependency, Typer CLI stubs and tests are added, the package itself is registered as a CLI, and an `app` stage is added to the Dockerfile that packages the CLI. | -| `continuous_integration`
["GitHub", "GitLab"] | Whether to include a [GitHub Actions](https://docs.github.com/en/actions) or a [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) continuous integration workflow for testing and publishing the package or app. | -| `docstring_style`
["NumPy", "Google"] | Whether to use and validate [NumPy-style](https://numpydoc.readthedocs.io/en/latest/format.html) or [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings). | -| `private_package_repository_name`
"Private Package Repository" | Optional name of a private package repository to install packages from and publish this package to. | -| `private_package_repository_url`
"https://pypi.example.com/simple" | Optional URL of a private package repository to install packages from and publish this package to. Make sure to include the `/simple` suffix. For instance, when using a GitLab Package Registry this value should be of the form `https://gitlab.com/api/v4/projects/` `{project_id}` `/packages/pypi/simple`. | +| Parameter | Description | +| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `package_name`
"Spline Reticulator" | The name of the package. Will be slugified to `snake_case` for importing and `kebab-case` for installing. | +| `package_description`
"A Python package that reticulates splines." | A single-line description of the package. | +| `package_url`
"https://github.com/user/spline-reticulator" | The URL to the package's repository. | +| `author_name`
"John Smith" | The full name of the primary author of the package. | +| `author_email`
"john@example.com" | The email address of the primary author of the package. | +| `python_version`
"3.8" | The minimum Python version that the package requires. | +| `docker_image`
"python:$PYTHON_VERSION-slim" | The base Docker image to use for the Dev Container and application. The $PYTHON_VERSION build argument is equal to the `python_version` value by default, but may be overridden when building the image to test different Python versions. If CUDA support is required, you may use [radixai/python-gpu:$PYTHON_VERSION-cuda11.8](https://github.com/radix-ai/python-gpu)). | +| `development_environment`
["simple", "strict"] | Whether to configure the development environment with a focus on simplicity or with a focus on strictness. In strict mode, additional [Ruff rules](https://beta.ruff.rs/docs/rules/) are added, and tools such as [Mypy](https://github.com/python/mypy) and [Pytest](https://github.com/pytest-dev/pytest) are set to strict mode. | +| `with_conventional_commits`
["0", "1"] | If "1", [Commitizen](https://github.com/commitizen-tools/commitizen) will verify that your commits follow the [Conventional Commits](https://www.conventionalcommits.org/) standard. In return, `cz bump` may be used to automate [Semantic Versioning](https://semver.org/) and [Keep A Changelog](https://keepachangelog.com/). | +| `with_fastapi_api`
["0", "1"] | If "1", [FastAPI](https://github.com/tiangolo/fastapi) is added as a run time dependency, FastAPI API stubs and tests are added, a `poe api` command for serving the API is added, and an `app` stage that packages the API is added to the Dockerfile. Additionally, the CI workflow will push the application as a Docker image instead of publishing the Python package. | +| `with_jupyter_lab`
["0", "1"] | If "1", [JupyterLab](https://github.com/jupyterlab/jupyterlab) is added to Poetry's dev dependencies, and a `poe lab` command is added to start Jupyter Lab in the `notebooks/` directory. | +| `with_pydantic_typing`
["0", "1"] | If "1", [Pydantic](https://github.com/samuelcolvin/pydantic) is added as a run time dependency, and the [Pydantic mypy plugin](https://pydantic-docs.helpmanual.io/mypy_plugin/) is enabled and configured. | +| `with_sentry_logging`
["0", "1"] | If "1", [Sentry](https://github.com/getsentry/sentry-python) is added as a run time dependency, and a Sentry configuration stub and tests are added. | +| `with_streamlit_app`
["0", "1"] | If "1", [Streamlit](https://github.com/streamlit/streamlit) is added as a run time dependency, a Streamlit application stub is added, a `poe app` command to serve the Streamlit app is added, and an `app` stage that packages the Streamlit app is added to the Dockerfile. Additionally, the CI workflow will push the application as a Docker image instead of publishing the Python package. | +| `with_typer_cli`
["0", "1"] | If "1", [Typer](https://github.com/tiangolo/typer) is added as a run time dependency, Typer CLI stubs and tests are added, the package itself is registered as a CLI, and an `app` stage is added to the Dockerfile that packages the CLI. | +| `continuous_integration`
["GitHub", "GitLab"] | Whether to include a [GitHub Actions](https://docs.github.com/en/actions) or a [GitLab CI/CD](https://docs.gitlab.com/ee/ci/) continuous integration workflow for testing and publishing the package or app. | +| `docstring_style`
["NumPy", "Google"] | Whether to use and validate [NumPy-style](https://numpydoc.readthedocs.io/en/latest/format.html) or [Google-style docstrings](https://google.github.io/styleguide/pyguide.html#38-comments-and-docstrings). | +| `private_package_repository_name`
"Private Package Repository" | Optional name of a private package repository to install packages from and publish this package to. | +| `private_package_repository_url`
"https://pypi.example.com/simple" | Optional URL of a private package repository to install packages from and publish this package to. Make sure to include the `/simple` suffix. For instance, when using a GitLab Package Registry this value should be of the form `https://gitlab.com/api/v4/projects/` `{project_id}` `/packages/pypi/simple`. | diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.devcontainer/devcontainer.json b/{{ cookiecutter.__package_name_kebab_case }}/.devcontainer/devcontainer.json index 055f6f4c..b36cc5aa 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/.devcontainer/devcontainer.json +++ b/{{ cookiecutter.__package_name_kebab_case }}/.devcontainer/devcontainer.json @@ -10,7 +10,11 @@ "vscode": { "extensions": [ "charliermarsh.ruff", + "ms-python.mypy-type-checker", "ms-python.python", + {%- if cookiecutter.with_jupyter_lab|int %} + "ms-toolsai.jupyter", + {%- endif %} "ryanluker.vscode-coverage-gutters", "tamasfe.even-better-toml", "visualstudioexptteam.vscodeintellicode" @@ -24,6 +28,9 @@ "source.organizeImports": true }, "editor.formatOnSave": true, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff" + }, "[toml]": { "editor.formatOnSave": false }, @@ -31,9 +38,8 @@ 100 ], "files.autoSave": "onFocusChange", + "mypy-type-checker.importStrategy": "fromEnvironment", "python.defaultInterpreterPath": "/opt/{{ cookiecutter.__package_name_kebab_case }}-env/bin/python", - "python.formatting.provider": "black", - "python.linting.mypyEnabled": true, "python.terminal.activateEnvironment": false, "python.testing.pytestEnabled": true, "ruff.importStrategy": "fromEnvironment", diff --git a/{{ cookiecutter.__package_name_kebab_case }}/.pre-commit-config.yaml b/{{ cookiecutter.__package_name_kebab_case }}/.pre-commit-config.yaml index 662b614a..e9754304 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/.pre-commit-config.yaml +++ b/{{ cookiecutter.__package_name_kebab_case }}/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: hooks: - id: check-useless-excludes - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.9.0 + rev: v1.10.0 hooks: - id: python-check-mock-methods - id: python-use-type-annotations @@ -16,7 +16,7 @@ repos: - id: rst-inline-touching-normal - id: text-unicode-replacement-char - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.5.0 hooks: - id: check-added-large-files - id: check-ast @@ -56,19 +56,20 @@ repos: language: system stages: [commit-msg] {%- endif %} - - id: ruff - name: ruff - entry: ruff - args: ["--extend-fixable={% if cookiecutter.development_environment == "strict" %}ERA001,F401,F841,T201,T203{% else %}F401,F841{% endif %}"{% if cookiecutter.development_environment == "simple" %}, "--fix-only"{% endif %}] + - id: ruff-check + name: ruff check + entry: ruff check + args: ["--force-exclude", "--extend-fixable={% if cookiecutter.development_environment == "strict" %}ERA001,F401,F841,T201,T203{% else %}F401,F841{% endif %}"{% if cookiecutter.development_environment == "simple" %}, "--fix-only"{% endif %}] require_serial: true language: system - types: [python] - - id: black - name: black - entry: black + types_or: [python, pyi] + - id: ruff-format + name: ruff format + entry: ruff format + args: [--force-exclude] require_serial: true language: system - types: [python] + types_or: [python, pyi] {%- if cookiecutter.development_environment == "strict" %} - id: shellcheck name: shellcheck diff --git a/{{ cookiecutter.__package_name_kebab_case }}/pyproject.toml b/{{ cookiecutter.__package_name_kebab_case }}/pyproject.toml index e5679f27..f7ed8d26 100644 --- a/{{ cookiecutter.__package_name_kebab_case }}/pyproject.toml +++ b/{{ cookiecutter.__package_name_kebab_case }}/pyproject.toml @@ -51,11 +51,6 @@ uvicorn = { extras = ["standard"], version = ">=0.20.0" } {%- endif %} [tool.poetry.group.test.dependencies] # https://python-poetry.org/docs/master/managing-dependencies/ -{%- if cookiecutter.with_jupyter_lab|int %} -black = { extras = ["jupyter"], version = ">=23.3.0" } -{%- else %} -black = ">=23.3.0" -{%- endif %} {%- if cookiecutter.with_conventional_commits|int %} commitizen = ">=3.2.1" {%- endif %} @@ -69,7 +64,7 @@ pytest = ">=7.3.1" pytest-clarity = ">=1.0.1" pytest-mock = ">=3.10.0" pytest-xdist = ">=3.2.1" -ruff = ">=0.0.270" +ruff = ">=0.1.3" {%- if cookiecutter.development_environment == "strict" %} safety = ">=2.3.4,!=2.3.5" shellcheck-py = ">=0.9.0" @@ -89,10 +84,6 @@ name = "{{ cookiecutter.private_package_repository_name|slugify }}" url = "{{ cookiecutter.private_package_repository_url }}" {%- endif %} -[tool.black] # https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file -line-length = 100 -target-version = ["py{{ cookiecutter.python_version.split('.')[:2]|join }}"] - [tool.coverage.report] # https://coverage.readthedocs.io/en/latest/config.html#report {%- if cookiecutter.development_environment == "strict" %} fail_under = 50 @@ -148,11 +139,11 @@ fix = true ignore-init-module-imports = true line-length = 100 {%- if cookiecutter.development_environment == "strict" %} -select = ["A", "ASYNC", "B", "BLE", "C4", "C90", "D", "DTZ", "E", "EM", "ERA", "F", "FLY", "G", "I", "ICN", "INP", "ISC", "N", "NPY", "PGH", "PIE", "PLC", "PLE", "PLR", "PLW", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "S", "SIM", "SLF", "T10", "T20", "TCH", "TID", "TRY", "UP", "W", "YTT"] +select = ["A", "ASYNC", "B", "BLE", "C4", "C90", "D", "DTZ", "E", "EM", "ERA", "F", "FBT", "FLY", "FURB", "G", "I", "ICN", "INP", "INT", "ISC", "LOG", "N", "NPY", "PERF", "PGH", "PIE", "PLC", "PLE", "PLR", "PLW", "PT", "PTH", "PYI", "RET", "RSE", "RUF", "S", "SIM", "SLF", "SLOT", "T10", "T20", "TCH", "TID", "TRY", "UP", "W", "YTT"] ignore = ["E501", "PGH001", "RET504", "S101"] unfixable = ["ERA001", "F401", "F841", "T201", "T203"] {%- else %} -select = ["A", "ASYNC", "B", "C4", "C90", "D", "DTZ", "E", "F", "FLY", "I", "ISC", "N", "NPY", "PGH", "PIE", "PLC", "PLE", "PLR", "PLW", "PT", "RET", "RUF", "RSE", "SIM", "TID", "UP", "W", "YTT"] +select = ["A", "ASYNC", "B", "C4", "C90", "D", "DTZ", "E", "F", "FLY", "FURB", "I", "ISC", "LOG", "N", "NPY", "PERF", "PGH", "PIE", "PLC", "PLE", "PLR", "PLW", "PT", "RET", "RUF", "RSE", "SIM", "TID", "UP", "W", "YTT"] ignore = ["E501", "PGH001", "PGH002", "PGH003", "RET504", "S101"] unfixable = ["F401", "F841"] {%- endif %}