Skip to content

Latest commit

 

History

History
1158 lines (844 loc) · 36.9 KB

File metadata and controls

1158 lines (844 loc) · 36.9 KB

CLAUDE.md

This file provides comprehensive guidance to Claude Code (claude.ai/code) when working with the Aignostics Python SDK repository.

You do raise the bar, always

It is your goal to enable the contributor while insisting on highest standards at all times:

  • Fully read, understand and follow this CLAUDE.md and ALL recursively referenced documents herein for guidance on style and conventions.
  • In case of doubt apply best practices of enterprise grade software engineering.
  • On every review you make or code you contribute raise the bar on engineering and operational excellence in this repository
  • Do web research on any libraries, frameworks, principles or tools you are not familiar with.

If you want to execute and verify code yourself:

  • uv, python and further development dependencies are already installed.
  • Use uv sync --all-extras to install any missing dependencies for your branch.
  • Use uv run pytest ... to run tests.
  • Use uv run aignostics ... to run the CLI and commands.
  • Use make lint to check code style and types.
  • Use make lint_fix to automatically fix code style issues.
  • Use make test_unit to run the unit test suite.
  • Use make test_integration to run the integration test suite.
  • Use make test_e2e to run the end-to-end (e2e) test suite.
  • Use make audit to run security audits of 3rd party dependencies and check compliance with our license policy.

If you write code yourself, it is a strict requirement to validate your work on completion before you call it done:

  • Linting must pass.
  • The unit, integration and e2e test suites must pass.
  • Auditing must pass.

If you you are creating a pull request yourself:

  • Add a label skip:test:long_running, to skip running long running tests. This is important because some tests in this repository are marked as long_running and can take a significant amount of time to complete. By adding this label, you help ensure that the CI pipeline runs efficiently and avoids unnecessary delays.

Module Documentation Index

Every module has detailed CLAUDE.md documentation. For module-specific guidance, see:

Modules without their own CLAUDE.md (see the source directory directly): dicom/, idc/, marimo/, thumbnail/, tiff/.

Development Commands

Primary workflow commands (use these):

make install          # Install dev dependencies + pre-commit hooks
make all             # Run lint, test, docs, audit (full CI pipeline)
make test            # Run tests with coverage
make test 3.14       # Run tests on specific Python version
make lint            # Ruff formatting + linting + MyPy type checking
make docs            # Build Sphinx documentation
make audit           # Security and license compliance checks

Package management:

  • Uses uv as package manager (not pip/poetry)
  • Run uv sync to install dependencies
  • Run uv add <package> to add new dependencies

Testing:

  • Coverage thresholds live in codecov.yml (project/patch targets). There is no local --fail-under gate.
  • Default timeout: 10 seconds (override with @pytest.mark.timeout(timeout=N))
  • Use uv run pytest tests/path/to/test.py::test_function for single tests
  • See Testing Workflow section below for complete marker documentation
  • Special test commands: make test_unit, make test_integration, make test_e2e, make test_long_running, make test_very_long_running, make test_sequential, make test_scheduled

Type Checking (dual type checkers):

  • MyPy: Strict mode enforced (make lint runs MyPy)
  • PyRight: Basic mode with selective exclusions (pyrightconfig.json)
  • Both type checkers must pass in CI/CD
  • All public APIs require type hints
  • Use from __future__ import annotations for forward references

Software Architecture Principles

This SDK follows a Modulith Architecture with these core principles:

1. Modulith Design

  • Single deployable unit with well-defined module boundaries
  • High cohesion within modules, loose coupling between modules
  • Each module is self-contained with its own service, configuration, and optional UI
  • Clear dependency hierarchy preventing circular dependencies

2. Dependency Injection & Service Discovery

  • No decorators or annotations - uses runtime service discovery
  • Dynamic module loading via locate_implementations(BaseService)
  • All services inherit from BaseService providing standard health() and info() interfaces
  • Singleton pattern for service instances within the DI container

3. Presentation Layer Pattern

Each module can have zero, one, or both presentation layers:

  • CLI (_cli.py): Text-based interface using Typer framework
  • GUI (_gui.py): Graphical interface using NiceGUI framework
  • Both layers depend on the Service layer, never on each other

