DSP minimal and DCAT-AP minimal public catalogues¶
httk.serve.dsp implements a DSP 2025-1 minimal public-catalogue feature set.
Publications can be declared inline or stored under the non-OPTIMADE
DspPublicationEntry family. Negotiation and transfer state remains in
memory, while every catalogue request reads a fresh snapshot from the
caller-owned store.
Official DSP discovery uses:
GET /dsp/.well-known/dspace-version
POST /dsp/2025-1/catalog/request
GET /dsp/2025-1/catalog is intentionally not implemented: it is not a DSP
2025-1 endpoint. A caller that also publishes a JSON-LD representation can
mount the vocabulary-neutral httk.serve.http.jsonld_get_app; discovery
and protocol-specific validation belong to that caller.
Store-native catalogue¶
from httk.core import Dataset, DatasetDistribution
from httk.store.db import Database, SqlStore
from httk.serve.dsp import (
DCAT_AP_3_0_1_PROFILE,
DCAT_AP_MINIMAL_PROFILE,
DspDatasetPublication,
DspProvider,
DspProviderConfig,
DspPublicationEntry,
DspPublicationRecord,
)
from httk.core import Service
publication = DspDatasetPublication(
dataset=Dataset(
id="https://provider.example/datasets/one",
title="Dataset one",
description="An example dataset.",
publisher_id="https://provider.example/participants/provider",
publisher_name="Provider",
distributions=(
DatasetDistribution(
access_url="https://provider.example/files/dataset.csv",
byte_size=123,
sha256="…publisher-computed lowercase SHA-256…",
),
),
),
)
store = SqlStore(
Database.sqlite(),
entry_records={DspPublicationEntry: DspPublicationRecord},
)
store.save(DspPublicationRecord(dataset=publication))
store.save(DspPublicationRecord(service=Service(
id="https://catalogue.example/services/dcat-ap",
title="Public DCAT-AP catalogue",
endpoint_url="https://catalogue.example/dcat-ap",
conforms_to=(DCAT_AP_MINIMAL_PROFILE, DCAT_AP_3_0_1_PROFILE),
)))
config = DspProviderConfig(
public_base_url="https://provider.example",
dsp_mount="/dsp",
service_id="https://provider.example/services/dsp",
service_title="Public DSP service",
participant_id="https://provider.example/participants/provider",
catalog_id="https://provider.example/catalogues/main",
catalog_title="Main catalogue",
catalog_description="Published datasets.",
dcat_ap_content_negotiation=True,
)
provider = DspProvider(config, store=store)
The store must declare DspPublicationEntry mapped solely to
DspPublicationRecord; neither application closes the store or database. Later
dataset or service envelope saves become visible without rebuilding the
provider. Duplicate dataset, offer, or distribution IDs and inconsistent
publishers are rejected when a snapshot is built. For fixed declarations, use
DspProvider(config, publications=(DspPublicationRecord(dataset=publication),));
exactly one source is required.
Replaceable catalogue policy¶
DspProvider keeps storage access, DSP message validation, negotiation and
transfer state, callbacks, and data-address handling fixed. The outbound
callback transport is the generic httk.serve.http.webhook implementation
(PinnedHttpsJsonPoster and deliver_with_retries), retained under the
DSP-facing names DefaultCallbackSender and callback_url. Applications that
are developing a stricter publication profile can replace only the catalogue
requirements and projections through the public DspCataloguePolicy contract:
provider = DspProvider(
config,
store=store,
catalogue_policy=ProjectCataloguePolicy(),
)
The policy receives the current DspPublicationRecord values whenever a live
snapshot is requested. It builds the public CatalogueProfile, validates
profile-specific catalogue-request constraints, serializes catalogues,
datasets, and offers, and selects the ordinary or alternate catalogue
representation. DspCatalogueRepresentation carries the selected media type,
projection flag, and additional HTTP headers. The media type must correspond
to a response declared by the packaged DSP OpenAPI contract.
MinimalDspCataloguePolicy is the default and preserves the feature set
described on this page. A standards prototype should implement the protocol
independently rather than subclassing this default, so its normative decisions
remain visible and cannot change when the built-in minimal profile evolves.
The lower-level policy does not replace DSP routing, schemas, error documents,
or process state machines.
Representations¶
Every DSP Distribution uses its full EU File Type IRI as both its DSP transfer
format and its DCAT dct:format resource. CSV and JSON therefore have distinct
transfer values. dcat:mediaType, direct access and download URLs, explicitly
typed byte size, and SPDX SHA-256 metadata are included in the common payload.
Ordinary catalogue requests use application/json and the protected DSP
context. When dcat_ap_content_negotiation=True, an explicit
Accept: application/ld+json on the same POST returns the same common JSON
value with an owned context and the profiled DCAT-AP media type. A generic
JSON-LD GET application can expose that same live document independently. It
handles GET/HEAD, JSON-LD content acceptance, ETags, caching, CORS, and an
optional profile link, but does not define discovery or claim conformance to a
retrieval protocol.
DspDatasetPublication is the DSP offer envelope around one neutral
Dataset. The dataset must contain exactly one DatasetDistribution with an
absolute HTTPS access URL. It never opens, measures, or hashes a local file.
Size and digest are publisher-supplied distribution metadata. .csv and
.json infer the authoritative EU File Type and IANA media-type IRIs; other
representations require format_iri and media_type_iri explicitly on the
distribution.
The package vendors pinned DSP schemas, DCAT-AP 3.0.1 SHACL material, EU
CSV/JSON vocabulary concepts, and the SPDX ontology with provenance and
integrity hashes. The complete runnable example is in
examples/dsp_store_catalogue/.