A comprehensive, production-ready async cancellation system for Python 3.13+ using anyio.
- Multiple Cancellation Sources: Timeout, manual tokens, OS signals, and custom conditions
- Composable Design: Combine multiple cancellation sources easily
- Stream Processing: Built-in support for cancelable async iterators
- Operation Tracking: Full lifecycle tracking with status and progress reporting
- Library Integrations: Ready-to-use FastAPI integration for request cancellation
- Type Safe: Full type hints and runtime validation with Pydantic
- Production Ready: Comprehensive error handling, logging, and performance optimized
The core library includes only essential dependencies (anyio and pydantic):
uv add hother-cancelableCancelable provides optional extras for various integrations and use cases:
| Extra | Dependencies | Purpose |
|---|---|---|
fastapi |
fastapi | FastAPI middleware for request cancellation |
examples |
google-genai, pynput, psutil | Run example scripts and demonstrations |
FastAPI integration:
uv add "hother-cancelable[fastapi]"Examples:
uv add "hother-cancelable[examples]"All extras:
uv add "hother-cancelable[fastapi,examples]"from hother.cancelable import Cancelable
# Timeout-based cancellation
async with Cancelable.with_timeout(30.0) as cancel:
result = await long_running_operation()
# Manual cancellation with token
from hother.cancelable import CancellationToken
token = CancellationToken()
async with Cancelable.with_token(token) as cancel:
# In another task/thread: await token.cancel()
result = await interruptible_operation()# Cancelable stream processing
async with Cancelable.with_timeout(60.0) as cancel:
async for item in cancel.stream(data_source(), report_interval=100):
await process_item(item)from hother.cancelable import cancelable
@cancelable(timeout=30.0, register_globally=True)
async def process_data(data: list, cancelable: Cancelable = None):
for i, item in enumerate(data):
await cancelable.report_progress(f"Processing item {i+1}/{len(data)}")
await process_item(item)Cancelable provides seamless integration with FastAPI. See the integrations documentation for detailed guides and examples.
- FastAPI: Add cancellation middleware to FastAPI applications with automatic request-scoped cancellation
To build and serve the documentation locally:
- Install the documentation dependencies:
uv sync --group doc
source .venv/bin/activate- Serve the documentation:
mkdocs serveCancelable uses dependency groups for development and documentation:
| Group | Purpose | Key Dependencies |
|---|---|---|
dev |
Development tools | pytest, ruff, basedpyright, twine, git-cliff |
doc |
Documentation building | mkdocs, mkdocs-material, mike |
Basic development setup:
uv sync --group dev
source .venv/bin/activate
lefthook installThis creates a virtual environment, installs all development dependencies, and installs the library in editable mode. It also sets up Lefthook git hooks.
Full development setup with extras:
Some tests and examples require optional extras. To run the full test suite:
# Install dev tools + all extras
uv sync --group dev --all-extrasSelective installation:
# Install with specific extras
uv sync --group dev --extra fastapi --extra examples
# Install documentation tools
uv sync --group docAvailable extras:
fastapi- FastAPI middlewareexamples- Example scripts
Available groups:
dev- Development tools (pytest, ruff, basedpyright, etc.)doc- Documentation tools (mkdocs, mkdocs-material, etc.)
This project uses Lefthook for managing git hooks. Hooks are automatically installed when you run make install-dev.
To run hooks manually:
# Run all pre-commit hooks
lefthook run pre-commit
Run core tests (without integration extras):
uv run pytestIntegration tests that require optional dependencies (fastapi) will be automatically skipped if the extras are not installed.
Run all tests including integrations:
# First install all extras
uv sync --all-extras
# Then run tests
uv run pytestRun specific test categories:
# Run only unit tests
uv run pytest tests/unit
# Run only integration tests (requires extras)
uv run pytest tests/integrationuv run pytest --cov=hother.cancelableuv build
This project uses python-semantic-release for fully automated versioning and releases. Every commit to the main branch is analyzed using conventional commits, and releases are created automatically when needed.
- Commit with conventional format to the
mainbranch - GitHub Actions automatically analyzes commits, determines version bump, creates tag, updates changelog, publishes to PyPI, and creates GitHub release
- Documentation is automatically deployed when a release is published
No manual intervention required! π
| Commit Type | Version Bump | Example |
|---|---|---|
feat: |
Minor | 0.5.0 β 0.6.0 |
fix:, perf:, refactor: |
Patch | 0.5.0 β 0.5.1 |
feat!:, BREAKING CHANGE: |
Major | 0.5.0 β 1.0.0 |
docs:, chore:, ci:, style:, test: |
No release | - |
# Minor version bump (new feature)
git commit -m "feat: add streaming cancellation support"
# Patch version bump (bug fix)
git commit -m "fix: resolve race condition in token cancellation"
# Major version bump (breaking change)
git commit -m "feat!: redesign cancellation API
BREAKING CHANGE: CancellationToken.cancel() is now async"If needed, you can manually trigger a release via GitHub Actions:
# Go to: Actions β Semantic Release β Run workflow β Run on main branchOr use the gh CLI:
gh workflow run semantic-release.ymlCheck what the next version would be without making changes:
# Check current version
grep 'version = ' pyproject.toml | cut -d'"' -f2
# Preview next version (requires being on main branch)
uv run semantic-release --noop version --printThis project uses PyPI's Trusted Publishing for secure, token-free releases. The GitHub Actions workflow is automatically authorized to publish to PyPI via OIDC.
No API tokens needed! The workflow authenticates using:
- Publisher: GitHub Actions
- Repository:
hotherio/cancelable - Workflow:
semantic-release.yml
When preparing for a release:
- Ensure all PRs use conventional commit format in titles
- Verify CI passes on main branch
- Commit messages follow conventional commits specification
- Breaking changes are documented in commit body with
BREAKING CHANGE: - Push to main or merge PR - release happens automatically!
The changelog is automatically generated from conventional commits and updated on every release. View it at CHANGELOG.md.
Documentation is automatically built and deployed when:
- A release is published (triggered by semantic-release)
- Changes are pushed to
docs/,mkdocs.yml, or the workflow file onmain
Manual deployment commands:
# Deploy a specific version
uv run mike deploy --push --update-aliases v0.5 latest
# Set default version
uv run mike set-default latest
# List deployed versions
uv run mike listCheck documentation locally:
uv run mkdocs serve
# or with mike
uv run mike serveGenerate the licenses:
uv run pip-licenses --from=mixed --order count -f md --output-file licenses.md
uv run pip-licenses --from=mixed --order count -f csv --output-file licenses.csv
Build the new documentation:
uv run mike deploy --push --update-aliases <version> latest
uv run mike set-default latest
uv run mike list
Checking the documentation locally
uv run mike serve
Each git branch should have the format <tag>/item_<id> with eventually a descriptive suffix.
We us a Squash & Merge approach.
We use Conventional Commits.
Format: <type>(<scope>): <subject>
<scope> is optional
feat: add hat wobble
^--^ ^------------^
| |
| +-> Summary in present tense.
|
+-------> Type: chore, docs, feat, fix, refactor, style, or test.
More Examples:
feat: (new feature for the user, not a new feature for build script)fix: (bug fix for the user, not a fix to a build script)docs: (changes to the documentation)style: (formatting, missing semi colons, etc; no production code change)refactor: (refactoring production code, eg. renaming a variable)test: (adding missing tests, refactoring tests; no production code change)chore: (updating grunt tasks etc; no production code change)build: (changes in the build system)ci: (changes in the CI/CD and deployment pipelines)perf: (significant performance improvement)revert: (revert a previous change)
We welcome contributions! Please see our Contributing Guide for details on how to get started.
This project is licensed under the MIT License - see the LICENSE file for details.