Skip to content

stacs.validate

Checks on item bodies before they are registered.

Two different questions, kept apart:

  • audit: does every item agree with the collection it is about to be registered into, and with the asset rules the catalogue declares? The property is HOMOGENEITY, not size. Item ids do not change during a rename, so id-set equality reads IN SYNC over a fully mixed catalogue; pgstac routes each item by its OWN collection field, so a stale item registers successfully into the old collection; and a count of assets cannot tell {image, dsm} from {dem, dsm}.
  • validate: is each item valid STAC, by pystac? Not run by register: the code this was extracted from never validated at registration, and per-item schema validation of a whole catalogue is a cost a caller should choose.

Asset rules are the caller's. Nothing here knows any catalogue's keys.

Audit dataclass

The outcome of audit_items, with paths rather than counts -- a count of offenders is no more use here than a count of items.

rules property

The asset rules as APPLIED, not as passed: a forbid argument of "," is non-empty and parses to no keys, and a caller printing its own flags would report a check that never ran.

parse_asset_keys(value)

Asset keys from a comma-separated string or a list.

None or an empty list means "no rule". Anything else must name keys, every one of them: "" (an unset variable), "," or a list holding an empty entry raises rather than reading as "forbid nothing" -- the item still carrying a retired key would otherwise pass.

A LIST, not a single key: a rename can retire more than one key, and a single-string parameter once meant a comma-joined value matched no real key and the check silently stopped checking anything.

Examples:

>>> parse_asset_keys("data, legacy,data")
['data', 'legacy']
>>> parse_asset_keys(None)
[]
>>> parse_asset_keys(",")
Traceback (most recent call last):
...
ValueError: empty asset key in ','

item_paths_in_dir(directory)

Every item JSON in a directory: *.json except collection.json, sorted.

Examples:

>>> import os, tempfile
>>> tmp = tempfile.TemporaryDirectory()
>>> for name in ("b.json", "a.json", "collection.json", "notes.txt"):
...     open(os.path.join(tmp.name, name), "w").close()
>>> [os.path.basename(p) for p in item_paths_in_dir(tmp.name)]
['a.json', 'b.json']
>>> tmp.cleanup()

audit_items(paths, collection_id, require_asset=None, forbid_assets=None, expect=None, expect_ids=None)

Which items disagree with the collection they are to be registered into.

expect is a count and expect_ids a set; pass the set wherever the ids are known, because a count is satisfied by the wrong items. Either exists so a run that silently processed a SUBSET -- a reused manifest is the way that happens -- fails here rather than registering.

Zero items is never a pass: a loop over an empty set reports nothing, which is indistinguishable from "everything checked out".

Examples:

>>> import json, os, tempfile
>>> tmp = tempfile.TemporaryDirectory()
>>> href = "https://example.invalid/x.tif"
>>> items = {"a": {"id": "a", "collection": "my-collection",
...                "assets": {"data": {"href": href}}},
...          "b": {"id": "b", "collection": "my-collection",
...                "assets": {"legacy": {"href": href}}}}
>>> paths = []
>>> for item_id, body in items.items():
...     paths.append(os.path.join(tmp.name, f"{item_id}.json"))
...     with open(paths[-1], "w") as f:
...         json.dump(body, f)
>>> audit = audit_items(paths, "my-collection", require_asset="data",
...                     forbid_assets=["legacy"], expect_ids={"a", "b"})
>>> audit.ok
False
>>> [os.path.basename(p) for p in audit.missing_asset + audit.forbidden_asset]
['b.json', 'b.json']
>>> tmp.cleanup()

validate_items(paths)

[(path, error)] for every item that is not valid STAC.

The body is validated AS WRITTEN, with pystac.validation.validate_dict. Not via pystac.Item.from_dict(...).validate(), which validates pystac's re-serialisation: it upgrades a 1.0.0 item to 1.1.0 and rewrites a naive datetime, so the body that would be registered is never the one checked.

Every item is checked; failures are collected rather than stopping at the first. A schema that cannot be fetched (extension schemas, and core schemas for STAC versions pystac does not bundle, are fetched by URL) is a failure, not a pass. Zero items raises: an empty result is this function's success value.

Examples:

STAC 1.1.0 core schemas ship with pystac, so a 1.1.0 item validates offline:

>>> import json, os, tempfile
>>> tmp = tempfile.TemporaryDirectory()
>>> item = {"type": "Feature", "stac_version": "1.1.0", "id": "a",
...         "geometry": {"type": "Point", "coordinates": [-126.0, 54.0]},
...         "bbox": [-126.0, 54.0, -126.0, 54.0],
...         "properties": {"datetime": "2020-01-01T00:00:00Z"},
...         "links": [], "assets": {}}
>>> path = os.path.join(tmp.name, "a.json")
>>> with open(path, "w") as f:
...     json.dump(item, f)
>>> validate_items([path])
[]
>>> tmp.cleanup()
>>> validate_items([])
Traceback (most recent call last):
...
ValueError: no item JSONs to validate