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

Add references endpoint #78

Merged
merged 12 commits into from
Nov 21, 2019
3,497 changes: 1,525 additions & 1,972 deletions openapi.json

Large diffs are not rendered by default.

26 changes: 21 additions & 5 deletions optimade/models/references.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from pydantic import Schema, BaseModel, UrlStr, validator
from typing import List, Optional

from .entries import EntryResource, EntryResourceAttributes


__all__ = ("Person", "ReferenceResourceAttributes", "ReferenceResource")

Expand All @@ -11,7 +13,7 @@ class Person(BaseModel):
lastname: Optional[str] = Schema(..., description="""Last name of the person.""")


class ReferenceResourceAttributes(BaseModel):
class ReferenceResourceAttributes(EntryResourceAttributes):
""" Model that stores the attributes of a reference. Many properties match the
meaning described in the
[BibTeX specification](http://bibtexml.sourceforge.net/btxdoc.pdf).
Expand Down Expand Up @@ -90,8 +92,9 @@ class ReferenceResourceAttributes(BaseModel):
title: Optional[str] = Schema(
..., description="Meaning of property matches the BiBTeX specification."
)
type: Optional[str] = Schema(
..., description="Meaning of property matches the BiBTeX specification."
bib_type: Optional[str] = Schema(
...,
description="Type of the reference, corresponding to the **type** property in the BiBTeX specification.",
)
volume: Optional[str] = Schema(
..., description="Meaning of property matches the BiBTeX specification."
Expand All @@ -101,7 +104,7 @@ class ReferenceResourceAttributes(BaseModel):
)


class ReferenceResource(BaseModel):
class ReferenceResource(EntryResource):
""" The :entry:`references` entries describe bibliographic references.
The following properties are used to provide the bibliographic details:

Expand All @@ -122,7 +125,20 @@ class ReferenceResource(BaseModel):
- **Query**: Support for queries on any of these properties is OPTIONAL.
If supported, filters MAY support only a subset of comparison operators. """

type: str = Schema(default="references", const=True)
type: str = Schema(
default="references",
const=True,
description="""The name of the type of an entry. Any entry MUST be able to be fetched using the `base URL <Base URL_>`_ type and ID at the url :endpoint:`<base URL>/<type>/<id>`.
- **Type**: string.
- **Requirements/Conventions**:

- **Response**: REQUIRED in the response unless explicitly excluded.
- **Query**: Support for queries on this property is OPTIONAL.
If supported, only a subset of string comparison operators MAY be supported.

