Skip to content

Commit

Permalink
Merge pull request #195 from CasperWA/test_updated_schema
Browse files Browse the repository at this point in the history
(Cosmetic) updates to models

Use only `pydantic` `List`s, since `Set` does not work properly
for the verifications.
The concept of a set it provided in the OpenAPI schema manually,
by passing `uniqueItems=True` to the relevant `Field`s.
  • Loading branch information
CasperWA committed Mar 2, 2020
2 parents e9dc886 + f8d190f commit 840d416
Show file tree
Hide file tree
Showing 9 changed files with 749 additions and 615 deletions.
316 changes: 174 additions & 142 deletions openapi/index_openapi.json

Large diffs are not rendered by default.

426 changes: 256 additions & 170 deletions openapi/openapi.json

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions optimade/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from .links import *
from .optimade_json import *
from .references import *
from .responses import *
from .structures import *
from .toplevel import *

__all__ = (
jsonapi.__all__ # noqa
Expand All @@ -20,6 +20,6 @@
+ links.__all__ # noqa
+ optimade_json.__all__ # noqa
+ references.__all__ # noqa
+ responses.__all__ # noqa
+ structures.__all__ # noqa
+ toplevel.__all__ # noqa
)
2 changes: 1 addition & 1 deletion optimade/models/entries.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# pylint: disable=line-too-long,no-self-argument
from datetime import datetime
from typing import Optional, Dict, List
from pydantic import BaseModel, Field, validator
from pydantic import BaseModel, Field, validator # pylint: disable=no-name-in-module

from .jsonapi import Relationships, Attributes, Resource
from .optimade_json import Relationship
Expand Down
25 changes: 16 additions & 9 deletions optimade/models/jsonapi.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
"""This module should reproduce JSON API v1.0 https://jsonapi.org/format/1.0/"""
# pylint: disable=no-self-argument
from typing import Optional, Set, Union, List
from pydantic import BaseModel, AnyUrl, Field, root_validator
from typing import Optional, Union, List
from pydantic import ( # pylint: disable=no-name-in-module
BaseModel,
AnyUrl,
Field,
root_validator,
)