Module Architecture Pattern

Each module follows a consistent three-layer architecture:

Module/
├── _service.py     # Business logic layer (core operations)
├── _cli.py         # CLI presentation layer (Typer commands)
├── _gui.py         # GUI presentation layer (NiceGUI interface; a package dir _gui/ when large, e.g. application/_gui/)
├── _settings.py    # Configuration (Pydantic models)
└── CLAUDE.md       # Comprehensive documentation

Presentation layers (CLI/GUI) depend on Service layer:

┌─────────────┐     ┌─────────────┐
│  CLI Layer  │     │  GUI Layer  │
│  (_cli.py)  │     │  (_gui.py)  │
└──────┬──────┘     └──────┬──────┘
       └──────────┬─────────┘
                  ↓
         ┌────────────────┐
         │  Service Layer │
         │ (_service.py)  │
         └────────────────┘

Core Modules & Dependencies

Foundation Layer

utils - Infrastructure module providing:

  • Dependency injection container (locate_implementations, locate_subclasses)
  • Structured logging (via loguru.logger)
  • Settings management (Pydantic-based)
  • Health check framework (BaseService, Health)
  • MCP server with auto-discovery of plugin tools (mcp_create_server, mcp_run, mcp_list_tools)
  • GUI navigation infrastructure (BaseNavBuilder, NavItem, NavGroup)
  • Enhanced user agent generation with CI/CD context (user_agent)

API Layer

platform - Authentication and API gateway:

  • OAuth 2.0 device flow authentication
  • Token lifecycle management
  • Resource clients (applications, runs)
  • Dependencies: utils

Domain Modules

application - ML application orchestration:

  • Run lifecycle management
  • Version control (semver)
  • File upload/download with progress
  • Dependencies: platform, bucket, wsi, utils, qupath (optional)

wsi - Whole slide image processing:

  • Multi-format support (OpenSlide, PyDICOM)
  • Thumbnail generation
  • Tile extraction
  • Dependencies: utils

dataset - Large-scale data operations:

  • IDC (Imaging Data Commons) integration
  • High-performance downloads (s5cmd)
  • Dependencies: platform, utils

bucket - Cloud storage abstraction:

  • S3/GCS unified interface
  • Signed URL generation
  • Chunked transfers
  • Dependencies: platform, utils

Integration Modules

qupath - Bioimage analysis platform:

  • QuPath installation and lifecycle
  • Project management
  • Script execution
  • Dependencies: utils (uses ijson, a core dep)

notebook - Interactive analysis:

  • Marimo notebook server
  • Process management
  • Dependencies: utils (uses marimo, a core dep)

System Modules

system - Diagnostics and monitoring:

  • Health aggregation from ALL modules via BaseService.health()
  • Comprehensive system information
  • Environment detection and diagnostics
  • Dependencies: All modules (queries health status from every service)

gui - Desktop launchpad:

  • Aggregates all module GUIs
  • Unified desktop interface
  • Dependencies: All modules with GUI components

Dependency Graph

                    ┌──────────────┐
                    │     gui      │ (GUI Aggregator)
                    └──────┬───────┘
                           │ uses all GUI modules
        ┌──────────────────┴──────────────────┐
        │                                      │
   ┌────┴─────┐                         ┌─────┴────┐
   │  system  │                         │ notebook │
   └────┬─────┘                         └─────┬────┘
        │ monitors health of ALL modules       │
   ┌────┴─────────────────────────────────────┴────┐
   │                                                │
   │            ┌──────────────┐                   │
   │            │ application  │                   │
   │            └──────┬───────┘                   │
   │                   │ uses                      │
   │    ┌──────┬───────┼────────┬──────────┐      │
   │    ↓      ↓       ↓        ↓          ↓      │
   │ ┌─────┐┌──────┐┌──────┐┌──────┐┌─────────┐  │
   │ │ wsi ││dataset││bucket││qupath││platform │  │
   │ └──┬──┘└───┬──┘└───┬──┘└───┬──┘└────┬────┘  │
   │    │       │       │       │         │       │
   │    └───────┴───────┴───────┴─────────┘       │
   │                        │                      │
   │                    ┌───┴────┐                 │
   └────────────────────│  utils │─────────────────┘
                        └────────┘
                      (Foundation Layer)

