Beispiel #1
0
def general_exception(
    request: Request,
    exc: Exception,
    status_code: int = 500,  # A status_code in `exc` will take precedence
    errors: List[OptimadeError] = None,
) -> JSONResponse:
    debug_info = {}
    if CONFIG.debug:
        tb = "".join(
            traceback.format_exception(etype=type(exc),
                                       value=exc,
                                       tb=exc.__traceback__))
        LOGGER.error("Traceback:\n%s", tb)
        debug_info[f"_{CONFIG.provider.prefix}_traceback"] = tb

    try:
        http_response_code = int(exc.status_code)
    except AttributeError:
        http_response_code = int(status_code)

    try:
        title = str(exc.title)
    except AttributeError:
        title = str(exc.__class__.__name__)

    try:
        detail = str(exc.detail)
    except AttributeError:
        detail = str(exc)

    if errors is None:
        errors = [
            OptimadeError(detail=detail,
                          status=http_response_code,
                          title=title)
        ]

    response = ErrorResponse(
        meta=meta_values(
            url=request.url,
            data_returned=0,
            data_available=0,
            more_data_available=False,
            **debug_info,
        ),
        errors=errors,
    )

    return JSONResponse(
        status_code=http_response_code,
        content=jsonable_encoder(response, exclude_unset=True),
    )
Beispiel #2
0
    def create_optimade_index(self) -> None:
        """Load or create an index that can handle aliased OPTIMADE fields and attach it
        to the current client.

        """
        body = self.predefined_index.get(self.name)
        if body is None:
            body = self.create_elastic_index_from_mapper(
                self.resource_mapper, self.all_fields
            )

        properties = {}
        for field in list(body["mappings"]["doc"]["properties"].keys()):
            properties[self.resource_mapper.get_backend_field(field)] = body[
                "mappings"
            ]["doc"]["properties"].pop(field)
        body["mappings"]["doc"]["properties"] = properties
        self.client.indices.create(index=self.name, body=body, ignore=400)

        LOGGER.debug(f"Created Elastic index for {self.name!r} with body {body}")
Beispiel #3
0
    def load_entries(endpoint_name: str, endpoint_collection: MongoCollection):
        LOGGER.debug("Loading test %s...", endpoint_name)

        endpoint_collection.collection.insert_many(getattr(data, endpoint_name, []))
        if endpoint_name == "links":
            LOGGER.debug(
                "  Adding Materials-Consortia providers to links from optimade.org"
            )
            providers = get_providers()
            for doc in providers:
                endpoint_collection.collection.replace_one(
                    filter={"_id": ObjectId(doc["_id"]["$oid"])},
                    replacement=bson.json_util.loads(bson.json_util.dumps(doc)),
                    upsert=True,
                )
        LOGGER.debug("Done inserting test %s!", endpoint_name)
    def load_entries(endpoint_name: str, endpoint_collection: EntryCollection):
        LOGGER.debug("Loading test %s...", endpoint_name)

        endpoint_collection.insert(getattr(data, endpoint_name, []))
        if (CONFIG.database_backend.value in ("mongomock", "mongodb")
                and endpoint_name == "links"):
            LOGGER.debug(
                "Adding Materials-Consortia providers to links from optimade.org"
            )
            providers = get_providers(add_mongo_id=True)
            for doc in providers:
                endpoint_collection.collection.replace_one(
                    filter={"_id": ObjectId(doc["_id"]["$oid"])},
                    replacement=bson.json_util.loads(
                        bson.json_util.dumps(doc)),
                    upsert=True,
                )
        LOGGER.debug("Done inserting test %s!", endpoint_name)
from optimade.server.exception_handlers import OPTIMADE_EXCEPTIONS
from optimade.server.middleware import OPTIMADE_MIDDLEWARE

from optimade.server.routers import (
    info,
    landing,
    links,
    references,
    structures,
    versions,
)
from optimade.server.routers.utils import BASE_URL_PREFIXES, JSONAPIResponse

if config_warnings:
    LOGGER.warn(
        f"Invalid config file or no config file provided, running server with default settings. Errors: "
        f"{[warnings.formatwarning(w.message, w.category, w.filename, w.lineno, '') for w in config_warnings]}"
    )
else:
    LOGGER.info(
        f"Loaded settings from {os.getenv('OPTIMADE_CONFIG_FILE', DEFAULT_CONFIG_FILE_PATH)}."
    )

if CONFIG.debug:  # pragma: no cover
    LOGGER.info("DEBUG MODE")

