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 %}