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

Update links resources #306

Merged
merged 5 commits into from
Jun 11, 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
23 changes: 14 additions & 9 deletions openapi/index_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -760,7 +760,7 @@
"description": "Reference to the child identifier object under the links endpoint that the provider has chosen as their 'default' OPTIMADE API database. A client SHOULD present this database as the first choice when an end-user chooses this provider."
}
},
"description": "Index Meta-Database Base URL Info enpoint resource"
"description": "Index Meta-Database Base URL Info endpoint resource"
},
"IndexInfoResponse": {
"title": "IndexInfoResponse",
Expand Down Expand Up @@ -837,10 +837,10 @@
"title": "Data",
"allOf": [
{
"$ref": "#/components/schemas/RelatedChildResource"
"$ref": "#/components/schemas/RelatedLinksResource"
}
],
"description": "JSON API resource linkage. It MUST be either null or contain a single child identifier object with the fields 'id' and 'type'"
"description": "JSON API resource linkage. It MUST be either null or contain a single Links identifier object with the fields 'id' and 'type'"
}
},
"description": "Index Meta-Database relationship"
Expand Down Expand Up @@ -898,7 +898,6 @@
"title": "LinksResource",
"required": [
"id",
"type",
"attributes"
],
"type": "object",
Expand All @@ -911,7 +910,7 @@
"type": {
"title": "Type",
"type": "string",
"description": "MUST be either \"parent\", \"child\", or \"provider\". These objects are described in detail in sections Parent and Child Objects and Provider Objects."
"description": "These objects are described in detail in the section Links Endpoint"
},
"links": {
"title": "Links",
Expand Down Expand Up @@ -958,7 +957,8 @@
"name",
"description",
"base_url",
"homepage"
"homepage",
"link_type"
],
"type": "object",
"properties": {
Expand Down Expand Up @@ -1009,6 +1009,11 @@
}
],
"description": "JSON API links object, pointing to a homepage URL for this implementation"
},
"link_type": {
"title": "Link Type",
"type": "string",
"description": "The link type of the represented resource in relation to this implementation. MUST be one of these values: 'child', 'root', 'external', 'providers'."
}
},
"description": "Links endpoint resource object attributes"
Expand Down Expand Up @@ -1274,8 +1279,8 @@
},
"description": "Similar to normal JSON API relationship, but with addition of OPTIONAL meta field for a resource"
},
"RelatedChildResource": {
"title": "RelatedChildResource",
"RelatedLinksResource": {
"title": "RelatedLinksResource",
"required": [
"id"
],
Expand All @@ -1291,7 +1296,7 @@
"type": "string"
}
},
"description": "Keep only type and id of a ChildResource"
"description": "A related Links resource object"
},
"RelationshipLinks": {
"title": "RelationshipLinks",
Expand Down
11 changes: 8 additions & 3 deletions openapi/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1680,7 +1680,6 @@
"title": "LinksResource",
"required": [
"id",
"type",
"attributes"
],
"type": "object",
Expand All @@ -1693,7 +1692,7 @@
"type": {
"title": "Type",
"type": "string",
"description": "MUST be either \"parent\", \"child\", or \"provider\". These objects are described in detail in sections Parent and Child Objects and Provider Objects."
"description": "These objects are described in detail in the section Links Endpoint"
},
"links": {
"title": "Links",
Expand Down Expand Up @@ -1740,7 +1739,8 @@
"name",
"description",
"base_url",
"homepage"
"homepage",
"link_type"
],
"type": "object",
"properties": {
Expand Down Expand Up @@ -1791,6 +1791,11 @@
}
],
"description": "JSON API links object, pointing to a homepage URL for this implementation"
},
"link_type": {
"title": "Link Type",
"type": "string",
"description": "The link type of the represented resource in relation to this implementation. MUST be one of these values: 'child', 'root', 'external', 'providers'."
}
},
"description": "Links endpoint resource object attributes"
Expand Down
27 changes: 18 additions & 9 deletions optimade/models/index_metadb.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from pydantic import Field, BaseModel
# pylint: disable=no-self-argument
from pydantic import Field, BaseModel, validator # pylint: disable=no-name-in-module
from typing import Union, Dict

from .jsonapi import BaseResource
Expand All @@ -7,7 +8,7 @@

__all__ = (
"IndexInfoAttributes",
"RelatedChildResource",
"RelatedLinksResource",
"IndexRelationship",
"IndexInfoResource",
)
Expand All @@ -25,30 +26,38 @@ class IndexInfoAttributes(BaseInfoAttributes):
)


class RelatedChildResource(BaseResource):
"""Keep only type and id of a ChildResource"""
class RelatedLinksResource(BaseResource):
"""A related Links resource object"""

type: str = Field("child", const=True)
type: str = Field("links", const=True)


class IndexRelationship(BaseModel):
"""Index Meta-Database relationship"""