__all__ = (
Expand Down Expand Up @@ -156,7 +161,7 @@ class Relationship(BaseModel):
description="a links object containing at least one of the following: self, related",
)
data: Optional[Union[BaseResource, List[BaseResource]]] = Field(
None, description="Resource linkage"
None, description="Resource linkage", uniqueItems=True
)
meta: Optional[Meta] = Field(
None,
Expand Down Expand Up @@ -250,19 +255,21 @@ class Resource(BaseResource):
class Response(BaseModel):
"""A top-level response"""

data: Optional[Union[None, Resource, Set[Resource]]] = Field(
None, description="Outputted Data"
data: Optional[Union[None, Resource, List[Resource]]] = Field(
None, description="Outputted Data", uniqueItems=True
)
meta: Optional[Meta] = Field(
None,
description="A meta object containing non-standard information related to the Success",
)
errors: Optional[Set[Error]] = Field(None, description="A list of errors")
included: Optional[Set[Resource]] = Field(
None, description="A list of resources that are included"
errors: Optional[List[Error]] = Field(
None, description="A list of unique errors", uniqueItems=True
)
included: Optional[List[Resource]] = Field(
None, description="A list of unique included resources", uniqueItems=True
)
links: Optional[ToplevelLinks] = Field(
None, description="Links associated with the failure"
None, description="Links associated with the primary data or errors"
)
jsonapi: Optional[JsonApi] = Field(
None, description="Information about the JSON API used"
Expand Down
222 changes: 168 additions & 54 deletions optimade/models/optimade_json.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
"""Modified JSON API v1.0 for OPTiMaDe API"""
# pylint: disable=no-self-argument
from pydantic import Field, root_validator, BaseModel
from typing import Optional, Set, Union, List
# pylint: disable=no-self-argument,no-name-in-module
from pydantic import Field, root_validator, BaseModel, AnyHttpUrl, EmailStr
from typing import Optional, Union, List

from datetime import datetime

from . import jsonapi


__all__ = (
"Error",
"Failure",
"ResponseMetaQuery",
"Provider",
"ImplementationMaintainer",
"Implementation",
"ResponseMeta",
"OptimadeError",
"Success",
"Warnings",
"BaseRealationshipMeta",
"BaseRelationshipMeta",
"BaseRelationshipResource",
"Relationship",
)


class Error(jsonapi.Error):
class OptimadeError(jsonapi.Error):
"""detail MUST be present"""

detail: str = Field(
Expand All @@ -26,42 +32,174 @@ class Error(jsonapi.Error):
)


class Failure(jsonapi.Response):
"""errors MUST be present and data MUST be skipped"""
class Warnings(OptimadeError):
"""OPTiMaDe-specific warning class based on OPTiMaDe-specific JSON API Error.
From the specification:
A warning resource object is defined similarly to a JSON API
error object, but MUST also include the field type, which MUST
have the value "warning". The field detail MUST be present and
SHOULD contain a non-critical message, e.g., reporting
unrecognized search attributes or deprecated features.
Note: Must be named "Warnings", since "Warning" is a built-in Python class.
"""

type: str = Field(
"warning", const=True, description='Warnings must be of type "warning"'
)

@root_validator(pre=True)
def status_must_not_be_specified(cls, values):
if values.get("status", None) is not None:
raise ValueError("status MUST NOT be specified for warnings")
return values


class ResponseMetaQuery(BaseModel):
""" Information on the query that was requested. """

representation: str = Field(
...,
description="a string with the part of the URL that follows the base URL. Example: '/structures?'",
)


class Provider(BaseModel):
"""Information on the database provider of the implementation."""

name: str = Field(..., description="a short name for the database provider")

description: str = Field(
..., description="a longer description of the database provider"
)

prefix: str = Field(
..., description="database-provider-specific prefix as found in " "Appendix 1."
)

homepage: Optional[Union[AnyHttpUrl, jsonapi.Link]] = Field(
None,
description="a [JSON API links object](http://jsonapi.org/format/1.0#document-links) "
"pointing to homepage of the database provider, either "
"directly as a string, or as a link object.",
)

meta: Optional[jsonapi.Meta] = Field(
index_base_url: Optional[Union[AnyHttpUrl, jsonapi.Link]] = Field(
None,
description="A meta object containing non-standard information related to the Success",
description="a [JSON API links object](http://jsonapi.org/format/1.0#document-links) "
"pointing to the base URL for the `index` meta-database as "
"specified in Appendix 1, either directly as a string, or "
"as a link object.",
)
errors: Set[Error] = Field(


class ImplementationMaintainer(BaseModel):
"""Details about the maintainer of the implementation"""

email: EmailStr = Field(..., description="the maintainer's email address")


class Implementation(BaseModel):
"""Information on the server implementation"""

name: Optional[str] = Field(None, description="name of the implementation")

version: Optional[str] = Field(
None, description="version string of the current implementation"
)

source_url: Optional[AnyHttpUrl] = Field(
None,
description="URL of the implementation source, either downloadable archive or version control system",
)

maintainer: Optional[ImplementationMaintainer] = Field(
None,
description="A dictionary providing details about the maintainer of the implementation.",
)


class ResponseMeta(jsonapi.Meta):
"""
A [JSON API meta member](https://jsonapi.org/format/1.0#document-meta)
that contains JSON API meta objects of non-standard
meta-information.
OPTIONAL additional information global to the query that is not
specified in this document, MUST start with a
database-provider-specific prefix.
"""

query: ResponseMetaQuery = Field(
..., description="information on the query that was requested"
)

api_version: str = Field(
...,
description="A list of OPTiMaDe-specific JSON API error objects, where the field detail MUST be present.",
description="a string containing the version of the API "
"implementation, e.g. v0.9.5",
)
links: Optional[jsonapi.ToplevelLinks] = Field(
None, description="Links associated with the primary data"

time_stamp: datetime = Field(
...,
description="a string containing the date and time at which the query was exexcuted",
)

@root_validator(pre=True)
def data_must_be_skipped(cls, values):
if values.get("data", None) is not None:
raise ValueError("data MUST be skipped for failures reporting errors")
return values
data_returned: int = Field(
...,
description="an integer containing the number of data objects "
"returned for the query.",
ge=0,
)

more_data_available: bool = Field(
..., description="`false` if all data has been returned, and `true` " "if not."
)

class Success(jsonapi.Response):
"""errors are not allowed"""
provider: Provider = Field(
..., description="information on the database provider of the implementation."
)

meta: Optional[jsonapi.Meta] = Field(
data_available: Optional[int] = Field(
None,
description="A meta object containing non-standard information related to the Success",
description="an integer containing the total number of data "
"objects available in the database",
)

last_id: Optional[str] = Field(
None, description="a string containing the last ID returned"
)
links: Optional[jsonapi.ToplevelLinks] = Field(
None, description="Links associated with the primary data"

response_message: Optional[str] = Field(
None, description="response string from the server"
)

implementation: Optional[Implementation] = Field(
None, description="a dictionary describing the server implementation"
)

warnings: Optional[List[Warnings]] = Field(
None,
description="List of warning resource objects representing non-critical errors or warnings. "
"A warning resource object is defined similarly to a JSON API error object, but MUST also include the field type, "
'which MUST have the value "warning". The field detail MUST be present and SHOULD contain a non-critical message, '
"e.g., reporting unrecognized search attributes or deprecated features. The field status, representing a HTTP "
"response status code, MUST NOT be present for a warning resource object. This is an exclusive field for error resource objects.",
uniqueItems=True,
)


class Success(jsonapi.Response):
"""errors are not allowed"""

meta: Optional[ResponseMeta] = Field(
None, description="A meta object containing non-standard information"
)

@root_validator(pre=True)
def either_data_meta_or_errors_must_be_set(cls, values):
"""Overwriting the existing validation function"""
"""Overwriting the existing validation function, since 'errors' MUST NOT be set"""
required_fields = ("data", "meta")
if not any(values.get(field) for field in required_fields):
raise ValueError(
Expand All @@ -75,31 +213,7 @@ def either_data_meta_or_errors_must_be_set(cls, values):
return values


class Warnings(Error):
"""OPTiMaDe-specific warning class based on OPTiMaDe-specific JSON API Error.
From the specification:
A warning resource object is defined similarly to a JSON API
error object, but MUST also include the field type, which MUST
have the value "warning". The field detail MUST be present and
SHOULD contain a non-critical message, e.g., reporting
unrecognized search attributes or deprecated features.
Note: Must be named "Warnings", since "Warning" is a built-in Python class.
"""

type: str = Field(
"warning", const=True, description='Warnings must be of type "warning"'
)

@root_validator(pre=True)
def status_must_not_be_specified(cls, values):
if values.get("status", None) is not None:
raise ValueError("status MUST NOT be specified for warnings")
return values


class BaseRealationshipMeta(BaseModel):
class BaseRelationshipMeta(jsonapi.Meta):
"""Specific meta field for base relationship resource"""

description: str = Field(
Expand All @@ -110,7 +224,7 @@ class BaseRealationshipMeta(BaseModel):
class BaseRelationshipResource(jsonapi.BaseResource):
"""Minimum requirements to represent a relationship resource"""

meta: Optional[BaseRealationshipMeta] = Field(
meta: Optional[BaseRelationshipMeta] = Field(
None,
description="Relationship meta field. MUST contain 'description' if supplied.",
)
Expand All @@ -121,4 +235,4 @@ class Relationship(jsonapi.Relationship):

data: Optional[
Union[BaseRelationshipResource, List[BaseRelationshipResource]]
] = Field(None, description="Resource linkage")
] = Field(None, description="Resource linkage", uniqueItems=True)
Loading

0 comments on commit 840d416

Please sign in to comment.