Metadata-Version: 2.4
Name: iiisight
Version: 0.2
Summary: A tolerant, async (and sync) Python client for consuming IIIF.
Author: Roger Howard
License-Expression: Apache-2.0
Project-URL: Repository, https://gitlab.com/iiiris-org/iiisight
Keywords: iiif,manifest,presentation-api,image-api,async
Classifier: Development Status :: 2 - Pre-Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Multimedia :: Graphics
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
License-File: NOTICE
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
Requires-Dist: pytest-cov>=5; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Provides-Extra: prezi3
Requires-Dist: iiif-prezi3; extra == "prezi3"
Provides-Extra: docs
Requires-Dist: sphinx>=7; extra == "docs"
Requires-Dist: myst-parser>=3; extra == "docs"
Requires-Dist: furo; extra == "docs"
Requires-Dist: sphinx-design; extra == "docs"
Dynamic: license-file

# iiisight

[![PyPI version](https://img.shields.io/pypi/v/iiisight.svg)](https://pypi.org/project/iiisight/)
[![Python versions](https://img.shields.io/pypi/pyversions/iiisight.svg)](https://pypi.org/project/iiisight/)
[![License: Apache-2.0](https://img.shields.io/pypi/l/iiisight.svg)](https://gitlab.com/iiiris-org/iiisight/-/blob/main/LICENSE)
[![Pipeline status](https://gitlab.com/iiiris-org/iiisight/badges/main/pipeline.svg)](https://gitlab.com/iiiris-org/iiisight/-/pipelines)
[![Documentation](https://readthedocs.org/projects/iiisight/badge/?version=latest)](https://iiisight.readthedocs.io/)

> **Goal:** a reusable Python package for building applications that *consume* IIIF — the read-side substrate for viewers, search, harvesting, and conformance tooling.

**A tolerant, async Python client for *consuming* IIIF.** Fetch a manifest or collection, normalize v2→v3 so you work against one model, walk canvases / ranges / annotations, and pull tile-aware image regions — behind a single typed interface.

## Why

Python's IIIF tooling is real but fragmented — split by concern and by spec version, and all of it synchronous:

- **[iiif-prezi3](https://github.com/iiif-prezi/iiif-prezi3)** is the *construction* library — it builds (and can load static) Presentation **v3** documents. It doesn't fetch from URLs, doesn't handle v2, isn't async, and offers no traversal or Image API helpers.
- **[piffle](https://github.com/Princeton-CDH/piffle)** parses/builds Image API URLs and can `from_url()` a manifest and iterate canvases — the closest thing to a client — but it's **sync**, does no v2→v3 normalization, has thin range/annotation traversal, and stops short of tile-aware region math.
- **[iiif-prezi](https://github.com/iiif-prezi/iiif-prezi)** (v2 reference) and **[prezi-2-to-3](https://github.com/IIIF/prezi-2-to-3)** (standalone v2→v3 upgrade) cover the older version and the upgrade in isolation.

Every *piece* exists somewhere; no single library assembles them for a **consumer**. There is no async, tolerant, version-agnostic **read** model with tile-aware retrieval — the shape the JS ecosystem gets from [`@iiif/parser`](https://github.com/IIIF-Commons/parser). That integration is the gap iiisight fills. Two of its capabilities — **async fetching** and **tile-aware Image API retrieval** — exist nowhere else in Python today.

## Positioning

iiisight's identity is the **read/consume** side of IIIF, done well in **one place**. Overlapping existing tools is fine — even desirable — when consolidating a capability into the same tolerant, async client makes it better or cheaper than today's fragmented status quo. The test for pulling something in is not *"does another tool already do this?"* but **"does iiisight's existing machinery make the unified version genuinely better, and does it stay read-shaped?"** By that test an [Image API conformance probe](#validation) (we already fetch `info.json` and build region requests) and a consumer-oriented manifest lint (our normalizer already locates every deviation it repairs) are natural extensions, not scope creep.

Reuse vs. reinvent is then a pure engineering call, not deference: reuse (e.g. iiif-prezi3's v3 types, prezi-2-to-3's upgrade) when the dependency is clean and saves maintenance; **own the layer** when its constraints — sync, write-oriented model, low tolerance — would cap how good the unified tool can be. Consolidating "better in one place" will often favor owning more of the stack so tolerance, async, and diagnostics stay first-class.

## Design pillars

- **Read-first** — consuming IIIF, not authoring it. Building manifests is iiif-prezi3's job.
- **Async-first, sync-supported** — non-blocking fetches for manifests, collections, `info.json`, and tiles (`httpx`), with a first-class sync `Client` for the many GLAM/library-tech consumers on sync stacks. Both share one pure, I/O-free core.
- **Tolerant** — accept real-world, spec-imperfect documents; degrade gracefully instead of raising on the first surprise.
- **Normalized** — present one model regardless of source version; upgrade Presentation v2 → v3 under the hood.
- **Ergonomic traversal** — walk collections → manifests → canvases → ranges → annotations without hand-parsing JSON-LD.
- **Tile-aware Image API** — read a service's `info.json` and compute correct region/size/tile requests rather than fetching full-resolution images.

## IIIF API coverage

IIIF is a family of six APIs. As a *consume*-side client, iiisight can support all of them to varying depth — core delivery now, the rest as read-shaped layers over the same machinery:

| IIIF API | Stable | iiisight support | Tier |
|---|---|---|---|
| **[Presentation](https://iiif.io/api/presentation/3.0/)** | 3.0 (4.0 RC) | Full — fetch, tolerant parse, v2→v3 normalize, traverse | **Core** |
| **[Image](https://iiif.io/api/image/3.0/)** | 3.0 (+2.x) | Full — `info.json` parse, URL build/parse, tile-aware retrieval | **Core** |
| **[Content Search](https://iiif.io/api/search/2.0/)** | 2.0 (+1.0) | Issue search to the service, parse hits as annotations (reuses our model) | Extension |
| **[Content State](https://iiif.io/api/content-state/1.0/)** | 1.0 | Decode/encode a resource/region reference, resolve → fetch | Extension |
| **[Change Discovery](https://iiif.io/api/discovery/1.0/)** | 1.0 | Consume Activity-Streams `OrderedCollection` for harvesting/sync | Adjacent |
| **[Auth Flow](https://iiif.io/api/auth/2.0/)** | 2.0 (+1.0) | **Partial** — model the auth services + drive a non-interactive token flow; interactive login stays the host app's job | Later |

Version handling: Presentation **2.0/2.1 → normalized to 3.0**; the **4.0 release candidate** informs forward-compatible modeling but 3.0 is the normalization target until 4.0 is stable. Image API **2.x and 3.0** URL/`info.json` syntaxes are both understood.

Two honest boundaries: **Auth is inherently partial** for a library — the interactive login step needs a browser/UI only the host app can own. And **server sides stay out** — iiisight is a client *of* Image / Search / Auth endpoints, never an implementation of one.

<a name="validation"></a>
## Validation & conformance

Overlap here is welcome, built as **optional layers over the same client** once the core read path is solid — not as separate tools:

- **Image API conformance probe** — the natural fit, because Image API validation *is* a client making requests to a service, which iiisight's tile-aware layer already does. A strict "probe this endpoint against its claimed compliance level" mode reuses the fetch machinery directly.
- **Consumer-oriented manifest lint** — a byproduct of tolerance: the normalizer records structured diagnostics (missing field → assumed default, wrong `type` → coerced, v2 → upgraded, bad URL → skipped) as it repairs a document, so it can report *"what a real client had to guess or fix to read this."* This is client-compatibility feedback, distinct from (and complementary to) strict spec conformance.

The client core stays **tolerant**; validation is a separate reporting posture over the same parse, never a stricter default that would make the client reject real-world documents.

## Non-goals

The one deliberate exclusion is **authoring** — building and serializing new manifests is a different posture (write vs. read) and audience; iiif-prezi3 owns it and iiisight stays a consumer. iiisight is likewise a *client* of Image / Search / Auth endpoints, never a **server** implementation of any of them.

## Distribution

iiisight is a **pure-Python library published to PyPI** — no compiled extensions, so it installs as a single universal wheel everywhere Python runs:

```bash
pip install iiisight
```

- **Library-first**, not an application or service: you `import iiisight` and bring your own event loop; there is no server or daemon.
- **Lean footprint** — one runtime dependency (`httpx`). Optional features live behind extras, e.g. `iiisight[prezi3]` (iiif-prezi3 interop).
- **Built-in CLI** — installing the package registers the `iiisight` command (stdlib-only, no extra needed): `iiisight lint <url>`, `iiisight info <url>`, `iiisight tiles <url>`.
- **Releases** are git-tag driven (setuptools-scm → `python -m build` → OIDC trusted publishing); see `CLAUDE.md`.

## Usage

Full documentation — installation, a quickstart, task-oriented guides, and the API
reference — lives on **[Read the Docs](https://iiisight.readthedocs.io/)**. A tour
of the main workflows follows; everything is available in both async (flagship) and
sync forms — `AsyncClient` / `Client` mirror `httpx`'s naming, and every
`await client.…` has a no-`await` twin.

### Fetch and traverse

```python
import iiisight

async with iiisight.AsyncClient() as client:
    manifest = await client.fetch_manifest("https://example.org/manifest.json")
    print(str(manifest.label))                       # LanguageMap -> display string
    for canvas in manifest.canvases:
        print(canvas.width, canvas.height, [img.id for img in canvas.images])
    for entry in manifest.structures:                # ranges / table of contents
        print(str(entry.label))

# sync equivalent — same methods, no await
with iiisight.Client() as client:
    manifest = client.fetch_manifest("https://example.org/manifest.json")
```

v2 and v3 manifests normalize to the same model; a v2 document is upgraded under
the hood. Network I/O is always explicit — `expand()` resolves a collection's
references, `load_info()` fetches an image service's `info.json`:

```python
collection = await client.fetch_collection("https://example.org/collection.json")
manifests = await client.expand(collection)          # fetch referenced manifests
```

### Tile-aware Image API retrieval

```python
service = await client.load_info(manifest.canvases[0])   # fetch info.json
service.url(region="full", size="max")                   # spec-correct Image API URL
service.thumbnail_url(width=400)                          # prefers an advertised size
for tile in service.tile_grid(scale_factor=4):           # a full tile grid at a zoom
    print(tile.region, tile.size, tile.url)
data = await client.fetch_region(service, region="0,0,512,512", size="256,")
```

### Content Search

```python
page = await client.search(manifest, "cathedral")        # discovers the search service
for hit in page.items:
    print(hit.target, hit.body)
```

### Content State

```python
state = iiisight.content_state.decode(token)             # bare URI, JSON, or base64url
doc = await client.resolve(state.reference)              # fetch what it points at
token = iiisight.content_state.encode("https://example.org/canvas/1", region="xywh=0,0,50,50")
```

### Auth Flow 2.0 (metadata + non-interactive)

```python
probe = iiisight.find_probe_service(manifest.canvases[0])
if probe:                                                # resource is access-controlled
    result = await client.probe(probe)                   # check access
    if not result.accessible and probe.token_service:
        token = await client.access_token(probe.token_service)   # non-interactive only
        result = await client.probe(probe, token=token.access_token)
```

Interactive browser login is the host application's job; iiisight supplies the
service metadata to drive it.

### Lint

```python
report = iiisight.lint(manifest)          # what a real client had to repair
print(report.summary())                   # "1 error(s), 2 warning(s), 3 info"
for problem in report.errors + report.warnings:
    print(problem.severity, problem.code, problem.path, problem.message)
```

### Command line

Installing the package registers the `iiisight` command (a URL or local JSON file
works for each):

```console
$ iiisight lint https://example.org/manifest.json     # exits non-zero on errors
$ iiisight info manifest.json                          # summarize a document
$ iiisight tiles https://example.org/manifest.json --scale 4
```

## Status

Working, pre-1.0 (see `PLAN.md` for phase-by-phase status). **Done:** the tolerant
normalizer + typed model, the v2→v3 upgrade, async/sync fetch clients, tile-aware
Image API retrieval (validated against live servers), Content Search, Content
State, Auth Flow 2.0, and the consumer lint. **Still to come:** the Image API
conformance probe, Change Discovery, an optional CLI, and the first PyPI release
(the name **`iiisight` is available**). The public API may still change before 1.0.

## License

Apache-2.0 — see [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE).
