diff --git a/.editorconfig b/.editorconfig index 74f0751..4e55940 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,5 +1,5 @@ # ============================================================================== -# EditorConfig for project_name +# EditorConfig for template-python # https://editorconfig.org # # This file enforces consistent coding styles across multiple editors and IDEs. diff --git a/.env.example b/.env.example index 15bfebd..31fc86b 100644 --- a/.env.example +++ b/.env.example @@ -15,7 +15,7 @@ APP_ENV=development # Enable/disable debug mode (verbose logging, stack traces) APP_DEBUG=true -APP_NAME="project_name" +APP_NAME="template_python" APP_VERSION="1.0.0" # Base URL for the application (useful for generating absolute links, emails) APP_URL=http://localhost:8080 @@ -42,13 +42,13 @@ CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080 DB_CONNECTION=postgres DB_HOST=localhost DB_PORT=5432 -DB_DATABASE=project_name_dev +DB_DATABASE=template_python_dev DB_USERNAME=admin DB_PASSWORD=secret # Database schema (if applicable, e.g., public for postgres) DB_SCHEMA=public # Full connection string alternative (often used by ORMs like Prisma or SQLAlchemy) -# DATABASE_URL=postgres://admin:secret@localhost:5432/project_name_dev +# DATABASE_URL=postgres://admin:secret@localhost:5432/template_python_dev # ------------------------------------------------------------------------------ # Cache / Key-Value Store / Message Brokers diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 4ec5aa8..7da2476 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,6 @@ --- name: 🐛 Bug Report -description: Create a report to help us improve {{project_name}} +description: Create a report to help us improve template-python labels: - "bug" - "triage" @@ -8,10 +8,10 @@ body: - type: markdown attributes: value: | - Thanks for taking the time to report a bug to `{{project_name}}`! + Thanks for taking the time to report a bug to `template-python`! > [!IMPORTANT] > If you have found a **security vulnerability**, please refer to our [Security Policy](../SECURITY.md) and do not open a public issue. - For general help and Q&A, please use [GitHub Discussions](https://github.com/{{organization}}/{{project_name}}/discussions) instead. + For general help and Q&A, please use [GitHub Discussions](https://github.com/markurtz/template-python/discussions) instead. - type: checkboxes id: checks attributes: @@ -19,10 +19,10 @@ body: description: Please confirm that you have checked the following before submitting your bug report. options: - - label: I have searched the [existing issues](https://github.com/{{organization}}/{{project_name}}/issues) + - label: I have searched the [existing issues](https://github.com/markurtz/template-python/issues) and could not find a similar bug. required: true - - label: I have read the [Official Documentation](https://{{organization}}.github.io/{{project_name}}). + - label: I have read the [Official Documentation](https://markurtz.github.io/template-python). required: true - type: textarea id: description @@ -60,7 +60,7 @@ body: value: | - OS (e.g., Linux, macOS, Windows): - Language Version (e.g., Python 3.10): - - `{{project_name}}` Version: + - `template-python` Version: - Other relevant dependency versions: validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 4914ff2..585d486 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,14 +1,14 @@ --- name: Feature Request -description: Suggest an idea for {{project_name}} +description: Suggest an idea for template-python labels: - "enhancement" body: - type: markdown attributes: value: | - Thanks for taking the time to suggest a feature for {{project_name}}! - Before submitting, please ensure you have searched our existing [Issues](https://github.com/{{organization}}/{{project_name}}/issues) and [Discussions](https://github.com/{{organization}}/{{project_name}}/discussions) to see if this feature has already been requested. + Thanks for taking the time to suggest a feature for template-python! + Before submitting, please ensure you have searched our existing [Issues](https://github.com/markurtz/template-python/issues) and [Discussions](https://github.com/markurtz/template-python/discussions) to see if this feature has already been requested. - type: textarea id: problem attributes: diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b1ce491..91f2a4d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,5 @@ @@ -16,11 +16,11 @@ If this PR introduces a new feature or changes existing behavior, describe the c -- [ ] 🐛 Bug fix (non-breaking change which fixes an issue) -- [ ] ✨ New feature (non-breaking change which adds functionality) -- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected) -- [ ] 📝 Documentation update (changes to `README.md`, `SUPPORT.md`, docstrings, etc.) -- [ ] 🛠️ Maintenance/Refactoring (non-breaking change that improves code structure or quality) +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] Documentation update (changes to `README.md`, `SUPPORT.md`, docstrings, etc.) +- [ ] Maintenance/Refactoring (non-breaking change that improves code structure or quality) ## Test Plan @@ -52,16 +52,16 @@ please add screenshots, terminal output, or recordings to demonstrate the change - [ ] Includes AI-assisted code completion - [ ] Includes code generated by an AI application -- [ ] Includes AI-generated tests (NOTE: AI written tests should have a docstring that includes `## WRITTEN BY AI ##`) +- [ ] Includes AI-generated tests ## Checklist > [!IMPORTANT] -> Please review and complete this checklist before submitting your PR. This helps our maintainers process your contribution faster and ensures it meets the quality standards of `{{project_name}}`. +> Please review and complete this checklist before submitting your PR. This helps our maintainers process your contribution faster and ensures it meets the quality standards of `template-python`. - [ ] "I certify that all code in this PR is my own, except as noted below." - [ ] I have read the [CONTRIBUTING.md](../CONTRIBUTING.md) guide. -- [ ] My code follows the established style guidelines of `{{project_name}}`. +- [ ] My code follows the established style guidelines of `template-python`. - [ ] I have performed a self-review of my own code. - [ ] I have commented my code, particularly in hard-to-understand areas. - [ ] I have made corresponding changes to the documentation. diff --git a/.github/workflows/development.yml b/.github/workflows/development.yml index e5fa23d..f2c0680 100644 --- a/.github/workflows/development.yml +++ b/.github/workflows/development.yml @@ -75,7 +75,9 @@ jobs: # ── Stage 3: Docs (Conditional) ──────────────────────────────────────────── docs: name: "Build & Deploy Docs" - needs: changes + needs: + - changes + - tests if: needs.changes.outputs.docs == 'true' && github.event.pull_request.head.repo.fork == false uses: ./.github/workflows/_docs.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9ac34f0..cab224d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -52,7 +52,9 @@ jobs: # ── Stage 3: Documentation ───────────────────────────────────────────────── docs: name: "Build & Deploy Docs" - needs: quality + needs: + - quality + - tests uses: ./.github/workflows/_docs.yml with: build_type: "dev" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 264f7d5..5ae3f85 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -81,7 +81,9 @@ jobs: # ── Stage 4: Alpha Docs ──────────────────────────────────────────────────── docs: name: "Nightly Docs" - needs: check-changes + needs: + - check-changes + - test if: needs.check-changes.outputs.has_changes == 'true' uses: ./.github/workflows/_docs.yml with: @@ -121,8 +123,7 @@ jobs: uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v1.5.1 with: subject-path: dist/* - - name: Publish to PyPI - if: github.repository_id != 1226495532 # Skip PyPI for the template repo itself - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14 - with: - packages-dir: dist/ +# - name: Publish to PyPI +# uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14 +# with: +# packages-dir: dist/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 222124f..9fbb0e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,7 @@ jobs: # ── Stage 4: Versioned Docs ──────────────────────────────────────────────── docs: name: "Versioned Docs" + needs: test uses: ./.github/workflows/_docs.yml with: build_type: "release" @@ -82,11 +83,10 @@ jobs: uses: actions/attest-build-provenance@c074443f1aee8d4aeeae555aebba3282517141b2 # v1.5.1 with: subject-path: dist/* - - name: Publish to PyPI - if: github.repository_id != 1226495532 # Skip PyPI for the template repo itself - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14 - with: - packages-dir: dist/ +# - name: Publish to PyPI +# uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14 +# with: +# packages-dir: dist/ - name: Create GitHub Release if: ${{ github.ref_type == 'tag' }} uses: softprops/action-gh-release@c062e08bd532815e2082a85e87e3ef29c3e6d191 # v2.0.8 diff --git a/.gitignore b/.gitignore index fce3b1d..3303803 100644 --- a/.gitignore +++ b/.gitignore @@ -161,7 +161,7 @@ nosetests.xml # ============================================================================== # Python -src/project_name/version.py +src/template_python/version.py __pycache__/ *.py[cod] *$py.class diff --git a/AGENTS.md b/AGENTS.md index 5fd3e62..8697a0e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,7 +5,7 @@ ## Project Context -**`project_name`** is a production-ready Apache 2.0 template repository for bootstrapping modern software projects. +**`template-python`** is a production-ready Apache 2.0 template repository for bootstrapping modern software projects. **Primary language:** `Python 3.10+`\ **Package manager:** `Hatch` @@ -54,7 +54,7 @@ - **`docs/`** and **`mkdocs.yml`** control the site. Do not create docs outside the `nav:` tree. - `docs/index.md` dynamically includes `README.md` via MkDocs snippets. -- Use `{{placeholder}}` variables for templated fields (e.g., `project_name`, `{{organization}}`). +- Use `{{placeholder}}` variables for templated fields (e.g., `template-python`, `markurtz`). ## Agent Notes diff --git a/CITATION.cff b/CITATION.cff index c9894c2..e42f54f 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -3,20 +3,19 @@ # For more information on the CFF format, see: https://citation-file-format.github.io/ cff-version: 1.2.0 -title: "project_name" +title: "template-python" message: "If you use this software, please cite it as below." type: software -version: "{{version}}" date-released: "{{date_released}}" license: Apache-2.0 # You can list individual authors or an organization. # This defaults to the organization to match the README's BibTeX. authors: - - name: "{{organization}}" + - name: "markurtz" # - family-names: "{{author_family_name}}" # given-names: "{{author_given_name}}" # orcid: "https://orcid.org/{{orcid}}" -repository-code: "https://github.com/{{organization}}/project_name" -url: "https://{{organization}}.github.io/project_name" +repository-code: "https://github.com/markurtz/template-python" +url: "https://markurtz.github.io/template-python" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 92544c5..c4745da 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,4 @@ -# Code of Conduct for `project_name` +# Code of Conduct for `template-python` ## Our Pledge @@ -61,7 +61,8 @@ representative at an online or offline event. > [!IMPORTANT] > Instances of abusive, harassing, or otherwise unacceptable behavior may be > reported to the community leaders responsible for enforcement by contacting -> **conduct@{{organization}}.com**. +> +> All complaints will be reviewed and investigated promptly and fairly. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f439bc..0294cd9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ -# Contributing to project_name +# Contributing to template-python -First off, thank you for considering contributing to `project_name`! It's people like you that make this project great. +First off, thank you for considering contributing to `template-python`! It's people like you that make this project great. ## Code of Conduct @@ -15,13 +15,13 @@ If you discover a security issue, please refer to our [Security Policy](SECURITY ## How Can I Contribute? -There are many ways to contribute to `project_name`, and not all of them involve writing code: +There are many ways to contribute to `template-python`, and not all of them involve writing code: - **Reporting Bugs:** Help us improve by submitting detailed bug reports via our issue tracker. - **Suggesting Features:** Propose new features or enhancements that could benefit the project. - **Improving Documentation:** Fix typos, add examples, or write new guides. - **Writing Code:** Fix bugs, implement features, or improve performance. -- **Helping Others:** Answer questions in [Discussions](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/discussions) or issue comments. +- **Helping Others:** Answer questions in [Discussions](https://github.com/markurtz/template-python/discussions) or issue comments. For general questions and help, please see [SUPPORT.md](SUPPORT.md). @@ -45,7 +45,7 @@ Before you start coding, please refer to our [Development Guide](DEVELOPING.md) ### 3. Making Changes -1. **Fork the Repository:** Fork the `project_name` repository to your GitHub account. +1. **Fork the Repository:** Fork the `template-python` repository to your GitHub account. 1. **Create a Branch:** Create a new branch from `main` for your work (e.g., `git checkout -b feat/add-new-feature`). 1. **Write Code:** Implement your changes, adhering to the project's coding standards. 1. **Write Tests:** Add unit tests or integration tests for your changes to ensure stability. @@ -67,4 +67,4 @@ Before you start coding, please refer to our [Development Guide](DEVELOPING.md) ## Licensing -By contributing to `project_name`, you agree that your contributions will be licensed under its [Apache 2.0 License](LICENSE). +By contributing to `template-python`, you agree that your contributions will be licensed under its [Apache 2.0 License](LICENSE). diff --git a/DEVELOPING.md b/DEVELOPING.md index cbc9177..efd8302 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -1,4 +1,4 @@ -# Developing `project_name` +# Developing `template-python` This guide provides instructions for setting up your development environment, navigating the project structure, and adhering to our coding standards. @@ -22,8 +22,8 @@ Ensure your system meets the following requirements before getting started: > Once implemented, you can spin up the development environment with: > > ```bash -> git clone https://github.com/{{organization}}/project_name.git -> cd project_name +> git clone https://github.com/markurtz/template-python.git +> cd template-python > > # Build and start the development environment in the background > docker-compose up -d --build diff --git a/Dockerfile b/Dockerfile index c2ad751..d9fcbbd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # --------------------------------------------------------- -# project_name Dockerfile +# template-python Dockerfile # Licensed under the Apache License, Version 2.0 # --------------------------------------------------------- # Standardized Multi-stage Dockerfile Template for Python @@ -23,7 +23,7 @@ RUN pip install uv hatch # Copy package manifests and install dependencies COPY pyproject.toml README.md ./ # Create a dummy src directory to satisfy hatch build if needed -RUN mkdir -p src/project_name && touch src/project_name/__init__.py +RUN mkdir -p src/template_python && touch src/template_python/__init__.py RUN uv pip install --system --no-cache -e . # Copy application source code @@ -40,10 +40,10 @@ RUN hatch build FROM python:3.10-slim # OCI Standard Labels -LABEL org.opencontainers.image.title="project_name" +LABEL org.opencontainers.image.title="template-python" LABEL org.opencontainers.image.description="Production-ready Python application container" LABEL org.opencontainers.image.licenses="Apache-2.0" -LABEL org.opencontainers.image.source="https://github.com/{{organization}}/project_name" +LABEL org.opencontainers.image.source="https://github.com/markurtz/template-python" # Define environment variables ENV APP_ENV=production \ @@ -68,4 +68,4 @@ USER appuser EXPOSE 8080 # Define the command to run the application -CMD ["python", "-m", "project_name"] +CMD ["python", "-m", "template_python"] diff --git a/NOTICE b/NOTICE index c4c3b8f..b91e07e 100644 --- a/NOTICE +++ b/NOTICE @@ -1,8 +1,8 @@ -project_name -Copyright {{year}} {{organization}} +template-python +Copyright {{year}} markurtz -This product includes software developed by {{organization}} -(https://github.com/{{organization}}/project_name). +This product includes software developed by markurtz +(https://github.com/markurtz/template-python). ========================================================================= Third-Party Attributions diff --git a/README.md b/README.md index 9caf5ca..da03eae 100644 --- a/README.md +++ b/README.md @@ -15,13 +15,15 @@ GitHub Release - + + + + +
CI Status @@ -31,11 +33,9 @@ Closed Issues - License @@ -62,7 +62,7 @@ ______________________________________________________________________ Welcome to the template-python template repository! This template provides a robust foundation for building high-quality, scalable software projects. It includes standard directories, issue templates, CI/CD workflows, and comprehensive placeholder documentation. -To use this template, run the included `scripts/bootstrap.sh` script to automatically replace all placeholder variables with your project details. For full setup instructions including GitHub settings, publishing, and docs, see the **[Repository Setup Guide](https://markurtz.github.io/template-python/guides/repository-setup/)**. +To use this template, run the included `scripts/bootstrap.py` script via `uv run` to automatically replace all placeholder variables with your project details. For full setup instructions including GitHub settings, publishing, and docs, see the **[Repository Setup Guide](https://markurtz.github.io/template-python/guides/repository-setup/)**. ### Why Use template-python? @@ -79,7 +79,7 @@ When evaluating template-python against other templates, consider the following | **Setup Speed** | Very Fast | Fast | Slower (requires CLI tool) | | **Visual Assets** | Pre-configured Light/Dark assets | None | Varies | | **CI/CD Built-in** | Yes (GitHub Actions) | No | Optional | -| **Complexity** | Low ([`scripts/bootstrap.sh`](https://github.com/markurtz/template-python/blob/main/scripts/bootstrap.sh)) | None | Medium (Jinja templates) | +| **Complexity** | Low ([`scripts/bootstrap.py`](https://github.com/markurtz/template-python/blob/main/scripts/bootstrap.py)) | None | Medium (Jinja templates) | ## What's New @@ -92,10 +92,12 @@ This project has just been instantiated from the template repository. Keep an ey ## Quick Start ```bash -pip install template-python +uv run scripts/bootstrap.py ``` -For full installation options (from source, Docker, platform-specific notes) and step-by-step onboarding, see the **[Getting Started guide](https://markurtz.github.io/template-python/getting-started/)**. +The script will interactively prompt you for your project details (organization, project name, descriptions, and feature toggles) and automatically configure the repository. + +For full setup instructions including GitHub settings, publishing, and docs, see the **[Repository Setup Guide](https://markurtz.github.io/template-python/guides/repository-setup/)**. ## Core Concepts @@ -146,9 +148,7 @@ If you use this template or the resulting software in your research, please cite @software{template-python, author = {markurtz}, title = {template-python}, - version = {{{version}}}, - month = {{{month}}}, - year = {2026}, + year = {{year}}, url = {https://github.com/markurtz/template-python} } ``` diff --git a/SECURITY.md b/SECURITY.md index 4ef3726..1a02e20 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,10 +1,10 @@ -# Security Policy for `project_name` +# Security Policy for `template-python` -We take the security of `project_name` seriously. This document outlines our security policies, supported versions, and how to responsibly disclose a vulnerability. +We take the security of `template-python` seriously. This document outlines our security policies, supported versions, and how to responsibly disclose a vulnerability. ## Supported Versions -Please check the table below for the versions of `project_name` that are currently being supported with security updates. +Please check the table below for the versions of `template-python` that are currently being supported with security updates. | Version | Supported | | :------------------------------ | :----------------- | @@ -20,8 +20,8 @@ Please check the table below for the versions of `project_name` that are current If you discover a security vulnerability, please bring it to our attention right away using one of the following methods: -1. **GitHub Security Advisories (Preferred):** Use the "Report a vulnerability" button on the **[Security tab](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/security/advisories)** of this repository. -1. **Email:** Send your report directly to **[INSERT EMAIL ADDRESS OR SECURITY CONTACT]**. *(Optional: Encrypt your email using our PGP key: [INSERT PGP KEY LINK/FINGERPRINT])* +1. **GitHub Security Advisories (Preferred):** Use the "Report a vulnerability" button on the **[Security tab](https://github.com/markurtz/template-python/security/advisories)** of this repository. +1. **Email:** Send your report directly to **contact the maintainers**. ### What to Include in Your Report @@ -31,7 +31,7 @@ To help us resolve the issue quickly, please include the following information: - **Detailed description** of the vulnerability and its potential impact. - **Step-by-step instructions** to reproduce the issue. - **Proof of Concept (PoC)** code or screenshots, if available. -- **Environment details** (e.g., version of `project_name`, OS, Python version, relevant configurations). +- **Environment details** (e.g., version of `template-python`, OS, Python version, relevant configurations). ## Triage and Resolution Process @@ -46,13 +46,13 @@ We will handle your report with strict confidentiality. Our process is as follow **In Scope:** -- Vulnerabilities within the core `project_name` codebase. +- Vulnerabilities within the core `template-python` codebase. - Security issues resulting from our default configurations or execution paths. **Out of Scope:** - Theoretical issues without a reproducible PoC. -- Vulnerabilities in third-party dependencies that are not exploitable through `project_name`. -- Issues requiring the victim to intentionally clone and run `project_name` against a malicious, untrusted Git repository, unless it leads to unexpected system compromise beyond the expected permissions. +- Vulnerabilities in third-party dependencies that are not exploitable through `template-python`. +- Issues requiring the victim to intentionally clone and run `template-python` against a malicious, untrusted Git repository, unless it leads to unexpected system compromise beyond the expected permissions. *(Note: We currently do not operate a bug bounty program. Disclosures are greatly appreciated but are not eligible for financial rewards at this time.)* diff --git a/SUPPORT.md b/SUPPORT.md index 2f5fea2..eb245ae 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -1,6 +1,6 @@ -# Support for `project_name` +# Support for `template-python` -We are excited to have you use `project_name`! If you need help, please follow these guidelines to ensure you get support quickly and efficiently. +We are excited to have you use `template-python`! If you need help, please follow these guidelines to ensure you get support quickly and efficiently. ## Security Vulnerabilities @@ -13,22 +13,22 @@ If you have found a security vulnerability, please refer to our [Security Policy Before reaching out, we recommend checking the following resources. Many common questions and issues are already covered there. -- **[Official Documentation](https://%7B%7Borganization%7D%7D.github.io/%7B%7Bproject_name%7D%7D):** Comprehensive guides, tutorials, and API references. -- **[GitHub Issues](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/issues):** Search existing issues to see if someone else has already reported your problem or requested your feature. Feel free to add a "+1" reaction to existing issues to show your interest. -- **[GitHub Discussions](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/discussions):** Search our discussions for Q&A, general advice, and community knowledge. +- **[Official Documentation](https://markurtz.github.io/template-python):** Comprehensive guides, tutorials, and API references. +- **[GitHub Issues](https://github.com/markurtz/template-python/issues):** Search existing issues to see if someone else has already reported your problem or requested your feature. Feel free to add a "+1" reaction to existing issues to show your interest. +- **[GitHub Discussions](https://github.com/markurtz/template-python/discussions):** Search our discussions for Q&A, general advice, and community knowledge. ## Opening a New Issue If you cannot find an answer in the documentation or existing issues, please open a new issue. To help us resolve your issue faster, please choose the correct venue: -| Issue Type | Venue | Description | -| :--------------------- | :--------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | -| **Bug Report** | [GitHub Issues](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/issues/new) | Use the "Bug Report" template. Provide reproducible steps, environment details, and relevant logs. | -| **Feature Request** | [GitHub Issues](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/issues/new) | Use the "Feature Request" template. Clearly describe your use case and the problem the feature would solve. | -| **Q&A / General Help** | [GitHub Discussions](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/discussions/new) | Start a discussion for questions about how to use `project_name`, architecture queries, or advice. | +| Issue Type | Venue | Description | +| :--------------------- | :-------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | +| **Bug Report** | [GitHub Issues](https://github.com/markurtz/template-python/issues/new) | Use the "Bug Report" template. Provide reproducible steps, environment details, and relevant logs. | +| **Feature Request** | [GitHub Issues](https://github.com/markurtz/template-python/issues/new) | Use the "Feature Request" template. Clearly describe your use case and the problem the feature would solve. | +| **Q&A / General Help** | [GitHub Discussions](https://github.com/markurtz/template-python/discussions/new) | Start a discussion for questions about how to use `template-python`, architecture queries, or advice. | Feel free to join the conversation on GitHub Discussions to connect with other users and maintainers. ## Commercial Support -At this time, there is no official commercial support available for `project_name`. Support is provided on a best-effort basis by the open-source community and maintainers. +At this time, there is no official commercial support available for `template-python`. Support is provided on a best-effort basis by the open-source community and maintainers. diff --git a/docker-compose.yml b/docker-compose.yml index ff79821..916fa7c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ --- # --------------------------------------------------------- -# project_name Docker Compose +# template-python Docker Compose # Licensed under the Apache License, Version 2.0 # --------------------------------------------------------- services: @@ -19,7 +19,7 @@ services: command: - "python" - "-m" - - "project_name" + - "template_python" # Standard Database Profile # Use `docker-compose --profile db up` to start this service diff --git a/docs/community/index.md b/docs/community/index.md index 9cd846c..fafef47 100644 --- a/docs/community/index.md +++ b/docs/community/index.md @@ -1,6 +1,6 @@ # Community -`project_name` is an open-source project and we welcome participation from everyone. This section contains all the resources you need to get involved, stay informed, and interact with the community. +`template-python` is an open-source project and we welcome participation from everyone. This section contains all the resources you need to get involved, stay informed, and interact with the community. ## Get Involved @@ -66,9 +66,11 @@ Where to ask questions, report issues, and find help. ## Community Channels -| Channel | Purpose | -| :------------------------------------------------------------------------------------------------ | :--------------------------------------------- | -| GitHub Issues | Bug reports and feature requests | -| GitHub Discussions | Q&A, ideas, and general community conversation | -| Slack | Real-time chat with the team and community | -| Blog | Project updates, tutorials, and announcements | +| Channel | Purpose | +| :--------------------------------------------------------------------------------------- | :--------------------------------------------- | +| GitHub Issues | Bug reports and feature requests | +| GitHub Discussions | Q&A, ideas, and general community conversation | + + + + diff --git a/docs/examples/index.md b/docs/examples/index.md index 6f72261..1cae486 100644 --- a/docs/examples/index.md +++ b/docs/examples/index.md @@ -1,6 +1,6 @@ # Examples -This section contains runnable code examples that demonstrate real-world usage of {{ project_name }}. Each example is self-contained and can be copied directly into your own project. +This section contains runnable code examples that demonstrate real-world usage of template-python. Each example is self-contained and can be copied directly into your own project. > [!NOTE] > All examples assume you have completed [Installation](../getting-started/installation.md). @@ -14,9 +14,9 @@ This section contains runnable code examples that demonstrate real-world usage o ______________________________________________________________________ -Simple, self-contained examples that demonstrate the core functionality of `{{ project_name }}`. +Simple, self-contained examples that demonstrate the core functionality of `template-python`. -- :octicons-arrow-right-24: Basic Example Template +- :octicons-arrow-right-24: Basic Example Template diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index ef00288..7671ef3 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -1,6 +1,6 @@ # Getting Started -Welcome to `project_name`! This section walks you through everything you need to go from zero to productive. +Welcome to `template-python`! This section walks you through everything you need to go from zero to productive. Follow the pages in order for the best experience, or jump directly to the section you need. @@ -13,7 +13,7 @@ Follow the pages in order for the best experience, or jump directly to the secti ______________________________________________________________________ -Install `project_name` via pip, from source, or using Docker. +Install `template-python` via pip, from source, or using Docker. Includes platform-specific notes and verification steps. [:octicons-arrow-right-24: Installation Guide](installation.md) @@ -25,7 +25,7 @@ Includes platform-specific notes and verification steps. ______________________________________________________________________ -Your first 5 minutes with `project_name`. Initialize, run a command, +Your first 5 minutes with `template-python`. Initialize, run a command, and see results — fast. [:octicons-arrow-right-24: Quick Start](quickstart.md) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index b0a9b20..7c18ad2 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -1,6 +1,6 @@ # Installation -This page covers all supported installation methods for `project_name`. +This page covers all supported installation methods for `template-python`. ## Requirements @@ -13,45 +13,15 @@ Before installing, ensure your system meets the following prerequisites: | **Git** | 2.x | Required for source installs | | **Docker** | 24.x | Optional — for containerized installs | -## Build Setup (Core Workflow) - -`project_name` is primarily used as a build plugin. The preferred pathway is to configure it in your `pyproject.toml` utilizing Hatchling or Setuptools. - -=== "Hatchling (Preferred)" - -```` -```toml -[build-system] -requires = ["hatchling", "project_name"] -build-backend = "hatchling.build" - -[tool.hatch.version] -source = "project_name" -``` -```` - -=== "Setuptools (pyproject.toml)" - -```` -```toml -[build-system] -requires = ["setuptools>=61.0", "project_name"] -build-backend = "setuptools.build_meta" - -[project] -dynamic = ["version"] -``` -```` - ## Standard Installation -If you need to install the package directly into an environment (e.g., for local development or testing without a build system), use `pip` or `uv`: +If you need to install the package directly into an environment (e.g., for local development or testing), use `pip` or `uv`: === "pip (Standard)" ```` ```bash -pip install project_name +pip install template_python ``` ```` @@ -59,7 +29,7 @@ pip install project_name ```` ```bash -uv pip install project_name +uv pip install template_python ``` ```` @@ -68,7 +38,7 @@ uv pip install project_name After installation, you can confirm it is available in your Python environment by running: ```bash -python -c "import project_name; print(project_name.__version__)" +python -c "import template_python; print(template_python.__version__)" ``` You should see output similar to: @@ -82,8 +52,8 @@ You should see output similar to: To install the latest unreleased code directly from the repository and set up a local development environment: ```bash -git clone https://github.com/{{ org_name }}/{{ project_name }}.git -cd {{ project_name }} +git clone https://github.com/markurtz/template-python.git +cd template-python # Sync the development environment (installs all groups and extras) uv sync --all-groups --all-extras @@ -101,10 +71,10 @@ A pre-built Docker image is available for containerized environments: ```bash # Pull the latest image -docker pull ghcr.io/{{ org_name }}/{{ project_name }}:latest +docker pull ghcr.io/markurtz/template-python:latest # Run a one-off command -docker run --rm ghcr.io/{{ org_name }}/{{ project_name }}:latest python -c "import project_name; print(project_name.__version__)" +docker run --rm ghcr.io/markurtz/template-python:latest python -c "import template_python; print(template_python.__version__)" ``` For a persistent, volume-mounted setup using Docker Compose, see the `docker-compose.yml` in the root of the repository. @@ -137,7 +107,7 @@ To upgrade an existing installation to the latest release: ```` ```bash -pip install --upgrade project_name +pip install --upgrade template-python ``` ```` @@ -145,7 +115,7 @@ pip install --upgrade project_name ```` ```bash -uv pip install --upgrade project_name +uv pip install --upgrade template-python ``` ```` @@ -155,7 +125,7 @@ uv pip install --upgrade project_name ```` ```bash -pip uninstall project_name +pip uninstall template-python ``` ```` @@ -163,7 +133,7 @@ pip uninstall project_name ```` ```bash -uv pip uninstall project_name +uv pip uninstall template-python ``` ```` diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 41b19ad..e6930a3 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,61 +1,62 @@ # Quick Start -This guide gets you from a fresh installation to running your first command in under 5 minutes. +This guide gets you from a fresh clone to a bootstrapped and running project in under 5 minutes. -> [!NOTE] -> Make sure you have completed [Installation](installation.md) before continuing. If you are the maintainer and just instantiated this repository from the template, please complete the [Repository Setup](../guides/repository-setup.md) first. +## Step 1 — Instantiate the Template -## Step 1 — Initialize Your Environment +1. Navigate to the [template-python repository on GitHub](https://github.com/markurtz/template-python). +1. Click the green **Use this template** button and select **Create a new repository**. +1. Choose your repository name and visibility, then click **Create repository**. +1. Clone your new repository to your local machine: + ```bash + git clone https://github.com/YOUR_ORG/YOUR_REPO.git + cd YOUR_REPO + ``` -If you haven't already, set up your project and install `{{ project_name }}`: +## Step 2 — Bootstrap the Project -```bash -python -m venv .venv -source .venv/bin/activate -pip install project_name -``` +The template comes with an interactive script that automatically replaces all placeholders with your project's details. -## Step 2 — Verify the Install +Run the bootstrap script using `uv`: ```bash -{{ project_name }} --version +uv run scripts/bootstrap.py ``` -Expected output: +Follow the interactive prompts to configure your project. Once complete, you can choose to finalize the process, which will delete the bootstrap script and its tests. -```console -{{ project_name }} 0.1.0 -``` +## Step 3 — Sync the Environment -## Step 3 — Run Your First Command +Now that your project is renamed and configured, install all dependencies and set up your local development environment: -```python -from project_name import Client +```bash +uv sync --all-groups --all-extras +``` -# Initialize the client -client = Client(api_key="YOUR_KEY") +## Step 4 — Run Tests and Quality Checks -# Run a core action -result = client.run_action("hello_world") -print(result) -``` +Ensure everything is configured correctly by running the pre-configured quality checks and tests: -Expected output: +```bash +# Run linting and type checking +hatch run lint:check +hatch run types:check -```console -[INFO] Initializing {{ project_name }} client... -[SUCCESS] Action completed! Result: Hello, World from {{ project_name }}! +# Run tests +hatch run test:all ``` +If all tests pass, your new project is successfully bootstrapped and ready for development! + > [!TIP] -> Replace `"YOUR_KEY"` with your actual API key or credentials. See the [Reference](../reference/index.md) for all available client configuration options. +> See the [Developer Guide](../community/developing.md) for more details on local development commands. -## Step 4 — Explore Further +## Step 5 — Explore Further -Now that your first command works, explore what `{{ project_name }}` can do: +Now that your project is ready, explore what `template-python` provides: -- **[Guides](../guides/index.md)** — Task-specific deep dives including CI/CD and repository setup -- **[Reference](../reference/index.md)** — Full API and CLI documentation -- **[Examples](../examples/index.md)** — Runnable code examples +- **[Repository Setup](../guides/repository-setup.md)** — Complete your GitHub configuration (Actions, Discussions, PyPI publishing). +- **[Guides](../guides/index.md)** — Task-specific deep dives including CI/CD workflows. +- **[Reference](../reference/index.md)** — Full configuration reference. -**Next:** [Guides →](../guides/index.md) +**Next:** [Repository Setup →](../guides/repository-setup.md) diff --git a/docs/guides/github-workflows.md b/docs/guides/github-workflows.md index 5fa6af9..5a47e74 100644 --- a/docs/guides/github-workflows.md +++ b/docs/guides/github-workflows.md @@ -1,6 +1,6 @@ # Using CI/CD and GitHub workflows -This guide explains the continuous integration and continuous deployment (CI/CD) pipelines used in `project_name`. It outlines the standard pathways, development cycles, and what standards must be met to contribute to the repository. +This guide explains the continuous integration and continuous deployment (CI/CD) pipelines used in `template-python`. It outlines the standard pathways, development cycles, and what standards must be met to contribute to the repository. ## Understand the architecture diff --git a/docs/guides/index.md b/docs/guides/index.md index 9eef64b..227242c 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -1,6 +1,6 @@ # Guides -This section contains task-oriented how-to guides for `{{ project_name }}`. Unlike the [Getting Started](../getting-started/index.md) section, which is structured as a linear onboarding path, these guides are standalone — jump to whichever one is relevant to your current task. +This section contains task-oriented how-to guides for `template-python`. Unlike the [Getting Started](../getting-started/index.md) section, which is structured as a linear onboarding path, these guides are standalone — jump to whichever one is relevant to your current task. ## Available Guides diff --git a/docs/guides/repository-setup.md b/docs/guides/repository-setup.md index 8d819ab..8f34ff3 100644 --- a/docs/guides/repository-setup.md +++ b/docs/guides/repository-setup.md @@ -6,20 +6,18 @@ This guide acts as a checklist for the project maintainer. Once these steps are ## 1. Bootstrap the repository -Before making any manual changes, run the included bootstrap script to automatically replace all placeholder variables (like `project_name` and `{{organization}}`) with your actual project details. +Before making any manual changes, run the included interactive bootstrap script to automatically replace all placeholder variables (like `template-python` and `markurtz`) with your actual project details. ```bash -./scripts/bootstrap.sh \ - --project-name my-cool-project \ - --project-desc "A description of my cool project" \ - --organization my-org \ - --org-name "My Organization" +uv run scripts/bootstrap.py ``` +The script will interactively prompt you for your project details (organization, project name, descriptions, and feature toggles) and automatically configure the repository. + > [!NOTE] -> This script will rename the source directory (`src/project_name/` -> `src/my_cool_project/`) and update all references in the documentation, GitHub Actions workflows, and configuration files. +> This script will rename the source directory (`src/template_python/` -> `src/my_cool_project/`) and update all references in the documentation, GitHub Actions workflows, and configuration files. -Once you have verified the changes, you can safely delete the `scripts/bootstrap.sh` file and commit the updates. +Once you have verified the changes and chosen to finalize the bootstrap process, the script will automatically delete itself and its related end-to-end tests. Commit the updates. ## 2. Configure GitHub settings diff --git a/docs/index.md b/docs/index.md index 1247b77..46fc7a2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,15 +10,15 @@ hide: - {{ project_name }} Logo + template-python Logo -# {{ project_name }} +# template-python -**{{ project_description }}** +**An opinionated, production-ready Apache 2.0 template repository for bootstrapping modern software projects.** [Get Started](getting-started/index.md){ .md-button .md-button--primary } -View on GitHub +View on GitHub @@ -64,7 +64,7 @@ Step-by-step guides for common tasks, integrations, and configuration patterns. ______________________________________________________________________ -Runnable code examples that demonstrate real-world usage of {{ project_name }}. +Runnable code examples that demonstrate real-world usage of template-python. [:octicons-arrow-right-24: See Examples](examples/index.md) @@ -107,24 +107,17 @@ Our security policy, responsible disclosure process, and supported versions. ## Quick Install -### Build Configuration - -{{ project_name }} is primarily used as a build plugin. The preferred pathway is to configure it in your `pyproject.toml`: - -```toml -[build-system] -requires = ["hatchling", "project_name"] -build-backend = "hatchling.build" - -[tool.hatch.version] -source = "project_name" +```bash +pip install template-python ``` -For advanced installation options, Setuptools alternatives, and step-by-step onboarding, see the [Installation Guide](getting-started/installation.md). +For advanced installation options, and step-by-step onboarding, see the [Installation Guide](getting-started/installation.md). ## Links -- :material-github: GitHub Repository -- :material-map-marker-path: Roadmap -- :material-post-outline: Blog -- :material-slack: Slack Community +- :material-github: GitHub Repository +- :material-map-marker-path: Roadmap + + + + diff --git a/docs/reference/api.md b/docs/reference/api.md deleted file mode 100644 index 7478965..0000000 --- a/docs/reference/api.md +++ /dev/null @@ -1,13 +0,0 @@ -# Python API Reference - -This section provides the auto-generated documentation for the `project_name` Python API. - -::: project_name.settings -options: -show_root_heading: true -show_source: true - -::: project_name.logging -options: -show_root_heading: true -show_source: true diff --git a/docs/reference/index.md b/docs/reference/index.md index 809f9cc..f101b3d 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,6 +1,6 @@ # Reference -The Reference section contains the complete technical documentation for {{ project_name }} — API classes and configuration options. This is the section to bookmark when you need to look something up. +The Reference section contains the complete technical documentation for template-python — API classes and configuration options. This is the section to bookmark when you need to look something up. ## In This Section @@ -13,7 +13,7 @@ ______________________________________________________________________ Auto-generated documentation for all public classes, methods, and modules. -[:octicons-arrow-right-24: Python API Reference](api.md) +[:octicons-arrow-right-24: Python API Reference](api/template_python/) @@ -22,9 +22,11 @@ Auto-generated documentation for all public classes, methods, and modules. ______________________________________________________________________ -Coverage reports are generated during CI/CD or locally. +Run `hatch run test:all-cov` to generate HTML reports locally, or view the latest pipeline runs: -Run `hatch run test:all-cov` to generate HTML reports in `docs/coverage/`. +- [:octicons-arrow-right-24: Unit Tests](../coverage/unit/htmlcov/index.html) +- [:octicons-arrow-right-24: Integration Tests](../coverage/integration/htmlcov/index.html) +- [:octicons-arrow-right-24: End-to-End Tests](../coverage/e2e/htmlcov/index.html) @@ -32,23 +34,20 @@ Run `hatch run test:all-cov` to generate HTML reports in `docs/coverage/`. ## Python API Usage -While `project_name` is primarily used as a build plugin, it can also be used programmatically in your own Python scripts: +`template-python` can also be used programmatically in your own Python scripts: ```python -from project_name import Settings, resolve_version -from project_name.utils import BuildEnvironment, GitRepository - -# Resolve the version using default settings -version, ref = resolve_version( - Settings(), GitRepository(), BuildEnvironment() -) -print(f"Resolved version: {version}") - -# Or pass custom settings -custom_settings = Settings(package_name="my_pkg", source_type=["commit"]) -version, ref = resolve_version( - custom_settings, GitRepository(), BuildEnvironment() -) +from template_python import Settings, configure_logger, logger +from template_python.logging import LoggingSettings + +# Initialize the global logger +configure_logger(LoggingSettings(enabled=True, level="INFO")) + +# Load application settings +settings = Settings(environment="production") + +# Log the current configuration +logger.info("Application initialized with settings: {}", settings) ``` ## Generating Reference Docs diff --git a/docs/scripts/gen_ref_pages.py b/docs/scripts/gen_ref_pages.py new file mode 100644 index 0000000..06775e8 --- /dev/null +++ b/docs/scripts/gen_ref_pages.py @@ -0,0 +1,76 @@ +# noqa: INP001 +""" +Generate the code reference pages and navigation. + +This module automates the creation of API reference documentation. It leverages the +``mkdocs-gen-files`` plugin to traverse the ``src/`` directory, generating Markdown +pages for each Python module and constructing a unified navigation structure. This +ensures the project's codebase remains fully documented and accessible. +""" + +from pathlib import Path + +import mkdocs_gen_files + +__all__ = ["generate_api_reference"] + + +def generate_api_reference() -> None: + """ + Generate the API reference documentation and navigation structure. + + Iterates through all Python source files in the ``src/`` directory, converting + their paths into a logical documentation structure. It generates Markdown files + with appropriate directives to render the API, and produces a literate navigation + summary. + + Example: + This function is executed directly when the script is run: + + .. code-block:: python + + generate_api_reference() + + :return: None + """ + navigation = mkdocs_gen_files.Nav() + root_directory = Path(__file__).parent.parent.parent + source_directory = root_directory / "src" + api_reference_path = Path("reference/api") + + for python_file in sorted(source_directory.rglob("*.py")): + module_path = python_file.relative_to(source_directory).with_suffix("") + document_path = python_file.relative_to(source_directory).with_suffix(".md") + full_document_path = api_reference_path / document_path + + path_parts = tuple(module_path.parts) + + if path_parts[-1] == "__init__": + path_parts = path_parts[:-1] + document_path = document_path.with_name("index.md") + full_document_path = full_document_path.with_name("index.md") + elif path_parts[-1] == "__main__": + continue + + if not path_parts: + continue + + navigation[path_parts] = document_path.as_posix() + + with mkdocs_gen_files.open(full_document_path, "w") as document_file: + identifier = ".".join(path_parts) + document_file.write( + f"::: {identifier}\n" + " options:\n" + " show_root_heading: true\n" + " show_source: true\n" + ) + + mkdocs_gen_files.set_edit_path(full_document_path, python_file) + + summary_file_path = api_reference_path / "SUMMARY.md" + with mkdocs_gen_files.open(summary_file_path, "w") as navigation_file: + navigation_file.writelines(navigation.build_literate_nav()) + + +generate_api_reference() diff --git a/examples/README.md b/examples/README.md index 157fdc9..53ab53a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,6 @@ -# project_name Examples +# template-python Examples -This directory contains practical, runnable demonstrations of how to use `project_name` in various scenarios. These examples are designed to help you quickly understand core concepts, advanced configurations, and best practices. +This directory contains practical, runnable demonstrations of how to use `template-python` in various scenarios. These examples are designed to help you quickly understand core concepts, advanced configurations, and best practices. ## Prerequisites @@ -10,15 +10,15 @@ Before running the examples, ensure you have set up your environment correctly: 1. **Environment Variables:** Copy `.env.example` to `.env` if the examples require configuration (e.g., API keys or external services). > [!NOTE] -> Some examples may require additional dependencies not included in the core `project_name` package. Please check the `README.md` within each specific example directory for details. +> Some examples may require additional dependencies not included in the core `template-python` package. Please check the `README.md` within each specific example directory for details. ## Example Index Below is a curated list of available examples, categorized by complexity: -| Example | Complexity | Description | -| :------------------------------------------- | :---------- | :--------------------------------------------------------------------------------------------- | -| **`[example_template/](example_template/)`** | ⭐ Beginner | A generic template demonstrating standard structure, configuration, and telemetry integration. | +| Example | Complexity | Description | +| :------------------------------------------- | :--------- | :--------------------------------------------------------------------------------------------- | +| **`[example_template/](example_template/)`** | Beginner | A generic template demonstrating standard structure, configuration, and telemetry integration. | diff --git a/examples/example_template/README.md b/examples/example_template/README.md index 9594db7..61bdb5d 100644 --- a/examples/example_template/README.md +++ b/examples/example_template/README.md @@ -1,6 +1,6 @@ # Example Template -This directory serves as a generic template for creating new examples in the `project_name` repository. It demonstrates the standard structure, documentation, and foundational code required for a well-formed example, ensuring it adheres to the latest standards and workflows. +This directory serves as a generic template for creating new examples in the `template-python` repository. It demonstrates the standard structure, documentation, and foundational code required for a well-formed example, ensuring it adheres to the latest standards and workflows. ## Prerequisites @@ -26,6 +26,6 @@ python examples/example_template/main.py This template highlights the following essential components that should be included in most project examples: - **Standard Structure:** A contained directory with its own `README.md`, `requirements.txt`, and executable python script (`main.py`). -- **Configuration (Settings):** How to initialize the `Settings` model using `project_name.settings.Settings`. -- **Telemetry (Logging):** How to properly configure application logging using `project_name.logging.LoggingSettings` and `configure_logger` to emit structured logs. +- **Configuration (Settings):** How to initialize the `Settings` model using `template_python.settings.Settings`. +- **Telemetry (Logging):** How to properly configure application logging using `template_python.logging.LoggingSettings` and `configure_logger` to emit structured logs. - **Best Practices:** Demonstration of clear code organization, descriptive docstrings, and safe execution patterns (`if __name__ == "__main__":`). diff --git a/examples/example_template/main.py b/examples/example_template/main.py index 6a752a3..8b87eac 100644 --- a/examples/example_template/main.py +++ b/examples/example_template/main.py @@ -1,6 +1,6 @@ """ A generic example template demonstrating the basic initialization -and usage of the project_name framework. +and usage of the template_python framework. This script shows how to: 1. Load configuration using the standard Settings model. @@ -8,14 +8,14 @@ 3. Execute standard application logic. """ -from project_name.logging import LoggingSettings, configure_logger, logger -from project_name.settings import Settings +from template_python.logging import LoggingSettings, configure_logger, logger +from template_python.settings import Settings def main() -> None: # 1. Initialize configuration # Settings automatically loads from the environment or .env file - # matching the PROJECT_NAME__ prefix. + # matching the TEMPLATE_PYTHON__ prefix. settings = Settings() # 2. Configure logging diff --git a/llms-full.txt b/llms-full.txt index 59f5a1b..ea194d9 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -1,27 +1,27 @@ -# project_name Full Documentation +# template-python Full Documentation This file contains the concatenated core documentation for the project to provide comprehensive context to LLMs. -# project_name +# template-python > An opinionated, production-ready Apache 2.0 template repository for bootstrapping modern software projects. -`project_name` is designed to eliminate boilerplate and enforce consistency across an organization's repositories. +`template-python` is designed to eliminate boilerplate and enforce consistency across an organization's repositories. For comprehensive context and full documentation, please see [llms-full.txt](llms-full.txt). ## Docs -- [Home](https://{{organization}}.github.io/project_name/): Documentation site home page -- [Getting Started](https://{{organization}}.github.io/project_name/getting-started/): Installation, quickstart, and workflow guides -- [Guides](https://{{organization}}.github.io/project_name/guides/): How-to guides for common tasks -- [API Reference](https://{{organization}}.github.io/project_name/reference/): Full API reference documentation +- [Home](https://markurtz.github.io/template-python/): Documentation site home page +- [Getting Started](https://markurtz.github.io/template-python/getting-started/): Installation, quickstart, and workflow guides +- [Guides](https://markurtz.github.io/template-python/guides/): How-to guides for common tasks +- [API Reference](https://markurtz.github.io/template-python/reference/): Full API reference documentation ## Repository Files -- [README.md](https://github.com/{{organization}}/project_name/blob/main/README.md): Project overview and quick start -- [AGENTS.md](https://github.com/{{organization}}/project_name/blob/main/AGENTS.md): AI agent coding instructions -- [DEVELOPING.md](https://github.com/{{organization}}/project_name/blob/main/DEVELOPING.md): Developer setup guide +- [README.md](https://github.com/markurtz/template-python/blob/main/README.md): Project overview and quick start +- [AGENTS.md](https://github.com/markurtz/template-python/blob/main/AGENTS.md): AI agent coding instructions +- [DEVELOPING.md](https://github.com/markurtz/template-python/blob/main/DEVELOPING.md): Developer setup guide ## Optional -- [Full Documentation](https://{{organization}}.github.io/project_name/llms-full.txt): The complete concatenated documentation for project_name +- [Full Documentation](https://markurtz.github.io/template-python/llms-full.txt): The complete concatenated documentation for template-python ## File: README.md @@ -30,7 +30,7 @@ For comprehensive context and full documentation, please see [llms-full.txt](llm - project_name Logo + template-python Logo

@@ -40,28 +40,28 @@ For comprehensive context and full documentation, please see [llms-full.txt](llm

- - GitHub Release - - - PyPI Release - - - Supported Python Versions + + GitHub Release + +
- - CI Status + + CI Status
- - Closed Issues + + Closed Issues @@ -70,10 +70,10 @@ For comprehensive context and full documentation, please see [llms-full.txt](llm

- Documentation | - Roadmap | - Issues | - Discussions + Documentation | + Roadmap | + Issues | + Discussions

______________________________________________________________________ @@ -88,11 +88,11 @@ ______________________________________________________________________

-Welcome to the project_name template repository! This template provides a robust foundation for building high-quality, scalable software projects. It includes standard directories, issue templates, CI/CD workflows, and comprehensive placeholder documentation. +Welcome to the template-python template repository! This template provides a robust foundation for building high-quality, scalable software projects. It includes standard directories, issue templates, CI/CD workflows, and comprehensive placeholder documentation. -To use this template, simply find and replace all instances of `project_name` and `{{organization}}` with your actual project details, update the placeholder SVG images in `docs/assets/branding/`, and you are ready to start coding. +To use this template, run `uv run scripts/bootstrap.py` to automatically configure your project with your organization and repository details, update the placeholder SVG images in `docs/assets/branding/`, and you are ready to start coding. -### Why Use project_name? +### Why Use template-python? - **Consistency:** Enforces a standardized layout and structure across your organization's repositories. - **Speed:** Bootstraps your project with pre-configured Actions, badges, and templates so you don't start from scratch. @@ -100,9 +100,9 @@ To use this template, simply find and replace all instances of `project_name` an ### Comparisons -When evaluating project_name against other templates, consider the following differences: +When evaluating template-python against other templates, consider the following differences: -| Feature | project_name Template | Standard GitHub Init | Cookiecutter / Copier | +| Feature | template-python Template | Standard GitHub Init | Cookiecutter / Copier | | :----------------- | :------------------------------- | :------------------- | :------------------------- | | **Setup Speed** | Very Fast | Fast | Slower (requires CLI tool) | | **Visual Assets** | Pre-configured Light/Dark assets | None | Varies | @@ -111,7 +111,7 @@ When evaluating project_name against other templates, consider the following dif ## What's New -**Welcome to the project_name Launch!** +**Welcome to the template-python Launch!** This project has just been instantiated from the template repository. Keep an eye on this section for future release highlights, new features, and community announcements! @@ -120,10 +120,10 @@ This project has just been instantiated from the template repository. Keep an ey ## Quick Start ```bash -pip install project_name +pip install template-python ``` -For full installation options (from source, Docker, platform-specific notes) and step-by-step onboarding, see the **[Getting Started guide](https://%7B%7Borganization%7D%7D.github.io/%7B%7Bproject_name%7D%7D/getting-started/)**. +For full installation options (from source, Docker, platform-specific notes) and step-by-step onboarding, see the **[Getting Started guide](https://markurtz.github.io/template-python/getting-started/)**. ## Core Concepts @@ -133,7 +133,7 @@ This project is built using modern Python tooling, enforcing strict code quality The repository is structured to separate documentation, application logic, and testing cleanly: -- `src/project_name/`: The primary application source code. +- `src/template_python/`: The primary application source code. - `tests/`: Comprehensive test suite ensuring reliability, organized into `unit/`, `integration/`, and `e2e/`. - `docs/`: Source code for the MkDocs Material documentation site, including step-by-step guides, references, and getting started tutorials. - `examples/`: Runnable reference projects demonstrating real-world configurations. @@ -171,13 +171,11 @@ This project is licensed under the Apache License 2.0. See the [LICENSE](LICENSE If you use this template or the resulting software in your research, please cite it using the following BibTeX entry: ```bibtex -@software{project_name, - author = {{{organization}}}, - title = {project_name}, - version = {{{version}}}, - month = {{{month}}}, - year = {2026}, - url = {https://github.com/{{organization}}/project_name} +@software{template-python, + author = {markurtz}, + title = {template-python}, + year = {{year}}, + url = {https://github.com/markurtz/template-python} } ``` @@ -191,7 +189,7 @@ If you use this template or the resulting software in your research, please cite ## Project Context -**`project_name`** is a production-ready Apache 2.0 template repository for bootstrapping modern software projects. +**`template-python`** is a production-ready Apache 2.0 template repository for bootstrapping modern software projects. **Primary language:** `Python 3.10+`\ **Package manager:** `Hatch` @@ -240,7 +238,7 @@ If you use this template or the resulting software in your research, please cite - **`docs/`** and **`mkdocs.yml`** control the site. Do not create docs outside the `nav:` tree. - `docs/index.md` dynamically includes `README.md` via MkDocs snippets. -- Use `{{placeholder}}` variables for templated fields (e.g., `project_name`, `{{organization}}`). +- Use `{{placeholder}}` variables for templated fields (e.g., `template-python`, `markurtz`). ## Agent Notes @@ -249,7 +247,7 @@ _Add notes here when updating instructions for AI agents._ ## File: DEVELOPING.md -# Developing `project_name` +# Developing `template-python` This guide provides instructions for setting up your development environment, navigating the project structure, and adhering to our coding standards. @@ -273,8 +271,8 @@ Ensure your system meets the following requirements before getting started: > Once implemented, you can spin up the development environment with: > > ```bash -> git clone https://github.com/{{organization}}/project_name.git -> cd project_name +> git clone https://github.com/markurtz/template-python.git +> cd template-python > > # Build and start the development environment in the background > docker-compose up -d --build @@ -457,9 +455,9 @@ For further assistance, please refer to our [SUPPORT.md](SUPPORT.md). ## File: CONTRIBUTING.md -# Contributing to project_name +# Contributing to template-python -First off, thank you for considering contributing to `project_name`! It's people like you that make this project great. +First off, thank you for considering contributing to `template-python`! It's people like you that make this project great. ## Code of Conduct @@ -474,13 +472,13 @@ If you discover a security issue, please refer to our [Security Policy](SECURITY ## How Can I Contribute? -There are many ways to contribute to `project_name`, and not all of them involve writing code: +There are many ways to contribute to `template-python`, and not all of them involve writing code: - **Reporting Bugs:** Help us improve by submitting detailed bug reports via our issue tracker. - **Suggesting Features:** Propose new features or enhancements that could benefit the project. - **Improving Documentation:** Fix typos, add examples, or write new guides. - **Writing Code:** Fix bugs, implement features, or improve performance. -- **Helping Others:** Answer questions in [Discussions](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/discussions) or issue comments. +- **Helping Others:** Answer questions in [Discussions](https://github.com/markurtz/template-python/discussions) or issue comments. For general questions and help, please see [SUPPORT.md](SUPPORT.md). @@ -504,7 +502,7 @@ Before you start coding, please refer to our [Development Guide](DEVELOPING.md) ### 3. Making Changes -1. **Fork the Repository:** Fork the `project_name` repository to your GitHub account. +1. **Fork the Repository:** Fork the `template-python` repository to your GitHub account. 1. **Create a Branch:** Create a new branch from `main` for your work (e.g., `git checkout -b feat/add-new-feature`). 1. **Write Code:** Implement your changes, adhering to the project's coding standards. 1. **Write Tests:** Add unit tests or integration tests for your changes to ensure stability. @@ -526,18 +524,18 @@ Before you start coding, please refer to our [Development Guide](DEVELOPING.md) ## Licensing -By contributing to `project_name`, you agree that your contributions will be licensed under its [Apache 2.0 License](LICENSE). +By contributing to `template-python`, you agree that your contributions will be licensed under its [Apache 2.0 License](LICENSE). ## File: SECURITY.md -# Security Policy for `project_name` +# Security Policy for `template-python` -We take the security of `project_name` seriously. This document outlines our security policies, supported versions, and how to responsibly disclose a vulnerability. +We take the security of `template-python` seriously. This document outlines our security policies, supported versions, and how to responsibly disclose a vulnerability. ## Supported Versions -Please check the table below for the versions of `project_name` that are currently being supported with security updates. +Please check the table below for the versions of `template-python` that are currently being supported with security updates. | Version | Supported | | :------------------------------ | :----------------- | @@ -553,7 +551,7 @@ Please check the table below for the versions of `project_name` that are current If you discover a security vulnerability, please bring it to our attention right away using one of the following methods: -1. **GitHub Security Advisories (Preferred):** Use the "Report a vulnerability" button on the **[Security tab](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/security/advisories)** of this repository. +1. **GitHub Security Advisories (Preferred):** Use the "Report a vulnerability" button on the **[Security tab](https://github.com/markurtz/template-python/security/advisories)** of this repository. 1. **Email:** Send your report directly to **[INSERT EMAIL ADDRESS OR SECURITY CONTACT]**. *(Optional: Encrypt your email using our PGP key: [INSERT PGP KEY LINK/FINGERPRINT])* ### What to Include in Your Report @@ -564,7 +562,7 @@ To help us resolve the issue quickly, please include the following information: - **Detailed description** of the vulnerability and its potential impact. - **Step-by-step instructions** to reproduce the issue. - **Proof of Concept (PoC)** code or screenshots, if available. -- **Environment details** (e.g., version of `project_name`, OS, Python version, relevant configurations). +- **Environment details** (e.g., version of `template-python`, OS, Python version, relevant configurations). ## Triage and Resolution Process @@ -579,23 +577,23 @@ We will handle your report with strict confidentiality. Our process is as follow **In Scope:** -- Vulnerabilities within the core `project_name` codebase. +- Vulnerabilities within the core `template-python` codebase. - Security issues resulting from our default configurations or execution paths. **Out of Scope:** - Theoretical issues without a reproducible PoC. -- Vulnerabilities in third-party dependencies that are not exploitable through `project_name`. -- Issues requiring the victim to intentionally clone and run `project_name` against a malicious, untrusted Git repository, unless it leads to unexpected system compromise beyond the expected permissions. +- Vulnerabilities in third-party dependencies that are not exploitable through `template-python`. +- Issues requiring the victim to intentionally clone and run `template-python` against a malicious, untrusted Git repository, unless it leads to unexpected system compromise beyond the expected permissions. *(Note: We currently do not operate a bug bounty program. Disclosures are greatly appreciated but are not eligible for financial rewards at this time.)* ## File: SUPPORT.md -# Support for `project_name` +# Support for `template-python` -We are excited to have you use `project_name`! If you need help, please follow these guidelines to ensure you get support quickly and efficiently. +We are excited to have you use `template-python`! If you need help, please follow these guidelines to ensure you get support quickly and efficiently. ## Security Vulnerabilities @@ -608,9 +606,9 @@ If you have found a security vulnerability, please refer to our [Security Policy Before reaching out, we recommend checking the following resources. Many common questions and issues are already covered there. -- **[Official Documentation](https://%7B%7Borganization%7D%7D.github.io/%7B%7Bproject_name%7D%7D):** Comprehensive guides, tutorials, and API references. -- **[GitHub Issues](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/issues):** Search existing issues to see if someone else has already reported your problem or requested your feature. Feel free to add a "+1" reaction to existing issues to show your interest. -- **[GitHub Discussions](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/discussions):** Search our discussions for Q&A, general advice, and community knowledge. +- **[Official Documentation](https://markurtz.github.io/template-python):** Comprehensive guides, tutorials, and API references. +- **[GitHub Issues](https://github.com/markurtz/template-python/issues):** Search existing issues to see if someone else has already reported your problem or requested your feature. Feel free to add a "+1" reaction to existing issues to show your interest. +- **[GitHub Discussions](https://github.com/markurtz/template-python/discussions):** Search our discussions for Q&A, general advice, and community knowledge. ## Opening a New Issue @@ -618,9 +616,9 @@ If you cannot find an answer in the documentation or existing issues, please ope | Issue Type | Venue | Description | | :--------------------- | :--------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------- | -| **Bug Report** | [GitHub Issues](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/issues/new) | Use the "Bug Report" template. Provide reproducible steps, environment details, and relevant logs. | -| **Feature Request** | [GitHub Issues](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/issues/new) | Use the "Feature Request" template. Clearly describe your use case and the problem the feature would solve. | -| **Q&A / General Help** | [GitHub Discussions](https://github.com/%7B%7Borganization%7D%7D/%7B%7Bproject_name%7D%7D/discussions/new) | Start a discussion for questions about how to use `project_name`, architecture queries, or advice. | +| **Bug Report** | [GitHub Issues](https://github.com/markurtz/template-python/issues/new) | Use the "Bug Report" template. Provide reproducible steps, environment details, and relevant logs. | +| **Feature Request** | [GitHub Issues](https://github.com/markurtz/template-python/issues/new) | Use the "Feature Request" template. Clearly describe your use case and the problem the feature would solve. | +| **Q&A / General Help** | [GitHub Discussions](https://github.com/markurtz/template-python/discussions/new) | Start a discussion for questions about how to use `template-python`, architecture queries, or advice. | Feel free to join the conversation on GitHub Discussions to connect with other users and maintainers. @@ -628,4 +626,4 @@ Feel free to join the conversation on GitHub Discussions to connect with other u -At this time, there is no official commercial support available for `project_name`. Support is provided on a best-effort basis by the open-source community and maintainers. +At this time, there is no official commercial support available for `template-python`. Support is provided on a best-effort basis by the open-source community and maintainers. diff --git a/llms.txt b/llms.txt index c964fa0..d5715cf 100644 --- a/llms.txt +++ b/llms.txt @@ -1,20 +1,20 @@ -# project_name +# template-python > An opinionated, production-ready Apache 2.0 template repository for bootstrapping modern software projects. -`project_name` is designed to eliminate boilerplate and enforce consistency across an organization's repositories. +`template-python` is designed to eliminate boilerplate and enforce consistency across an organization's repositories. For comprehensive context and full documentation, please see [llms-full.txt](llms-full.txt). ## Docs -- [Home](https://{{organization}}.github.io/project_name/): Documentation site home page -- [Getting Started](https://{{organization}}.github.io/project_name/getting-started/): Installation, quickstart, and workflow guides -- [Guides](https://{{organization}}.github.io/project_name/guides/): How-to guides for common tasks -- [API Reference](https://{{organization}}.github.io/project_name/reference/): Full API reference documentation +- [Home](https://markurtz.github.io/template-python/): Documentation site home page +- [Getting Started](https://markurtz.github.io/template-python/getting-started/): Installation, quickstart, and workflow guides +- [Guides](https://markurtz.github.io/template-python/guides/): How-to guides for common tasks +- [API Reference](https://markurtz.github.io/template-python/reference/): Full API reference documentation ## Repository Files -- [README.md](https://github.com/{{organization}}/project_name/blob/main/README.md): Project overview and quick start -- [AGENTS.md](https://github.com/{{organization}}/project_name/blob/main/AGENTS.md): AI agent coding instructions -- [DEVELOPING.md](https://github.com/{{organization}}/project_name/blob/main/DEVELOPING.md): Developer setup guide +- [README.md](https://github.com/markurtz/template-python/blob/main/README.md): Project overview and quick start +- [AGENTS.md](https://github.com/markurtz/template-python/blob/main/AGENTS.md): AI agent coding instructions +- [DEVELOPING.md](https://github.com/markurtz/template-python/blob/main/DEVELOPING.md): Developer setup guide ## Optional -- [Full Documentation](https://{{organization}}.github.io/project_name/llms-full.txt): The complete concatenated documentation for project_name +- [Full Documentation](https://markurtz.github.io/template-python/llms-full.txt): The complete concatenated documentation for template-python diff --git a/mkdocs.yml b/mkdocs.yml index 2044ec3..a1b407c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,26 +1,26 @@ --- -site_name: "{{project_name}}" -site_description: "{{project_description}}" -site_author: "{{org_name}}" -site_url: "https://{{org_name}}.github.io/{{project_name}}/" -repo_url: "https://github.com/{{org_name}}/{{project_name}}" -repo_name: "{{org_name}}/{{project_name}}" +site_name: "template-python" +site_description: "An opinionated, production-ready Apache 2.0 template repository for bootstrapping\ + \ modern software projects." +site_author: "markurtz" +site_url: "https://markurtz.github.io/template-python/" +repo_url: "https://github.com/markurtz/template-python" +repo_name: "markurtz/template-python" edit_uri: edit/main/docs/ -# --------------------------------------------------------------------------- -# Template Variables — Update these when using this template. -# All {{ variable }} references across the docs site are driven from here. -# --------------------------------------------------------------------------- extra: - project_name: "project_name" - project_description: "{{project_description}}" - org_name: "{{organization}}" - org_email: "conduct@{{organization}}.com" - docs_url: "https://{{org_name}}.github.io/{{project_name}}" + version: + provider: mike + project_name: "template_python" + project_description: "An opinionated, production-ready Apache 2.0 template repository for\ + \ bootstrapping modern software projects." + org_name: "markurtz" +# org_email: "conduct@example.com" + docs_url: "https://markurtz.github.io/template-python" min_python: "3.9" current_major_version: "0" - slack_url: "https://slack.{{organization}}.org" - blog_url: "https://blog.{{organization}}.org" - roadmap_url: "https://github.com/{{organization}}/project_name/milestones" +# slack_url: "https://slack.example.com" +# blog_url: "https://blog.example.com" + roadmap_url: "https://github.com/markurtz/template-python/milestones" theme: name: material logo: assets/branding/icon-white.svg @@ -97,6 +97,11 @@ markdown_extensions: - toc: permalink: true plugins: + - gen-files: + scripts: + - docs/scripts/gen_ref_pages.py + - literate-nav: + nav_file: SUMMARY.md - macros - mike - minify: diff --git a/pyproject.toml b/pyproject.toml index abbc80f..b171710 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,14 +3,23 @@ requires = ["hatchling", "gitversioned[hatchling]"] build-backend = "hatchling.build" [project] -name = "project_name" +name = "template-python" dynamic = ["version"] -description = "{{project_description}}" +description = "An opinionated, production-ready Apache 2.0 template repository for bootstrapping modern software projects." readme = "README.md" requires-python = ">=3.10" license = { text = "Apache-2.0" } -authors = [{ name = "{{organization}}" }] +authors = [{ name = "markurtz" }] +classifiers = [ + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", +] dependencies = [ + "click>=8.0.0", "loguru>=0.7.0", "pydantic>=2.0.0", "pydantic-settings>=2.0.0", @@ -49,6 +58,8 @@ docs = [ "mike~=2.2", "mkdocs~=1.6", "mkdocstrings[python]~=1.0", + "mkdocs-gen-files~=0.5", + "mkdocs-literate-nav~=0.6", "mkdocs-macros-plugin~=1.5", "mkdocs-material~=9.7", "mkdocs-minify-plugin~=0.8", @@ -58,12 +69,13 @@ docs = [ ] [project.scripts] -project_name = "project_name.__main__:main" +template-python = "template_python.__main__:main" [project.urls] -Homepage = "https://github.com/{{org_name}}/{{project_name}}" -Repository = "https://github.com/{{org_name}}/{{project_name}}.git" -Issues = "https://github.com/{{org_name}}/{{project_name}}/issues" +Homepage = "https://github.com/markurtz/template-python" +Repository = "https://github.com/markurtz/template-python.git" +Issues = "https://github.com/markurtz/template-python/issues" +Documentation = "https://markurtz.github.io/template-python/" [tool.hatch.version] source = "gitversioned" @@ -163,12 +175,12 @@ exclude = [ ] [tool.hatch.build.targets.wheel] -packages = ["src/project_name"] +packages = ["src/template_python"] # --- Tool Configurations --- [tool.mypy] -files = ["src/project_name", "tests"] +files = ["src/template_python", "tests"] python_version = "3.10" warn_redundant_casts = true warn_unused_ignores = false @@ -181,7 +193,7 @@ ignore_missing_imports = true [tool.ruff] line-length = 88 indent-width = 4 -exclude = ["build", "dist", "env", ".venv", "src/project_name/version.py"] +exclude = ["build", "dist", "env", ".venv", "src/template_python/version.py"] [tool.ruff.format] quote-style = "double" @@ -278,7 +290,7 @@ select = [ ] [tool.ruff.lint.isort] -known-first-party = ["project_name", "tests"] +known-first-party = ["template_python", "tests"] [tool.pytest.ini_options] addopts = "-s -vvv --cache-clear" @@ -286,10 +298,7 @@ asyncio_mode = "auto" markers = [ "smoke: quick tests to check basic functionality", "sanity: detailed tests to ensure major functions work correctly", - "regression: tests to ensure that new changes do not break existing functionality", - "e2e: end-to-end integration tests", - "unit: unit tests", - "integration: integration tests" + "regression: tests to ensure that new changes do not break existing functionality" ] testpaths = ["tests"] diff --git a/scripts/README.md b/scripts/README.md index 4e853c7..ca384dc 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,6 +1,6 @@ -# `project_name` - Utility Scripts +# Utility Scripts -This directory contains utility scripts designed to assist with local development, maintenance, and automation tasks for `project_name`. +This directory contains utility scripts designed to assist with local development, maintenance, and automation tasks. > [!NOTE] > These scripts are intended for developer and CI/CD use only and are **not** distributed as part of the final application package. @@ -15,10 +15,10 @@ Scripts should generally be executed from the root of the repository to ensure r ```bash # Example -./scripts/setup_dev_env.sh +uv run scripts/bootstrap.py ``` -Ensure the script has execution permissions before running: +Ensure Bash scripts have execution permissions before running: ```bash chmod +x scripts/.sh @@ -28,7 +28,7 @@ chmod +x scripts/.sh If you are adding or modifying a script in this directory, please ensure it adheres to the following best practices: -- **Prefer Bash (`.sh`) or Python (`.py`):** These languages provide the best cross-platform compatibility. +- **Prefer Python (`.py`) or Bash (`.sh`):** Python is preferred for complex logic (managed via `uv run`). Bash is acceptable for simple wrappers. - **Fail Fast (Bash):** Always begin Bash scripts with `set -euo pipefail` to ensure they exit immediately on errors, undefined variables, or pipeline failures. - **Environment Variables:** If your script requires secrets or environment-specific configurations, document them at the top of the script and ensure they align with the `.env.example` file. - **Provide Help:** Scripts should ideally accept a `-h` or `--help` flag that outputs usage instructions. @@ -36,10 +36,9 @@ If you are adding or modifying a script in this directory, please ensure it adhe ## Available Scripts -| Script | Description | -| :----------------- | :--------------------------------------------------------------------- | -| `setup_dev_env.sh` | *(Example)* Bootstraps the local development environment. | -| `release.sh` | *(Example)* Automates the version bumping and release tagging process. | +| Script | Description | +| :------------- | :----------------------------------------------------------------------------------------------------------------------- | +| `bootstrap.py` | Interactively bootstraps a new repository from this template by replacing placeholders and configuring project features. | ## Contributing diff --git a/scripts/bootstrap.py b/scripts/bootstrap.py new file mode 100644 index 0000000..2fea3c6 --- /dev/null +++ b/scripts/bootstrap.py @@ -0,0 +1,897 @@ +#!/usr/bin/env python3 +# /// script +# requires-python = ">=3.10" +# dependencies = [ +# "click", +# "pydantic", +# ] +# /// +""" +Template Initialization Script. + +Automatically replaces all template placeholders with your actual project values +by providing a set of variables either via the CLI or an interactive prompt. + +Example: + uv run scripts/bootstrap.py --organization my-org --repository my-repo +""" + +from __future__ import annotations + +import datetime +import os +import re +import subprocess +import sys +from pathlib import Path, PurePosixPath +from re import Pattern +from typing import Annotated, Any + +import click +from pydantic import BaseModel, ConfigDict, Field + +__all__ = ["IS_INTERACTIVE", "BootstrapConfig", "ReplacementRule", "main"] + +IS_INTERACTIVE: Annotated[ + bool, + "Determines if the script is running in an interactive TTY session " + "without any CI or test constraints.", +] = ( + sys.stdin.isatty() + and not os.environ.get("PYTEST_CURRENT_TEST") + and not os.environ.get("CI") +) + +DEFAULT_GLOBAL_BLACKLIST: Annotated[ + tuple[str, ...], + "Default glob patterns to universally ignore during template replacement.", +] = ( + ".git/**", + ".venv/**", + ".tox/**", + "__pycache__/**", + "node_modules/**", + "site/**", + "build/**", + "dist/**", + ".pytest_cache/**", + ".mypy_cache/**", + ".ruff_cache/**", + "scripts/bootstrap.py", + "*.pyc", + "*.png", + "*.svg", + "*.jpg", + "*.jpeg", + "*.ico", + "*.woff", + "*.woff2", + "*.ttf", + "*.eot", +) + + +class ReplacementRule(BaseModel): + """ + Defines a single replacement operation during bootstrapping. + + This class encapsulates the logic for a single search and replace operation, + allowing for both exact string matching and regular expression substitution + across the targeted repository files. + + Example: + rule = ReplacementRule( + search="old_text", + replace="new_text", + whitelist_globs=["*.md"] + ) + """ + + model_config = ConfigDict(arbitrary_types_allowed=True) + + search: str | Pattern[str] = Field( + description=( + "The exact text or compiled regex pattern to search " + "for within file content." + ) + ) + replace: Any = Field( + description=( + "The replacement string or a callable for regex substitution operations." + ) + ) + whitelist_globs: list[str] | None = Field( + default=None, + description=( + "Optional list of glob patterns; if provided, only files " + "matching these globs are processed." + ), + ) + blacklist_globs: list[str] | None = Field( + default=None, + description=( + "Optional list of glob patterns; files matching these " + "globs are explicitly ignored." + ), + ) + + +class BootstrapConfig(BaseModel): + """ + Configuration state for the repository bootstrap process. + + This class acts as the central data structure holding all the necessary + variables used to template the repository. It includes factory methods + to evaluate default values and interactively prompt users when needed. + + Example: + config = BootstrapConfig.create(organization="my-org", repository="my-repo") + config.apply(Path("."), rules) + """ + + organization: str = Field( + description="The GitHub organization or username owning the repository." + ) + repository: str = Field(description="The name of the GitHub repository.") + project_name: str = Field( + description="The Python package and project name, typically using underscores." + ) + project_desc: str = Field( + description="A short description of the project used in README and packaging." + ) + env_prefix: str = Field( + description=( + "The prefix used for environment variables, typically " + "uppercase project name." + ) + ) + disable_github_discussions: bool = Field( + description="Flag to disable GitHub Discussions links and workflows." + ) + disable_github_issues: bool = Field( + description="Flag to disable GitHub Issues links and workflows." + ) + disable_github_roadmap: bool = Field( + description="Flag to disable GitHub Roadmap links and workflows." + ) + disable_pypi: bool = Field( + description="Flag to disable PyPI publishing workflows and badges." + ) + docs_url: str | None = Field( + description="The absolute URL where project documentation is hosted." + ) + maintainer: str = Field( + description="The primary maintainer or author of the project." + ) + support_email: str = Field( + description=( + "The primary contact email for support and code of conduct inquiries." + ) + ) + slack_url: str = Field( + description="The Slack community URL to display in documentation." + ) + blog_url: str = Field( + description="The blog or website URL to display in documentation." + ) + release_date: str = Field( + description="The initial release date formatted as YYYY-MM-DD." + ) + global_blacklist: list[str] = Field( + description="A list of glob patterns to universally ignore during replacements." + ) + + @classmethod + def create(cls, **kwargs: Any) -> BootstrapConfig: + """ + Evaluates the provided configuration, injecting defaults and prompting + where necessary. + + This factory method constructs the configuration object by evaluating + provided arguments, attempting to guess defaults via git configuration, + and optionally prompting the user interactively if the session allows it. + + :param kwargs: The initial keyword arguments provided via CLI or scripts. + :return: A fully populated configuration instance. + """ + git_org, git_repo = cls._get_git_origin() + default_org = git_org or "markurtz" + default_repo = git_repo or Path.cwd().name + default_maintainer = cls._get_git_config("user.name") or default_org + default_desc = ( + "An opinionated, production-ready Apache 2.0 template repository " + "for bootstrapping modern software projects." + ) + today = datetime.date.today() + default_date = today.isoformat() + + if IS_INTERACTIVE: + click.echo("--- Template Initialization ---") + click.echo("Please confirm or provide the following values.") + + organization = ( + cls._prompt_if_none(kwargs.get("organization"), "Organization", default_org) + or default_org + ) + repository = ( + cls._prompt_if_none(kwargs.get("repository"), "Repository", default_repo) + or default_repo + ) + + default_project_name = ( + repository.replace("-", "_") if repository else "template_python" + ) + project_name = ( + cls._prompt_if_none( + kwargs.get("project_name"), "Project Name", default_project_name + ) + or default_project_name + ) + + default_env_prefix = project_name.upper() if project_name else "TEMPLATE_PYTHON" + env_prefix = ( + cls._prompt_if_none( + kwargs.get("env_prefix"), "Environment Prefix", default_env_prefix + ) + or default_env_prefix + ) + + project_desc = ( + cls._prompt_if_none( + kwargs.get("project_desc"), "Project Description", default_desc + ) + or default_desc + ) + + if not default_maintainer or default_maintainer == default_org: + default_maintainer = organization + + default_email = "" + + maintainer = ( + cls._prompt_if_none( + kwargs.get("maintainer"), "Maintainer", default_maintainer + ) + or default_maintainer + ) + + support_email = cls._prompt_if_none( + kwargs.get("support_email"), "Support Email", default_email + ) + + slack_url = cls._prompt_if_none( + kwargs.get("slack_url"), "Slack URL (leave blank to omit)", "" + ) + blog_url = cls._prompt_if_none( + kwargs.get("blog_url"), "Blog URL (leave blank to omit)", "" + ) + release_date = ( + cls._prompt_if_none( + kwargs.get("release_date"), "Release Date", default_date + ) + or default_date + ) + + disable_github_discussions = cls._prompt_if_none( + kwargs.get("disable_github_discussions"), + "Disable GitHub Discussions?", + False, + is_bool=True, + ) + disable_github_issues = cls._prompt_if_none( + kwargs.get("disable_github_issues"), + "Disable GitHub Issues?", + False, + is_bool=True, + ) + disable_github_roadmap = cls._prompt_if_none( + kwargs.get("disable_github_roadmap"), + "Disable GitHub Roadmap?", + False, + is_bool=True, + ) + disable_pypi = cls._prompt_if_none( + kwargs.get("disable_pypi"), "Disable PyPI Publishing?", False, is_bool=True + ) + + default_docs_url = ( + f"https://{organization}.github.io/{repository}/" + if organization and repository + else "https://example.com/docs/" + ) + docs_url_value = kwargs.get("docs_url") + if docs_url_value is None and not IS_INTERACTIVE: + docs_url_value = default_docs_url + + docs_url = cls._prompt_if_none( + docs_url_value, "Docs URL (leave blank to disable)", default_docs_url + ) + if docs_url is not None and str(docs_url).strip() == "": + docs_url = None + + init_kwargs = { + "organization": organization, + "repository": repository, + "project_name": project_name, + "project_desc": project_desc, + "env_prefix": env_prefix, + "disable_github_discussions": disable_github_discussions, + "disable_github_issues": disable_github_issues, + "disable_github_roadmap": disable_github_roadmap, + "disable_pypi": disable_pypi, + "docs_url": docs_url, + "maintainer": maintainer, + "support_email": support_email, + "slack_url": slack_url, + "blog_url": blog_url, + "release_date": release_date, + "global_blacklist": ( + kwargs.get("global_blacklist") or list(DEFAULT_GLOBAL_BLACKLIST) + ), + } + + return cls(**init_kwargs) + + @property + def release_year(self) -> str: + """ + Retrieves the year from the configured release date. + + :return: A four-digit year string based on the release_date. + """ + return ( + self.release_date.split("-")[0] + if "-" in self.release_date + else str(datetime.date.today().year) + ) + + def apply(self, repo_root: Path, rules: list[ReplacementRule]) -> None: + """ + Applies all bootstrap replacements and filesystem operations to the + targeted repository. + + This method coordinates the execution of all provided replacement rules + across the targeted directory structure, applying renames and cleaning + up the bootstrap scripts themselves upon successful completion. + + :param repo_root: The root path of the repository to bootstrap. + :param rules: A list of configured replacement rules to execute. + """ + if IS_INTERACTIVE: + click.echo("\nApplying replacements...") + + for file_path in repo_root.rglob("*"): + self._apply_replacements(file_path, rules) + + # Rename src/template_python directory + old_pkg_dir = repo_root / "src" / "template_python" + new_pkg_dir = repo_root / "src" / self.project_name + if old_pkg_dir.exists() and old_pkg_dir != new_pkg_dir: + old_pkg_dir.rename(new_pkg_dir) + + if IS_INTERACTIVE: + click.echo("\nBootstrap complete! Check the repository for changes.") + + if click.confirm( + "\nWARNING: Do you want to finalize the bootstrap process? " + "This will permanently DELETE scripts/bootstrap.py " + "and tests/e2e/test_bootstrap.py.", + default=True, + ): + script_path = repo_root / "scripts" / "bootstrap.py" + test_path = repo_root / "tests" / "e2e" / "test_bootstrap.py" + unit_test_path = repo_root / "tests" / "unit" / "test_bootstrap.py" + if script_path.exists(): + script_path.unlink() + if test_path.exists(): + test_path.unlink() + if unit_test_path.exists(): + unit_test_path.unlink() + click.echo("Bootstrap scripts deleted.") + + @classmethod + def _get_git_config(cls, key: str) -> str | None: + """Gets a value from git config.""" + try: + result = subprocess.run( # noqa: S603 + ["git", "config", "--get", key], # noqa: S607 + capture_output=True, + text=True, + check=False, + ) + if result.returncode == 0 and result.stdout.strip(): + return result.stdout.strip() + except Exception as exception: # noqa: BLE001 + click.secho( + f"Warning: Could not get git config '{key}': {exception}", + fg="yellow", + err=True, + ) + return None + + @classmethod + def _get_git_origin(cls) -> tuple[str | None, str | None]: + """Gets organization and repository from git remote origin url.""" + remote_url = cls._get_git_config("remote.origin.url") + if not remote_url: + return None, None + + pattern = re.compile( + r"^(?:https?://[^/]+/|git@[^:]+:)([^/]+)/([^/.]+)(?:\.git)?$" + ) + match = pattern.match(remote_url) + if match: + return match.group(1), match.group(2) + + return None, None + + @classmethod + def _prompt_if_none( + cls, value: Any, prompt_text: str, default_value: Any, is_bool: bool = False + ) -> Any: + """Prompts the user if the value was not provided via CLI.""" + if value is not None: + return value + + if not IS_INTERACTIVE: + return default_value + + try: + if is_bool: + return click.confirm(prompt_text, default=default_value) + return click.prompt(prompt_text, default=default_value) + except EOFError: + return default_value + + def _matches_glob(self, path: Path, patterns: list[str]) -> bool: + path_matcher = PurePosixPath(path.as_posix()) + for pattern in patterns: + if path_matcher.match(pattern): + return True + if pattern.endswith("/**"): + directory_pattern = pattern[:-3] + if directory_pattern in path.parts: + return True + return False + + def _should_bootstrap_file( + self, + file_path: Path, + whitelist: list[str] | None = None, + blacklist: list[str] | None = None, + ) -> bool: + """Checks whether the file should be targeted for a specific rule.""" + if blacklist and self._matches_glob(file_path, blacklist): + return False + + return not whitelist or self._matches_glob(file_path, whitelist) + + def _apply_replacements( + self, file_path: Path, rules: list[ReplacementRule] + ) -> bool: + """Applies a list of replacement rules to the specified file.""" + if not file_path.is_file() or self._matches_glob( + file_path, self.global_blacklist + ): + return False + + try: + content = file_path.read_text(encoding="utf-8") + except UnicodeDecodeError: + return False + + original_content = content + + for rule in rules: + if not self._should_bootstrap_file( + file_path, rule.whitelist_globs, rule.blacklist_globs + ): + continue + + if isinstance(rule.search, Pattern): + content = rule.search.sub(rule.replace, content) + elif rule.replace is not None and rule.search != rule.replace: + content = content.replace(rule.search, rule.replace) + + if content != original_content: + file_path.write_text(content, encoding="utf-8") + return True + + return False + + +@click.command() +@click.option( + "--organization", + default=None, + help=( + "The GitHub organization or user account that will own the repository. " + "If not provided, attempts to infer from git remote or defaults to 'markurtz'." + ), +) +@click.option( + "--repository", + default=None, + help=( + "The exact name of the GitHub repository. " + "If not provided, attempts to infer from git remote or defaults " + "to the current working directory." + ), +) +@click.option( + "--project-name", + default=None, + help=( + "The Python package name (typically using underscores instead of hyphens). " + "If not provided, defaults to the repository name with hyphens replaced " + "by underscores." + ), +) +@click.option( + "--project-desc", + default=None, + help=( + "A short, one-sentence description of the project used in README " + "and package metadata. If not provided, it will be asked interactively." + ), +) +@click.option( + "--env-prefix", + default=None, + help=( + "The prefix used for environment variables parsed by Pydantic settings. " + "If not provided, defaults to the uppercase version of the project name." + ), +) +@click.option( + "--disable-github-discussions", + is_flag=True, + default=None, + help=( + "Removes GitHub Discussions links from the documentation and issues templates. " + "Defaults to false (enabled)." + ), +) +@click.option( + "--disable-github-issues", + is_flag=True, + default=None, + help=( + "Removes GitHub Issues links from the documentation and disables " + "issue tracking configurations. Defaults to false (enabled)." + ), +) +@click.option( + "--disable-github-roadmap", + is_flag=True, + default=None, + help=( + "Removes roadmap milestone links from the documentation. " + "Defaults to false (enabled)." + ), +) +@click.option( + "--disable-pypi", + is_flag=True, + default=None, + help=( + "Disables PyPI publishing workflows and removes PyPI badges from " + "the documentation. Defaults to false (publishing enabled)." + ), +) +@click.option( + "--docs-url", + default=None, + help=( + "The absolute URL where project documentation will be hosted. " + "Leave blank to completely disable documentation site workflows and links. " + "If not provided, defaults to 'https://.github.io//'." + ), +) +@click.option( + "--maintainer", + default=None, + help=( + "The primary maintainer or author of the project. " + "If not provided, attempts to infer from git config or defaults " + "to the organization name." + ), +) +@click.option( + "--support-email", + default=None, + help=( + "The primary contact email for support and code of conduct inquiries. " + "If not provided, defaults to an empty string, removing it and " + "replacing with a generic reference." + ), +) +@click.option( + "--slack-url", + default=None, + help=( + "The Slack community URL to display in documentation. " + "If not provided, defaults to an empty string, removing it." + ), +) +@click.option( + "--blog-url", + default=None, + help=( + "The blog or website URL to display in documentation. " + "If not provided, defaults to an empty string, removing it." + ), +) +@click.option( + "--release-date", + default=None, + help=( + "The initial release date formatted as YYYY-MM-DD. " + "If not provided, defaults to the current date." + ), +) +@click.option( + "--global-blacklist", + multiple=True, + default=DEFAULT_GLOBAL_BLACKLIST, + help=( + "A list of glob patterns to universally ignore during template " + "variable replacement." + ), +) +def main(**kwargs: Any) -> None: # noqa: C901 + """ + Bootstraps the repository by evaluating configuration and applying + template variables. + + This function serves as the primary entrypoint for the CLI. It handles + initialization of the configuration state, defines the core replacement + rules for enabling/disabling various project features, and applies those + rules to the repository. + + :param kwargs: Keyword arguments mapped directly from the click CLI options. + """ + config = BootstrapConfig.create(**kwargs) + + repo_root = Path(__file__).resolve().parent.parent + + # Exact Replacements Map + rules = [] + + def comment_out_match(match: re.Match[str]) -> str: + lines = match.group(0).splitlines(keepends=True) + return "".join([f"# {line}" if line.strip() else line for line in lines]) + + def uncomment_match(match: re.Match[str]) -> str: + lines = match.group(0).splitlines(keepends=True) + return "".join( + [ + line.replace("# ", "", 1) if line.strip().startswith("#") else line + for line in lines + ] + ) + + def remove_html_comments(match: re.Match[str]) -> str: + return match.group(1) + + def html_comment_out(match: re.Match[str]) -> str: + return f"" + + # 1. PyPI Toggle + if not config.disable_pypi: + # Uncomment PyPI + rules.extend( + [ + ReplacementRule( + search=re.compile( + r"", re.DOTALL + ), + replace=remove_html_comments, + whitelist_globs=["README.md"], + ), + ReplacementRule( + search=re.compile( + r"^\s*#\s+- name: Publish to PyPI\n(?:\s*#.*\n)*", re.MULTILINE + ), + replace=uncomment_match, + whitelist_globs=[".github/workflows/*.yml"], + ), + ] + ) + + # 2. Docs Toggle + if config.docs_url: + rules.extend( + [ + ReplacementRule( + search=re.compile(r'(docs_url:\s*)".*?"', re.MULTILINE), + replace=f'\\g<1>"{config.docs_url}"', + whitelist_globs=["mkdocs.yml"], + ), + ReplacementRule( + search=re.compile(r'(Documentation = )".*?"', re.MULTILINE), + replace=f'\\g<1>"{config.docs_url}"', + whitelist_globs=["pyproject.toml"], + ), + ReplacementRule( + search=re.compile( + r'(Documentation)', + re.MULTILINE, + ), + replace=f"\\g<1>{config.docs_url}\\g<2>", + whitelist_globs=["README.md"], + ), + ] + ) + else: + # Comment out Docs blocks + rules.extend( + [ + ReplacementRule( + search=re.compile(r"^(docs_url:.*\n)", re.MULTILINE), + replace=comment_out_match, + whitelist_globs=["mkdocs.yml"], + ), + ReplacementRule( + search=re.compile(r"^(Documentation = .*\n)", re.MULTILINE), + replace=comment_out_match, + whitelist_globs=["pyproject.toml"], + ), + ReplacementRule( + search=re.compile( + r"^(docs\s*=\s*\[\n(?:[\s\S]*?)\]\n)", re.MULTILINE + ), + replace=comment_out_match, + whitelist_globs=["pyproject.toml"], + ), + ReplacementRule( + search=re.compile( + r"^(\[tool\.hatch\.envs\.docs\]\n" + r"(?:[a-zA-Z0-9_\-\.]+\s*=.*\n)*)", + re.MULTILINE, + ), + replace=comment_out_match, + whitelist_globs=["pyproject.toml"], + ), + ReplacementRule( + search=re.compile( + r"^(.*?docs:\s*\n(?:\s+.*\n)*?(?=\s*[a-zA-Z0-9_-]+:\s*\n|\Z))", + re.MULTILINE, + ), + replace=comment_out_match, + whitelist_globs=[".github/workflows/*.yml"], + ), + ReplacementRule( + search=re.compile(r"^(\s*- docs\n)", re.MULTILINE), + replace=comment_out_match, + whitelist_globs=[".github/workflows/*.yml"], + ), + ReplacementRule( + search=re.compile( + r'(Documentation)', re.MULTILINE + ), + replace=html_comment_out, + whitelist_globs=["README.md"], + ), + ] + ) + + # 3. GitHub Feature Toggles + if config.disable_github_discussions: + rules.extend( + [ + ReplacementRule( + search=re.compile( + r'(Discussions)', re.MULTILINE + ), + replace=html_comment_out, + whitelist_globs=["README.md"], + ), + ReplacementRule( + search=re.compile( + r"^(.*and \[Discussions\]\([^)]+\) .*\n)", re.MULTILINE + ), + replace=comment_out_match, + whitelist_globs=[".github/ISSUE_TEMPLATE/*.yml"], + ), + ReplacementRule( + search=re.compile( + r"^(.*For general help and Q&A, please use " + r"\[GitHub Discussions\].*?instead\.\n)", + re.MULTILINE, + ), + replace=comment_out_match, + whitelist_globs=[".github/ISSUE_TEMPLATE/*.yml"], + ), + ] + ) + + if config.disable_github_issues: + rules.extend( + [ + ReplacementRule( + search=re.compile( + r'(Issues)', re.MULTILINE + ), + replace=html_comment_out, + whitelist_globs=["README.md"], + ), + ReplacementRule( + search=re.compile( + r'^(.*Issues = "https://github.com/[^"]+/issues"\n)', + re.MULTILINE, + ), + replace=comment_out_match, + whitelist_globs=["pyproject.toml"], + ), + ] + ) + + if config.disable_github_roadmap: + rules.extend( + [ + ReplacementRule( + search=re.compile( + r'(Roadmap)', re.MULTILINE + ), + replace=html_comment_out, + whitelist_globs=["README.md"], + ), + ReplacementRule( + search=re.compile(r"^(.*roadmap_url:.*\n)", re.MULTILINE), + replace=comment_out_match, + whitelist_globs=["mkdocs.yml"], + ), + ] + ) + + # 4. Optional URLs + if config.slack_url: + rules.append( + ReplacementRule( + search=re.compile(r"^#\s*(slack_url:.*)$", re.MULTILINE), + replace=f' slack_url: "{config.slack_url}"', + whitelist_globs=["mkdocs.yml"], + ) + ) + + if config.blog_url: + rules.append( + ReplacementRule( + search=re.compile(r"^#\s*(blog_url:.*)$", re.MULTILINE), + replace=f' blog_url: "{config.blog_url}"', + whitelist_globs=["mkdocs.yml"], + ) + ) + + rules.extend( + [ + ReplacementRule( + search="{{maintainer_username}}", replace=config.maintainer + ), + ReplacementRule( + search="author = {markurtz}", + replace=f"author = {{{config.maintainer}}}", + ), + ReplacementRule(search="template-python", replace=config.repository), + ReplacementRule(search="template_python", replace=config.project_name), + ReplacementRule(search="TEMPLATE_PYTHON", replace=config.env_prefix), + ReplacementRule(search="markurtz", replace=config.organization), + ReplacementRule( + search=re.compile( + r"An opinionated, production-ready Apache 2\.0 template " + r"repository[\s\S]{0,50}modern software projects\." + ), + replace=config.project_desc, + ), + ReplacementRule(search="conduct@example.com", replace=config.support_email), + ReplacementRule(search="{{year}}", replace=config.release_year), + ] + ) + + config.apply(repo_root, rules) + + +if __name__ == "__main__": + main() diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh deleted file mode 100755 index 258db81..0000000 --- a/scripts/bootstrap.sh +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env bash -# ============================================================================= -# bootstrap.sh — Template Initialization -# -# Automatically replaces all template placeholders (e.g., project_name) -# with your actual project values. -# -# Usage: -# ./scripts/bootstrap.sh --project-name my-app --organization my-org -# ============================================================================= - -set -euo pipefail - -if [[ $# -eq 0 ]] || [[ "$1" == "-h" ]] || [[ "$1" == "--help" ]]; then - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Bootstraps the repository by replacing template variables." - echo "" - echo "Options:" - echo " --project-name NAME Replacement for project_name" - echo " --project-desc DESC Replacement for {{project_description}}" - echo " --organization ORG Replacement for {{organization}}" - echo " --org-name ORG Replacement for {{org_name}}" - echo "" - echo "Example: $0 --project-name my-app --organization my-org" - exit 0 -fi - -PROJECT_NAME="" -PROJECT_DESC="" -ORGANIZATION="" -ORG_NAME="" - -while [[ $# -gt 0 ]]; do - case "$1" in - --project-name) - if [[ $# -lt 2 ]] || [[ "$2" == -* ]]; then - echo "[ERROR] Option $1 requires a value." - echo "Use $0 --help for usage guidance." - exit 1 - fi - PROJECT_NAME="$2" - shift 2 - ;; - --project-desc) - if [[ $# -lt 2 ]] || [[ "$2" == -* ]]; then - echo "[ERROR] Option $1 requires a value." - echo "Use $0 --help for usage guidance." - exit 1 - fi - PROJECT_DESC="$2" - shift 2 - ;; - --organization) - if [[ $# -lt 2 ]] || [[ "$2" == -* ]]; then - echo "[ERROR] Option $1 requires a value." - echo "Use $0 --help for usage guidance." - exit 1 - fi - ORGANIZATION="$2" - shift 2 - ;; - --org-name) - if [[ $# -lt 2 ]] || [[ "$2" == -* ]]; then - echo "[ERROR] Option $1 requires a value." - echo "Use $0 --help for usage guidance." - exit 1 - fi - ORG_NAME="$2" - shift 2 - ;; - *) - echo "[ERROR] Unknown option: $1" - exit 1 - ;; - esac -done - -if [[ -z "$PROJECT_NAME" ]] || [[ -z "$PROJECT_DESC" ]] || [[ -z "$ORGANIZATION" ]] || [[ -z "$ORG_NAME" ]]; then - echo "[ERROR] Missing required arguments." - echo "All of the following options must be provided:" - echo " --project-name" - echo " --project-desc" - echo " --organization" - echo " --org-name" - echo "" - echo "Use $0 --help for usage guidance." - exit 1 -fi - -echo "[INFO] Starting repository bootstrap..." - -export PROJECT_NAME -export PROJECT_DESC -export ORGANIZATION -export ORG_NAME - -python3 -c " -import os, sys, shutil - -project_name = os.environ.get('PROJECT_NAME', '') -python_pkg = project_name.replace('-', '_') - -replacements = { - '{{project_name}}': project_name, - '%7B%7Bproject_name%7D%7D': project_name, - 'template-python': project_name, - 'template_python': python_pkg, - 'project_name': python_pkg, - '{{project_description}}': os.environ.get('PROJECT_DESC', ''), - '%7B%7Bproject_description%7D%7D': os.environ.get('PROJECT_DESC', ''), - '{{organization}}': os.environ.get('ORGANIZATION', ''), - '%7B%7Borganization%7D%7D': os.environ.get('ORGANIZATION', ''), - 'markurtz': os.environ.get('ORGANIZATION', ''), - '{{org_name}}': os.environ.get('ORG_NAME', ''), - '%7B%7Borg_name%7D%7D': os.environ.get('ORG_NAME', '') -} - - -ignore_dirs = {'.git', '.venv', 'node_modules', 'site', '__pycache__', 'assets'} -ignore_exts = ('.pyc', '.png', '.svg', '.jpg', '.jpeg', '.gif', '.ico', '.woff', '.woff2', '.ttf', '.eot') - -for root, dirs, files in os.walk('.'): - dirs[:] = [d for d in dirs if d not in ignore_dirs] - for file in files: - if file.endswith(ignore_exts) or file == 'bootstrap.sh': - continue - - filepath = os.path.join(root, file) - - try: - with open(filepath, 'r', encoding='utf-8') as f: - content = f.read() - except UnicodeDecodeError: - continue - - new_content = content - for placeholder, replacement in replacements.items(): - new_content = new_content.replace(placeholder, replacement) - - if content != new_content: - with open(filepath, 'w', encoding='utf-8') as f: - f.write(new_content) - print(f'[INFO] Updated: {filepath}') - -src_dir = os.path.join('.', 'src', 'project_name') -dst_dir = os.path.join('.', 'src', python_pkg) -if os.path.exists(src_dir) and src_dir != dst_dir: - os.rename(src_dir, dst_dir) - print(f'[INFO] Renamed directory: {src_dir} -> {dst_dir}') - -" - -echo "[INFO] Bootstrap complete! You may now delete this script if desired." diff --git a/src/project_name/__init__.py b/src/template_python/__init__.py similarity index 88% rename from src/project_name/__init__.py rename to src/template_python/__init__.py index bb7fa47..75da1fb 100644 --- a/src/project_name/__init__.py +++ b/src/template_python/__init__.py @@ -1,5 +1,5 @@ """ -Core initialization module for the package. +Core initialization module for the template-python package. This module serves as the primary entry point for the library, exposing the public API components such as logging settings, application configuration, and version diff --git a/src/project_name/__main__.py b/src/template_python/__main__.py similarity index 61% rename from src/project_name/__main__.py rename to src/template_python/__main__.py index 63cae30..c37eff5 100644 --- a/src/project_name/__main__.py +++ b/src/template_python/__main__.py @@ -1,17 +1,17 @@ """ -Main entrypoint for the {{project_name}} package. +Main entrypoint for the template_python package. This module provides the executable routine when the package is run directly -via the command line (e.g., ``python -m project_name``). It initializes the logger +via the command line (e.g., ``python -m template_python``). It initializes the logger and settings, outputting the current version and configuration to verify the installation and environment setup. """ from __future__ import annotations -import sys +import click -from project_name import ( +from template_python import ( LoggingSettings, Settings, __version__, @@ -22,7 +22,9 @@ __all__ = ["main"] -def main() -> int: +@click.command(context_settings={"help_option_names": ["-h", "--help"]}) +@click.version_option(version=__version__, prog_name="template-python") +def main() -> None: """ Execute the main routine. @@ -33,27 +35,22 @@ def main() -> int: Example: .. code-block:: python - import sys - from project_name.__main__ import main + from template_python.__main__ import main - sys.exit(main()) - - :return: The exit code of the execution (0 for success). - :rtype: int + main() """ configure_logger( LoggingSettings( enabled=True, level="INFO", clear_loggers=True, - filter=("project_name", "__main__"), + filter=("template_python", "__main__"), ) ) - logger.info("Hello from {{project_name}} v{}!", __version__) + logger.info("Hello from template-python v{}!", __version__) settings = Settings() logger.info("Settings: {}", settings) - return 0 if __name__ == "__main__": - sys.exit(main()) + main() diff --git a/src/project_name/compat.py b/src/template_python/compat.py similarity index 71% rename from src/project_name/compat.py rename to src/template_python/compat.py index fe0791d..9481eb9 100644 --- a/src/project_name/compat.py +++ b/src/template_python/compat.py @@ -2,10 +2,10 @@ Compatibility abstractions for optional dependencies. This module centralizes fallback logic for safely importing optional dependencies -like ``psutil``, ``opentelemetry``, and TOML parsers. It provides standardized -access points for these modules, avoiding scattered ``try-except`` blocks across -the codebase. Maintainers should import optional dependencies from this module -rather than attempting direct imports elsewhere. +like ``opentelemetry``. It provides standardized access points for these modules, +avoiding scattered ``try-except`` blocks across the codebase. Maintainers should +import optional dependencies from this module rather than attempting direct +imports elsewhere. """ from __future__ import annotations diff --git a/src/project_name/logging.py b/src/template_python/logging.py similarity index 90% rename from src/project_name/logging.py rename to src/template_python/logging.py index 7edf5d0..83330ff 100644 --- a/src/project_name/logging.py +++ b/src/template_python/logging.py @@ -1,9 +1,8 @@ """ -Loguru-based logging configuration and environment settings for project_name. +Loguru-based logging configuration and environment settings for template_python. This module provides a unified interface for configuring application-level logging using loguru and Pydantic settings. It handles dynamic OpenTelemetry formatting, -sink resolution, and configurable log levels to ensure consistent telemetry across the codebase and build environments. """ @@ -20,7 +19,7 @@ from pydantic import Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict -from project_name.compat import opentelemetry_trace +from template_python.compat import opentelemetry_trace __all__ = ["LoggingSettings", "configure_logger", "logger"] @@ -32,13 +31,13 @@ class LoggingSettings(BaseSettings): Settings model for configuring the loguru logging infrastructure. This Pydantic model loads configuration from environment variables prefixed - with ``PROJECT_NAME__LOGGING__`` and provides typed fields for controlling + with ``TEMPLATE_PYTHON__LOGGING__`` and provides typed fields for controlling log output, formatting, and OpenTelemetry integration. Example: .. code-block:: python - from project_name.logging import LoggingSettings, configure_logger + from template_python.logging import LoggingSettings, configure_logger settings = LoggingSettings(enabled=True, level="DEBUG") configure_logger(settings) @@ -47,7 +46,7 @@ class LoggingSettings(BaseSettings): enabled: bool = Field( default=False, description=( - "Whether to enable project_name loguru logging across the application." + "Whether to enable template_python loguru logging across the application." ), ) clear_loggers: bool = Field( @@ -85,7 +84,7 @@ class LoggingSettings(BaseSettings): default=True, description=( "Filters log records. Defaults to True to filter by the " - "'project_name' prefix." + "'template_python' prefix." ), ) enqueue: bool = Field( @@ -100,7 +99,7 @@ class LoggingSettings(BaseSettings): ) model_config: ClassVar[SettingsConfigDict] = SettingsConfigDict( - env_prefix="PROJECT_NAME__LOGGING__", + env_prefix="TEMPLATE_PYTHON__LOGGING__", env_nested_delimiter="__", ) """Pydantic configuration dict dictating environment variable prefixes.""" @@ -136,7 +135,7 @@ def _otel_formatter(record: dict[str, Any]) -> str: "timestamp": record["time"].isoformat(), "severity_text": record["level"].name, "body": record["message"], - "resource": {"service.name": "project_name"}, + "resource": {"service.name": "template_python"}, "attributes": { "module": record["name"], "function": record["function"], @@ -179,7 +178,7 @@ def configure_logger(settings: LoggingSettings | None = None) -> None: Example: .. code-block:: python - from project_name.logging import configure_logger, LoggingSettings + from template_python.logging import configure_logger, LoggingSettings configure_logger(LoggingSettings(level="DEBUG")) @@ -194,10 +193,10 @@ def configure_logger(settings: LoggingSettings | None = None) -> None: settings = settings or LoggingSettings() if not settings.enabled: - logger.disable("project_name") + logger.disable("template_python") return - logger.enable("project_name") + logger.enable("template_python") if settings.clear_loggers: logger.remove() @@ -216,7 +215,7 @@ def configure_logger(settings: LoggingSettings | None = None) -> None: ) log_format = _otel_formatter if use_otel else settings.format - filter_val = "project_name" if settings.filter is True else settings.filter + filter_val = "template_python" if settings.filter is True else settings.filter if isinstance(filter_val, (list, tuple)): prefixes = tuple(filter_val) diff --git a/src/project_name/py.typed b/src/template_python/py.typed similarity index 100% rename from src/project_name/py.typed rename to src/template_python/py.typed diff --git a/src/project_name/settings.py b/src/template_python/settings.py similarity index 93% rename from src/project_name/settings.py rename to src/template_python/settings.py index e8970db..4a06337 100644 --- a/src/project_name/settings.py +++ b/src/template_python/settings.py @@ -1,5 +1,5 @@ """ -Settings configuration for the {{project_name}} application. +Settings configuration for the template-python application. This module provides the primary configuration structure for the application using Pydantic Settings. It aggregates configuration from multiple sources, @@ -32,7 +32,7 @@ class Settings(BaseSettings): Example: .. code-block:: python - from project_name.settings import Settings + from template_python.settings import Settings settings = Settings(environment="production") print(settings.project_root) @@ -43,7 +43,7 @@ class Settings(BaseSettings): extra="ignore", populate_by_name=True, validate_assignment=True, - env_prefix="PROJECT_NAME__", + env_prefix="TEMPLATE_PYTHON__", ) """Pydantic config dict dictating environment prefixes and validation.""" diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..25b0cf9 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,84 @@ +# Testing Guide + +This directory contains the testing suite for `template-python`. We use `pytest` as our testing framework and `hatch` to manage test environments and execution. + +## Test Tiers + +Tests are categorized into three distinct tiers, each located in its respective subdirectory: + +| Test Tier | Directory | Description | +| :-------------- | :------------------- | :------------------------------------------------------------------------------------------------------------------------ | +| **Unit** | `tests/unit/` | Fast, isolated tests for individual functions and classes. These tests should not rely on external services or databases. | +| **Integration** | `tests/integration/` | Slower tests that verify interactions between multiple components or modules within the application. | +| **End-to-End** | `tests/e2e/` | Full-stack tests simulating real user workflows, from entry points to expected outcomes. | + +## Pytest Markers + +We use custom `pytest` markers to categorize test scope and intent. Every test should be decorated with appropriate markers. + +| Marker | Purpose | Example Use Case | +| :------------------------ | :-------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------- | +| `@pytest.mark.smoke` | Quick tests to check basic functionality. | A crucial happy-path test that must pass for the system to be considered fundamentally operational. | +| `@pytest.mark.sanity` | Detailed tests to ensure major functions work correctly. | Testing key business logic and typical user flows. | +| `@pytest.mark.regression` | Tests to ensure that new changes do not break existing functionality. | Tests written specifically to prevent known bugs from reoccurring. | + +> [!NOTE] +> Every test should be decorated with one of the above markers to indicate its role in the testing pipeline. + +## Running Tests + +We recommend using `hatch` to run tests, as it automatically manages the required virtual environments and dependencies. + +### Standard Test Runs + +```bash +# Run all tests +hatch run test:all + +# Run only unit tests +hatch run test:unit + +# Run tests with a specific marker +hatch run test:all -m "smoke" + +# Run tests in a specific file +hatch run test:all tests/unit/test_version.py +``` + +### Coverage Reports + +To generate coverage reports, use the `-cov` suffixed commands. These will output both a terminal report and an HTML report located in `docs/coverage/`. + +```bash +# Run all tests with coverage +hatch run test:all-cov + +# Run only unit tests with coverage +hatch run test:unit-cov +``` + +## Adding New Tests + +When creating new tests, ensure they are placed in the appropriate tier directory (`unit/`, `integration/`, or `e2e/`) and include the necessary markers. + +### Example Unit Test + +```python +"""Unit tests for my_module.""" + +from __future__ import annotations + +import pytest + +from template_python import my_module + + +@pytest.mark.smoke +def test_my_function() -> None: + """Verify my_function behaves as expected.""" + result = my_module.my_function() + assert result is True +``` + +> [!TIP] +> **Type Hints:** Ensure all test functions are fully type-hinted (e.g., `-> None:` for test return types) to satisfy our strict `mypy` configuration. diff --git a/tests/e2e/test_bootstrap.py b/tests/e2e/test_bootstrap.py new file mode 100644 index 0000000..f2c00b9 --- /dev/null +++ b/tests/e2e/test_bootstrap.py @@ -0,0 +1,247 @@ +"""End-to-end tests for the bootstrap.py script.""" + +from __future__ import annotations + +import asyncio +import shutil +import sys +from functools import wraps +from pathlib import Path +from typing import Any + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent)) + +from scripts import bootstrap # noqa: E402 +from scripts.bootstrap import BootstrapConfig, main # noqa: E402 + + +def async_timeout(delay: float) -> Any: + """Decorator to timeout asyncio tests.""" + + def decorator(func: Any) -> Any: + @wraps(func) + async def new_func(*args: Any, **kwargs: Any) -> Any: + return await asyncio.wait_for(func(*args, **kwargs), timeout=delay) + + return new_func + + return decorator + + +@pytest.fixture(autouse=True) +def _mock_interactive(monkeypatch: pytest.MonkeyPatch) -> None: + """Ensure IS_INTERACTIVE is False for all tests to prevent blocking prompts.""" + monkeypatch.setattr(bootstrap, "IS_INTERACTIVE", False) + + +def generate_test_cases() -> list[dict[str, Any]]: + """Generates all test case variations for CLI arguments.""" + cases: list[dict[str, Any]] = [] + + # Default state (all defaults) + cases.append({}) + + # String argument cases + str_args = [ + "organization", + "repository", + "project_name", + "project_desc", + "env_prefix", + "docs_url", + "maintainer", + "support_email", + "slack_url", + "blog_url", + "release_date", + ] + for arg in str_args: + # Empty string case + cases.append({arg: ""}) + # Custom value case + cases.append({arg: f"custom_{arg}"}) + + # Boolean argument cases + bool_args = [ + "disable_github_discussions", + "disable_github_issues", + "disable_github_roadmap", + "disable_pypi", + ] + for arg in bool_args: + cases.append({arg: True}) + + return cases + + +class TestMain: + """Test suite for the main bootstrap function.""" + + @pytest.mark.regression + @pytest.mark.parametrize("cli_arguments", generate_test_cases()) + def test_invocation( + self, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + cli_arguments: dict[str, Any], + ) -> None: + """Tests bootstrap script with various arguments, verifying all files.""" + repository_root = Path(__file__).resolve().parent.parent.parent + target_directory = tmp_path / "repo" + + # Copy repo excluding temp/cache/git directories + shutil.copytree( + repository_root, + target_directory, + ignore=shutil.ignore_patterns( + ".git", + ".venv", + ".tox", + "__pycache__", + "node_modules", + "site", + "build", + "dist", + ".pytest_cache", + ".mypy_cache", + ".ruff_cache", + ), + ) + + class MockPath: + def __init__(self, *args: Any, **kwargs: Any) -> None: + self._path = Path(*args, **kwargs) + + def resolve(self) -> Any: + class MockResolved: + @property + def parent(self) -> Any: + class MockParent: + @property + def parent(self) -> Path: + return target_directory + + return MockParent() + + return MockResolved() + + @classmethod + def cwd(cls) -> Path: + return target_directory + + monkeypatch.setattr(bootstrap, "Path", MockPath) + + # Merge defaults so the test config is fully populated like the click app would + base_args = { + "global_blacklist": bootstrap.main.params[-1].default, + } + test_args = {**base_args, **cli_arguments} + + # Determine expected state + config = BootstrapConfig.create(**test_args) + + # Run script + main.callback(**test_args) # type: ignore[misc] + + self._verify_repo_state(target_directory, config) + + def _verify_repo_state(self, target_dir: Path, config: BootstrapConfig) -> None: + """Thoroughly checks the target directory against the expected state.""" + assert (target_dir / "src" / config.project_name).exists() + if config.project_name != "template_python": + assert not (target_dir / "src" / "template_python").exists() + + placeholders = self._build_placeholders(config) + + for file_path in target_dir.rglob("*"): + self._verify_file(file_path, target_dir, config, placeholders) + + def _build_placeholders(self, config: BootstrapConfig) -> list[str]: + """Builds the list of placeholders expected to be absent.""" + placeholders = ["{{maintainer_username}}"] + if config.repository != "template-python": + placeholders.append("template-python") + if config.project_name != "template_python": + placeholders.append("template_python") + if config.env_prefix != "TEMPLATE_PYTHON": + placeholders.append("TEMPLATE_PYTHON") + if config.support_email != "conduct@example.com": + placeholders.append("conduct@example.com") + if config.project_desc != ( + "An opinionated, production-ready Apache 2.0 template repository " + "for bootstrapping modern software projects." + ): + placeholders.append( + "An opinionated, production-ready Apache 2.0 template repository" + ) + if config.organization != "markurtz" and config.maintainer != "markurtz": + placeholders.append("markurtz") + return placeholders + + def _verify_file( + self, + file_path: Path, + target_dir: Path, + config: BootstrapConfig, + placeholders: list[str], + ) -> None: + """Verifies an individual file for placeholders and specific logic.""" + if not file_path.is_file(): + return + + ignore_dirs = { + ".git", + ".venv", + ".tox", + "__pycache__", + "node_modules", + "site", + "build", + "dist", + ".pytest_cache", + ".mypy_cache", + ".ruff_cache", + } + if any(part in ignore_dirs for part in file_path.parts): + return + + # Skip testing scripts since they contain placeholders + if file_path.name in ("bootstrap.py", "test_bootstrap.py"): + return + + try: + content = file_path.read_text(encoding="utf-8") + except UnicodeDecodeError: + return + + for ph in placeholders: + assert ph not in content, f"Placeholder '{ph}' found in {file_path}" + + self._verify_file_specific_content(file_path, target_dir, config, content) + + def _verify_file_specific_content( + self, file_path: Path, target_dir: Path, config: BootstrapConfig, content: str + ) -> None: + """Verifies specific configuration changes in specific files.""" + if file_path.name == "README.md" and file_path.parent == target_dir: + if config.docs_url: + assert config.docs_url in content + + if config.disable_github_discussions: + assert "