diff --git a/cookiecutter.json b/cookiecutter.json index d8cff6a..f95e42e 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -12,27 +12,21 @@ "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", + "minimum_python": "3.13", "with_django": "0", "with_flask": "0", "with_fastapi": "0", "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}}/.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/ + diff --git a/{{cookiecutter.project_name}}/.env.template b/{{cookiecutter.project_name}}/.env.template index c817a5f..dd67401 100644 --- a/{{cookiecutter.project_name}}/.env.template +++ b/{{cookiecutter.project_name}}/.env.template @@ -30,17 +30,15 @@ # >> 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/' +IPYTHON_CACHE_DIR='__CWD__/var/cache/ipython' +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 @@ -51,16 +49,19 @@ 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" -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 +82,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 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 b36e6c2..0b61540 100644 --- a/{{cookiecutter.project_name}}/.just/bandit.justfile +++ b/{{cookiecutter.project_name}}/.just/bandit.justfile @@ -1,17 +1,11 @@ # bandit, see ../justfile -{% raw -%} - -# show which bandit is used -[group: 'bandit'] -bandit-which: - @ which bandit # run bandit [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,12 +13,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 -{%- 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..0d9eb0e 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 @@ -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 @@ -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..6f1b8de 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" @@ -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 @@ -55,4 +85,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..5d3174b 100644 --- a/{{cookiecutter.project_name}}/.just/ipython.justfile +++ b/{{cookiecutter.project_name}}/.just/ipython.justfile @@ -1,33 +1,28 @@ -{% raw -%} -# ipython - -# symlink ipython to ip -[group: 'ipython'] -symlink-ipython: - @ cd .venv/bin && ln -sf ipython ip +# 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 + +# open ipython-shell in debug-mode [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,6 +30,4 @@ alias ipython-debug := ipython-shell-debug # create an ipython profile [group: 'ipython'] ipython-create-profile name="": - .venv/bin/ipython profile create {{name}} - -{%- endraw %} \ No newline at end of file + uv run ipython profile create {{name}} diff --git a/{{cookiecutter.project_name}}/.just/just.justfile b/{{cookiecutter.project_name}}/.just/just.justfile index 78ae983..c4019b8 100644 --- a/{{cookiecutter.project_name}}/.just/just.justfile +++ b/{{cookiecutter.project_name}}/.just/just.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # just @@ -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 @@ -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/mutmut.justfile b/{{cookiecutter.project_name}}/.just/mutmut.justfile index d1e3396..9a405ab 100644 --- a/{{cookiecutter.project_name}}/.just/mutmut.justfile +++ b/{{cookiecutter.project_name}}/.just/mutmut.justfile @@ -3,59 +3,51 @@ # install mutmut [group: 'mutmut'] -[unix] mutmut-install: - uv ad mutmut + uv add mutmut 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 +mutmut-run *args: + mutmut run {{args}} # browse mutmut results [group: 'mutmut'] -[unix] -mutmut-browse: - mutmut browse +mutmut-browse *args: + mutmut browse {{args}} # show mutmut results [group: 'mutmut'] -[unix] -mutmut-results: - mutmut results +mutmut-results *args: + mutmut results {{args}} # apply mutant by name [group: 'mutmut'] -[unix] -mutmut-apply name="": - mutmut apply {{name}} +mutmut-apply name="" *args: + mutmut apply {{name}} {{args}} # test specific mutant by name [group: 'mutmut'] -[unix] -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 02613c8..221e77f 100644 --- a/{{cookiecutter.project_name}}/.just/mypy.justfile +++ b/{{cookiecutter.project_name}}/.just/mypy.justfile @@ -1,31 +1,21 @@ -{% raw -%} -# mypy - -# show which mypy is used -[group: 'mypy'] -mypy-which: - @ which mypy +# mypy # run mypy on python-files [group: 'mypy'] -mypy args="": mypy-which - mypy src tests {{ args }} - +mypy *args: + uv run 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 }} + uv run mypy src tests --html-report {{ path }} {{ args }} # alias for mypy-report [group: 'mypy'] 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..21c0742 100644 --- a/{{cookiecutter.project_name}}/.just/pre-commit.justfile +++ b/{{cookiecutter.project_name}}/.just/pre-commit.justfile @@ -1,19 +1,10 @@ -{% raw -%} -# See ../justfile - - -# show which pre-commit is used -[group: 'pre-commit'] -pre-commit-which: - @ which pre-commit - -alias precommit-which := pre-commit-which +# See ../justfile # 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,10 +28,9 @@ 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 -{%- 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..ed29b9e 100644 --- a/{{cookiecutter.project_name}}/.just/pylint.justfile +++ b/{{cookiecutter.project_name}}/.just/pylint.justfile @@ -1,28 +1,21 @@ -{% raw -%} -# pylint - -# show which pylint is used -[group: 'pylint'] -pylint-which: - @ which pylint +# 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}} -{%- 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..455e505 100644 --- a/{{cookiecutter.project_name}}/.just/pyroma.justfile +++ b/{{cookiecutter.project_name}}/.just/pyroma.justfile @@ -1,16 +1,8 @@ -{% raw -%} -# See ../justfile - -# show which pyroma is used -[group: 'pyroma'] -pyroma-which: - @ which pyroma +# See ../justfile # run pyroma [group: 'pyroma'] -pyroma: pyroma-which - - pyroma . - -{%- endraw %} \ No newline at end of file +pyroma: + uv run pyroma . diff --git a/{{cookiecutter.project_name}}/.just/pytest.justfile b/{{cookiecutter.project_name}}/.just/pytest.justfile index ddadd9b..5e29025 100644 --- a/{{cookiecutter.project_name}}/.just/pytest.justfile +++ b/{{cookiecutter.project_name}}/.just/pytest.justfile @@ -1,51 +1,50 @@ -{% raw -%} + # pytest # 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}} -{%- 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..b4c72b9 100644 --- a/{{cookiecutter.project_name}}/.just/ruff.justfile +++ b/{{cookiecutter.project_name}}/.just/ruff.justfile @@ -1,49 +1,48 @@ -{% raw -%} + # ruff # 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}} +ruff-check *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}} +ruff-check-fix *args: + uv run ruff check docs/ etc/ src/ tests/ --fix {{args}} 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}} +ruff-check-fix-unsafe *args: + uv run ruff check docs/ etc/ src/ tests/ --fix --unsafe-fixes {{args}} 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}} +ruff-format *args: + uv run ruff 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..47772f0 100644 --- a/{{cookiecutter.project_name}}/.just/safety.justfile +++ b/{{cookiecutter.project_name}}/.just/safety.justfile @@ -1,23 +1,16 @@ -{% raw -%} -# safety - -# show which safety is used -[group: 'safety'] -safety-which: - @ which safety +# 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 -{%- 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..ca913d3 100644 --- a/{{cookiecutter.project_name}}/.just/sshx.justfile +++ b/{{cookiecutter.project_name}}/.just/sshx.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # sshx @@ -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 @@ -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..0ed9473 100644 --- a/{{cookiecutter.project_name}}/.just/ty.justfile +++ b/{{cookiecutter.project_name}}/.just/ty.justfile @@ -1,16 +1,17 @@ -{% raw -%} + # ty # 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}} + -{%- 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..934164b 100644 --- a/{{cookiecutter.project_name}}/.just/uv.justfile +++ b/{{cookiecutter.project_name}}/.just/uv.justfile @@ -1,4 +1,4 @@ -{% raw -%} + # uv @@ -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,31 +75,30 @@ 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}} # 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,11 +108,9 @@ 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 @ echo -e "Set python version to {{version}}" -{%- endraw %} \ No newline at end of file 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 182820b..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}/.venv/bin/python", - "python.linting.enabled": true, - "python.linting.banditArgs": [ - "--configfile", - "${workspaceFolder}/pyproject.toml", - "--recursive" - ], "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}}/etc/logging.yaml b/{{cookiecutter.project_name}}/etc/logging.yaml index 5f65f53..e3df6b8 100644 --- a/{{cookiecutter.project_name}}/etc/logging.yaml +++ b/{{cookiecutter.project_name}}/etc/logging.yaml @@ -52,6 +52,7 @@ filters: - "^: , - # 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. @@ -138,15 +115,15 @@ handlers: filters: - "regex-filter" - console_http: - class: "logging.StreamHandler" - # formatter: !env PYTHON_CONSOLE_FORMATTER, console_bw - # formatter: console_http - formatter: console_color_http - level: !env LOGLEVEL_CONSOLE, DEBUG - stream: "ext://sys.stdout" - filters: - - "regex-filter" + # console_http: + # class: "logging.StreamHandler" + # # formatter: !env PYTHON_CONSOLE_FORMATTER, console_bw + # # formatter: console_http + # formatter: console_color_http + # level: !env LOGLEVEL_CONSOLE, DEBUG + # stream: "ext://sys.stdout" + # filters: + # - "regex-filter" debug_file: &default class: "logging.handlers.RotatingFileHandler" @@ -200,17 +177,17 @@ handlers: formatter: !env PYTHON_CONSOLE_FORMATTER, console_bw {% endif %} - uvicorn_default: - formatter: "uvicorn_default" - class: "logging.StreamHandler" - stream: "ext://sys.stderr" - level: "DEBUG" + # uvicorn_default: + # formatter: "uvicorn_default" + # class: "logging.StreamHandler" + # stream: "ext://sys.stderr" + # level: "DEBUG" - uvicorn_access: - formatter: "uvicorn_access" - class: "logging.StreamHandler" - stream: "ext://sys.stdout" - level: "DEBUG" + # uvicorn_access: + # formatter: "uvicorn_access" + # class: "logging.StreamHandler" + # stream: "ext://sys.stdout" + # level: "DEBUG" loggers: # Notes: @@ -247,20 +224,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 +273,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 diff --git a/{{cookiecutter.project_name}}/justfile b/{{cookiecutter.project_name}}/justfile index 3667171..d13d31a 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 --no-aliases + @ just --list --unsorted + + +# Display all canonical tasks +list-canonical: + @ {{just_executable()}} --list --unsorted --no-aliases diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 498faba..ea40f67 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,31 +67,9 @@ 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" +cache_dir = "$MYPY_CONFIG_FILE_DIR/var/cache/mypy" check_untyped_defs = true exclude = "^bin/" ignore_missing_imports = true @@ -126,35 +106,20 @@ 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", "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", @@ -162,17 +127,14 @@ dependencies = [ # "wagtail>=4.1", "watchfiles>=0.18.0", {%- endif %} - {%- 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", ] - - [dependency-groups] dev = [ "pylint>=3.1.0", @@ -198,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", @@ -251,53 +215,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,10 +228,8 @@ 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] +# https://pylint.pycqa.org/en/stable/user_guide max-line-length = 120 good-names = [ "foo", # dummy variable @@ -327,75 +242,114 @@ 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 +fix = true line-length = 120 +target-version = "py{{''.join(cookiecutter.minimum_python.split('.')[0:2])|int}}" [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 [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 = ["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 = [ +default-groups = "all" +# 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/*"] + diff --git a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/__init__.py index af1ae0e..837b5bd 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 similarity index 50% rename from {{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/_about.py rename to {{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/about.py index 91d41f9..6701cfc 100644 --- a/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/_about.py +++ b/{{cookiecutter.project_name}}/src/{{cookiecutter.package_name}}/about.py @@ -24,22 +24,11 @@ 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") - -__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"])