app = FastAPI(
    root_path=CONFIG.root_path,
    title="OPTIMADE API",
    description=
    (f"""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.
from optimade import __api_version__, __version__
import optimade.server.exception_handlers as exc_handlers
from optimade.server.logger import LOGGER
from optimade.server.middleware import (
    AddWarnings,
    CheckWronglyVersionedBaseUrls,
    EnsureQueryParamIntegrity,
    HandleApiHint,
)
from optimade.server.routers import index_info, links, versions
from optimade.server.routers.utils import BASE_URL_PREFIXES

if CONFIG.config_file is None:
    LOGGER.warn(
        f"Invalid config file or no config file provided, running server with default settings. Errors: "
        f"{[warnings.formatwarning(w.message, w.category, w.filename, w.lineno, '') for w in config_warnings]}"
    )
else:
    LOGGER.info(f"Loaded settings from {CONFIG.config_file}.")

if CONFIG.debug:  # pragma: no cover
    LOGGER.info("DEBUG MODE")

app = FastAPI(
    title="OPTIMADE API - Index meta-database",
    description=
    (f"""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.
This is the "special" index meta-database.

This specification is generated using [`optimade-python-tools`](https://github.com/Materials-Consortia/optimade-python-tools/tree/v{__version__}) v{__version__}."""
     ),
Beispiel #7
0
from typing import Dict, Tuple, List, Any

from optimade.filterparser import LarkParser
from optimade.filtertransformers.mongo import MongoTransformer
from optimade.models import EntryResource
from optimade.server.config import CONFIG
from optimade.server.entry_collections import EntryCollection
from optimade.server.logger import LOGGER
from optimade.server.mappers import BaseResourceMapper

if CONFIG.database_backend.value == "mongodb":
    from pymongo import MongoClient

    LOGGER.info("Using: Real MongoDB (pymongo)")

elif CONFIG.database_backend.value == "mongomock":
    from mongomock import MongoClient

    LOGGER.info("Using: Mock MongoDB (mongomock)")

if CONFIG.database_backend.value in ("mongomock", "mongodb"):
    CLIENT = MongoClient(CONFIG.mongo_uri)


class MongoCollection(EntryCollection):
    """Class for querying MongoDB collections (implemented by either pymongo or mongomock)
    containing serialized [`EntryResource`][optimade.models.entries.EntryResource]s objects.

    """
    def __init__(
        self,
Beispiel #8
0
    HandleApiHint,
)
from optimade.server.routers import (
    info,
    landing,
    links,
    references,
    structures,
    versions,
)
from optimade.server.routers.utils import BASE_URL_PREFIXES


if CONFIG.config_file is None:
    LOGGER.warn(
        f"Invalid config file or no config file provided, running server with default settings. Errors: "
        f"{[warnings.formatwarning(w.message, w.category, w.filename, w.lineno, '') for w in config_warnings]}"
    )
else:
    LOGGER.info(f"Loaded settings from {CONFIG.config_file}.")

if CONFIG.debug:  # pragma: no cover
    LOGGER.info("DEBUG MODE")

app = FastAPI(
    title="OPTIMADE API",
    description=(
        f"""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.

This specification is generated using [`optimade-python-tools`](https://github.com/Materials-Consortia/optimade-python-tools/tree/v{__version__}) v{__version__}."""
    ),
    version=__api_version__,
with warnings.catch_warnings(record=True) as w:
    from optimade.server.config import CONFIG

    config_warnings = w

from optimade import __api_version__, __version__
from optimade.server.logger import LOGGER
from optimade.server.exception_handlers import OPTIMADE_EXCEPTIONS
from optimade.server.middleware import OPTIMADE_MIDDLEWARE
from optimade.server.routers import index_info, links, versions
from optimade.server.routers.utils import BASE_URL_PREFIXES

if os.getenv("OPTIMADE_CONFIG_FILE") is None:
    LOGGER.warn(
        f"Invalid config file or no config file provided, running server with default settings. Errors: "
        f"{[warnings.formatwarning(w.message, w.category, w.filename, w.lineno, '') for w in config_warnings]}"
    )
else:
    LOGGER.info(f"Loaded settings from {os.getenv('OPTIMADE_CONFIG_FILE')}.")

if CONFIG.debug:  # pragma: no cover
    LOGGER.info("DEBUG MODE")

app = FastAPI(
    root_path=CONFIG.root_path,
    title="OPTIMADE API - Index meta-database",
    description=
    (f"""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.
This is the "special" index meta-database.

This specification is generated using [`optimade-python-tools`](https://github.com/Materials-Consortia/optimade-python-tools/tree/v{__version__}) v{__version__}."""
Beispiel #10
0
def general_exception(
    request: Request,
    exc: Exception,
    status_code: int = 500,  # A status_code in `exc` will take precedence
    errors: List[OptimadeError] = None,
) -> JSONAPIResponse:
    """Handle an exception

    Parameters:
        request: The HTTP request resulting in the exception being raised.
        exc: The exception being raised.
        status_code: The returned HTTP status code for the error response.
        errors: List of error resources as defined in
            [the OPTIMADE specification](https://github.com/Materials-Consortia/OPTIMADE/blob/develop/optimade.rst#json-response-schema-common-fields).

    Returns:
        A JSON HTTP response based on [`ErrorResponse`][optimade.models.responses.ErrorResponse].

    """
    debug_info = {}
    if CONFIG.debug:
        tb = "".join(
            traceback.format_exception(type(exc),
                                       value=exc,
                                       tb=exc.__traceback__))
        LOGGER.error("Traceback:\n%s", tb)
        debug_info[f"_{CONFIG.provider.prefix}_traceback"] = tb

    try:
        http_response_code = int(exc.status_code)
    except AttributeError:
        http_response_code = int(status_code)

    try:
        title = str(exc.title)
    except AttributeError:
        title = str(exc.__class__.__name__)

    try:
        detail = str(exc.detail)
    except AttributeError:
        detail = str(exc)

    if errors is None:
        errors = [
            OptimadeError(detail=detail,
                          status=http_response_code,
                          title=title)
        ]

    response = ErrorResponse(
        meta=meta_values(
            url=request.url,
            data_returned=0,
            data_available=0,
            more_data_available=False,
            **debug_info,
        ),
        errors=errors,
    )

    return JSONAPIResponse(
        status_code=http_response_code,
        content=jsonable_encoder(response, exclude_unset=True),
    )
Beispiel #11
0
def get_providers() -> list:
    """Retrieve Materials-Consortia providers (from https://providers.optimade.org/v1/links).

    Fallback order if providers.optimade.org is not available:

    1. Try Materials-Consortia/providers on GitHub.
    2. Try submodule `providers`' list of providers.
    3. Log warning that providers list from Materials-Consortia is not included in the
       `/links`-endpoint.

    Returns:
        List of raw JSON-decoded providers including MongoDB object IDs.

    """
    import requests

    try:
        import simplejson as json
    except ImportError:
        import json

    provider_list_urls = [
        "https://providers.optimade.org/v1/links",
        "https://raw.githubusercontent.com/Materials-Consortia/providers",
        "/master/src/links/v1/providers.json",
    ]

    for provider_list_url in provider_list_urls:
        try:
            providers = requests.get(provider_list_url).json()
        except (
                requests.exceptions.ConnectionError,
                requests.exceptions.ConnectTimeout,
                json.JSONDecodeError,
        ):
            pass
        else:
            break
    else:
        try:
            from optimade.server.data import providers
        except ImportError:
            from optimade.server.logger import LOGGER

            LOGGER.warning("""Could not retrieve a list of providers!

    Tried the following resources:

{}
    The list of providers will not be included in the `/links`-endpoint.
""".format("".join([f"    * {_}\n" for _ in provider_list_urls])))
            return []

    providers_list = []
    for provider in providers.get("data", []):
        # Remove/skip "exmpl"
        if provider["id"] == "exmpl":
            continue

        provider.update(provider.pop("attributes", {}))

        # Add MongoDB ObjectId
        provider["_id"] = {
            "$oid": mongo_id_for_database(provider["id"], provider["type"])
        }

        providers_list.append(provider)

    return providers_list
from typing import Dict, Tuple, List, Any

from optimade.filterparser import LarkParser
from optimade.filtertransformers.mongo import MongoTransformer
from optimade.models import EntryResource
from optimade.server.config import CONFIG
from optimade.server.entry_collections import EntryCollection
from optimade.server.logger import LOGGER
from optimade.server.mappers import BaseResourceMapper

if CONFIG.database_backend.value == "mongodb":
    from pymongo import MongoClient, version_tuple

    if version_tuple[0] < 4:
        LOGGER.warning(
            "Support for pymongo<=3 (and thus MongoDB v3) is deprecated and will be "
            "removed in the next minor release.")

    LOGGER.info("Using: Real MongoDB (pymongo)")

elif CONFIG.database_backend.value == "mongomock":
    from mongomock import MongoClient

    LOGGER.info("Using: Mock MongoDB (mongomock)")

if CONFIG.database_backend.value in ("mongomock", "mongodb"):
    CLIENT = MongoClient(CONFIG.mongo_uri)


class MongoCollection(EntryCollection):
    """Class for querying MongoDB collections (implemented by either pymongo or mongomock)
Beispiel #13
0
from optimade.filtertransformers.elasticsearch import ElasticTransformer
from optimade.server.config import CONFIG
from optimade.server.logger import LOGGER
from optimade.models import EntryResource
from optimade.server.mappers import BaseResourceMapper
from optimade.server.entry_collections import EntryCollection


if CONFIG.database_backend.value == "elastic":
    from elasticsearch import Elasticsearch
    from elasticsearch.helpers import bulk
    from elasticsearch_dsl import Search

    CLIENT = Elasticsearch(hosts=CONFIG.elastic_hosts)
    LOGGER.info("Using: Elasticsearch backend at %s", CONFIG.elastic_hosts)


class ElasticCollection(EntryCollection):
    def __init__(
        self,
        name: str,
        resource_cls: EntryResource,
        resource_mapper: BaseResourceMapper,
        client: Optional["Elasticsearch"] = None,
    ):
        """Initialize the ElasticCollection for the given parameters.

        Parameters:
            name: The name of the collection.
            resource_cls: The type of entry resource that is stored by the collection.