Source code for iiisight.image

"""Parse an Image API ``info.json`` into a loaded :class:`~iiisight.model.ImageService`.

Handles both v2 and v3 ``info.json`` shapes: v3 carries limits and features at the
top level with a plain ``profile`` string, while v2 packs the compliance-level URI
and the limits/formats/qualities/supports into a ``profile`` array. Both fold into
the same unified :class:`ImageService`, with ``loaded=True`` and the tile/size
pyramid populated so the URL and tile helpers can run.
"""

from collections.abc import Mapping
from typing import Any

from .model import ImageService, Size, TileSet


[docs] def parse_info(info: Mapping[str, Any]) -> ImageService: """Build a loaded :class:`ImageService` from a parsed ``info.json`` document.""" service_id = str(info.get("id") or info.get("@id") or "") api_version, level, profile_extras = _version_level_extras(info) tiles = tuple( TileSet( width=int(entry["width"]), height=int(entry["height"]) if "height" in entry else None, scale_factors=tuple(int(factor) for factor in entry.get("scaleFactors", [1])), ) for entry in info.get("tiles", []) if isinstance(entry, Mapping) and "width" in entry ) sizes = tuple( Size(width=int(entry["width"]), height=int(entry["height"])) for entry in info.get("sizes", []) if isinstance(entry, Mapping) and "width" in entry and "height" in entry ) return ImageService( id=service_id, api_version=api_version, compliance_level=level, loaded=True, protocol=info.get("protocol"), width=_maybe_int(info.get("width")), height=_maybe_int(info.get("height")), max_width=_maybe_int(info.get("maxWidth", profile_extras.get("maxWidth"))), max_height=_maybe_int(info.get("maxHeight", profile_extras.get("maxHeight"))), max_area=_maybe_int(info.get("maxArea", profile_extras.get("maxArea"))), sizes=sizes, tiles=tiles, preferred_formats=tuple(info.get("preferredFormats", [])), extra_formats=tuple(info.get("extraFormats", profile_extras.get("formats", []))), extra_qualities=tuple(info.get("extraQualities", profile_extras.get("qualities", []))), extra_features=tuple(info.get("extraFeatures", profile_extras.get("supports", []))), )
def _version_level_extras(info: Mapping[str, Any]) -> tuple[int, str | None, Mapping[str, Any]]: """Return ``(api_version, compliance_level, v2_profile_extras)`` from info.json.""" context = info.get("@context") context_text = " ".join(context if isinstance(context, list) else [context or ""]) profile = info.get("profile") level: str | None = None extras: Mapping[str, Any] = {} if isinstance(profile, list): for entry in profile: if isinstance(entry, str) and level is None: level = _level_from(entry) elif isinstance(entry, Mapping): extras = entry elif isinstance(profile, str): level = _level_from(profile) if "/image/3" in context_text or info.get("type") == "ImageService3": version = 3 elif "/image/2" in context_text or isinstance(profile, list): version = 2 elif isinstance(profile, str) and "/image/" in profile: version = 2 else: version = 3 return version, level, extras def _level_from(profile: str) -> str | None: """Extract ``levelN`` from a plain ``levelN`` string or a v2 profile URI.""" if profile.startswith("level"): return profile tail = profile.rstrip("/").rsplit("/", 1)[-1].split(".", 1)[0] return tail if tail.startswith("level") else None def _maybe_int(value: Any) -> int | None: if value is None: return None try: return int(value) except (TypeError, ValueError): return None