data: Union[None, RelatedChildResource] = Field(
data: Union[None, RelatedLinksResource] = Field(
...,
description="JSON API resource linkage. It MUST be either null or contain "
"a single child identifier object with the fields 'id' and 'type'",
"a single Links identifier object with the fields 'id' and 'type'",
)


class IndexInfoResource(BaseInfoResource):
"""Index Meta-Database Base URL Info enpoint resource"""
"""Index Meta-Database Base URL Info endpoint resource"""

attributes: IndexInfoAttributes = Field(...)
relationships: Dict[str, IndexRelationship] = Field(
relationships: Union[None, Dict[str, IndexRelationship]] = Field(
...,
description="Reference to the child identifier object under the links endpoint "
"that the provider has chosen as their 'default' OPTIMADE API database. "
"A client SHOULD present this database as the first choice when an end-user "
"chooses this provider.",
)

@validator("relationships")
def relationships_key_must_be_default(cls, value):
if value is not None and all([key != "default" for key in value]):
raise ValueError(
"if the relationships value is a dict, the key MUST be 'default'"
)
return value
56 changes: 22 additions & 34 deletions optimade/models/links.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# pylint: disable=no-self-argument
from pydantic import Field, AnyUrl, validator, root_validator
from pydantic import ( # pylint: disable=no-name-in-module
Field,
AnyUrl,
validator,
root_validator,
)
from typing import Union

from .jsonapi import Link, Attributes
Expand All @@ -9,9 +14,6 @@
__all__ = (
"LinksResourceAttributes",
"LinksResource",
"ChildResource",
"ParentResource",
"ProviderResource",
)


Expand All @@ -38,15 +40,27 @@ class LinksResourceAttributes(Attributes):
description="JSON API links object, pointing to a homepage URL for this implementation",
)

link_type: str = Field(
...,
description="The link type of the represented resource in relation to this implementation. MUST be one of these values: 'child', 'root', 'external', 'providers'.",
)

@validator("link_type")
def link_type_must_be_in_specific_set(cls, value):
if value not in {"child", "root", "external", "providers"}:
raise ValueError(
"link_type MUST be either 'child, 'root', 'external', or 'providers'"
)
return value


class LinksResource(EntryResource):
"""A Links endpoint resource object"""

type: str = Field(
...,
description='MUST be either "parent", "child", or "provider". '
"These objects are described in detail in sections Parent and Child Objects "
"and Provider Objects.",
"links",
const=True,
description="These objects are described in detail in the section Links Endpoint",
)

attributes: LinksResourceAttributes = Field(
Expand All @@ -55,34 +69,8 @@ class LinksResource(EntryResource):
"entry's properties.",
)

@validator("type")
def type_must_be_in_specific_set(cls, value):
if value not in {"parent", "child", "provider"}:
raise ValueError(
"name of Links endpoint resource MUST be either 'parent, 'child', or 'provider'"
)
return value

@root_validator(pre=True)
def relationships_must_not_be_present(cls, values):
if values.get("relationships", None) is not None:
raise ValueError('"relationships" is not allowed for links resources')
return values


class ChildResource(LinksResource):
"""A child object representing a link to an implementation exactly one layer below the current implementation"""

type: str = Field("child", const=True)


class ParentResource(LinksResource):
"""A parent object representing a link to an implementation exactly one layer above the current implementation"""

type: str = Field("parent", const=True)


class ProviderResource(LinksResource):
"""A provider object representing a link to another index meta-database by another database provider"""

type: str = Field("provider", const=True)
5 changes: 3 additions & 2 deletions optimade/server/data/test_links.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"$oid": "696e646578706172656e7430"
},
"id": "index",
"type": "parent",
"type": "links",
"name": "Index meta-database",
"description": "Index for example's OPTIMADE databases",
"base_url": "http://localhost:5001",
"homepage": "https://example.com"
"homepage": "https://example.com",
"link_type": "root"
}
]
5 changes: 3 additions & 2 deletions optimade/server/index_links.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
[
{
"id": "test_server",
"type": "child",
"type": "links",
"name": "OPTIMADE API",
"description": "The [Open Databases Integration for Materials Design (OPTIMADE) consortium](https://www.optimade.org/) aims to make materials databases interoperational by developing a common REST API.",
"base_url": "http://localhost:5000",
"homepage": "https://example.com"
"homepage": "https://example.com",
"link_type": "child"
}
]
8 changes: 7 additions & 1 deletion optimade/server/routers/index_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
IndexInfoAttributes,
IndexInfoResource,
IndexRelationship,
RelatedLinksResource,
)

from optimade.server.config import CONFIG
Expand Down Expand Up @@ -47,7 +48,12 @@ def get_info(request: Request):
),
relationships={
"default": IndexRelationship(
data={"type": "child", "id": CONFIG.default_db}
data={
"type": RelatedLinksResource.schema()["properties"]["type"][
"const"
],
"id": CONFIG.default_db,
}
)
},
),
Expand Down
15 changes: 6 additions & 9 deletions optimade/validator/validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -620,15 +620,12 @@ def get_endpoint(self, request_str, optional=False):
response = self.client.get(request_str)

if response.status_code != 200:
error_titles = [
error.get("title", "") for error in response.json().get("errors", [])
]
error_details = [
error.get("detail", "") for error in response.json().get("errors", [])
]
message = f"Request to '{request_str}' returned HTTP code {response.status_code}. Errors:\n"
for error_title, error_detail in zip(error_titles, error_details):
message += f"{error_title}: {error_details}"
message = (
f"Request to '{request_str}' returned HTTP code {response.status_code}."
)
message += "\nError(s):"
for error in response.json().get("errors", []):
message += f'\n {error.get("title", "N/A")}: {error.get("detail", "N/A")} ({error.get("source", {}).get("pointer", "N/A")})'
raise ResponseError(message)

return response, "request successful."
Expand Down
2 changes: 1 addition & 1 deletion tests/server/test_query_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def test_required_fields_links(self):
illegal_top_level_field = "relationships"
non_used_top_level_fields = {"links"}
non_used_top_level_fields.add(illegal_top_level_field)
expected_fields = {"homepage", "base_url"}
expected_fields = {"homepage", "base_url", "link_type"}
self.required_fields_test_helper(
endpoint, non_used_top_level_fields, expected_fields
)
Expand Down