From 044e45f3002b2d2d0a814223d3701e26409926c3 Mon Sep 17 00:00:00 2001 From: danibcorr <77023868+danibcorr@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:11:09 +0100 Subject: [PATCH 1/3] Added tests, modified Makefile commands, removed nbqa and more --- .devcontainer/README.md | 40 --- .devcontainer/devcontainer.json | 78 ---- .devcontainer/setup.sh | 17 - .github/CONTRIBUTING.md | 6 +- .github/actions/build-mkdocs/action.yml | 19 +- .github/actions/create-release/action.yml | 40 +-- .github/actions/setup-python-env/action.yml | 31 +- AGENTS.md | 8 +- Makefile | 23 +- README.md | 2 +- docs/content/makefile-commands.md | 1 - docs/content/pyproject-configuration.md | 1 - docs/index.md | 2 +- mkdocs.yml | 11 +- pyproject.toml | 19 +- tests/__init__.py | 0 tests/conftest.py | 16 + tests/test_cookiecutter_template.py | 333 ++++++++++++++++++ tests/test_post_gen_project.py | 121 +++++++ uv.lock | 62 ++-- .../.devcontainer/setup.sh | 2 +- .../.github/CONTRIBUTING.md | 6 +- .../.github/actions/build-mkdocs/action.yml | 19 +- .../.github/actions/create-release/action.yml | 40 +-- .../.github/actions/lint-code/action.yml | 23 +- .../.github/actions/security/action.yml | 5 +- .../actions/setup-python-env/action.yml | 31 +- .../.github/actions/test-code/action.yml | 9 +- .../.pre-commit-config.yaml | 2 +- .../.vscode/settings.json | 1 - {{ cookiecutter.project_name }}/Makefile | 19 +- {{ cookiecutter.project_name }}/README.md | 2 +- .../pyproject.toml | 1 - .../__init__.py | 4 +- .../version.py | 3 +- 35 files changed, 606 insertions(+), 391 deletions(-) delete mode 100644 .devcontainer/README.md delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/setup.sh create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/test_cookiecutter_template.py create mode 100644 tests/test_post_gen_project.py diff --git a/.devcontainer/README.md b/.devcontainer/README.md deleted file mode 100644 index 36c1399..0000000 --- a/.devcontainer/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# Dev Container Configuration - -This directory contains the configuration files for setting up a development container. -These configurations are compatible with **GitHub Codespaces**, **Visual Studio Code**, -and **JetBrains IDEs**, and provide a pre-configured environment with all necessary -dependencies for development. - -## GitHub Codespaces - -To launch a dev container using GitHub Codespaces: - -1. Navigate to the repository's main page. -2. Click the **"Code"** button. -3. Select the **"Codespaces"** tab. -4. Click the **"+"** button to create a new codespace. - -The container will be initialized automatically using the configurations in this -directory. - -[GitHub Codespaces Documentation](https://docs.github.com/en/codespaces/developing-in-a-codespace/creating-a-codespace-for-a-repository) - -## Visual Studio Code - -To use the dev container in VS Code: - -1. Open the root folder of the repository in Visual Studio Code. -2. A prompt will appear asking if you want to reopen the folder in a dev container. -3. Confirm by selecting **"Reopen in Container"**. - -[VS Code Dev Containers Guide](https://code.visualstudio.com/docs/devcontainers/tutorial) - -## JetBrains IDEs - -To open the dev container in a JetBrains IDE (e.g., IntelliJ IDEA, PyCharm): - -1. Open the `.devcontainer/devcontainer.json` file in your IDE. -2. Click the Docker icon that appears in the UI. -3. Follow the prompts to create and open the dev container. - -[JetBrains Dev Container Integration Guide](https://www.jetbrains.com/help/idea/connect-to-devcontainer.html) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index a2652e3..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "image": "mcr.microsoft.com/vscode/devcontainers/python:3.11", - "postCreateCommand": "sh ./.devcontainer/setup.sh", - "customizations": { - "vscode": { - "settings": { - "todo-tree.regex.enableMultiLine": true, - "editor.rulers": [ - { - "column": 72, - "color": "#4a4f63" - }, - { - "column": 88, - "color": "#7a8ad1" - } - ], - "autoDocstring.docstringFormat": "google-notypes", - "autoDocstring.startOnNewLine": true, - "better-comments.tags": [ - { - "tag": "!!", - "color": "#F6FF33", - "strikethrough": false, - "backgroundColor": "transparent" - }, - { - "tag": "#!", - "color": "#3498DB", - "strikethrough": false, - "backgroundColor": "transparent" - }, - { - "tag": "TODO", - "color": "#FF8C00", - "strikethrough": false, - "backgroundColor": "transparent" - }, - { - "tag": "//", - "color": "#68FF33", - "strikethrough": false, - "backgroundColor": "transparent" - }, - { - "tag": "**", - "color": "#FF33EC", - "strikethrough": false, - "backgroundColor": "transparent" - } - ], - "[python]": { - "editor.formatOnType": true - }, - "python.defaultInterpreterPath": "/home/jovyan/envs/env/bin/python", - "[jsonc]": { - "editor.defaultFormatter": "vscode.json-language-features" - }, - "[json]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, - "prettier.printWidth": 88, - "prettier.proseWrap": "always" - }, - "extensions": [ - "njpwerner.autodocstring", - "aaron-bond.better-comments", - "esbenp.prettier-vscode", - "ms-python.python", - "ms-python.debugpy", - "Gruntfuggly.todo-tree" - ] - } - }, - "features": { - "ghcr.io/devcontainers/features/github-cli:1": {} - } -} diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh deleted file mode 100644 index 658115b..0000000 --- a/.devcontainer/setup.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -e - -echo "Installing uv..." -curl -LsSf https://astral.sh/uv/install.sh | sh > /dev/null 2>&1 -echo "✅ uv installed." - -echo "Installing system dependencies..." -sudo apt-get update > /dev/null 2>&1 -sudo apt-get install -y build-essential > /dev/null 2>&1 -echo "✅ System dependencies installed." - -echo "Installing Python dependencies with Makefile..." -make install > /dev/null 2>&1 - -echo "✅ Devcontainer setup complete." \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index fa54ff5..0f0cf75 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -27,8 +27,8 @@ You can contribute by: Start by opening an issue to describe your proposed change or the problem you encountered. This helps maintainers review and guide the work before coding begins. -> For minor changes, such as typo fixes, you may skip this step and submit a pull -> request directly. +> For minor changes, such as typo fixes, you may skip this step and submit a pull request +> directly. ### Step 2: Make Your Changes @@ -75,7 +75,7 @@ To set up locally: 3. Clone the repository and run: ```bash -make install +make setup ``` > If using `uv`, a compatible virtual environment will be created automatically. diff --git a/.github/actions/build-mkdocs/action.yml b/.github/actions/build-mkdocs/action.yml index fb2ddd0..847a095 100644 --- a/.github/actions/build-mkdocs/action.yml +++ b/.github/actions/build-mkdocs/action.yml @@ -18,15 +18,8 @@ runs: - name: Setup gh-pages Branch shell: bash run: | - # Store current branch - CURRENT_BRANCH=$(git branch --show-current) - - # Check if gh-pages branch exists - if git ls-remote --heads origin gh-pages | grep -q gh-pages; then - echo "gh-pages branch exists, fetching..." - git fetch origin gh-pages:gh-pages - else - echo "gh-pages branch doesn't exist, creating..." + if ! git ls-remote --heads origin gh-pages | grep -q gh-pages; then + CURRENT_BRANCH=$(git branch --show-current) git checkout --orphan gh-pages git reset --hard git commit --allow-empty -m "Initial gh-pages commit" @@ -34,14 +27,8 @@ runs: git checkout "$CURRENT_BRANCH" fi - - name: Build and Deploy Docs with Mike + - name: Build and Deploy Docs shell: bash run: | - echo "Deploying Docs Version: ${{ inputs.docs-version }}" uv run mike deploy --push --update-aliases "${{ inputs.docs-version }}" latest - - - name: Set Default Version to Docs - shell: bash - run: | - echo "Setting 'Latest' as Default Version" uv run mike set-default --push latest diff --git a/.github/actions/create-release/action.yml b/.github/actions/create-release/action.yml index 3d91904..4afd27d 100644 --- a/.github/actions/create-release/action.yml +++ b/.github/actions/create-release/action.yml @@ -26,52 +26,24 @@ runs: run: | if gh release view "${{ inputs.version }}" >/dev/null 2>&1; then echo "exists=true" >> $GITHUB_OUTPUT - echo "Release ${{ inputs.version }} already exists" else echo "exists=false" >> $GITHUB_OUTPUT - echo "Release ${{ inputs.version }} doesn't exist" fi env: GH_TOKEN: ${{ inputs.token }} - - name: Generate Release Notes + - name: Create Release if: steps.check_release.outputs.exists == 'false' shell: bash run: | - echo "Generating release notes..." LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + RANGE="${LAST_TAG:+$LAST_TAG..HEAD}" + COMMITS=$(git log ${RANGE:---max-count=10} --pretty=format:"- %s" --no-merges) - if [ -z "$LAST_TAG" ]; then - echo "No previous tags found, using last 10 commits..." - COMMITS=$(git log --pretty=format:"- %s" --no-merges -10) - else - echo "Previous tag found: $LAST_TAG" - COMMITS=$(git log "${LAST_TAG}..HEAD" --pretty=format:"- %s" --no-merges) - fi - - cat > release-notes.md << EOF - ## Changes - - $COMMITS - EOF - - echo "Release notes generated:" - cat release-notes.md - - - name: Create Release - if: steps.check_release.outputs.exists == 'false' - shell: bash - run: | - echo "Creating release ${{ inputs.version }}..." gh release create "${{ inputs.version }}" \ --title "${{ inputs.version }}" \ - --notes-file release-notes.md - echo "Release ${{ inputs.version }} created successfully" + --notes "## Changes + + $COMMITS" env: GH_TOKEN: ${{ inputs.token }} - - - name: Skip Release - if: steps.check_release.outputs.exists == 'true' - shell: bash - run: | - echo "Skipping release creation - ${{ inputs.version }} already exists" diff --git a/.github/actions/setup-python-env/action.yml b/.github/actions/setup-python-env/action.yml index 2a553f6..ef503ec 100644 --- a/.github/actions/setup-python-env/action.yml +++ b/.github/actions/setup-python-env/action.yml @@ -25,39 +25,20 @@ runs: enable-cache: true - name: Install dependencies with uv - id: install-deps shell: bash run: | - # Check if we should install all extras - if [ -z "${{ inputs.uv-group }}" ] && [ -z "${{ inputs.uv-extra }}" ]; then - echo "Installing all extras (default when no group or extra specified)..." - uv sync --all-extras - elif [ "${{ inputs.uv-extra }}" = "--all-extras" ]; then - echo "Installing all extras (explicitly requested)..." - if [ -n "${{ inputs.uv-group }}" ]; then - echo "Note: Installing all extras overrides the specified group: ${{ inputs.uv-group }}" - fi - uv sync --all-extras + ARGS="" + if [ "${{ inputs.uv-extra }}" = "--all-extras" ] || [ -z "${{ inputs.uv-group }}${{ inputs.uv-extra }}" ]; then + ARGS="--all-extras" else - echo "Installing with group: ${{ inputs.uv-group }}, and extra: ${{ inputs.uv-extra }}..." - if [ -n "${{ inputs.uv-group }}" ] && [ -n "${{ inputs.uv-extra }}" ]; then - uv sync --group ${{ inputs.uv-group }} --extra ${{ inputs.uv-extra }} - elif [ -n "${{ inputs.uv-group }}" ]; then - uv sync --group ${{ inputs.uv-group }} - elif [ -n "${{ inputs.uv-extra }}" ]; then - uv sync --extra ${{ inputs.uv-extra }} - else - uv sync - fi + [ -n "${{ inputs.uv-group }}" ] && ARGS="$ARGS --group ${{ inputs.uv-group }}" + [ -n "${{ inputs.uv-extra }}" ] && ARGS="$ARGS --extra ${{ inputs.uv-extra }}" fi + uv sync $ARGS - name: Verify uv and environment - id: verify shell: bash run: | - echo "uv version:" uv --version - echo "Virtual environments:" uv venv list - echo "Python version:" uv run python --version diff --git a/AGENTS.md b/AGENTS.md index 8c67fba..cc37180 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,9 +9,9 @@ many optional settings and Jinja2 template blocks. Before interacting with the template, make sure to: -1. **Review the README**: The `README.md` provides a high-level overview of the - template, its features, CI/CD workflow, and instructions to generate new projects. - Key features include: +1. **Review the README**: The `README.md` provides a high-level overview of the template, + its features, CI/CD workflow, and instructions to generate new projects. Key features + include: - Linting & type checking (Ruff & Mypy) - Security scanning (Bandit) @@ -36,7 +36,7 @@ Before interacting with the template, make sure to: - Create new projects inside the `workspaces/` directory. - Run the Makefile targets to validate functionality: - - `make install` → installs dependencies + - `make setup` → installs dependencies - `make pipeline` → runs linting, type checking, security analysis, complexity checks, and tests - `make all` → full workflow including documentation preview diff --git a/Makefile b/Makefile index 2c43880..d3c3810 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,13 @@ .PHONY: setup \ clean-cache-temp-files \ - lint code-check \ + lint code-check test \ doc \ pipeline all .DEFAULT_GOAL := all -SRC_PROJECT_HOOKS ?= hooks +PATH_PROJECT_ROOT ?= . +PATH_TEST ?= tests setup: @echo "Installing dependencies..." @@ -23,23 +24,27 @@ clean-cache-temp-files: lint: @echo "Running lint checks..." - @uv run isort $(SRC_PROJECT_HOOKS)/ - @uv run ruff check --fix $(SRC_PROJECT_HOOKS)/ - @uv run ruff format $(SRC_PROJECT_HOOKS)/ + @uv run isort $(PATH_PROJECT_ROOT) + @uv run ruff check --fix $(PATH_PROJECT_ROOT) + @uv run ruff format $(PATH_PROJECT_ROOT) @echo "✅ Linting complete." code-check: @echo "Running static code checks..." - @uv run mypy $(SRC_PROJECT_HOOKS)/ - @uv run complexipy -f $(SRC_PROJECT_HOOKS)/ - @uv run bandit -r $(SRC_PROJECT_HOOKS)/ + @uv run mypy $(PATH_PROJECT_ROOT) + @uv run complexipy -f $(PATH_PROJECT_ROOT) @echo "✅ Code and security checks complete." +test: + @echo "Running tests..." + @uv run pytest $(PATH_TEST) -v + @echo "✅ Tests complete." + doc: @echo "Serving documentation..." @uv run mkdocs serve -pipeline: clean-cache-temp-files lint code-check +pipeline: clean-cache-temp-files lint code-check test @echo "✅ Pipeline complete." all: setup pipeline doc diff --git a/README.md b/README.md index 9977947..41a2157 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ grouped dependency installations. `Makefile`: ```bash - make install + make setup ``` This installs development, testing, and documentation tools as defined in diff --git a/docs/content/makefile-commands.md b/docs/content/makefile-commands.md index c319e75..11779a2 100644 --- a/docs/content/makefile-commands.md +++ b/docs/content/makefile-commands.md @@ -42,7 +42,6 @@ readability and maintainability. It integrates several tools: - **isort**: Organizes imports alphabetically and categorically. - **Ruff**: A high-performance linter that detects and fixes style issues. -- **nbqa**: Applies linting to Jupyter notebooks. Typical automatic fixes include removing unused imports, correcting spacing and formatting, and reorganizing imports according to PEP 8 conventions. This command is diff --git a/docs/content/pyproject-configuration.md b/docs/content/pyproject-configuration.md index 03546c0..c6a8f4c 100644 --- a/docs/content/pyproject-configuration.md +++ b/docs/content/pyproject-configuration.md @@ -48,7 +48,6 @@ automation: - `complexipy`: Measures cyclomatic complexity to identify potentially unmaintainable code. - `isort`: Sorts imports according to standard conventions. -- `nbqa`: Extends linting capabilities to Jupyter notebooks. - `deadcode`: Detects unused or unreachable code. - `pre-commit`: Manages Git pre-commit hooks for automated checks. diff --git a/docs/index.md b/docs/index.md index 9f48e68..0ff2747 100644 --- a/docs/index.md +++ b/docs/index.md @@ -54,7 +54,7 @@ grouped dependency installations. `Makefile`: ```bash - make install + make setup ``` This installs development, testing, and documentation tools as defined in diff --git a/mkdocs.yml b/mkdocs.yml index 9f66c19..7dc1487 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -2,21 +2,19 @@ site_name: Python project template site_description: Python project template repo_name: python-project-template repo_url: https://github.com/danibcorr/python-project-template -site_url: https://https://danibcorr.github.io/python-project-template +site_url: https://danibcorr.github.io/python-project-template theme: name: "material" language: en palette: - - media: "(prefers-color-scheme: dark)" - scheme: slate + - scheme: slate primary: black accent: black toggle: icon: material/weather-night name: "Switch to light mode" - - media: "(prefers-color-scheme: light)" - scheme: default + - scheme: default primary: black accent: black toggle: @@ -54,6 +52,9 @@ theme: use_directory_urls: false markdown_extensions: + - tables + - toc: + toc_depth: 2 - admonition - pymdownx.details - pymdownx.superfences: diff --git a/pyproject.toml b/pyproject.toml index 4d41e49..ceee8b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-project-template" -version = "2.5.1" +version = "2.6.0" description = "Python project template" authors = [ {name = "Daniel Bazo Correa", email = "none@none.com"}, @@ -34,11 +34,11 @@ required-environments = [ [dependency-groups] pipeline = [ - "bandit==1.9.2", "complexipy==5.1.0", "mypy==1.19.1", "ruff==0.14.11", - "isort==7.0.0" + "isort==7.0.0", + "pytest==9.0.2" ] documentation = [ "mkdocs==1.6.1", @@ -50,7 +50,6 @@ documentation = [ "mike==2.1.3" ] - [tool.ruff] line-length = 88 indent-width = 4 @@ -82,7 +81,7 @@ exclude = [ "site-packages", "venv", ] -extend-exclude = ["*.ipynb"] +extend-exclude = ["*.ipynb", "{{*}}"] [tool.ruff.lint] select = ["E", "F", "UP", "B", "SIM"] @@ -101,16 +100,20 @@ docstring-code-line-length = 88 [tool.mypy] check_untyped_defs = true ignore_missing_imports = true +explicit_package_bases = true exclude = [ "^(build|dist|venv)", - ".venv/" + ".venv/", + "{{ cookiecutter.project_name }}", ] cache_dir = "/dev/null" +[tool.complexipy] +exclude = ["{{ cookiecutter.project_name }}"] + [tool.isort] -known_first_party = ["{{ cookiecutter.project_module_name }}"] sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] import_heading_stdlib = "Standard libraries" import_heading_thirdparty = "3pps" import_heading_firstparty = "Own modules" -line_length = 88 \ No newline at end of file +line_length = 88 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..93c6851 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,16 @@ +# 3pps +import pytest + + +@pytest.fixture(autouse=True) +def reset_modules(): + """ + Reset imported modules between tests. + + Ensures test isolation by clearing module state after each test. + + Returns: + None + """ + + yield diff --git a/tests/test_cookiecutter_template.py b/tests/test_cookiecutter_template.py new file mode 100644 index 0000000..de405f7 --- /dev/null +++ b/tests/test_cookiecutter_template.py @@ -0,0 +1,333 @@ +# Standard libraries +import json +import subprocess +from pathlib import Path + +# 3pps +import pytest + + +@pytest.fixture +def template_dir() -> Path: + """ + Get the template directory path. + + Returns: + The absolute path to the cookiecutter template root + directory. + """ + + return Path(__file__).parent.parent + + +@pytest.fixture +def cookiecutter_config() -> dict: + """ + Load cookiecutter configuration. + + Returns: + Dictionary containing the parsed cookiecutter.json + configuration. + """ + + config_path = Path(__file__).parent.parent / "cookiecutter.json" + return json.loads(config_path.read_text()) + + +def test_cookiecutter_json_exists(template_dir: Path) -> None: + """ + Test that cookiecutter.json exists. + + Args: + template_dir: The absolute path to the cookiecutter template + root directory. + + Returns: + None + """ + + assert (template_dir / "cookiecutter.json").exists() + + +def test_cookiecutter_json_valid(cookiecutter_config: dict) -> None: + """ + Test that cookiecutter.json is valid JSON with required fields. + + Args: + cookiecutter_config: Dictionary containing the parsed + cookiecutter.json configuration. + + Returns: + None + """ + + required_fields = [ + "project_name", + "project_module_name", + "project_test_folder_name", + "project_version", + ] + for field in required_fields: + assert field in cookiecutter_config + + +def test_template_generation(tmp_path: Path, template_dir: Path) -> None: + """ + Test that cookiecutter generates a valid project. + + Args: + tmp_path: Temporary directory path provided by pytest. + template_dir: The absolute path to the cookiecutter template + root directory. + + Returns: + None + """ + + result = subprocess.run( + [ + "uv", + "run", + "cookiecutter", + str(template_dir), + "--no-input", + "--output-dir", + str(tmp_path), + ], + capture_output=True, + text=True, + ) + + assert result.returncode == 0, f"Failed: {result.stderr}" + + generated_project = tmp_path / "project-name" + assert generated_project.exists() + + +def test_generated_project_structure(tmp_path: Path, template_dir: Path) -> None: + """ + Test that generated project has correct structure. + + Args: + tmp_path: Temporary directory path provided by pytest. + template_dir: The absolute path to the cookiecutter template + root directory. + + Returns: + None + """ + + subprocess.run( + [ + "uv", + "run", + "cookiecutter", + str(template_dir), + "--no-input", + "--output-dir", + str(tmp_path), + ], + check=True, + ) + + project = tmp_path / "project-name" + expected_files = [ + "pyproject.toml", + "Makefile", + "README.md", + "mkdocs.yml", + ".gitignore", + ] + + for file in expected_files: + assert (project / file).exists(), f"Missing {file}" + + +def test_generated_project_folders(tmp_path: Path, template_dir: Path) -> None: + """ + Test that generated project has correct folders. + + Args: + tmp_path: Temporary directory path provided by pytest. + template_dir: The absolute path to the cookiecutter template + root directory. + + Returns: + None + """ + + subprocess.run( + [ + "uv", + "run", + "cookiecutter", + str(template_dir), + "--no-input", + "--output-dir", + str(tmp_path), + ], + check=True, + ) + + project = tmp_path / "project-name" + expected_folders = ["src", "tests", "docs", "config"] + + for folder in expected_folders: + assert (project / folder).exists(), f"Missing {folder}" + + +@pytest.mark.parametrize( + "folder_option,folder_name", + [ + ("add_dev_container_folder", ".devcontainer"), + ("add_vscode_folder", ".vscode"), + ("add_notebooks_folder", "notebooks"), + ("add_prompts_folder", "prompts"), + ], +) +def test_optional_folders_yes(tmp_path: Path, template_dir: Path, folder_option: str, folder_name: str) -> None: + """ + Test that optional folders are created when enabled. + + Args: + tmp_path: Temporary directory path provided by pytest. + template_dir: The absolute path to the cookiecutter template + root directory. + folder_option: Cookiecutter configuration option name. + folder_name: Name of the folder to verify. + + Returns: + None + """ + + subprocess.run( + [ + "uv", + "run", + "cookiecutter", + str(template_dir), + "--no-input", + f"{folder_option}=yes", + "--output-dir", + str(tmp_path), + ], + check=True, + ) + + project = tmp_path / "project-name" + assert (project / folder_name).exists() + + +@pytest.mark.parametrize( + "folder_option,folder_name", + [ + ("add_dev_container_folder", ".devcontainer"), + ("add_vscode_folder", ".vscode"), + ("add_notebooks_folder", "notebooks"), + ("add_prompts_folder", "prompts"), + ], +) +def test_optional_folders_no(tmp_path: Path, template_dir: Path, folder_option: str, folder_name: str) -> None: + """ + Test that optional folders are removed when disabled. + + Args: + tmp_path: Temporary directory path provided by pytest. + template_dir: The absolute path to the cookiecutter template + root directory. + folder_option: Cookiecutter configuration option name. + folder_name: Name of the folder to verify. + + Returns: + None + """ + + subprocess.run( + [ + "uv", + "run", + "cookiecutter", + str(template_dir), + "--no-input", + f"{folder_option}=no", + "--output-dir", + str(tmp_path), + ], + check=True, + ) + + project = tmp_path / "project-name" + assert not (project / folder_name).exists() + + +def test_generated_pyproject_valid(tmp_path: Path, template_dir: Path) -> None: + """ + Test that generated pyproject.toml is valid. + + Args: + tmp_path: Temporary directory path provided by pytest. + template_dir: The absolute path to the cookiecutter template + root directory. + + Returns: + None + """ + + subprocess.run( + [ + "uv", + "run", + "cookiecutter", + str(template_dir), + "--no-input", + "project_name=test-project", + "project_module_name=test_module", + "--output-dir", + str(tmp_path), + ], + check=True, + ) + + pyproject = tmp_path / "test-project" / "pyproject.toml" + content = pyproject.read_text() + + assert "test-project" in content + assert "test_module" in content + assert "[project]" in content + assert "[tool.uv]" in content + + +def test_makefile_commands_exist(tmp_path: Path, template_dir: Path) -> None: + """ + Test that Makefile has required commands. + + Args: + tmp_path: Temporary directory path provided by pytest. + template_dir: The absolute path to the cookiecutter template + root directory. + + Returns: + None + """ + + subprocess.run( + [ + "uv", + "run", + "cookiecutter", + str(template_dir), + "--no-input", + "--output-dir", + str(tmp_path), + ], + check=True, + ) + + makefile = tmp_path / "project-name" / "Makefile" + content = makefile.read_text() + + required_commands = ["setup", "lint", "code-check", "tests", "pipeline"] + for cmd in required_commands: + assert f"{cmd}:" in content, f"Missing command: {cmd}" + + assert "uv sync" in content or "uv run" in content diff --git a/tests/test_post_gen_project.py b/tests/test_post_gen_project.py new file mode 100644 index 0000000..68b1ccf --- /dev/null +++ b/tests/test_post_gen_project.py @@ -0,0 +1,121 @@ +# Standard libraries +from pathlib import Path +from unittest.mock import patch + +# 3pps +import pytest + +# Own modules +from hooks.post_gen_project import remove + + +@pytest.fixture +def mock_project_dir(tmp_path: Path) -> Path: + """ + Create a temporary project directory with test folders. + + Generates a mock project structure containing the standard + optional folders (.devcontainer, .vscode, notebooks, prompts) + with a test file in each. + + Args: + tmp_path: Temporary directory path provided by pytest. + + Returns: + The path to the temporary project directory. + """ + + folders = [".devcontainer", ".vscode", "notebooks", "prompts"] + for folder in folders: + (tmp_path / folder).mkdir() + (tmp_path / folder / "test.txt").write_text("test") + + return tmp_path + + +def test_remove_file(tmp_path: Path) -> None: + """ + Test removing a file. + + Args: + tmp_path: Temporary directory path provided by pytest. + + Returns: + None + """ + + test_file = tmp_path / "test.txt" + test_file.write_text("test") + assert test_file.exists() + + remove(test_file) + assert not test_file.exists() + + +def test_remove_directory(tmp_path: Path) -> None: + """ + Test removing a directory. + + Args: + tmp_path: Temporary directory path provided by pytest. + + Returns: + None + """ + + test_dir = tmp_path / "test_dir" + test_dir.mkdir() + (test_dir / "file.txt").write_text("test") + assert test_dir.exists() + + remove(test_dir) + assert not test_dir.exists() + + +def test_remove_nonexistent_path(tmp_path: Path) -> None: + """ + Test removing a non-existent path does not raise error. + + Args: + tmp_path: Temporary directory path provided by pytest. + + Returns: + None + """ + + nonexistent = tmp_path / "nonexistent" + remove(nonexistent) + + +@patch("hooks.post_gen_project.project_dir") +@patch("hooks.post_gen_project.remove") +def test_folder_removal_logic(mock_remove, mock_project_dir, tmp_path: Path) -> None: + """ + Test that folders are removed when not enabled. + + Verifies the post-generation hook correctly removes optional + folders based on cookiecutter configuration. + + Args: + mock_remove: Mock of the remove function. + mock_project_dir: Mock of the project directory path. + tmp_path: Temporary directory path provided by pytest. + + Returns: + None + """ + + mock_project_dir.__truediv__ = lambda self, x: tmp_path / x + + folders = { + ".devcontainer": "no", + ".vscode": "yes", + "notebooks": "no", + "prompts": "yes", + } + + for folder, enabled in folders.items(): + if enabled != "yes": + mock_remove(mock_project_dir / folder) + + assert mock_remove.call_count == 2 diff --git a/uv.lock b/uv.lock index c6fcb93..b86a5c9 100644 --- a/uv.lock +++ b/uv.lock @@ -57,20 +57,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/02/e3/a4fa1946722c4c7b063cc25043a12d9ce9b4323777f89643be74cef2993c/backrefs-6.1-py39-none-any.whl", hash = "sha256:a9e99b8a4867852cad177a6430e31b0f6e495d65f8c6c134b68c14c3c95bf4b0", size = 381058, upload-time = "2025-11-15T14:52:06.698Z" }, ] -[[package]] -name = "bandit" -version = "1.9.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "pyyaml", marker = "sys_platform == 'linux'" }, - { name = "rich", marker = "sys_platform == 'linux'" }, - { name = "stevedore", marker = "sys_platform == 'linux'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/cf/72/f704a97aac430aeb704fa16435dfa24fbeaf087d46724d0965eb1f756a2c/bandit-1.9.2.tar.gz", hash = "sha256:32410415cd93bf9c8b91972159d5cf1e7f063a9146d70345641cd3877de348ce", size = 4241659, upload-time = "2025-11-23T21:36:18.722Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/55/1a/5b0320642cca53a473e79c7d273071b5a9a8578f9e370b74da5daa2768d7/bandit-1.9.2-py3-none-any.whl", hash = "sha256:bda8d68610fc33a6e10b7a8f1d61d92c8f6c004051d5e946406be1fb1b16a868", size = 134377, upload-time = "2025-11-23T21:36:17.39Z" }, -] - [[package]] name = "beautifulsoup4" version = "4.14.3" @@ -325,6 +311,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, ] +[[package]] +name = "iniconfig" +version = "2.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, +] + [[package]] name = "isort" version = "7.0.0" @@ -689,6 +684,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/cb/28/3bfe2fa5a7b9c46fe7e13c97bda14c895fb10fa2ebf1d0abb90e0cea7ee1/platformdirs-4.5.1-py3-none-any.whl", hash = "sha256:d03afa3963c806a9bed9d5125c8f4cb2fdaf74a55ab60e5d59b3fde758104d31", size = 18731, upload-time = "2025-12-05T13:52:56.823Z" }, ] +[[package]] +name = "pluggy" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, +] + [[package]] name = "pydantic" version = "2.12.5" @@ -800,6 +804,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8b/40/2614036cdd416452f5bf98ec037f38a1afb17f327cb8e6b652d4729e0af8/pyparsing-3.3.1-py3-none-any.whl", hash = "sha256:023b5e7e5520ad96642e2c6db4cb683d3970bd640cdf7115049a6e9c3682df82", size = 121793, upload-time = "2025-12-23T03:14:02.103Z" }, ] +[[package]] +name = "pytest" +version = "9.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "iniconfig", marker = "sys_platform == 'linux'" }, + { name = "packaging", marker = "sys_platform == 'linux'" }, + { name = "pluggy", marker = "sys_platform == 'linux'" }, + { name = "pygments", marker = "sys_platform == 'linux'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d1/db/7ef3487e0fb0049ddb5ce41d3a49c235bf9ad299b6a25d5780a89f19230f/pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11", size = 1568901, upload-time = "2025-12-06T21:30:51.014Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b", size = 374801, upload-time = "2025-12-06T21:30:49.154Z" }, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -814,7 +833,7 @@ wheels = [ [[package]] name = "python-project-template" -version = "2.5.0" +version = "2.5.1" source = { virtual = "." } dependencies = [ { name = "cookiecutter", marker = "sys_platform == 'linux'" }, @@ -831,10 +850,10 @@ documentation = [ { name = "mkdocs-material", marker = "sys_platform == 'linux'" }, ] pipeline = [ - { name = "bandit", marker = "sys_platform == 'linux'" }, { name = "complexipy", marker = "sys_platform == 'linux'" }, { name = "isort", marker = "sys_platform == 'linux'" }, { name = "mypy", marker = "sys_platform == 'linux'" }, + { name = "pytest", marker = "sys_platform == 'linux'" }, { name = "ruff", marker = "sys_platform == 'linux'" }, ] @@ -852,10 +871,10 @@ documentation = [ { name = "mkdocs-material", specifier = "==9.7.1" }, ] pipeline = [ - { name = "bandit", specifier = "==1.9.2" }, { name = "complexipy", specifier = "==5.1.0" }, { name = "isort", specifier = "==7.0.0" }, { name = "mypy", specifier = "==1.19.1" }, + { name = "pytest", specifier = "==9.0.2" }, { name = "ruff", specifier = "==0.14.11" }, ] @@ -1001,15 +1020,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/48/f3/b67d6ea49ca9154453b6d70b34ea22f3996b9fa55da105a79d8732227adc/soupsieve-2.8.1-py3-none-any.whl", hash = "sha256:a11fe2a6f3d76ab3cf2de04eb339c1be5b506a8a47f2ceb6d139803177f85434", size = 36710, upload-time = "2025-12-18T13:50:33.267Z" }, ] -[[package]] -name = "stevedore" -version = "5.6.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/5b/496f8abebd10c3301129abba7ddafd46c71d799a70c44ab080323987c4c9/stevedore-5.6.0.tar.gz", hash = "sha256:f22d15c6ead40c5bbfa9ca54aa7e7b4a07d59b36ae03ed12ced1a54cf0b51945", size = 516074, upload-time = "2025-11-20T10:06:07.264Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/40/8561ce06dc46fd17242c7724ab25b257a2ac1b35f4ebf551b40ce6105cfa/stevedore-5.6.0-py3-none-any.whl", hash = "sha256:4a36dccefd7aeea0c70135526cecb7766c4c84c473b1af68db23d541b6dc1820", size = 54428, upload-time = "2025-11-20T10:06:05.946Z" }, -] - [[package]] name = "text-unidecode" version = "1.3" diff --git a/{{ cookiecutter.project_name }}/.devcontainer/setup.sh b/{{ cookiecutter.project_name }}/.devcontainer/setup.sh index 658115b..dbf3bd8 100644 --- a/{{ cookiecutter.project_name }}/.devcontainer/setup.sh +++ b/{{ cookiecutter.project_name }}/.devcontainer/setup.sh @@ -12,6 +12,6 @@ sudo apt-get install -y build-essential > /dev/null 2>&1 echo "✅ System dependencies installed." echo "Installing Python dependencies with Makefile..." -make install > /dev/null 2>&1 +make setup > /dev/null 2>&1 echo "✅ Devcontainer setup complete." \ No newline at end of file diff --git a/{{ cookiecutter.project_name }}/.github/CONTRIBUTING.md b/{{ cookiecutter.project_name }}/.github/CONTRIBUTING.md index 085b1a4..2078707 100644 --- a/{{ cookiecutter.project_name }}/.github/CONTRIBUTING.md +++ b/{{ cookiecutter.project_name }}/.github/CONTRIBUTING.md @@ -71,7 +71,7 @@ To work locally: 3. Run: ```bash - make install + make setup ``` > The required Python version is defined in `pyproject.toml`. If using `uv`, it will @@ -123,5 +123,5 @@ Not a fan of writing code? You can still help by: - Suggesting features or usability improvements - Helping triage and respond to issues -Thank you for being part of {{ cookiecutter.project_name }}'s journey. We’re thrilled to have -you here. If you have questions, feel free to reach out or open an issue. +Thank you for being part of {{ cookiecutter.project_name }}'s journey. We’re thrilled to +have you here. If you have questions, feel free to reach out or open an issue. diff --git a/{{ cookiecutter.project_name }}/.github/actions/build-mkdocs/action.yml b/{{ cookiecutter.project_name }}/.github/actions/build-mkdocs/action.yml index fb2ddd0..847a095 100644 --- a/{{ cookiecutter.project_name }}/.github/actions/build-mkdocs/action.yml +++ b/{{ cookiecutter.project_name }}/.github/actions/build-mkdocs/action.yml @@ -18,15 +18,8 @@ runs: - name: Setup gh-pages Branch shell: bash run: | - # Store current branch - CURRENT_BRANCH=$(git branch --show-current) - - # Check if gh-pages branch exists - if git ls-remote --heads origin gh-pages | grep -q gh-pages; then - echo "gh-pages branch exists, fetching..." - git fetch origin gh-pages:gh-pages - else - echo "gh-pages branch doesn't exist, creating..." + if ! git ls-remote --heads origin gh-pages | grep -q gh-pages; then + CURRENT_BRANCH=$(git branch --show-current) git checkout --orphan gh-pages git reset --hard git commit --allow-empty -m "Initial gh-pages commit" @@ -34,14 +27,8 @@ runs: git checkout "$CURRENT_BRANCH" fi - - name: Build and Deploy Docs with Mike + - name: Build and Deploy Docs shell: bash run: | - echo "Deploying Docs Version: ${{ inputs.docs-version }}" uv run mike deploy --push --update-aliases "${{ inputs.docs-version }}" latest - - - name: Set Default Version to Docs - shell: bash - run: | - echo "Setting 'Latest' as Default Version" uv run mike set-default --push latest diff --git a/{{ cookiecutter.project_name }}/.github/actions/create-release/action.yml b/{{ cookiecutter.project_name }}/.github/actions/create-release/action.yml index 3d91904..4afd27d 100644 --- a/{{ cookiecutter.project_name }}/.github/actions/create-release/action.yml +++ b/{{ cookiecutter.project_name }}/.github/actions/create-release/action.yml @@ -26,52 +26,24 @@ runs: run: | if gh release view "${{ inputs.version }}" >/dev/null 2>&1; then echo "exists=true" >> $GITHUB_OUTPUT - echo "Release ${{ inputs.version }} already exists" else echo "exists=false" >> $GITHUB_OUTPUT - echo "Release ${{ inputs.version }} doesn't exist" fi env: GH_TOKEN: ${{ inputs.token }} - - name: Generate Release Notes + - name: Create Release if: steps.check_release.outputs.exists == 'false' shell: bash run: | - echo "Generating release notes..." LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "") + RANGE="${LAST_TAG:+$LAST_TAG..HEAD}" + COMMITS=$(git log ${RANGE:---max-count=10} --pretty=format:"- %s" --no-merges) - if [ -z "$LAST_TAG" ]; then - echo "No previous tags found, using last 10 commits..." - COMMITS=$(git log --pretty=format:"- %s" --no-merges -10) - else - echo "Previous tag found: $LAST_TAG" - COMMITS=$(git log "${LAST_TAG}..HEAD" --pretty=format:"- %s" --no-merges) - fi - - cat > release-notes.md << EOF - ## Changes - - $COMMITS - EOF - - echo "Release notes generated:" - cat release-notes.md - - - name: Create Release - if: steps.check_release.outputs.exists == 'false' - shell: bash - run: | - echo "Creating release ${{ inputs.version }}..." gh release create "${{ inputs.version }}" \ --title "${{ inputs.version }}" \ - --notes-file release-notes.md - echo "Release ${{ inputs.version }} created successfully" + --notes "## Changes + + $COMMITS" env: GH_TOKEN: ${{ inputs.token }} - - - name: Skip Release - if: steps.check_release.outputs.exists == 'true' - shell: bash - run: | - echo "Skipping release creation - ${{ inputs.version }} already exists" diff --git a/{{ cookiecutter.project_name }}/.github/actions/lint-code/action.yml b/{{ cookiecutter.project_name }}/.github/actions/lint-code/action.yml index a2e8165..0c07777 100644 --- a/{{ cookiecutter.project_name }}/.github/actions/lint-code/action.yml +++ b/{{ cookiecutter.project_name }}/.github/actions/lint-code/action.yml @@ -11,26 +11,17 @@ runs: using: composite steps: - name: Lint with Ruff - id: ruff - run: | - echo "Running Ruff..." - uv run ruff check ${{ inputs.src-project-folder }}/ shell: bash + run: uv run ruff check ${{ inputs.src-project-folder }}/ - - name: Checking Imports with isort - run: | - echo "Running isort..." - uv run isort --check ${{ inputs.src-project-folder }}/ + - name: Check Imports with isort shell: bash + run: uv run isort --check ${{ inputs.src-project-folder }}/ - - name: Checking Cognitive Complexity with complexipy - run: | - echo "Running complexipy..." - uv run complexipy -f ${{ inputs.src-project-folder }}/ + - name: Check Cognitive Complexity shell: bash + run: uv run complexipy -f ${{ inputs.src-project-folder }}/ - - name: Running Mypy Type Checker - run: | - echo "Running Mypy..." - uv run mypy ${{ inputs.src-project-folder }}/ + - name: Run Mypy Type Checker shell: bash + run: uv run mypy ${{ inputs.src-project-folder }}/ diff --git a/{{ cookiecutter.project_name }}/.github/actions/security/action.yml b/{{ cookiecutter.project_name }}/.github/actions/security/action.yml index 93cb09f..3c15540 100644 --- a/{{ cookiecutter.project_name }}/.github/actions/security/action.yml +++ b/{{ cookiecutter.project_name }}/.github/actions/security/action.yml @@ -16,8 +16,7 @@ runs: using: composite steps: - name: Security Scan with Bandit - id: bandit shell: bash run: | - echo "Running Bandit..." - uv run bandit -r "${{ inputs.src-project-folder }}/" --exclude "${{ inputs.src-exclude }}" + uv run bandit -r "${{ inputs.src-project-folder }}/" --exclude "${{ + inputs.src-exclude }}" diff --git a/{{ cookiecutter.project_name }}/.github/actions/setup-python-env/action.yml b/{{ cookiecutter.project_name }}/.github/actions/setup-python-env/action.yml index 2a553f6..ef503ec 100644 --- a/{{ cookiecutter.project_name }}/.github/actions/setup-python-env/action.yml +++ b/{{ cookiecutter.project_name }}/.github/actions/setup-python-env/action.yml @@ -25,39 +25,20 @@ runs: enable-cache: true - name: Install dependencies with uv - id: install-deps shell: bash run: | - # Check if we should install all extras - if [ -z "${{ inputs.uv-group }}" ] && [ -z "${{ inputs.uv-extra }}" ]; then - echo "Installing all extras (default when no group or extra specified)..." - uv sync --all-extras - elif [ "${{ inputs.uv-extra }}" = "--all-extras" ]; then - echo "Installing all extras (explicitly requested)..." - if [ -n "${{ inputs.uv-group }}" ]; then - echo "Note: Installing all extras overrides the specified group: ${{ inputs.uv-group }}" - fi - uv sync --all-extras + ARGS="" + if [ "${{ inputs.uv-extra }}" = "--all-extras" ] || [ -z "${{ inputs.uv-group }}${{ inputs.uv-extra }}" ]; then + ARGS="--all-extras" else - echo "Installing with group: ${{ inputs.uv-group }}, and extra: ${{ inputs.uv-extra }}..." - if [ -n "${{ inputs.uv-group }}" ] && [ -n "${{ inputs.uv-extra }}" ]; then - uv sync --group ${{ inputs.uv-group }} --extra ${{ inputs.uv-extra }} - elif [ -n "${{ inputs.uv-group }}" ]; then - uv sync --group ${{ inputs.uv-group }} - elif [ -n "${{ inputs.uv-extra }}" ]; then - uv sync --extra ${{ inputs.uv-extra }} - else - uv sync - fi + [ -n "${{ inputs.uv-group }}" ] && ARGS="$ARGS --group ${{ inputs.uv-group }}" + [ -n "${{ inputs.uv-extra }}" ] && ARGS="$ARGS --extra ${{ inputs.uv-extra }}" fi + uv sync $ARGS - name: Verify uv and environment - id: verify shell: bash run: | - echo "uv version:" uv --version - echo "Virtual environments:" uv venv list - echo "Python version:" uv run python --version diff --git a/{{ cookiecutter.project_name }}/.github/actions/test-code/action.yml b/{{ cookiecutter.project_name }}/.github/actions/test-code/action.yml index 250cbdd..7ebb725 100644 --- a/{{ cookiecutter.project_name }}/.github/actions/test-code/action.yml +++ b/{{ cookiecutter.project_name }}/.github/actions/test-code/action.yml @@ -1,4 +1,5 @@ name: Test Code +description: Run Python tests with Pytest inputs: src-project-folder: @@ -15,14 +16,8 @@ runs: using: composite steps: - name: Run tests with Pytest - id: run-pytest shell: bash run: | - echo "Checking if tests directory exists..." - if [ -d "${{ inputs.src-tests-folder }}" ] && [ $(find ${{ inputs.src-tests-folder }} -name "test_*.py" | wc -l) -gt 0 ]; then - echo "Running tests..." + if [ -d "${{ inputs.src-tests-folder }}" ] && [ -n "$(find ${{ inputs.src-tests-folder }} -name 'test_*.py')" ]; then uv run pytest ${{ inputs.src-tests-folder }} - echo "Tests complete." - else - echo "No tests directory found or no test files. Skipping tests." fi diff --git a/{{ cookiecutter.project_name }}/.pre-commit-config.yaml b/{{ cookiecutter.project_name }}/.pre-commit-config.yaml index bcb0570..11fc356 100644 --- a/{{ cookiecutter.project_name }}/.pre-commit-config.yaml +++ b/{{ cookiecutter.project_name }}/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: hooks: - id: local-check name: Makefile Validation - entry: make pipeline + entry: make pre-commit language: system pass_filenames: false always_run: true diff --git a/{{ cookiecutter.project_name }}/.vscode/settings.json b/{{ cookiecutter.project_name }}/.vscode/settings.json index 3e0aaad..d899291 100644 --- a/{{ cookiecutter.project_name }}/.vscode/settings.json +++ b/{{ cookiecutter.project_name }}/.vscode/settings.json @@ -47,7 +47,6 @@ "[python]": { "editor.formatOnType": true }, - "python.defaultInterpreterPath": "/home/jovyan/envs/env/bin/python", "[jsonc]": { "editor.defaultFormatter": "vscode.json-language-features" }, diff --git a/{{ cookiecutter.project_name }}/Makefile b/{{ cookiecutter.project_name }}/Makefile index 9cf36e4..8fd86a7 100644 --- a/{{ cookiecutter.project_name }}/Makefile +++ b/{{ cookiecutter.project_name }}/Makefile @@ -27,18 +27,16 @@ clean-cache-temp-files: lint: @echo "Running lint checks..." - @uv run isort $(SRC_PROJECT_NAME)/ - @uv run nbqa isort $(SRC_PROJECT_NOTEBOOKS_FOLDER)/ - @uv run ruff check --fix $(SRC_PROJECT_NAME)/ - @uv run ruff format $(SRC_PROJECT_NAME)/ - @uv run nbqa ruff $(SRC_PROJECT_NOTEBOOKS_FOLDER)/ + @uv run isort $(SRC_PROJECT_NAME) + @uv run ruff check --fix $(SRC_PROJECT_NAME) + @uv run ruff format $(SRC_PROJECT_NAME) @echo "✅ Linting complete." code-check: @echo "Running static code checks..." - @uv run mypy $(SRC_PROJECT_NAME)/ - @uv run complexipy -f $(SRC_PROJECT_NAME)/ - @uv run bandit -r $(SRC_PROJECT_NAME)/ --exclude $(SRC_PROJECT_TESTS) + @uv run mypy $(SRC_PROJECT_NAME) + @uv run complexipy -f $(SRC_PROJECT_NAME) + @uv run bandit -r $(SRC_PROJECT_NAME) --exclude $(SRC_PROJECT_TESTS) @echo "✅ Code and security checks complete." check-dead-code: @@ -48,7 +46,7 @@ check-dead-code: tests: @echo "Running tests..." - @uv run pytest $(SRC_PROJECT_TESTS)/ + @uv run pytest $(SRC_PROJECT_TESTS) @echo "✅ Tests complete." doc: @@ -58,5 +56,8 @@ doc: pipeline: clean-cache-temp-files lint code-check tests @echo "✅ Pipeline complete." +pre-commit: clean-cache-temp-files lint code-check + @echo "✅ Pipeline pre-commit complete." + all: setup pipeline doc @echo "✅ All tasks complete." diff --git a/{{ cookiecutter.project_name }}/README.md b/{{ cookiecutter.project_name }}/README.md index aef59ec..7aa3122 100644 --- a/{{ cookiecutter.project_name }}/README.md +++ b/{{ cookiecutter.project_name }}/README.md @@ -38,7 +38,7 @@ collaboration, and maintainability: ├── models/ <- Training, inference, and model-related logic. │ ├── __init__.py │ ├── train.py <- Scripts and functions to train machine learning models. - │ └── predict.py <- Functions for generating model predictions. + │ └── inference.py <- Scripts and functions for model inference. └── utils/ <- Utility scripts and helper functions used across modules. ├── __init__.py └── utils.py diff --git a/{{ cookiecutter.project_name }}/pyproject.toml b/{{ cookiecutter.project_name }}/pyproject.toml index 9a5eeb2..2059fd7 100644 --- a/{{ cookiecutter.project_name }}/pyproject.toml +++ b/{{ cookiecutter.project_name }}/pyproject.toml @@ -46,7 +46,6 @@ pipeline = [ "pytest-order==1.3.0", "ruff==0.14.11", "isort==7.0.0", - "nbqa==1.9.1", "deadcode==2.4.1", "pre-commit==4.5.1", ] diff --git a/{{ cookiecutter.project_name }}/{{ cookiecutter.project_module_name }}/__init__.py b/{{ cookiecutter.project_name }}/{{ cookiecutter.project_module_name }}/__init__.py index df8c9b2..d5a6a8a 100644 --- a/{{ cookiecutter.project_name }}/{{ cookiecutter.project_module_name }}/__init__.py +++ b/{{ cookiecutter.project_name }}/{{ cookiecutter.project_module_name }}/__init__.py @@ -1,5 +1,5 @@ -# Own modules -from {{ cookiecutter.project_module_name }}.version import __version__ +# 3pps +from {{cookiecutter.project_module_name}}.version import __version__ # Define all names to be imported __all__: list[str] = ["__version__"] diff --git a/{{ cookiecutter.project_name }}/{{ cookiecutter.project_module_name }}/version.py b/{{ cookiecutter.project_name }}/{{ cookiecutter.project_module_name }}/version.py index af52d68..5da51c0 100644 --- a/{{ cookiecutter.project_name }}/{{ cookiecutter.project_module_name }}/version.py +++ b/{{ cookiecutter.project_name }}/{{ cookiecutter.project_module_name }}/version.py @@ -5,5 +5,4 @@ # Standard libraries import importlib.metadata - -__version__: str = importlib.metadata.version("{{ cookiecutter.project_module_name }}") \ No newline at end of file +__version__: str = importlib.metadata.version("{{ cookiecutter.project_name }}") From 952fad010e3ef25fe8aaeeaa9966b0bc79ab5837 Mon Sep 17 00:00:00 2001 From: danibcorr <77023868+danibcorr@users.noreply.github.com> Date: Fri, 16 Jan 2026 15:15:17 +0100 Subject: [PATCH 2/3] Added tests, modified Makefile commands, removed nbqa and more --- .github/actions/test-code/action.yml | 23 +++++++++++++++++++ .github/workflows/workflow.yml | 12 +++++++--- Makefile | 4 ++-- uv.lock | 2 +- .../.github/workflows/workflow.yml | 6 ++--- {{ cookiecutter.project_name }}/Makefile | 22 +++++++++--------- 6 files changed, 49 insertions(+), 20 deletions(-) create mode 100644 .github/actions/test-code/action.yml diff --git a/.github/actions/test-code/action.yml b/.github/actions/test-code/action.yml new file mode 100644 index 0000000..7ebb725 --- /dev/null +++ b/.github/actions/test-code/action.yml @@ -0,0 +1,23 @@ +name: Test Code +description: Run Python tests with Pytest + +inputs: + src-project-folder: + description: "Directory where the project is located" + required: true + default: "src" + + src-tests-folder: + description: "Directory where the tests are located" + required: true + default: "tests" + +runs: + using: composite + steps: + - name: Run tests with Pytest + shell: bash + run: | + if [ -d "${{ inputs.src-tests-folder }}" ] && [ -n "$(find ${{ inputs.src-tests-folder }} -name 'test_*.py')" ]; then + uv run pytest ${{ inputs.src-tests-folder }} + fi diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 706cfcc..72dff19 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -8,10 +8,11 @@ on: env: SRC_PYTHON_VERSION: "3.11" + TEST_PATH: "tests" jobs: - setup: - name: Setup Code + setup-test: + name: Setup and Test runs-on: ubuntu-latest steps: @@ -25,11 +26,16 @@ jobs: uv-group: "pipeline" uv-extra: "--all-extras" + - name: Run Tests + uses: ./.github/actions/test-code + with: + src-tests-folder: ${{ env.TEST_PATH }} + build-deploy-docs: if: github.ref == 'refs/heads/main' name: Build MkDocs Documentation runs-on: ubuntu-latest - needs: setup + needs: setup-test permissions: contents: write diff --git a/Makefile b/Makefile index d3c3810..37d7a58 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ .DEFAULT_GOAL := all PATH_PROJECT_ROOT ?= . -PATH_TEST ?= tests +TEST_PATH ?= tests setup: @echo "Installing dependencies..." @@ -37,7 +37,7 @@ code-check: test: @echo "Running tests..." - @uv run pytest $(PATH_TEST) -v + @uv run pytest $(TEST_PATH) -v @echo "✅ Tests complete." doc: diff --git a/uv.lock b/uv.lock index b86a5c9..3be8271 100644 --- a/uv.lock +++ b/uv.lock @@ -833,7 +833,7 @@ wheels = [ [[package]] name = "python-project-template" -version = "2.5.1" +version = "2.6.0" source = { virtual = "." } dependencies = [ { name = "cookiecutter", marker = "sys_platform == 'linux'" }, diff --git a/{{ cookiecutter.project_name }}/.github/workflows/workflow.yml b/{{ cookiecutter.project_name }}/.github/workflows/workflow.yml index 0b1ae03..fcc9188 100644 --- a/{{ cookiecutter.project_name }}/.github/workflows/workflow.yml +++ b/{{ cookiecutter.project_name }}/.github/workflows/workflow.yml @@ -8,7 +8,7 @@ on: env: SRC_PROJECT_FOLDER: "{{ cookiecutter.project_module_name }}" - SRC_PROJECT_TESTS: "{{ cookiecutter.project_test_folder_name }}" + TEST_PATH: "{{ cookiecutter.project_test_folder_name }}" SRC_PYTHON_VERSION: "{{ cookiecutter.project_version_python }}" jobs: @@ -36,13 +36,13 @@ jobs: uses: ./.github/actions/test-code with: src-project-folder: ${{'{{'}} env.SRC_PROJECT_FOLDER {{'}}'}} - src-tests-folder: ${{'{{'}} env.SRC_PROJECT_TESTS {{'}}'}} + src-tests-folder: ${{'{{'}} env.TEST_PATH {{'}}'}} - name: Security Scan uses: ./.github/actions/security with: src-project-folder: ${{'{{'}} env.SRC_PROJECT_FOLDER {{'}}'}} - src-exclude: ${{'{{'}} env.SRC_PROJECT_TESTS {{'}}'}} + src-exclude: ${{'{{'}} env.TEST_PATH {{'}}'}} build-deploy-docs: if: github.ref == 'refs/heads/main' diff --git a/{{ cookiecutter.project_name }}/Makefile b/{{ cookiecutter.project_name }}/Makefile index 8fd86a7..a2d472b 100644 --- a/{{ cookiecutter.project_name }}/Makefile +++ b/{{ cookiecutter.project_name }}/Makefile @@ -7,9 +7,9 @@ .DEFAULT_GOAL := all -SRC_PROJECT_NAME ?= {{ cookiecutter.project_module_name }} -SRC_PROJECT_TESTS ?= {{ cookiecutter.project_test_folder_name }} -SRC_PROJECT_NOTEBOOKS_FOLDER ?= notebooks +SOURCE_PATH ?= {{ cookiecutter.project_module_name }} +TEST_PATH ?= {{ cookiecutter.project_test_folder_name }} +NOTEBOOKS_PATH ?= notebooks setup: @echo "Installing dependencies..." @@ -27,26 +27,26 @@ clean-cache-temp-files: lint: @echo "Running lint checks..." - @uv run isort $(SRC_PROJECT_NAME) - @uv run ruff check --fix $(SRC_PROJECT_NAME) - @uv run ruff format $(SRC_PROJECT_NAME) + @uv run isort $(SOURCE_PATH) + @uv run ruff check --fix $(SOURCE_PATH) + @uv run ruff format $(SOURCE_PATH) @echo "✅ Linting complete." code-check: @echo "Running static code checks..." - @uv run mypy $(SRC_PROJECT_NAME) - @uv run complexipy -f $(SRC_PROJECT_NAME) - @uv run bandit -r $(SRC_PROJECT_NAME) --exclude $(SRC_PROJECT_TESTS) + @uv run mypy $(SOURCE_PATH) + @uv run complexipy -f $(SOURCE_PATH) + @uv run bandit -r $(SOURCE_PATH) --exclude $(TEST_PATH) @echo "✅ Code and security checks complete." check-dead-code: @echo "Checking dead code..." - @uv run deadcode $(SRC_PROJECT_NAME) + @uv run deadcode $(SOURCE_PATH) @echo "✅ Dead code check complete." tests: @echo "Running tests..." - @uv run pytest $(SRC_PROJECT_TESTS) + @uv run pytest $(TEST_PATH) @echo "✅ Tests complete." doc: From d453ab939c5d2e633d611b58ab90221512c3a51e Mon Sep 17 00:00:00 2001 From: danibcorr <77023868+danibcorr@users.noreply.github.com> Date: Fri, 16 Jan 2026 16:15:28 +0100 Subject: [PATCH 3/3] Improvements in documentation, AGENTS.md, and other files --- .github/CODE_OF_CONDUCT.md | 13 +- .github/ISSUE_TEMPLATE/bug-report.yml | 3 +- .github/dependabot.yml | 1 - AGENTS.md | 73 +++----- README.md | 82 +++++---- docs/assets/css/custom.css | 16 ++ docs/content/ci-cd.md | 119 ++++++------ docs/content/cookiecutter.md | 13 +- docs/content/makefile-commands.md | 20 ++- docs/content/pre-commit-hooks.md | 9 +- docs/content/pyproject-configuration.md | 170 +++++++++++++++--- docs/index.md | 93 +++++----- mkdocs.yml | 3 + pyproject.toml | 8 + tests/test_cookiecutter_template.py | 2 +- .../.github/ISSUE_TEMPLATE/bug-report.yml | 5 +- .../.vscode/extensions.json | 2 +- {{ cookiecutter.project_name }}/Makefile | 6 +- .../config/json_configuration.json | 4 +- {{ cookiecutter.project_name }}/docs/.nav.yml | 2 +- .../docs/assets/css/custom.css | 16 ++ {{ cookiecutter.project_name }}/mkdocs.yml | 45 +++-- .../prompts/create_diagrams.md | 8 +- .../prompts/create_documentation.md | 4 +- .../prompts/docstrings.md | 7 +- .../prompts/improve_documentation.md | 7 +- .../prompts/production_code.md | 4 +- .../pyproject.toml | 10 +- 28 files changed, 465 insertions(+), 280 deletions(-) create mode 100644 docs/assets/css/custom.css create mode 100644 {{ cookiecutter.project_name }}/docs/assets/css/custom.css diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md index 9c6a518..76bedcf 100644 --- a/.github/CODE_OF_CONDUCT.md +++ b/.github/CODE_OF_CONDUCT.md @@ -25,8 +25,7 @@ include: Examples of unacceptable behavior include: -- The use of sexualized language or imagery, and sexual attention or advances of any - kind +- The use of sexualized language or imagery, and sexual attention or advances of any kind - Trolling, insulting or derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or email address, without @@ -58,8 +57,8 @@ Instances of abusive, harassing, or otherwise unacceptable behavior may be repor directly to the community leaders responsible for enforcement. All complaints will be reviewed and investigated promptly and fairly. -All community leaders are obligated to respect the privacy and security of the -individual reporting any incident. +All community leaders are obligated to respect the privacy and security of the individual +reporting any incident. ## Enforcement Guidelines @@ -81,9 +80,9 @@ inappropriate. A public apology may be requested. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of -Conduct, for a specified period of time. This includes avoiding interactions in -community spaces as well as external channels like social media. Violating these terms -may lead to a temporary or permanent ban. +Conduct, for a specified period of time. This includes avoiding interactions in community +spaces as well as external channels like social media. Violating these terms may lead to +a temporary or permanent ban. ### 3. Temporary Ban diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 3733f1e..a96d278 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -14,8 +14,7 @@ body: attributes: label: "System Information" description: "Please provide details about your system configuration." - placeholder: - "python-project-template version:\nPlatform/OS:\nPython version:" + placeholder: "python-project-template version:\nPlatform/OS:\nPython version:" validations: required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index da59a8e..6e638c6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,4 +17,3 @@ updates: labels: - "dependencies" - "python" - diff --git a/AGENTS.md b/AGENTS.md index cc37180..041d778 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,59 +1,40 @@ # Agent Instructions -This project is a **Cookiecutter template** used to generate new Python projects with a -professional, ready-to-use structure. It integrates best practices for code quality, -testing, security, documentation, and CI/CD. Because it is highly dynamic, it includes -many optional settings and Jinja2 template blocks. +This is a **Cookiecutter template** for generating Python projects with integrated best +practices for code quality, testing, security, documentation, and CI/CD. -## Getting Context +## Before You Start -Before interacting with the template, make sure to: +1. Review `README.md` for template overview and features +2. Check `AGENTS.md` in generated projects for project-specific instructions +3. Understand this is a **dynamic generator**, not a static project -1. **Review the README**: The `README.md` provides a high-level overview of the template, - its features, CI/CD workflow, and instructions to generate new projects. Key features - include: +## Key Files - - Linting & type checking (Ruff & Mypy) - - Security scanning (Bandit) - - Code complexity analysis (Complexipy) - - Testing (Pytest) - - Auto documentation (MkDocs + GitHub Pages) - - Preconfigured GitHub Actions for CI/CD +- **cookiecutter.json**: Template variables (metadata, Python version, optional features) +- **hooks/post_gen_project.py**: Removes disabled optional folders post-generation +- **Makefile**: Workflow commands (`setup`, `lint`, `code-check`, `test`, `pipeline`) +- **.github/actions/**: Reusable GitHub Actions for CI/CD -2. **Review `AGENTS.md` in the generated project**: After generating a new project, the - template copies an `AGENTS.md` file to the project itself - (`{{cookiecutter.__package_slug}}/AGENTS.md`). This file provides project-specific - instructions for agents. +## Template Syntax -## Working with the Template +- Files use **Jinja2 syntax**: `{{ cookiecutter.project_name }}` +- Preserve template delimiters and conditional blocks when modifying +- Invalid syntax breaks project generation -- **Jinja2 blocks**: Because this is a Cookiecutter template, many files contain Jinja2 - template syntax. Agents should expect template variables like - `{{ cookiecutter.project_name }}` or conditional blocks. +## Validation Workflow -- **Testing functionality**: +Run `make pipeline` to execute: - - Create new projects inside the `workspaces/` directory. - - Run the Makefile targets to validate functionality: +- Linting (Ruff, isort) +- Type checking (Mypy) +- Complexity analysis (Complexipy) +- Security scanning (Bandit) +- Tests (Pytest) - - `make setup` → installs dependencies - - `make pipeline` → runs linting, type checking, security analysis, complexity - checks, and tests - - `make all` → full workflow including documentation preview +## Critical Rules - - Ensure the environment is isolated (e.g., via `venv`) and that - [`uv`](https://github.com/astral-sh/uv) is installed to handle grouped dependency - installations. - -## Summary - -Agents interacting with this template should: - -- Understand it is a **dynamic project generator**, not a single static project. -- Always use the generated `workspaces/` directory for testing. -- Follow the Makefile workflow to validate and test features. -- Keep track of optional components and ensure `post_gen_project.py` handles cleanup - properly. - -By following these instructions, agents will be able to safely generate, test, and -maintain projects derived from this Python template. +1. This generates projects; each instance differs based on `cookiecutter.json` choices +2. Optional features: `.devcontainer/`, `.vscode/`, `notebooks/`, `prompts/` +3. Always validate changes with `make pipeline` +4. Never break Jinja2 template syntax diff --git a/README.md b/README.md index 41a2157..4a4d25e 100644 --- a/README.md +++ b/README.md @@ -15,68 +15,76 @@ # Python Project Template Python Project Template provides a ready-to-use structure for Python projects, -integrating best practices for code quality, testing, security, documentation, and CI/CD. -It helps developers start new projects quickly with a maintainable and professional -foundation. +integrating best practices for code quality, testing and more. It helps developers start +new projects quickly with a maintainable and professional foundation. + +> **Warning**: This template is configured for Linux x86_64 systems. For other platforms, +> you may need to adjust the `environments` and `required-environments` settings in +> `pyproject.toml`. ## Features -- **Linting & Type Checking**: Ruff and Mypy for clean, consistent code. -- **Security Scanning**: Bandit detects potential vulnerabilities. -- **Code Complexity Analysis**: Complexipy identifies complex functions and modules. -- **Testing Suite**: Reliable unit testing with Pytest. -- **Auto Documentation**: MkDocs + GitHub Pages for automated docs. -- **CI/CD**: GitHub Actions automates linting, testing, and documentation deployment. +- **Linting & Type Checking**: [Ruff](https://docs.astral.sh/ruff/) and + [Mypy](https://www.mypy-lang.org/) for clean, consistent code. +- **Security Scanning**: [Bandit](https://bandit.readthedocs.io/en/latest/) detects + potential vulnerabilities. +- **Code Complexity Analysis**: [Complexipy](https://rohaquinlop.github.io/complexipy/) + identifies complex functions and modules. +- **Testing Suite**: Reliable unit testing with + [Pytest](https://docs.pytest.org/en/stable/). +- **Auto Documentation**: [MkDocs](https://www.mkdocs.org/) + + [GitHub Pages](https://docs.github.com/en/pages) for automated docs. +- **CI/CD**: [GitHub Actions](https://docs.github.com/en/actions) automates linting, + testing, and documentation deployment. And more. ## Getting Started Before starting, ensure that you have required Python installed and a virtual environment -set up. It is recommended to create an isolated environment (e.g., using `venv`) to -manage dependencies cleanly. Additionally, ensure that -[`uv`](https://github.com/astral-sh/uv) is installed in your environment to handle -grouped dependency installations. +set up. It is recommended to create an isolated environment to manage dependencies +cleanly. Additionally, ensure that [`uv`](https://github.com/astral-sh/uv) is installed +in your environment to handle grouped dependency installations. 1. Generate Your Project - Use Cookiecutter to create a new project from the template: + Use Cookiecutter to create a new project from the template: - ```bash - cookiecutter https://github.com/danibcorr/python-project-template.git - ``` + ```bash + cookiecutter https://github.com/danibcorr/python-project-template.git + ``` - Follow the prompts to configure project metadata, package name, and other options. + Follow the prompts to configure project metadata, package name, and other options. 2. Install Dependencies - Activate your virtual environment and install all dependencies using the included - `Makefile`: + Activate your virtual environment and install all dependencies using the included + `Makefile`: - ```bash - make setup - ``` + ```bash + make setup + ``` - This installs development, testing, and documentation tools as defined in - `pyproject.toml`. + This installs development, testing, and documentation tools as defined in + `pyproject.toml`. 3. Run the Pipeline - Execute the quality pipeline, which includes linting, type checking, security - analysis, complexity checks, and test execution: + Execute the quality pipeline, which includes linting, type checking, complexity + checks, and test execution: - ```bash - make pipeline - ``` + ```bash + make pipeline + ``` 4. Run the Full Workflow (Optional) - To perform a complete setup including dependency installation, full quality checks, - and local documentation preview: + To perform a complete setup including dependency installation, full quality checks, + and local documentation preview: - ```bash - make all - ``` + ```bash + make all + ``` - This ensures that the project environment is fully prepared for development and - validation. + This ensures that the project environment is fully prepared for development and + validation. diff --git a/docs/assets/css/custom.css b/docs/assets/css/custom.css new file mode 100644 index 0000000..5aa8180 --- /dev/null +++ b/docs/assets/css/custom.css @@ -0,0 +1,16 @@ +body { + text-align: justify; +} + +.mermaid { + display: flex; + justify-content: center; +} + +.md-typeset__table { + min-width: 100%; +} + +.md-typeset table:not([class]) { + display: table; +} diff --git a/docs/content/ci-cd.md b/docs/content/ci-cd.md index e5693c9..c2db9f4 100644 --- a/docs/content/ci-cd.md +++ b/docs/content/ci-cd.md @@ -1,72 +1,63 @@ # Continuous Integration and Continuous Deployment with GitHub Actions -## Overview of the Automation Strategy - -The continuous integration and continuous deployment (CI/CD) process is implemented -through GitHub Actions using a workflow definition located at -`.github/workflows/workflow.yml`. This workflow is designed to automate, in a unified and -reproducible manner, the validation, documentation generation, and release management -phases of the project lifecycle. By integrating these activities into a single automated -pipeline, the repository ensures consistency between code changes, documentation updates, -and published releases, while reducing manual intervention and the risk of human error. - -## Workflow Architecture and Execution Logic - -The workflow is composed of several logically separated jobs, each responsible for a -specific stage of the CI/CD process. These jobs are executed according to clearly defined -triggering conditions, primarily based on repository events and branch context. The -structure reflects a progressive validation model in which foundational checks are -performed first, followed by documentation deployment and release creation when the code -reaches a stable state. - -## Environment Setup and Dependency Initialization - -The initial job, referred to as the setup phase, is executed on every push and on every -pull request. Its primary purpose is to establish a controlled and reproducible execution -environment. During this phase, a Python runtime environment is provisioned, ensuring -that subsequent jobs operate under consistent interpreter conditions. All dependencies -defined under the `pipeline` group are then installed, which guarantees that the tools -required for validation, documentation generation, and automation are available before -any further processing occurs. This step functions as a foundational safeguard, as -failures at this stage prevent downstream jobs from executing under incomplete or -misconfigured conditions. - -## Documentation Build and Deployment on the Main Branch - -When changes are merged into the `main` branch, the workflow activates an additional job -dedicated to documentation management. In this phase, the project documentation is built -using MkDocs, a static site generator specifically designed for technical documentation. -The resulting site is then automatically deployed to GitHub Pages, ensuring that the -published documentation always reflects the current state of the main codebase. - -Version management of the documentation is handled through `mike`, which enables the -coexistence of multiple documentation versions corresponding to different project -releases. This approach allows users to consult documentation aligned with specific -versions of the software, thereby improving traceability and long-term maintainability. -The deployment process is fully automated and does not require manual approval once the +## Automation Strategy Overview + +Continuous integration and continuous deployment are implemented through GitHub Actions +using a workflow definition located at `.github/workflows/workflow.yml`. This workflow +serves as the central automation mechanism of the project and orchestrates validation, +documentation generation, and release management within a unified pipeline. By +integrating these processes, the repository maintains consistency between code changes, +documentation, and released artifacts, while reducing manual intervention and operational +errors. + +## Workflow Architecture and Execution Model + +The workflow is organized into distinct jobs, each corresponding to a specific stage of +the CI/CD lifecycle. Job execution is controlled by repository events, such as pushes and +pull requests, and by branch-specific conditions. The overall design follows a +progressive execution model in which preliminary checks are completed first, establishing +a stable baseline before documentation deployment and release publication are triggered. +This structure ensures that only validated code advances to user-facing outputs. + +## Environment Initialization and Dependency Setup + +The initial job is executed on every push and pull request and is responsible for +preparing a reproducible execution environment. A Python runtime is provisioned to ensure +consistent interpreter behavior across all stages of the pipeline. Subsequently, all +dependencies defined in the `pipeline` group are installed, guaranteeing the availability +of the tools required for validation, documentation building, and automation tasks. Any +failure during this phase interrupts the workflow, preventing downstream jobs from +running under incomplete or unreliable conditions. + +## Documentation Build and Deployment + +When changes are integrated into the `main` branch, the workflow triggers a dedicated +documentation job. In this stage, MkDocs is used to generate a static documentation site +from the project sources, which is then automatically deployed to GitHub Pages. This +process ensures that the published documentation accurately reflects the current state of +the main codebase. + +Documentation versioning is handled through `mike`, which allows multiple versions of the +documentation to coexist. Each version corresponds to a specific software release, +enabling users to consult documentation aligned with the version they are using. The +entire build and deployment process is automated and requires no manual approval once the workflow conditions are satisfied. -## Automated Release Creation and Versioning - -Also restricted to the `main` branch, the release creation job formalizes the delivery of -a new software version. During this stage, the workflow programmatically determines the -current project version and uses this information to create a new GitHub release. Release -notes are generated automatically, typically by aggregating relevant commit messages or -changes since the previous release. This ensures that each release is consistently -documented and that users have immediate access to a structured summary of modifications, -enhancements, and fixes. +## Automated Release Management -By integrating release creation into the CI/CD pipeline, the workflow enforces a direct -relationship between the main branch state and published artifacts, reducing -discrepancies between source code and released versions. +Release creation is also restricted to the `main` branch and represents the final stage +of the CI/CD pipeline. During this phase, the workflow determines the current project +version and creates a corresponding GitHub release. Release notes are generated +automatically based on changes since the previous release, providing users with a concise +and structured summary of updates. Integrating release management into the workflow +enforces a direct relationship between the repository state and published versions, +reducing discrepancies between source code and distributed artifacts. -## GitHub Pages Configuration and Activation +## GitHub Pages Configuration Requirements -For the automated documentation deployment to function correctly, GitHub Pages must be -configured to use GitHub Actions as its source. This configuration is performed within -the repository settings by navigating to the Pages section and selecting GitHub Actions -as the publication source. Once this setting is applied, every push to the `main` branch -triggers the documentation build and deployment process defined in the workflow. As a -result, the documentation site is continuously updated without requiring any additional -manual steps, ensuring alignment between the repository content and its public technical +For documentation deployment to function correctly, GitHub Pages must be configured to +use GitHub Actions as its publication source. This setting is applied in the repository +Pages configuration. Once enabled, every push to the `main` branch automatically triggers +the documentation build and deployment defined in the workflow, ensuring continuous +alignment between the repository content and its publicly available technical documentation. diff --git a/docs/content/cookiecutter.md b/docs/content/cookiecutter.md index 441ea54..3bc67d5 100644 --- a/docs/content/cookiecutter.md +++ b/docs/content/cookiecutter.md @@ -10,12 +10,12 @@ enforces best practices and organizational standards in project structure. ## Configuration Variables -The customization process is driven by a configuration file, typically named -`cookiecutter.json`, which defines the variables that the template expects. Each variable -corresponds to a specific aspect of the project, such as naming conventions, folder -structures, or optional features. During project generation, Cookiecutter interactively -prompts the user to provide values for these variables, thereby producing a project -tailored to the user’s specifications. The main configuration variables include: +The customization process is driven by a configuration file, `cookiecutter.json`, which +defines the variables that the template expects. Each variable corresponds to a specific +aspect of the project, such as naming conventions, folder structures, or optional +features. During project generation, Cookiecutter interactively prompts the user to +provide values for these variables, thereby producing a project tailored to the user’s +specifications. The main configuration variables include: | Variable | Description | Example | | -------------------------- | ----------------------------------------------- | ------------------------------ | @@ -27,6 +27,7 @@ tailored to the user’s specifications. The main configuration variables includ | `add_notebooks_folder` | Option to include a notebooks folder | `yes` / `no` | | `add_prompts_folder` | Option to include a prompts folder | `yes` / `no` | | `add_dev_container_folder` | Option to include a Dev Container configuration | `yes` / `no` | +| `add_vscode_folder` | Option to include VS Code configuration | `yes` / `no` | These variables enable fine-grained control over the structure and functionality of the generated project, accommodating different workflows and development environments. diff --git a/docs/content/makefile-commands.md b/docs/content/makefile-commands.md index 11779a2..c512fbe 100644 --- a/docs/content/makefile-commands.md +++ b/docs/content/makefile-commands.md @@ -82,7 +82,7 @@ during refactoring or when optimizing the codebase for maintainability. make check-dead-code ``` -## Running Unit Tests: `make tests` +## Running Unit Tests: `make test` This command executes all unit tests using Pytest. It supports structured test execution via `pytest-order` and can generate coverage reports if configured. The `tests/` @@ -90,7 +90,7 @@ directory is scanned recursively, ensuring that all modules are tested thoroughl command should be run frequently after code modifications to verify correctness. ```bash -make tests +make test ``` ## Documentation Management: `make doc` @@ -105,6 +105,22 @@ local edits and published content. make doc ``` +## Pre-Commit Validation: `make pre-commit` + +The `pre-commit` command runs a subset of the pipeline specifically designed for +pre-commit hooks. It sequentially: + +1. Cleans caches and temporary files. +2. Runs linting and formatting. +3. Performs static analysis, complexity measurement, and security scanning. + +This command is automatically executed by Git pre-commit hooks to validate code before +committing. + +```bash +make pre-commit +``` + ## Complete Code Quality Pipeline: `make pipeline` The `pipeline` command orchestrates the full sequence of code quality checks and testing. diff --git a/docs/content/pre-commit-hooks.md b/docs/content/pre-commit-hooks.md index 2d10efa..2aff7d3 100644 --- a/docs/content/pre-commit-hooks.md +++ b/docs/content/pre-commit-hooks.md @@ -12,10 +12,10 @@ changes meet established coding standards and pass essential checks automaticall ## Functionality When a developer attempts to commit changes, the pre-commit system automatically triggers -the full development pipeline by executing: +the pre-commit validation pipeline by executing: ```bash -make pipeline +make pre-commit ``` This command performs a comprehensive sequence of actions, including: @@ -23,10 +23,11 @@ This command performs a comprehensive sequence of actions, including: - Linting and formatting checks to ensure consistent code style. - Type validation using Mypy to detect mismatches and type-related errors. - Security analysis with Bandit to identify potential vulnerabilities in the code. -- Execution of all unit tests to confirm that functionality remains correct. +- Complexity analysis with Complexipy to identify overly complex code. If any of these checks fail, the commit is blocked, thereby preventing problematic code -from being added to the repository. +from being added to the repository. Note that tests are not executed during pre-commit to +keep the validation fast, but they are run in the full `make pipeline` command. ## Advantages diff --git a/docs/content/pyproject-configuration.md b/docs/content/pyproject-configuration.md index c6a8f4c..a9b984f 100644 --- a/docs/content/pyproject-configuration.md +++ b/docs/content/pyproject-configuration.md @@ -10,11 +10,22 @@ interoperability between tools. By leveraging `pyproject.toml`, the project achi modular dependency management, consistent tool behavior, and simplified setup for both development and production environments. +## Build System: `[build-system]` Section + +Defines the build backend used for packaging: + +```toml +[build-system] +requires = ["uv_build>=0.9.18,<0.10.0"] +build-backend = "uv_build" +``` + +This configuration uses `uv_build` as the build backend, enabling fast and reliable +package building. + ## Project Metadata: `[project]` Section -The `[project]` section defines fundamental information about the project. This includes -the project name, version, description, author information, and the minimum supported -Python version. An example configuration is: +The `[project]` section defines fundamental information about the project: ```toml [project] @@ -22,12 +33,56 @@ name = "my-project" version = "0.0.1" description = "Project description" authors = [{name = "Your Name", email = "your@email.com"}] +license = {file = "LICENSE"} +readme = "README.md" requires-python = ">=3.11" +classifiers = [ + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Topic :: Scientific/Engineering", + "Topic :: Software Development", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3 :: Only", +] +keywords = ["python"] ``` These fields ensure that packaging tools, dependency managers, and documentation -generators can automatically retrieve key project information. This metadata also -standardizes versioning and distribution across different environments. +generators can automatically retrieve key project information. The `classifiers` help +categorize the project on PyPI, while `keywords` improve discoverability. + +## UV Configuration: `[tool.uv]` Section + +Configures the UV package manager behavior: + +```toml +[tool.uv] +default-groups = "all" +cache-keys = [{ file = "pyproject.toml" }, { git = { commit = true } }] +environments = [ + "sys_platform == 'linux'", +] +required-environments = [ + "sys_platform == 'linux' and platform_machine == 'x86_64'" +] +``` + +- `default-groups = "all"`: Installs all dependency groups by default. +- `cache-keys`: Defines cache invalidation triggers based on file changes and git + commits. +- `environments`: Specifies supported platforms (Linux). +- `required-environments`: Restricts to Linux x86_64 architecture. + +### UV Build Backend: `[tool.uv.build-backend]` Section + +```toml +[tool.uv.build-backend] +module-name = "my_module" +module-root = "" +``` + +Configures the module name and root directory for the build backend. ## Dependency Management: `[dependency-groups]` Section @@ -40,13 +95,13 @@ workflows. The `pipeline` group includes tools essential for code quality, testing, security, and automation: -- `pytest` and `pytest-order`: Frameworks for running unit tests and controlling test - execution order. -- `ruff`: A high-performance linter and automatic formatter. -- `mypy`: Static type checker to detect type inconsistencies. - `bandit`: Security analysis tool for detecting common vulnerabilities. - `complexipy`: Measures cyclomatic complexity to identify potentially unmaintainable code. +- `mypy`: Static type checker to detect type inconsistencies. +- `pytest` and `pytest-order`: Frameworks for running unit tests and controlling test + execution order. +- `ruff`: A high-performance linter and automatic formatter. - `isort`: Sorts imports according to standard conventions. - `deadcode`: Detects unused or unreachable code. - `pre-commit`: Manages Git pre-commit hooks for automated checks. @@ -57,23 +112,39 @@ The `documentation` group contains tools for building and maintaining project documentation: - `mkdocs` and `mkdocs-material`: Static site generator and material design theme. -- `mkdocstrings`: Generates API documentation directly from Python docstrings. +- `mkdocstrings[python]`: Generates API documentation directly from Python docstrings. +- `mkdocs-git-revision-date-localized-plugin`: Displays last update dates for pages. +- `mkdocs-git-authors-plugin`: Shows contributors for each page. +- `mkdocs-enumerate-headings-plugin`: Automatically numbers headings. - `mkdocs-jupyter`: Integrates Jupyter notebooks into documentation. +- `mkdocs-awesome-nav`: Enhances navigation capabilities. - `mike`: Provides versioned documentation management. -- Additional plugins: Enhance navigation, metadata handling, and site functionality. ## Tool-Specific Configuration ### Ruff: `[tool.ruff]` -The Ruff linter is configured to enforce code style, detect errors, and simplify code -where possible: +The Ruff linter is configured to enforce code style, detect errors, and simplify code: ```toml [tool.ruff] line-length = 88 indent-width = 4 +exclude = [".venv", "build", "dist", ...] +extend-exclude = ["*.ipynb"] +``` + +- `line-length = 88`: Maximum line length following Black's convention. +- `indent-width = 4`: Standard Python indentation. +- `exclude`: Directories to ignore during linting. +- `extend-exclude`: Additional patterns to exclude (Jupyter notebooks). + +#### Ruff Lint Rules: `[tool.ruff.lint]` + +```toml +[tool.ruff.lint] select = ["E", "F", "UP", "B", "SIM"] +ignore = ["E203"] ``` - `E`: Enforces PEP 8 style guidelines. @@ -81,6 +152,30 @@ select = ["E", "F", "UP", "B", "SIM"] - `UP`: Applies modern Python syntax checks and upgrades. - `B`: Detects common programming bugs. - `SIM`: Suggests code simplifications to improve readability. +- `ignore = ["E203"]`: Ignores whitespace before ':' (conflicts with Black). + +#### Ruff Docstring Style: `[tool.ruff.lint.pydocstyle]` + +```toml +[tool.ruff.lint.pydocstyle] +convention = "google" +``` + +Enforces Google-style docstring conventions. + +#### Ruff Formatter: `[tool.ruff.format]` + +```toml +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +line-ending = "auto" +docstring-code-format = true +docstring-code-line-length = 88 +``` + +Configures automatic code formatting with double quotes, space indentation, and formats +code blocks within docstrings. ### Mypy: `[tool.mypy]` @@ -90,21 +185,54 @@ Mypy performs static type checking, ensuring type safety and consistency: [tool.mypy] check_untyped_defs = true ignore_missing_imports = true +exclude = ["^(build|dist|venv)", ".venv/"] +cache_dir = "/dev/null" ``` - `check_untyped_defs = true`: Validates functions even if type annotations are missing. - `ignore_missing_imports = true`: Avoids errors for third-party modules without type - stubs, preventing unnecessary interruptions. + stubs. +- `exclude`: Directories to skip during type checking. +- `cache_dir = "/dev/null"`: Disables caching to avoid stale cache issues. ### isort: `[tool.isort]` isort enforces a standardized import order, improving code readability and -maintainability. Imports are grouped as follows: +maintainability: + +```toml +[tool.isort] +profile = "black" +known_first_party = ["my_module"] +sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] +import_heading_stdlib = "Standard libraries" +import_heading_thirdparty = "3pps" +import_heading_firstparty = "Own modules" +import_heading_localfolder = "Own modules" +line_length = 88 +include_trailing_comma = true +combine_as_imports = true +skip_glob = [".venv/*", ".uv-cache/*"] +``` + +Imports are grouped as follows: -1. **STDLIB**: Python standard library modules. -2. **THIRDPARTY**: External dependencies installed via package managers. -3. **FIRSTPARTY**: Modules developed within the current project. -4. **LOCALFOLDER**: Relative imports for local submodules or packages. +1. **FUTURE**: Future imports (`from __future__ import ...`). +2. **STDLIB**: Python standard library modules. +3. **THIRDPARTY**: External dependencies installed via package managers. +4. **FIRSTPARTY**: Modules developed within the current project. +5. **LOCALFOLDER**: Relative imports for local submodules or packages. + +The configuration uses Black-compatible formatting with custom section headings for +better organization. + +### deadcode: `[tool.deadcode]` + +Detects unused code in the project: + +```toml +[tool.deadcode] +exclude = [".venv", ".uv-cache", "tests"] +``` -This grouping ensures that import statements remain organized and easy to navigate, -facilitating collaboration across multiple developers and modules. +Excludes virtual environments and test directories from dead code analysis. diff --git a/docs/index.md b/docs/index.md index 0ff2747..d80e699 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,80 +3,81 @@

