Skip to main content

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:

  1. MCP integration — MCP server lifecycle, tool registration, progress reporting, and logging
  2. Manifest read-edit with consistency — hierarchical manifest traversal, atomic read-modify-write with optimistic concurrency
  3. 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:

  1. IMAGE_PROC_BINARY environment variable
  2. bin/image-proc[.exe] bundled inside the installed wheel
  3. image-proc on $PATH
  4. image-proc/target/release/image-proc relative 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: pytest on PATH or uv run pytest may 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 SDK
  • pyexiv2>=2.8 — EXIF extraction from image files (wraps Exiv2); requires brew install inih on macOS
  • blake3>=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 _extra dict 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

ouestcharlie_py_toolkit-0.7.0.tar.gz (1.2 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

ouestcharlie_py_toolkit-0.7.0-cp312-cp312-win_amd64.whl (1.7 MB view details)

Uploaded CPython 3.12Windows x86-64

ouestcharlie_py_toolkit-0.7.0-cp312-cp312-manylinux_2_34_x86_64.whl (2.1 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ x86-64

ouestcharlie_py_toolkit-0.7.0-cp312-cp312-macosx_15_0_x86_64.whl (2.0 MB view details)

Uploaded CPython 3.12macOS 15.0+ x86-64

ouestcharlie_py_toolkit-0.7.0-cp312-cp312-macosx_15_0_arm64.whl (1.6 MB view details)

Uploaded CPython 3.12macOS 15.0+ ARM64

File details

Details for the file ouestcharlie_py_toolkit-0.7.0.tar.gz.

File metadata

  • Download URL: ouestcharlie_py_toolkit-0.7.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

Hashes for ouestcharlie_py_toolkit-0.7.0.tar.gz
Algorithm Hash digest
SHA256 2f6f11e69401e526e1ccffcaee72b7e00ebdc57d4a7841f37a7cccc252c889b3
MD5 6555977fb25dd7c440f7c65621a9544b
BLAKE2b-256 197e989d937870485b49d6351b30210c0ba1d9ee6e2aa0d273ecf3d33cbd5eba

See more details on using hashes here.

Provenance

The following attestation bundles were made for ouestcharlie_py_toolkit-0.7.0.tar.gz:

Publisher: publish.yml on ouestcharlie/ouestcharlie-py-toolkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ouestcharlie_py_toolkit-0.7.0-cp312-cp312-win_amd64.whl.

File metadata

File hashes

Hashes for ouestcharlie_py_toolkit-0.7.0-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 676a132246b72e9b91c5dbe0fbdd6de5f4b1efc64d3020c8e875e0edcf880631
MD5 fb6c090933038d626874bd14b5a38fe1
BLAKE2b-256 d0708e2d6dc25b8e94f8dae5d73f9689646853c1b04e9ecd3004b63d4b5b8d0a

See more details on using hashes here.

Provenance

The following attestation bundles were made for ouestcharlie_py_toolkit-0.7.0-cp312-cp312-win_amd64.whl:

Publisher: publish.yml on ouestcharlie/ouestcharlie-py-toolkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ouestcharlie_py_toolkit-0.7.0-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for ouestcharlie_py_toolkit-0.7.0-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 b61c7e3e4cd87c83ed8e9be99e69f66e06ae61396d827163b70e8dec563da48e
MD5 07b1954f4ca14e41491ffb5fb63c1dd2
BLAKE2b-256 2ae835332f95cd04d205f4ea6d8e14523bd0b5aa9223c255d72bb3ea2d617bd0

See more details on using hashes here.

Provenance

The following attestation bundles were made for ouestcharlie_py_toolkit-0.7.0-cp312-cp312-manylinux_2_34_x86_64.whl:

Publisher: publish.yml on ouestcharlie/ouestcharlie-py-toolkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ouestcharlie_py_toolkit-0.7.0-cp312-cp312-macosx_15_0_x86_64.whl.

File metadata

File hashes

Hashes for ouestcharlie_py_toolkit-0.7.0-cp312-cp312-macosx_15_0_x86_64.whl
Algorithm Hash digest
SHA256 dba8958b28baead0337609162689fb905ff48fbf34908c1cf9b86891cfb3b8f9
MD5 ae806fc87f81031db36efd1f33646dc7
BLAKE2b-256 98415b80419725d79fb0a9d18ff5397d961cb5a6c17787569a8b6e92a1e1edd0

See more details on using hashes here.

Provenance

The following attestation bundles were made for ouestcharlie_py_toolkit-0.7.0-cp312-cp312-macosx_15_0_x86_64.whl:

Publisher: publish.yml on ouestcharlie/ouestcharlie-py-toolkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file ouestcharlie_py_toolkit-0.7.0-cp312-cp312-macosx_15_0_arm64.whl.

File metadata

File hashes

Hashes for ouestcharlie_py_toolkit-0.7.0-cp312-cp312-macosx_15_0_arm64.whl
Algorithm Hash digest
SHA256 87b881bf3deec571c51b43593331004b10cf98a02fff9274c1f7bc0fdb384751
MD5 e7d76a8ae0cf6af23eb53dfdb475f011
BLAKE2b-256 eb941434f7aa225a7216587c4f6dabef02942975274a49985026d7151628327c

See more details on using hashes here.

Provenance

The following attestation bundles were made for ouestcharlie_py_toolkit-0.7.0-cp312-cp312-macosx_15_0_arm64.whl:

Publisher: publish.yml on ouestcharlie/ouestcharlie-py-toolkit

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page