A minimal OPTIMADE server fed by an httk-core entry provider.¶
This is the runnable version of the example in docs/serving_providers.md:
a provider serving a custom widgets entry type alongside the standard
references entry type, with declared relationships between them, wired
into a server with adapter_from_providers. The declared
RelatedEntry objects are served automatically as the OPTIMADE
relationships block (with per-identifier meta), resolved by
include=references, and filterable both by id (references.id HAS "ref-1") and by related properties (references.title CONTAINS "study")
without any hand-wired filter handlers. See examples/demo_server/ for the
lower-level wiring this automates.
from collections.abc import Iterable, Mapping
from typing import Any
from httk.core import (
EntryProvider,
EntryTypeDefinition,
PropertyDefinition,
RelatedEntry,
standard_entry_type,
)
from httk.optimade import adapter_from_providers, serve
# Run this example by hand (``python examples/provider_server/serve.py``): it
# binds port 8080 and serves until interrupted, so the examples smoke test in
# tests/test_examples.py must not launch it unattended.
HTTK_EXAMPLE_NO_AUTORUN = True
class WidgetProvider(EntryProvider):
"""A provider serving ``widgets`` linked to the standard ``references``."""
def __init__(self, widgets: list[dict[str, Any]], references: list[dict[str, Any]]) -> None:
self._widgets = widgets
self._references = references
def entry_types(self) -> Mapping[str, EntryTypeDefinition]:
widgets = EntryTypeDefinition(
"widgets",
"A widgets entry.",
{
"id": PropertyDefinition.from_simple("id", description="The widget id.", required_response=True),
"type": PropertyDefinition.from_simple("type", description="The entry type.", required_response=True),
"cogs": PropertyDefinition.from_simple("cogs", description="Number of cogs.", fulltype="integer"),
"tags": PropertyDefinition.from_simple("tags", description="Tag labels.", fulltype="list of string"),
},
)
return {"widgets": widgets, "references": standard_entry_type("references")}
def property_keys(self, entry_type: str) -> Mapping[str, str]:
if entry_type == "widgets":
return {"id": "__id", "type": "type", "cogs": "cogs", "tags": "tags"}
return {"id": "__id", "type": "type", "title": "title", "doi": "doi"}
def records(self, entry_type: str) -> Iterable[Mapping[str, Any]]:
if entry_type == "widgets":
return self._widgets
return self._references
def relationships(self, entry_type: str) -> Mapping[str, tuple[RelatedEntry, ...]]:
# Declared relationships are auto-wired by adapter_from_providers:
# served in each widget's relationships block (with the meta below),
# resolvable via ?include=references, and filterable via
# `references.id HAS "ref-1"` or `references.title CONTAINS "study"`.
if entry_type == "widgets":
return {
"w-1": (RelatedEntry("references", "ref-1", description="Widget design study", role="documentation"),),
}
return {}
def make_provider() -> WidgetProvider:
return WidgetProvider(
widgets=[
{"__id": "w-1", "type": "widgets", "cogs": 3, "tags": ["red", "small"]},
{"__id": "w-2", "type": "widgets", "cogs": 5, "tags": ["blue"]},
],
references=[
{"__id": "ref-1", "type": "references", "title": "A study of widgets", "doi": "10.1234/widgets.1"},
],
)
if __name__ == "__main__":
print("Serving on http://localhost:8080 - try:")
print(" curl 'http://localhost:8080/v1/widgets?filter=cogs=5'")
print(" curl 'http://localhost:8080/v1/widgets/w-1?include=references'")
print(' curl \'http://localhost:8080/v1/widgets?filter=references.id HAS "ref-1"\'')
print(" curl 'http://localhost:8080/v1/widgets?filter=references.title CONTAINS \"study\"'")
serve(adapter_from_providers([make_provider()]), port=8080)