Note: The system module collects health status from ALL modules
in the SDK by calling their health() methods, providing a
comprehensive view of the entire SDK's operational status.

Module Capabilities Matrix

Module Service CLI GUI Purpose
platform Authentication & API client
application ML application orchestration
wsi Medical image processing
dataset Dataset downloads
bucket Cloud storage
utils Core Infrastructure
gui Desktop launchpad
notebook Marimo notebooks
qupath QuPath integration
system Diagnostics

SDK Usage Patterns

Client Library Usage

from aignostics import platform

# Main SDK entry point
client = platform.Client()

# List applications
for app in client.applications.list():
    print(app.application_id)

# Submit run
run = client.runs.create(application_id="heta", files=["slide.svs"])

Service Discovery Pattern

from aignostics.utils import locate_implementations, BaseService

# Find all service implementations dynamically
services = locate_implementations(BaseService)

# Each service provides health and info
for service_class in services:
    service = service_class()
    health = service.health()
    info = service.info(mask_secrets=True)

CLI Usage

# Authentication
aignostics user login

# Application operations
aignostics application list
aignostics application run submit --application-id heta --files "*.svs"

# Dataset downloads
aignostics dataset idc download --collection-id TCGA-LUAD

# WSI processing
aignostics wsi inspect slide.svs

# QuPath integration
aignostics qupath install
aignostics qupath launch --project my_project.qpproj

# System diagnostics
aignostics system health

# MCP server (AI agent integration)
aignostics mcp run
aignostics mcp list-tools

GUI Launch

The GUI (NiceGUI) ships in the core install — there is no [gui] extra. The launcher is gated on find_spec("nicegui"), find_spec("webview"), and not running in a container (see src/aignostics/cli.py).

# Launch the desktop Launchpad
aignostics launchpad

# Or with uvx
uvx aignostics launchpad

Code Standards

Type Checking:

  • MyPy strict mode enforced
  • All public APIs must have type hints
  • Use from __future__ import annotations for forward references

Code Style:

  • Ruff handles all formatting/linting (Black-compatible)
  • 120 character line limit
  • Google-style docstrings required for public APIs

Import Organization:

  • Standard library imports first
  • Third-party imports second
  • Local imports last
  • Use relative imports within modules (from ._service import Service)

Error Handling:

  • Custom exceptions in system/_exceptions.py
  • Use structured logging with correlation IDs
  • HTTP errors wrapped in domain-specific exceptions

Security:

  • OAuth-based authentication via platform/_authentication.py
  • No secrets/tokens in code or commits
  • Signed URLs for data transfer
  • Sensitive data masking in logs and info outputs

Medical Domain Context

This is a computational pathology SDK working with:

  • DICOM medical imaging standards - Medical image format
  • Whole slide images (WSI) - Gigapixel-scale pathology images
  • IDC (Imaging Data Commons) - National Cancer Institute data repository
  • QuPath - Leading bioimage analysis platform
  • Machine learning inference - AI/ML model execution on medical data
  • HIPAA compliance - Medical data privacy requirements

WSI Processing:

  • OpenSlide for standard formats (.svs, .tiff, .ndpi)
  • PyDICOM for DICOM files
  • Support for multi-resolution pyramidal images
  • Tile-based processing for memory efficiency

Build System

Project structure:

aignostics-python-sdk/
├── src/aignostics/      # Source code
├── tests/               # Test suite
├── docs/                # Sphinx documentation
├── pyproject.toml       # Project configuration
├── Makefile            # Build commands
└── CLAUDE.md           # This file

Build configuration — all tool config is inlined in pyproject.toml; there are no standalone ruff.toml, cliff.toml, or .coveragerc files:

  • pyproject.toml - Package metadata, dependencies, and [tool.ruff], [tool.git-cliff], [tool.coverage.run], [tool.pytest.ini_options]
  • noxfile.py - Build sessions; also regenerates the versioned SDK metadata JSON Schema into docs/source/_static/ during doc builds
  • .pre-commit-config.yaml - Git hooks

