diff --git a/cookiecutter.json b/cookiecutter.json index 8adb8b4..d42dbfd 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -5,7 +5,12 @@ "project_name": "example-project", "project_slug": "{{cookiecutter.project_name|lower|replace('-', '_')}}", "project_description": "This is a template repository for Python projects that use Poetry for their dependency management.", + "branch_name": "main", + "minor_python_version": ["3.8", "3.9", "3.10", "3.11", "3.12"], "include_github_actions": ["y", "n"], + "test_on_windows": ["y", "n"], + "test_on_macos": ["y", "n"], + "test_on_ubuntu": ["y", "n"], "publish_to": ["pypi", "artifactory", "none"], "typechecking": ["mypy", "pyright"], "deptry": ["y", "n"], diff --git a/cookiecutter_poetry/cli.py b/cookiecutter_poetry/cli.py index 6e26086..0a0cf0b 100644 --- a/cookiecutter_poetry/cli.py +++ b/cookiecutter_poetry/cli.py @@ -1,9 +1,13 @@ from __future__ import annotations import os +import sys def main() -> None: cwd = os.path.dirname(__file__) package_dir = os.path.abspath(os.path.join(cwd, "..")) - os.system(f"cookiecutter {package_dir}") # noqa: S605 | No injection, retrieving path in OS + try: + os.system(f"cookiecutter {package_dir}") # noqa: S605 | No injection, retrieving path in OS + except KeyboardInterrupt: + sys.exit(0) diff --git a/hooks/pre_gen_project.py b/hooks/pre_gen_project.py index 0ea8841..272369d 100644 --- a/hooks/pre_gen_project.py +++ b/hooks/pre_gen_project.py @@ -20,3 +20,29 @@ ) # Exit to cancel project sys.exit(1) + +# Update python version matrix +""" +{% set python_version_matrix = [] %} +{% for ver in range( + cookiecutter.minor_python_version|replace("3.", "")|int, 13 +) %} +{% set _ = python_version_matrix.append("3." ~ ver) %} +{% endfor %} +{{ cookiecutter.update({"_python_version_matrix": python_version_matrix}) }} +""" + +# Update OS matrix +""" +{% set os_matrix = [] %} +{% if cookiecutter.test_on_windows == "y" %} +{% set _ = os_matrix.append("windows-latest") %} +{% endif %} +{% if cookiecutter.test_on_macos == "y" %} +{% set _ = os_matrix.append("macos-latest") %} +{% endif %} +{% if cookiecutter.test_on_ubuntu == "y" or os_matrix|length == 0 %} +{% set _ = os_matrix.append("ubuntu-latest") %} +{% endif %} +{{ cookiecutter.update({"_os_matrix": os_matrix}) }} +""" diff --git a/tests/test_cookiecutter.py b/tests/test_cookiecutter.py index a8b9f8f..10d5306 100644 --- a/tests/test_cookiecutter.py +++ b/tests/test_cookiecutter.py @@ -135,7 +135,10 @@ def test_codecov(cookies, tmp_path): with run_within_dir(tmp_path): result = cookies.bake() assert result.exit_code == 0 - assert is_valid_yaml(result.project_path / ".github" / "workflows" / "main.yml") + + main_yml_file = f"{result.project_path}/.github/workflows/main.yml" + assert is_valid_yaml(main_yml_file) + assert file_contains_text(main_yml_file, "--cov --cov-config=pyproject.toml --cov-report=xml") assert os.path.isfile(f"{result.project_path}/codecov.yaml") assert os.path.isfile(f"{result.project_path}/.github/workflows/validate-codecov-config.yml") @@ -144,7 +147,10 @@ def test_not_codecov(cookies, tmp_path): with run_within_dir(tmp_path): result = cookies.bake(extra_context={"codecov": "n"}) assert result.exit_code == 0 - assert is_valid_yaml(result.project_path / ".github" / "workflows" / "main.yml") + + main_yml_file = f"{result.project_path}/.github/workflows/main.yml" + assert is_valid_yaml(main_yml_file) + assert not file_contains_text(main_yml_file, "--cov --cov-config=pyproject.toml --cov-report=xml") assert not os.path.isfile(f"{result.project_path}/codecov.yaml") assert not os.path.isfile(f"{result.project_path}/.github/workflows/validate-codecov-config.yml") @@ -192,3 +198,53 @@ def test_mypy(cookies, tmp_path): # check the tox file assert file_contains_text(f"{result.project_path}/tox.ini", "mypy") assert not file_contains_text(f"{result.project_path}/tox.ini", "pyright") + + +def test_branch_name_prompt(cookies, tmp_path): + with run_within_dir(tmp_path): + result = cookies.bake(extra_context={"branch_name": "master"}) + + assert result.exit_code == 0 + assert os.path.isfile(f"{result.project_path}/.github/workflows/master.yml") + assert os.path.isfile(f"{result.project_path}/.github/workflows/on-release-master.yml") + + +def test_minor_python_version_prompt(cookies, tmp_path): + with run_within_dir(tmp_path): + result = cookies.bake(extra_context={"minor_python_version": "3.10"}) + + assert result.exit_code == 0 + + main_yml_file = f"{result.project_path}/.github/workflows/main.yml" + assert os.path.isfile(main_yml_file) + assert not file_contains_text(main_yml_file, "3.8") + assert not file_contains_text(main_yml_file, "3.9") + assert file_contains_text(main_yml_file, "3.10") + assert file_contains_text(main_yml_file, "3.11") + assert file_contains_text(main_yml_file, "3.12") + + tox_ini_file = f"{result.project_path}/tox.ini" + assert os.path.isfile(tox_ini_file) + assert not file_contains_text(tox_ini_file, "3.8: py38") + assert not file_contains_text(tox_ini_file, "3.9: py39") + assert file_contains_text(tox_ini_file, "3.10: py310") + assert file_contains_text(tox_ini_file, "3.11: py311") + assert file_contains_text(tox_ini_file, "3.12: py312") + + pyproject_toml_file = f"{result.project_path}/pyproject.toml" + assert os.path.isfile(pyproject_toml_file) + assert file_contains_text(pyproject_toml_file, ">=3.10,<3.13") + + +def test_test_on_os_prompt(cookies, tmp_path): + with run_within_dir(tmp_path): + result = cookies.bake(extra_context={"test_on_windows": "y", "test_on_macos": "n", "test_on_ubuntu": "y"}) + + assert result.exit_code == 0 + + main_yml_file = f"{result.project_path}/.github/workflows/main.yml" + + assert os.path.isfile(main_yml_file) + assert file_contains_text(main_yml_file, "windows-latest") + assert file_contains_text(main_yml_file, "ubuntu-latest") + assert not file_contains_text(main_yml_file, "macos-latest") diff --git a/{{cookiecutter.project_name}}/.editorconfig b/{{cookiecutter.project_name}}/.editorconfig index 9395b54..f6631b7 100644 --- a/{{cookiecutter.project_name}}/.editorconfig +++ b/{{cookiecutter.project_name}}/.editorconfig @@ -1,5 +1,25 @@ -max_line_length = 120 +max_line_length = 100 + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{py,rst,ini}] +indent_style = space +indent_size = 4 [*.json] indent_style = space indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab + +[default.conf] +indent_style = space +indent_size = 2 diff --git a/{{cookiecutter.project_name}}/.github/actions/setup-poetry-env/action.yml b/{{cookiecutter.project_name}}/.github/actions/setup-poetry-env/action.yml index 8ed6bcc..e532dd9 100644 --- a/{{cookiecutter.project_name}}/.github/actions/setup-poetry-env/action.yml +++ b/{{cookiecutter.project_name}}/.github/actions/setup-poetry-env/action.yml @@ -15,13 +15,27 @@ runs: with: python-version: {% raw %}${{ inputs.python-version }}{% endraw %} - - name: Install Poetry + - name: Install Poetry on Windows + if: runner.os == 'Windows' + env: + POETRY_VERSION: "1.7.1" + run: (Invoke-WebRequest -Uri https://install.python-poetry.org -UseBasicParsing).Content | python - -y + shell: pwsh + + - name: Install Poetry on Linux/macOS + if: runner.os != 'Windows' env: POETRY_VERSION: "1.7.1" run: curl -sSL https://install.python-poetry.org | python - -y shell: bash - - name: Add Poetry to Path + - name: Add Poetry to Path on Windows + if: runner.os == 'Windows' + run: echo "$env:APPDATA\Python\Scripts" | Out-File -Append -FilePath $env:GITHUB_PATH -Encoding utf8 + shell: pwsh + + - name: Add Poetry to Path on Linux/macOS + if: runner.os != 'Windows' run: echo "$HOME/.local/bin" >> $GITHUB_PATH shell: bash diff --git a/{{cookiecutter.project_name}}/.github/workflows/on-release-main.yml b/{{cookiecutter.project_name}}/.github/workflows/on-release-{{cookiecutter.branch_name}}.yml similarity index 94% rename from {{cookiecutter.project_name}}/.github/workflows/on-release-main.yml rename to {{cookiecutter.project_name}}/.github/workflows/on-release-{{cookiecutter.branch_name}}.yml index fb19096..4b7b576 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/on-release-main.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/on-release-{{cookiecutter.branch_name}}.yml @@ -1,9 +1,9 @@ -name: release-main +name: release-{{ cookiecutter.branch_name }} on: release: types: [published] - branches: [main] + branches: [{{ cookiecutter.branch_name }}] jobs: {%- if cookiecutter.publish_to != "none" %} diff --git a/{{cookiecutter.project_name}}/.github/workflows/validate-codecov-config.yml b/{{cookiecutter.project_name}}/.github/workflows/validate-codecov-config.yml index 2a8fd11..8074535 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/validate-codecov-config.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/validate-codecov-config.yml @@ -4,7 +4,7 @@ on: pull_request: paths: [codecov.yaml] push: - branches: [main] + branches: [{{ cookiecutter.branch_name }}] jobs: validate-codecov-config: diff --git a/{{cookiecutter.project_name}}/.github/workflows/main.yml b/{{cookiecutter.project_name}}/.github/workflows/{{cookiecutter.branch_name}}.yml similarity index 79% rename from {{cookiecutter.project_name}}/.github/workflows/main.yml rename to {{cookiecutter.project_name}}/.github/workflows/{{cookiecutter.branch_name}}.yml index cfcb02c..305c0dd 100644 --- a/{{cookiecutter.project_name}}/.github/workflows/main.yml +++ b/{{cookiecutter.project_name}}/.github/workflows/{{cookiecutter.branch_name}}.yml @@ -1,9 +1,9 @@ -name: Main +name: {{ cookiecutter.branch_name|capitalize }} on: push: branches: - - main + - {{ cookiecutter.branch_name }} pull_request: types: [opened, synchronize, reopened, ready_for_review] @@ -26,11 +26,12 @@ jobs: run: make check tests-and-type-check: - runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + os: {{ cookiecutter._os_matrix }} + python-version: {{ cookiecutter._python_version_matrix }} fail-fast: false + runs-on: {% raw %}${{ matrix.os }}{% endraw %} defaults: run: shell: bash @@ -44,14 +45,21 @@ jobs: python-version: {% raw %}${{ matrix.python-version }}{% endraw %} - name: Run tests +{%- if cookiecutter.codecov == "y"%} run: poetry run pytest tests --cov --cov-config=pyproject.toml --cov-report=xml +{%- else %} + run: poetry run pytest tests +{%- endif %} - name: Check typing run: poetry run {{ cookiecutter.typechecking }} + {% if cookiecutter.codecov == "y" %} - name: Upload coverage reports to Codecov with GitHub Action on Python 3.11 uses: codecov/codecov-action@v4 if: {% raw %}${{ matrix.python-version == '3.11' }}{% endraw %} + env: + CODECOV_TOKEN: {% raw %}${{ secrets.CODECOV_TOKEN }}{% endraw %} {%- endif %} {%- if cookiecutter.mkdocs == "y" %} diff --git a/{{cookiecutter.project_name}}/CONTRIBUTING.md b/{{cookiecutter.project_name}}/CONTRIBUTING.md index a14a8e1..cdf040e 100644 --- a/{{cookiecutter.project_name}}/CONTRIBUTING.md +++ b/{{cookiecutter.project_name}}/CONTRIBUTING.md @@ -52,8 +52,7 @@ Please note this documentation assumes you already have `poetry` and `Git` insta 2. Clone your fork locally: ```bash -cd -git clone git@github.com:YOUR_NAME/{{cookiecutter.project_name}}.git +git clone git@github.com:YOUR_USERNAME/{{cookiecutter.project_name}}.git ``` 3. Now we need to install the environment. Navigate into the directory diff --git a/{{cookiecutter.project_name}}/Dockerfile b/{{cookiecutter.project_name}}/Dockerfile index 874dcd5..676c1a9 100644 --- a/{{cookiecutter.project_name}}/Dockerfile +++ b/{{cookiecutter.project_name}}/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -FROM python:3.9-slim-buster +FROM python:{{cookiecutter.minor_python_version}}-slim-buster ENV POETRY_VERSION=1.4 \ POETRY_VIRTUALENVS_CREATE=false diff --git a/{{cookiecutter.project_name}}/Makefile b/{{cookiecutter.project_name}}/Makefile index 42cbab9..ebcee94 100644 --- a/{{cookiecutter.project_name}}/Makefile +++ b/{{cookiecutter.project_name}}/Makefile @@ -1,13 +1,13 @@ .PHONY: install install: ## Install the poetry environment and install the pre-commit hooks - @echo "🚀 Creating virtual environment using pyenv and poetry" + @echo "🚀 Creating virtual environment poetry" @poetry install - @ poetry run pre-commit install + @poetry run pre-commit install @poetry shell .PHONY: check check: ## Run code quality tools. - @echo "🚀 Checking Poetry lock file consistency with 'pyproject.toml': Running poetry check --lock" + @echo "🚀 Checking Poetry lock file consistency with 'pyproject.toml'" @poetry check --lock @echo "🚀 Linting code: Running pre-commit" @poetry run pre-commit run -a @@ -34,7 +34,7 @@ build: clean-build ## Build wheel file using poetry .PHONY: clean-build clean-build: ## clean build artifacts - @rm -rf dist + @python -c "import shutil; shutil.rmtree('dist', ignore_errors=True)" {%- if cookiecutter.publish_to == "pypi"%} @@ -46,8 +46,6 @@ publish: ## publish a release to pypi. @echo "🚀 Publishing." @poetry publish -.PHONY: build-and-publish -build-and-publish: build publish ## Build and publish. {%- elif cookiecutter.publish_to == "artifactory" %} .PHONY: publish @@ -58,9 +56,10 @@ publish: ## Publish to the Artifactory repository using poetry. Requires ARTIFAC @echo "🚀 Publishing." @poetry publish --repository artifactory --username $(ARTIFACTORY_USERNAME) --password $(ARTIFACTORY_PASSWORD) +{%- endif%} + .PHONY: build-and-publish build-and-publish: build publish ## Build and publish. -{%- endif%} {%- if cookiecutter.mkdocs == "y" %} diff --git a/{{cookiecutter.project_name}}/README.md b/{{cookiecutter.project_name}}/README.md index 9179424..b7dcc69 100644 --- a/{{cookiecutter.project_name}}/README.md +++ b/{{cookiecutter.project_name}}/README.md @@ -1,12 +1,24 @@ # {{cookiecutter.project_name}} -[![Release](https://img.shields.io/github/v/release/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}})](https://img.shields.io/github/v/release/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}) -[![Build status](https://img.shields.io/github/actions/workflow/status/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}/main.yml?branch=main)](https://github.com/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}/actions/workflows/main.yml?query=branch%3Amain) -[![codecov](https://codecov.io/gh/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}/branch/main/graph/badge.svg)](https://codecov.io/gh/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}) -[![Commit activity](https://img.shields.io/github/commit-activity/m/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}})](https://img.shields.io/github/commit-activity/m/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}) -[![License](https://img.shields.io/github/license/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}})](https://img.shields.io/github/license/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}) +

