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 /files endpoint and entry type #1414

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
Draft
5 changes: 5 additions & 0 deletions docs/api_reference/models/files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# files

::: optimade.models.files
options:
show_if_no_docstring: true
3 changes: 3 additions & 0 deletions docs/api_reference/server/mappers/files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# files

::: optimade.server.mappers.files
3 changes: 3 additions & 0 deletions docs/api_reference/server/routers/files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# files

::: optimade.server.routers.files
16 changes: 8 additions & 8 deletions openapi/index_openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -506,15 +506,15 @@
"type": "string",
"title": "Id",
"description": "An entry's ID as defined in section Definition of Terms.\n\n- **Type**: string.\n\n- **Requirements/Conventions**:\n - **Support**: MUST be supported by all implementations, MUST NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response.\n\n- **Examples**:\n - `\"db/1234567\"`\n - `\"cod/2000000\"`\n - `\"cod/2000000@1234567\"`\n - `\"nomad/L1234567890\"`\n - `\"42\"`",
"x-optimade-queryable": "must",
"x-optimade-support": "must"
"x-optimade-support": "must",
"x-optimade-queryable": "must"
},
"type": {
"type": "string",
"title": "Type",
"description": "The name of the type of an entry.\n\n- **Type**: string.\n\n- **Requirements/Conventions**:\n - **Support**: MUST be supported by all implementations, MUST NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response.\n - MUST be an existing entry type.\n - The entry of type `<type>` and ID `<id>` MUST be returned in response to a request for `/<type>/<id>` under the versioned base URL.\n\n- **Example**: `\"structures\"`",
"x-optimade-queryable": "must",
"x-optimade-support": "must"
"x-optimade-support": "must",
"x-optimade-queryable": "must"
},
"links": {
"anyOf": [
Expand Down Expand Up @@ -580,8 +580,8 @@
],
"title": "Immutable Id",
"description": "The entry's immutable ID (e.g., an UUID). This is important for databases having preferred IDs that point to \"the latest version\" of a record, but still offer access to older variants. This ID maps to the version-specific record, in case it changes in the future.\n\n- **Type**: string.\n\n- **Requirements/Conventions**:\n - **Support**: OPTIONAL support in implementations, i.e., MAY be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n\n- **Examples**:\n - `\"8bd3e750-b477-41a0-9b11-3a799f21b44f\"`\n - `\"fjeiwoj,54;@=%<>#32\"` (Strings that are not URL-safe are allowed.)",
"x-optimade-queryable": "must",
"x-optimade-support": "optional"
"x-optimade-support": "optional",
"x-optimade-queryable": "must"
},
"last_modified": {
"anyOf": [
Expand Down Expand Up @@ -1265,8 +1265,8 @@
"type": "string",
"title": "Id",
"description": "An entry's ID as defined in section Definition of Terms.\n\n- **Type**: string.\n\n- **Requirements/Conventions**:\n - **Support**: MUST be supported by all implementations, MUST NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response.\n\n- **Examples**:\n - `\"db/1234567\"`\n - `\"cod/2000000\"`\n - `\"cod/2000000@1234567\"`\n - `\"nomad/L1234567890\"`\n - `\"42\"`",
"x-optimade-queryable": "must",
"x-optimade-support": "must"
"x-optimade-support": "must",
"x-optimade-queryable": "must"
},
"type": {
"type": "string",
Expand Down
1,710 changes: 1,298 additions & 412 deletions openapi/openapi.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions optimade/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .baseinfo import * # noqa: F403
from .entries import * # noqa: F403
from .files import * # noqa: F403
from .index_metadb import * # noqa: F403
from .jsonapi import * # noqa: F403
from .links import * # noqa: F403
Expand All @@ -20,4 +21,5 @@
+ references.__all__ # type: ignore[name-defined] # noqa: F405
+ responses.__all__ # type: ignore[name-defined] # noqa: F405
+ structures.__all__ # type: ignore[name-defined] # noqa: F405
+ files.__all__ # type: ignore[name-defined] # noqa: F405
)
225 changes: 225 additions & 0 deletions optimade/models/files.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
# pylint: disable=no-self-argument,line-too-long,no-name-in-module
from datetime import datetime
from typing import Optional

from optimade.models.entries import EntryResource, EntryResourceAttributes
from optimade.models.utils import OptimadeField, StrictField, SupportLevel

__all__ = (
"FileResourceAttributes",
"FileResource",
)


CORRELATED_FILE_FIELDS: tuple[set[str], ...]


class FileResourceAttributes(EntryResourceAttributes):
"""This class contains the Field for the attributes used to represent a file, e.g. ."""

url: str = OptimadeField(
...,
description="""The URL to get the contents of a file.
- **Type**: string
- **Requirements/Conventions**:

- **Support**: MUST be supported by all implementations, MUST NOT be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.
- **Response**: REQUIRED in the response.
- The URL MUST point to the actual contents of a file (i.e. byte stream), not an intermediate (preview) representation.
For example, if referring to a file on GitHub, a link should point to raw contents.

- **Examples**:

- :val:`"https://example.org/files/cifs/1000000.cif"`
""",
support=SupportLevel.MUST,
queryable=SupportLevel.OPTIONAL,
)

url_stable_until: Optional[datetime] = OptimadeField(
...,
description="""Point in time until which the URL in `url` is guaranteed to stay stable.
- **Type**: timestamp
- **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.
- :val:`null` means that there is no stability guarantee for the URL in `url`.
Indefinite support could be communicated by providing a date sufficiently far in the future, for example, :val:`9999-12-31`.""",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
)

name: str = OptimadeField(
...,
description="""Base name of a file.
- **Type**: string
- **Requirements/Conventions**:

- **Support**: MUST be supported by all implementations, MUST NOT be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.
- File name extension is an integral part of a file name and, if available, MUST be included.

- **Examples**:

- :val:`"1000000.cif"`""",
support=SupportLevel.MUST,
queryable=SupportLevel.OPTIONAL,
)

size: Optional[int] = OptimadeField(
...,
description="""Size of a file in bytes.
- **Type**: integer
- **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.
- If provided, it MUST be guaranteed that either exact size of a file is given or its upper bound.
This way if a client reserves a static buffer or truncates the download stream after this many bytes the whole file would be received.
Such provision is included to allow the providers to serve on-the-fly compressed files.""",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
)

media_type: Optional[str] = OptimadeField(
...,
description="""Media type identifier (also known as MIME type), for a file as per `RFC 6838 Media Type Specifications and Registration Procedures <https://datatracker.ietf.org/doc/html/rfc6838>`__.
- **Type**: string
- **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.

- **Examples**:

- :val:`"chemical/x-cif"`""",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
)

version: Optional[str] = OptimadeField(
None,
description="""Version information of a file (e.g. commit, revision, timestamp).
- **Type**: string
- **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.
- If provided, it MUST be guaranteed that file contents pertaining to the same combination of :field:`id` and :field:`version` are the same""",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
)

modification_timestamp: Optional[datetime] = OptimadeField(
...,
description="""Timestamp of the last modification of file contents.
A modification is understood as an addition, change or deletion of one or more bytes, resulting in file contents different from the previous.
- **Type**: timestamp
- **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.
- Timestamps of subsequent file modifications SHOULD be increasing (not earlier than previous timestamps).""",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
)

description: Optional[str] = OptimadeField(
...,
description="""Free-form description of a file.
- **Type**: string
- **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.

- **Examples**:

- :val:`"POSCAR format file"`""",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
)

checksums: Optional[dict[str, str]] = OptimadeField(
...,
description="""Dictionary providing checksums of file contents.
* **Type**: dictionary with keys identifying checksum functions and values (strings) giving the actual checksums
* **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.
- Supported dictionary keys: :property:`md5`, :property:`sha1`, :property:`sha224`, :property:`sha256`, :property:`sha384`, :property:`sha512`.
Checksums outside this list MAY be used, but their names MUST be prefixed by database-provider-specific namespace prefix (see appendix `Database-Provider-Specific Namespace Prefixes`_).
""",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
)

atime: Optional[datetime] = OptimadeField(
...,
description="""Time of last access of a file as per POSIX standard.
- **Type**: timestamp
- **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.""",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
)

ctime: Optional[datetime] = OptimadeField(
...,
description="""Time of last status change of a file as per POSIX standard.
- **Type**: timestamp
- **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.""",
unit="Å",
support=SupportLevel.OPTIONAL,
queryable=SupportLevel.OPTIONAL,
)

mtime: Optional[datetime] = OptimadeField(
...,
description=""" Time of last modification of a file as per POSIX standard.
- **Type**: timestamp
- **Requirements/Conventions**:

- **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.
- **Query**: Support for queries on this property is OPTIONAL.
- It should be noted that the values of :field:`last_modified`, :field:`modification_timestamp` and :field:`mtime` do not necessary match.
:field:`last_modified` pertains to the modification of the OPTIMADE metadata, :field:`modification_timestamp` pertains to file contents and :field:`mtime` pertains to the modification of the file (not necessary changing its contents).
For example, appending an empty string to a file would result in the change of :field:`mtime` in some operating systems, but this would not be deemed as a modification of its contents.
""",
queryable=SupportLevel.OPTIONAL,
support=SupportLevel.OPTIONAL,
)


class FileResource(EntryResource):
"""Representing a structure."""

type: str = StrictField(
"files",
description="""The name of the type of an entry.

- **Type**: string.

- **Requirements/Conventions**:
- **Support**: MUST be supported by all implementations, MUST NOT be `null`.
- **Query**: MUST be a queryable property with support for all mandatory filter features.
- **Response**: REQUIRED in the response.
- MUST be an existing entry type.
- The entry of type `<type>` and ID `<id>` MUST be returned in response to a request for `/<type>/<id>` under the versioned base URL.

- **Examples**:
- `"structures"`""",
pattern="^files$",
support=SupportLevel.MUST,
queryable=SupportLevel.MUST,
)

attributes: FileResourceAttributes
17 changes: 17 additions & 0 deletions optimade/models/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from optimade.models.baseinfo import BaseInfoResource
from optimade.models.entries import EntryInfoResource, EntryResource
from optimade.models.files import FileResource
from optimade.models.index_metadb import IndexInfoResource
from optimade.models.jsonapi import Response
from optimade.models.links import LinksResource
Expand All @@ -22,6 +23,8 @@
"EntryResponseMany",
"StructureResponseOne",
"StructureResponseMany",
"FileResponseOne",
"FileResponseMany",
"ReferenceResponseOne",
"ReferenceResponseMany",
)
Expand Down Expand Up @@ -137,6 +140,20 @@ class StructureResponseMany(EntryResponseMany):
]


