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"], 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 }}" diff --git a/{{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} b/{{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} deleted file mode 100644 index fb280d7..0000000 --- a/{{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} +++ /dev/null @@ -1,101 +0,0 @@ - -on: - pull_request: - paths: - - crates/** - - docs/source/src/rust/** - - examples/** - - py-polars/src/** - - py-polars/Cargo.toml - - Cargo.toml - - .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 - -env: - RUSTFLAGS: -C debuginfo=0 # Do not produce debug symbols to keep memory usage down - -jobs: - clippy-nightly: - 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: Set up Rust - run: rustup override set stable && rustup update - - - name: Install clippy - run: rustup component add clippy - - - name: Cache Rust - uses: Swatinem/rust-cache@v2 - 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 - - - name: Set up miri - run: cargo miri setup - - - 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 diff --git a/{{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} b/{{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} deleted file mode 100644 index 6f05939..0000000 --- a/{{cookiecutter.project_name}}/.github/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} +++ /dev/null @@ -1,52 +0,0 @@ -name: Test Rust - -on: - pull_request: - paths: - - crates/** - - examples/** - - Cargo.toml - - .github/workflows/test-rust.yml - push: - branches: - - main - - master - paths: - - crates/** - - examples/** - - Cargo.toml - - .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 - -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - - steps: - - 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: Compile tests - run: cargo test --all-features --no-run - - - name: Run tests - if: github.ref_name != 'main' - run: cargo test --all-features diff --git a/{{cookiecutter.project_name}}/CONTRIBUTING.md b/{{cookiecutter.project_name}}/CONTRIBUTING.md index f56f408..0a6e37d 100644 --- a/{{cookiecutter.project_name}}/CONTRIBUTING.md +++ b/{{cookiecutter.project_name}}/CONTRIBUTING.md @@ -1,44 +1,205 @@ -# 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]. ## 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][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][uv-documentation] for dependency management +- Git for version control + +### Setting Up Your Development Environment + +1. **Fork and clone the repository:** + ```bash + git clone https://{{ cookiecutter.repository_host }}/{{ cookiecutter.repository_path }}.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][ruff-documentation] (automatically applied by pre-commit) +- **Linting:** Ruff with comprehensive rule set +- **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 + +- 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][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 + +## 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][cookiecutter-robust-python] template.* + + +[code-of-conduct]: CODE_OF_CONDUCT.md +[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 +[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 diff --git a/{{cookiecutter.project_name}}/README.md b/{{cookiecutter.project_name}}/README.md index c6fe7ed..4409345 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): @@ -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 @@ -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. @@ -87,4 +87,9 @@ 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 + +[documentation]: https://{{ cookiecutter.project_name.replace('_', '-') }}.readthedocs.io/ diff --git a/{{cookiecutter.project_name}}/noxfile.py b/{{cookiecutter.project_name}}/noxfile.py index b7e2f91..5cf0c08 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" @@ -42,7 +43,7 @@ DOCS: str = "docs" BUILD: str = "build" RELEASE: str = "release" -CI: str = "ci" +QUALITY: str = "quality" PYTHON: str = "python" RUST: str = "rust" @@ -59,7 +60,16 @@ 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=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.""" args: list[str] = session.posargs or ["run", "--all-files", "--show-diff-on-failure"] @@ -72,7 +82,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}.") @@ -90,7 +100,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}.") @@ -108,7 +118,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...") @@ -118,7 +128,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}.") @@ -129,7 +139,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...") @@ -138,7 +148,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}}/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" 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 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..083d089 --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'bitbucket' %}bitbucket-pipelines.yml{% endif %} @@ -0,0 +1,623 @@ +# 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 + +definitions: + caches: + 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: + docker: + memory: 2048 + +pipelines: + # Default pipeline for pushes to any branch + default: + - step: + name: Python Quality Checks + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uv --version + - uvx nox -t quality + - 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-deps + - pip-deps + 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 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 and platforms + - parallel: + steps: + # Linux testing across all Python versions + - step: + name: Test Python 3.9 (Linux) + 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-3.9 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.10 (Linux) + 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-3.10 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.11 (Linux) + 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-3.11 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.12 (Linux) + 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-3.12 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.13 (Linux) + 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-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 + - 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-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uv --version + - uvx nox -t quality + - 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-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - 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-deps + - pip-deps + 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-deps + - pip-deps + 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-deps + - pip-deps + 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-deps + - pip-deps + 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-deps + - pip-deps + 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 and platforms + - parallel: + steps: + # Linux testing across all Python versions + - step: + name: Test Python 3.9 (Linux) + image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-3.9 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.10 (Linux) + image: ghcr.io/astral-sh/uv:latest-python3.10-bookworm-slim + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-3.10 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.11 (Linux) + image: ghcr.io/astral-sh/uv:latest-python3.11-bookworm-slim + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-3.11 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.12 (Linux) + image: ghcr.io/astral-sh/uv:latest-python3.12-bookworm-slim + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-3.12 + - uv cache prune --ci + artifacts: + - tests/results/*.xml + - coverage.xml + + - step: + name: Test Python 3.13 (Linux) + image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - 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 + - 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-deps + - pip-deps + 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-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uv --version + - uvx nox -t quality + - 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-deps + - pip-deps + 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 (Linux) + image: ghcr.io/astral-sh/uv:latest-python3.9-bookworm-slim + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s tests-python-3.9 + - uv cache prune --ci + + - step: + name: Test Python 3.13 (Linux) + image: ghcr.io/astral-sh/uv:latest-python3.13-bookworm-slim + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - 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' -%} + - 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 Package + caches: + - uv-deps + - pip-deps + script: + - export UV_CACHE_DIR=.uv-cache + - export UV_LINK_MODE=copy + - uvx nox -s build-python + - uv cache prune --ci + artifacts: + - 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 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 83% 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 index 6acae86..eadd3e0 100644 --- a/{{cookiecutter.project_name}}/.github/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: @@ -45,8 +43,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}}/.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 96% 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 index f5aed7b..37db8d0 100644 --- a/{{cookiecutter.project_name}}/.github/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}}/.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}}/{% 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 %} new file mode 100644 index 0000000..b1394d6 --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} lint-rust.yml {%- endif %} @@ -0,0 +1,48 @@ + +name: Lint Rust Code + +on: + pull_request: + paths: + - "rust/**/*.rs" + - "Cargo.toml" + - "noxfile.py" + - ".github/workflows/lint-rust.yml" + push: + branches: + - main + - master + paths: + - "rust/**/*.rs" + - "Cargo.toml" + - "noxfile.py" + - ".github/workflows/lint-rust.yml" + + workflow_dispatch: + +jobs: + lint-rust: + name: Run Rust Linting Checks + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Rust + run: | + rustup component add rustfmt clippy + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: ".github/workflows/.python-version" + + - name: Run Rust format check + run: uvx nox -s format-rust + + - 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 %} new file mode 100644 index 0000000..bb7626d --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'github' %}.github{% endif %}/workflows/{% if cookiecutter.add_rust_extension == 'y' %} test-rust.yml {%- endif %} @@ -0,0 +1,49 @@ +name: Test Rust Code + +on: + pull_request: + paths: + - "rust/**/*.rs" + - "Cargo.toml" + - "noxfile.py" + - ".github/workflows/test-rust.yml" + push: + branches: + - main + - master + paths: + - "rust/**/*.rs" + - "Cargo.toml" + - "noxfile.py" + - ".github/workflows/test-rust.yml" + + workflow_dispatch: + +jobs: + test-rust: + name: Run Rust Tests on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + include: + - { os: "ubuntu-latest" } + - { os: "macos-latest" } + - { os: "windows-latest" } + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Rust + run: rustup show + + - name: Set up uv + uses: astral-sh/setup-uv@v6 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version-file: ".github/workflows/.python-version" + + - name: Run Rust tests + run: uvx nox -s test-rust 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..a57702c --- /dev/null +++ b/{{cookiecutter.project_name}}/{% if cookiecutter.repository_provider == 'gitlab' %}.gitlab-ci.yml{% endif %} @@ -0,0 +1,323 @@ +# .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 + +variables: + UV_CACHE_DIR: .uv-cache + UV_LINK_MODE: copy + PIP_CACHE_DIR: $CI_PROJECT_DIR/.cache/pip + +# Define stages +stages: + - quality + - typecheck + - test + - security + - build + - release + +# Global cache configuration for uv +.uv-cache: &uv-cache + cache: + key: + files: + - pyproject.toml + - uv.lock + - requirements*.txt + - "**/requirements*.txt" + 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 Job +quality-python: + <<: *quality-job + script: + - uvx nox -t quality + changes: + - "src/**/*.py" + - "tests/**/*.py" + - "noxfile.py" + - "pyproject.toml" + - ".ruff.toml" + - "pyrightconfig.json" + - ".gitlab-ci.yml" + +typecheck-python: + 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-$PYTHON_VERSION + after_script: + - uv cache prune --ci + 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"] + image: ghcr.io/astral-sh/uv:latest-python$PYTHON_VERSION-bookworm-slim + 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" + +# 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 + 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 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 + 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" + +# 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 + 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 }}/ + needs: + - test-release-python + when: manual