- **Requirements/Conventions**: MUST be an existing entry type.
- **Example**: :val:`"structures"`""",
)
attributes: ReferenceResourceAttributes

@validator("attributes")
Expand Down
47 changes: 34 additions & 13 deletions optimade/models/toplevel.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
from .jsonapi import Link, Meta
from .util import NonnegativeInt
from .baseinfo import BaseInfoResource
from .entries import EntryInfoResource
from .entries import EntryInfoResource, EntryResource
from .optimade_json import Error, Success, Failure, Warnings
from .references import ReferenceResource
from .structures import StructureResource


Expand All @@ -17,11 +18,15 @@
"ImplementationMaintainer",
"Implementation",
"ResponseMeta",
"StructureResponseOne",
"StructureResponseMany",
"ErrorResponse",
"EntryInfoResponse",
"InfoResponse",
"EntryResponseOne",
"EntryResponseMany",
"StructureResponseOne",
"StructureResponseMany",
"ReferenceResponseOne",
"ReferenceResponseMany",
)


Expand Down Expand Up @@ -162,16 +167,6 @@ class ResponseMeta(Meta):
)


class StructureResponseOne(Success):
meta: ResponseMeta = Schema(...)
data: Union[StructureResource, Dict[str, Any], None] = Schema(...)


class StructureResponseMany(Success):
meta: ResponseMeta = Schema(...)
data: Union[List[StructureResource], List[Dict[str, Any]]] = Schema(...)


class ErrorResponse(Failure):
meta: Optional[ResponseMeta] = Schema(...)
errors: List[Error] = Schema(...)
Expand All @@ -185,3 +180,29 @@ class EntryInfoResponse(Success):
class InfoResponse(Success):
meta: Optional[ResponseMeta] = Schema(...)
data: BaseInfoResource = Schema(...)


class EntryResponseOne(Success):
meta: ResponseMeta = Schema(...)
data: Union[EntryResource, Dict[str, Any], None] = Schema(...)


class EntryResponseMany(Success):
meta: ResponseMeta = Schema(...)
data: Union[List[EntryResource], List[Dict[str, Any]]] = Schema(...)


class StructureResponseOne(EntryResponseOne):
data: Union[StructureResource, Dict[str, Any], None] = Schema(...)


class StructureResponseMany(EntryResponseMany):
data: Union[List[StructureResource], List[Dict[str, Any]]] = Schema(...)


class ReferenceResponseOne(EntryResponseOne):
data: Union[ReferenceResource, Dict[str, Any], None] = Schema(...)


class ReferenceResponseMany(EntryResponseMany):
data: Union[List[ReferenceResource], List[Dict[str, Any]]] = Schema(...)
13 changes: 10 additions & 3 deletions optimade/server/config.ini
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
[DEFAULT]
PROVIDER = _exmpl_
PAGE_LIMIT = 500
USE_REAL_MONGO = no
MONGO_DATABASE = optimade
MONGO_COLLECTION = structures

[STRUCTURE]
[PROVIDER]
prefix = _exmpl_
name = Example provider
description = Provider used for examples, not to be assigned to a real database
homepage = http://example.com
index_base_url = http://example.com/optimade/index

[structures]
band_gap :
_mp_chemsys :

[references]
27 changes: 16 additions & 11 deletions optimade/server/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import json
from typing import Dict, Set
from configparser import ConfigParser
from pathlib import Path

Expand Down Expand Up @@ -34,10 +35,15 @@ class ServerConfig(Config):

use_real_mongo = False
mongo_database = "optimade"
mongo_collection = "structures"
provider = "_exmpl_"
provider = {
"prefix": "_exmpl_",
"name": "Example provider",
"description": "Provider used for examples, not to be assigned to a real database",
"homepage": "http://example.com",
"index_base_url": "http://example.com/optimade/index",
}
page_limit = 500
provider_fields = set()
provider_fields: Dict[str, Set] = {}
_path = Path(__file__).resolve().parent

def load_from_ini(self):
Expand All @@ -55,14 +61,14 @@ def load_from_ini(self):
self.mongo_database = config.get(
"DEFAULT", "MONGO_DATABASE", fallback=self.mongo_database
)
self.mongo_collection = config.get(
"DEFAULT", "MONGO_COLLECTION", fallback=self.mongo_collection
)
self.provider = config.get("DEFAULT", "PROVIDER", fallback=self.provider)
if "PROVIDER" in config.sections():
self.provider = dict(config["PROVIDER"])

self.provider_fields = {
field for field, _ in config["STRUCTURE"].items() if _ == ""
}
self.provider_fields = {}
for endpoint in {"structures", "references"}:
self.provider_fields[endpoint] = {
field for field, _ in config[endpoint].items() if _ == ""
}

def load_from_json(self):
""" Load from the file "config.json", if it exists. """
Expand All @@ -73,7 +79,6 @@ def load_from_json(self):
self.use_real_mongo = bool(config.get("use_real_mongo", self.use_real_mongo))
self.page_limit = int(config.get("page_limit", self.page_limit))
self.mongo_database = config.get("mongo_database", self.mongo_database)
self.mongo_collection = config.get("mongo_collection", self.mongo_collection)
self.provider = config.get("provider", self.provider)
self.provider_fields = set(config.get("provider_fields", self.provider_fields))

Expand Down
25 changes: 15 additions & 10 deletions optimade/server/entry_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,19 @@
from optimade.filtertransformers.mongo import NewMongoTransformer
from optimade.models import NonnegativeInt, EntryResource

from .deps import EntryListingQueryParams, SingleEntryQueryParams
from .config import CONFIG

from .mappers import StructureMapper
from .deps import EntryListingQueryParams, SingleEntryQueryParams
from .mappers import ResourceMapper


class EntryCollection(Collection): # pylint: disable=inherit-non-class
def __init__(self, collection, resource_cls: EntryResource):
def __init__(
self, collection, resource_cls: EntryResource, resource_mapper: ResourceMapper
):
self.collection = collection
self.parser = LarkParser()
self.resource_cls = resource_cls
self.resource_mapper = resource_mapper

def __len__(self):
return self.collection.count()
Expand Down Expand Up @@ -69,12 +71,13 @@ def __init__(
pymongo.collection.Collection, mongomock.collection.Collection
],
resource_cls: EntryResource,
resource_mapper: ResourceMapper,
):
super().__init__(collection, resource_cls)
super().__init__(collection, resource_cls, resource_mapper)
self.transformer = NewMongoTransformer()

self.provider = CONFIG.provider
self.provider_fields = CONFIG.provider_fields
self.provider = CONFIG.provider["prefix"]
self.provider_fields = CONFIG.provider_fields[resource_mapper.ENDPOINT]
self.page_limit = CONFIG.page_limit
self.parser = LarkParser(
version=(0, 10, 0), variant="default"
Expand Down Expand Up @@ -118,7 +121,7 @@ def find(
fields = all_fields.copy()
results = []
for doc in self.collection.find(**criteria):
results.append(self.resource_cls(**StructureMapper.map_back(doc)))
results.append(self.resource_cls(**self.resource_mapper.map_back(doc)))

if isinstance(params, SingleEntryQueryParams):
results = results[0] if results else None
Expand All @@ -134,7 +137,7 @@ def _alias_filter(self, filter_: dict) -> dict:
new_value = value
if isinstance(value, dict):
new_value = self._alias_filter(value)
res[StructureMapper.alias_for(key)] = new_value
res[self.resource_mapper.alias_for(key)] = new_value
return res

def _parse_params(
Expand Down Expand Up @@ -175,7 +178,9 @@ def _parse_params(
# All provider-specific fields
fields |= {self.provider + _ for _ in self.provider_fields}
cursor_kwargs["fields"] = fields
cursor_kwargs["projection"] = [StructureMapper.alias_for(f) for f in fields]
cursor_kwargs["projection"] = [
self.resource_mapper.alias_for(f) for f in fields
]

if getattr(params, "sort", False):
sort_spec = []
Expand Down