class FileResponseOne(EntryResponseOne):
data: Union[FileResource, dict[str, Any], None] = StrictField(
..., description="A single files entry resource."
)


class FileResponseMany(EntryResponseMany):
data: Union[list[FileResource], list[dict[str, Any]]] = StrictField(
...,
description="List of unique OPTIMADE files entry resource objects.",
uniqueItems=True,
)


class ReferenceResponseOne(EntryResponseOne):
data: Annotated[
Optional[Union[ReferenceResource, dict[str, Any]]],
Expand Down
10 changes: 8 additions & 2 deletions optimade/server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,12 @@ class ServerConfig(BaseSettings):
mongo_uri: Annotated[str, Field(description="URI for the Mongo server")] = (
"localhost:27017"
)
files_collection: Annotated[
str,
Field(
description="Mongo collection name for /files endpoint resources",
),
] = "files"
links_collection: Annotated[
str, Field(description="Mongo collection name for /links endpoint resources")
] = "links"
Expand Down Expand Up @@ -315,7 +321,7 @@ class ServerConfig(BaseSettings):
),
] = {}
aliases: Annotated[
dict[Literal["links", "references", "structures"], dict[str, str]],
dict[Literal["links", "references", "structures", "files"], dict[str, str]],
Field(
description=(
"A mapping between field names in the database with their corresponding "
Expand All @@ -324,7 +330,7 @@ class ServerConfig(BaseSettings):
),
] = {}
length_aliases: Annotated[
dict[Literal["links", "references", "structures"], dict[str, str]],
dict[Literal["links", "references", "structures", "files"], dict[str, str]],
Field(
description=(
"A mapping between a list property (or otherwise) and an integer property "
Expand Down
1 change: 1 addition & 0 deletions optimade/server/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"structures": "test_structures.json",
"references": "test_references.json",
"links": "test_links.json",
"files": "test_files.json",
"providers": "providers.json",
}

Expand Down
11 changes: 11 additions & 0 deletions optimade/server/data/test_files.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[
{
"url": "www.example.com/file001.pdb",
"name": "file001.pdb",
"size": 12536,
"media_type": "chemical/x-pdb",
"modification_timestamp": {
"$date": "2022-08-26T13:09:37.945Z"
}
}
]
Loading
Loading