From 8abce5478f2b7d204322364ee29cd86105a53d6d Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Wed, 17 Dec 2025 10:31:10 +0100 Subject: [PATCH 01/15] use copy without render for justfiles --- cookiecutter.json | 22 +++--- .../.just/bandit.justfile | 3 +- .../.just/dir-structure.justfile | 3 +- .../.just/dotenv.justfile | 3 +- .../.just/ipython.justfile | 3 +- .../.just/just.justfile | 3 +- .../.just/mypy.justfile | 3 +- .../.just/pre-commit.justfile | 3 +- .../.just/project.justfile | 3 +- .../.just/pylint.justfile | 3 +- .../.just/pyroma.justfile | 3 +- .../.just/pytest.justfile | 3 +- .../.just/readthedocs.justfile | 3 +- .../.just/ruff.justfile | 3 +- .../.just/safety.justfile | 3 +- .../.just/sshx.justfile | 3 +- .../.just/ty.justfile | 4 +- .../.just/ubuntu.justfile | 3 +- .../.just/uv.justfile | 3 +- {{cookiecutter.project_name}}/pyproject.toml | 68 +------------------ 20 files changed, 29 insertions(+), 116 deletions(-) diff --git a/cookiecutter.json b/cookiecutter.json index d8cff6a..41d200b 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -12,20 +12,10 @@ "version": "0.1", "copyright_year": "{% now 'utc', '%Y' %}", "license": [ - "MIT", - // "Apache-2.0", - // "BSD-3-Clause", - // "GPL-3.0-or-later", - // "LGPL-3.0-or-later", + "MIT" ], "development_status": [ - // "Development Status :: 1 - Planning", - // "Development Status :: 2 - Pre-Alpha", - // "Development Status :: 3 - Alpha", - "Development Status :: 4 - Beta", - // "Development Status :: 5 - Production/Stable", - // "Development Status :: 6 - Mature", - // "Development Status :: 7 - Inactive" + "Development Status :: 4 - Beta" ], "minimum_python": "3.13.0", "with_django": "0", @@ -34,5 +24,9 @@ "with_cyclopts": "1", "with_postgres": "0", "support_rtd": "0", - "__package_name_snake_case": "{{ cookiecutter.package_name|slugify(separator='_') }}" -} \ No newline at end of file + "__package_name_snake_case": "{{ cookiecutter.package_name|slugify(separator='_') }}", + "_copy_without_render": [ + "*.justfile", + ".just/*.justfile" + ] +} diff --git a/{{cookiecutter.project_name}}/.just/bandit.justfile b/{{cookiecutter.project_name}}/.just/bandit.justfile index b36e6c2..a7d16d9 100644 --- a/{{cookiecutter.project_name}}/.just/bandit.justfile +++ b/{{cookiecutter.project_name}}/.just/bandit.justfile @@ -1,5 +1,5 @@ # bandit, see ../justfile -{% raw -%} + # show which bandit is used [group: 'bandit'] @@ -27,4 +27,3 @@ bandit-html: bandit-update-baseline: .venv/bin/bandit --configfile pyproject.toml --recursive . --format json --output etc/bandit-baseline.json -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/dir-structure.justfile b/{{cookiecutter.project_name}}/.just/dir-structure.justfile index 02778b2..8a3c285 100644 --- a/{{cookiecutter.project_name}}/.just/dir-structure.justfile +++ b/{{cookiecutter.project_name}}/.just/dir-structure.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # See ../justfile # # - justfile_directory() is the directory of the toplevel justfile @@ -71,4 +71,3 @@ clean: clean-symlinks clean-venv clean-pyhon-cache-files alias clear := clean -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/dotenv.justfile b/{{cookiecutter.project_name}}/.just/dotenv.justfile index 4e52a90..d127889 100644 --- a/{{cookiecutter.project_name}}/.just/dotenv.justfile +++ b/{{cookiecutter.project_name}}/.just/dotenv.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # dotenv env_file := justfile_directory() / ".env" @@ -55,4 +55,3 @@ show-dotenv: @ echo -e "Following environment variables are defined in the {{env_file}}:" @ cat .env -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/ipython.justfile b/{{cookiecutter.project_name}}/.just/ipython.justfile index cfd2d05..7c0abeb 100644 --- a/{{cookiecutter.project_name}}/.just/ipython.justfile +++ b/{{cookiecutter.project_name}}/.just/ipython.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # ipython @@ -37,4 +37,3 @@ alias ipython-debug := ipython-shell-debug ipython-create-profile name="": .venv/bin/ipython profile create {{name}} -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/just.justfile b/{{cookiecutter.project_name}}/.just/just.justfile index 78ae983..2e61c2e 100644 --- a/{{cookiecutter.project_name}}/.just/just.justfile +++ b/{{cookiecutter.project_name}}/.just/just.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # just @@ -170,4 +170,3 @@ just-check: # Invoke-WebRequest -Uri "https://just.systems/install.sh" -UseBasicParsing | Invoke-Expression -ArgumentList "--to $DEST" -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/mypy.justfile b/{{cookiecutter.project_name}}/.just/mypy.justfile index 02613c8..996238a 100644 --- a/{{cookiecutter.project_name}}/.just/mypy.justfile +++ b/{{cookiecutter.project_name}}/.just/mypy.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # mypy @@ -28,4 +28,3 @@ mypy-html: mypy-report -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/pre-commit.justfile b/{{cookiecutter.project_name}}/.just/pre-commit.justfile index f19564d..e02dfa5 100644 --- a/{{cookiecutter.project_name}}/.just/pre-commit.justfile +++ b/{{cookiecutter.project_name}}/.just/pre-commit.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # See ../justfile @@ -43,4 +43,3 @@ pre-commit-run-files: pre-commit-which alias precommit-run-files := pre-commit-run-files -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/project.justfile b/{{cookiecutter.project_name}}/.just/project.justfile index 0f860a5..dea8ef6 100644 --- a/{{cookiecutter.project_name}}/.just/project.justfile +++ b/{{cookiecutter.project_name}}/.just/project.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # project @@ -6,4 +6,3 @@ [group: 'project'] install: create-dirs dotenv-install uv-sync-all-groups && symlink-venv-dirs -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/pylint.justfile b/{{cookiecutter.project_name}}/.just/pylint.justfile index ee69e8b..f71c834 100644 --- a/{{cookiecutter.project_name}}/.just/pylint.justfile +++ b/{{cookiecutter.project_name}}/.just/pylint.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # pylint @@ -25,4 +25,3 @@ pylint-src args="": pylint-which pylint-tests args="": pylint-which - pylint tests/ {{args}} -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/pyroma.justfile b/{{cookiecutter.project_name}}/.just/pyroma.justfile index 2fb77fb..0d69e1f 100644 --- a/{{cookiecutter.project_name}}/.just/pyroma.justfile +++ b/{{cookiecutter.project_name}}/.just/pyroma.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # See ../justfile @@ -13,4 +13,3 @@ pyroma-which: pyroma: pyroma-which - pyroma . -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/pytest.justfile b/{{cookiecutter.project_name}}/.just/pytest.justfile index ddadd9b..ee5e316 100644 --- a/{{cookiecutter.project_name}}/.just/pytest.justfile +++ b/{{cookiecutter.project_name}}/.just/pytest.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # pytest @@ -48,4 +48,3 @@ alias pytest-lf-pdf := pytest-pdb-failed pytest-filter filter args="": .venv/bin/pytest tests --color=yes -k {{filter}} {{args}} -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/readthedocs.justfile b/{{cookiecutter.project_name}}/.just/readthedocs.justfile index 3ccece9..68e411e 100644 --- a/{{cookiecutter.project_name}}/.just/readthedocs.justfile +++ b/{{cookiecutter.project_name}}/.just/readthedocs.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # readthedocs, see ../justfile # readthedocs already provisions a virtualenv for us. @@ -9,4 +9,3 @@ install-rtd: - uv sync --only docs -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/ruff.justfile b/{{cookiecutter.project_name}}/.just/ruff.justfile index c5f2ce7..e0fccb7 100644 --- a/{{cookiecutter.project_name}}/.just/ruff.justfile +++ b/{{cookiecutter.project_name}}/.just/ruff.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # ruff @@ -46,4 +46,3 @@ alias ruff-fix-unsafe := ruff-check-fix-unsafe ruff-format args="": - {{RUFF_EXE}} format docs/ etc/ src/ tests/ {{args}} -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/safety.justfile b/{{cookiecutter.project_name}}/.just/safety.justfile index 9f7c78e..fb72c92 100644 --- a/{{cookiecutter.project_name}}/.just/safety.justfile +++ b/{{cookiecutter.project_name}}/.just/safety.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # safety @@ -20,4 +20,3 @@ safety-check-html: @ mkdir -p var/html/safety safety check --save-html var/html/safety/safety.html -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/sshx.justfile b/{{cookiecutter.project_name}}/.just/sshx.justfile index 832695f..b84c868 100644 --- a/{{cookiecutter.project_name}}/.just/sshx.justfile +++ b/{{cookiecutter.project_name}}/.just/sshx.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # sshx @@ -30,4 +30,3 @@ sshx-run: [unix] sshx: sshx-install sshx-run -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/ty.justfile b/{{cookiecutter.project_name}}/.just/ty.justfile index 3c763b6..99a7843 100644 --- a/{{cookiecutter.project_name}}/.just/ty.justfile +++ b/{{cookiecutter.project_name}}/.just/ty.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # ty @@ -13,4 +13,4 @@ ty-check args="": - .venv/bin/ty check src/ tests/ {{args}} -{%- endraw %} + diff --git a/{{cookiecutter.project_name}}/.just/ubuntu.justfile b/{{cookiecutter.project_name}}/.just/ubuntu.justfile index c89b27a..3b94dad 100644 --- a/{{cookiecutter.project_name}}/.just/ubuntu.justfile +++ b/{{cookiecutter.project_name}}/.just/ubuntu.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # ubuntu @@ -83,4 +83,3 @@ shellcheck-install target_dir="~/bin": chmod u+x {{target_dir}}/shellcheck -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/uv.justfile b/{{cookiecutter.project_name}}/.just/uv.justfile index 734b47b..e281a6b 100644 --- a/{{cookiecutter.project_name}}/.just/uv.justfile +++ b/{{cookiecutter.project_name}}/.just/uv.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # uv @@ -116,4 +116,3 @@ uv-set-python-version version="3.10": @ echo -e "Set python version to {{version}}" -{%- endraw %} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 498faba..a39bd49 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -126,17 +126,9 @@ classifiers = [ "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: POSIX :: Linux", - {%- for v in reversed(range(cookiecutter.minimum_python|int, 15)) %} + {%- for v in range(cookiecutter.minimum_python.split('.')[1]|int, 15)|reverse %} "Programming Language :: Python :: 3.{{ v }}", {%- endfor %} - - # "Programming Language :: Python :: 3.14", - # "Programming Language :: Python :: 3.13", - # "Programming Language :: Python :: 3.12", - # "Programming Language :: Python :: 3.11", - # "Programming Language :: Python :: 3.10", - # "Programming Language :: Python :: 3.9", - # "Programming Language :: Python :: {{cookiecutter.minimum_python}}", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", "Topic :: Software Development :: Libraries", @@ -146,8 +138,6 @@ classifiers = [ - - dependencies = [ "autoread-dotenv>=1.0.3", @@ -251,53 +241,6 @@ webtesting = [ ] - - -# [tool.poetry.group.dev.dependencies] -# absolufy-imports = ">=0.3" -# bandit = ">=1.7" -# black = { version = ">=22.0", allow-prereleases = true } -# flake8 = ">=4.0" -# flake8-bugbear = ">=23.2" -# flake8-docstrings = ">=1.6" -# flake8-pyproject = ">=1.2" -# flake8-pytest-style = ">=1.6" -# flake8-rst-docstrings = ">=0.2" -# isort = ">=5.10" -# pep8-naming = ">=0.12" -# pre-commit = ">=2.14" -# pre-commit-hooks = ">=4.1" -# pydocstyle = ">=6.1" -# pylint = ">=2.12" -# ruff = ">=0.0" -# safety = ">=1.10" - - - - - - - - -# [tool.poetry.group.testing.dependencies] -# coverage = { extras = ["toml"], version = ">=6.2" } -# hypothesis = ">=6.72" -# nox = ">=2022.11" -# nox-poetry = ">=1.0" -# pytest = ">=7.0" -# pytest-clarity = ">=1.0" -# pytest-codecov = ">=0.5" -# pytest-cov = ">=3.0" -# {%- if cookiecutter.with_cyclopts|int %} -# pytest-click = ">=1.1" -# {%- endif %} -# pytest-mock = ">=3.6" -# pytest-xdist = ">=3.2" -# tox = ">=4.4" -# xdoctest = { extras = ["colors"], version = ">=0.15" } - - - [project.scripts] {{cookiecutter.project_name}} = "{{cookiecutter.package_name}}.cli:app" @@ -311,9 +254,6 @@ repository = "https://github.com/{{cookiecutter.github_user}}/{{cookiecutter.pro {% if cookiecutter.support_rtd|int == 0 %}documentation = "https://github.com/{{cookiecutter.github_user}}/{{cookiecutter.project_name}}"{% endif %} - - - [tool.pylint.format] max-line-length = 120 good-names = [ @@ -338,11 +278,6 @@ testpaths = ["tests"] addopts = "--color=yes --junit-xml='var/coverage/pytest.xml'" - - - - - [tool.ruff] # https://docs.astral.sh/ruff/configuration/ cache-dir = "var/cache/ruff" # relative to project_root @@ -391,6 +326,7 @@ known-local-folder = ["_helpers"] [tool.uv] managed = true package = true +default-groups = "all" default-groups = [ # "dev", # "ipython", From af6a89f29d03b83c8486deb7367908f38ed92a1c Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Wed, 17 Dec 2025 10:36:12 +0100 Subject: [PATCH 02/15] fix: logging config --- .../etc/logging.yaml | 70 +++++-------------- 1 file changed, 19 insertions(+), 51 deletions(-) diff --git a/{{cookiecutter.project_name}}/etc/logging.yaml b/{{cookiecutter.project_name}}/etc/logging.yaml index 5f65f53..5b903c2 100644 --- a/{{cookiecutter.project_name}}/etc/logging.yaml +++ b/{{cookiecutter.project_name}}/etc/logging.yaml @@ -89,38 +89,14 @@ formatters: ERROR: red CRITICAL: purple - console_http: # suited for black background - "()": "demo_flask.formatter.HttpFormatter" - datefmt: "%Y/%m/%d %H:%M:%S" - format: "{asctime} - {name} - {levelname:<7} - {message}" - style: "{" - - console_color_http: # suited for black background - "()": "demo_flask.formatter.ColorHttpFormatter" - datefmt: "%Y/%m/%d %H:%M:%S" - format: "{log_color}{asctime} - {name} - {levelname:<7} - {message}{reset}" - reset: True - style: "{" - log_colors: - # cfr. https://github.com/borntyping/python-colorlog - # supported colors: blue|cyan|green|yellow|red|purple|white|black - # supported prefixes: "bold_" and "bg_" - # : , - # comma is the separator, but no extra spaces allowed - DEBUG: bold_cyan # bold_cyan,bg_white - INFO: bold_green - WARNING: yellow - ERROR: red - CRITICAL: purple - - uvicorn_default: # see uvicorn/config.py - "()": "uvicorn.logging.DefaultFormatter" - datefmt: "%Y-%m-%d %H:%M:%S" - format: "%(levelprefix)s %(asctime)s %(message)s" + # uvicorn_default: # see uvicorn/config.py + # "()": "uvicorn.logging.DefaultFormatter" + # datefmt: "%Y-%m-%d %H:%M:%S" + # format: "%(levelprefix)s %(asctime)s %(message)s" - uvicorn_access: # see uvicorn/config.py - "()": "uvicorn.logging.AccessFormatter" - format: '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s' + # uvicorn_access: # see uvicorn/config.py + # "()": "uvicorn.logging.AccessFormatter" + # format: '%(levelprefix)s %(client_addr)s - "%(request_line)s" %(status_code)s' handlers: # Generally there is no need to change to loglevel specified on the handlers. @@ -247,20 +223,12 @@ loggers: dotenv: level: !env LOGLEVEL_DOTENV, NOTSET - flask_cors: - level: !env LOGLEVEL_FLASK_CORS, NOTSET - - gunicorn: - level: !env LOGLEVEL_GUNICORN, NOTSET + # gunicorn: + # level: !env LOGLEVEL_GUNICORN, NOTSET http.client: level: !env LOGLEVEL_HTTP_CLIENT, NOTSET - httplogger: - level: !env LOGLEVEL_HTTPLOGGER, NOTSET - handlers: - - console_http - ipython-startup: level: !env LOGLEVEL_IPYTHON _STARTUP, NOTSET propagate: false @@ -304,18 +272,18 @@ loggers: urllib3.util.retry: level: !env LOGLEVEL_URLLIB3_UTIL_RETRY, NOTSET - uvicorn: - level: !env LOGLEVEL_UVICORN, NOTSET - # handlers: - # - uvicorn_default + # uvicorn: + # level: !env LOGLEVEL_UVICORN, NOTSET + # handlers: + # - uvicorn_default - uvicorn_access: - level: !env LOGLEVEL_UVICORN_ACCESS, NOTSET - handlers: - - uvicorn_access + # uvicorn_access: + # level: !env LOGLEVEL_UVICORN_ACCESS, NOTSET + # handlers: + # - uvicorn_access - uvicorn_error: # confusing logger-name, see https://github.com/encode/uvicorn/issues/562 - level: !env LOGLEVEL_UVICORN_ERROR, NOTSET + # uvicorn_error: # confusing logger-name, see https://github.com/encode/uvicorn/issues/562 + # level: !env LOGLEVEL_UVICORN_ERROR, NOTSET watchfiles: level: !env LOGLEVEL_WATCHFILES, NOTSET From ed6df16109789852d4071c77b82ec24018fd71a0 Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Wed, 17 Dec 2025 11:02:07 +0100 Subject: [PATCH 03/15] fix: about-section --- .../{{cookiecutter.package_name}}/__init__.py | 8 +++-- .../{{cookiecutter.package_name}}/_about.py | 29 ++++++++++++------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py index af1ae0e..45e35e2 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py @@ -1,11 +1,13 @@ """{{cookiecutter.package_name}}.""" -from {{cookiecutter.package_name}}._about import __author__, __author_email__, __copyright__, __license__, __version__ +from {{cookiecutter.package_name}}._about import ( + authors as __author__, + license_ as __license__, + version as __version__, +) __all__: list[str] = [ "__author__", - "__author_email__", - "__copyright__", "__license__", "__version__", ] diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/_about.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/_about.py index 91d41f9..5529844 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/_about.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/_about.py @@ -24,22 +24,31 @@ pkginfo = {} -__metadata__: dict = pkginfo -__author__: str | list[str] = pkginfo.get("author", "unknown") +authors: str | list[str] = pkginfo.get("author_email", "unknown") -__author_email__: str | list[str] = pkginfo.get("author_email", "unknown") +license_: str | list[str] = pkginfo.get("license_expression") or pkginfo.get("license", "unknown") or "unknown" -__copyright__: str | list[str] = pkginfo.get("license", "unknown") +version: str | list[str] = pkginfo.get("version", "unknown") -__description__: str | list[str] = pkginfo.get("summary", "unknown") -__license__: str | list[str] = pkginfo.get("license", "unknown") -__pkg_name__: str | list[str] = pkginfo.get("name", "unknown") +# __metadata__: dict = pkginfo -__readme__: str | list[str] = pkginfo.get("description", "unknown") +# __author__: str | list[str] = pkginfo.get("author", "unknown") -__url__: str | list[str] = pkginfo.get("project_url", "unknown") +# __author_email__: str | list[str] = pkginfo.get("author_email", "unknown") -__version__: str | list[str] = pkginfo.get("version", "unknown") +# __copyright__: str | list[str] = pkginfo.get("license", "unknown") + +# __description__: str | list[str] = pkginfo.get("summary", "unknown") + +# __license__: str | list[str] = pkginfo.get("license", "unknown") + +# __pkg_name__: str | list[str] = pkginfo.get("name", "unknown") + +# __readme__: str | list[str] = pkginfo.get("description", "unknown") + +# __url__: str | list[str] = pkginfo.get("project_url", "unknown") + +# __version__: str | list[str] = pkginfo.get("version", "unknown") From 19ddc97b3c578475d41b12d04714699466f06eec Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Wed, 17 Dec 2025 11:04:05 +0100 Subject: [PATCH 04/15] feat: add starter CLAUDE file --- .../.claude/CLAUDE.md | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 {{cookiecutter.project_name}}/.claude/CLAUDE.md diff --git a/{{cookiecutter.project_name}}/.claude/CLAUDE.md b/{{cookiecutter.project_name}}/.claude/CLAUDE.md new file mode 100644 index 0000000..ca619b3 --- /dev/null +++ b/{{cookiecutter.project_name}}/.claude/CLAUDE.md @@ -0,0 +1,35 @@ + +# Python Package Management with uv + +Use uv exclusively for Python package management in this project. + +## Package Management Commands + +- All Python dependencies **must be installed, synchronized, and locked** using uv +- Never use pip, pip-tools, poetry, or conda directly for dependency management + +Use these commands: + +- Install dependencies: `uv add ` +- Remove dependencies: `uv remove ` +- Sync dependencies: `uv sync` + +## Running Python Code + +- Run a Python script with `uv run .py` +- Run Python tools like Pytest with `uv run pytest` or `uv run ruff` +- Launch a Python repl with `uv run python` + +## Managing Scripts with PEP 723 Inline Metadata + +- Run a Python script with inline metadata (dependencies defined at the top of the file) with: `uv run script.py` +- You can add or remove dependencies manually from the `dependencies =` section at the top of the script, or +- Or using uv CLI: + - `uv add package-name --script script.py` + - `uv remove package-name --script script.py` + + + +# References +- https://pydevtools.com/handbook/how-to/how-to-configure-claude-code-to-use-uv/ + From 57c5f0c7fcee51a271a64654f37eb4145e5d36b7 Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Fri, 26 Dec 2025 19:23:30 +0100 Subject: [PATCH 05/15] support windows --- .../.just/dotenv.justfile | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/{{cookiecutter.project_name}}/.just/dotenv.justfile b/{{cookiecutter.project_name}}/.just/dotenv.justfile index d127889..6f1b8de 100644 --- a/{{cookiecutter.project_name}}/.just/dotenv.justfile +++ b/{{cookiecutter.project_name}}/.just/dotenv.justfile @@ -20,6 +20,20 @@ dotenv-install-from-template: + +[group: 'dotenv'] +[windows] +dotenv-install-from-template: + #!pwsh + if (Test-Path .env) { + Write-Host ".env file already exists. Not overwriting it.`n" + } else { + Write-Host "Copying .env.template to .env" + Copy-Item .env.template .env + } + + + # # replace placeholder __CWD__ with current working directory # [group: 'dotenv'] # dotenv-set-basedir: @@ -42,6 +56,22 @@ dotenv-set-basedir: fi +[group: 'dotenv'] +[windows] +dotenv-set-basedir: + #!pwsh + if (Test-Path .env) { + $currentDir = (Get-Location).Path + Write-Host "Replacing string __CWD__ with current directory $currentDir in .env file." + Copy-Item .env .env.backup + (Get-Content .env) -replace '__CWD__', $currentDir | Set-Content .env + Write-Host ".env updated successfully. Please review any credentials.`n" + } else { + Write-Error "Error: .env file not found!`n" + exit 1 + } + + # install .env-file from .env.template [group: 'dotenv'] dotenv-install: dotenv-install-from-template dotenv-set-basedir From 5a43c8888a23f0a6344dbe8fe1d3acf76a4237a7 Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Sun, 28 Dec 2025 11:14:46 +0100 Subject: [PATCH 06/15] fixes for windows --- .../.just/dir-structure.justfile | 6 +++--- {{cookiecutter.project_name}}/.just/mutmut.justfile | 8 -------- {{cookiecutter.project_name}}/.just/sshx.justfile | 2 +- {{cookiecutter.project_name}}/.just/uv.justfile | 2 -- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/{{cookiecutter.project_name}}/.just/dir-structure.justfile b/{{cookiecutter.project_name}}/.just/dir-structure.justfile index 8a3c285..0d9eb0e 100644 --- a/{{cookiecutter.project_name}}/.just/dir-structure.justfile +++ b/{{cookiecutter.project_name}}/.just/dir-structure.justfile @@ -13,8 +13,8 @@ export PATH := if os_family() == "windows" { venv_bin_dir + x";${PATH}" } else { [group: 'dir-structure'] create-dirs: # vscode does not create cache-dirs, so we need to create it - @ echo -e "In current working dir: ${PWD}" - @ echo -e "Creating project directory-structure:" + @ echo "In current working dir: ${PWD}" + @ echo "Creating project directory-structure:" mkdir -p var mkdir -p var/cache mkdir -p var/cache/mypy @@ -23,7 +23,7 @@ create-dirs: mkdir -p var/log mkdir -p var/run mkdir -p var/tmp - @ echo -e "" + @ echo "" # symlinks to venv-dirs to make bin/python work diff --git a/{{cookiecutter.project_name}}/.just/mutmut.justfile b/{{cookiecutter.project_name}}/.just/mutmut.justfile index d1e3396..5d46c42 100644 --- a/{{cookiecutter.project_name}}/.just/mutmut.justfile +++ b/{{cookiecutter.project_name}}/.just/mutmut.justfile @@ -3,7 +3,6 @@ # install mutmut [group: 'mutmut'] -[unix] mutmut-install: uv ad mutmut @@ -12,49 +11,42 @@ alias install-mutmut := mutmut-install # display version of mutmut [group: 'mutmut'] -[unix] mutmut-version: mutmut --version # display help for mutmut [group: 'mutmut'] -[unix] mutmut-help: mutmut --help # run mutmut [group: 'mutmut'] -[unix] mutmut-run: mutmut run # browse mutmut results [group: 'mutmut'] -[unix] mutmut-browse: mutmut browse # show mutmut results [group: 'mutmut'] -[unix] mutmut-results: mutmut results # apply mutant by name [group: 'mutmut'] -[unix] mutmut-apply name="": mutmut apply {{name}} # test specific mutant by name [group: 'mutmut'] -[unix] mutmut-test-for-mutant name="": mutmut tests-for-mutant {{name}} diff --git a/{{cookiecutter.project_name}}/.just/sshx.justfile b/{{cookiecutter.project_name}}/.just/sshx.justfile index b84c868..ca913d3 100644 --- a/{{cookiecutter.project_name}}/.just/sshx.justfile +++ b/{{cookiecutter.project_name}}/.just/sshx.justfile @@ -8,7 +8,7 @@ sshx-install: curl -sSf https://sshx.io/get | sh -alias install-sshx := sshx-install +# alias install-sshx := sshx-install # display version of sshx diff --git a/{{cookiecutter.project_name}}/.just/uv.justfile b/{{cookiecutter.project_name}}/.just/uv.justfile index e281a6b..0b3c63b 100644 --- a/{{cookiecutter.project_name}}/.just/uv.justfile +++ b/{{cookiecutter.project_name}}/.just/uv.justfile @@ -99,7 +99,6 @@ uv-publish path="dist/" args="": # export uv-defined requirements to a pip-installable requirements-file [group: 'uv'] -[unix] uv-export-requirements: uv export --format requirements-txt --no-hashes --output-file etc/requirements.txt @ echo -e "Updated etc/requirements.txt" @@ -109,7 +108,6 @@ alias uv-export := uv-export-requirements # set python-version in .python-version file [group: 'uv'] -[unix] uv-set-python-version version="3.10": mv .python-version .python-version.backup @ echo "{{version}}" > .python-version From 35449a6ad7a5d1e4758d40c70e86b300d1bfefb7 Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Sun, 28 Dec 2025 11:15:11 +0100 Subject: [PATCH 07/15] use single quotes for .env --- {{cookiecutter.project_name}}/.env.template | 30 ++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/{{cookiecutter.project_name}}/.env.template b/{{cookiecutter.project_name}}/.env.template index c817a5f..7e466d8 100644 --- a/{{cookiecutter.project_name}}/.env.template +++ b/{{cookiecutter.project_name}}/.env.template @@ -30,17 +30,17 @@ # >> alias source-env='set -a && source .env && set +a' PROJECT_NAME='{{cookiecutter.package_name}}' -BASE_DIR="__CWD__" -IPYTHONDIR="__CWD__/etc/ipython" -PYTHONSTARTUP="__CWD__/etc/pythonstartup.py" +BASE_DIR='__CWD__' +IPYTHONDIR='__CWD__/etc/ipython' +PYTHONSTARTUP='__CWD__/etc/pythonstartup.py' # caching -CACHE_DIR="__CWD__/var/cache/" -BLACK_CACHE_DIR="__CWD__/var/cache/black" -IPYTHON_CACHE_DIR="__CWD__/var/cache/ipython" -MYPY_CACHE_DIR="__CWD__/var/cache/mypy" -PRE_COMMIT_HOME="__CWD__/var/cache/pre-commit" -PYLINTHOME="__CWD__/var/cache/pylint" +CACHE_DIR='__CWD__/var/cache/' +BLACK_CACHE_DIR='__CWD__/var/cache/black' +IPYTHON_CACHE_DIR='__CWD__/var/cache/ipython' +MYPY_CACHE_DIR='__CWD__/var/cache/mypy' +PRE_COMMIT_HOME='__CWD__/var/cache/pre-commit' +PYLINTHOME='__CWD__/var/cache/pylint' # debugging # used by python-interpreter, cfr. https://docs.python.org/3/using/cmdline.html#environment-variables @@ -52,15 +52,15 @@ PYTHONBREAKPOINT='ipdb.set_trace' DEBUGLEVEL_HTTPCONNECTION='1' # tmp -TMP="__CWD__/var/tmp" -TMPDIR="__CWD__/var/tmp" -TEMP="__CWD__/var/tmp" +TMP='__CWD__/var/tmp' +TMPDIR='__CWD__/var/tmp' +TEMP='__CWD__/var/tmp' # libranet-logging - etc/logging.yaml # Supported values for logging, from lowest to highest priority: # LOGLEVEL_XXX: NOTSET|TRACE|DEBUG|INFO|WARNING|ERROR -LOGGING_YML_FILE="__CWD__/etc/logging.yaml" -LOG_DIR="__CWD__/var/log" +LOGGING_YML_FILE='__CWD__/etc/logging.yaml' +LOG_DIR='__CWD__/var/log' PYTHON_CONSOLE_FORMATTER='console_color' LOGLEVEL_ROOT='NOTSET' LOGLEVEL_ASYNCIO='NOTSET' @@ -81,7 +81,7 @@ LOGLEVEL_URLLIB3='NOTSET' LOGLEVEL_URLLIB3_CONNECTIONPOOL='NOTSET' LOGLEVEL_URLLIB3_UTIL_RETRY='NOTSET' PYTHONASYNCIODDEBUG='1' -LOG_HANDLERS="console|debug_file|info_file|warning_file|error_file" +LOG_HANDLERS='console|debug_file|info_file|warning_file|error_file' PYTHON_ENABLE_LOGGING_TREE=0 From ce13e97c790105b938357cf6135960ff2b19526a Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Sun, 28 Dec 2025 11:15:24 +0100 Subject: [PATCH 08/15] simplify logging config --- .../etc/logging.yaml | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/{{cookiecutter.project_name}}/etc/logging.yaml b/{{cookiecutter.project_name}}/etc/logging.yaml index 5b903c2..e3df6b8 100644 --- a/{{cookiecutter.project_name}}/etc/logging.yaml +++ b/{{cookiecutter.project_name}}/etc/logging.yaml @@ -52,6 +52,7 @@ filters: - "^ Date: Sun, 28 Dec 2025 11:15:35 +0100 Subject: [PATCH 09/15] fix toml syntax --- {{cookiecutter.project_name}}/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index a39bd49..50aca33 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -327,11 +327,11 @@ known-local-folder = ["_helpers"] managed = true package = true default-groups = "all" -default-groups = [ +# default-groups = [ # "dev", # "ipython", # "jupyter", # "pre-commit", # "testing", # "typing", -] \ No newline at end of file +# ] \ No newline at end of file From e4e5bf3ba19164254f30467f03148f14c13f4717 Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Wed, 31 Dec 2025 12:11:55 +0100 Subject: [PATCH 10/15] modernize pyproject.toml --- {{cookiecutter.project_name}}/pyproject.toml | 156 ++++++++++--------- 1 file changed, 86 insertions(+), 70 deletions(-) diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 50aca33..6086e49 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -1,41 +1,43 @@ -# This is a comment. -# syntax-documentation: -# - https://python-poetry.org/docs/pyproject -# - https://flit.readthedocs.io/en/latest/pyproject_toml.html +# This pyproject.toml is the config-file for this workspace and its development-tools. # -# NOTE: you have to use single-quoted strings in TOML for regular expressions. -# It's the equivalent of r-strings in Python. Multiline strings are treated as -# verbose regular expressions by Black. Use [ ] to denote a significant space -# character. +# Official documentation: +# - https://packaging.python.org/en/latest/guides/writing-pyproject-toml/ # -# > poetry install -# > poetry install --only docs -# > poetry install (--with|--without) (docs|dev|ipython|profiling|testing|typing) - +# Notes: +# - Sort toplevel keys alphabetically. +# - As a general rule, sort keys within tables alphabetically. +# - Always sort dependencies alphabetically. +# - Separate top-level sections by a two blank lines. +# - Separate sub-sections by a single blank line. +# - Use single-quoted strings in TOML for regular expressions. +# It's the equivalent of r-strings in Python. Multiline strings are treated as +# verbose regular expressions by Black. Use [ ] to denote a significant space character. + + +# https://docs.astral.sh/uv/concepts/projects/config/#build-systems [build-system] -requires = ["uv_build>=0.8.9,<0.9.0"] +requires = ["uv_build>=0.9"] build-backend = "uv_build" [tool.bandit] +# https://bandit.readthedocs.io/en/latest/config.html # bandit does not use this config by default. # You need to invoke "bandit --configfile pyproject.toml" # see https://github.com/PyCQA/bandit/issues/318" baseline = "etc/bandit-baseline.json" exclude_dirs = [".venv", "var"] recursive = true -skips = ["B101"] targets = ["src", "tests"] - - -[tool.black] -include = '\.py$' # regex -> single-quotes -line-length = 120 -profile = "black" -target_version = ["py311"] +# skips = [ +# "B101", # assert used +# ] +[tool.bandit.assert_used] +skips = ["tests/*"] [tool.coverage.html] +# https://coverage.readthedocs.io/en/latest/config.html directory = "var/coverage/html" [tool.coverage.xml] @@ -65,28 +67,6 @@ data_file = "var/coverage/coverage.db" source = ["{{cookiecutter.package_name}}", "tests"] -# [tool.flake8] -# max_line_length = 121 -# per_file_ignores = [ -# "tests/test_entrypoints.py:B011", # B011: Do not call assert False -# "__init__.py:F401", # F401: imported but unused -# ] -# extend-ignore = -# E203 # https://black.readthedocs.io/en/stable/the_black_code_style/current_style.html#slices -# S101 # Allow assertions since we do not intend to compile to optimised byte code. - - -[tool.isort] -include_trailing_comma = true # corresponds to -tc flag -known_third_party = [] -line_length = 120 # corresponds to -w flag -multi_line_output = 3 # corresponds to -m flag -skip_glob = '^((?!py$).)*$' # isort all Python files -# profile = "black" -# force_single_line = true -# lines_after_imports = 2 - - [tool.mypy] # cfr. https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml-file cache_dir = "var/cache/mypy" @@ -135,16 +115,11 @@ classifiers = [ "Topic :: Software Development", "Typing :: Typed", ] - - - dependencies = [ "autoread-dotenv>=1.0.3", - {%- if cookiecutter.with_cyclopts|int %} "cyclopts>=3.24", {%- endif %} - {%- if cookiecutter.with_django|int %} "django>=4.2", "django-ninja>=0.19", @@ -152,7 +127,6 @@ dependencies = [ # "wagtail>=4.1", "watchfiles>=0.18.0", {%- endif %} - {%- if cookiecutter.with_fastapi|int %} "fastapi[all]>=0.117", {%- endif %} @@ -161,8 +135,6 @@ dependencies = [ "python-dateutil>=2.9", "pydantic>=2.9", ] - - [dependency-groups] dev = [ "pylint>=3.1.0", @@ -255,6 +227,7 @@ repository = "https://github.com/{{cookiecutter.github_user}}/{{cookiecutter.pro [tool.pylint.format] +# https://pylint.pycqa.org/en/stable/user_guide max-line-length = 120 good-names = [ "foo", # dummy variable @@ -267,36 +240,46 @@ disable = [ "C0116", # missing-function-docstring ] + [tool.pytest.ini_options] +# Settings reference: https://docs.pytest.org/en/stable/reference/customize.html cache_dir = "var/cache/pytest" -log_cli = false # enable to show log-output +log_cli = false # enable to show log-output log_cli_level = "NOTSET" filterwarnings = [] markers = ["unit", "integration"] testpaths = ["tests"] # the junit-report is used to report coverage in gitlab -addopts = "--color=yes --junit-xml='var/coverage/pytest.xml'" +# addopts = "--color=yes --junit-xml='var/coverage/pytest.xml'" [tool.ruff] -# https://docs.astral.sh/ruff/configuration/ +# Settings reference: https://docs.astral.sh/ruff/configuration/ cache-dir = "var/cache/ruff" # relative to project_root line-length = 120 +[tool.ruff.lint.isort] +# Settings reference: https://docs.astral.sh/ruff/settings/#lintisort +# known-first-party = ["{{cookiecutter.package_name}}"] +combine-as-imports = false +force-sort-within-sections = false # sort straight-style imports before from-style imports + [tool.ruff.lint] +select = ["ALL"] ignore = [ - # COM812 may cause conflicts when used with the ruff formatter - "COM812", + "COM812", # conflicts with ruff format + "ERA001", # found commented code + "T201", # print statement found + # D203 conflicts withs D211 - "D203", # 1 blank line required before class docstring - # "D211", # No blank lines allowed before class docstring + # "D211", # no blank lines allowed before class docstring + "D203", # blank line required before class docstring + # D212 conflicts with D213 - "D212", # Multi-line docstring summary should start at the first line - # "D213", # Multi-line docstring summary should start at the second line - "ERA001", # Found commented-out code -] -select = ["ALL"] + # "D213", # multi-line docstring summary should start at the second line + "D212", # multi-line docstring summary should start at the first line +] [tool.ruff.lint.flake8-annotations] allow-star-arg-any = true @@ -307,31 +290,64 @@ combine-as-imports = true force-single-line = false from-first = false known-third-party = [] -known-first-party = ["autoread_dotenv"] +known-first-party = ["{{cookiecutter.package_name}}"] known-local-folder = ["_helpers"] [tool.ruff.lint.per-file-ignores] "tests/**" = [ - "ANN001", # Missing type annotation - "D103", # Missing docstring in public function + "ANN001", # missing type annotation + "D103", # missing docstring in public function "INP001", # tests-dir should not be a python-package - "PLC0415", # `import` should be at the top-level of a file - "S101", # Use of `assert` detected + "PLC0415", # 'import' should be at top-level of file + "S101", # use of `assert` detected ] [tool.ty] +# https://docs.astral.sh/ty/reference/configuration/ + +[tool.ty.analysis] +# Disable support for `type: ignore` comments +respect-type-ignore-comments = true + +[tool.ty.environment] +# extra-paths = ["docs/"] + +[tool.ty.rules] +possibly-unresolved-reference = "warn" +division-by-zero = "ignore" + +[tool.ty.src] +include = [ + "src", + "tests", +] [tool.uv] +# Settings reference: https://docs.astral.sh/uv/reference/settings/ +keyring-provider = "subprocess" managed = true package = true default-groups = "all" -# default-groups = [ +# default-groups = [ # avoids typing 'uv sync --all-groups' # "dev", # "ipython", # "jupyter", # "pre-commit", # "testing", # "typing", -# ] \ No newline at end of file +# ] + + +# [[tool.uv.index]] +# Settings reference: https://docs.astral.sh/uv/reference/settings/#index + +[tool.uv.sources] +# Settings reference: https://docs.astral.sh/uv/reference/settings/#sources +# foo = { workspace = true } + +# [tool.uv.workspace] +# Settings reference: https://docs.astral.sh/uv/reference/settings/#workspace +# members = ["packages/*"] + From 7f97cc4d740ece6af3e6379368757c5eb7e711af Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Fri, 9 Jan 2026 13:25:11 +0100 Subject: [PATCH 11/15] optimize variadic args in justfiles --- {{cookiecutter.project_name}}/.gitlab-ci.yaml | 2 +- .../.just/bandit.justfile | 6 ++-- .../.just/ipython.justfile | 15 +++++----- .../.just/mutmut.justfile | 22 +++++++-------- .../.just/mypy.justfile | 6 ++-- .../.just/pylint.justfile | 6 ++-- .../.just/pytest.justfile | 28 +++++++++---------- .../.just/ruff.justfile | 10 +++---- .../.just/ty.justfile | 6 ++-- .../.just/uv.justfile | 12 ++++---- .../.vscode/settings.json | 2 +- {{cookiecutter.project_name}}/pyproject.toml | 4 +++ 12 files changed, 60 insertions(+), 59 deletions(-) diff --git a/{{cookiecutter.project_name}}/.gitlab-ci.yaml b/{{cookiecutter.project_name}}/.gitlab-ci.yaml index c4df334..43a7e73 100644 --- a/{{cookiecutter.project_name}}/.gitlab-ci.yaml +++ b/{{cookiecutter.project_name}}/.gitlab-ci.yaml @@ -36,7 +36,7 @@ cache: before_script: - test -e $CI_PROJECT_DIR/.poetry/bin/poetry || curl -sSL https://install.python-poetry.org | python3 - - - export PATH="$CI_PROJECT_DIR/.poetry/bin/:$CI_PROJECT_DIR/.venv/bin/:$PATH" + - export PATH="$CI_PROJECT_DIR/.poetry/bin/:$CI_PROJECT_DIR/uv run :$PATH" - echo $PATH - poetry --version - poetry install diff --git a/{{cookiecutter.project_name}}/.just/bandit.justfile b/{{cookiecutter.project_name}}/.just/bandit.justfile index a7d16d9..4aa43dc 100644 --- a/{{cookiecutter.project_name}}/.just/bandit.justfile +++ b/{{cookiecutter.project_name}}/.just/bandit.justfile @@ -11,7 +11,7 @@ bandit-which: [group: 'bandit'] bandit: # bandit --configfile pyproject.toml --recursive src --baseline etc/bandit-baseline.json - .venv/bin/bandit --configfile pyproject.toml --recursive . + uv run bandit --configfile pyproject.toml --recursive . # run bandit with htm-report @@ -19,11 +19,11 @@ bandit: bandit-html: @ mkdir -p var/html/bandit @ echo -e "Bandit-report generated in var/html/bandit/bandit.html" - .venv/bin/bandit --config pyproject.toml --recursive . --format html > var/html/bandit/bandit.html + uv run bandit --config pyproject.toml --recursive . --format html > var/html/bandit/bandit.html # update bandit baseline [group: 'bandit'] bandit-update-baseline: - .venv/bin/bandit --configfile pyproject.toml --recursive . --format json --output etc/bandit-baseline.json + uv run bandit --configfile pyproject.toml --recursive . --format json --output etc/bandit-baseline.json diff --git a/{{cookiecutter.project_name}}/.just/ipython.justfile b/{{cookiecutter.project_name}}/.just/ipython.justfile index 7c0abeb..d2c8ad0 100644 --- a/{{cookiecutter.project_name}}/.just/ipython.justfile +++ b/{{cookiecutter.project_name}}/.just/ipython.justfile @@ -10,24 +10,24 @@ symlink-ipython: # open python-shell [group: 'ipython'] -python-shell args="": - @ .venv/bin/python {{args}} +python-shell *args: + @ uv run python {{args}} alias python := python-shell # open ipython-shell [group: 'ipython'] -ipython-shell args="": - @ .venv/bin/ipython {{args}} +ipython-shell *args: + @ uv run ipython {{args}} alias ipython := ipython-shell alias ip := ipython-shell # open ipython-shell [group: 'ipython'] -ipython-shell-debug args="": - @ .venv/bin/ipython --debug {{args}} +ipython-shell-debug *args: + @ uv run ipython --debug {{args}} alias ipython-debug := ipython-shell-debug @@ -35,5 +35,4 @@ alias ipython-debug := ipython-shell-debug # create an ipython profile [group: 'ipython'] ipython-create-profile name="": - .venv/bin/ipython profile create {{name}} - + uv run ipython profile create {{name}} diff --git a/{{cookiecutter.project_name}}/.just/mutmut.justfile b/{{cookiecutter.project_name}}/.just/mutmut.justfile index 5d46c42..9a405ab 100644 --- a/{{cookiecutter.project_name}}/.just/mutmut.justfile +++ b/{{cookiecutter.project_name}}/.just/mutmut.justfile @@ -4,7 +4,7 @@ # install mutmut [group: 'mutmut'] mutmut-install: - uv ad mutmut + uv add mutmut alias install-mutmut := mutmut-install @@ -23,31 +23,31 @@ mutmut-help: # run mutmut [group: 'mutmut'] -mutmut-run: - mutmut run +mutmut-run *args: + mutmut run {{args}} # browse mutmut results [group: 'mutmut'] -mutmut-browse: - mutmut browse +mutmut-browse *args: + mutmut browse {{args}} # show mutmut results [group: 'mutmut'] -mutmut-results: - mutmut results +mutmut-results *args: + mutmut results {{args}} # apply mutant by name [group: 'mutmut'] -mutmut-apply name="": - mutmut apply {{name}} +mutmut-apply name="" *args: + mutmut apply {{name}} {{args}} # test specific mutant by name [group: 'mutmut'] -mutmut-test-for-mutant name="": - mutmut tests-for-mutant {{name}} +mutmut-test-for-mutant name="" *args: + mutmut tests-for-mutant {{name}} {{args}} alias mutmut-test := mutmut-test-for-mutant \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.just/mypy.justfile b/{{cookiecutter.project_name}}/.just/mypy.justfile index 996238a..d193aa0 100644 --- a/{{cookiecutter.project_name}}/.just/mypy.justfile +++ b/{{cookiecutter.project_name}}/.just/mypy.justfile @@ -10,14 +10,14 @@ mypy-which: # run mypy on python-files [group: 'mypy'] -mypy args="": mypy-which +mypy *args: mypy src tests {{ args }} # run mypy with html-reporting [group: 'mypy'] -mypy-report path="var/html/mypy/" args="": mypy-which +mypy-report path="var/html/mypy/" *args: @ mkdir -p {{ path }} mypy src tests --html-report {{ path }} {{ args }} @@ -26,5 +26,3 @@ mypy-report path="var/html/mypy/" args="": mypy-which [group: 'mypy'] mypy-html: mypy-report - - diff --git a/{{cookiecutter.project_name}}/.just/pylint.justfile b/{{cookiecutter.project_name}}/.just/pylint.justfile index f71c834..8ee9971 100644 --- a/{{cookiecutter.project_name}}/.just/pylint.justfile +++ b/{{cookiecutter.project_name}}/.just/pylint.justfile @@ -10,18 +10,18 @@ pylint-which: ## run pylint on python-files [group: 'pylint'] -pylint args="": pylint-which +pylint *args: pylint-which - pylint src/ tests/ {{args}} ## run pylint on python-files in src/ [group: 'pylint'] -pylint-src args="": pylint-which +pylint-src *args: pylint-which - pylint src/ {{args}} ## run pylint on python-files in tests/ [group: 'pylint'] -pylint-tests args="": pylint-which +pylint-tests *args: pylint-which - pylint tests/ {{args}} diff --git a/{{cookiecutter.project_name}}/.just/pytest.justfile b/{{cookiecutter.project_name}}/.just/pytest.justfile index ee5e316..5e29025 100644 --- a/{{cookiecutter.project_name}}/.just/pytest.justfile +++ b/{{cookiecutter.project_name}}/.just/pytest.justfile @@ -4,47 +4,47 @@ # run pytest [group: 'pytest'] -pytest args="": - .venv/bin/pytest tests {{args}} +pytest *args: + uv run pytest tests {{args}} # run pytest with markers [group: 'pytest'] -pytest-marked markers="" args="": - .venv/bin/pytest tests -m '{{markers}}' {{args}} +pytest-marked markers="" *args: + uv run pytest tests -m '{{markers}}' {{args}} # run pytest with coverage [group: 'pytest'] -pytest-coverage args="": - .venv/bin/pytest tests --color=yes --cov=autoread_dotenv --cov-fail-under=5 --cov-report html:var/coverage/html --cov-report xml:var/coverage/pytest-cobertura.xml --cov-report term-missing --junit-xml='var/coverage/pytest-junit.xml' {{args}} +pytest-coverage *args: + uv run pytest tests --color=yes --cov=autoread_dotenv --cov-fail-under=5 --cov-report html:var/coverage/html --cov-report xml:var/coverage/pytest-cobertura.xml --cov-report term-missing --junit-xml='var/coverage/pytest-junit.xml' {{args}} alias pytest-cov := pytest-coverage # run pytest with pdb [group: 'pytest'] -pytest-pdb args="": - .venv/bin/pytest tests --color=yes --pdb -v {{args}} +pytest-pdb *args: + uv run pytest tests --color=yes --pdb -v {{args}} # run pytest with last-failed [group: 'pytest'] -pytest-failed args="": - .venv/bin/pytest tests --color=yes --lf {{args}} +pytest-failed *args: + uv run pytest tests --color=yes --lf {{args}} alias pytest-lf := pytest-failed # run pytest with pdb + last-failed [group: 'pytest'] -pytest-pdb-failed args="": - .venv/bin/pytest tests --color=yes --pdb --lf {{args}} +pytest-pdb-failed *args: + uv run pytest tests --color=yes --pdb --lf {{args}} alias pytest-lf-pdf := pytest-pdb-failed # run pytest with filter [group: 'pytest'] -pytest-filter filter args="": - .venv/bin/pytest tests --color=yes -k {{filter}} {{args}} +pytest-filter filter *args: + uv run pytest tests --color=yes -k {{filter}} {{args}} diff --git a/{{cookiecutter.project_name}}/.just/ruff.justfile b/{{cookiecutter.project_name}}/.just/ruff.justfile index e0fccb7..64cc0ae 100644 --- a/{{cookiecutter.project_name}}/.just/ruff.justfile +++ b/{{cookiecutter.project_name}}/.just/ruff.justfile @@ -15,19 +15,19 @@ ruff-which: # run ruff on python-files [group: 'ruff'] -ruff args="": +ruff *args: - {{RUFF_EXE}} docs/ etc/ src/ tests/ {{args}} # run ruff --check on python-files [group: 'ruff'] -ruff-check args="": +ruff-check *args: - {{RUFF_EXE}} check docs/ etc/ src/ tests/ {{args}} # run ruff --fix on python-files [group: 'ruff'] -ruff-check-fix args="": +ruff-check-fix *args: - {{RUFF_EXE}} check docs/ etc/ src/ tests/ --fix {{args}} alias ruff-fix := ruff-check-fix @@ -35,7 +35,7 @@ alias ruff-fix := ruff-check-fix # run ruff --fix on python-files [group: 'ruff'] -ruff-check-fix-unsafe args="": +ruff-check-fix-unsafe *args: - {{RUFF_EXE}} check docs/ etc/ src/ tests/ --fix --unsafe-fixes {{args}} alias ruff-fix-unsafe := ruff-check-fix-unsafe @@ -43,6 +43,6 @@ alias ruff-fix-unsafe := ruff-check-fix-unsafe # run ruff format on python-files [group: 'ruff'] -ruff-format args="": +ruff-format *args: - {{RUFF_EXE}} format docs/ etc/ src/ tests/ {{args}} diff --git a/{{cookiecutter.project_name}}/.just/ty.justfile b/{{cookiecutter.project_name}}/.just/ty.justfile index 99a7843..ebfb5d3 100644 --- a/{{cookiecutter.project_name}}/.just/ty.justfile +++ b/{{cookiecutter.project_name}}/.just/ty.justfile @@ -5,12 +5,12 @@ # show verion of ty [group: 'ty'] ty-version: - @ .venv/bin/ty --version + @ uv run ty --version # run ty --check on python-files [group: 'ty'] -ty-check args="": - - .venv/bin/ty check src/ tests/ {{args}} +ty-check *args: + - uv run ty check src/ tests/ {{args}} diff --git a/{{cookiecutter.project_name}}/.just/uv.justfile b/{{cookiecutter.project_name}}/.just/uv.justfile index 0b3c63b..934164b 100644 --- a/{{cookiecutter.project_name}}/.just/uv.justfile +++ b/{{cookiecutter.project_name}}/.just/uv.justfile @@ -58,7 +58,7 @@ uv-cache-dir: # install the project and all dependencies from only the default groups [group: 'uv'] -uv-sync args="": +uv-sync *args: uv sync {{args}} # alias uv-install := uv-sync @@ -67,7 +67,7 @@ alias create-venv := uv-sync # install the project including all dependencies from all groups [group: 'uv'] -uv-sync-all-groups args="": +uv-sync-all-groups *args: uv sync --all-groups {{args}} alias uv-sync-all := uv-sync-all-groups @@ -75,25 +75,25 @@ alias uv-sync-all := uv-sync-all-groups # update uv.lock [group: 'uv'] -uv-lock args="": +uv-lock *args: uv lock {{args}} # check uv.lock is up-to-date [group: 'uv'] -uv-lock-check args="": +uv-lock-check *args: uv lock --check {{args}} # build the python-package [group: 'uv'] -uv-build args="": +uv-build *args: uv build {{args}} # publish the python-package [group: 'uv'] -uv-publish path="dist/" args="": +uv-publish path="dist/" *args: uv publish {{path}} --verbose {{args}} diff --git a/{{cookiecutter.project_name}}/.vscode/settings.json b/{{cookiecutter.project_name}}/.vscode/settings.json index 182820b..f4dfa2e 100644 --- a/{{cookiecutter.project_name}}/.vscode/settings.json +++ b/{{cookiecutter.project_name}}/.vscode/settings.json @@ -21,7 +21,7 @@ "makefile.configurations": [], // https://code.visualstudio.com/docs/python/settings-reference "python.envFile": "${workspaceFolder}/.env", - "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", + "python.defaultInterpreterPath": "${workspaceFolder}/uv run python", "python.linting.enabled": true, "python.linting.banditArgs": [ "--configfile", diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 6086e49..eaedaf0 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -256,7 +256,11 @@ testpaths = ["tests"] [tool.ruff] # Settings reference: https://docs.astral.sh/ruff/configuration/ cache-dir = "var/cache/ruff" # relative to project_root +fix = true line-length = 120 +target-version = "cookiecutter.minimum_python.split('.')[1]|int" + + [tool.ruff.lint.isort] # Settings reference: https://docs.astral.sh/ruff/settings/#lintisort From 9e20f1c0667f213b5a8fd412aff8f24d75a5b7e7 Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Fri, 9 Jan 2026 13:35:22 +0100 Subject: [PATCH 12/15] more fixes --- .../.just/ipython.justfile | 9 ++++---- .../.just/pyroma.justfile | 9 ++++---- .../.just/ruff.justfile | 22 +++++++++---------- .../.just/safety.justfile | 10 ++++----- {{cookiecutter.project_name}}/pyproject.toml | 14 +++++------- 5 files changed, 30 insertions(+), 34 deletions(-) diff --git a/{{cookiecutter.project_name}}/.just/ipython.justfile b/{{cookiecutter.project_name}}/.just/ipython.justfile index d2c8ad0..4e811b4 100644 --- a/{{cookiecutter.project_name}}/.just/ipython.justfile +++ b/{{cookiecutter.project_name}}/.just/ipython.justfile @@ -3,9 +3,9 @@ # symlink ipython to ip -[group: 'ipython'] -symlink-ipython: - @ cd .venv/bin && ln -sf ipython ip +# [group: 'ipython'] +# symlink-ipython: +# @ cd .venv/bin && ln -sf ipython ip # open python-shell @@ -24,7 +24,8 @@ ipython-shell *args: alias ipython := ipython-shell alias ip := ipython-shell -# open ipython-shell + +# open ipython-shell in debug-mode [group: 'ipython'] ipython-shell-debug *args: @ uv run ipython --debug {{args}} diff --git a/{{cookiecutter.project_name}}/.just/pyroma.justfile b/{{cookiecutter.project_name}}/.just/pyroma.justfile index 0d69e1f..3c3fe46 100644 --- a/{{cookiecutter.project_name}}/.just/pyroma.justfile +++ b/{{cookiecutter.project_name}}/.just/pyroma.justfile @@ -3,13 +3,12 @@ # show which pyroma is used -[group: 'pyroma'] -pyroma-which: - @ which pyroma +# [group: 'pyroma'] +# pyroma-which: +# @ which pyroma # run pyroma [group: 'pyroma'] pyroma: pyroma-which - - pyroma . - + uv run pyroma . diff --git a/{{cookiecutter.project_name}}/.just/ruff.justfile b/{{cookiecutter.project_name}}/.just/ruff.justfile index 64cc0ae..b4c72b9 100644 --- a/{{cookiecutter.project_name}}/.just/ruff.justfile +++ b/{{cookiecutter.project_name}}/.just/ruff.justfile @@ -4,31 +4,31 @@ # display absolute path to the executable # RUFF_EXE := `which ruff || true` -RUFF_EXE := which('ruff') +# RUFF_EXE := which('ruff') # show which ruff is used -[group: 'ruff'] -ruff-which: - @ echo -e "Using executable {{RUFF_EXE}}" +# [group: 'ruff'] +# ruff-which: +# @ echo -e "Using executable {{RUFF_EXE}}" # run ruff on python-files -[group: 'ruff'] -ruff *args: - - {{RUFF_EXE}} docs/ etc/ src/ tests/ {{args}} +# [group: 'ruff'] +# ruff *args: +# uv run ruff docs/ etc/ src/ tests/ {{args}} # run ruff --check on python-files [group: 'ruff'] ruff-check *args: - - {{RUFF_EXE}} check docs/ etc/ src/ tests/ {{args}} + uv run ruff check docs/ etc/ src/ tests/ {{args}} # run ruff --fix on python-files [group: 'ruff'] ruff-check-fix *args: - - {{RUFF_EXE}} check docs/ etc/ src/ tests/ --fix {{args}} + uv run ruff check docs/ etc/ src/ tests/ --fix {{args}} alias ruff-fix := ruff-check-fix @@ -36,7 +36,7 @@ alias ruff-fix := ruff-check-fix # run ruff --fix on python-files [group: 'ruff'] ruff-check-fix-unsafe *args: - - {{RUFF_EXE}} check docs/ etc/ src/ tests/ --fix --unsafe-fixes {{args}} + uv run ruff check docs/ etc/ src/ tests/ --fix --unsafe-fixes {{args}} alias ruff-fix-unsafe := ruff-check-fix-unsafe @@ -44,5 +44,5 @@ alias ruff-fix-unsafe := ruff-check-fix-unsafe # run ruff format on python-files [group: 'ruff'] ruff-format *args: - - {{RUFF_EXE}} format docs/ etc/ src/ tests/ {{args}} + uv run ruff format docs/ etc/ src/ tests/ {{args}} diff --git a/{{cookiecutter.project_name}}/.just/safety.justfile b/{{cookiecutter.project_name}}/.just/safety.justfile index fb72c92..38d1782 100644 --- a/{{cookiecutter.project_name}}/.just/safety.justfile +++ b/{{cookiecutter.project_name}}/.just/safety.justfile @@ -3,20 +3,20 @@ # show which safety is used -[group: 'safety'] -safety-which: - @ which safety +# [group: 'safety'] +# safety-which: +# @ which safety # run safety check [group: 'safety'] safety-check: - safety check + uv run safety check # run safety check with html-report [group: 'safety'] safety-check-html: @ mkdir -p var/html/safety - safety check --save-html var/html/safety/safety.html + uv run safety check --save-html var/html/safety/safety.html diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index eaedaf0..429a124 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -260,14 +260,6 @@ fix = true line-length = 120 target-version = "cookiecutter.minimum_python.split('.')[1]|int" - - -[tool.ruff.lint.isort] -# Settings reference: https://docs.astral.sh/ruff/settings/#lintisort -# known-first-party = ["{{cookiecutter.package_name}}"] -combine-as-imports = false -force-sort-within-sections = false # sort straight-style imports before from-style imports - [tool.ruff.lint] select = ["ALL"] ignore = [ @@ -289,9 +281,13 @@ ignore = [ allow-star-arg-any = true [tool.ruff.lint.isort] +# Settings reference: https://docs.astral.sh/ruff/settings/#lintisort + # https://docs.astral.sh/ruff/settings/#lintisort -combine-as-imports = true +# combine-as-imports = true +combine-as-imports = false force-single-line = false +force-sort-within-sections = false # sort straight-style imports before from-style imports from-first = false known-third-party = [] known-first-party = ["{{cookiecutter.package_name}}"] From 0da472685052952c5f467777a4f5a4d1a1ecc20c Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Mon, 12 Jan 2026 10:32:07 +0100 Subject: [PATCH 13/15] major cleanup --- cookiecutter.json | 2 +- {{cookiecutter.project_name}}/.env.template | 3 + .../.just/bandit.justfile | 6 -- .../.just/ipython.justfile | 6 -- .../.just/mypy.justfile | 11 +-- .../.just/pre-commit.justfile | 17 +--- .../.just/pylint.justfile | 18 ++-- .../.just/pyroma.justfile | 8 +- .../.just/safety.justfile | 6 -- .../.just/ty.justfile | 1 + .../.vscode/markdown.code-snippets | 32 ------- .../.vscode/python.code-snippets | 96 ------------------- .../.vscode/settings.json | 75 +++++++++------ .../profile_default/startup/10-logging.py | 15 ++- .../profile_default/startup/20-rich.py | 27 ++++++ .../profile_default/startup/30-demo.py | 61 ++++++++++++ {{cookiecutter.project_name}}/justfile | 11 ++- {{cookiecutter.project_name}}/pyproject.toml | 8 +- .../{{cookiecutter.package_name}}/__init__.py | 2 +- .../{_about.py => about.py} | 20 ---- .../cli/__init__.py | 6 +- .../tests/test_cli.py | 4 +- 22 files changed, 185 insertions(+), 250 deletions(-) delete mode 100644 {{cookiecutter.project_name}}/.vscode/markdown.code-snippets delete mode 100644 {{cookiecutter.project_name}}/.vscode/python.code-snippets create mode 100644 {{cookiecutter.project_name}}/etc/ipython/profile_default/startup/20-rich.py create mode 100644 {{cookiecutter.project_name}}/etc/ipython/profile_default/startup/30-demo.py rename {{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/{_about.py => about.py} (57%) diff --git a/cookiecutter.json b/cookiecutter.json index 41d200b..f95e42e 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -17,7 +17,7 @@ "development_status": [ "Development Status :: 4 - Beta" ], - "minimum_python": "3.13.0", + "minimum_python": "3.13", "with_django": "0", "with_flask": "0", "with_fastapi": "0", diff --git a/{{cookiecutter.project_name}}/.env.template b/{{cookiecutter.project_name}}/.env.template index 7e466d8..556f842 100644 --- a/{{cookiecutter.project_name}}/.env.template +++ b/{{cookiecutter.project_name}}/.env.template @@ -51,6 +51,9 @@ PYTHONBREAKPOINT='ipdb.set_trace' # show headers in urllib3-http-connections DEBUGLEVEL_HTTPCONNECTION='1' +# generated sercrets key +APP_SECRET_KEY='__APP_SECRET_KEY__' + # tmp TMP='__CWD__/var/tmp' TMPDIR='__CWD__/var/tmp' diff --git a/{{cookiecutter.project_name}}/.just/bandit.justfile b/{{cookiecutter.project_name}}/.just/bandit.justfile index 4aa43dc..0b61540 100644 --- a/{{cookiecutter.project_name}}/.just/bandit.justfile +++ b/{{cookiecutter.project_name}}/.just/bandit.justfile @@ -1,12 +1,6 @@ # bandit, see ../justfile -# show which bandit is used -[group: 'bandit'] -bandit-which: - @ which bandit - - # run bandit [group: 'bandit'] bandit: diff --git a/{{cookiecutter.project_name}}/.just/ipython.justfile b/{{cookiecutter.project_name}}/.just/ipython.justfile index 4e811b4..5d3174b 100644 --- a/{{cookiecutter.project_name}}/.just/ipython.justfile +++ b/{{cookiecutter.project_name}}/.just/ipython.justfile @@ -2,12 +2,6 @@ # ipython -# symlink ipython to ip -# [group: 'ipython'] -# symlink-ipython: -# @ cd .venv/bin && ln -sf ipython ip - - # open python-shell [group: 'ipython'] python-shell *args: diff --git a/{{cookiecutter.project_name}}/.just/mypy.justfile b/{{cookiecutter.project_name}}/.just/mypy.justfile index d193aa0..221e77f 100644 --- a/{{cookiecutter.project_name}}/.just/mypy.justfile +++ b/{{cookiecutter.project_name}}/.just/mypy.justfile @@ -2,24 +2,17 @@ # mypy -# show which mypy is used -[group: 'mypy'] -mypy-which: - @ which mypy - - # run mypy on python-files [group: 'mypy'] mypy *args: - mypy src tests {{ args }} - + uv run mypy src tests {{ args }} # run mypy with html-reporting [group: 'mypy'] mypy-report path="var/html/mypy/" *args: @ mkdir -p {{ path }} - mypy src tests --html-report {{ path }} {{ args }} + uv run mypy src tests --html-report {{ path }} {{ args }} # alias for mypy-report diff --git a/{{cookiecutter.project_name}}/.just/pre-commit.justfile b/{{cookiecutter.project_name}}/.just/pre-commit.justfile index e02dfa5..21c0742 100644 --- a/{{cookiecutter.project_name}}/.just/pre-commit.justfile +++ b/{{cookiecutter.project_name}}/.just/pre-commit.justfile @@ -2,18 +2,9 @@ # See ../justfile -# show which pre-commit is used -[group: 'pre-commit'] -pre-commit-which: - @ which pre-commit - - -alias precommit-which := pre-commit-which - - # install the pre-commit-hook in .git/hooks [group: 'pre-commit'] -pre-commit-install-hook: pre-commit-which +pre-commit-install-hook: pre-commit install alias precommit-install-hook := pre-commit-install-hook @@ -21,7 +12,7 @@ alias precommit-install-hook := pre-commit-install-hook # uninstall the pre-commit-hook in .git/hooks [group: 'pre-commit'] -pre-commit-uninstall-hook: pre-commit-which +pre-commit-uninstall-hook: pre-commit uninstall alias precommit-uninstall-hook := pre-commit-uninstall-hook @@ -29,7 +20,7 @@ alias precommit-uninstall-hook := pre-commit-uninstall-hook # validate .pre-commit-config.yaml [group: 'pre-commit'] -pre-commit-validate-config: pre-commit-which +pre-commit-validate-config: pre-commit validate-config alias precommit-validate-config := pre-commit-validate-config @@ -37,7 +28,7 @@ alias precommit-validate-config := pre-commit-validate-config # run all precommit-steps on all files [group: 'pre-commit'] -pre-commit-run-files: pre-commit-which +pre-commit-run-files: pre-commit run --all-files alias precommit-run-files := pre-commit-run-files diff --git a/{{cookiecutter.project_name}}/.just/pylint.justfile b/{{cookiecutter.project_name}}/.just/pylint.justfile index 8ee9971..ed29b9e 100644 --- a/{{cookiecutter.project_name}}/.just/pylint.justfile +++ b/{{cookiecutter.project_name}}/.just/pylint.justfile @@ -2,26 +2,20 @@ # pylint -# show which pylint is used -[group: 'pylint'] -pylint-which: - @ which pylint - - ## run pylint on python-files [group: 'pylint'] -pylint *args: pylint-which - - pylint src/ tests/ {{args}} +pylint *args: + uv run pylint src/ tests/ {{args}} ## run pylint on python-files in src/ [group: 'pylint'] -pylint-src *args: pylint-which - - pylint src/ {{args}} +pylint-src *args: + uv run pylint src/ {{args}} ## run pylint on python-files in tests/ [group: 'pylint'] -pylint-tests *args: pylint-which - - pylint tests/ {{args}} +pylint-tests *args: + uv run pylint tests/ {{args}} diff --git a/{{cookiecutter.project_name}}/.just/pyroma.justfile b/{{cookiecutter.project_name}}/.just/pyroma.justfile index 3c3fe46..455e505 100644 --- a/{{cookiecutter.project_name}}/.just/pyroma.justfile +++ b/{{cookiecutter.project_name}}/.just/pyroma.justfile @@ -2,13 +2,7 @@ # See ../justfile -# show which pyroma is used -# [group: 'pyroma'] -# pyroma-which: -# @ which pyroma - - # run pyroma [group: 'pyroma'] -pyroma: pyroma-which +pyroma: uv run pyroma . diff --git a/{{cookiecutter.project_name}}/.just/safety.justfile b/{{cookiecutter.project_name}}/.just/safety.justfile index 38d1782..47772f0 100644 --- a/{{cookiecutter.project_name}}/.just/safety.justfile +++ b/{{cookiecutter.project_name}}/.just/safety.justfile @@ -2,12 +2,6 @@ # safety -# show which safety is used -# [group: 'safety'] -# safety-which: -# @ which safety - - # run safety check [group: 'safety'] safety-check: diff --git a/{{cookiecutter.project_name}}/.just/ty.justfile b/{{cookiecutter.project_name}}/.just/ty.justfile index ebfb5d3..0ed9473 100644 --- a/{{cookiecutter.project_name}}/.just/ty.justfile +++ b/{{cookiecutter.project_name}}/.just/ty.justfile @@ -7,6 +7,7 @@ ty-version: @ uv run ty --version + # run ty --check on python-files [group: 'ty'] ty-check *args: diff --git a/{{cookiecutter.project_name}}/.vscode/markdown.code-snippets b/{{cookiecutter.project_name}}/.vscode/markdown.code-snippets deleted file mode 100644 index 1b877a2..0000000 --- a/{{cookiecutter.project_name}}/.vscode/markdown.code-snippets +++ /dev/null @@ -1,32 +0,0 @@ -{ - // Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and - // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope - // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is - // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: - // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. - // Placeholders with the same ids are connected. - // Example: - // "Print to console": { - // "scope": "javascript,typescript", - // "prefix": "log", - // "body": [ - // "console.log('$1');", - // "$2" - // ], - // "description": "Log output to console" - // } - // https://code.visualstudio.com/docs/editor/userdefinedsnippets - // https://stackoverflow.com/questions/44312494/how-to-create-per-workspace-snippets-in-vscode - "set unreleased header": { - "scope": "markdown", - "prefix": [ - "un", - "unrel", - "unreleased", - ], - "body": [ - "## Unreleased (YYYY-MM-DD)\n\n- No changes yet." - ], - "description": "set unreleased header." - } -} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.vscode/python.code-snippets b/{{cookiecutter.project_name}}/.vscode/python.code-snippets deleted file mode 100644 index a1506e8..0000000 --- a/{{cookiecutter.project_name}}/.vscode/python.code-snippets +++ /dev/null @@ -1,96 +0,0 @@ -{ - // Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and - // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope - // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is - // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: - // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. - // Placeholders with the same ids are connected. - // Example: - // "Print to console": { - // "scope": "javascript,typescript", - // "prefix": "log", - // "body": [ - // "console.log('$1');", - // "$2" - // ], - // "description": "Log output to console" - // } - // https://code.visualstudio.com/docs/editor/userdefinedsnippets - // https://stackoverflow.com/questions/44312494/how-to-create-per-workspace-snippets-in-vscode - "Set breakpoint": { - "scope": "python", - "prefix": [ - "br", - "breakp" - ], - "body": [ - "breakpoint()" - ], - "description": "Set a pdb-breakpoint." - }, - "Import pathlib": { - "scope": "python", - "prefix": [ - "import pl", - "import pa", - ], - "body": [ - "import pathlib as pl" - ], - "description": "Import pathlib." - }, - "Import datetime": { - "scope": "python", - "prefix": [ - "import datetime", - "import dt", - ], - "body": [ - "import datetime as dt" - ], - "description": "Import datetime." - }, - "Import functools": { - "scope": "python", - "prefix": [ - "import fu", - "import ft", - ], - "body": [ - "import functools as ft" - ], - "description": "Import functools." - }, - "Import itertools": { - "scope": "python", - "prefix": [ - "import iter", - "import it", - ], - "body": [ - "import itertools as it" - ], - "description": "Import itertools." - }, - "Import typing": { - "scope": "python", - "prefix": [ - "import ty", - "import tp", - ], - "body": [ - "import typing as tp" - ], - "description": "Import typing." - }, - "Get logger": { - "scope": "python", - "prefix": [ - "log =", - ], - "body": [ - "log = logging.getLogger(__name__)" - ], - "description": "Get logger." - } -} \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/.vscode/settings.json b/{{cookiecutter.project_name}}/.vscode/settings.json index f4dfa2e..91bcfc1 100644 --- a/{{cookiecutter.project_name}}/.vscode/settings.json +++ b/{{cookiecutter.project_name}}/.vscode/settings.json @@ -1,45 +1,66 @@ { - // oh-my-posh uses special font (CaskaydiaCove) to display icons in the console - "editor.fontFamily": "Consolas, 'Courier New', monospace,'DejaVu Sans Mono for Powerline','CaskaydiaCove NF'", + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit", + "source.fixAll": "explicit" + }, "editor.detectIndentation": false, + // "editor.formatOnSave": true, // avoid removing all blank lines in json-files "editor.insertSpaces": true, "editor.tabSize": 4, + "files.associations": { ".env": "dotenv", - ".env.example": "dotenv" + ".env.template": "dotenv" }, - "makefile.extensionOutputFolder": "./var/cache/vscode", - "[makefile]": { // per-language config - "editor.insertSpaces": false, + "files.exclude": { + "**/__pycache__": true, + "lib/": true, + "lib64/": true + }, - "[yaml]": { // per-language config - "editor.insertSpaces": true, - "editor.tabSize": 4, + "files.trimTrailingWhitespace": true, + + "[json]": { + "editor.formatOnSave": false + }, + + // "jupyter.defaultKernel": "${workspaceFolder}/.venv/bin/python", + // "jupyter.jupyterServerType": "local", + + "mypy-type-checker.args": ["--config-file", "${workspaceFolder}/pyproject.toml"], + "mypy-type-checker.cwd": "${workspaceFolder}/", + "mypy-type-checker.importStrategy": "fromEnvironment", + + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true }, - "makefile.defaultLaunchConfiguration": {}, - "makefile.makefilePath": "makefile", // Makefile is default - "makefile.configurations": [], - // https://code.visualstudio.com/docs/python/settings-reference "python.envFile": "${workspaceFolder}/.env", - "python.defaultInterpreterPath": "${workspaceFolder}/uv run python", - "python.linting.enabled": true, - "python.linting.banditArgs": [ - "--configfile", - "${workspaceFolder}/pyproject.toml", - "--recursive" - ], + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", "python.terminal.activateEnvironment": true, "python.testing.unittestEnabled": false, "python.testing.pytestEnabled": true, "python.testing.pytestArgs": [ + "--color=yes", "--cov=src", - "--cov-report=xml", - "--cov-report=html", - "--pdb", + // "--cov-fail-under=90", + // "--cov-report xml:${workspaceFolder}/var/coverage/pytest-cobertura.xml", + // "--cov-report html:${workspaceFolder}/var/coverage/html", + // "--cov-report term-missing", + // "--junit-xml=${workspaceFolder}/var/coverage/pytest-junit.xml", + // "--pdb", "-v", "${workspaceFolder}/tests/", ], - "yaml.customTags": [ - "!env" - ] -} \ No newline at end of file + "ruff.configuration": "${workspaceFolder}/pyproject.toml", + // "ruff.lint.args": ["--extend-ignore=F401"], + "ruff.nativeServer": "on", + + "[yaml]": { // per-language config + "editor.insertSpaces": true, + "editor.tabSize": 4, + }, + + "vscode-just.runInTerminal": true, + "vscode-just.useSingleTerminal": true, +} diff --git a/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/10-logging.py b/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/10-logging.py index 241fff0..20a467c 100644 --- a/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/10-logging.py +++ b/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/10-logging.py @@ -14,20 +14,25 @@ return-statements are not allowed. """ -print(f"\nRunning {__file__}") +print(f"\nImporting {__file__}") +# already import highly used modules +import datetime as dt import logging -import os # noqa -import sys # noqa +import os +import pathlib as pl +import sys +import typing as tp + + import libranet_logging -# import demo_flask.cfg as cfg # setup the logging according to etc/logging.yml libranet_logging.initialize() -log = logging.getLogger("ipython-startup") # name = "__main__" +log = logging.getLogger("ipython-startup") log.debug("debug-message") log.info("info-message") diff --git a/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/20-rich.py b/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/20-rich.py new file mode 100644 index 0000000..59f45b3 --- /dev/null +++ b/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/20-rich.py @@ -0,0 +1,27 @@ +# pylint: disable=unused-import +# pylint: disable=wrong-import-position +# pylint: disable=invalid-name + +# ruff: noqa: INP001 (implicit part of namespace package) +# ruff: noqa: E402 (module level import not at top of file) +# ruff: noqa: F401 (unused import) + +"""IPython startup-file, outside of PYTHONPATH. See readme.""" + +print(f"Running {__file__}") + +import rich.panel +import rich.pretty +from rich import inspect +from rich import print as rprint +from rich.panel import Panel +from rich.pretty import pprint +from rich.pretty import pprint as pp # shorter alias + +# see https://rich.readthedocs.io/en/stable/introduction.html#rich-in-the-repl +rich.pretty.install() + +# in ipython-startup files, Panel.fit() alone does not display the panel +# The issue is that Panel.fit() creates a Panel object, but doesn't automatically display it. +# By wrapping it with rich.print the Panel will be rendered when IPython starts up. +# rprint(rich.panel.Panel.fit("[bold yellow]Hi, I'm a Panel", border_style="blue")) diff --git a/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/30-demo.py b/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/30-demo.py new file mode 100644 index 0000000..f50f9a3 --- /dev/null +++ b/{{cookiecutter.project_name}}/etc/ipython/profile_default/startup/30-demo.py @@ -0,0 +1,61 @@ +# flake8: noqa: E402 (module level import not at top of file) +# pylint: disable=unused-import +# pylint: disable=wrong-import-position +# pylint: disable=invalid-name +"""IPython startup-file, outside of PYTHONPATH. + +Files in this startup-folder will be run in lexicographical order, +so you can control the execution order of files with a prefix, e.g.:: + + 00-foo.py + 10-baz.py + 20-bar.py + +return-statements are not allowed. + +""" +print(f"\nRunning {__file__}") + +import logging + + +import rich + +log: logging.Logger = logging.getLogger("ipython-startup") + + +def demo_urllib(): + """Demo urllib usage.""" + import urllib.request + url = "https://httpbin.org/get" + resp = urllib.request.urlopen(url) + + +def demo_requests(): + """Demo requests usage.""" + import requests + + url = "https://httpbin.org/get" + resp = requests.get(url) + log.info("Requests response status code: %s", resp.status_code) + data = resp.json() + log.info("Requests response data: %s", data) + + rich.print_json(data=data) + +def demo_httpx(): + """Demo httpx usage.""" + import httpx + + url = "https://httpbin.org/get" + log.debug(f"Fetching data from {url}") + resp = httpx.get(url) + log.info("HTTPX response status code: %s", resp.status_code) + data = resp.json() + log.info("HTTPX response data: %s", data) + + rich.print_json(data=data) + + # for key, value in data.items(): + # r + # log.debug("Key: %s; Value: %s", key, value) \ No newline at end of file diff --git a/{{cookiecutter.project_name}}/justfile b/{{cookiecutter.project_name}}/justfile index 3667171..8516892 100644 --- a/{{cookiecutter.project_name}}/justfile +++ b/{{cookiecutter.project_name}}/justfile @@ -54,6 +54,15 @@ import '.just/ubuntu.justfile' import '.just/uv.justfile' -# Display all canonical tasks (default recipe) + + +# default is the first recipe in the justfile. +# Display all tasks (default recipe) +[group: 'default'] list: + @ just --list --unsorted + + +# Display all canonical tasks +list-canonical: @ just --list --unsorted --no-aliases diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 429a124..17f6e91 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -130,7 +130,7 @@ dependencies = [ {%- if cookiecutter.with_fastapi|int %} "fastapi[all]>=0.117", {%- endif %} - "httpx>=0.28", + "httpclient-logging>=1.0", "libranet-logging>=1.5", "python-dateutil>=2.9", "pydantic>=2.9", @@ -160,9 +160,11 @@ docs = [ "sphinx-rtd-theme>=1.0", ] ipython = [ + "httpx>=0.20", # demo-function "ipdb>=0.13", "ipython>=8.0", - "rich>=13.9.4", + "requests>=2.3", # demo-function + "rich>=13.9", ] jupyter = [ "ipykernel>=6.0", @@ -258,7 +260,7 @@ testpaths = ["tests"] cache-dir = "var/cache/ruff" # relative to project_root fix = true line-length = 120 -target-version = "cookiecutter.minimum_python.split('.')[1]|int" +target-version = "py{{''.join(cookiecutter.minimum_python.split('.')[0:2])|int}}" [tool.ruff.lint] select = ["ALL"] diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py index 45e35e2..837b5bd 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py @@ -1,6 +1,6 @@ """{{cookiecutter.package_name}}.""" -from {{cookiecutter.package_name}}._about import ( +from {{cookiecutter.package_name}}.about import ( authors as __author__, license_ as __license__, version as __version__, diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/_about.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/about.py similarity index 57% rename from {{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/_about.py rename to {{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/about.py index 5529844..6701cfc 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/_about.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/about.py @@ -32,23 +32,3 @@ version: str | list[str] = pkginfo.get("version", "unknown") - -# __metadata__: dict = pkginfo - -# __author__: str | list[str] = pkginfo.get("author", "unknown") - -# __author_email__: str | list[str] = pkginfo.get("author_email", "unknown") - -# __copyright__: str | list[str] = pkginfo.get("license", "unknown") - -# __description__: str | list[str] = pkginfo.get("summary", "unknown") - -# __license__: str | list[str] = pkginfo.get("license", "unknown") - -# __pkg_name__: str | list[str] = pkginfo.get("name", "unknown") - -# __readme__: str | list[str] = pkginfo.get("description", "unknown") - -# __url__: str | list[str] = pkginfo.get("project_url", "unknown") - -# __version__: str | list[str] = pkginfo.get("version", "unknown") diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/cli/__init__.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/cli/__init__.py index b5426ef..9271d8b 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/cli/__init__.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/cli/__init__.py @@ -2,10 +2,10 @@ import cyclopts -from {{cookiecutter.package_name}} import __version__ +from {{cookiecutter.package_name}}.about import version from {{cookiecutter.package_name}}.cli.subcmd1 import app_subcmd1 -app: cyclopts.App = cyclopts.App(version=__version__) +app: cyclopts.App = cyclopts.App(version=version) # register subcommands app.command(obj=app_subcmd1) @@ -13,5 +13,5 @@ @app.default def main() -> None: - """Main command when no subcommand is provided.""" + """Main command.""" pass diff --git a/{{cookiecutter.project_name}}/tests/test_cli.py b/{{cookiecutter.project_name}}/tests/test_cli.py index 56dcc0c..24ed2f9 100644 --- a/{{cookiecutter.project_name}}/tests/test_cli.py +++ b/{{cookiecutter.project_name}}/tests/test_cli.py @@ -8,7 +8,7 @@ # assert result.exit_code == 0 -def test_cli(): +def test_cli() -> None: from {{cookiecutter.package_name}}.cli import main # Invoke the main() function @@ -18,7 +18,7 @@ def test_cli(): -def test_app_version(cli_runner): +def test_app_version(cli_runner) -> None: from {{cookiecutter.package_name}}.cli import app result = cli_runner.invoke(app, ["--version"]) From 4c019a50bbf73c7ef8a1bc12667822ad849d17cc Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Wed, 14 Jan 2026 12:59:49 +0100 Subject: [PATCH 14/15] fix mypy cache-dir without envars --- {{cookiecutter.project_name}}/.env.template | 2 -- {{cookiecutter.project_name}}/pyproject.toml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/{{cookiecutter.project_name}}/.env.template b/{{cookiecutter.project_name}}/.env.template index 556f842..dd67401 100644 --- a/{{cookiecutter.project_name}}/.env.template +++ b/{{cookiecutter.project_name}}/.env.template @@ -36,9 +36,7 @@ PYTHONSTARTUP='__CWD__/etc/pythonstartup.py' # caching CACHE_DIR='__CWD__/var/cache/' -BLACK_CACHE_DIR='__CWD__/var/cache/black' IPYTHON_CACHE_DIR='__CWD__/var/cache/ipython' -MYPY_CACHE_DIR='__CWD__/var/cache/mypy' PRE_COMMIT_HOME='__CWD__/var/cache/pre-commit' PYLINTHOME='__CWD__/var/cache/pylint' diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 17f6e91..ea40f67 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -69,7 +69,7 @@ source = ["{{cookiecutter.package_name}}", "tests"] [tool.mypy] # cfr. https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml-file -cache_dir = "var/cache/mypy" +cache_dir = "$MYPY_CONFIG_FILE_DIR/var/cache/mypy" check_untyped_defs = true exclude = "^bin/" ignore_missing_imports = true From 64bf2ba40d72a41e566bd8ed18884805304ab777 Mon Sep 17 00:00:00 2001 From: Wouter Vanden Hove Date: Wed, 14 Jan 2026 13:00:36 +0100 Subject: [PATCH 15/15] allow for other just-executables in justfile --- .../.just/just.justfile | 16 ++++++++-------- {{cookiecutter.project_name}}/justfile | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/{{cookiecutter.project_name}}/.just/just.justfile b/{{cookiecutter.project_name}}/.just/just.justfile index 2e61c2e..c4019b8 100644 --- a/{{cookiecutter.project_name}}/.just/just.justfile +++ b/{{cookiecutter.project_name}}/.just/just.justfile @@ -22,24 +22,24 @@ alias just-update := just-install # show help [group: 'just'] help: - @ just --help + @ {{just_executable()}} --help # Display all canonical tasks and their aliases [group: 'just'] list-all: - @ just --list --unsorted + @ {{just_executable()}} --list --unsorted # select recipe from list [group: 'just'] choose: - @ just --choose --justfile justfile + @ {{just_executable()}} --choose --justfile justfile [group: 'just'] evaluate: - @ just evaluate + @ {{just_executable()}} evaluate # Install or update tab-completion for just-recipes @@ -48,7 +48,7 @@ evaluate: just-install-completions: #!pwsh.exe $filePath = Join-Path $HOME 'completions-just.ps1' - just --completions powershell > $filePath + {{just_executable()}} --completions powershell > $filePath Write-Host "Generated completions-file in $filePath" Write-Host "Add following line to your Powershell-profile:" Write-Host ". ~\completions-just.ps1 -Force"% @@ -60,7 +60,7 @@ just-install-completions: just-update-completions: #!pwsh.exe $filePath = 'etc/just/completions-just.ps1' - just --completions powershell > $filePath + {{just_executable()}} --completions powershell > $filePath Write-Host "Generated new completions-file for powershell in $filePath" @@ -71,12 +71,12 @@ just-update-completions: # Command to get the version of just -JUST_VERSION_COMMAND := `just --version || 1` +JUST_VERSION_COMMAND := `{{just_executable()}} --version || 1` # Show version of just if it's available [group: 'just'] just-show-version: - @ Write-Host "`t just: {{JUST_VERSION_COMMAND}}" + @ Write-Host "`t {{just_executable()}}: {{JUST_VERSION_COMMAND}}" # show location of just.exe diff --git a/{{cookiecutter.project_name}}/justfile b/{{cookiecutter.project_name}}/justfile index 8516892..d13d31a 100644 --- a/{{cookiecutter.project_name}}/justfile +++ b/{{cookiecutter.project_name}}/justfile @@ -65,4 +65,4 @@ list: # Display all canonical tasks list-canonical: - @ just --list --unsorted --no-aliases + @ {{just_executable()}} --list --unsorted --no-aliases