From c05287711c04d6e9ee995302960b7be386372678 Mon Sep 17 00:00:00 2001 From: Mark Kurtz Date: Wed, 13 May 2026 22:21:02 -0400 Subject: [PATCH 01/13] Fix issues around docs versions not showing, coverage reports not being included in docs, and misc incorrect docs and incorrect references for the org and project due to intended replacement --- .github/ISSUE_TEMPLATE/bug_report.yml | 12 +++--- .github/ISSUE_TEMPLATE/feature_request.yml | 6 +-- .github/PULL_REQUEST_TEMPLATE.md | 6 +-- .github/workflows/development.yml | 4 +- .github/workflows/main.yml | 4 +- .github/workflows/nightly.yml | 4 +- .github/workflows/release.yml | 1 + AGENTS.md | 2 +- CODE_OF_CONDUCT.md | 2 +- DEVELOPING.md | 2 +- README.md | 8 +++- docs/community/index.md | 12 +++--- docs/examples/index.md | 6 +-- docs/getting-started/installation.md | 40 +++-------------- docs/getting-started/quickstart.md | 29 +++++++------ docs/guides/index.md | 2 +- docs/guides/repository-setup.md | 2 +- docs/index.md | 27 ++++-------- docs/reference/index.md | 35 ++++++++------- llms-full.txt | 50 +++++++++++----------- llms.txt | 16 +++---- mkdocs.yml | 30 +++++++------ 22 files changed, 139 insertions(+), 161 deletions(-) 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..4398158 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,5 @@ @@ -57,11 +57,11 @@ please add screenshots, terminal output, or recordings to demonstrate the change ## 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..039e287 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: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 222124f..e5f49a3 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" diff --git a/AGENTS.md b/AGENTS.md index 5fd3e62..a386a7b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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., `project_name`, `markurtz`). ## Agent Notes diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 92544c5..a5c1cb5 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -61,7 +61,7 @@ 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**. +> **conduct@markurtz.com**. All complaints will be reviewed and investigated promptly and fairly. diff --git a/DEVELOPING.md b/DEVELOPING.md index cbc9177..5ca0cf8 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -22,7 +22,7 @@ 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 +> git clone https://github.com/markurtz/project_name.git > cd project_name > > # Build and start the development environment in the background diff --git a/README.md b/README.md index 9caf5ca..778c0ec 100644 --- a/README.md +++ b/README.md @@ -92,10 +92,14 @@ This project has just been instantiated from the template repository. Keep an ey ## Quick Start ```bash -pip install template-python +./scripts/bootstrap.sh \ + --project-name my-app \ + --project-desc "My cool application" \ + --organization my-org \ + --org-name my-org ``` -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/)**. +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 diff --git a/docs/community/index.md b/docs/community/index.md index 9cd846c..d58dfbd 100644 --- a/docs/community/index.md +++ b/docs/community/index.md @@ -66,9 +66,9 @@ 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 | +| Slack | Real-time chat with the team and community | +| Blog | Project updates, tutorials, and announcements | 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/installation.md b/docs/getting-started/installation.md index b0a9b20..0ecd4dd 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -13,39 +13,9 @@ 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)" @@ -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 project_name; print(project_name.__version__)" ``` For a persistent, volume-mounted setup using Docker Compose, see the `docker-compose.yml` in the root of the repository. diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 41b19ad..52c3dc4 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -7,7 +7,7 @@ This guide gets you from a fresh installation to running your first command in u ## Step 1 — Initialize Your Environment -If you haven't already, set up your project and install `{{ project_name }}`: +If you haven't already, set up your project and install `template-python`: ```bash python -m venv .venv @@ -18,41 +18,44 @@ pip install project_name ## Step 2 — Verify the Install ```bash -{{ project_name }} --version +project_name ``` Expected output: ```console -{{ project_name }} 0.1.0 +Hello from project_name v0.1.0! +Settings: Settings(environment='development', project_root=PosixPath('...')) ``` ## Step 3 — Run Your First Command ```python -from project_name import Client +from project_name import Settings, configure_logger, logger +from project_name.logging import LoggingSettings -# Initialize the client -client = Client(api_key="YOUR_KEY") +# Initialize the global logger +configure_logger(LoggingSettings(enabled=True, level="INFO")) -# Run a core action -result = client.run_action("hello_world") -print(result) +# Load application settings +settings = Settings(environment="production") + +# Log the current configuration +logger.info("Application initialized with settings: {}", settings) ``` Expected output: ```console -[INFO] Initializing {{ project_name }} client... -[SUCCESS] Action completed! Result: Hello, World from {{ project_name }}! +2026-05-13 12:00:00 | INFO | __main__: - Application initialized with settings: Settings(environment='production', project_root=PosixPath('...')) ``` > [!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 [Reference](../reference/index.md) for all available configuration options. ## Step 4 — Explore Further -Now that your first command works, explore what `{{ project_name }}` can do: +Now that your first command works, explore what `template-python` can do: - **[Guides](../guides/index.md)** — Task-specific deep dives including CI/CD and repository setup - **[Reference](../reference/index.md)** — Full API and CLI documentation 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..7ff3cd7 100644 --- a/docs/guides/repository-setup.md +++ b/docs/guides/repository-setup.md @@ -6,7 +6,7 @@ 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 bootstrap script to automatically replace all placeholder variables (like `project_name` and `markurtz`) with your actual project details. ```bash ./scripts/bootstrap.sh \ diff --git a/docs/index.md b/docs/index.md index 1247b77..61a21c6 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 }}** [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,15 @@ 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 project_name ``` -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-github: GitHub Repository +- :material-map-marker-path: Roadmap - :material-post-outline: Blog - :material-slack: Slack Community diff --git a/docs/reference/index.md b/docs/reference/index.md index 809f9cc..be1ce51 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 @@ -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: +`project_name` 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 project_name import Settings, configure_logger, logger +from project_name.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/llms-full.txt b/llms-full.txt index 59f5a1b..9560868 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -10,18 +10,18 @@ This file contains the concatenated core documentation for the project to provid 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/project_name/): Documentation site home page +- [Getting Started](https://markurtz.github.io/project_name/getting-started/): Installation, quickstart, and workflow guides +- [Guides](https://markurtz.github.io/project_name/guides/): How-to guides for common tasks +- [API Reference](https://markurtz.github.io/project_name/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/project_name/blob/main/README.md): Project overview and quick start +- [AGENTS.md](https://github.com/markurtz/project_name/blob/main/AGENTS.md): AI agent coding instructions +- [DEVELOPING.md](https://github.com/markurtz/project_name/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/project_name/llms-full.txt): The complete concatenated documentation for project_name ## File: README.md @@ -40,8 +40,8 @@ For comprehensive context and full documentation, please see [llms-full.txt](llm

