From f4ce25e7712d280dedf358e29c2d07093bdc8681 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 00:57:53 -0400 Subject: [PATCH 01/26] docs: replace accidentally copied CONTRIBUTING.md in generated project with a custom one --- {{cookiecutter.project_name}}/CONTRIBUTING.md | 203 +++++++++++++++--- 1 file changed, 176 insertions(+), 27 deletions(-) diff --git a/{{cookiecutter.project_name}}/CONTRIBUTING.md b/{{cookiecutter.project_name}}/CONTRIBUTING.md index f56f408..7f6aab4 100644 --- a/{{cookiecutter.project_name}}/CONTRIBUTING.md +++ b/{{cookiecutter.project_name}}/CONTRIBUTING.md @@ -1,44 +1,193 @@ -# Contributing to cookiecutter-robust-python +# Contributing to {{ cookiecutter.project_name }} -Thank you for considering contributing to the `cookiecutter-robust-python` template! We welcome contributions that help improve the template, keep its tooling current, and enhance its documentation. +Thank you for your interest in contributing to `{{ cookiecutter.package_name }}`! We welcome bug reports, feature requests, and code contributions that help improve this project. -By participating in this project, you are expected to uphold our [Code of Conduct]. +By participating in this project, you are expected to uphold our [Code of Conduct](CODE_OF_CONDUCT.md). ## How to Contribute -There are several ways to contribute: +### Reporting Bugs -1. **Reporting Bugs:** If you find an issue with the template itself (e.g., it doesn't generate correctly, the generated project's workflow doesn't work on a specific OS, a tool is misconfigured), please open an issue on the [issue tracker](https://github.com/56kyle/cookiecutter-robust-python/issues). Provide clear steps to reproduce the bug. -2. **Suggesting Enhancements:** Have an idea for a new feature, a different tool choice you think is better, or an improvement to the template structure or documentation? Open an issue on the [issue tracker](https://github.com/56kyle/cookiecutter-robust-python/issues) to discuss your suggestion. Clearly articulate the proposed change and the rationale behind it, ideally referencing the template's philosophy and criteria ([Template Philosophy](https://56kyle.github.io/cookiecutter-robust-python/philosophy.html), [Criteria for Tool Selection](https://56kyle.github.io/cookiecutter-robust-python/criteria.html)). -3. **Submitting Code Contributions:** Ready to contribute code (e.g., fix a bug, implement a suggested enhancement, update a tool version)? Please fork the repository and submit a Pull Request. +If you find a bug, please open an issue on our [issue tracker](https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/issues) with: -## Setting Up Your Development Environment +- A clear description of the bug +- Steps to reproduce the issue +- Expected vs. actual behavior +- Your environment details (Python version, OS, etc.) +- Relevant error messages or logs -Refer to the **[Getting Started: Contributing to the Template](https://56kyle.github.io/cookiecutter-robust-python/getting-started-template-contributing.html)** section in the template documentation for instructions on cloning the repository, installing template development dependencies (using uv), setting up the template's pre-commit hooks, and running template checks/tests. +### Suggesting Features -## Contribution Workflow +For feature requests, please open an issue with: -1. **Fork** the repository and **clone** your fork. -2. Create a **new branch** for your contribution based on the main branch. Use a descriptive name (e.g., `fix/ci-workflow-on-windows`, `feat/update-uv-version`). -3. Set up your development environment following the [Getting Started](https://56kyle.github.io/cookiecutter-robust-python/getting-started-template-contributing.html) guide (clone, `uv sync`, `uvx nox -s pre-commit -- install`). -4. Make your **code or documentation changes**. -5. Ensure your changes adhere to the template's **code quality standards** (configured in the template's `.pre-commit-config.yaml`, `.ruff.toml`, etc.). The pre-commit hooks will help with this. Run `uvx nox -s lint`, `uvx nox -s check` in the template repository for more comprehensive checks. -6. Ensure your changes **do not break existing functionality**. Run the template's test suite: `uvx nox -s test`. Ideally, add tests for new functionality or bug fixes. -7. Ensure the **template documentation builds correctly** with your changes: `uvx nox -s docs`. -8. Write clear, concise **commit messages** following the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification where possible, especially for significant changes (fixes, features, chore updates, etc.). -9. **Push** your branch to your fork. -10. **Open a Pull Request** from your branch to the main branch of the main template repository. Provide a clear description of your changes. Link to any relevant issues. +- A clear description of the proposed feature +- The problem it would solve or use case it would address +- Any relevant examples or mockups +- Consideration of potential alternatives -## Updating Tool Evaluations +### Contributing Code -If your contribution involves updating a major tool version or suggesting a different tool entirely, you **must** update the relevant sections in the template's documentation (`docs/topics/` files) to reflect the changes in configuration, behavior, or re-justify the choice based on the current state of the tools and criteria. This is crucial for keeping the documentation accurate and useful over time. +We welcome pull requests! For significant changes, it's best to open an issue first to discuss the approach. -## Communication +## Development Setup -For questions or discussion about contributions, open an issue or a discussion on the [GitHub repository](https://github.com/56kyle/cookiecutter-robust-python). +### Prerequisites ---- +- Python {{ cookiecutter.min_python_version }}+ (this project supports Python {{ cookiecutter.min_python_version }}-{{ cookiecutter.max_python_version }}) +- [uv](https://docs.astral.sh/uv/) for dependency management +- Git for version control + +### Setting Up Your Development Environment + +1. **Fork and clone the repository:** + ```bash + git clone https://github.com/{{ cookiecutter.github_user }}/{{ cookiecutter.project_name.replace('_', '-') }}.git + cd {{ cookiecutter.project_name }} + ``` + +2. **Install dependencies:** + ```bash + uv sync + ``` + +3. **Set up pre-commit hooks:** + ```bash + uvx nox -s pre-commit -- install + ``` + +4. **Verify your setup:** + ```bash + uvx nox -l # List available development tasks + ``` + +## Development Workflow + +### Making Changes + +1. **Create a feature branch:** + ```bash + git checkout -b feature/your-feature-name + # or + git checkout -b fix/your-bug-fix + ``` + +2. **Make your changes** following our coding standards (see below) + +3. **Test your changes:** + ```bash + # Run the full test suite + uvx nox -s tests-python + + # Run tests for a specific Python version + uvx nox -s tests-python-{{ cookiecutter.max_python_version.replace('.', '') }} + + # Run a specific test file + uvx nox -s tests-python -- tests/unit_tests/test_specific.py + ``` + +4. **Check code quality:** + ```bash + # Format code + uvx nox -s format-python + + # Lint code + uvx nox -s lint-python + + # Type check + uvx nox -s typecheck + + # Security checks + uvx nox -s security-python + + # Or run all checks at once + uvx nox -t ci + ``` + +5. **Update documentation if needed:** + ```bash + # Build docs locally + uvx nox -s build-docs + ``` + +### Coding Standards + +This project follows these standards: - +- **Code formatting:** [Ruff](https://docs.astral.sh/ruff/) (automatically applied by pre-commit) +- **Linting:** Ruff with comprehensive rule set +- **Type checking:** [Pyright](https://github.com/microsoft/pyright) +- **Security:** [Bandit](https://bandit.readthedocs.io/) for security linting +- **Commit messages:** [Conventional Commits](https://www.conventionalcommits.org/) format preferred +- **Testing:** [pytest](https://docs.pytest.org/) with good coverage + +### Testing Guidelines + +- Write tests for new functionality in the appropriate test directory: + - `tests/unit_tests/` - Fast, isolated unit tests + - `tests/integration_tests/` - Tests that involve multiple components + - `tests/acceptance_tests/` - End-to-end behavior tests +- Aim for good test coverage (check with `uvx nox -s coverage`) +- Use descriptive test names and docstrings +- Mock external dependencies appropriately + +## Submitting Changes + +### Pull Request Process + +1. **Push your branch** to your fork +2. **Open a pull request** with: + - Clear title describing the change + - Description explaining what and why + - Link to any relevant issues + - Note any breaking changes + +3. **Ensure CI passes** - all automated checks must pass +4. **Respond to review feedback** if requested +5. **Squash commits** if requested before merge + +### Pull Request Guidelines + +- Keep changes focused and atomic +- Update documentation for user-facing changes +- Add tests for new functionality +- Follow the existing code style +- Ensure all CI checks pass + +## Development Tasks Reference + +Common Nox sessions for development: + +```bash +# Code quality +uvx nox -s format-python # Format with Ruff +uvx nox -s lint-python # Lint with Ruff +uvx nox -s typecheck # Type check with Pyright +uvx nox -s security-python # Security checks + +# Testing +uvx nox -s tests-python # Run full test suite +uvx nox -s coverage # Generate coverage report + +# Documentation +uvx nox -s build-docs # Build documentation + +# Building +uvx nox -s build-python # Build package + +# Run everything CI runs +uvx nox -t ci # All CI checks +``` + +## Getting Help + +- Check existing [issues](https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/issues) and [discussions](https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/discussions) +- Open a new issue for bugs or feature requests +- Start a discussion for questions or ideas + +## Recognition + +Contributors will be recognized in our release notes and documentation. Thank you for helping make this project better! + +--- -[code of conduct]: CODE_OF_CONDUCT.md +*This project was generated from the [cookiecutter-robust-python](https://github.com/56kyle/cookiecutter-robust-python) template.* From 36ab3dd8b6b4c9901f6db5b2720c480e3f544123 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 01:04:19 -0400 Subject: [PATCH 02/26] docs: consolidate links in the generated project's CONTRIBUTING.md --- {{cookiecutter.project_name}}/CONTRIBUTING.md | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/{{cookiecutter.project_name}}/CONTRIBUTING.md b/{{cookiecutter.project_name}}/CONTRIBUTING.md index 7f6aab4..e7b4ce2 100644 --- a/{{cookiecutter.project_name}}/CONTRIBUTING.md +++ b/{{cookiecutter.project_name}}/CONTRIBUTING.md @@ -2,13 +2,13 @@ Thank you for your interest in contributing to `{{ cookiecutter.package_name }}`! We welcome bug reports, feature requests, and code contributions that help improve this project. -By participating in this project, you are expected to uphold our [Code of Conduct](CODE_OF_CONDUCT.md). +By participating in this project, you are expected to uphold our [Code of Conduct][code-of-conduct]. ## How to Contribute ### Reporting Bugs -If you find a bug, please open an issue on our [issue tracker](https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/issues) with: +If you find a bug, please open an issue on our [issue tracker][issues] with: - A clear description of the bug - Steps to reproduce the issue @@ -34,7 +34,7 @@ We welcome pull requests! For significant changes, it's best to open an issue fi ### Prerequisites - Python {{ cookiecutter.min_python_version }}+ (this project supports Python {{ cookiecutter.min_python_version }}-{{ cookiecutter.max_python_version }}) -- [uv](https://docs.astral.sh/uv/) for dependency management +- [uv][uv-documentation] for dependency management - Git for version control ### Setting Up Your Development Environment @@ -113,12 +113,12 @@ We welcome pull requests! For significant changes, it's best to open an issue fi This project follows these standards: -- **Code formatting:** [Ruff](https://docs.astral.sh/ruff/) (automatically applied by pre-commit) +- **Code formatting:** [Ruff][ruff-documentation] (automatically applied by pre-commit) - **Linting:** Ruff with comprehensive rule set -- **Type checking:** [Pyright](https://github.com/microsoft/pyright) -- **Security:** [Bandit](https://bandit.readthedocs.io/) for security linting -- **Commit messages:** [Conventional Commits](https://www.conventionalcommits.org/) format preferred -- **Testing:** [pytest](https://docs.pytest.org/) with good coverage +- **Type checking:** [Pyright][pyright-documentation] +- **Security:** [Bandit][bandit-documentation] for security linting +- **Commit messages:** [Conventional Commits][conventional-commits] format preferred +- **Testing:** [pytest][pytest-documentation] with good coverage ### Testing Guidelines @@ -180,7 +180,7 @@ uvx nox -t ci # All CI checks ## Getting Help -- Check existing [issues](https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/issues) and [discussions](https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/discussions) +- Check existing [issues][issues] and [discussions][discussions] - Open a new issue for bugs or feature requests - Start a discussion for questions or ideas @@ -190,4 +190,16 @@ Contributors will be recognized in our release notes and documentation. Thank yo --- -*This project was generated from the [cookiecutter-robust-python](https://github.com/56kyle/cookiecutter-robust-python) template.* +*This project was generated from the [cookiecutter-robust-python][cookiecutter-robust-python] template.* + + +[code-of-conduct]: CODE_OF_CONDUCT.md +[issues]: https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/issues +[discussions]: https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/discussions +[uv-documentation]: https://docs.astral.sh/uv/ +[ruff-documentation]: https://docs.astral.sh/ruff/ +[pyright-documentation]: https://github.com/microsoft/pyright +[bandit-documentation]: https://bandit.readthedocs.io/ +[conventional-commits]: https://www.conventionalcommits.org/ +[pytest-documentation]: https://docs.pytest.org/ +[cookiecutter-robust-python]: https://github.com/56kyle/cookiecutter-robust-python From c423ae14339544785b879da1ea9150b4bfaa42f2 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 01:17:56 -0400 Subject: [PATCH 03/26] feat: swap github_user with more generic options to allow for bitbucket/gitlab compatibility moving forward --- cookiecutter.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cookiecutter.json b/cookiecutter.json index af9e48c..7178392 100644 --- a/cookiecutter.json +++ b/cookiecutter.json @@ -7,7 +7,9 @@ "add_rust_extension": false, "author": "Kyle Oliver", "email": "56kyleoliver+cookiecutter-robust-python@gmail.com", - "github_user": "56kyle", + "repository_provider": ["github", "gitlab", "bitbucket"], + "repository_host": "{% if cookiecutter.repository_provider == 'github' %}github.com{% elif cookiecutter.repository_provider == 'gitlab' %}gitlab.com{% else %}bitbucket.org{% endif %}", + "repository_path": "56kyle/{{ cookiecutter.project_name.replace('_', '-') }}", "version": "0.0.0", "copyright_year": "{% now 'utc', '%Y' %}", "license": ["MIT", "Apache-2.0", "GPL-3.0"], From 3984e444dd79eacda9b4e7ac905ea15e36314d91 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 01:20:53 -0400 Subject: [PATCH 04/26] docs: swap urls in CONTRIBUTING.md to use new cookiecutter kwargs --- {{cookiecutter.project_name}}/CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/{{cookiecutter.project_name}}/CONTRIBUTING.md b/{{cookiecutter.project_name}}/CONTRIBUTING.md index e7b4ce2..0a6e37d 100644 --- a/{{cookiecutter.project_name}}/CONTRIBUTING.md +++ b/{{cookiecutter.project_name}}/CONTRIBUTING.md @@ -41,7 +41,7 @@ We welcome pull requests! For significant changes, it's best to open an issue fi 1. **Fork and clone the repository:** ```bash - git clone https://github.com/{{ cookiecutter.github_user }}/{{ cookiecutter.project_name.replace('_', '-') }}.git + git clone https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}.git cd {{ cookiecutter.project_name }} ``` @@ -180,7 +180,7 @@ uvx nox -t ci # All CI checks ## Getting Help -- Check existing [issues][issues] and [discussions][discussions] +- Check existing [issues][issues]{% if cookiecutter.repository_provider == 'github' %} and [discussions][discussions]{% endif %} - Open a new issue for bugs or feature requests - Start a discussion for questions or ideas @@ -194,8 +194,8 @@ Contributors will be recognized in our release notes and documentation. Thank yo [code-of-conduct]: CODE_OF_CONDUCT.md -[issues]: https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/issues -[discussions]: https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/discussions +[issues]: https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}/issues +{% if cookiecutter.repository_provider == 'github' %}[discussions]: https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}/discussions{% endif %} [uv-documentation]: https://docs.astral.sh/uv/ [ruff-documentation]: https://docs.astral.sh/ruff/ [pyright-documentation]: https://github.com/microsoft/pyright From 5704d987ba88853747e8981246ede7ad351a9ae9 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 01:23:44 -0400 Subject: [PATCH 05/26] feat: swap urls that used old github_user cookiecutter value --- {{cookiecutter.project_name}}/README.md | 4 ++-- {{cookiecutter.project_name}}/noxfile.py | 3 ++- {{cookiecutter.project_name}}/pyproject.toml | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/{{cookiecutter.project_name}}/README.md b/{{cookiecutter.project_name}}/README.md index c6fe7ed..a38a426 100644 --- a/{{cookiecutter.project_name}}/README.md +++ b/{{cookiecutter.project_name}}/README.md @@ -4,7 +4,7 @@ --- -**[📚 View Documentation](https://{{ cookiecutter.project_name.replace('_', '-') }}.readthedocs.io/)** | **[🐛 Report a Bug](https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/issues)** | **[✨ Request a Feature](https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/issues)** +**[📚 View Documentation](https://{{ cookiecutter.project_name.replace('_', '-') }}.readthedocs.io/)** | **[🐛 Report a Bug](https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}/issues)** | **[✨ Request a Feature](https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}/issues)** --- @@ -22,7 +22,7 @@ To set up `{{ cookiecutter.package_name }}` for local development: 1. Clone the repository: ```bash - git clone https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}.git + git clone https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}.git cd {{ cookiecutter.project_name }} ``` 2. Install dependencies using [:term:`uv`](uv-documentation): diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index b7e2f91..bdc7590 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -29,7 +29,8 @@ PROJECT_NAME: str = "{{cookiecutter.project_name}}" PACKAGE_NAME: str = "{{cookiecutter.package_name}}" -GITHUB_USER: str = "{{cookiecutter.github_user}}" +REPOSITORY_HOST: str = "{{cookiecutter.repository_host}}" +REPOSITORY_PATH: str = "{{cookiecutter.repository_path}}" ENV: str = "env" FORMAT: str = "format" diff --git a/{{cookiecutter.project_name}}/pyproject.toml b/{{cookiecutter.project_name}}/pyproject.toml index 59b0e1f..093f774 100644 --- a/{{cookiecutter.project_name}}/pyproject.toml +++ b/{{cookiecutter.project_name}}/pyproject.toml @@ -47,8 +47,8 @@ docs = [ ] [project.urls] -Homepage = "https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}" -Repository = "https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}" +Homepage = "https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}" +Repository = "https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}" [[tool.uv.index]] name = "testpypi" From fcb8666f724b5132ac9a8efaab173b2e6c6c2b3c Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 01:25:06 -0400 Subject: [PATCH 06/26] feat: adapt setup-remote.py to account for the new cookiecutter values --- .../scripts/setup-remote.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/{{cookiecutter.project_name}}/scripts/setup-remote.py b/{{cookiecutter.project_name}}/scripts/setup-remote.py index c77fe43..06b5056 100644 --- a/{{cookiecutter.project_name}}/scripts/setup-remote.py +++ b/{{cookiecutter.project_name}}/scripts/setup-remote.py @@ -15,15 +15,15 @@ def main() -> None: """Parses command line input and passes it through to setup_git.""" parser: argparse.ArgumentParser = get_parser() args: argparse.Namespace = parser.parse_args() - setup_remote(path=args.path, github_user=args.github_user, repo_name=args.repo_name) + setup_remote(path=args.path, repository_host=args.repository_host, repository_path=args.repository_path) -def setup_remote(path: Path, github_user: str, repo_name: str) -> None: +def setup_remote(path: Path, repository_host: str, repository_path: str) -> None: """Set up the provided cookiecutter-robust-python project's git repo.""" commands: list[list[str]] = [ ["git", "fetch", "origin"], - ["git", "remote", "add", "origin", f"https://github.com/{github_user}/{repo_name}.git"], - ["git", "remote", "set-url", "origin", f"https://github.com/{github_user}/{repo_name}.git"], + ["git", "remote", "add", "origin", f"https://{repository_host}/{repository_path}.git"], + ["git", "remote", "set-url", "origin", f"https://{repository_host}/{repository_path}.git"], ["git", "pull"], ["git", "checkout", "main"], ["git", "push", "-u", "origin", "main"], @@ -40,7 +40,7 @@ def get_parser() -> argparse.ArgumentParser: """Creates the argument parser for setup-git.""" parser: argparse.ArgumentParser = argparse.ArgumentParser( prog="setup-git", - usage="python ./scripts/setup-remote.py . -u 56kyle -n robust-python-demo", + usage="python ./scripts/setup-remote.py . -h github.com -p 56kyle/robust-python-demo", description="Set up the provided cookiecutter-robust-python project's remote repo connection.", ) parser.add_argument( @@ -49,8 +49,8 @@ def get_parser() -> argparse.ArgumentParser: metavar="PATH", help="Path to the repo's root directory (must already exist).", ) - parser.add_argument("-u", "--user", dest="github_user", help="GitHub user name.") - parser.add_argument("-n", "--name", dest="repo_name", help="Name of the repo.") + parser.add_argument("-h", "--host", dest="repository_host", help="Repository host (e.g., github.com, gitlab.com).") + parser.add_argument("-p", "--path", dest="repository_path", help="Repository path (e.g., user/repo, group/subgroup/repo).") return parser From 7fc7806335f2b6f0a2d0f92e1ebfa271eee200b9 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 01:34:15 -0400 Subject: [PATCH 07/26] docs: adjust README.md in generated project to account for new cookiecutter values --- {{cookiecutter.project_name}}/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/README.md b/{{cookiecutter.project_name}}/README.md index a38a426..4567e15 100644 --- a/{{cookiecutter.project_name}}/README.md +++ b/{{cookiecutter.project_name}}/README.md @@ -77,7 +77,7 @@ Explore the `noxfile.py` and the project documentation for detailed information (This section should guide contributions *to this specific generated project*, not the template. It should refer to the project's `CODE_OF_CONDUCT.md` and link to a `CONTRIBUTING.md` specific to the project, if you choose to generate one.) -Report bugs or suggest features via the [issue tracker](https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}/issues). +Report bugs or suggest features via the [issue tracker](https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}/issues). See [CONTRIBUTING.md](#) for contribution guidelines. From 612b7a31d25a57eeddfa342449e9b98f8f5ef139 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 01:36:22 -0400 Subject: [PATCH 08/26] feat: adjust base_url in .cz.toml of the generated project to account for cookiecutter value changes --- {{cookiecutter.project_name}}/.cz.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/{{cookiecutter.project_name}}/.cz.toml b/{{cookiecutter.project_name}}/.cz.toml index 619a25e..d7c0733 100644 --- a/{{cookiecutter.project_name}}/.cz.toml +++ b/{{cookiecutter.project_name}}/.cz.toml @@ -16,4 +16,4 @@ update_changelog_on_bump = true release = true release_asset_path = "dist/*" release_asset_descriptions = { "*.tar.gz" = "Source distribution", "*.whl" = "Python Wheel" } -base_url = "https://github.com/{{ cookiecutter.github_user | lower | replace(' ', '-') }}/{{ cookiecutter.project_name.replace('_', '-') }}" +base_url = "https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}" From 63ca7eb84e2a391979326abbeedb5bf0d40edaa5 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 02:05:15 -0400 Subject: [PATCH 09/26] feat: add proof of concept gitlab cicd --- ...er == 'gitlab' %}.gitlab-ci.yml{% endif %} | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} new file mode 100644 index 0000000..713930f --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} @@ -0,0 +1,229 @@ +# .gitlab-ci.yml +# See https://docs.gitlab.com/ee/ci/yaml/ + +# Global settings +image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim + +variables: + UV_CACHE_DIR: .uv-cache + UV_LINK_MODE: copy + PIP_CACHE_DIR: $CI_PROJECT_DIR/.cache/pip + +# Define stages +stages: + - quality + - test + - security + - build + - release + +# Global cache configuration for uv +.uv-cache: &uv-cache + cache: + key: + files: + - pyproject.toml + paths: + - $UV_CACHE_DIR + - $PIP_CACHE_DIR + policy: pull-push + +# Shared rules for when to run jobs +.on-merge-requests-and-main: &on-merge-requests-and-main + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + - if: $CI_COMMIT_BRANCH == "main" + - if: $CI_PIPELINE_SOURCE == "web" + +# Base job template for Python quality checks +.quality-job: &quality-job + stage: quality + <<: *uv-cache + <<: *on-merge-requests-and-main + before_script: + - uv --version + after_script: + - uv cache prune --ci + +# Python Quality Checks Jobs +format-python: + <<: *quality-job + script: + - uvx nox -s format-python + changes: + - "src/**/*.py" + - "tests/**/*.py" + - "noxfile.py" + - "pyproject.toml" + - ".ruff.toml" + - ".pydocstyle" + - ".gitlab-ci.yml" + +lint-python: + <<: *quality-job + script: + - uvx nox -s lint-python + changes: + - "src/**/*.py" + - "tests/**/*.py" + - "noxfile.py" + - "pyproject.toml" + - ".ruff.toml" + - ".pydocstyle" + - ".gitlab-ci.yml" + +typecheck-python: + <<: *quality-job + script: + - uvx nox -s typecheck + changes: + - "src/**/*.py" + - "tests/**/*.py" + - "noxfile.py" + - "pyproject.toml" + - "pyrightconfig.json" + - ".gitlab-ci.yml" + +# Security Checks +security-python: + stage: security + <<: *uv-cache + <<: *on-merge-requests-and-main + script: + - uvx nox -s security-python + after_script: + - uv cache prune --ci + allow_failure: true + changes: + - "src/**/*.py" + - "tests/**/*.py" + - "noxfile.py" + - "pyproject.toml" + - "bandit.yml" + - ".gitlab-ci.yml" + +# Python Tests - Using GitLab Matrix Strategy +test-python: + stage: test + <<: *uv-cache + <<: *on-merge-requests-and-main + parallel: + matrix: + - PYTHON_VERSION: ["3.9", "3.10", "3.11", "3.12", "3.13"] + OS_IMAGE: ["bookworm-slim"] + # Add cross-platform testing for latest Python version + - PYTHON_VERSION: ["3.13"] + OS_IMAGE: ["alpine", "bookworm-slim"] + image: ghcr.io/astral-sh/uv:latest-python$PYTHON_VERSION-$OS_IMAGE + script: + - uvx nox -s tests-python-${PYTHON_VERSION//.} + after_script: + - uv cache prune --ci + artifacts: + reports: + junit: tests/results/*.xml + coverage_report: + coverage_format: cobertura + path: coverage.xml + paths: + - tests/results/ + - coverage.xml + expire_in: 5 days + changes: + - "src/**/*.py" + - "tests/**/*.py" + - "noxfile.py" + - "pyproject.toml" + - ".coveragerc" + - ".gitlab-ci.yml" + +{% if cookiecutter.add_rust_extension == 'y' -%} +# Rust-specific jobs (conditional on rust extension flag) +.rust-job: &rust-job + image: rust:latest + stage: quality + <<: *on-merge-requests-and-main + changes: + - "rust/**/*.rs" + - "Cargo.toml" + - ".gitlab-ci.yml" + +format-rust: + <<: *rust-job + before_script: + - rustup component add rustfmt + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:/root/.cargo/bin" + script: + - uvx nox -s format-rust + +lint-rust: + <<: *rust-job + before_script: + - rustup component add clippy + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:/root/.cargo/bin" + script: + - uvx nox -s lint-rust + +test-rust: + <<: *rust-job + stage: test + before_script: + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:/root/.cargo/bin" + script: + - uvx nox -s test-rust +{%- endif %} + +# Build Stage +build-python: + stage: build + <<: *uv-cache + script: + - uvx nox -s build-python + after_script: + - uv cache prune --ci + artifacts: + paths: + - dist/ + expire_in: 30 days + rules: + - if: $CI_COMMIT_TAG + - if: $CI_COMMIT_BRANCH == "main" + - if: $CI_PIPELINE_SOURCE == "web" + +# Documentation build (GitLab Pages) +pages: + stage: build + <<: *uv-cache + script: + - uvx nox -s build-docs + - mv docs/_build/html public + after_script: + - uv cache prune --ci + artifacts: + paths: + - public + expire_in: 30 days + rules: + - if: $CI_COMMIT_BRANCH == "main" + changes: + - "docs/**/*" + - "src/**/*.py" + - "noxfile.py" + - "pyproject.toml" + +# Release Job (only on tags) +release-python: + stage: release + <<: *uv-cache + script: + - uvx nox -s publish-python + after_script: + - uv cache prune --ci + rules: + - if: $CI_COMMIT_TAG + environment: + name: production + url: https://pypi.org/project/{{ cookiecutter.package_name }}/ From 3c9b1f226606f48d72d59043f729f0d222d0209a Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 02:44:09 -0400 Subject: [PATCH 10/26] feat: ensure the .github folder isn't created when not using github --- {{cookiecutter.project_name}}/.readthedocs.yml | 14 -------------- .../dependabot.yml | 0 .../workflows/.python-version | 0 .../workflows/build-docs.yml | 0 .../workflows/lint-python.yml | 0 .../workflows/prepare-release.yml | 0 .../workflows/release-python.yml | 0 .../workflows/security-python.yml | 0 .../workflows/test-python.yml | 0 .../workflows/typecheck-python.yml | 0 ..._extension == 'y' %} lint-rust.yml {%- endif %} | 0 ..._extension == 'y' %} test-rust.yml {%- endif %} | 0 12 files changed, 14 deletions(-) delete mode 100644 {{cookiecutter.project_name}}/.readthedocs.yml rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/dependabot.yml (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/.python-version (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/build-docs.yml (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/lint-python.yml (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/prepare-release.yml (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/release-python.yml (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/security-python.yml (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/test-python.yml (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/typecheck-python.yml (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} (100%) rename {{cookiecutter.project_name}}/{.github => {% if cookiecutter.repository_provider == 'github' %}.github{% endif %}}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} (100%) diff --git a/{{cookiecutter.project_name}}/.readthedocs.yml b/{{cookiecutter.project_name}}/.readthedocs.yml deleted file mode 100644 index e9d27b7..0000000 --- a/{{cookiecutter.project_name}}/.readthedocs.yml +++ /dev/null @@ -1,14 +0,0 @@ -version: 2 - -build: - os: ubuntu-22.04 - tools: - python: "3.13" - -python: - install: - - requirements: docs/requirements.txt - - path: . - -sphinx: - configuration: docs/conf.py diff --git a/{{cookiecutter.project_name}}/.github/dependabot.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/dependabot.yml similarity index 100% rename from {{cookiecutter.project_name}}/.github/dependabot.yml rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/dependabot.yml diff --git a/{{cookiecutter.project_name}}/.github/workflows/.python-version b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/.python-version similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/.python-version rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/.python-version diff --git a/{{cookiecutter.project_name}}/.github/workflows/build-docs.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/build-docs.yml similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/build-docs.yml rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/build-docs.yml diff --git a/{{cookiecutter.project_name}}/.github/workflows/lint-python.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/lint-python.yml rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml diff --git a/{{cookiecutter.project_name}}/.github/workflows/prepare-release.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/prepare-release.yml similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/prepare-release.yml rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/prepare-release.yml diff --git a/{{cookiecutter.project_name}}/.github/workflows/release-python.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/release-python.yml similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/release-python.yml rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/release-python.yml diff --git a/{{cookiecutter.project_name}}/.github/workflows/security-python.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/security-python.yml similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/security-python.yml rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/security-python.yml diff --git a/{{cookiecutter.project_name}}/.github/workflows/test-python.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/test-python.yml similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/test-python.yml rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/test-python.yml diff --git a/{{cookiecutter.project_name}}/.github/workflows/typecheck-python.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/typecheck-python.yml similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/typecheck-python.yml rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/typecheck-python.yml diff --git a/{{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} diff --git a/{{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} similarity index 100% rename from {{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} rename to {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} From 3f54dfb4adb1bd8755ebc9f1a193e00046b1e57b Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 02:45:40 -0400 Subject: [PATCH 11/26] docs: update several non reference links --- {{cookiecutter.project_name}}/README.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/README.md b/{{cookiecutter.project_name}}/README.md index 4567e15..2cbcb2a 100644 --- a/{{cookiecutter.project_name}}/README.md +++ b/{{cookiecutter.project_name}}/README.md @@ -59,7 +59,7 @@ If your project defines command-line entry points in `pyproject.toml`: # {{ cookiecutter.project_name }} do-something --input file.txt ``` -For detailed API documentation and CLI command references, see the **[Documentation](https://{{ cookiecutter.project_name.replace('_', '-') }}.readthedocs.io/)**. +For detailed API documentation and CLI command references, see the **[Documentation][documentation]**. ## Development Workflow @@ -87,4 +87,15 @@ Distributed under the terms of the **{{ cookiecutter.license }}** license. See [ --- -**This project was generated from the [cookiecutter-robust-python template](https://github.com/56kyle/cookiecutter-robust-python).** +**This project was generated from the [cookiecutter-robust-python template][cookiecutter-robust-python].** + + +[cookiecutter-robust-python]: https://github.com/56kyle/cookiecutter-robust-python + +{% if cookiecutter.repository_provider == 'github' %} +[documentation]: https://{{ cookiecutter.repository_path.split('/')[0] }}.github.io/{{ cookiecutter.project_name.replace('_', '-') }}/ +{% elif cookiecutter.repository_provider == 'gitlab' %} +[documentation]: https://{{ cookiecutter.repository_path.replace('/', '.') }}.gitlab.io/{{ cookiecutter.project_name.replace('_', '-') }}/ +{% else %} +[documentation]: https://{{ cookiecutter.project_name.replace('_', '-') }}.readthedocs.io/ +{% endif %} From 2d6546968c399adf690c757fa893e9bc015b086c Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 02:46:19 -0400 Subject: [PATCH 12/26] feat: add a proof of concept bitbucket-pipelines.yml --- ...cket' %}bitbucket-pipelines.yml{% endif %} | 383 ++++++++++++++++++ 1 file changed, 383 insertions(+) create mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} new file mode 100644 index 0000000..531b2b7 --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} @@ -0,0 +1,383 @@ +# bitbucket-pipelines.yml +# See https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines/ + +image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim + +definitions: + caches: + uv: .uv-cache + pip: .cache/pip + + # Shared configuration for uv + services: + docker: + memory: 2048 + +pipelines: + # Default pipeline for pushes to any branch + default: + - step: + name: Python Quality Checks + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uv --version + - uvx nox -s format-python + - uvx nox -s lint-python + - uvx nox -s typecheck + - uv cache prune --ci + after-script: + - echo "Python quality checks completed" + +{% if cookiecutter.add_rust_extension == 'y' -%} + - step: + name: Rust Quality Checks + image: rust:latest + script: + - rustup component add rustfmt clippy + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:/root/.cargo/bin" + - uvx nox -s format-rust + - uvx nox -s lint-rust + after-script: + - echo "Rust quality checks completed" +{%- endif %} + + - step: + name: Security Checks + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s security-python + - uv cache prune --ci + after-script: + - echo "Security checks completed" + + # Parallel test execution across Python versions + - parallel: + steps: + - step: + name: Test Python 3.9 + image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-39 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.10 + image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-310 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.11 + image: ghcr.io/astral-sh/uv:latest-python3.11-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-311 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.12 + image: ghcr.io/astral-sh/uv:latest-python3.12-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-312 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.13 + image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-313 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + +{% if cookiecutter.add_rust_extension == 'y' -%} + - step: + name: Test Rust + image: rust:latest + script: + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:/root/.cargo/bin" + - uvx nox -s test-rust +{%- endif %} + + # Pipeline for main/master branch + branches: + main: + - step: + name: Python Quality Checks + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uv --version + - uvx nox -s format-python + - uvx nox -s lint-python + - uvx nox -s typecheck + - uv cache prune --ci + +{% if cookiecutter.add_rust_extension == 'y' -%} + - step: + name: Rust Quality Checks + image: rust:latest + script: + - rustup component add rustfmt clippy + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:/root/.cargo/bin" + - uvx nox -s format-rust + - uvx nox -s lint-rust +{%- endif %} + + - step: + name: Security Checks + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s security-python + - uv cache prune --ci + + # Parallel test execution + - parallel: + steps: + - step: + name: Test Python 3.9 + image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-39 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.10 + image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-310 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.11 + image: ghcr.io/astral-sh/uv:latest-python3.11-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-311 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.12 + image: ghcr.io/astral-sh/uv:latest-python3.12-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-312 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.13 + image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-313 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + +{% if cookiecutter.add_rust_extension == 'y' -%} + - step: + name: Test Rust + image: rust:latest + script: + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:/root/.cargo/bin" + - uvx nox -s test-rust +{%- endif %} + + - step: + name: Build Package + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s build-python + - uv cache prune --ci + artifacts: + - dist/** + + # Pipeline for pull requests + pull-requests: + '**': + - step: + name: Python Quality Checks + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uv --version + - uvx nox -s format-python + - uvx nox -s lint-python + - uvx nox -s typecheck + - uv cache prune --ci + +{% if cookiecutter.add_rust_extension == 'y' -%} + - step: + name: Rust Quality Checks + image: rust:latest + script: + - rustup component add rustfmt clippy + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:/root/.cargo/bin" + - uvx nox -s format-rust + - uvx nox -s lint-rust +{%- endif %} + + - step: + name: Security Checks + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s security-python + - uv cache prune --ci + + # Parallel test execution for PRs (reduced matrix for speed) + - parallel: + steps: + - step: + name: Test Python 3.9 + image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-39 + - uv cache prune --ci + + - step: + name: Test Python 3.13 + image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-313 + - uv cache prune --ci + +{% if cookiecutter.add_rust_extension == 'y' -%} + - step: + name: Test Rust + image: rust:latest + script: + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:/root/.cargo/bin" + - uvx nox -s test-rust +{%- endif %} + + # Pipeline for tags (releases) + tags: + 'v*': + - step: + name: Build and Release + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s build-python + - uvx nox -s publish-python + - uv cache prune --ci + artifacts: + - dist/** \ No newline at end of file From 78fae0d005a10b5a0afbba3e61a12efbaae6b8f1 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 02:46:38 -0400 Subject: [PATCH 13/26] refactor: ensure .readthedocs.yml is only used with bitbucket --- ...er == 'bitbucket' %}.readthedocs.yml{% endif %} | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}.readthedocs.yml{% endif %} diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}.readthedocs.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}.readthedocs.yml{% endif %} new file mode 100644 index 0000000..e9d27b7 --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}.readthedocs.yml{% endif %} @@ -0,0 +1,14 @@ +version: 2 + +build: + os: ubuntu-22.04 + tools: + python: "3.13" + +python: + install: + - requirements: docs/requirements.txt + - path: . + +sphinx: + configuration: docs/conf.py From 3abe1931e67a215691faa618da41c409ce9062d2 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 03:14:30 -0400 Subject: [PATCH 14/26] feat: combine various styling nox sessions in cicd --- {{cookiecutter.project_name}}/noxfile.py | 16 +++++++-------- .../workflows/lint-python.yml | 7 ++----- ...er == 'gitlab' %}.gitlab-ci.yml{% endif %} | 20 ++++--------------- 3 files changed, 14 insertions(+), 29 deletions(-) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index bdc7590..018a862 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -43,7 +43,7 @@ DOCS: str = "docs" BUILD: str = "build" RELEASE: str = "release" -CI: str = "ci" +QUALITY: str = "quality" PYTHON: str = "python" RUST: str = "rust" @@ -60,7 +60,7 @@ def setup_venv(session: Session) -> None: session.run("python", SCRIPTS_FOLDER / "setup-venv.py", REPO_ROOT, "-p", PYTHON_VERSIONS[0], external=True) -@nox.session(python=DEFAULT_PYTHON_VERSION, name="pre-commit", tags=[CI]) +@nox.session(python=DEFAULT_PYTHON_VERSION, name="pre-commit", tags=[QUALITY]) def precommit(session: Session) -> None: """Lint using pre-commit.""" args: list[str] = session.posargs or ["run", "--all-files", "--show-diff-on-failure"] @@ -73,7 +73,7 @@ def precommit(session: Session) -> None: activate_virtualenv_in_precommit_hooks(session) -@nox.session(python=False, name="format-python", tags=[FORMAT, PYTHON]) +@nox.session(python=False, name="format-python", tags=[FORMAT, PYTHON, QUALITY]) def format_python(session: Session) -> None: """Run Python code formatter (Ruff format).""" session.log(f"Running Ruff formatter check with py{session.python}.") @@ -91,7 +91,7 @@ def format_rust(session: Session) -> None: {% endif -%} -@nox.session(python=False, name="lint-python", tags=[LINT, PYTHON]) +@nox.session(python=False, name="lint-python", tags=[LINT, PYTHON, QUALITY]) def lint_python(session: Session) -> None: """Run Python code linters (Ruff check, Pydocstyle rules).""" session.log(f"Running Ruff check with py{session.python}.") @@ -109,7 +109,7 @@ def lint_rust(session: Session) -> None: {% endif -%} -@nox.session(python=PYTHON_VERSIONS, name="typecheck", tags=[TYPE, PYTHON, CI]) +@nox.session(python=PYTHON_VERSIONS, name="typecheck", tags=[TYPE, PYTHON]) def typecheck(session: Session) -> None: """Run static type checking (Pyright) on Python code.""" session.log("Installing type checking dependencies...") @@ -119,7 +119,7 @@ def typecheck(session: Session) -> None: session.run("pyright", "--pythonversion", session.python) -@nox.session(python=False, name="security-python", tags=[SECURITY, PYTHON, CI]) +@nox.session(python=False, name="security-python", tags=[SECURITY, PYTHON]) def security_python(session: Session) -> None: """Run code security checks (Bandit) on Python code.""" session.log(f"Running Bandit static security analysis with py{session.python}.") @@ -130,7 +130,7 @@ def security_python(session: Session) -> None: {% if cookiecutter.add_rust_extension == 'y' -%} -@nox.session(python=False, name="security-rust", tags=[SECURITY, RUST, CI]) +@nox.session(python=False, name="security-rust", tags=[SECURITY, RUST]) def security_rust(session: Session) -> None: """Run code security checks (cargo audit).""" session.log("Installing security dependencies...") @@ -139,7 +139,7 @@ def security_rust(session: Session) -> None: {% endif -%} -@nox.session(python=PYTHON_VERSIONS, name="tests-python", tags=[TEST, PYTHON, CI]) +@nox.session(python=PYTHON_VERSIONS, name="tests-python", tags=[TEST, PYTHON]) def tests_python(session: Session) -> None: """Run the Python test suite (pytest with coverage).""" session.log("Installing test dependencies...") diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml index 6acae86..f821c63 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml @@ -45,8 +45,5 @@ jobs: with: python-version-file: ".github/workflows/.python-version" - - name: Run formatting checks - run: uvx nox -s format-python - - - name: Run linting checks - run: uvx nox -s lint-python + - name: Run Python quality checks + run: uvx nox -t quality diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} index 713930f..c627241 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} @@ -45,24 +45,11 @@ stages: after_script: - uv cache prune --ci -# Python Quality Checks Jobs -format-python: +# Python Quality Checks Job +quality-python: <<: *quality-job script: - - uvx nox -s format-python - changes: - - "src/**/*.py" - - "tests/**/*.py" - - "noxfile.py" - - "pyproject.toml" - - ".ruff.toml" - - ".pydocstyle" - - ".gitlab-ci.yml" - -lint-python: - <<: *quality-job - script: - - uvx nox -s lint-python + - uvx nox -t quality changes: - "src/**/*.py" - "tests/**/*.py" @@ -70,6 +57,7 @@ lint-python: - "pyproject.toml" - ".ruff.toml" - ".pydocstyle" + - "pyrightconfig.json" - ".gitlab-ci.yml" typecheck-python: From 43440cd27af9747c5ca1b98348feb9cc51666461 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 03:18:53 -0400 Subject: [PATCH 15/26] feat: adjust cicd in gitlab and bitbucket pipelines to ensure parity between platform providers in cicd --- ...cket' %}bitbucket-pipelines.yml{% endif %} | 63 +++++++++++++++++++ ...er == 'gitlab' %}.gitlab-ci.yml{% endif %} | 15 ++++- 2 files changed, 76 insertions(+), 2 deletions(-) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} index 531b2b7..37cec6a 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} @@ -59,6 +59,69 @@ pipelines: after-script: - echo "Security checks completed" + # Parallel typecheck execution across Python versions + - parallel: + steps: + - step: + name: Typecheck Python 3.9 + image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.9 + - uv cache prune --ci + + - step: + name: Typecheck Python 3.10 + image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.10 + - uv cache prune --ci + + - step: + name: Typecheck Python 3.11 + image: ghcr.io/astral-sh/uv:latest-python3.11-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.11 + - uv cache prune --ci + + - step: + name: Typecheck Python 3.12 + image: ghcr.io/astral-sh/uv:latest-python3.12-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.12 + - uv cache prune --ci + + - step: + name: Typecheck Python 3.13 + image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.13 + - uv cache prune --ci + # Parallel test execution across Python versions - parallel: steps: diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} index c627241..19929ae 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} @@ -12,6 +12,7 @@ variables: # Define stages stages: - quality + - typecheck - test - security - build @@ -61,9 +62,19 @@ quality-python: - ".gitlab-ci.yml" typecheck-python: - <<: *quality-job + stage: typecheck + <<: *uv-cache + <<: *on-merge-requests-and-main + parallel: + matrix: + - PYTHON_VERSION: ["3.9", "3.10", "3.11", "3.12", "3.13"] + image: ghcr.io/astral-sh/uv:latest-python$PYTHON_VERSION-bookworm-slim + before_script: + - uv --version script: - - uvx nox -s typecheck + - uvx nox -s typecheck-$PYTHON_VERSION + after_script: + - uv cache prune --ci changes: - "src/**/*.py" - "tests/**/*.py" From f9948625d9635659036ba11cfaf86a4cba8ff090 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 18:53:21 -0400 Subject: [PATCH 16/26] feat: condense lint/format commands in bitbucket pipelines and ensure full typecheck matrix gets run --- ...cket' %}bitbucket-pipelines.yml{% endif %} | 71 +++++++++++++++++-- 1 file changed, 65 insertions(+), 6 deletions(-) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} index 37cec6a..fa302c9 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} @@ -222,9 +222,7 @@ pipelines: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - uv --version - - uvx nox -s format-python - - uvx nox -s lint-python - - uvx nox -s typecheck + - uvx nox -t quality - uv cache prune --ci {% if cookiecutter.add_rust_extension == 'y' -%} @@ -250,6 +248,69 @@ pipelines: - uvx nox -s security-python - uv cache prune --ci + # Parallel typecheck execution across Python versions + - parallel: + steps: + - step: + name: Typecheck Python 3.9 + image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.9 + - uv cache prune --ci + + - step: + name: Typecheck Python 3.10 + image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.10 + - uv cache prune --ci + + - step: + name: Typecheck Python 3.11 + image: ghcr.io/astral-sh/uv:latest-python3.11-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.11 + - uv cache prune --ci + + - step: + name: Typecheck Python 3.12 + image: ghcr.io/astral-sh/uv:latest-python3.12-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.12 + - uv cache prune --ci + + - step: + name: Typecheck Python 3.13 + image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim + caches: + - uv + - pip + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s typecheck-3.13 + - uv cache prune --ci + # Parallel test execution - parallel: steps: @@ -363,9 +424,7 @@ pipelines: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - uv --version - - uvx nox -s format-python - - uvx nox -s lint-python - - uvx nox -s typecheck + - uvx nox -t quality - uv cache prune --ci {% if cookiecutter.add_rust_extension == 'y' -%} From 9dac8557050b5765f848237fb12696bde9e9f99c Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 18:54:25 -0400 Subject: [PATCH 17/26] fix: fix files checked in cicd --- .../workflows/lint-python.yml | 2 -- .../workflows/security-python.yml | 4 ++-- ...epository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml index f821c63..eadd3e0 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/lint-python.yml @@ -11,7 +11,6 @@ on: - "noxfile.py" - "pyproject.toml" - ".ruff.toml" - - ".pydocstyle" - ".github/workflows/lint-python.yml" push: branches: @@ -23,7 +22,6 @@ on: - "noxfile.py" - "pyproject.toml" - ".ruff.toml" - - ".pydocstyle" - ".github/workflows/lint-python.yml" workflow_dispatch: diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/security-python.yml b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/security-python.yml index f5aed7b..37db8d0 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/security-python.yml +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/security-python.yml @@ -10,7 +10,7 @@ on: - "tests/**/*.py" - "noxfile.py" - "pyproject.toml" - - ".bandit" + - "bandit.yml" - ".ruff.toml" - ".github/workflows/security-python.yml" push: @@ -22,7 +22,7 @@ on: - "tests/**/*.py" - "noxfile.py" - "pyproject.toml" - - ".bandit" + - "bandit.yml" - ".ruff.toml" - ".github/workflows/security-python.yml" diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} index 19929ae..492ab12 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} @@ -57,7 +57,6 @@ quality-python: - "noxfile.py" - "pyproject.toml" - ".ruff.toml" - - ".pydocstyle" - "pyrightconfig.json" - ".gitlab-ci.yml" From 34e5828861fdeba97b61b5c5c3a823ddf14f202e Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 19:09:34 -0400 Subject: [PATCH 18/26] ci(gitlab): add more cache key file options --- ...repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} | 3 +++ 1 file changed, 3 insertions(+) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} index 492ab12..580a1e5 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} @@ -24,6 +24,9 @@ stages: key: files: - pyproject.toml + - uv.lock + - requirements*.txt + - "**/requirements*.txt" paths: - $UV_CACHE_DIR - $PIP_CACHE_DIR From 20805d40ccdb9092f9aea7b9a844e2706cad541f Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 19:10:45 -0400 Subject: [PATCH 19/26] ci(bitbucket): add caching based on file change being present --- ...cket' %}bitbucket-pipelines.yml{% endif %} | 98 +++++++++++-------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} index fa302c9..d9243db 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} @@ -5,8 +5,22 @@ image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim definitions: caches: - uv: .uv-cache - pip: .cache/pip + uv-deps: + key: + files: + - pyproject.toml + - uv.lock + - requirements*.txt + - "**/requirements*.txt" + path: .uv-cache + pip-deps: + key: + files: + - pyproject.toml + - uv.lock + - requirements*.txt + - "**/requirements*.txt" + path: .cache/pip # Shared configuration for uv services: @@ -19,8 +33,8 @@ pipelines: - step: name: Python Quality Checks caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -49,8 +63,8 @@ pipelines: - step: name: Security Checks caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -216,8 +230,8 @@ pipelines: - step: name: Python Quality Checks caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -240,8 +254,8 @@ pipelines: - step: name: Security Checks caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -255,8 +269,8 @@ pipelines: name: Typecheck Python 3.9 image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -267,8 +281,8 @@ pipelines: name: Typecheck Python 3.10 image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -279,8 +293,8 @@ pipelines: name: Typecheck Python 3.11 image: ghcr.io/astral-sh/uv:latest-python3.11-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -291,8 +305,8 @@ pipelines: name: Typecheck Python 3.12 image: ghcr.io/astral-sh/uv:latest-python3.12-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -303,8 +317,8 @@ pipelines: name: Typecheck Python 3.13 image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -318,8 +332,8 @@ pipelines: name: Test Python 3.9 image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -333,8 +347,8 @@ pipelines: name: Test Python 3.10 image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -348,8 +362,8 @@ pipelines: name: Test Python 3.11 image: ghcr.io/astral-sh/uv:latest-python3.11-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -363,8 +377,8 @@ pipelines: name: Test Python 3.12 image: ghcr.io/astral-sh/uv:latest-python3.12-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -378,8 +392,8 @@ pipelines: name: Test Python 3.13 image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -402,8 +416,8 @@ pipelines: - step: name: Build Package caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -418,8 +432,8 @@ pipelines: - step: name: Python Quality Checks caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -442,8 +456,8 @@ pipelines: - step: name: Security Checks caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -457,8 +471,8 @@ pipelines: name: Test Python 3.9 image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -469,8 +483,8 @@ pipelines: name: Test Python 3.13 image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -493,8 +507,8 @@ pipelines: - step: name: Build and Release caches: - - uv - - pip + - uv-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy From fe02eca43e8b6e0e5f95268316f0da1b46d284c1 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 19:17:31 -0400 Subject: [PATCH 20/26] ci(bitbucket): fix missed spot to replace lint and format with a nox tag instead --- ...ovider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} index d9243db..0c3bf55 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} @@ -39,9 +39,7 @@ pipelines: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - uv --version - - uvx nox -s format-python - - uvx nox -s lint-python - - uvx nox -s typecheck + - uvx nox -t quality - uv cache prune --ci after-script: - echo "Python quality checks completed" From 00a7002c3871dd225a584655d51710e0e88f1323 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 19:19:37 -0400 Subject: [PATCH 21/26] ci(github): replace placeholder rust cicd with actual implementation using nox sessions --- ...nsion == 'y' %} lint-rust.yml {%- endif %} | 105 +++++------------- ...nsion == 'y' %} test-rust.yml {%- endif %} | 55 +++++---- 2 files changed, 52 insertions(+), 108 deletions(-) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} index fb280d7..b1394d6 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} @@ -1,101 +1,48 @@ +name: Lint Rust Code + on: pull_request: paths: - - crates/** - - docs/source/src/rust/** - - examples/** - - py-polars/src/** - - py-polars/Cargo.toml - - Cargo.toml - - .github/workflows/lint-rust.yml + - "rust/**/*.rs" + - "Cargo.toml" + - "noxfile.py" + - ".github/workflows/lint-rust.yml" push: branches: - main - master paths: - - crates/** - - docs/source/src/rust/** - - examples/** - - py-polars/src/** - - py-polars/Cargo.toml - - Cargo.toml - - .github/workflows/lint-rust.yml - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + - "rust/**/*.rs" + - "Cargo.toml" + - "noxfile.py" + - ".github/workflows/lint-rust.yml" -env: - RUSTFLAGS: -C debuginfo=0 # Do not produce debug symbols to keep memory usage down + workflow_dispatch: jobs: - clippy-nightly: + lint-rust: + name: Run Rust Linting Checks runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Set up Rust - run: rustup component add clippy - - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - with: - save-if: ${{ github.ref_name == 'main' }} - - - name: Run cargo clippy with all features enabled - run: cargo clippy --workspace --all-targets --all-features --locked -- -D warnings -D clippy::dbg_macro - - # Default feature set should compile on the stable toolchain - clippy-stable: - runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 - name: Set up Rust - run: rustup override set stable && rustup update + run: | + rustup component add rustfmt clippy - - name: Install clippy - run: rustup component add clippy + - name: Set up uv + uses: astral-sh/setup-uv@v6 - - name: Cache Rust - uses: Swatinem/rust-cache@v2 + - name: Set up Python + uses: actions/setup-python@v5 with: - save-if: ${{ github.ref_name == 'main' }} - - - name: Run cargo clippy - run: cargo clippy --all-targets --locked -- -D warnings -D clippy::dbg_macro - - rustfmt: - if: github.ref_name != 'main' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Rust - run: rustup component add rustfmt - - - name: Run cargo fmt - run: cargo fmt --all --check - - miri: - if: github.ref_name != 'main' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Rust - run: rustup component add miri + python-version-file: ".github/workflows/.python-version" - - name: Set up miri - run: cargo miri setup + - name: Run Rust format check + run: uvx nox -s format-rust - - name: Run miri - env: - MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-ignore-leaks -Zmiri-disable-stacked-borrows - POLARS_ALLOW_EXTENSION: '1' - run: > - cargo miri test - --features object - -p polars-core -# -p polars-arrow + - name: Run Rust lint check + run: uvx nox -s lint-rust diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} index 6f05939..bb7626d 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} @@ -1,52 +1,49 @@ -name: Test Rust +name: Test Rust Code on: pull_request: paths: - - crates/** - - examples/** - - Cargo.toml - - .github/workflows/test-rust.yml + - "rust/**/*.rs" + - "Cargo.toml" + - "noxfile.py" + - ".github/workflows/test-rust.yml" push: branches: - main - master paths: - - crates/** - - examples/** - - Cargo.toml - - .github/workflows/test-rust.yml + - "rust/**/*.rs" + - "Cargo.toml" + - "noxfile.py" + - ".github/workflows/test-rust.yml" -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -env: - RUSTFLAGS: -C debuginfo=0 # Do not produce debug symbols to keep memory usage down - RUST_BACKTRACE: 1 + workflow_dispatch: jobs: - test: + test-rust: + name: Run Rust Tests on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: - fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + include: + - { os: "ubuntu-latest" } + - { os: "macos-latest" } + - { os: "windows-latest" } steps: - - uses: actions/checkout@v4 + - name: Checkout code + uses: actions/checkout@v4 - name: Set up Rust run: rustup show - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - with: - save-if: ${{ github.ref_name == 'main' }} + - name: Set up uv + uses: astral-sh/setup-uv@v6 - - name: Compile tests - run: cargo test --all-features --no-run + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: ".github/workflows/.python-version" - - name: Run tests - if: github.ref_name != 'main' - run: cargo test --all-features + - name: Run Rust tests + run: uvx nox -s test-rust From e7be631124b2c925101cadd7410212a665ba2d2f Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 19:31:57 -0400 Subject: [PATCH 22/26] ci(bitbucket): add missing windows and macos tests along with uploading to testpypi --- ...cket' %}bitbucket-pipelines.yml{% endif %} | 163 ++++++++++++++---- 1 file changed, 134 insertions(+), 29 deletions(-) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} index 0c3bf55..4e162cf 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} @@ -134,11 +134,12 @@ pipelines: - uvx nox -s typecheck-3.13 - uv cache prune --ci - # Parallel test execution across Python versions + # Parallel test execution across Python versions and platforms - parallel: steps: + # Linux testing across all Python versions - step: - name: Test Python 3.9 + name: Test Python 3.9 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim caches: - uv @@ -146,14 +147,14 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-39 + - uvx nox -s tests-python-3.9 - uv cache prune --ci artifacts: - tests/results/*.xml - coverage.xml - step: - name: Test Python 3.10 + name: Test Python 3.10 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim caches: - uv @@ -161,14 +162,14 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-310 + - uvx nox -s tests-python-3.10 - uv cache prune --ci artifacts: - tests/results/*.xml - coverage.xml - step: - name: Test Python 3.11 + name: Test Python 3.11 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.11-bookworm-slim caches: - uv @@ -176,14 +177,14 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-311 + - uvx nox -s tests-python-3.11 - uv cache prune --ci artifacts: - tests/results/*.xml - coverage.xml - step: - name: Test Python 3.12 + name: Test Python 3.12 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.12-bookworm-slim caches: - uv @@ -191,14 +192,14 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-312 + - uvx nox -s tests-python-3.12 - uv cache prune --ci artifacts: - tests/results/*.xml - coverage.xml - step: - name: Test Python 3.13 + name: Test Python 3.13 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim caches: - uv @@ -206,7 +207,35 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-313 + - uvx nox -s tests-python-3.13 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + # Cross-platform testing with latest Python + - step: + name: Test Python 3.13 (macOS) + runs-on: macos + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:$HOME/.cargo/bin" + - uvx nox -s tests-python-3.13 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.13 (Windows) + runs-on: windows + script: + - set UV_CACHE_DIR=.uv-cache + - set UV_LINK_MODE=copy + - powershell -c "irm https://astral.sh/uv/install.ps1 | iex" + - uvx nox -s tests-python-3.13 - uv cache prune --ci artifacts: - tests/results/*.xml @@ -323,11 +352,12 @@ pipelines: - uvx nox -s typecheck-3.13 - uv cache prune --ci - # Parallel test execution + # Parallel test execution across Python versions and platforms - parallel: steps: + # Linux testing across all Python versions - step: - name: Test Python 3.9 + name: Test Python 3.9 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim caches: - uv-deps @@ -335,14 +365,14 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-39 + - uvx nox -s tests-python-3.9 - uv cache prune --ci artifacts: - tests/results/*.xml - coverage.xml - step: - name: Test Python 3.10 + name: Test Python 3.10 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim caches: - uv-deps @@ -350,14 +380,14 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-310 + - uvx nox -s tests-python-3.10 - uv cache prune --ci artifacts: - tests/results/*.xml - coverage.xml - step: - name: Test Python 3.11 + name: Test Python 3.11 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.11-bookworm-slim caches: - uv-deps @@ -365,14 +395,14 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-311 + - uvx nox -s tests-python-3.11 - uv cache prune --ci artifacts: - tests/results/*.xml - coverage.xml - step: - name: Test Python 3.12 + name: Test Python 3.12 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.12-bookworm-slim caches: - uv-deps @@ -380,14 +410,14 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-312 + - uvx nox -s tests-python-3.12 - uv cache prune --ci artifacts: - tests/results/*.xml - coverage.xml - step: - name: Test Python 3.13 + name: Test Python 3.13 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim caches: - uv-deps @@ -395,7 +425,35 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-313 + - uvx nox -s tests-python-3.13 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + # Cross-platform testing with latest Python + - step: + name: Test Python 3.13 (macOS) + runs-on: macos + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:$HOME/.cargo/bin" + - uvx nox -s tests-python-3.13 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.13 (Windows) + runs-on: windows + script: + - set UV_CACHE_DIR=.uv-cache + - set UV_LINK_MODE=copy + - powershell -c "irm https://astral.sh/uv/install.ps1 | iex" + - uvx nox -s tests-python-3.13 - uv cache prune --ci artifacts: - tests/results/*.xml @@ -466,7 +524,7 @@ pipelines: - parallel: steps: - step: - name: Test Python 3.9 + name: Test Python 3.9 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim caches: - uv-deps @@ -474,11 +532,11 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-39 + - uvx nox -s tests-python-3.9 - uv cache prune --ci - step: - name: Test Python 3.13 + name: Test Python 3.13 (Linux) image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim caches: - uv-deps @@ -486,7 +544,29 @@ pipelines: script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - - uvx nox -s tests-python-313 + - uvx nox -s tests-python-3.13 + - uv cache prune --ci + + # Cross-platform testing with latest Python + - step: + name: Test Python 3.13 (macOS) + runs-on: macos + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:$HOME/.cargo/bin" + - uvx nox -s tests-python-3.13 + - uv cache prune --ci + + - step: + name: Test Python 3.13 (Windows) + runs-on: windows + script: + - set UV_CACHE_DIR=.uv-cache + - set UV_LINK_MODE=copy + - powershell -c "irm https://astral.sh/uv/install.ps1 | iex" + - uvx nox -s tests-python-3.13 - uv cache prune --ci {% if cookiecutter.add_rust_extension == 'y' -%} @@ -503,7 +583,7 @@ pipelines: tags: 'v*': - step: - name: Build and Release + name: Build Package caches: - uv-deps - pip-deps @@ -511,7 +591,32 @@ pipelines: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - uvx nox -s build-python - - uvx nox -s publish-python - uv cache prune --ci artifacts: - - dist/** \ No newline at end of file + - dist/** + + - step: + name: Publish to TestPyPI + deployment: test + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - export PYPI_URL=https://test.pypi.org/legacy/ + - uvx nox -s publish-python + - uv cache prune --ci + + - step: + name: Publish to Production PyPI + deployment: production + trigger: manual + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s publish-python + - uv cache prune --ci \ No newline at end of file From 5ff614d6d8c8c6041e17c6cc0b7379675d8c082a Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 19:41:38 -0400 Subject: [PATCH 23/26] ci(gitlab): remove testing against alpine linux and add tests against macos and windows --- ...er == 'gitlab' %}.gitlab-ci.yml{% endif %} | 84 +++++++++++++++++-- 1 file changed, 78 insertions(+), 6 deletions(-) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} index 580a1e5..85a6d70 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} @@ -111,11 +111,7 @@ test-python: parallel: matrix: - PYTHON_VERSION: ["3.9", "3.10", "3.11", "3.12", "3.13"] - OS_IMAGE: ["bookworm-slim"] - # Add cross-platform testing for latest Python version - - PYTHON_VERSION: ["3.13"] - OS_IMAGE: ["alpine", "bookworm-slim"] - image: ghcr.io/astral-sh/uv:latest-python$PYTHON_VERSION-$OS_IMAGE + image: ghcr.io/astral-sh/uv:latest-python$PYTHON_VERSION-bookworm-slim script: - uvx nox -s tests-python-${PYTHON_VERSION//.} after_script: @@ -138,6 +134,64 @@ test-python: - ".coveragerc" - ".gitlab-ci.yml" +# Cross-platform testing with macOS (GitLab.com SaaS runners) +test-python-macos: + stage: test + <<: *on-merge-requests-and-main + tags: + - saas-macos-medium-m1 + before_script: + - curl -LsSf https://astral.sh/uv/install.sh | sh + - export PATH="$PATH:$HOME/.cargo/bin" + script: + - uvx nox -s tests-python-3.13 + artifacts: + reports: + junit: tests/results/*.xml + coverage_report: + coverage_format: cobertura + path: coverage.xml + paths: + - tests/results/ + - coverage.xml + expire_in: 5 days + changes: + - "src/**/*.py" + - "tests/**/*.py" + - "noxfile.py" + - "pyproject.toml" + - ".coveragerc" + - ".gitlab-ci.yml" + +# Cross-platform testing with Windows (GitLab.com SaaS runners) +test-python-windows: + stage: test + <<: *on-merge-requests-and-main + tags: + - saas-windows-medium-amd64 + before_script: + - powershell -c "irm https://astral.sh/uv/install.ps1 | iex" + - $env:PATH += ";$env:USERPROFILE\.cargo\bin" + script: + - uvx nox -s tests-python-3.13 + artifacts: + reports: + junit: tests/results/*.xml + coverage_report: + coverage_format: cobertura + path: coverage.xml + paths: + - tests/results/ + - coverage.xml + expire_in: 5 days + changes: + - "src/**/*.py" + - "tests/**/*.py" + - "noxfile.py" + - "pyproject.toml" + - ".coveragerc" + - ".gitlab-ci.yml" + {% if cookiecutter.add_rust_extension == 'y' -%} # Rust-specific jobs (conditional on rust extension flag) .rust-job: &rust-job @@ -215,7 +269,22 @@ pages: - "noxfile.py" - "pyproject.toml" -# Release Job (only on tags) +# Test Release Job (TestPyPI) +test-release-python: + stage: release + <<: *uv-cache + script: + - export PYPI_URL=https://test.pypi.org/legacy/ + - uvx nox -s publish-python + after_script: + - uv cache prune --ci + rules: + - if: $CI_COMMIT_TAG + environment: + name: test + url: https://test.pypi.org/project/{{ cookiecutter.package_name }}/ + +# Production Release Job (PyPI) release-python: stage: release <<: *uv-cache @@ -228,3 +297,6 @@ release-python: environment: name: production url: https://pypi.org/project/{{ cookiecutter.package_name }}/ + needs: + - test-release-python + when: manual From 97a6c9c371b3a5778c10849f09370b7f8ea8987c Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 20:01:14 -0400 Subject: [PATCH 24/26] ci: add caveats to non github cicd attempts --- ...= 'bitbucket' %}bitbucket-pipelines.yml{% endif %} | 11 ++++++----- ...y_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} index 4e162cf..083d089 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} @@ -1,5 +1,6 @@ # bitbucket-pipelines.yml # See https://support.atlassian.com/bitbucket-cloud/docs/get-started-with-bitbucket-pipelines/ +# This is currently untested serves as a best guess for an equivalent pipeline image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim @@ -71,7 +72,7 @@ pipelines: after-script: - echo "Security checks completed" - # Parallel typecheck execution across Python versions + # Parallel typecheck execution across Python versions - parallel: steps: - step: @@ -91,7 +92,7 @@ pipelines: image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim caches: - uv - - pip + - pip script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -289,7 +290,7 @@ pipelines: - uvx nox -s security-python - uv cache prune --ci - # Parallel typecheck execution across Python versions + # Parallel typecheck execution across Python versions - parallel: steps: - step: @@ -309,7 +310,7 @@ pipelines: image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim caches: - uv-deps - - pip-deps + - pip-deps script: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy @@ -619,4 +620,4 @@ pipelines: - export UV_CACHE_DIR=.uv-cache - export UV_LINK_MODE=copy - uvx nox -s publish-python - - uv cache prune --ci \ No newline at end of file + - uv cache prune --ci diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} index 85a6d70..a308b1f 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} @@ -1,5 +1,6 @@ # .gitlab-ci.yml # See https://docs.gitlab.com/ee/ci/yaml/ +# This is currently untested and serves as a best guess for what might be an equivalent pipeline # Global settings image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim From 61c55963423a6f6369feb7c17b53450f3fef27b1 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Mon, 28 Jul 2025 21:04:51 -0400 Subject: [PATCH 25/26] build: add setup-remote as a nox session --- {{cookiecutter.project_name}}/noxfile.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index 018a862..5cf0c08 100644 --- a/{{cookiecutter.project_name}}/noxfile.py +++ b/{{cookiecutter.project_name}}/noxfile.py @@ -60,6 +60,15 @@ def setup_venv(session: Session) -> None: session.run("python", SCRIPTS_FOLDER / "setup-venv.py", REPO_ROOT, "-p", PYTHON_VERSIONS[0], external=True) +@nox.session(python=False, name="setup-remote") +def setup_remote(session: Session) -> None: + """Set up the remote repository for the current project.""" + command: list[str] = [ + "python", SCRIPTS_FOLDER / "setup-remote.py", REPO_ROOT, "--host", REPOSITORY_HOST, "--path", REPOSITORY_PATH + ] + session.run(*command, external=True) + + @nox.session(python=DEFAULT_PYTHON_VERSION, name="pre-commit", tags=[QUALITY]) def precommit(session: Session) -> None: """Lint using pre-commit.""" From 0d27f94da79b348582ab1fda682167bc163bb0b0 Mon Sep 17 00:00:00 2001 From: Kyle Oliver Date: Tue, 29 Jul 2025 00:03:51 -0400 Subject: [PATCH 26/26] ci: set the readthedocs config to be used for all platform providers --- ...hedocs.yml{% endif %} => .readthedocs.yml} | 0 {{cookiecutter.project_name}}/README.md | 6 ----- ...er == 'gitlab' %}.gitlab-ci.yml{% endif %} | 22 ++++++++++++++++++- 3 files changed, 21 insertions(+), 7 deletions(-) rename {{cookiecutter.project_name}}/{{% if cookiecutter.repository_provider == 'bitbucket' %}.readthedocs.yml{% endif %} => .readthedocs.yml} (100%) diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}.readthedocs.yml{% endif %} b/{{cookiecutter.project_name}}/.readthedocs.yml similarity index 100% rename from {{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}.readthedocs.yml{% endif %} rename to {{cookiecutter.project_name}}/.readthedocs.yml diff --git a/{{cookiecutter.project_name}}/README.md b/{{cookiecutter.project_name}}/README.md index 2cbcb2a..4409345 100644 --- a/{{cookiecutter.project_name}}/README.md +++ b/{{cookiecutter.project_name}}/README.md @@ -92,10 +92,4 @@ Distributed under the terms of the **{{ cookiecutter.license }}** license. See [ [cookiecutter-robust-python]: https://github.com/56kyle/cookiecutter-robust-python -{% if cookiecutter.repository_provider == 'github' %} -[documentation]: https://{{ cookiecutter.repository_path.split('/')[0] }}.github.io/{{ cookiecutter.project_name.replace('_', '-') }}/ -{% elif cookiecutter.repository_provider == 'gitlab' %} -[documentation]: https://{{ cookiecutter.repository_path.replace('/', '.') }}.gitlab.io/{{ cookiecutter.project_name.replace('_', '-') }}/ -{% else %} [documentation]: https://{{ cookiecutter.project_name.replace('_', '-') }}.readthedocs.io/ -{% endif %} diff --git a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} index a308b1f..a57702c 100644 --- a/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} @@ -249,7 +249,27 @@ build-python: - if: $CI_COMMIT_BRANCH == "main" - if: $CI_PIPELINE_SOURCE == "web" -# Documentation build (GitLab Pages) +# Documentation build validation (ReadTheDocs handles hosting) +build-docs: + stage: build + <<: *uv-cache + script: + - uvx nox -s build-docs + after_script: + - uv cache prune --ci + artifacts: + paths: + - docs/_build/html/ + expire_in: 5 days + <<: *on-merge-requests-and-main + changes: + - "docs/**/*" + - "src/**/*.py" + - "noxfile.py" + - "pyproject.toml" + - ".gitlab-ci.yml" + +# Documentation build (GitLab Pages - fallback/mirror) pages: stage: build <<: *uv-cache