Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move entry schemas to separate submodule #511

Merged
merged 6 commits into from
Sep 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/api_reference/server/schemas.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# schemas

::: optimade.server.schemas
9 changes: 1 addition & 8 deletions optimade/server/routers/info.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,17 @@
ErrorResponse,
InfoResponse,
EntryInfoResponse,
ReferenceResource,
StructureResource,
)
from optimade.server.schemas import ENTRY_INFO_SCHEMAS, retrieve_queryable_properties

from optimade.server.routers.utils import (
meta_values,
retrieve_queryable_properties,
get_base_url,
)


router = APIRouter(redirect_slashes=True)

ENTRY_INFO_SCHEMAS = {
"structures": StructureResource.schema,
"references": ReferenceResource.schema,
}


@router.get(
"/info",
Expand Down
36 changes: 0 additions & 36 deletions optimade/server/routers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,13 @@
EntryResponseMany,
EntryResponseOne,
ToplevelLinks,
ReferenceResource,
StructureResource,
DataType,
)

from optimade.server.config import CONFIG
from optimade.server.entry_collections import EntryCollection
from optimade.server.exceptions import BadRequest
from optimade.server.query_params import EntryListingQueryParams, SingleEntryQueryParams

ENTRY_INFO_SCHEMAS = {
"structures": StructureResource.schema,
"references": ReferenceResource.schema,
}

# we need to get rid of any release tags (e.g. -rc.2) and any build metadata (e.g. +py36)
# from the api_version before allowing the URL
BASE_URL_PREFIXES = {
Expand Down Expand Up @@ -271,34 +263,6 @@ def get_single_entry(
)


def retrieve_queryable_properties(schema: dict, queryable_properties: list) -> dict:
properties = {}
for name, value in schema["properties"].items():
if name in queryable_properties:
if "$ref" in value:
path = value["$ref"].split("/")[1:]
sub_schema = schema.copy()
while path:
next_key = path.pop(0)
sub_schema = sub_schema[next_key]
sub_queryable_properties = sub_schema["properties"].keys()
properties.update(
retrieve_queryable_properties(sub_schema, sub_queryable_properties)
)
else:
properties[name] = {"description": value.get("description", "")}
if "unit" in value:
properties[name]["unit"] = value["unit"]
# All properties are sortable with the MongoDB backend.
# While the result for sorting lists may not be as expected, they are still sorted.
properties[name]["sortable"] = True
# Try to get OpenAPI-specific "format" if possible, else get "type"; a mandatory OpenAPI key.
properties[name]["type"] = DataType.from_json_type(
value.get("format", value["type"])
)
return properties


def mongo_id_for_database(database_id: str, database_type: str) -> str:
"""Produce a MondoDB ObjectId for a database"""
from bson.objectid import ObjectId
Expand Down
50 changes: 50 additions & 0 deletions optimade/server/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from optimade.models import DataType, StructureResource, ReferenceResource

ENTRY_INFO_SCHEMAS = {
"structures": StructureResource.schema,
"references": ReferenceResource.schema,
}


def retrieve_queryable_properties(schema: dict, queryable_properties: list) -> dict:
"""Recurisvely loops through the schema of a pydantic model and
resolves all references, returning a dictionary of all the
OPTIMADE-queryable properties of that model.

Parameters:
schema: The schema of the pydantic model.
queryable_properties: The list of properties to find in the schema.

Returns:
A flat dictionary with properties as keys, containing the field
description, unit, sortability, support level, queryability
and type, where provided.

"""
properties = {}
for name, value in schema["properties"].items():
if name in queryable_properties:
if "$ref" in value:
path = value["$ref"].split("/")[1:]
sub_schema = schema.copy()
while path:
next_key = path.pop(0)
sub_schema = sub_schema[next_key]
sub_queryable_properties = sub_schema["properties"].keys()
properties.update(
retrieve_queryable_properties(sub_schema, sub_queryable_properties)
)
else:
properties[name] = {"description": value.get("description", "")}
# Update schema with extension keys provided they are not None
for key in [_ for _ in ("unit", "queryable", "support") if _ in value]:
properties[name][key] = value[key]
# All properties are sortable with the MongoDB backend.
# While the result for sorting lists may not be as expected, they are still sorted.
properties[name]["sortable"] = value.get("sortable", True)
# Try to get OpenAPI-specific "format" if possible, else get "type"; a mandatory OpenAPI key.
properties[name]["type"] = DataType.from_json_type(
value.get("format", value["type"])
)

return properties
32 changes: 32 additions & 0 deletions tests/server/test_schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from optimade.server.schemas import ENTRY_INFO_SCHEMAS, retrieve_queryable_properties


def test_schemas():
"""Test that the default `ENTRY_INFO_SCHEMAS` contain
all the required information about the OPTIMADE properties
after dereferencing.

"""
for entry in ("Structures", "References"):
schema = ENTRY_INFO_SCHEMAS[entry.lower()]()

top_level_props = ("id", "type", "attributes")
properties = retrieve_queryable_properties(schema, top_level_props)

fields = list(
schema["definitions"][f"{entry[:-1]}ResourceAttributes"][
"properties"
].keys()
)
fields += ["id", "type"]

# Check all fields are present
assert all(field in properties for field in fields)

# Check that there are no references to definitions remaining
assert "$ref" not in properties
assert not any("$ref" in properties[field] for field in properties)

# Check that all expected keys are present for OPTIMADE fields
for key in ("type", "sortable", "queryable", "description"):
assert all(key in properties[field] for field in properties)