Development Guidelines

Adding New Modules

  1. Create module directory in src/aignostics/
  2. Implement service layer (_service.py) inheriting from BaseService
  3. Add CLI commands (_cli.py) using Typer
  4. Add GUI interface (_gui.py) using NiceGUI (optional)
  5. Create settings (_settings.py) with Pydantic
  6. Write comprehensive CLAUDE.md documentation
  7. Add tests in tests/aignostics/<module>/
  8. Update module index in src/aignostics/CLAUDE.md

Service Implementation Pattern

from aignostics.utils import BaseService, Health


class Service(BaseService):
    """Module service implementation."""

    def health(self) -> Health:
        """Health check implementation."""
        return Health(status=Health.Code.UP)

    def info(self, mask_secrets: bool = True) -> dict:
        """Service information."""
        return {"version": "1.0.0"}

CLI Pattern

import typer
from ._service import Service

cli = typer.Typer(name="module", help="Module description")


@cli.command("action")
def action_command(param: str):
    """Action description."""
    service = Service()
    result = service.perform_action(param)
    console.print(result)

Testing Requirements

  • Coverage targets defined in codecov.yml
  • Unit tests for all public methods
  • Integration tests for CLI commands
  • Mock external dependencies
  • Use fixtures from conftest.py

Important Notes

Module Loading

nicegui, marimo, and ijson are core dependencies, so modules using them load unconditionally. The Launchpad CLI command is still gated at runtime on find_spec("nicegui"), find_spec("webview"), and not running in a container (see src/aignostics/cli.py).

Platform Authentication

  • Token cached in ~/.aignostics/token.json
  • Format: token:expiry_timestamp
  • 5-minute refresh buffer before expiry
  • OAuth 2.0 device flow

SDK Metadata System

Every run and item submitted through the SDK gets custom metadata attached automatically: submission context (script/CLI/launchpad, initiator), CI/CD + pytest context, authenticated user/org, tags, and timestamps. Validated against versioned Pydantic-derived JSON Schemas published under docs/source/_static/.

  • Implementation: platform/_sdk_metadata.py (build_*/validate_*/get_*_json_schema functions)
  • Schema version constants: SDK_METADATA_SCHEMA_VERSION / ITEM_SDK_METADATA_SCHEMA_VERSION in that file — do not hardcode the numbers here
  • Integration: automatic in run submit; user agent built by utils.user_agent()
  • CLI: aignostics sdk metadata-schema, aignostics application run custom-metadata update|dump

See platform/CLAUDE.md for the full field list and CLI details.

Operation Caching & Retry System

platform/_operation_cache.py provides per-user (token-aware) caching of read operations with per-kind TTLs and full invalidation on mutations, plus tenacity-based retry with exponential backoff + jitter for transient errors (5xx, timeouts, pool/connection errors). Read calls accept nocache=True to bypass the cache. TTLs, retry attempts, and timeouts are configurable via AIGNOSTICS_* env vars.

See platform/CLAUDE.md for cached-operation list, env var names, and patterns.

Run / Item / Artifact State Models

State is enum-based: RunState / ItemState / ArtifactState (PENDING → PROCESSING → TERMINATED), paired with termination-reason enums (RunTerminationReason, ItemTerminationReason, ArtifactTerminationReason) that separate "what happened" from "why". RunItemStatistics carries aggregate counts; RunOutput/ItemOutput/ArtifactOutput bundle state + reason.

See platform/CLAUDE.md for the state machine and model definitions.

Testing Workflow

Test Suite Organization

The SDK has a comprehensive test suite organized by test type and execution strategy.

Pytest Configuration: default timeout 10s per test, async mode auto, coverage via pytest-cov, parallel execution via pytest-xdist. Full config is in [tool.pytest.ini_options] in pyproject.toml.

Test Markers: the authoritative marker definitions (with their descriptions) live in the markers = [...] list under [tool.pytest.ini_options] in pyproject.toml — read them there rather than duplicating here.

