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

Assorted server optimizations #1717

Merged
merged 3 commits into from
Jul 22, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 5 additions & 15 deletions optimade/server/entry_collections/entry_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,22 +137,15 @@ def count(self, **kwargs: Any) -> int:

def find(
self, params: Union[EntryListingQueryParams, SingleEntryQueryParams]
) -> Tuple[
Union[None, List[EntryResource], EntryResource, List[Dict]],
int,
bool,
Set[str],
Set[str],
]:
) -> Tuple[Union[None, Dict, List[Dict]], int, bool, Set[str], Set[str],]:
"""
Fetches results and indicates if more data is available.

Also gives the total number of data available in the absence of `page_limit`.
See [`EntryListingQueryParams`][optimade.server.query_params.EntryListingQueryParams]
for more information.

Returns either the list of validated pydantic models matching the query, or simply the
mapped database reponse, depending on the value of `CONFIG.validate_api_response`.
Returns a list of the mapped database reponse.

If no results match the query, then `results` is set to `None`.

Expand Down Expand Up @@ -202,13 +195,10 @@ def find(
detail=f"Unrecognised OPTIMADE field(s) in requested `response_fields`: {bad_optimade_fields}."
)

results: Union[None, List[EntryResource], EntryResource, List[Dict]] = None
results: Union[None, List[Dict], Dict] = None

if raw_results:
if CONFIG.validate_api_response:
results = self.resource_mapper.deserialize(raw_results)
else:
results = [self.resource_mapper.map_back(doc) for doc in raw_results]
results = [self.resource_mapper.map_back(doc) for doc in raw_results]

if single_entry:
results = results[0] # type: ignore[assignment]
Expand Down Expand Up @@ -468,7 +458,7 @@ def parse_sort_params(self, sort_params: str) -> Iterable[Tuple[str, int]]:
def get_next_query_params(
self,
params: EntryListingQueryParams,
results: Union[None, List[EntryResource], EntryResource, List[Dict]],
results: Union[None, Dict, List[Dict]],
) -> Dict[str, List[str]]:
"""Provides url query pagination parameters that will be used in the next
link.
Expand Down
8 changes: 4 additions & 4 deletions optimade/server/routers/links.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any, Dict

from fastapi import APIRouter, Depends, Request

from optimade.models import LinksResource, LinksResponse
Expand All @@ -19,14 +21,12 @@

@router.get(
"/links",
response_model=LinksResponse,
response_model=LinksResponse if CONFIG.validate_api_response else Dict,
response_model_exclude_unset=True,
tags=["Links"],
responses=ERROR_RESPONSES,
)
def get_links(
request: Request, params: EntryListingQueryParams = Depends()
) -> LinksResponse:
def get_links(request: Request, params: EntryListingQueryParams = Depends()) -> Any:
JPBergsma marked this conversation as resolved.
Show resolved Hide resolved
return get_entries(
collection=links_coll, response=LinksResponse, request=request, params=params
)
10 changes: 6 additions & 4 deletions optimade/server/routers/references.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any, Dict

from fastapi import APIRouter, Depends, Request