{{cookiecutter.project_description}}

+ +--- + +

+ + {{cookiecutter.project_name}} version + + + {{cookiecutter.project_name}} CI status + + + {{cookiecutter.project_name}} codecov + + + {{cookiecutter.project_name}} license + +

-{{cookiecutter.project_description}} - **Github repository**: - **Documentation** diff --git a/{{cookiecutter.project_name}}/docs/index.md b/{{cookiecutter.project_name}}/docs/index.md index 729c170..79d0a88 100644 --- a/{{cookiecutter.project_name}}/docs/index.md +++ b/{{cookiecutter.project_name}}/docs/index.md @@ -1,8 +1,20 @@ # {{cookiecutter.project_name}} -[![Release](https://img.shields.io/github/v/release/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}})](https://img.shields.io/github/v/release/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}) -[![Build status](https://img.shields.io/github/actions/workflow/status/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}/main.yml?branch=main)](https://github.com/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}/actions/workflows/main.yml?query=branch%3Amain) -[![Commit activity](https://img.shields.io/github/commit-activity/m/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}})](https://img.shields.io/github/commit-activity/m/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}) -[![License](https://img.shields.io/github/license/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}})](https://img.shields.io/github/license/{{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}}) +

{{cookiecutter.project_description}}

-{{cookiecutter.project_description}} +--- + +