IMPORTANT: Every test MUST carry at least one of unit, integration, or e2e — CI only runs tests with these category markers, so an unmarked test silently never runs. See "Finding Unmarked Tests" below.

The other markers gate execution: long_running / very_long_running (excluded from make test; run via make test_long_running etc.; skippable/enable-able via PR labels skip:test:long_running / enable:test:very_long_running), scheduled / scheduled_only, stress / stress_only, sequential, docker, skip_with_act, no_extras.

Tests live under tests/aignostics/<module>/ mirroring the source layout; see tests/CLAUDE.md.

Running Tests

Quick commands:

# Run all default tests (unit + integration + e2e, no long_running)
make test

# Run specific test types
make test_unit              # Unit tests only
make test_integration       # Integration tests only
make test_e2e               # E2E tests (requires .env with credentials)

# Run tests with specific markers
make test_sequential        # Sequential tests only
make test_long_running      # Long-running tests
make test_scheduled         # Scheduled tests

# Run on specific Python version
make test 3.12              # Python 3.12
make test 3.13              # Python 3.13
make test 3.14              # Python 3.14

Direct pytest commands:

# Run single test file
uv run pytest tests/aignostics/platform/sdk_metadata_test.py -v

# Run specific test function
uv run pytest tests/aignostics/platform/sdk_metadata_test.py::test_build_sdk_metadata_minimal -v

# Run with markers
uv run pytest -m "unit and not long_running" -v

# Run with coverage
uv run pytest --cov=src/aignostics --cov-report=term-missing

# Debug mode (with pdb)
uv run pytest tests/test_file.py --pdb

# Show print statements
uv run pytest tests/test_file.py -s

# Verbose output
uv run pytest tests/test_file.py -vv

Test Parallelization

The test suite uses pytest-xdist for parallel execution with intelligent distribution:

Configuration (noxfile.py):

# Worker factors control parallelism
XDIST_WORKER_FACTOR = {
    "unit": 0.0,  # No parallelization (fast, no overhead needed)
    "integration": 0.2,  # 20% of logical CPUs
    "e2e": 1.0,  # 100% of logical CPUs (I/O bound)
    "default": 1.0,  # 100% for mixed test runs
}

# Calculate workers: max(1, int(cpu_count * factor))
# Example: 8 CPU machine
#   unit: 1 worker (sequential)
#   integration: max(1, int(8 * 0.2)) = 1 worker
#   e2e: max(1, int(8 * 1.0)) = 8 workers

Parallel vs Sequential:

# Parallel tests (most tests)
uv run pytest -n logical --dist worksteal tests/

# Sequential tests (marked with @pytest.mark.sequential)
uv run pytest -m sequential tests/

Why different factors?

  • Unit tests (0.0): Fast enough that parallelization overhead hurts performance
  • Integration tests (0.2): Some I/O but mostly CPU-bound, limited parallelism
  • E2E tests (1.0): Network I/O bound, full parallelization maximizes throughput

Coverage Requirements

Thresholds are enforced by Codecov (see codecov.yml), not a local --fail-under. Coverage settings — source paths, omit list, branch/parallel mode — live in [tool.coverage.run] in pyproject.toml.

# Coverage is collected automatically by `make test` (pytest-cov)
uv run coverage report          # text summary
uv run coverage html            # HTML report → reports/coverage_html/

E2E Test Setup

E2E tests require credentials to run against staging environment:

Required .env file:

# Create .env in repository root
AIGNOSTICS_API_ROOT=https://platform-staging.aignostics.com
AIGNOSTICS_CLIENT_ID_DEVICE=your-staging-client-id
AIGNOSTICS_REFRESH_TOKEN=your-staging-refresh-token

In CI/CD:

  • GitHub Actions secrets automatically populate .env
  • Uses AIGNOSTICS_CLIENT_ID_DEVICE_STAGING and AIGNOSTICS_REFRESH_TOKEN_STAGING
  • GCP credentials for bucket access also configured

Running E2E locally:

# Ensure .env exists with staging credentials
make test_e2e

# Or with pytest directly
uv run pytest -m "e2e and not long_running" -v

