terria-catalog
A Pythonic SDK for creating, modifying, validating and publishing TerriaJS initialization catalogs.
terria-catalog lets you build and edit Terria init files as strongly-typed
Python objects — CatalogGroup, GeoJsonCatalogItem,
WebMapServiceCatalogItem, and friends — instead of hand-editing JSON or juggling
dictionaries. It mirrors Terria's own terminology and object model (derived
directly from the TerriaJS source Traits classes), and produces valid Terria
init JSON that is compatible with the official specification.
It is designed to be the canonical way our Digital Twin ecosystem creates and modifies catalogs stored in Amazon S3 — but it has no hard dependency on S3 (or even on any I/O): storage and serialization are cleanly decoupled and injectable.
from terria_catalog import Catalog, GeoJsonCatalogItem
catalog = Catalog.from_s3(bucket="my-bucket", key="init/catalog.json")
catalog.add(
GeoJsonCatalogItem(
name="Reservoirs",
id="reservoirs",
url="https://example.org/reservoirs.geojson",
),
path="/Water Resources/Reservoirs", # missing groups are auto-created
)
catalog.move("reservoirs", "/Water Resources")
catalog.update("reservoirs", description="Major reservoirs in the basin")
catalog.remove("some-old-dataset")
catalog.publish() # validates, serializes, and writes back to S3
You never touch JSON. You never touch dictionaries. The JSON exists only inside the serializer.
Installation
pip install terria-catalog # core (no cloud dependencies)
pip install 'terria-catalog[s3]' # + boto3 for the S3 storage backend
Requires Python 3.10+.
Core concepts
| Concept | Type | Notes |
|---|---|---|
| The whole init file | Catalog |
Facade: holds the member tree, homeCamera, and other init-level properties. |
| A folder | CatalogGroup ("group") |
First-class object; nestable; add/remove members through it. |
| A dataset | CatalogItem subclasses |
e.g. GeoJsonCatalogItem, WebMapServiceCatalogItem. |
| A shared trait | Rectangle, InfoSection, GeoJsonStyle, TableStyle, … |
Plain typed value objects. |
| Where it's stored | StorageBackend (LocalStorage, S3Storage) |
Injected; the catalog doesn't care where it lives. |
| Turning objects into JSON | TerriaJsonSerializer |
The only place JSON exists. |
Supported item types
Fully modeled (with type-specific traits):
GeoJsonCatalogItem · WebMapServiceCatalogItem · WebMapTileServiceCatalogItem
· CsvCatalogItem · ShapefileCatalogItem · CogCatalogItem ·
CesiumTerrainCatalogItem · Cesium3DTilesCatalogItem · IonImageryCatalogItem
· ArcGisMapServerCatalogItem · ArcGisFeatureServerCatalogItem
Placeholders (correct type, traits not yet modeled — data still round-trips):
KmlCatalogItem, CzmlCatalogItem, GpxCatalogItem, GltfCatalogItem,
WebFeatureServiceCatalogItem, OpenStreetMapCatalogItem,
MapboxVectorTileCatalogItem.
Any unknown Terria type encountered when loading a catalog becomes a
GenericCatalogMember so no data is ever lost on a load/save round-trip.
The public API
Loading
from terria_catalog import Catalog
Catalog.from_s3(bucket="b", key="init/catalog.json") # needs [s3] extra
Catalog.from_file("wwwroot/init/catalog.json")
Catalog.from_json(json_string)
Catalog() # a new, empty catalog
Editing (paths or ids everywhere)
catalog.add(item, path="/Water Resources/Reservoirs") # returns the item
catalog.remove("dataset-id") # id, name, or path
catalog.update("dataset-id", description="...", opacity=0.6)
catalog.move("dataset-id", "/Infrastructure/Dams")
catalog.reorder("/Water Resources", ["reservoirs", "gauges"])
Identifiers are resolved as a path if they contain /, otherwise by id
(then by name). Unknown keyword arguments to update() are stored in the
member's extra mapping, so you can set traits the SDK doesn't model yet.
Groups as first-class objects
water = catalog.group("/Water Resources") # created if missing
water.add(GeoJsonCatalogItem(name="Reservoirs", id="r", url="..."))
water.create_group("Rivers") # idempotent
water.group("Rivers/Perennial") # relative, auto-creating
water.remove("r")
Validation & transactions
catalog.validate() # raises ValidationFailedError on problems
errors = catalog.validate(raise_on_error=False)
with catalog.transaction(): # atomic: rolls back on error…
catalog.add(...)
catalog.move(...)
catalog.remove(...)
# …or if validation fails at the end of the block
Built-in validators cover duplicate ids, duplicate paths, invalid hierarchy, missing required properties, and unknown Terria types.
Publishing
catalog.publish() # to the bound storage backend
catalog.publish(LocalStorage("out.json"))
catalog.to_json() # -> str (no I/O)
catalog.to_dict() # -> dict (no I/O)
Safe releases: versioning, rollback & concurrency
catalog.publish() is a plain overwrite (last-write-wins). When several pipelines
edit the same S3 catalog and you need rollback + a CDN cache refresh, use
ReleaseManager:
from terria_catalog import ReleaseManager, S3Storage
manager = ReleaseManager(
S3Storage("my-bucket", "init/catalog.json"), # enable S3 bucket versioning
cache_invalidator=my_cloudfront_invalidator, # your CacheInvalidator impl
)
# Concurrency-safe read-modify-write: reloads & retries if another pipeline wins.
manager.update(lambda cat: cat.add(item, path="/Climate"), message="add rainfall")
# Rollback to a previous version.
versions = manager.history() # newest first
manager.rollback(to=versions[1].version_id)
ReleaseManager.publish() validates first and returns a Release handle (ETag,
version id, checksum, timestamp). Optimistic concurrency uses conditional writes
(If-Match), so competing pipelines retry instead of clobbering each other. The
CacheInvalidator interface is where CloudFront (or any CDN) plugs in — the
library itself has no CDN dependency. Environment specifics (bucket, IAM, the
actual CloudFront call) stay in your deployment code.
Use it with your AI (no JSON, no code study)
Colleagues can manage the catalog in natural language through their assistant of choice. Three surfaces, one safe tool set (validate → preview → confirm → publish, with version history and rollback):
- MCP server — for Claude Desktop/Code, Cursor, etc.
pip install 'terria-catalog[mcp]', point it at a catalog with env vars, runterria-catalog-mcp. Then just chat: "add the 2026 flood COG under Flood Forecasting and publish." - CLI —
terria-catalog --s3 bucket/init/catalog.json add --type cog …(with--jsonand--dry-run), ideal for shell-driving agents. - Python —
terria_catalog.agent.CatalogSession+run_tool/export_schemasfor custom function-calling agents.
There's also a Claude Skill (skills/terria-catalog/)
and a drop-in system prompt. Full guide:
docs/AI_USAGE.md.
Design philosophy
- Objects, not JSON. Consumers manipulate typed Terria objects. JSON lives
only in
serializers/terria_json.py. - Decoupled storage. The catalog talks to a
StorageBackendinterface; S3 is just one implementation and is imported lazily so core has no cloud deps. - Injectable everything. Serializer, storage, config and validators are all passed in, never hard-wired.
- Open for extension. Adding a new item type is: create a dataclass that sets
terria_type, export it, add a test. It self-registers — no serializer, registry, or facade changes required.
See docs/ARCHITECTURE.md for the full design and
examples/ for runnable scripts.
Documentation
- Use it with your AI — MCP / CLI / Python for assistants.
- Coordinating multiple writers — the pipeline-vs-human plan.
- Architecture — how the library is built.
- System prompt · Claude Skill · AGENTS.md · llms.txt
Development
pip install -e '.[dev]'
pre-commit install
ruff check . && ruff format --check .
black --check .
mypy
pytest --cov=terria_catalog
See CONTRIBUTING.md.
License
MIT © International Water Management Institute (IWMI).
Release files for terria-catalog 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| terria_catalog-0.1.2.tar.gz | 61.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| terria_catalog-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 131.2 kB
Release files / terria_catalog-0.1.2.tar.gz
| Download URL | terria_catalog-0.1.2.tar.gz |
|---|---|
| Size | 61.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a8db275862df8d82fb950cac499d2c4ea8c8df031a57a10bedb44432648068cc
|
|
BLAKE2b-256 checksum How to use checksums |
c6a4fcba44c4f0dd70097833128519e7a5cde33dd041b6ca8100f584a07a9fc9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.
Transparency logRelease files / terria_catalog-0.1.2-py3-none-any.whl
| Download URL | terria_catalog-0.1.2-py3-none-any.whl |
|---|---|
| Size | 70.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fa9b9edee078a239b20b9cde3a0e0212fa28fb750f0aa40fd3ae323109a7da79
|
|
BLAKE2b-256 checksum How to use checksums |
a3b51891e150b2ae4b0cc30083638829a1ebb0c7ad9268ffe1e3357b69411069
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.
Transparency log