- - GitHub Release + + GitHub Release PyPI Release @@ -51,17 +51,17 @@ For comprehensive context and full documentation, please see [llms-full.txt](llm
- - 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

______________________________________________________________________ @@ -90,7 +90,7 @@ ______________________________________________________________________ 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. -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, simply find and replace all instances of `project_name` and `markurtz` with your actual project details, update the placeholder SVG images in `docs/assets/branding/`, and you are ready to start coding. ### Why Use project_name? @@ -172,12 +172,12 @@ If you use this template or the resulting software in your research, please cite ```bibtex @software{project_name, - author = {{{organization}}}, + author = {markurtz}, title = {project_name}, version = {{{version}}}, month = {{{month}}}, year = {2026}, - url = {https://github.com/{{organization}}/project_name} + url = {https://github.com/markurtz/project_name} } ``` @@ -240,7 +240,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., `project_name`, `markurtz`). ## Agent Notes @@ -273,7 +273,7 @@ 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 +> git clone https://github.com/markurtz/project_name.git > cd project_name > > # Build and start the development environment in the background diff --git a/llms.txt b/llms.txt index c964fa0..7418863 100644 --- a/llms.txt +++ b/llms.txt @@ -6,15 +6,15 @@ 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/project_name/): Documentation site home page +- [Getting Started](https://markurtz.github.io/project_name/getting-started/): Installation, quickstart, and workflow guides +- [Guides](https://markurtz.github.io/project_name/guides/): How-to guides for common tasks +- [API Reference](https://markurtz.github.io/project_name/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/project_name/blob/main/README.md): Project overview and quick start +- [AGENTS.md](https://github.com/markurtz/project_name/blob/main/AGENTS.md): AI agent coding instructions +- [DEVELOPING.md](https://github.com/markurtz/project_name/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/project_name/llms-full.txt): The complete concatenated documentation for project_name diff --git a/mkdocs.yml b/mkdocs.yml index 2044ec3..4e277d2 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,26 +1,30 @@ --- -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: + version: + provider: mike 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}}" + project_description: "An opinionated, production-ready Apache 2.0 template repository for\ + \ bootstrapping modern software projects." + org_name: "markurtz" + org_email: "conduct@markurtz.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.markurtz.org" + blog_url: "https://blog.markurtz.org" + roadmap_url: "https://github.com/markurtz/project_name/milestones" theme: name: material logo: assets/branding/icon-white.svg From 2466acafe599a9d087204a7c1063699d200bd836 Mon Sep 17 00:00:00 2001 From: Mark Kurtz Date: Thu, 14 May 2026 14:56:56 -0400 Subject: [PATCH 02/13] Fixes around documentation and general fixes for boostrapping the project to ensure it is easy and foolproof to do and generates a usable repo while also ensuring template-python is a usable repo on its own --- .editorconfig | 2 +- .env.example | 6 +- .github/PULL_REQUEST_TEMPLATE.md | 12 +- .github/workflows/nightly.yml | 9 +- .github/workflows/release.yml | 9 +- .gitignore | 2 +- AGENTS.md | 4 +- CITATION.cff | 9 +- CODE_OF_CONDUCT.md | 4 +- CONTRIBUTING.md | 12 +- DEVELOPING.md | 6 +- Dockerfile | 10 +- NOTICE | 8 +- README.md | 21 +- SECURITY.md | 18 +- SUPPORT.md | 22 +- docker-compose.yml | 4 +- docs/community/index.md | 8 +- docs/getting-started/index.md | 6 +- docs/getting-started/installation.md | 18 +- docs/getting-started/quickstart.md | 12 +- docs/guides/github-workflows.md | 2 +- docs/guides/repository-setup.md | 11 +- docs/index.md | 10 +- docs/reference/api.md | 13 - docs/reference/index.md | 8 +- docs/scripts/gen_ref_pages.py | 76 ++ examples/README.md | 8 +- examples/example_template/README.md | 6 +- examples/example_template/main.py | 8 +- llms-full.txt | 140 +-- llms.txt | 20 +- mkdocs.yml | 19 +- pyproject.toml | 39 +- scripts/README.md | 4 +- scripts/bootstrap.py | 867 ++++++++++++++++++ scripts/bootstrap.sh | 154 ---- .../__init__.py | 2 +- .../__main__.py | 27 +- .../compat.py | 8 +- .../logging.py | 25 +- .../py.typed | 0 .../settings.py | 6 +- tests/README.md | 84 ++ tests/e2e/test_bootstrap.py | 210 +++++ tests/e2e/test_main.py | 13 +- tests/integration/test_integration.py | 12 +- tests/unit/test_compat.py | 3 +- tests/unit/test_init.py | 7 +- tests/unit/test_logging.py | 16 +- tests/unit/test_settings.py | 10 +- tests/unit/test_version.py | 13 +- uv.lock | 268 ++++-- 53 files changed, 1733 insertions(+), 558 deletions(-) delete mode 100644 docs/reference/api.md create mode 100644 docs/scripts/gen_ref_pages.py create mode 100644 scripts/bootstrap.py delete mode 100755 scripts/bootstrap.sh rename src/{project_name => template_python}/__init__.py (88%) rename src/{project_name => template_python}/__main__.py (61%) rename src/{project_name => template_python}/compat.py (71%) rename src/{project_name => template_python}/logging.py (90%) rename src/{project_name => template_python}/py.typed (100%) rename src/{project_name => template_python}/settings.py (93%) create mode 100644 tests/README.md create mode 100644 tests/e2e/test_bootstrap.py 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/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 4398158..91f2a4d 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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,7 +52,7 @@ 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 diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 039e287..5ae3f85 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -123,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 e5f49a3..9fbb0e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -83,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 a386a7b..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`, `markurtz`). +- 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 a5c1cb5..8e191bd 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,7 @@ 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@markurtz.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 5ca0cf8..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/markurtz/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..8cc2b97 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 778c0ec..ab6fc3b 100644 --- a/README.md +++ b/README.md @@ -15,12 +15,14 @@ GitHub Release - + + + +
@@ -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,11 +92,10 @@ This project has just been instantiated from the template repository. Keep an ey ## Quick Start ```bash -./scripts/bootstrap.sh \ +uv run scripts/bootstrap.py \ --project-name my-app \ --project-desc "My cool application" \ - --organization my-org \ - --org-name my-org + --organization my-org ``` For full setup instructions including GitHub settings, publishing, and docs, see the **[Repository Setup Guide](https://markurtz.github.io/template-python/guides/repository-setup/)**. 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 d58dfbd..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 @@ -70,5 +70,7 @@ Where to ask questions, report issues, and find help. | :--------------------------------------------------------------------------------------- | :--------------------------------------------- | | 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 | + + + + 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 0ecd4dd..96fa6e5 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 @@ -21,7 +21,7 @@ If you need to install the package directly into an environment (e.g., for local ```` ```bash -pip install project_name +pip install template_python ``` ```` @@ -29,7 +29,7 @@ pip install project_name ```` ```bash -uv pip install project_name +uv pip install template_python ``` ```` @@ -38,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: @@ -74,7 +74,7 @@ A pre-built Docker image is available for containerized environments: docker pull ghcr.io/markurtz/template-python:latest # Run a one-off command -docker run --rm ghcr.io/markurtz/template-python: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. @@ -107,7 +107,7 @@ To upgrade an existing installation to the latest release: ```` ```bash -pip install --upgrade project_name +pip install --upgrade template-python ``` ```` @@ -115,7 +115,7 @@ pip install --upgrade project_name ```` ```bash -uv pip install --upgrade project_name +uv pip install --upgrade template-python ``` ```` @@ -125,7 +125,7 @@ uv pip install --upgrade project_name ```` ```bash -pip uninstall project_name +pip uninstall template-python ``` ```` @@ -133,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 52c3dc4..17143b5 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,4 +1,4 @@ -# Quick Start +# Quick Start TODO: need to change quickstart doc to be for this specific template walkthrough rather than general purpose quickstart This guide gets you from a fresh installation to running your first command in under 5 minutes. @@ -12,27 +12,27 @@ If you haven't already, set up your project and install `template-python`: ```bash python -m venv .venv source .venv/bin/activate -pip install project_name +pip install template-python ``` ## Step 2 — Verify the Install ```bash -project_name +template-python ``` Expected output: ```console -Hello from project_name v0.1.0! +Hello from template_python v0.1.0! Settings: Settings(environment='development', project_root=PosixPath('...')) ``` ## Step 3 — Run Your First Command ```python -from project_name import Settings, configure_logger, logger -from project_name.logging import LoggingSettings +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")) 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/repository-setup.md b/docs/guides/repository-setup.md index 7ff3cd7..3311c23 100644 --- a/docs/guides/repository-setup.md +++ b/docs/guides/repository-setup.md @@ -6,20 +6,19 @@ 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 `markurtz`) with your actual project details. +Before making any manual changes, run the included bootstrap script to automatically replace all placeholder variables (like `template-python` and `markurtz`) with your actual project details. ```bash -./scripts/bootstrap.sh \ +uv run scripts/bootstrap.py \ --project-name my-cool-project \ --project-desc "A description of my cool project" \ - --organization my-org \ - --org-name "My Organization" + --organization my-org ``` > [!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, you can safely delete the `scripts/bootstrap.py` file and commit the updates. ## 2. Configure GitHub settings diff --git a/docs/index.md b/docs/index.md index 61a21c6..46fc7a2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -15,7 +15,7 @@ hide: # 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 @@ -108,7 +108,7 @@ Our security policy, responsible disclosure process, and supported versions. ## Quick Install ```bash -pip install project_name +pip install template-python ``` For advanced installation options, and step-by-step onboarding, see the [Installation Guide](getting-started/installation.md). @@ -117,5 +117,7 @@ For advanced installation options, and step-by-step onboarding, see the [Install - :material-github: GitHub Repository - :material-map-marker-path: Roadmap -- :material-post-outline: Blog -- :material-slack: Slack Community + + + + 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 be1ce51..f101b3d 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -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/) @@ -34,11 +34,11 @@ Run `hatch run test:all-cov` to generate HTML reports locally, or view the lates ## Python API Usage -`project_name` 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, configure_logger, logger -from project_name.logging import LoggingSettings +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")) 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..e394c37 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,7 +10,7 @@ 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 @@ -18,7 +18,7 @@ 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_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..783af3b 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 9560868..0264f81 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://markurtz.github.io/project_name/): Documentation site home page -- [Getting Started](https://markurtz.github.io/project_name/getting-started/): Installation, quickstart, and workflow guides -- [Guides](https://markurtz.github.io/project_name/guides/): How-to guides for common tasks -- [API Reference](https://markurtz.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/markurtz/project_name/blob/main/README.md): Project overview and quick start -- [AGENTS.md](https://github.com/markurtz/project_name/blob/main/AGENTS.md): AI agent coding instructions -- [DEVELOPING.md](https://github.com/markurtz/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://markurtz.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 `markurtz` 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, simply find and replace all instances of `template-python` and `markurtz` with your actual project 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,13 @@ 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, +@software{template-python, author = {markurtz}, - title = {project_name}, + title = template-python, version = {{{version}}}, month = {{{month}}}, year = {2026}, - url = {https://github.com/markurtz/project_name} + url = {https://github.com/markurtz/template-python} } ``` @@ -191,7 +191,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 +240,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`, `markurtz`). +- Use `{{placeholder}}` variables for templated fields (e.g., `template-python`, `markurtz`). ## Agent Notes @@ -249,7 +249,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 +273,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/markurtz/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 +457,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 +474,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 +504,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 +526,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 +553,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 +564,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 +579,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 +608,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 +618,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 +628,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 7418863..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://markurtz.github.io/project_name/): Documentation site home page -- [Getting Started](https://markurtz.github.io/project_name/getting-started/): Installation, quickstart, and workflow guides -- [Guides](https://markurtz.github.io/project_name/guides/): How-to guides for common tasks -- [API Reference](https://markurtz.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/markurtz/project_name/blob/main/README.md): Project overview and quick start -- [AGENTS.md](https://github.com/markurtz/project_name/blob/main/AGENTS.md): AI agent coding instructions -- [DEVELOPING.md](https://github.com/markurtz/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://markurtz.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 4e277d2..a1b407c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -7,24 +7,20 @@ 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: version: provider: mike - project_name: "project_name" + 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@markurtz.com" +# 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.markurtz.org" - blog_url: "https://blog.markurtz.org" - roadmap_url: "https://github.com/markurtz/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 @@ -101,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..0e03b97 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 Python 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..33149bb 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,6 +1,6 @@ -# `project_name` - Utility Scripts +# `template-python` - Utility Scripts # TODO: update docs thouroughly based on the boostrap.py script (the only one now) and all of the contained options / workflows -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 for `template-python`. > [!NOTE] > These scripts are intended for developer and CI/CD use only and are **not** distributed as part of the final application package. diff --git a/scripts/bootstrap.py b/scripts/bootstrap.py new file mode 100644 index 0000000..7b16176 --- /dev/null +++ b/scripts/bootstrap.py @@ -0,0 +1,867 @@ +#!/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 fnmatch +import os +import re +import subprocess +import sys +from pathlib import Path +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") +) + + +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 + ) + repository = cls._prompt_if_none( + kwargs.get("repository"), "Repository", 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 + ) + + 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 + ) + + project_desc = cls._prompt_if_none( + kwargs.get("project_desc"), "Project Description", 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 + ) + + 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 + ) + + 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, + } + if "global_blacklist" in kwargs and kwargs["global_blacklist"] is not None: + init_kwargs["global_blacklist"] = kwargs["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" + if script_path.exists(): + script_path.unlink() + if test_path.exists(): + 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: + posix_path = path.as_posix() + for pattern in patterns: + if fnmatch.fnmatch(posix_path, pattern) or fnmatch.fnmatch( + path.name, 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 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=( + ".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", + ), + 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="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=( + "An opinionated, production-ready Apache 2.0 template repository " + "for bootstrapping modern software projects." + ), + replace=config.project_desc, + ), + ReplacementRule(search="conduct@example.com", replace=config.support_email), + ReplacementRule(search="2026", 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..b54f7c9 --- /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..4a9d256 --- /dev/null +++ b/tests/e2e/test_bootstrap.py @@ -0,0 +1,210 @@ +"""End-to-end tests for the bootstrap.py script.""" + +from __future__ import annotations + +import shutil +import subprocess +import sys +from pathlib import Path + +import pytest + + +class TestBootstrap: + """Test suite for the bootstrap.py script.""" + + @pytest.fixture + def repo_copy(self, tmp_path: Path) -> Path: + """Fixture to create a copy of the repository in a temporary directory.""" + repo_root = Path(__file__).resolve().parent.parent.parent + target_dir = tmp_path / "repo_copy" + + # Ignore typical large/irrelevant directories + ignore_patterns = shutil.ignore_patterns( + ".git", + ".venv", + ".tox", + "__pycache__", + "dist", + "build", + ".pytest_cache", + ".mypy_cache", + ".ruff_cache", + "node_modules", + "site", + ) + + shutil.copytree(repo_root, target_dir, ignore=ignore_patterns) + return target_dir + + @pytest.mark.smoke + @pytest.mark.sanity + @pytest.mark.regression + @pytest.mark.parametrize( + ("cli_args", "expected_replacements"), + [ + ( + [ + "--repository", + "my-new-app", + "--project-desc", + "A cool new app.", + "--organization", + "myorg", + "--disable-pypi", + "--disable-docs", + ], + { + "template-python": "my-new-app", + "template_python": "my_new_app", + "markurtz": "myorg", + "conduct@example.com": "conduct@myorg.com", + "An opinionated, production-ready Apache 2.0 template repository " + "for bootstrapping modern software projects.": "A cool new app.", + }, + ), + ( + [ + "--repository", + "template-python", + "--project-desc", + "An opinionated, production-ready Apache 2.0 template repository for bootstrapping modern software projects.", + "--organization", + "markurtz", + "--email", + "conduct@example.com", + "--author-name", + "markurtz", + "--slack-url", + "https://slack.example.com", + "--blog-url", + "https://blog.example.com", + ], + { + "template-python": "template-python", + "template_python": "template_python", + "markurtz": "markurtz", + "conduct@example.com": "conduct@example.com", + "An opinionated, production-ready Apache 2.0 template repository " + "for bootstrapping modern software projects.": "An opinionated, production-ready Apache 2.0 template repository for bootstrapping modern software projects.", + }, + ), + ], + ) + def test_bootstrap( + self, + repo_copy: Path, + cli_args: list[str], + expected_replacements: dict[str, str], + ) -> None: + """Test that bootstrap.py replaces placeholders and renames directories.""" + script_path = repo_copy / "scripts" / "bootstrap.py" + + # Run bootstrap.py + cmd = [sys.executable, str(script_path)] + cli_args + result = subprocess.run( + cmd, + cwd=repo_copy, + capture_output=True, + text=True, + check=False, + ) + + print("STDOUT:") + print(result.stdout) + print("STDERR:") + print(result.stderr) + + assert result.returncode == 0, ( + f"Bootstrap script failed:\n{result.stderr}\n{result.stdout}" + ) + + # Verify old text is gone (excluding the script and .git) + self._verify_file_contents(repo_copy, expected_replacements, cli_args) + + # Verify python package directory renaming + old_pkg_dir = repo_copy / "src" / "template_python" + new_pkg_name = expected_replacements["template_python"] + new_pkg_dir = repo_copy / "src" / new_pkg_name + + if old_pkg_dir != new_pkg_dir: + assert not old_pkg_dir.exists(), "Old package directory still exists." + assert new_pkg_dir.exists(), "New package directory was not created." + + def _verify_file_contents( + self, + repo_copy: Path, + expected_replacements: dict[str, str], + cli_args: list[str], + ) -> None: + """Helper to verify file contents after bootstrap.""" + for file_path in repo_copy.rglob("*"): + if not file_path.is_file(): + continue + + # Skip the script itself + if file_path.name == "bootstrap.py": + continue + + # Skip binary files + if file_path.suffix in ( + ".pyc", + ".png", + ".svg", + ".jpg", + ".jpeg", + ".ico", + ".woff", + ".woff2", + ".ttf", + ".eot", + ): + continue + + try: + content = file_path.read_text(encoding="utf-8") + except UnicodeDecodeError: + continue + + print("EXPECTED_REPLACEMENTS:", expected_replacements) + + for old_text, new_text in expected_replacements.items(): + print(f"Checking {old_text=} against {new_text=}") + + if old_text == new_text: + continue + if old_text in content: + lines = content.split("\n") + for i, line in enumerate(lines): + if old_text in line: + pytest.fail( + f"Found '{old_text}' in {file_path.name} on line {i + 1}: {line.strip()}" + ) + + # Check email uncommenting in mkdocs.yml + if "mkdocs.yml" in file_path.name: + assert "# org_email:" not in content + assert " org_email:" in content + + # Check email uncommenting in CODE_OF_CONDUCT.md + if "CODE_OF_CONDUCT.md" in file_path.name: + # Get the email from expected_replacements + expected_email = expected_replacements.get("conduct@example.com") + if expected_email: + assert f"" not in content + assert expected_email in content + + if "--disable-pypi" in cli_args: + if file_path.name in ("release.yml", "nightly.yml"): + assert " - name: Publish to PyPI" not in content + assert "# - name: Publish to PyPI" in content + if file_path.name == "README.md": + assert ' All complaints will be reviewed and investigated promptly and fairly. diff --git a/README.md b/README.md index ab6fc3b..f456e36 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Supported Python Versions --> -
+
CI Status diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 17143b5..b96caae 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,4 +1,4 @@ -# Quick Start TODO: need to change quickstart doc to be for this specific template walkthrough rather than general purpose quickstart +# Quick Start TODO: need to change quickstart doc to be for this specific template walkthrough rather than general purpose quickstart This guide gets you from a fresh installation to running your first command in under 5 minutes. diff --git a/examples/README.md b/examples/README.md index e394c37..53ab53a 100644 --- a/examples/README.md +++ b/examples/README.md @@ -16,9 +16,9 @@ Before running the examples, ensure you have set up your environment correctly: 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/pyproject.toml b/pyproject.toml index 0e03b97..7dbef0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "hatchling.build" [project] name = "template-python" dynamic = ["version"] -description = "An opinionated, production-ready Apache 2.0 template repository for bootstrapping modern Python projects." +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" } diff --git a/scripts/README.md b/scripts/README.md index 33149bb..620795d 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,4 +1,4 @@ -# `template-python` - Utility Scripts # TODO: update docs thouroughly based on the boostrap.py script (the only one now) and all of the contained options / workflows +# `template-python` - Utility Scripts # TODO: update docs thouroughly based on the boostrap.py script (the only one now) and all of the contained options / workflows This directory contains utility scripts designed to assist with local development, maintenance, and automation tasks for `template-python`. diff --git a/scripts/bootstrap.py b/scripts/bootstrap.py index 7b16176..8c6f35f 100644 --- a/scripts/bootstrap.py +++ b/scripts/bootstrap.py @@ -182,27 +182,38 @@ def create(cls, **kwargs: Any) -> BootstrapConfig: click.echo("--- Template Initialization ---") click.echo("Please confirm or provide the following values.") - organization = cls._prompt_if_none( - kwargs.get("organization"), "Organization", default_org + 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 + 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 + 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 + 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 + 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: @@ -210,8 +221,11 @@ def create(cls, **kwargs: Any) -> BootstrapConfig: default_email = "" - maintainer = cls._prompt_if_none( - kwargs.get("maintainer"), "Maintainer", default_maintainer + maintainer = ( + cls._prompt_if_none( + kwargs.get("maintainer"), "Maintainer", default_maintainer + ) + or default_maintainer ) support_email = cls._prompt_if_none( @@ -224,8 +238,11 @@ def create(cls, **kwargs: Any) -> BootstrapConfig: 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 + release_date = ( + cls._prompt_if_none( + kwargs.get("release_date"), "Release Date", default_date + ) + or default_date ) disable_github_discussions = cls._prompt_if_none( @@ -444,7 +461,7 @@ def _apply_replacements( if isinstance(rule.search, Pattern): content = rule.search.sub(rule.replace, content) - elif rule.replace and rule.search != rule.replace: + elif rule.replace is not None and rule.search != rule.replace: content = content.replace(rule.search, rule.replace) if content != original_content: @@ -844,14 +861,21 @@ def html_comment_out(match: re.Match[str]) -> str: 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=( - "An opinionated, production-ready Apache 2.0 template repository " - "for bootstrapping modern software projects." + 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, ), diff --git a/tests/README.md b/tests/README.md index b54f7c9..25b0cf9 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,21 +6,21 @@ This directory contains the testing suite for `template-python`. We use `pytest` 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. | +| 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. | +| 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. diff --git a/tests/e2e/test_bootstrap.py b/tests/e2e/test_bootstrap.py index 4a9d256..7bf5e6a 100644 --- a/tests/e2e/test_bootstrap.py +++ b/tests/e2e/test_bootstrap.py @@ -2,209 +2,242 @@ from __future__ import annotations +import asyncio import shutil -import subprocess 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)) -class TestBootstrap: - """Test suite for the bootstrap.py script.""" +from scripts import bootstrap +from scripts.bootstrap import BootstrapConfig, main - @pytest.fixture - def repo_copy(self, tmp_path: Path) -> Path: - """Fixture to create a copy of the repository in a temporary directory.""" - repo_root = Path(__file__).resolve().parent.parent.parent - target_dir = tmp_path / "repo_copy" - # Ignore typical large/irrelevant directories - ignore_patterns = shutil.ignore_patterns( +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() + + 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__", - "dist", + "node_modules", + "site", "build", + "dist", ".pytest_cache", ".mypy_cache", ".ruff_cache", - "node_modules", - "site", - ) - - shutil.copytree(repo_root, target_dir, ignore=ignore_patterns) - return target_dir + } + if any(part in ignore_dirs for part in file_path.parts): + return - @pytest.mark.smoke - @pytest.mark.sanity - @pytest.mark.regression - @pytest.mark.parametrize( - ("cli_args", "expected_replacements"), - [ - ( - [ - "--repository", - "my-new-app", - "--project-desc", - "A cool new app.", - "--organization", - "myorg", - "--disable-pypi", - "--disable-docs", - ], - { - "template-python": "my-new-app", - "template_python": "my_new_app", - "markurtz": "myorg", - "conduct@example.com": "conduct@myorg.com", - "An opinionated, production-ready Apache 2.0 template repository " - "for bootstrapping modern software projects.": "A cool new app.", - }, - ), - ( - [ - "--repository", - "template-python", - "--project-desc", - "An opinionated, production-ready Apache 2.0 template repository for bootstrapping modern software projects.", - "--organization", - "markurtz", - "--email", - "conduct@example.com", - "--author-name", - "markurtz", - "--slack-url", - "https://slack.example.com", - "--blog-url", - "https://blog.example.com", - ], - { - "template-python": "template-python", - "template_python": "template_python", - "markurtz": "markurtz", - "conduct@example.com": "conduct@example.com", - "An opinionated, production-ready Apache 2.0 template repository " - "for bootstrapping modern software projects.": "An opinionated, production-ready Apache 2.0 template repository for bootstrapping modern software projects.", - }, - ), - ], - ) - def test_bootstrap( - self, - repo_copy: Path, - cli_args: list[str], - expected_replacements: dict[str, str], - ) -> None: - """Test that bootstrap.py replaces placeholders and renames directories.""" - script_path = repo_copy / "scripts" / "bootstrap.py" - - # Run bootstrap.py - cmd = [sys.executable, str(script_path)] + cli_args - result = subprocess.run( - cmd, - cwd=repo_copy, - capture_output=True, - text=True, - check=False, - ) - - print("STDOUT:") - print(result.stdout) - print("STDERR:") - print(result.stderr) + # Skip testing scripts since they contain placeholders + if file_path.name in ("bootstrap.py", "test_bootstrap.py"): + return - assert result.returncode == 0, ( - f"Bootstrap script failed:\n{result.stderr}\n{result.stdout}" - ) + try: + content = file_path.read_text(encoding="utf-8") + except UnicodeDecodeError: + return - # Verify old text is gone (excluding the script and .git) - self._verify_file_contents(repo_copy, expected_replacements, cli_args) + for ph in placeholders: + assert ph not in content, f"Placeholder '{ph}' found in {file_path}" - # Verify python package directory renaming - old_pkg_dir = repo_copy / "src" / "template_python" - new_pkg_name = expected_replacements["template_python"] - new_pkg_dir = repo_copy / "src" / new_pkg_name + self._verify_file_specific_content(file_path, target_dir, config, content) - if old_pkg_dir != new_pkg_dir: - assert not old_pkg_dir.exists(), "Old package directory still exists." - assert new_pkg_dir.exists(), "New package directory was not created." - - def _verify_file_contents( - self, - repo_copy: Path, - expected_replacements: dict[str, str], - cli_args: list[str], + def _verify_file_specific_content( + self, file_path: Path, target_dir: Path, config: BootstrapConfig, content: str ) -> None: - """Helper to verify file contents after bootstrap.""" - for file_path in repo_copy.rglob("*"): - if not file_path.is_file(): - continue - - # Skip the script itself - if file_path.name == "bootstrap.py": - continue - - # Skip binary files - if file_path.suffix in ( - ".pyc", - ".png", - ".svg", - ".jpg", - ".jpeg", - ".ico", - ".woff", - ".woff2", - ".ttf", - ".eot", - ): - continue - - try: - content = file_path.read_text(encoding="utf-8") - except UnicodeDecodeError: - continue - - print("EXPECTED_REPLACEMENTS:", expected_replacements) - - for old_text, new_text in expected_replacements.items(): - print(f"Checking {old_text=} against {new_text=}") - - if old_text == new_text: - continue - if old_text in content: - lines = content.split("\n") - for i, line in enumerate(lines): - if old_text in line: - pytest.fail( - f"Found '{old_text}' in {file_path.name} on line {i + 1}: {line.strip()}" - ) - - # Check email uncommenting in mkdocs.yml - if "mkdocs.yml" in file_path.name: - assert "# org_email:" not in content - assert " org_email:" in content - - # Check email uncommenting in CODE_OF_CONDUCT.md - if "CODE_OF_CONDUCT.md" in file_path.name: - # Get the email from expected_replacements - expected_email = expected_replacements.get("conduct@example.com") - if expected_email: - assert f"" not in content - assert expected_email in content - - if "--disable-pypi" in cli_args: - if file_path.name in ("release.yml", "nightly.yml"): - assert " - name: Publish to PyPI" not in content - assert "# - name: Publish to PyPI" in content - if file_path.name == "README.md": - assert '