Pytest Configuration Details

Full config is [tool.pytest.ini_options] in pyproject.toml. Non-obvious points worth knowing:

  • --strict-markers — an unregistered marker is an error, so add new markers to the markers list in pyproject.toml first.
  • -p nicegui.testing.plugin is always loaded (GUI test support).
  • Coverage runs with subprocess collection via env vars COVERAGE_FILE and COVERAGE_PROCESS_START=pyproject.toml; branch + parallel (thread + multiprocessing) mode are on — so run make test_coverage_reset if .coverage data gets stale/corrupted.
  • Markdown report (md_report) is written to reports/pytest.md showing only failures/errors.

Test Fixtures and Patterns

Key fixtures (conftest.py):

  • Environment isolation (HOME, config dirs)
  • Mocked responses for API calls
  • Temporary file creation
  • Authentication mocking

Example test pattern:

import pytest
from unittest.mock import patch


@pytest.mark.unit
def test_sdk_metadata_minimal(monkeypatch):
    """Test SDK metadata with clean environment."""
    # Isolate environment
    monkeypatch.delenv("GITHUB_ACTIONS", raising=False)
    monkeypatch.delenv("PYTEST_CURRENT_TEST", raising=False)

    # Run test
    result = build_sdk_metadata()

    # Assertions
    assert result.submission.date is not None
    assert result.user_agent is not None

See tests/CLAUDE.md for comprehensive testing patterns and examples.

Finding Unmarked Tests

Critical: To find tests missing category markers (which will NOT run in CI):

# Find all tests without unit/integration/e2e markers
uv run pytest -m "not unit and not integration and not e2e" --collect-only

# This should return 0 tests if all are properly marked
# If tests are found, they are missing required markers

Why this works: The marker expression matches tests that don't have any of the required category markers.

Add to pre-commit checks:

# Verify no unmarked tests exist
if uv run pytest -m "not unit and not integration and not e2e" --collect-only 2>&1 | grep -q "collected 0 items"; then
    echo "✅ All tests have category markers"
else
    echo "❌ Found tests without category markers - they will NOT run in CI!"
    exit 1
fi

Development Workflow

Initial Setup

# Clone repository
git clone https://github.com/aignostics/python-sdk.git
cd python-sdk

# Install uv (if not installed)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Install all dependencies including dev tools
make install
# This runs: uv sync --all-extras + installs pre-commit hooks

# Verify installation
uv run aignostics --version

Development Cycle

1. Create Feature Branch

# From main branch
git checkout main
git pull origin main

# Create feature branch
git checkout -b feat/my-feature

# Or bugfix branch
git checkout -b fix/bug-description

2. Make Changes and Validate

# Run linting (this is fast, run frequently)
make lint
# Runs: ruff format, ruff check, pyright, mypy

# Run tests
make test
# Or specific test types
make test_unit           # Fast unit tests only
make test_integration    # Integration tests

# Full validation (what CI runs)
make all
# Runs: lint + test + docs + audit (~20 minutes)

3. Pre-commit Hooks (Automatic)

The repository uses pre-commit hooks installed by make install:

# .pre-commit-config.yaml
hooks:
  - ruff formatting check
  - ruff linting check
  - mypy type checking
  - trailing whitespace removal
  - end-of-file fixer
  - yaml validation

Skip hooks only if necessary:

git commit --no-verify -m "WIP: debugging"

4. Commit Convention

Use conventional commits for automatic changelog generation:

# Feature
git commit -m "feat(platform): add operation caching system"

# Bug fix
git commit -m "fix(application): handle missing artifact states"

# Documentation
git commit -m "docs: update testing workflow in CLAUDE.md"

# Refactor
git commit -m "refactor(wsi): simplify thumbnail generation"

# Test
git commit -m "test(platform): add SDK metadata validation tests"

# Chore
git commit -m "chore: bump dependencies"

Types: feat, fix, docs, refactor, test, chore, ci, perf, build

5. Push and Create PR

# Push to remote
git push origin feat/my-feature

# Create PR (via gh cli or GitHub UI)
gh pr create --title "feat: add operation caching" --body "Description..."

