Shared Python toolkit for OuEstCharlie agents
Project description
OuEstCharlie Python Toolkit
Shared Python library for building OuEstCharlie photo management agents.
Overview
This toolkit provides three core capabilities:
- MCP integration — MCP server lifecycle, tool registration, progress reporting, and logging
- Manifest read-edit with consistency — hierarchical manifest traversal, atomic read-modify-write with optimistic concurrency
- XMP read-edit with consistency — sidecar read-modify-write with optimistic concurrency and field-level semantics
Package Structure
ouestcharlie-toolkit/
├── pyproject.toml
├── hatch_build.py # Build hook: compiles image-proc and bundles it in the wheel
├── image-proc/ # Rust CLI: decode + resize + AVIF/JPEG assembly
│ ├── Cargo.toml
│ └── src/main.rs
└── src/
└── ouestcharlie_toolkit/
├── bin/ # Bundled image-proc binary (populated at build time)
├── schema.py # Data models, exceptions, constants
├── backend.py # Backend protocol
├── backends/
│ └── local.py # Local filesystem backend
├── manifest.py # ManifestStore for manifest operations
├── xmp.py # XmpStore for XMP sidecar operations
├── thumbnail_builder.py # Thumbnail generation (delegates to image-proc)
├── progress.py # ProgressReporter for MCP progress
└── server.py # AgentBase for MCP server lifecycle
Installation
From PyPI (recommended)
pip install ouestcharlie-toolkit
The image-proc binary is compiled and bundled inside the wheel at publish time — no Rust toolchain required at install time.
System prerequisites:
- macOS:
brew install inih(required by pyexiv2 at runtime) - Linux/Windows: no extra steps — pyexiv2 and image-proc wheels are self-contained
From source (development)
System prerequisites:
brew install inih nasm # macOS — inih for pyexiv2, nasm for rav1e AVIF encoder
sudo apt install nasm # Linux
choco install nasm # Windows
Create virtual environment and install dependencies:
uv venv --python 3.13
uv pip install -e ".[dev]"
The image-proc binary is not compiled automatically in editable installs. Build it once:
cd image-proc && cargo build --release
# binary: image-proc/target/release/image-proc
With optional features:
cargo build --release --features raw # RAW format support (pure Rust, no extra deps)
cargo build --release --features heic # HEIC support (requires brew install libheif)
The toolkit resolves the binary in this order:
IMAGE_PROC_BINARYenvironment variablebin/image-proc[.exe]bundled inside the installed wheelimage-procon$PATHimage-proc/target/release/image-procrelative to this repo (dev build)
Optional features (RAW and HEIC)
To build with RAW or HEIC support, set env vars before hatch build or cargo build:
IMAGE_PROC_FEATURE_RAW=1 hatch build # enables rawler (pure Rust RAW decoder)
IMAGE_PROC_FEATURE_HEIC=1 hatch build # enables libheif-rs (requires brew install libheif)
Running Tests
Always use .venv/bin/python -m pytest — do not use .venv/bin/pytest or a system python:
# Unit tests (no binary required — all image-proc calls are mocked)
.venv/bin/python -m pytest tests/ -v
# Integration tests (spawn the real image-proc binary)
.venv/bin/python -m pytest tests_integration/ -v
# Both together
.venv/bin/python -m pytest tests/ tests_integration/ -v
# Run a specific file
.venv/bin/python -m pytest tests/test_photo.py -v --tb=short
Why:
pyteston PATH oruv run pytestmay resolve to the wrong Python or fail on native dependencies.
Integration tests require the image-proc binary to be built (uv sync or cargo build --release). They are skipped automatically when the binary is absent.
To run the Rust unit tests:
cd image-proc && cargo test
Building a Wheel
The hatch_build.py hook compiles image-proc and bundles the binary inside the wheel:
pip install hatch
hatch build
# produces dist/ouestcharlie_toolkit-*.whl (platform-specific)
Set env vars to enable optional features:
IMAGE_PROC_FEATURE_RAW=1 hatch build
IMAGE_PROC_FEATURE_HEIC=1 hatch build
Dependencies
mcp>=1.0— Official MCP Python SDKpyexiv2>=2.8— EXIF extraction from image files (wraps Exiv2); requiresbrew install inihon macOSblake3>=1.0.8— Fast content hashing
image-proc (Rust binary, bundled in the wheel) handles all image decoding, resizing, AVIF assembly, and JPEG preview generation.
XMP parsing and serialization use stdlib only and have no native dependencies.
Usage
Creating an Agent
from ouestcharlie_toolkit import AgentBase
class HousekeepingAgent(AgentBase):
def __init__(self):
super().__init__(name="ouestcharlie-housekeeping", version="1.0.0")
# Register tools using the FastMCP instance
@self.mcp.tool()
async def rebuild_partition(backend: str, partition: str, mode: str = "lazy"):
"""Rebuild partition manifest and thumbnails."""
# Agent logic here
photos = await self.backend.list_files(partition, suffix=".jpg")
progress = self.progress(total=len(photos))
for photo in photos:
await self.check_cancelled()
# Process photo...
await progress.advance(message=f"Processing {photo.path}")
return {"photosProcessed": len(photos), "errors": 0}
if __name__ == "__main__":
agent = HousekeepingAgent()
agent.run() # Runs on stdio transport
Working with Manifests
from ouestcharlie_toolkit import ManifestStore, PhotoEntry
# Read-modify-write pattern
async def add_photo_to_manifest(store: ManifestStore, partition: str, photo: PhotoEntry):
def modify(manifest):
manifest.photos.append(photo)
# Recompute summary...
return manifest
await store.read_modify_write_leaf(partition, modify)
Working with XMP Sidecars
from ouestcharlie_toolkit import XmpStore
# Read-modify-write pattern
async def add_face_tags(store: XmpStore, photo_path: str, faces: list[str]):
def modify(xmp):
for face in faces:
tag = f"ouestcharlie:faces/{face}"
if tag not in xmp.tags:
xmp.tags.append(tag)
return xmp
await store.read_modify_write(photo_path, modify)
Backend Configuration
The toolkit reads backend configuration from the WOOF_BACKEND_CONFIG environment variable:
export WOOF_BACKEND_CONFIG='{"type": "filesystem", "root": "/Users/alice/Photos"}'
Implementation Status
✅ Completed
- Package structure and build configuration
- Backend protocol and local filesystem implementation
- ManifestStore with optimistic concurrency
- XmpStore with optimistic concurrency
- ProgressReporter
- AgentBase with MCP server lifecycle
- Thumbnail generation: per-partition AVIF grid via avif-grid Rust binary
- Parallel decode (rayon) for JPEG, PNG, WebP, TIFF
- Orientation correction (TIFF values 1–8)
- Crop and pad fit modes
- Stubbed RAW (
--features raw) and HEIC (--features heic) support
📋 Future Work
- Cloud backend implementations (S3, GCS, ADLS Gen2, OneDrive, Kdrive)
- Bloom filter implementation for partition summaries
Architecture
See ouestcharlie/agent/agent_LLD_rationale.md for technology selection rationale.
Key design principles:
- Optimistic concurrency — All manifest and XMP writes use version tokens to detect conflicts
- Unknown field preservation — Schema evolution via
_extradict in dataclasses - Async throughout — All I/O operations are async
- Backend abstraction — Swappable storage backends (local, S3, GCS, etc.)
- MCP-native — Built on FastMCP for clean agent implementation
References
License
MIT license
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file ouestcharlie_py_toolkit-0.6.0.tar.gz.
File metadata
- Download URL: ouestcharlie_py_toolkit-0.6.0.tar.gz
- Upload date:
- Size: 1.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e78f8e7af741ffdf93382571476579003e3ea7ab1dfaef3b04a83dad265e493
|
|
| MD5 |
fb6778350de81f69b84269aab1978804
|
|
| BLAKE2b-256 |
8a32edf9853565341e8811a7a6851a928bce7f6660b61e25d7c20dd3b4ba566c
|
Provenance
The following attestation bundles were made for ouestcharlie_py_toolkit-0.6.0.tar.gz:
Publisher:
publish.yml on ouestcharlie/ouestcharlie-py-toolkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ouestcharlie_py_toolkit-0.6.0.tar.gz -
Subject digest:
9e78f8e7af741ffdf93382571476579003e3ea7ab1dfaef3b04a83dad265e493 - Sigstore transparency entry: 1340686017
- Sigstore integration time:
-
Permalink:
ouestcharlie/ouestcharlie-py-toolkit@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/ouestcharlie
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Trigger Event:
push
-
Statement type:
File details
Details for the file ouestcharlie_py_toolkit-0.6.0-cp312-cp312-win_amd64.whl.
File metadata
- Download URL: ouestcharlie_py_toolkit-0.6.0-cp312-cp312-win_amd64.whl
- Upload date:
- Size: 1.7 MB
- Tags: CPython 3.12, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
be3336ef3fa3956e76ea30047cd2621b1b1c940c7d9f95a49ff69c397e751817
|
|
| MD5 |
2ac5911bcd792df0adf3aabf7be18fe2
|
|
| BLAKE2b-256 |
a9b8edf56ba97589f98085ce323d8132244b2862063db8b26c724b3b47e2a4ba
|
Provenance
The following attestation bundles were made for ouestcharlie_py_toolkit-0.6.0-cp312-cp312-win_amd64.whl:
Publisher:
publish.yml on ouestcharlie/ouestcharlie-py-toolkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ouestcharlie_py_toolkit-0.6.0-cp312-cp312-win_amd64.whl -
Subject digest:
be3336ef3fa3956e76ea30047cd2621b1b1c940c7d9f95a49ff69c397e751817 - Sigstore transparency entry: 1340686023
- Sigstore integration time:
-
Permalink:
ouestcharlie/ouestcharlie-py-toolkit@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/ouestcharlie
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Trigger Event:
push
-
Statement type:
File details
Details for the file ouestcharlie_py_toolkit-0.6.0-cp312-cp312-manylinux_2_34_x86_64.whl.
File metadata
- Download URL: ouestcharlie_py_toolkit-0.6.0-cp312-cp312-manylinux_2_34_x86_64.whl
- Upload date:
- Size: 2.1 MB
- Tags: CPython 3.12, manylinux: glibc 2.34+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
47944a7b653351ee98ec1451b1a5dcf9f381d9ad39b77f3795622761c2cf9a81
|
|
| MD5 |
c2cd5a20cf3d7f7decb8095c53a29e98
|
|
| BLAKE2b-256 |
568944a23e693166b1b34575efb1656ede4f92ce8e1d204cb5b2aac1384df6f1
|
Provenance
The following attestation bundles were made for ouestcharlie_py_toolkit-0.6.0-cp312-cp312-manylinux_2_34_x86_64.whl:
Publisher:
publish.yml on ouestcharlie/ouestcharlie-py-toolkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ouestcharlie_py_toolkit-0.6.0-cp312-cp312-manylinux_2_34_x86_64.whl -
Subject digest:
47944a7b653351ee98ec1451b1a5dcf9f381d9ad39b77f3795622761c2cf9a81 - Sigstore transparency entry: 1340686039
- Sigstore integration time:
-
Permalink:
ouestcharlie/ouestcharlie-py-toolkit@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/ouestcharlie
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Trigger Event:
push
-
Statement type:
File details
Details for the file ouestcharlie_py_toolkit-0.6.0-cp312-cp312-macosx_15_0_x86_64.whl.
File metadata
- Download URL: ouestcharlie_py_toolkit-0.6.0-cp312-cp312-macosx_15_0_x86_64.whl
- Upload date:
- Size: 2.0 MB
- Tags: CPython 3.12, macOS 15.0+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8cf93bc3670b749e83e4dcafb7dc959e078636891c8674320230c0977676c2fb
|
|
| MD5 |
fd7c301a5eafd9e1d2b1b41e796d5240
|
|
| BLAKE2b-256 |
078d2596ae586fb8300547a487e61dadd044da6fc24b408259435f5682647426
|
Provenance
The following attestation bundles were made for ouestcharlie_py_toolkit-0.6.0-cp312-cp312-macosx_15_0_x86_64.whl:
Publisher:
publish.yml on ouestcharlie/ouestcharlie-py-toolkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ouestcharlie_py_toolkit-0.6.0-cp312-cp312-macosx_15_0_x86_64.whl -
Subject digest:
8cf93bc3670b749e83e4dcafb7dc959e078636891c8674320230c0977676c2fb - Sigstore transparency entry: 1340686028
- Sigstore integration time:
-
Permalink:
ouestcharlie/ouestcharlie-py-toolkit@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/ouestcharlie
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Trigger Event:
push
-
Statement type:
File details
Details for the file ouestcharlie_py_toolkit-0.6.0-cp312-cp312-macosx_15_0_arm64.whl.
File metadata
- Download URL: ouestcharlie_py_toolkit-0.6.0-cp312-cp312-macosx_15_0_arm64.whl
- Upload date:
- Size: 1.6 MB
- Tags: CPython 3.12, macOS 15.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cf0da62fdf605cfb50b270280fff48d6bc152ab96191bc635aeaaf940c012f71
|
|
| MD5 |
834985d244fcfca0e57396c178afcc6f
|
|
| BLAKE2b-256 |
13c79f13e556655c1f28068615fd49e7c4c837d28719a81bb4f8376c642bf3bd
|
Provenance
The following attestation bundles were made for ouestcharlie_py_toolkit-0.6.0-cp312-cp312-macosx_15_0_arm64.whl:
Publisher:
publish.yml on ouestcharlie/ouestcharlie-py-toolkit
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ouestcharlie_py_toolkit-0.6.0-cp312-cp312-macosx_15_0_arm64.whl -
Subject digest:
cf0da62fdf605cfb50b270280fff48d6bc152ab96191bc635aeaaf940c012f71 - Sigstore transparency entry: 1340686034
- Sigstore integration time:
-
Permalink:
ouestcharlie/ouestcharlie-py-toolkit@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Branch / Tag:
refs/tags/v0.6.0 - Owner: https://github.com/ouestcharlie
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d8a603f83f108d251218b10fbe9b7e7e635db9fe -
Trigger Event:
push
-
Statement type: