Metadata-Version: 2.4
Name: pyrig
Version: 1.1.35
Summary: A dev kit that standardizes configurations and testing
License-Expression: MIT
License-File: LICENSE
Author: Winipedia
Author-email: win.steveker@gmx.de
Requires-Python: >=3.12
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Dist: dotenv
Requires-Dist: networkx
Requires-Dist: packaging
Requires-Dist: pathspec
Requires-Dist: pillow
Requires-Dist: pygithub
Requires-Dist: pyyaml
Requires-Dist: setuptools
Requires-Dist: tomlkit
Requires-Dist: typer
Description-Content-Type: text/markdown

# pyrig

**pyrig** is a Python development toolkit that helps you **rig up** your Python projects by standardizing project configurations and automating testing workflows. It eliminates boilerplate setup work by providing opinionated, best-practice configurations for linting, type checking, testing, and CI/CD—allowing you to focus on writing code instead of configuring tools.

Built for Python 3.12+ projects using Poetry and GitHub, pyrig automatically generates project structure, creates test skeletons that mirror your source code, and maintains configuration files for tools like ruff, mypy, pytest, and pre-commit hooks.

---

## Table of Contents

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Initialization](#initialization)
- [Configuration Files](#configuration-files)
- [CLI Commands](#cli-commands)
- [Repository Protection](#repository-protection)
- [Testing](#testing)
- [Building Artifacts](#building-artifacts)
- [Examples](#examples)
- [Troubleshooting](#troubleshooting)
- [Contributing](#contributing)
- [License](#license)

---

## Features

- **Zero-Configuration Setup**: Opinionated, best-practice configurations for Python development tools
- **ConfigFile Machinery**: Automated system for discovering, creating, validating, and updating all configuration files
- **Automatic Test Generation**: Creates test skeletons that mirror your source code structure
- **Intelligent Fixture System**: Automatic discovery and loading of pytest fixtures across all packages
- **Strict Type Checking**: Enforces mypy strict mode with comprehensive type coverage
- **Code Quality Tools**: Pre-configured ruff (linting + formatting), mypy, bandit (security)
- **CI/CD Workflows**: GitHub Actions workflows for health checks, releases, and publishing
- **Repository Protection**: Automated GitHub branch protection and security settings
- **Dependency Management**: Automatic dependency updates with Poetry
- **Pre-commit Hooks**: Automated code quality checks before every commit with automatic installation
- **Artifact Building**: Extensible build system with PyInstaller support
- **Custom CLI**: Automatically generates CLI commands from your functions
- **Cross-Platform Testing**: Matrix testing across multiple OS and Python versions
- **Multi-Package Architecture**: Automatic discovery of configs, builders, fixtures, and resources across all packages depending on pyrig

---

## Requirements

- **Python**: 3.12 or higher
- **Poetry**: Package and dependency manager
- **Git**: Version control
- **GitHub**: For full CI/CD and repository protection features (optional but recommended)

---

## Installation

### From PyPI

```bash
pip install pyrig
# or
poetry add pyrig
```

**Note**: pyrig should be added as a regular dependency, not a dev dependency. 
Some might argue it should be a dev dependency. I hav ethought about it, but
decided against it for several reasons. 
The CLI functionality requires pyrig availability at runtime. Also pyrig has a small but often useful utility functionality that is available at runtime if you should need it. Also in the future functionality of pyrig might be extended around other things every project needs and these could include things that require runtime availability. 
Also pyrig decides itself what should be a dev dependency and what not. 
You will see in the generated pyproject.toml file that pyrig adds many dev dependencies. 
These are things that are only needed for development and testing and are not needed at runtime. 
pyrig does not add itself to the dev dependencies because it is needed at runtime for some of the functionality. 
You can add it as a dev dependency if you want, but all the functionality that requires pyrig at runtime will not be working then outside of the dev environment.

### From Source

```bash
git clone https://github.com/winipedia/pyrig.git
cd pyrig
poetry install
```

---

## Quick Start

```bash
# Create a new GitHub repository
# Clone it locally
git clone https://github.com/your-username/your-project.git
cd your-project

# Initialize Poetry project
poetry init

# Add pyrig
poetry add pyrig

# Initialize pyrig (creates all config files, tests, and runs setup)
poetry run pyrig init

# Commit and push
git add .
git commit -m "chore: init project with pyrig"
git push
```

---

## Initialization

### Prerequisites

1. **Create a GitHub repository** for your project (e.g., `your-project`)

2. **Configure GitHub Secrets**
   - `REPO_TOKEN`: GitHub Fine-Grained Personal Access Token with permissions:
     - `contents:read and write` (needed to commit after release)
     - `administration:read and write` (needed to protect the repo)
   - `PYPI_TOKEN`: PyPI token for your project (only needed if publishing to PyPI)

### Setup Steps

1. **Clone the repository**
   ```bash
   git clone https://github.com/your-username/your-project.git
   cd your-project
   ```

2. **Install Poetry** (if not already installed)
   ```bash
   curl -sSL https://install.python-poetry.org | python3 -
   ```

3. **Initialize Poetry project**
   ```bash
   poetry init  # or poetry new
   ```

4. **Add pyrig as a dependency**
   ```bash
   poetry add pyrig
   ```

5. **Run pyrig initialization**
   ```bash
   poetry run pyrig init
   ```

6. **Commit and push changes**
   ```bash
   git add .
   git commit -m "chore: init project with pyrig"
   git push
   ```

---

## ConfigFile Machinery

The ConfigFile Machinery is pyrig's automated system for managing all configuration files. All configuration files are subclasses of `pyrig.dev.configs.base.base.ConfigFile` and are automatically discovered from `pkg/dev/configs/**` directories across all packages depending on pyrig.

**Key Features**:

- **Automatic Discovery**: Scans all `dev/configs/` directories across all packages depending on pyrig
- **Automatic Initialization**: Creates missing config files based on their class definitions
- **Automatic Validation**: Checks existing config files against their expected content
- **Automatic Updates**: Updates config files when their definitions change
- **Intelligent Subclass Discovery**: Only executes the most specific (leaf) implementations. If you subclass an existing config, only your custom subclass will be executed, preventing duplicates and ensuring your customizations take precedence.

**Note**: To prevent pyrig from managing a specific config file, make the file empty (the file must exist).

### Extending Configuration Files

The ConfigFile Machinery uses a **subset validation algorithm** that allows you to extend configuration files with custom settings while maintaining pyrig's required settings.

**Subset Validation Rules**:

- **Dictionaries**: All required keys must exist, but you can add additional keys
- **Lists**: All required items must exist (order doesn't matter), but you can add additional items
- **Values**: Required values must match exactly (unless they are nested structures)

**Example**:

If pyrig requires:
```toml
[tool.mypy]
strict = true
warn_redundant_casts = true
```

You can extend it with:
```toml
[tool.mypy]
strict = true
warn_redundant_casts = true
exclude = ["tests/fixtures/"]  # Your custom setting
plugins = ["pydantic.mypy"]     # Your custom setting
```

**Recommended Approach**: Subclass existing configs (e.g., `PyprojectConfigFile`) in your own `dev/configs/python/pyproject.py` file. When you subclass an existing config, only your subclass executes, ensuring your customizations take full control.

### ConfigFile Types

- **`ConfigFile`**: Base class for all config files
- **`CopyModuleConfigFile`**: Copies entire module content to a config file
- **`CopyModuleOnlyDocstringConfigFile`**: Copies only the docstring from a module
- **`PythonConfigFile`**: For Python source files
- **`YamlConfigFile`**: For YAML configuration files
- **`JsonConfigFile`**: For JSON configuration files

### Configuration Files Managed by the Machinery

The ConfigFile Machinery automatically manages the following configuration files:

#### `pyproject.toml`

Stores project metadata and dependencies. Automatically adds essential dev dependencies (ruff, mypy, pytest, pre-commit) with strict settings.

- All dependencies use `*` as the version to stay up-to-date
- Use dictionary syntax for specific constraints: `{"version": "*", "python": "<3.15"}`
- Enforces that GitHub repo name and cwd name are equal; hyphens in repo names are converted to underscores in package names

#### `pkg/py.typed`

Indicates that the package supports type checking.

#### `README.md`

Must start with `# <project_name>`. The rest of the content is up to you.

#### `LICENCE`

Empty file for you to add your own license.

#### `experiment.py`

Empty file for experimentation. Git-ignored, not for production code.

#### `.python-version`

Set to the lowest supported Python version. Used by pyenv.

#### `.pre-commit-config.yaml`

Configured with the following hooks:

- `check-package-manager-config`: poetry check --strict
- `lint-code`: ruff check --fix
- `format-code`: ruff format
- `check-static-types`: mypy --exclude-gitignore
- `check-security`: bandit -c pyproject.toml -r .

Automatically installed during test session via autouse fixture.

**Note**: Heavy operations like `install-dependencies` and `create-root` run as autouse session fixtures during test execution, not as pre-commit hooks, to keep commits fast.

#### `.gitignore`

Pulls the latest [github/python.gitignore](https://github.com/github/gitignore/blob/main/Python.gitignore) and adds project-specific ignores.

#### `.env`

Empty file for environment variables. Git-ignored, used by python-dotenv.

#### `.github/workflows/health_check.yaml`

- **Triggers**: workflow_dispatch, pull_request, schedule (daily)
- **Purpose**: Matrix testing across different OS and Python versions

#### `.github/workflows/release.yaml`

- **Triggers**: workflow_dispatch, commit to main, schedule (weekly)
- **Process**: Runs health check, creates tag and changelog, creates GitHub release, builds and uploads artifacts, commits updates
- **Synchronization**: Keeps tags, poetry version, and PyPI in sync

#### `.github/workflows/publish.yaml`

- **Trigger**: Successful completion of release workflow
- **Purpose**: Publishes the package to PyPI
- **Note**: Empty the file to disable PyPI publishing

#### `pkg/main.py`

Main entry point for your application. Used by PyInstaller for building executables.

- **Usage**: `poetry run your-pkg-name main` or `python -m your-pkg-name`

#### `pkg/src/`

Subfolder for organizing your source code, separating it from development infrastructure (`dev/`).

#### `pkg/dev/subcommands.py`

Define custom CLI subcommands. Any function in this file is automatically added as a subcommand.

**Example**:
```python
def hello(name: str = "World") -> None:
    """Say hello to someone."""
    print(f"Hello, {name}!")

# Run with: poetry run your-pkg-name hello --name Alice
```

#### `pkg/dev/configs/configs.py`

Define custom configuration files. Any subclass of `ConfigFile` is automatically discovered and initialized. Configs can be defined in any file in the `pkg/dev/configs` folder.

**Subclass Behavior**: If you subclass an existing `ConfigFile`, only your most specific subclass will be executed, preventing duplicates.

#### `pkg/dev/artifacts/builder/builder.py`

Define build scripts. Any subclass of `Builder` is automatically discovered and executed. Builders can be defined in any file in the `pkg/dev/artifacts/builder` folder.

**Subclass Behavior**: If you subclass an existing `Builder`, only your most specific subclass will be executed, preventing duplicate builds.

#### `pkg/dev/artifacts/resources/`

Directory for storing static resources (images, data files, etc.). Automatically included in PyInstaller builds.

- **Resource Access**: Use `get_resource_path()` to access resources at runtime
- **Automatic Discovery**: Resources from all packages depending on pyrig are automatically included

**Example**:
```python
from pyrig.dev.artifacts.resources.resource import get_resource_path
import your_project.dev.artifacts.resources as resources

config_path = get_resource_path("config.json", resources)
data = config_path.read_text()
```

#### `pkg/dev/configs/python/resources_init.py`

Manages the `resources/__init__.py` file to ensure proper package initialization.

#### `pkg/dev/configs/testing/fixtures/`

Configuration files that manage the fixture system structure (fixture.py, scopes/session.py, scopes/package.py, scopes/module.py, scopes/class_.py, scopes/function.py).

#### `tests/conftest.py`

Automatically discovers and loads fixtures from all packages depending on pyrig. Fixtures are globally available across all tests without manual imports.

#### `tests/test_zero.py`

Empty test file to ensure pytest doesn't complain about missing tests during initial setup.

---


## CLI Commands

pyrig provides the following CLI commands:

```bash
pyrig --help

Usage: pyrig [OPTIONS] COMMAND [ARGS]...

╭─ Options ───────────────────────────────────────────────────────────────────────────────────────────╮
│ --install-completion          Install completion for the current shell.                             │
│ --show-completion             Show completion for the current shell, to copy it or customize the    │
│                               installation.                                                         │
│ --help                        Show this message and exit.                                           │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ──────────────────────────────────────────────────────────────────────────────────────────╮
│ create-root    Creates the root of the project.                                                     │
│ create-tests   Create all test files for the project.                                               │
│ init           Set up the project.                                                                  │
│ build          Build all artifacts.                                                                 │
│ protect-repo   Protect the repository.                                                              │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────╯
```

---

## Repository Protection

pyrig automatically configures comprehensive GitHub repository protection measures to enforce code quality, security, and collaboration best practices. The protection is applied via the `pyrig protect-repo` command, which is automatically run in CI/CD workflows.

### Repository Settings

The following repository-level settings are automatically configured:

| Setting | Configuration | Purpose |
|---------|--------------|---------|
| **Default Branch** | `main` | Standard default branch |
| **Delete Branch on Merge** | Enabled | Automatically deletes head branches after pull requests are merged |
| **Allow Update Branch** | Enabled | Allows updating pull request branches with the base branch |
| **Merge Commit** | Disabled | Prevents merge commits to maintain clean history |
| **Rebase Merge** | Enabled | Allows rebase and merge strategy |
| **Squash Merge** | Enabled | Allows squash and merge strategy |

### Branch Protection Rules

A comprehensive ruleset named "main protection" is applied to the default branch with the following protections:

#### Branch Modification Protections

- **Deletion Protection**: Prevents deletion of the protected branch
- **Non-Fast-Forward Protection**: Prevents force pushes and history rewrites
- **Creation Protection**: Controls branch creation patterns
- **Update Protection**: Restricts direct updates to the branch

#### Pull Request Requirements

- **Required Approving Reviews**: At least 1 approval required before merging
- **Dismiss Stale Reviews**: Automatically dismisses approvals when new commits are pushed
- **Code Owner Review**: Requires review from code owners (if CODEOWNERS file exists)
- **Last Push Approval**: Requires approval from someone other than the last person to push
- **Review Thread Resolution**: All review comments must be resolved before merging
- **Allowed Merge Methods**: Only squash and rebase merges are permitted

#### Code Quality Requirements

- **Required Linear History**: Enforces a linear commit history
- **Required Commit Signatures**: Requires commits to be signed (GPG/SSH signatures)
- **Required Status Checks**: All CI/CD checks must pass before merging
  - **Strict Status Checks**: Branch must be up-to-date with base branch before merging
  - **Required Workflow**: The `health_check.yaml` workflow must pass successfully

#### Bypass Actors

- Repository owner can bypass all protection rules when necessary

### Manual Application

The repository protection is automatically applied during CI/CD workflows, but you can also manually apply it:

```bash
# Set the REPO_TOKEN environment variable with a GitHub Personal Access Token
export REPO_TOKEN=your_github_token  # or add it to your .env file; pyrig picks it up automatically

# Run the protection command
poetry run pyrig protect-repo
```

**Required Token Permissions**:
- `contents:read and write`
- `administration:read and write`

### CI/CD Integration

The `protect-repo` step is automatically included in both the `health_check.yaml` and `release.yaml` workflows, ensuring that protection rules are consistently applied and up-to-date with every CI/CD run.

---


## Testing

pyrig uses pytest as the test framework, which is automatically added as a dev dependency and configured in pyproject.toml.

### Test Structure

pyrig generates test skeletons that mirror your source code structure:

- **Module Level**: Each source module has a corresponding test module
- **Class Level**: Each source class has a corresponding test class
- **Function Level**: Each source function/method has a corresponding test function

### Automatic Test Generation

1. **Manual**: Run `poetry run pyrig create-tests`
2. **Automatic**: An autouse session fixture creates missing tests when you run `pytest`

#### Fixture System

pyrig automatically discovers and loads fixtures from all packages depending on pyrig.

**Built-in Fixtures**:
- `config_file_factory` - Factory for creating test config file classes
- `builder_factory` - Factory for creating test builder classes

**Creating Custom Fixtures**:

Add a `pkg/dev/tests/fixtures/` directory:

```
your_project/dev/tests/fixtures/
├── __init__.py
├── fixture.py          # Custom fixtures
└── scopes/
    ├── __init__.py
    ├── session.py      # Session-level autouse fixtures
    └── ...
```

**Notes**:
- All fixtures are automatically discovered - no manual imports needed
- Autouse fixtures must be decorated with `@pytest.fixture(autouse=True)` or `@autouse_session_fixture`
- Fixtures from all packages are available to all tests

### Autouse Session Fixtures

Autouse session fixtures automatically enforce code quality and project conventions:

**Project Structure**:
- `assert_no_namespace_packages`: Ensures all packages have `__init__.py` files
- `assert_all_src_code_in_one_package`: Verifies all source code is in a single package
- `assert_src_package_correctly_named`: Checks package name matches `pyproject.toml`

**Test Coverage**:
- `assert_all_modules_tested`: Creates missing test modules, classes, and functions

**Code Quality**:
- `assert_no_unit_test_package_usage`: Prevents usage of `unittest` package (enforces pytest)

**ConfigFile Machinery**:
- `assert_config_files_are_correct`: Verifies and fixes all configuration files

**Pre-commit & Dependencies**:
- `assert_pre_commit_is_installed`: Ensures pre-commit hooks are installed
- `assert_dependencies_are_up_to_date`: Runs `poetry self update` and `poetry update --with dev`

### Running Tests

```bash
# Run all tests
poetry run pytest

# Run specific test file
poetry run pytest tests/test_your_project/test_calculator.py

# Run with coverage
poetry run pytest --cov=your_project
```

### Disabling Tests

To disable tests for a specific module, empty the test file:
```bash
echo "" > tests/test_your_project/test_src/test_calculator.py
```

---

## Building Artifacts

pyrig provides an extensible build system. All builders are subclasses of `Builder`. When you run `pyrig build`, the system automatically discovers all `Builder` subclasses. If you subclass an existing builder, only your most specific subclass executes.

### Basic Builder

Create custom builders by subclassing `Builder` in `pkg/dev/artifacts/builder/builder.py`:

```python
from pathlib import Path
from pyrig.dev.artifacts.builder.base.base import Builder

class MyBuilder(Builder):
    """Custom builder for creating artifacts."""

    @classmethod
    def create_artifacts(cls, temp_artifacts_dir: Path) -> None:
        """Build the project."""
        # Create your artifacts in temp_artifacts_dir
        artifact_path = temp_artifacts_dir / "my_artifact.txt"
        artifact_path.write_text("Hello, World!")
```

**Build artifacts**:
```bash
poetry run pyrig build
```

Artifacts are placed in the `artifacts/` directory with platform-specific naming (e.g., `my_artifact-Linux.txt`, `my_artifact-Windows.txt`).

### PyInstaller Builder

pyrig includes a `PyInstallerBuilder` class for creating standalone executables.

1. **Implement your main function** in `your_project/main.py`
2. **Create an icon.png file** at `your_project/dev/artifacts/resources/icon.png` (256x256 recommended)
3. **Add resources** to `your_project/dev/artifacts/resources/` (optional)
4. **Subclass PyInstallerBuilder** in `your_project/dev/artifacts/builder/builder.py`:
   ```python
   from pyrig.dev.artifacts.builder.base.base import PyInstallerBuilder

   class MyAppBuilder(PyInstallerBuilder):
       """Build standalone executable with PyInstaller."""
   ```

5. **Build**:
   ```bash
   poetry run pyrig build
   ```

The builder automatically:
- Creates a single executable file
- Converts icon.png to platform-specific format
- Auto-discovers and includes resources from all packages depending on pyrig
- Names output with platform suffix (e.g., `your-project-Windows.exe`)

### Accessing Resources in Built Executables

Use `get_resource_path()` to access resources:

```python
from pyrig.dev.artifacts.resources.resource import get_resource_path
import your_project.dev.artifacts.resources as resources

config_path = get_resource_path("config.json", resources)
data = json.loads(config_path.read_text())
```

### Multi-Package Architecture

pyrig supports multi-package architecture where multiple packages can depend on pyrig and automatically share configurations, builders, fixtures, and resources.

**Automatic Discovery**:

When you run pyrig commands or tests, it discovers components from all packages depending on pyrig:

1. **ConfigFile Machinery**: All `ConfigFile` subclasses from all `dev/configs/` directories
2. **Builders**: All `Builder` subclasses from all `dev/artifacts/builder/` directories
3. **Fixtures**: All pytest fixtures from all `dev/tests/fixtures/` directories
4. **Resources**: All files from all `dev/artifacts/resources/` directories

**Intelligent Subclass Discovery**:

- Only the most specific (leaf) subclasses are executed for configs and builders
- If you subclass a config or builder, only your subclass runs (not the parent)
- Fixtures use a different mechanism that loads all fixture modules without filtering

This enables:
- **Modular development**: Split your project into multiple packages with shared infrastructure
- **Reusable components**: Share configs, builders, fixtures, and resources across projects
- **Zero configuration**: Everything works automatically through dependency discovery



---

## Examples

### Example 1: Complete Project Structure

After running `pyrig init`:

```
your-project/
├── .env, .gitignore, .pre-commit-config.yaml, .python-version
├── experiment.py, LICENSE, poetry.lock, pyproject.toml, README.md
├── .github/workflows/
│   ├── health_check.yaml, publish.yaml, release.yaml
├── your_project/
│   ├── __init__.py, main.py, py.typed
│   ├── src/
│   └── dev/
│       ├── artifacts/builder/, artifacts/resources/
│       ├── cli/subcommands.py
│       ├── configs/configs.py
│       └── tests/fixtures/
└── tests/
    ├── conftest.py, test_zero.py
    └── test_your_project/
```

### Example 2: Adding a Custom Config File

```python
from pathlib import Path
from pyrig.dev.configs.base.base import YamlConfigFile

class MyConfigFile(YamlConfigFile):
    @classmethod
    def get_filename(cls) -> str:
        return "myconfig"

    @classmethod
    def get_parent_path(cls) -> Path:
        return Path("config")

    @classmethod
    def get_configs(cls) -> dict[str, Any]:
        return {"setting1": "value1", "setting2": "value2"}
```

### Example 3: Custom CLI Command

```python
def deploy(environment: str = "staging") -> None:
    """Deploy the application."""
    print(f"Deploying to {environment}...")

# Run with: poetry run your-project deploy --environment production
```

---

## Troubleshooting

### Common Issues

#### `poetry run pyrig` command not found
```bash
poetry install
```

#### Pre-commit hooks failing
```bash
poetry run pre-commit install
poetry run pre-commit run --all-files
```

#### Tests not being generated automatically
```bash
poetry run pyrig create-tests
```

#### GitHub Actions permission errors
Ensure `REPO_TOKEN` secret has: `contents:read and write`, `administration:read and write`

#### MyPy errors
Add type hints:
```python
def add(a: int, b: int) -> int:
    return a + b
```

#### Dependency conflicts
```bash
poetry update --with dev
```

#### PyInstaller build fails
1. Ensure `main.py` exists and has `main()` function implemented
2. Ensure `icon.png` exists at `your_project/dev/artifacts/resources/icon.png`

#### Resources not found in built executable
Use `get_resource_path()`:
```python
from pyrig.dev.artifacts.resources.resource import get_resource_path
import your_project.dev.artifacts.resources as resources
path = get_resource_path("config.json", resources)
```

---

## Contributing

1. **Report Issues**: [Open an issue](https://github.com/winipedia/pyrig/issues)
2. **Suggest Features**: [Start a discussion](https://github.com/winipedia/pyrig/discussions)
3. **Submit Pull Requests**: Fork, create feature branch, make changes, run tests, commit, push, open PR

### Development Setup

```bash
git clone https://github.com/winipedia/pyrig.git
cd pyrig
poetry install --with dev
poetry run pytest
```

---

## License

pyrig is licensed under the MIT License. See [LICENSE](LICENSE) for more information.

Copyright (c) 2025 Winipedia

---

## Links

- **Repository**: [github.com/winipedia/pyrig](https://github.com/winipedia/pyrig)
- **PyPI**: [pypi.org/project/pyrig](https://pypi.org/project/pyrig/)

---