# IMPORTANT: Add label to skip long-running tests
gh pr edit --add-label "skip:test:long_running"

PR triggers:

  • Lint checks (~5 min)
  • Security audit (~3 min)
  • Test matrix on Python 3.11, 3.12, 3.13, 3.14 (~15 min)
  • CodeQL security scanning (~10 min)
  • Claude Code automated review (~10 min)
  • Ketryx compliance reporting

6. Address Review Feedback

# Make changes
git add .
git commit -m "fix: address review comments"
git push origin feat/my-feature

# CI re-runs automatically

7. Merge PR

  • Ensure all CI checks pass (green checkmarks)
  • Get approval from maintainer
  • Squash and merge (default) or merge commit
  • Delete feature branch after merge

Build System (Nox)

The SDK uses Nox for build automation with uv integration:

Key Nox sessions:

# Lint session (ruff format + check + pyright + mypy)
uv run nox -s lint

# Audit session (pip-audit + pip-licenses + SBOMs)
uv run nox -s audit

# Test session (pytest with coverage)
uv run nox -s test           # Default markers
uv run nox -s test -- -m unit  # Specific markers

# Test matrix (all Python versions)
uv run nox -s test-3.11
uv run nox -s test-3.12
uv run nox -s test-3.13
uv run nox -s test-3.14

# Documentation
uv run nox -s docs           # Build Sphinx docs

# Setup session (install all dev tools)
uv run nox -s setup

# Version bumping
uv run nox -s bump -- patch  # 1.0.0 -> 1.0.1
uv run nox -s bump -- minor  # 1.0.0 -> 1.1.0
uv run nox -s bump -- major  # 1.0.0 -> 2.0.0

Makefile wraps Nox for convenience:

make lint      → uv run nox -s lint
make test      → uv run nox -s test
make docs      → uv run nox -s docs
make audit     → uv run nox -s audit
make all       → all of the above

Adding Dependencies

Runtime dependency:

# Add to main dependencies
uv add requests

# Add with version constraint
uv add "httpx>=0.25.0"

# Update pyproject.toml automatically

Development dependency:

# Add to dev dependencies
uv add --dev pytest-mock

# Or specific group
uv add --group docs sphinx-rtd-theme

Optional dependency group:

# Edit pyproject.toml [project.optional-dependencies]
# (current extras: pyinstaller, jupyter, marimo, qupath)

# Install with a single extra, or all of them
uv sync --extra jupyter
uv sync --all-extras

Version Bumping and Releases

Releases follow a four-phase GitHub workflow–based strategy that allows Ketryx compliance approvals to be collected before publishing:

Prerequisite (anytime): Create the Ketryx release in the Ketryx portal
Phase 1:                make prepare-release x.y.z
Phase 2:                Point Ketryx release to release/vX.Y.Z; collect approvals
Phase 3:                make publish-release
Phase 4:                make merge-release

Phase 1 — Prepare the release branch:

# Creates release/vX.Y.Z branch from main, bumps version files, and pushes.
# No tag is created yet.
make prepare-release 1.2.3   # explicit version

This triggers prepare-release.yml on GitHub Actions, which:

  1. Creates release/vX.Y.Z branch from main
  2. Runs bump-my-version (commits version files + uv.lock)
  3. Pushes the branch — CI runs lint/test/audit on it

Phase 2 — Collect Ketryx approvals:

Point the Ketryx release to the release/vX.Y.Z branch and collect required approvals. CI must be green on the branch before proceeding.

Phase 3 — Publish (tag + PyPI):

# Generates CHANGELOG.md, creates vX.Y.Z tag, pushes → triggers CI/CD publish.
make publish-release

# Optionally specify a branch explicitly:
make publish-release release/v1.2.3

This triggers publish-release.yml, which:

  1. Generates CHANGELOG.md for the release range
  2. Commits the changelog
  3. Creates and pushes the annotated vX.Y.Z tag
  4. CI/CD fires on the tag; Ketryx check must pass before PyPI publish

Phase 4 — Merge back to main:

# Merges the release branch into main (--no-ff) and deletes the branch.
make merge-release