from optimade.models import (
Expand All @@ -23,14 +25,14 @@

@router.get(
"/references",
response_model=ReferenceResponseMany,
response_model=ReferenceResponseMany if CONFIG.validate_api_response else Dict,
response_model_exclude_unset=True,
tags=["References"],
responses=ERROR_RESPONSES,
)
def get_references(
request: Request, params: EntryListingQueryParams = Depends()
) -> ReferenceResponseMany:
) -> Any:
return get_entries(
collection=references_coll,
response=ReferenceResponseMany,
Expand All @@ -41,14 +43,14 @@ def get_references(

@router.get(
"/references/{entry_id:path}",
response_model=ReferenceResponseOne,
response_model=ReferenceResponseOne if CONFIG.validate_api_response else Dict,
response_model_exclude_unset=True,
tags=["References"],
responses=ERROR_RESPONSES,
)
def get_single_reference(
request: Request, entry_id: str, params: SingleEntryQueryParams = Depends()
) -> ReferenceResponseOne:
) -> Any:
return get_single_entry(
collection=references_coll,
entry_id=entry_id,
Expand Down
10 changes: 6 additions & 4 deletions optimade/server/routers/structures.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any, Dict

from fastapi import APIRouter, Depends, Request

from optimade.models import (
Expand All @@ -23,14 +25,14 @@

@router.get(
"/structures",
response_model=StructureResponseMany,
response_model=StructureResponseMany if CONFIG.validate_api_response else Dict,
response_model_exclude_unset=True,
tags=["Structures"],
responses=ERROR_RESPONSES,
)
def get_structures(
request: Request, params: EntryListingQueryParams = Depends()
) -> StructureResponseMany:
) -> Any:
return get_entries(
collection=structures_coll,
response=StructureResponseMany,
Expand All @@ -41,14 +43,14 @@ def get_structures(

@router.get(
"/structures/{entry_id:path}",
response_model=StructureResponseOne,
response_model=StructureResponseOne if CONFIG.validate_api_response else Dict,
response_model_exclude_unset=True,
tags=["Structures"],
responses=ERROR_RESPONSES,
)
def get_single_structure(
request: Request, entry_id: str, params: SingleEntryQueryParams = Depends()
) -> StructureResponseOne:
) -> Any:
return get_single_entry(
collection=structures_coll,
entry_id=entry_id,
Expand Down
10 changes: 5 additions & 5 deletions optimade/server/routers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,10 @@ def get_base_url(

def get_entries(
collection: EntryCollection,
response: Type[EntryResponseMany],
response: Type[EntryResponseMany], # noqa
request: Request,
params: EntryListingQueryParams,
) -> EntryResponseMany:
) -> Dict:
"""Generalized /{entry} endpoint getter"""
from optimade.server.routers import ENTRY_COLLECTIONS

Expand Down Expand Up @@ -285,7 +285,7 @@ def get_entries(
if results is not None and (fields or include_fields):
results = handle_response_fields(results, fields, include_fields) # type: ignore[assignment]

return response(
return dict(
links=links,
data=results if results else [],
meta=meta_values(
Expand All @@ -307,7 +307,7 @@ def get_single_entry(
response: Type[EntryResponseOne],
request: Request,
params: SingleEntryQueryParams,
) -> EntryResponseOne:
) -> Dict:
from optimade.server.routers import ENTRY_COLLECTIONS

params.check_params(request.query_params)
Expand Down Expand Up @@ -338,7 +338,7 @@ def get_single_entry(
if results is not None and (fields or include_fields):
results = handle_response_fields(results, fields, include_fields)[0] # type: ignore[assignment]

return response(
return dict(
links=links,
data=results if results else None,
meta=meta_values(
Expand Down
6 changes: 5 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@
# Server minded
elastic_deps = ["elasticsearch-dsl~=7.4,<8.0", "elasticsearch~=7.17"]
mongo_deps = ["pymongo>=3.12.1,<5", "mongomock~=4.1"]
server_deps = ["uvicorn~=0.19", "fastapi~=0.86,<0.99", "pyyaml~=6.0"] + mongo_deps
server_deps = [
"uvicorn[standard]~=0.19",
"fastapi~=0.86,<0.99",
"pyyaml~=6.0",
] + mongo_deps


# Client minded
Expand Down
34 changes: 16 additions & 18 deletions tests/server/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

from optimade.client.cli import _get
from optimade.server.config import CONFIG, SupportedBackend
from optimade.warnings import MissingExpectedField

try:
from optimade.client import OptimadeClient as OptimadeTestClient
Expand Down Expand Up @@ -115,24 +114,23 @@ def test_filter_validation(async_http_client, http_client, use_async):

@pytest.mark.parametrize("use_async", [True, False])
def test_client_response_fields(async_http_client, http_client, use_async):
with pytest.warns(MissingExpectedField):
cli = OptimadeClient(
base_urls=[TEST_URL],
use_async=use_async,
http_client=async_http_client if use_async else http_client,
)
results = cli.get(response_fields=["chemical_formula_reduced"])
for d in results["structures"][""][TEST_URL]["data"]:
assert "chemical_formula_reduced" in d["attributes"]
assert len(d["attributes"]) == 1
cli = OptimadeClient(
base_urls=[TEST_URL],
use_async=use_async,
http_client=async_http_client if use_async else http_client,
)
results = cli.get(response_fields=["chemical_formula_reduced"])
for d in results["structures"][""][TEST_URL]["data"]:
assert "chemical_formula_reduced" in d["attributes"]
assert len(d["attributes"]) == 1

results = cli.get(
response_fields=["chemical_formula_reduced", "cartesian_site_positions"]
)
for d in results["structures"][""][TEST_URL]["data"]:
assert "chemical_formula_reduced" in d["attributes"]
assert "cartesian_site_positions" in d["attributes"]
assert len(d["attributes"]) == 2
results = cli.get(
response_fields=["chemical_formula_reduced", "cartesian_site_positions"]
)
for d in results["structures"][""][TEST_URL]["data"]:
assert "chemical_formula_reduced" in d["attributes"]
assert "cartesian_site_positions" in d["attributes"]
assert len(d["attributes"]) == 2


@pytest.mark.parametrize("use_async", [True, False])
Expand Down