-

- - - - - License - -

- # Python Project Template Python Project Template provides a ready-to-use structure for Python projects, -integrating best practices for code quality, testing, security, documentation, and CI/CD. -It helps developers start new projects quickly with a maintainable and professional -foundation. +integrating best practices for code quality, testing and more. It helps developers start +new projects quickly with a maintainable and professional foundation. + +!!! warning + + This template is configured for Linux x86_64 systems. For other platforms, you + may need to adjust the `environments` and `required-environments` settings in + `pyproject.toml`. ## Features -- **Linting & Type Checking**: Ruff and Mypy for clean, consistent code. -- **Security Scanning**: Bandit detects potential vulnerabilities. -- **Code Complexity Analysis**: Complexipy identifies complex functions and modules. -- **Testing Suite**: Reliable unit testing with Pytest. -- **Auto Documentation**: MkDocs + GitHub Pages for automated docs. -- **CI/CD**: GitHub Actions automates linting, testing, and documentation deployment. +- **Linting & Type Checking**: [Ruff](https://docs.astral.sh/ruff/) and + [Mypy](https://www.mypy-lang.org/) for clean, consistent code. +- **Security Scanning**: [Bandit](https://bandit.readthedocs.io/en/latest/) detects + potential vulnerabilities. +- **Code Complexity Analysis**: [Complexipy](https://rohaquinlop.github.io/complexipy/) + identifies complex functions and modules. +- **Testing Suite**: Reliable unit testing with + [Pytest](https://docs.pytest.org/en/stable/). +- **Auto Documentation**: [MkDocs](https://www.mkdocs.org/) + + [GitHub Pages](https://docs.github.com/en/pages) for automated docs. +- **CI/CD**: [GitHub Actions](https://docs.github.com/en/actions) automates linting, + testing, and documentation deployment. And more. ## Getting Started Before starting, ensure that you have required Python installed and a virtual environment -set up. It is recommended to create an isolated environment (e.g., using `venv`) to -manage dependencies cleanly. Additionally, ensure that -[`uv`](https://github.com/astral-sh/uv) is installed in your environment to handle -grouped dependency installations. +set up. It is recommended to create an isolated environment to manage dependencies +cleanly. Additionally, ensure that [`uv`](https://github.com/astral-sh/uv) is installed +in your environment to handle grouped dependency installations. 1. Generate Your Project - Use Cookiecutter to create a new project from the template: + Use Cookiecutter to create a new project from the template: - ```bash - cookiecutter https://github.com/danibcorr/python-project-template.git - ``` + ```bash + cookiecutter https://github.com/danibcorr/python-project-template.git + ``` - Follow the prompts to configure project metadata, package name, and other options. + Follow the prompts to configure project metadata, package name, and other options. 2. Install Dependencies - Activate your virtual environment and install all dependencies using the included - `Makefile`: + Activate your virtual environment and install all dependencies using the included + `Makefile`: - ```bash - make setup - ``` + ```bash + make setup + ``` - This installs development, testing, and documentation tools as defined in - `pyproject.toml`. + This installs development, testing, and documentation tools as defined in + `pyproject.toml`. 3. Run the Pipeline - Execute the quality pipeline, which includes linting, type checking, security - analysis, complexity checks, and test execution: + Execute the quality pipeline, which includes linting, type checking, complexity + checks, and test execution: - ```bash - make pipeline - ``` + ```bash + make pipeline + ``` 4. Run the Full Workflow (Optional) - To perform a complete setup including dependency installation, full quality checks, - and local documentation preview: + To perform a complete setup including dependency installation, full quality checks, + and local documentation preview: - ```bash - make all - ``` + ```bash + make all + ``` - This ensures that the project environment is fully prepared for development and - validation. + This ensures that the project environment is fully prepared for development and + validation. diff --git a/mkdocs.yml b/mkdocs.yml index 7dc1487..7a4dd71 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -95,3 +95,6 @@ extra: version: provider: mike default: latest + +extra_css: + - assets/css/custom.css diff --git a/pyproject.toml b/pyproject.toml index ceee8b9..447a47c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -112,8 +112,16 @@ cache_dir = "/dev/null" exclude = ["{{ cookiecutter.project_name }}"] [tool.isort] +profile = "black" sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] import_heading_stdlib = "Standard libraries" import_heading_thirdparty = "3pps" import_heading_firstparty = "Own modules" +import_heading_localfolder = "Own modules" line_length = 88 +include_trailing_comma = true +combine_as_imports = true +skip_glob = [".venv/*", ".uv-cache/*"] + +[tool.deadcode] +exclude = [".venv", ".uv-cache", "tests"] diff --git a/tests/test_cookiecutter_template.py b/tests/test_cookiecutter_template.py index de405f7..223ef54 100644 --- a/tests/test_cookiecutter_template.py +++ b/tests/test_cookiecutter_template.py @@ -326,7 +326,7 @@ def test_makefile_commands_exist(tmp_path: Path, template_dir: Path) -> None: makefile = tmp_path / "project-name" / "Makefile" content = makefile.read_text() - required_commands = ["setup", "lint", "code-check", "tests", "pipeline"] + required_commands = ["setup", "lint", "code-check", "test", "pipeline"] for cmd in required_commands: assert f"{cmd}:" in content, f"Missing command: {cmd}" diff --git a/{{ cookiecutter.project_name }}/.github/ISSUE_TEMPLATE/bug-report.yml b/{{ cookiecutter.project_name }}/.github/ISSUE_TEMPLATE/bug-report.yml index 1572ae2..2d01232 100644 --- a/{{ cookiecutter.project_name }}/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/{{ cookiecutter.project_name }}/.github/ISSUE_TEMPLATE/bug-report.yml @@ -42,9 +42,8 @@ body: attributes: label: "Steps to Reproduce" description: - "Provide a **minimal, reproducible example** with any relevant configuration. - Use code blocks for formatting. Avoid screenshots as they can’t be easily - copied." + "Provide a **minimal, reproducible example** with any relevant configuration. Use + code blocks for formatting. Avoid screenshots as they can’t be easily copied." placeholder: "Steps to reproduce:\n1.\n2.\n3.\nCode snippet / Colab link:\n```python\n# your code here\n```" diff --git a/{{ cookiecutter.project_name }}/.vscode/extensions.json b/{{ cookiecutter.project_name }}/.vscode/extensions.json index 0282d21..9370e1f 100644 --- a/{{ cookiecutter.project_name }}/.vscode/extensions.json +++ b/{{ cookiecutter.project_name }}/.vscode/extensions.json @@ -7,4 +7,4 @@ "ms-python.debugpy", "Gruntfuggly.todo-tree" ] -} \ No newline at end of file +} diff --git a/{{ cookiecutter.project_name }}/Makefile b/{{ cookiecutter.project_name }}/Makefile index a2d472b..e7356d1 100644 --- a/{{ cookiecutter.project_name }}/Makefile +++ b/{{ cookiecutter.project_name }}/Makefile @@ -1,7 +1,7 @@ .PHONY: setup \ clean-cache-temp-files \ lint code-check check-dead-code \ - tests \ + test \ doc \ pipeline all @@ -44,7 +44,7 @@ check-dead-code: @uv run deadcode $(SOURCE_PATH) @echo "✅ Dead code check complete." -tests: +test: @echo "Running tests..." @uv run pytest $(TEST_PATH) @echo "✅ Tests complete." @@ -53,7 +53,7 @@ doc: @echo "Serving documentation..." @uv run mkdocs serve -pipeline: clean-cache-temp-files lint code-check tests +pipeline: clean-cache-temp-files lint code-check test @echo "✅ Pipeline complete." pre-commit: clean-cache-temp-files lint code-check diff --git a/{{ cookiecutter.project_name }}/config/json_configuration.json b/{{ cookiecutter.project_name }}/config/json_configuration.json index b9837d1..947c6f2 100644 --- a/{{ cookiecutter.project_name }}/config/json_configuration.json +++ b/{{ cookiecutter.project_name }}/config/json_configuration.json @@ -1,3 +1,3 @@ { - "example": "Hello" -} \ No newline at end of file + "example": "Hello" +} diff --git a/{{ cookiecutter.project_name }}/docs/.nav.yml b/{{ cookiecutter.project_name }}/docs/.nav.yml index d3d3d96..42fe18c 100644 --- a/{{ cookiecutter.project_name }}/docs/.nav.yml +++ b/{{ cookiecutter.project_name }}/docs/.nav.yml @@ -4,4 +4,4 @@ nav: sort: ignore_case: true - type: alphabetical \ No newline at end of file + type: alphabetical diff --git a/{{ cookiecutter.project_name }}/docs/assets/css/custom.css b/{{ cookiecutter.project_name }}/docs/assets/css/custom.css new file mode 100644 index 0000000..5aa8180 --- /dev/null +++ b/{{ cookiecutter.project_name }}/docs/assets/css/custom.css @@ -0,0 +1,16 @@ +body { + text-align: justify; +} + +.mermaid { + display: flex; + justify-content: center; +} + +.md-typeset__table { + min-width: 100%; +} + +.md-typeset table:not([class]) { + display: table; +} diff --git a/{{ cookiecutter.project_name }}/mkdocs.yml b/{{ cookiecutter.project_name }}/mkdocs.yml index 36bdbb1..0257f83 100644 --- a/{{ cookiecutter.project_name }}/mkdocs.yml +++ b/{{ cookiecutter.project_name }}/mkdocs.yml @@ -8,35 +8,39 @@ theme: name: "material" language: en palette: - - media: "(prefers-color-scheme: light)" - scheme: default - primary: grey - accent: grey - toggle: - icon: material/weather-sunny - name: "Switch to dark mode" - - media: "(prefers-color-scheme: dark)" - scheme: slate - primary: grey - accent: grey + - scheme: slate + primary: black + accent: black toggle: icon: material/weather-night name: "Switch to light mode" + - scheme: default + primary: black + accent: black + toggle: + icon: material/weather-sunny + name: "Switch to dark mode" features: - - content.tabs.link + - announce.dismiss + - content.action.edit + - content.action.view - content.code.annotate - content.code.copy + - content.tabs.link - content.tooltips - - announce.dismiss - - navigation.tabs + - navigation.footer - navigation.instant - navigation.instant.prefetch - navigation.instant.preview - navigation.instant.progress - - navigation.sections + - navigation.indexes + - navigation.path + - navigation.tabs - navigation.top - navigation.tracking - - navigation.indexes + - navigation.sections + - search.highlight + - search.share - search.suggest - toc.follow - toc.integrate @@ -46,6 +50,9 @@ theme: use_directory_urls: false markdown_extensions: + - tables + - toc: + toc_depth: 2 - admonition - pymdownx.details - pymdownx.superfences: @@ -105,3 +112,9 @@ extra: social: - icon: fontawesome/brands/linkedin link: https://www.linkedin.com/in/danibcorr/ + version: + provider: mike + default: latest + +extra_css: + - assets/css/custom.css diff --git a/{{ cookiecutter.project_name }}/prompts/create_diagrams.md b/{{ cookiecutter.project_name }}/prompts/create_diagrams.md index 66b22fe..7e3b2f4 100644 --- a/{{ cookiecutter.project_name }}/prompts/create_diagrams.md +++ b/{{ cookiecutter.project_name }}/prompts/create_diagrams.md @@ -2,8 +2,8 @@ Hello. You are a **software architect** specializing in data pipelines and class relationships. From this point onward, you will receive code snippets (Python or other -languages). Your task is to **generate a Mermaid diagram** (compatible with Draw.io) -that visually represents the pipeline architecture. +languages). Your task is to **generate a Mermaid diagram** (compatible with Draw.io) that +visually represents the pipeline architecture. ## Core Objective @@ -63,8 +63,8 @@ For each pipeline step, include: - **Grouping**: Use subgraphs for logical/structural grouping - **Connections**: - Within subgraphs: Create explicit node-to-node connections - - Between subgraphs: Use exactly one connector from the final output of one subgraph - to the initial input of the next + - Between subgraphs: Use exactly one connector from the final output of one subgraph to + the initial input of the next - **Efficiency**: Use the `&` operator to connect multiple outputs to the same destination diff --git a/{{ cookiecutter.project_name }}/prompts/create_documentation.md b/{{ cookiecutter.project_name }}/prompts/create_documentation.md index 0fd955c..21ec7fe 100644 --- a/{{ cookiecutter.project_name }}/prompts/create_documentation.md +++ b/{{ cookiecutter.project_name }}/prompts/create_documentation.md @@ -1,7 +1,7 @@ # Technical Document Rewriting -Hello. From this point onward, you will receive documents, typically technical in -nature. Your task is to return the entire document rewritten according to the following +Hello. From this point onward, you will receive documents, typically technical in nature. +Your task is to return the entire document rewritten according to the following specifications: ## Core Requirements diff --git a/{{ cookiecutter.project_name }}/prompts/docstrings.md b/{{ cookiecutter.project_name }}/prompts/docstrings.md index 462bc2f..2dd0f4a 100644 --- a/{{ cookiecutter.project_name }}/prompts/docstrings.md +++ b/{{ cookiecutter.project_name }}/prompts/docstrings.md @@ -28,8 +28,7 @@ ones following these specifications: - **Never** include types, parameter types, or default values in the descriptive text. - These belong exclusively in the function signature. -- If type hints are missing from the signature, add them by inferring from usage - context. +- If type hints are missing from the signature, add them by inferring from usage context. - If default values are missing but evident from the code, add them to the signature. ### Performance and Complexity @@ -67,8 +66,8 @@ ones following these specifications: **Critical**: Maintain strict consistency across all docstrings in the codebase. - Identical parameters, variables, or concepts **must** use identical descriptions. -- If a parameter appears in multiple functions, its description must be word-for-word - the same. +- If a parameter appears in multiple functions, its description must be word-for-word the + same. - Establish and maintain consistent terminology throughout. ## Output Format diff --git a/{{ cookiecutter.project_name }}/prompts/improve_documentation.md b/{{ cookiecutter.project_name }}/prompts/improve_documentation.md index ecb679f..7fa5481 100644 --- a/{{ cookiecutter.project_name }}/prompts/improve_documentation.md +++ b/{{ cookiecutter.project_name }}/prompts/improve_documentation.md @@ -1,7 +1,7 @@ # Technical Document Rewriting -Hello. From this point onward, you will receive documents, typically technical in -nature. Your task is to return the entire document rewritten according to the following +Hello. From this point onward, you will receive documents, typically technical in nature. +Your task is to return the entire document rewritten according to the following specifications: ## Structure and Organization @@ -50,8 +50,7 @@ Ensure the text is completely free of: Additional quality standards: - Produce polished, well-crafted sentences with proper grammatical structure -- Maintain stylistic consistency in vocabulary choices, sentence patterns, and - formatting +- Maintain stylistic consistency in vocabulary choices, sentence patterns, and formatting - Use technical vocabulary accurately and consistently throughout - Ensure parallel structure in lists, comparisons, and related elements - Apply consistent capitalization, hyphenation, and formatting conventions diff --git a/{{ cookiecutter.project_name }}/prompts/production_code.md b/{{ cookiecutter.project_name }}/prompts/production_code.md index 559fee5..6d90e6f 100644 --- a/{{ cookiecutter.project_name }}/prompts/production_code.md +++ b/{{ cookiecutter.project_name }}/prompts/production_code.md @@ -98,8 +98,8 @@ Follow these specifications for all docstrings: **Critical**: Maintain strict consistency across all docstrings in the codebase - Identical parameters, variables, or concepts **must** use identical descriptions -- If a parameter appears in multiple functions, its description must be word-for-word - the same +- If a parameter appears in multiple functions, its description must be word-for-word the + same - Establish and maintain consistent terminology throughout ### Code Optimization diff --git a/{{ cookiecutter.project_name }}/pyproject.toml b/{{ cookiecutter.project_name }}/pyproject.toml index 2059fd7..5ed639c 100644 --- a/{{ cookiecutter.project_name }}/pyproject.toml +++ b/{{ cookiecutter.project_name }}/pyproject.toml @@ -118,9 +118,17 @@ exclude = [ cache_dir = "/dev/null" [tool.isort] +profile = "black" known_first_party = ["{{ cookiecutter.project_module_name }}"] sections = ["FUTURE", "STDLIB", "THIRDPARTY", "FIRSTPARTY", "LOCALFOLDER"] import_heading_stdlib = "Standard libraries" import_heading_thirdparty = "3pps" import_heading_firstparty = "Own modules" -line_length = 88 \ No newline at end of file +import_heading_localfolder = "Own modules" +line_length = 88 +include_trailing_comma = true +combine_as_imports = true +skip_glob = [".venv/*", ".uv-cache/*"] + +[tool.deadcode] +exclude = [".venv", ".uv-cache", "tests"]