# Optionally specify a branch explicitly:
make merge-release release/v1.2.3

This triggers merge-release.yml, which:

  1. Merges release/vX.Y.Z into main with --no-ff
  2. Pushes main
  3. Deletes the remote release branch

What triggers CI/CD:

make prepare-release  → push to release/vX.Y.Z  → lint + test + audit + Ketryx
make publish-release  → push vX.Y.Z tag          → full CI + PyPI + Docker + GitHub release
make merge-release    → push to main              → full CI pipeline

CI/CD Integration

See .github/CLAUDE.md for comprehensive CI/CD documentation including:

  • Complete workflow architecture
  • Claude Code automation (PR reviews, interactive sessions)
  • Environment configuration (staging/production)
  • Scheduled testing (6h staging, 24h production)
  • Debugging failed CI runs
  • Secrets management

Quick CI reference:

# Skip CI for commit
git commit -m "docs: update README [skip ci]"

# Or with skip:ci in commit message
git commit -m "skip:ci: work in progress"

# Add PR label to skip long-running tests
gh pr edit --add-label "skip:test:long_running"

IDE Setup Recommendations

VS Code (.vscode/settings.json):

{
  "python.defaultInterpreterPath": ".venv/bin/python",
  "python.testing.pytestEnabled": true,
  "python.testing.pytestArgs": ["-v"],
  "python.linting.enabled": true,
  "python.linting.ruffEnabled": true,
  "python.formatting.provider": "ruff",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.organizeImports": true
  }
}

PyCharm:

  • Configure Python interpreter: .venv/bin/python
  • Enable pytest as test runner
  • Set up ruff as external tool
  • Configure mypy plugin for type checking

Repo-specific commands worth remembering

  • Find unmarked tests (they will NOT run in CI — every test needs a unit/integration/e2e marker): uv run pytest -m "not unit and not integration and not e2e" --collect-only (expect "collected 0 items").
  • Reset corrupted coverage data: make test_coverage_reset, then re-run make test.
  • Generated reports land in reports/: pytest.md (failures only), coverage.xml, coverage_html/.
  • Type checkers are split — check both when make lint fails: uv run mypy <path> and uv run pyright <path> (exclusions in pyrightconfig.json).

Performance Considerations

  • Chunked uploads/downloads, streaming for large files
  • Memory-efficient WSI tile processing (process in tiles, not full image)

Common Pitfalls

  1. Import errors: run uv sync --all-extras
  2. Token expiry: force refresh with remove_cached_token()
  3. WSI memory: process in tiles, not full image
  4. Platform differences: watch Windows path lengths

This documentation provides comprehensive guidance for working with the Aignostics Python SDK. Each module has detailed CLAUDE.md files with implementation specifics, usage examples, and best practices.

SDLC Configuration

  • JIRA Project Key: PYSDK
  • Atlassian Cloud ID: fff788d2-8a2a-4c36-a884-dde2bb4a2b49
  • Ketryx Project: Python SDK (KXPRJ2Q4PA8AADY975SFMKF276TYV75)

Item type locations:

  • Change Requests: Ketryx Change Request (JIRA)
  • Stakeholder Requirements: requirements/SHR-*.md (Git)
  • Software Requirements: Requirement (JIRA) | requirements/SWR-*.md (Git, preferred)
  • Risks: Risk (JIRA)
  • Software Item Specs: Software Item Spec (JIRA) | specifications/SPEC-*.md (Git, preferred)
  • Test Cases: tests/**/TC-*.feature (Git)

Approval structure:

  • Ketryx Change Request: Product Manager → Engineering Tech Lead → Quality Managers → Risk Managers
  • Software Requirement: Area Lead (BD) → Security Engineer → Regulatory Affairs Specialist → Engineer → Engineering Tech Lead → Product Manager
  • Software Item Spec: Engineer → Engineering Tech Lead
  • Risk: Quality Managers → Security Engineer → Product Managers → Tech Lead → Engineer → Engineering Tech Lead → Product Manager → Engineer
  • Test Case: Engineer → Engineering Tech Lead
  • Anomaly: Engineer → Engineering Tech Lead