From e6b4f5c51419482b3f4893ee2fe871c4e3680743 Mon Sep 17 00:00:00 2001 From: ChrsBaur Date: Mon, 4 May 2026 01:02:43 +0200 Subject: [PATCH 1/2] feat: add GitHub Actions as CI pipeline option (closes #44) Adds `github` to the `ci_pipeline` cookiecutter choice. Generates a `.github/workflows/ci.yml` with build and test jobs for all supported package managers (conda, pip, poetry, uv). Updates post_gen_project.py to clean up the .github directory when GitHub Actions is not selected. Adds three new tests covering pip, conda, and poetry variants. --- cookiecutter.json | 5 +- hooks/post_gen_project.py | 10 +- tests/test_options.py | 33 ++++- .../.github/workflows/ci.yml | 140 ++++++++++++++++++ 4 files changed, 183 insertions(+), 5 deletions(-) create mode 100644 {{cookiecutter.project_slug}}/.github/workflows/ci.yml diff --git a/cookiecutter.json b/cookiecutter.json index 642c0af..3bc069a 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -9,7 +9,7 @@ "package_manager": ["conda", "pip", "poetry","uv"], "use_notebooks": ["no", "yes"], "use_docker": ["no", "yes"], - "ci_pipeline": ["none", "gitlab"], + "ci_pipeline": ["none", "gitlab", "github"], "create_cli": ["no", "yes"], "config_file": ["none", "hocon", "yaml"], "code_formatter": ["none", "black"], @@ -41,7 +41,8 @@ "ci_pipeline": { "__prompt__": "What [bold yellow]CI pipeline[/] would you like to use?", "none": "None", - "gitlab": "GitLab CI" + "gitlab": "GitLab CI", + "github": "GitHub Actions" }, "create_cli": { "__prompt__": "Do you want to create a [bold yellow]CLI[/] for your project?", diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 39f50af..f02ca00 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -80,7 +80,11 @@ ".gitlab-ci.yml", } -files_ci_all = files_ci_gitlab +files_ci_github = { + ".github/workflows/ci.yml", +} + +files_ci_all = files_ci_gitlab | files_ci_github folders_editor = [ '.idea__editor', @@ -182,8 +186,12 @@ def handle_ci(): ci_pipeline = '{{ cookiecutter.ci_pipeline }}' if ci_pipeline == "gitlab": _delete_files(files_ci_all - files_ci_gitlab) + shutil.rmtree(".github", ignore_errors=True) + elif ci_pipeline == "github": + _delete_files(files_ci_all - files_ci_github) elif ci_pipeline == 'none': _delete_files(files_ci_all) + shutil.rmtree(".github", ignore_errors=True) def print_success(): diff --git a/tests/test_options.py b/tests/test_options.py index 6f98d83..a73dba9 100644 --- a/tests/test_options.py +++ b/tests/test_options.py @@ -231,5 +231,34 @@ def test_no_ci_pipeline(): settings={ "ci_pipeline": "none" }, - files_non_existent=[".gitlab-ci.yml"] - ) + files_non_existent=[".gitlab-ci.yml", ".github/workflows/ci.yml"]) + + +def test_github_pip(): + check_project( + settings={ + "package_manager": "pip", + "ci_pipeline": "github" + }, + files_existent=[".github/workflows/ci.yml"], + files_non_existent=[".gitlab-ci.yml"]) + + +def test_github_conda(): + check_project( + settings={ + "package_manager": "conda", + "ci_pipeline": "github" + }, + files_existent=[".github/workflows/ci.yml"], + files_non_existent=[".gitlab-ci.yml"]) + + +def test_github_poetry(): + check_project( + settings={ + "package_manager": "poetry", + "ci_pipeline": "github" + }, + files_existent=[".github/workflows/ci.yml"], + files_non_existent=[".gitlab-ci.yml"]) diff --git a/{{cookiecutter.project_slug}}/.github/workflows/ci.yml b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml new file mode 100644 index 0000000..e204ea8 --- /dev/null +++ b/{{cookiecutter.project_slug}}/.github/workflows/ci.yml @@ -0,0 +1,140 @@ +name: CI + +on: + push: + branches: ["master", "main"] + pull_request: + branches: ["master", "main"] + +{%- if cookiecutter.package_manager == 'conda' %} +env: + CONDA_PKGS_DIRS: ~/.cache/conda +{%- elif cookiecutter.package_manager == 'poetry' %} +env: + POETRY_VERSION: "2.1.1" + POETRY_CACHE_DIR: ~/.cache/poetry +{%- endif %} + +jobs: + build: + name: Build package + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 +{%- if cookiecutter.package_manager == 'poetry' %} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: pip + - name: Install Poetry + run: pip install poetry==$POETRY_VERSION + - name: Build wheel + run: poetry build -f wheel +{%- elif cookiecutter.package_manager == 'conda' %} + - name: Set up conda (micromamba) + uses: mamba-org/setup-micromamba@v2 + with: + environment-file: environment.yml + init-shell: bash + cache-environment: true + - name: Build wheel + shell: micromamba-shell {0} + run: python setup.py bdist_wheel +{%- elif cookiecutter.package_manager == 'uv' %} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Build wheel + run: | + uv add --dev build + uv run python -m build --wheel +{%- else %} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: pip + - name: Build wheel + run: | + pip install setuptools wheel + python setup.py bdist_wheel +{%- endif %} + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: "{{ cookiecutter.module_name }}-wheel" + path: dist/{{ cookiecutter.module_name }}-*.whl + retention-days: 7 + + test: + name: Run tests + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v4 + - name: Download wheel artifact + uses: actions/download-artifact@v4 + with: + name: "{{ cookiecutter.module_name }}-wheel" + path: dist/ +{%- if cookiecutter.package_manager == 'poetry' %} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: pip + - name: Install Poetry & dependencies + run: | + pip install poetry==$POETRY_VERSION + poetry install --only=main,test + - name: Run tests + run: poetry run pytest tests --cov src --cov-report=xml + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage.xml +{%- elif cookiecutter.package_manager == 'conda' %} + - name: Set up conda (micromamba) + uses: mamba-org/setup-micromamba@v2 + with: + environment-file: environment-dev.yml + init-shell: bash + cache-environment: true + - name: Install package and run tests + shell: micromamba-shell {0} + run: | + pip install dist/{{ cookiecutter.module_name }}-*.whl + pytest tests +{%- elif cookiecutter.package_manager == 'uv' %} + - name: Install uv + uses: astral-sh/setup-uv@v5 + - name: Run tests + run: uv run pytest tests --cov src --cov-report=xml + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage.xml +{%- else %} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: pip + - name: Install dependencies + run: pip install -r requirements.txt -r requirements-dev.txt + - name: Install wheel + run: pip install dist/{{ cookiecutter.module_name }}-*.whl + - name: Run tests + run: pytest tests --cov src --cov-report=xml + - name: Upload coverage + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage.xml +{%- endif %} From 522dd8b3f3211b9b57cb6343f838a58532934fce Mon Sep 17 00:00:00 2001 From: ChrsBaur Date: Mon, 4 May 2026 01:15:42 +0200 Subject: [PATCH 2/2] chore: bump version to 1.4.0, update CHANGELOG and README for #44 --- CHANGELOG.md | 7 ++++++- README.md | 1 + pyproject.toml | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b157fba..0afc72d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.4.0] - 2026-05-04 + ### Added -- Placeholder for future updates and new features. +- Added `github` as a new `ci_pipeline` option (closes #44). Selecting GitHub Actions generates `.github/workflows/ci.yml` with `build` and `test` jobs, supporting all four package managers (conda with micromamba, pip, poetry, uv). The GitLab CI option is unchanged. + +### Changed +- Updated the README to document the new GitHub Actions CI choice under "Choices explained". ## [1.3.0] - 2026-01-27 diff --git a/README.md b/README.md index cad9cd2..09ed9ec 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,7 @@ Feedback and contributions are very welcome! Learn more in the [Contributing](#- * Select your `ci_pipeline` - `none` (default): Don't use any CI/CD pipeline. - `gitlab`: If you plan to use GitLab, this option will add a CI/CD Pipeline definition for [GitLab CI/CD](https://docs.gitlab.com/ee/ci/). The pipeline includes basic steps to build, test and deploy your code. The deployment steps do nothing but echoing a String, as deployment is very project-specific. + - `github`: If you plan to use GitHub, this option will add a workflow for [GitHub Actions](https://docs.github.com/en/actions) at `.github/workflows/ci.yml`. The workflow runs on every push and pull request to `main`/`master` and includes a `build` job (produces a wheel artifact) and a `test` job that runs your pytest suite. All package managers are supported. * `create_cli` (yes or no): if you plan to build an application with a command line interface (CLI), select *yes* here. This will integrate a template for the CLI into your project - minimal boilerplate guaranteed! (We're leveraging the awesome [typer](https://typer.tiangolo.com/) library for this.) * `config_file`: select your preferred config format. It is best practice to store your configuration separate from your code, even for small projects, but because there are a gazillion ways to do this, each project seems to reinvents the wheel. We want to provide a few options to set you up with a working configuration: - `yaml`: use [YAML](https://yaml.org/) as your configuration file format. Easy to read and write, widely adopted, relies on the [PyYAML](https://pyyaml.org/) package. diff --git a/pyproject.toml b/pyproject.toml index cdfc728..e755041 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "at-python-template" -version = "1.3.0" +version = "1.4.0" description = "This is the official Python Project Template of Alexander Thamm GmbH (AT)" authors = [ "Christian Baur ",