utils¶
Utility functions for all routers
aretrieve_queryable_properties(schema, queryable_properties)
async
¶
Asynchronous implementation of retrieve_queryable_properties()
from optimade
Reference to the function in the optimade
API documentation: retrieve_queryable_properties()
.
Recursively loops through the schema of a pydantic model and resolves all references, returning a dictionary of all the OPTIMADE-queryable properties of that model.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
schema | dict | The schema of the pydantic model. | required |
queryable_properties | list | The list of properties to find in the schema. | required |
Returns:
Type | Description |
---|---|
dict | A flat dictionary with properties as keys, containing the field description, unit, sortability, support level, queryability and type, where provided. |
Source code in optimade_gateway/routers/utils.py
async def aretrieve_queryable_properties(
schema: dict, queryable_properties: list
) -> dict:
"""Asynchronous implementation of `retrieve_queryable_properties()` from `optimade`
Reference to the function in the `optimade` API documentation:
[`retrieve_queryable_properties()`](https://www.optimade.org/optimade-python-tools/api_reference/server/schemas/#optimade.server.schemas.retrieve_queryable_properties).
Recursively loops through the schema of a pydantic model and resolves all references, returning
a dictionary of all the OPTIMADE-queryable properties of that model.
Parameters:
schema: The schema of the pydantic model.
queryable_properties: The list of properties to find in the schema.
Returns:
A flat dictionary with properties as keys, containing the field description, unit,
sortability, support level, queryability and type, where provided.
"""
from optimade.server.schemas import retrieve_queryable_properties
return retrieve_queryable_properties(
schema=schema,
queryable_properties=queryable_properties,
)
get_entries(collection, response_cls, request, params)
async
¶
Generalized /{entries}
endpoint getter
Source code in optimade_gateway/routers/utils.py
async def get_entries(
collection: AsyncMongoCollection,
response_cls: EntryResponseMany,
request: Request,
params: EntryListingQueryParams,
) -> EntryResponseMany:
"""Generalized `/{entries}` endpoint getter"""
results, more_data_available, fields = await collection.find(params=params)
if more_data_available:
# Deduce the `next` link from the current request
query = urllib.parse.parse_qs(request.url.query)
query["page_offset"] = int(query.get("page_offset", [0])[0]) + len(results)
urlencoded = urllib.parse.urlencode(query, doseq=True)
base_url = get_base_url(request.url)
links = ToplevelLinks(next=f"{base_url}{request.url.path}?{urlencoded}")
else:
links = ToplevelLinks(next=None)
if fields:
results = handle_response_fields(results, fields)
return response_cls(
links=links,
data=results,
meta=meta_values(
url=request.url,
data_returned=await collection.count(params=params),
data_available=await collection.count(),
more_data_available=more_data_available,
),
)
get_valid_resource(collection, entry_id)
async
¶
Validate and retrieve a resource
Source code in optimade_gateway/routers/utils.py
async def get_valid_resource(
collection: AsyncMongoCollection, entry_id: str
) -> EntryResource:
"""Validate and retrieve a resource"""
from optimade_gateway.routers.utils import validate_resource
await validate_resource(collection, entry_id)
return await collection.get_one(filter={"id": entry_id})
resource_factory(create_resource)
async
¶
Get or create a resource
Currently supported resources:
"databases"
(DatabaseCreate
->LinksResource
)"gateways"
(GatewayCreate
->GatewayResource
)"queries"
(QueryCreate
->QueryResource
)
For each of the resources, "uniqueness" is determined in the following way:
The base_url
field is considered unique across all databases.
If a base_url
is provided via a Link
model, the base_url.href
value is used to query the MongoDB.
The collected list of databases.attributes.base_url
values is considered unique across all gateways.
In the database, the search is done as a combination of the length/size of the databases
' Python list/MongoDB array and a match on all (using the MongoDB $all
operator) of the databases.attributes.base_url
element values, when compared with the create_resource
.
The gateway_id
, query_parameters
, and endpoint
fields are collectively considered to define uniqueness for a QueryResource
in the MongoDB collection.
Attention
Only the /structures
entry endpoint can be queried with multiple expected responses.
This means the endpoint
field defaults to "structures"
and endpoint_model
defaults to ("optimade.models.responses", "StructureResponseMany")
, i.e., the StructureResponseMany
response model.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
create_resource | Union[optimade_gateway.models.databases.DatabaseCreate, optimade_gateway.models.gateways.GatewayCreate, optimade_gateway.models.queries.QueryCreate] | The resource to be retrieved or created anew. | required |
Returns:
Type | Description |
---|---|
Tuple[Union[optimade.models.links.LinksResource, optimade_gateway.models.gateways.GatewayResource, optimade_gateway.models.queries.QueryResource], bool] | Two things in a tuple:
|
Source code in optimade_gateway/routers/utils.py
async def resource_factory(
create_resource: Union[DatabaseCreate, GatewayCreate, QueryCreate]
) -> Tuple[Union[LinksResource, GatewayResource, QueryResource], bool]:
"""Get or create a resource
Currently supported resources:
- `"databases"` ([`DatabaseCreate`][optimade_gateway.models.databases.DatabaseCreate] ->
[`LinksResource`](https://www.optimade.org/optimade-python-tools/api_reference/models/links/#optimade.models.links.LinksResource))
- `"gateways"` ([`GatewayCreate`][optimade_gateway.models.gateways.GatewayCreate] ->
[`GatewayResource`][optimade_gateway.models.gateways.GatewayResource])
- `"queries"` ([`QueryCreate`][optimade_gateway.models.queries.QueryCreate] ->
[`QueryResource`][optimade_gateway.models.queries.QueryResource])
For each of the resources, "uniqueness" is determined in the following way:
=== "Databases"
The `base_url` field is considered unique across all databases.
If a `base_url` is provided via a
[`Link`](https://www.optimade.org/optimade-python-tools/api_reference/models/jsonapi/#optimade.models.jsonapi.Link)
model, the `base_url.href` value is used to query the MongoDB.
=== "Gateways"
The collected list of `databases.attributes.base_url` values is considered unique across
all gateways.
In the database, the search is done as a combination of the length/size of the `databases`'
Python list/MongoDB array and a match on all (using the MongoDB `$all` operator) of the
[`databases.attributes.base_url`](https://www.optimade.org/optimade-python-tools/api_reference/models/links/#optimade.models.links.LinksResourceAttributes.base_url)
element values, when compared with the `create_resource`.
=== "Queries"
The `gateway_id`, `query_parameters`, and `endpoint` fields are collectively considered to
define uniqueness for a [`QueryResource`][optimade_gateway.models.queries.QueryResource]
in the MongoDB collection.
!!! attention
Only the `/structures` entry endpoint can be queried with multiple expected responses.
This means the `endpoint` field defaults to `"structures"` and `endpoint_model`
defaults to `("optimade.models.responses", "StructureResponseMany")`, i.e., the
[`StructureResponseMany`](https://www.optimade.org/optimade-python-tools/api_reference/models/responses/#optimade.models.responses.StructureResponseMany)
response model.
Parameters:
create_resource: The resource to be retrieved or created anew.
Returns:
Two things in a tuple:
- Either a [`GatewayResource`][optimade_gateway.models.gateways.GatewayResource]; a
[`QueryResource`][optimade_gateway.models.queries.QueryResource]; or a
[`LinksResource`](https://www.optimade.org/optimade-python-tools/api_reference/models/links/#optimade.models.links.LinksResource) and
- whether or not the resource was newly created.
"""
from optimade_gateway.common.utils import clean_python_types, get_resource_attribute
created = False
if isinstance(create_resource, DatabaseCreate):
from optimade_gateway.routers.databases import (
DATABASES_COLLECTION as RESOURCE_COLLECTION,
)
base_url = get_resource_attribute(create_resource, "base_url")
mongo_query = {
"$or": [
{"base_url": {"$eq": await clean_python_types(base_url)}},
{"base_url.href": {"$eq": await clean_python_types(base_url)}},
]
}
elif isinstance(create_resource, GatewayCreate):
from optimade_gateway.routers.gateways import (
GATEWAYS_COLLECTION as RESOURCE_COLLECTION,
)
mongo_query = {
"databases": {"$size": len(create_resource.databases)},
"databases.attributes.base_url": {
"$all": await clean_python_types(
[_.attributes.base_url for _ in create_resource.databases]
)
},
}
elif isinstance(create_resource, QueryCreate):
from optimade_gateway.models.queries import QueryState
from optimade_gateway.routers.queries import (
QUERIES_COLLECTION as RESOURCE_COLLECTION,
)
# Currently only /structures entry endpoints can be queried with multiple expected responses.
create_resource.endpoint = (
create_resource.endpoint if create_resource.endpoint else "structures"
)
create_resource.endpoint_model = (
create_resource.endpoint_model
if create_resource.endpoint_model
else ("optimade.models.responses", "StructureResponseMany")
)
mongo_query = {
"gateway_id": {"$eq": create_resource.gateway_id},
"query_parameters": {
"$eq": await clean_python_types(create_resource.query_parameters),
},
"endpoint": {"$eq": create_resource.endpoint},
}
else:
raise TypeError(
"create_resource must be either a GatewayCreate or QueryCreate object not "
f"{type(create_resource)!r}"
)
result, more_data_available, _ = await RESOURCE_COLLECTION.find(
criteria={"filter": mongo_query}
)
if more_data_available:
raise OptimadeGatewayError(
"more_data_available MUST be False for a single entry response, however it is "
f"{more_data_available}"
)
if result:
if len(result) > 1:
raise OptimadeGatewayError(
f"More than one {result[0].type} were found. IDs of found {result[0].type}: "
f"{[_.id for _ in result]}"
)
result = result[0]
else:
if isinstance(create_resource, DatabaseCreate):
# Set required `LinksResourceAttributes` values if not set
if not create_resource.description:
create_resource.description = f"{create_resource.name} created by OPTIMADE gateway database registration."
if not create_resource.link_type:
create_resource.link_type = LinkType.EXTERNAL
if not create_resource.homepage:
create_resource.homepage = None
elif isinstance(create_resource, GatewayCreate):
# Do not store `database_ids`
if "database_ids" in create_resource.__fields_set__:
create_resource.database_ids = None
create_resource.__fields_set__.remove("database_ids")
elif isinstance(create_resource, QueryCreate):
create_resource.state = QueryState.CREATED
result = await RESOURCE_COLLECTION.create_one(create_resource)
LOGGER.debug("Created new %s: %r", result.type, result)
created = True
return result, created
validate_resource(collection, entry_id)
async
¶
Validate whether a resource exists in a collection
Source code in optimade_gateway/routers/utils.py
async def validate_resource(collection: AsyncMongoCollection, entry_id: str) -> None:
"""Validate whether a resource exists in a collection"""
if not await collection.exists(entry_id):
raise BadRequest(
title="Not Found",
status_code=404,
detail=f"Resource <id={entry_id}> not found in {collection}.",
)