+ + {{cookiecutter.project_name}} version + + + {{cookiecutter.project_name}} CI status + + + {{cookiecutter.project_name}} codecov + + + {{cookiecutter.project_name}} license + +

diff --git a/{{cookiecutter.project_name}}/mkdocs.yml b/{{cookiecutter.project_name}}/mkdocs.yml index 69ba4aa..d93bb1e 100644 --- a/{{cookiecutter.project_name}}/mkdocs.yml +++ b/{{cookiecutter.project_name}}/mkdocs.yml @@ -5,7 +5,7 @@ site_description: {{cookiecutter.project_description}} site_author: {{cookiecutter.author}} edit_uri: edit/main/docs/ repo_name: {{cookiecutter.author_github_handle}}/{{cookiecutter.project_name}} -copyright: Maintained by Florian. +copyright: Maintained by {{cookiecutter.author}}. nav: - Home: index.md diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 7283173..feee220 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -11,7 +11,7 @@ packages = [ ] [tool.poetry.dependencies] -python = ">=3.8,<4.0" +python = ">={{cookiecutter.minor_python_version}},<3.13" [tool.poetry.group.dev.dependencies] pytest = "^7.2.0" @@ -66,7 +66,7 @@ testpaths = ["tests"] [tool.ruff] target-version = "py39" -line-length = 120 +line-length = 100 fix = true select = [ # flake8-2020 diff --git a/{{cookiecutter.project_name}}/tox.ini b/{{cookiecutter.project_name}}/tox.ini index f694ef7..eb09e1d 100644 --- a/{{cookiecutter.project_name}}/tox.ini +++ b/{{cookiecutter.project_name}}/tox.ini @@ -1,18 +1,24 @@ [tox] skipsdist = true -envlist = py38, py39, py310, py311 +envlist = + {%- for version in cookiecutter._python_version_matrix %} + py{{ version | replace('.', '') }}{{ "," if not loop.last else "" }} + {%- endfor %} [gh-actions] python = - 3.8: py38 - 3.9: py39 - 3.10: py310 - 3.11: py311 + {%- for version in cookiecutter._python_version_matrix %} + {{ version }}: py{{ version | replace('.', '') }} + {%- endfor %} [testenv] passenv = PYTHON_VERSION allowlist_externals = poetry commands = poetry install -v + {%- if cookiecutter.codecov == "y"%} pytest --doctest-modules tests --cov --cov-config=pyproject.toml --cov-report=xml + {%- else %} + pytest --doctest-modules tests + {%- endif %} {{cookiecutter.typechecking}}