Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 75 additions & 50 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,79 +2,104 @@

Octohook is a Python library that simplifies working with GitHub Webhooks by parsing incoming payloads into typed Python classes. It provides automatic payload parsing, URL interpolation helpers, and a decorator-based system for registering webhook handlers.

## Development Commands
## Key Concepts

### Testing
```bash
# Run all tests
uv run pytest
### Hook System

# Run with verbose output
uv run pytest -v
```
The hook system uses a three-level filtering mechanism:

### Building
```bash
# Build the package
uv build
```
1. **Event filtering** - Match on `WebhookEvent` (e.g., PULL_REQUEST, LABEL)
2. **Action filtering** - Match on `WebhookEventAction` (e.g., OPENED, CLOSED) or `ANY_ACTION` (*)
3. **Repository filtering** - Match specific repo full_name or `ANY_REPO` (*)

**Handler resolution order:**
1. If any debug hooks exist for the event, ONLY debug hooks run
2. Otherwise: handlers for (event, specific_action, ANY_REPO) + (event, specific_action, specific_repo) + (event, ANY_ACTION, ANY_REPO)

**Execution behavior:**
- Handlers run sequentially in registration order
- Exceptions are logged but don't stop execution
- Debug mode (`debug=True` on any hook) causes only debug hooks to fire for that event

### Payload Handling

GitHub sends different payload structures for the same model depending on the event type and action. For example:
- `changes` key is only present for some actions with "edited" events
- Not all fields are populated in all contexts

Octohook uses `Optional` types extensively to handle missing fields gracefully. Models conform to the "least common denominator" - required fields are only those present in ALL payloads for that model.

## Architecture

### Core Components
### Module Organization

**octohook/__init__.py** - Entry point providing:
- `setup(modules)` - Configures octohook by loading webhook handlers. Raises on import errors. Always calls reset() first to clear existing state.
- `reset()` - Clears all registered hooks and imported modules. Returns octohook to unconfigured state.
**octohook/__init__.py** - Entry point
- `setup(modules)` - Configures octohook by loading webhook handlers. Raises on import errors. Always calls reset() first.
- `reset()` - Clears all registered hooks and imported modules
- `OctohookConfigError` - Exception raised for configuration errors
- Exports: `hook`, `handle_webhook`, `parse`, `setup`, `reset`, `WebhookEvent`, `WebhookEventAction`, `OctohookConfigError`

**octohook/decorators.py** - Decorator system (`_WebhookDecorator` class):
- `@hook(event, actions, repositories, debug)` - Registers functions as webhook handlers
**octohook/decorators.py** - Decorator system
- `@hook` decorator - Registers functions as webhook handlers
- `handle_webhook(event_name, payload)` - Dispatches webhooks to registered handlers
- Handler storage: nested defaultdicts organized by event → action → repo → handlers
- Debug mode: when `debug=True` on any hook, only debug hooks fire for that event
- Sequential execution: handlers run in order, exceptions are logged but don't stop execution
- `_WebhookDecorator` class - Manages handler registration and routing

**octohook/events.py** - Event classes:
**octohook/events.py** - Event classes
- `BaseWebhookEvent` - Base class with common fields (sender, repository, organization, enterprise)
- 40+ specific event classes (e.g., `PullRequestEvent`, `IssuesEvent`, `LabelEvent`)
- `WebhookEvent` enum - All GitHub event types (check_run, pull_request, etc.)
- `WebhookEventAction` enum - All GitHub actions (opened, closed, synchronize, etc.)
- Specific event classes (e.g., `PullRequestEvent`, `IssuesEvent`, `LabelEvent`)
- `WebhookEvent` enum - All GitHub event types
- `WebhookEventAction` enum - All GitHub actions
- `parse(event_name, payload)` - Factory function that returns appropriate event object

**octohook/models.py** - Model classes:
**octohook/models.py** - Model classes
- `BaseGithubModel` - Base class for all GitHub models
- Core models: `User`, `Repository`, `PullRequest`, `Issue`, `Comment`, etc.
- URL interpolation: Many models have methods like `archive_url(format, ref)` that fill in URL templates
- `_transform(url, local_variables)` - Helper that replaces `{param}` and `{/param}` patterns in URLs
- `_optional(payload, key, class_type)` - Helper for nullable fields
- URL interpolation helpers that fill in URL templates with parameters

### Hook System
## Development

The hook system uses a three-level filtering mechanism:
### Testing

1. **Event filtering** - Match on `WebhookEvent` (e.g., PULL_REQUEST, LABEL)
2. **Action filtering** - Match on `WebhookEventAction` (e.g., OPENED, CLOSED) or `ANY_ACTION` (*)
3. **Repository filtering** - Match specific repo full_name or `ANY_REPO` (*)
```bash
# Run all tests
uv run pytest

Handler resolution order:
1. If any debug hooks exist for the event, ONLY debug hooks run
2. Otherwise: handlers for (event, specific_action, ANY_REPO) + (event, specific_action, specific_repo) + (event, ANY_ACTION, ANY_REPO)
# Run with verbose output
uv run pytest -v
```

### Payload Inconsistencies
**Test organization:**
- Tests use `pytest` and `pytest-mock`
- Test fixtures in `tests/fixtures/complete/` contain real GitHub webhook payloads
- Hook tests verify decorator system routing
- Model tests verify parsing and URL interpolation
- `tests/conftest.py` provides autouse fixture that calls `reset()` before/after each test for isolation

GitHub sends different payload structures for the same model depending on the event type and action. For example:
- `changes` key is only present for some actions with "edited" events
- Not all fields are populated in all contexts
### Building

```bash
# Build the package
uv build
```

Octohook uses `Optional` types extensively and the `_optional()` helper to handle missing fields gracefully. Models conform to the "least common denominator" - required fields are only those present in ALL payloads for that model.
## Documentation Standards

## Testing Notes
### Code Documentation

- Tests use `pytest` and `pytest-mock`
- Test fixtures in `tests/fixtures/complete/` contain real GitHub webhook payloads
- Hook tests verify that the decorator system correctly routes events to handlers
- Model tests verify parsing and URL interpolation
- `tests/conftest.py` provides an autouse fixture that calls `reset()` before/after each test for isolation
- The autouse fixture also clears test module imports from `sys.modules` to ensure decorators re-register on each test
**Public API functions** (exported in `__all__`):
- Google-style docstrings with Args, Raises, Example sections
- Include at least one runnable usage example
- Type hints required on all signatures

### User Documentation

**Tone:**
- Direct and technical - assume reader competence
- No marketing language or superlatives
- Imperative mood for instructions
- Explain "why" before "how"

**Content:**
- Show real, runnable examples first
- Link to external docs rather than duplicating them
- Keep explanations concise