diff --git a/Procfile b/Procfile index f891fdb62..6eeb82563 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -web: uvicorn optimade.server.main:app --host 0.0.0.0 --port $PORT +web: --host 0.0.0.0 --port $PORT diff --git a/docs/api_reference/adapters/jsonl.md b/docs/api_reference/adapters/jsonl.md new file mode 100644 index 000000000..6e9eeb43e --- /dev/null +++ b/docs/api_reference/adapters/jsonl.md @@ -0,0 +1,3 @@ +# jsonl + +::: optimade.adapters.jsonl diff --git a/docs/api_reference/models/partial_data.md b/docs/api_reference/models/partial_data.md new file mode 100644 index 000000000..07200e8b8 --- /dev/null +++ b/docs/api_reference/models/partial_data.md @@ -0,0 +1,5 @@ +# partial_data + +::: optimade.models.partial_data + options: + show_if_no_docstring: true diff --git a/docs/api_reference/models/trajectories.md b/docs/api_reference/models/trajectories.md new file mode 100644 index 000000000..dfe8cc693 --- /dev/null +++ b/docs/api_reference/models/trajectories.md @@ -0,0 +1,5 @@ +# trajectories + +::: optimade.models.trajectories + options: + show_if_no_docstring: true diff --git a/docs/api_reference/server/mappers/partial_data.md b/docs/api_reference/server/mappers/partial_data.md new file mode 100644 index 000000000..fee81e882 --- /dev/null +++ b/docs/api_reference/server/mappers/partial_data.md @@ -0,0 +1,3 @@ +# partial_data + +::: optimade.server.mappers.partial_data diff --git a/docs/api_reference/server/mappers/trajectories.md b/docs/api_reference/server/mappers/trajectories.md new file mode 100644 index 000000000..6f291e801 --- /dev/null +++ b/docs/api_reference/server/mappers/trajectories.md @@ -0,0 +1,3 @@ +# trajectories + +::: optimade.server.mappers.trajectories diff --git a/docs/api_reference/server/routers/partial_data.md b/docs/api_reference/server/routers/partial_data.md new file mode 100644 index 000000000..c9e94cfa4 --- /dev/null +++ b/docs/api_reference/server/routers/partial_data.md @@ -0,0 +1,3 @@ +# partial_data + +::: optimade.server.routers.partial_data diff --git a/docs/api_reference/server/routers/trajectories.md b/docs/api_reference/server/routers/trajectories.md new file mode 100644 index 000000000..ec935992f --- /dev/null +++ b/docs/api_reference/server/routers/trajectories.md @@ -0,0 +1,3 @@ +# trajectories + +::: optimade.server.routers.trajectories diff --git a/docs/getting_started/tutorial_trajectories.md b/docs/getting_started/tutorial_trajectories.md new file mode 100644 index 000000000..c4459934d --- /dev/null +++ b/docs/getting_started/tutorial_trajectories.md @@ -0,0 +1,302 @@ +## Setting up an OPTIMADE Trajectory database. + +The tutorial explains how to set up an OPTIMADE server for trajectory data. +The code linked to here matches the trajectory endpoint as described in [v0.2 of the trajectories proposal](https://github.com/JPBergsma/OPTIMADE/tree/JPBergsma_add_Trajectories/optimade.rst) and discussed in [OPTIMADE PR#377](https://github.com/Materials-Consortia/OPTIMADE/pull/377). +This corresponds to [version 1.2 of the OPTIMADE specification](https://github.com/Materials-Consortia/OPTIMADE/tree/develop). +The trajectory endpoint is however still under development and, based on feedback from the community, there may still be significant changes before it is merged with the main OPTIMADE specification. + +The [optimade-python-tools](https://github.com/Materials-Consortia/optimade-python-tools) library currently supports the [MongoDB](https://www.mongodb.com) and [Elasticsearch](https://www.elastic.co/elasticsearch) database backends. +Other backends can be used as well, but will require creating a custom filtertransformer for converting the query into a backend compatible format and a custom [`EntryCollection`](https://www.optimade.org/optimade-python-tools/latest/api_reference/server/entry_collections/entry_collections/) object for interacting with the database backend. +More generic advice on setting up an OPTIMADE API can be found in the [online documentation](https://www.optimade.org/optimade-python-tools/latest/getting_started/setting_up_an_api/). + +In this tutorial, we will use an Ubuntu-based Linux distribution with MongoDB as the backend for our trajectory data. +At the end of this document you can find a troubleshooting section. + +### Acquiring the trajectory version of optimade-python-tools + +The first step is to install optimade-python-tools. +You can find more details in the installation instructions described in [INSTALL.md](https://github.com/JPBergsma/optimade-python-tools/blob/optimade_python_tools_trajectory_0.1/INSTALL.md). +You will however need to use the code found on GitHub at [https://github.com/JPBergsma/optimade-python-tools/tree/JPBergsma/trajectory_endpoint](https://github.com/JPBergsma/optimade-python-tools/tree/JPBergsma/trajectory_endpoint). + +If you only want to use the optimade python tools as a library you could use: + +``` +pip install git+https://github.com/JPBergsma/optimade-python-tools/tree/JPBergsma/trajectory_endpoint +``` + +In this tutorial, we are however describing how to set up your own database for sharing trajectory data. +In that case it is better to clone the repository and create your own branch from the [`optimade_python_tools_trajectory`](https://github.com/JPBergsma/optimade-python-tools/tree/JPBergsma/trajectory_endpoint) branch. +This way you can easily make modifications to the code when you want to change the behaviour of your server later on. + +If you already have a GitHub account setup you can clone the repository with: + +``` +git clone --recursive git@github.com:JPBergsma/optimade-python-tools.git -b optimade_python_tools_trajectory_0.1 +``` + +Without GitHub account you can use: + +``` +git clone --recursive https://github.com/JPBergsma/optimade-python-tools.git -b optimade_python_tools_trajectory_0.1 +``` + + +### Conda + +You may now want to set up a separate Conda environment so there can't be a version conflict between the dependencies of different python programs. +See the instructions on how to install (Mini)Conda on the [conda website](https://conda.io/projects/conda/en/stable/user-guide/install/index.html). + +If you use Conda you can create a separate environment using: + + +``` +conda create -n optimade-traj python=3.10 +``` + +You could also use Python versions 3.9+ if you need to integrate with other libraries that require them. +You can then activate and begin using the Conda environment with: `conda activate optimade-traj` + + +### Install the trajectory version of the optimade python tools + +Next, you can install the local version of this package by going into the optimade-python-tools folder, created during the `git clone`, and installing the package locally with: + +``` +pip install -e .[server] +``` + +### Installing MongoDB + +You can either install MongoDB directly on your system, or use the docker instructions in the Deployment instructions below. +For the former, installation instructions for MongoDB can be found on the [MongoDB website](https://www.mongodb.com/docs/manual/installation/) +The free community edition is good enough for our purposes. +To automatically run the `mongod` daemon when the machine is booted, you can run: `systemctl enable mongod.service`, to run it just once you can use: `systemctl start mongod.service`. + +### Set up the config file + +The next step is setting up the server configuration file. +The default location is in the user's home directory, i.e `"~/.optimade.json"`. +More information and alternative ways for setting up the configuration parameters can be found in the [online configuration instructions](https://github.com/JPBergsma/optimade-python-tools/blob/optimade_python_tools_trajectory_0.1/docs/configuration.md). +An example configuration file [`optimade_config.json`](https://github.com/JPBergsma/optimade-python-tools/blob/JPBergsma/trajectory_endpoint/optimade_config.json) is bundled with the package and can be used as starting point for creating your own configuration file. +If you are setting up a new API, the important parameters to set are: + +* `insert_test_data`: + * description: This value needs to be set to false, otherwise the test data will be inserted in the database you are trying to construct. + * type: boolean + +* `database_backend`: + * description: The type of backend that is used. the options are: "elastic" for the Elasticsearch backend, "mongodb" for the MongoDB backend and "mongomock" for the test backend. + In this tutorial, we use MongoDB, so it should be set to "mongodb". + * type: string + +* `base_url`: + * description: The URL at which you will serve the database. + If you are only testing the optimade python tools locally, you can use: "http://localhost:5000". + * type: string + +* `provider`: + * description: This field contains information about the organization that provides the database. + * type: dictionary + * keys: + * name: + * description: The name of the organization providing the database. + * type: string + * description: + * description: A description of the organization that provides the database. + * type: string + * prefix: + * description: An abbreviation of the name of the database provider with an underscore on each side. e.g. "\_exmpl_". + This is used as a prefix for fields in the database that are not described by the optimade standard, but have instead stead been defined by the database provider. + * type: string + * homepage: + * description: A URL to the website of the provider. + * type: string + +* `provider_fields`: + * description: In this dictionary, fields that are specific to this provider are defined. + * type: dictionary + * keys: Valid keys are the names of the types of endpoints ("links", "references", "structures", "trajectories") that are on this server. + * values: A list with a dictionary for each database specific field/property that has been defined for the endpoint specified by the key. + * keys: The sub-dictionaries describing the database specific properties/fields can contain the following keys: + * name: + * description: The name, without prefix, of the field as it should be presented in the OPTIMADE response. + * type: string + * type: + * description: The JSON type of the field. + Possible values are: "boolean", "object" (for an OPTIMADE dictionary), "array" (for an OPTIMADE list), "number" (for an OPTIMADE float), "string", and "integer". + * type: string + * unit: + * description: The unit belonging to the property. One is encouraged to use the top most notation for the unit as described in [definitions.units](https://github.com/Materials-Consortia/OPTIMADE/blob/develop/units/definitions.units), which was taken from [GNU Units version 2.22](https://www.gnu.org/software/units/), as this will become the unit system for OPTIMADE 1.2 onward. + * type: string + * description: + * description: A description of the property. + * type: string + +* `length_aliases`: + * description: This property maps list properties to integer properties that define the length of those lists. + For example: elements -> nelements. + The standard aliases are applied first, so this dictionary must refer to the API fields, not the database fields. + * type: dictionary of dictionaries. + * keys: The names of the entrypoints available on this server. i.e. `["links", "references", "structures", "trajectories"]` + * values: A dictionary with the name of the list field as the key and the field corresponding to the length of this list as the value. + +* `max_datsa_size`: + * description: Approximately the maximum size of a response for a particular response format. + The optimade python tools will try to estimate the size of each frame that is to be returned and subsequently try to calculate the number of frames that can be returned in a single response. + The final file can be larger if the estimate was poor. + * type: Dictionary + * keys: The names of the different supported return formats. + * values: An integer containing the maximum size of the response in megabytes. + +You can still adjust some of these parameters later if, for example, you want to add more database specific properties later on. +The script in the next section however uses the information in this file to connect to MongoDB, so that information must be present before the next step can be executed. +More parameters can be found by checking the `ServerConfig` class defined in [optimade.server.config.py](https://github.com/JPBergsma/optimade-python-tools/blob/JPBergsma/trajectory_endpoint/optimade/server/config.py), which are useful if you already have a pre-existing database or want to customize the setup of the MongoDB database. + + +### Loading trajectory data into MongoDB + +The next step is to load the data that is needed to create valid OPTIMADE responses into the MongoDB database. +A small example script to generate a MongoDB entry from a trajectory file can be found at [JPBergsma/Export_traj_to_mongo](https://github.com/JPBergsma/Export_traj_to_mongo). +It uses the [MDanalysis](https://docs.mdanalysis.org/stable/index.html) package to read the trajectory files. +It can be downloaded with `git clone https://github.com/JPBergsma/Export_traj_to_mongo.git` +And installed with `pip install -e ` +You can use the same environment as before. + +You can use this script to load the trajectory data into your database. + +Instructions on how to run this script can be found in the accompanying [README.md](https://github.com/JPBergsma/Export_traj_to_mongo/blob/master/README.md) file. +If you have not restarted since installing MongoDB you still need to start MongoDB with: `systemctl start mongod` +To check whether MongoDB is running, you can use: `systemctl status mongod` + +### Validation + +To launch the OPTIMADE API server and test the setup, you can go to optimade-python-tools folder and run: + +``` +uvicorn optimade.server.main:app --reload --port=5000 +``` + +By adding the --reload flag, the server is automatically restarted when code is changed as you develop your server. +Next, you can run `optimade-validator http://localhost:5000` to validate the setup of your database. + +At the moment, the validator may still give a `ConfigError: duplicate validator function` error, which can be safely ignored for now. +Any errors under INTERNAL FAILURES indicate problems with the validator itself and not with the server setup. You can report those [here](https://github.com/JPBergsma/optimade-python-tools/issues). +More details about validating your server can be found in [the online documentation](https://github.com/JPBergsma/optimade-python-tools/blob/optimade_python_tools_trajectory_0.1/docs/concepts/validation.md). + +### Deployment + +Uvicorn runs as a single process and thus uses only a single cpu core. +If you want to run multiple processes, you can use Gunicorn. +Instructions for this on how to set this up can be found on https://fastapi.tiangolo.com/uk/deployment/server-workers/ + +In many organizations there is a firewall between the internet and the internal network. +You may therefore need to contact t ICT department of your organization to make your server reachable from outside the internal network. +This is also a good opportunity to ask them about extra security measures you may need to take, e.g., run the server within a container/virtual machine or using nginx. + +### Using a docker container + +You can run the optimade-python-tools from within a [docker](https://docs.docker.com/) container. +This adds a bit of security and is also a way to run multiple instances of the server. +The first step is building a docker image with your local version of the optimade-python-tools. +Run the following command in you local optimade-python-tools folder to create a docker image. + +``` +docker build -t optimade-traj:0.1 +``` + +If you also want to run mongodb in a docker container the next step is to create a docker network, so the containers can communicate with each other, via: + +``` +docker network create -d bridge optimade +``` + +You can start mongodb in a docker container via: + +``` +docker run \ + --detach \ + --name mongo \ + --volume mongodb-persist:/data/db \ + --network optimade \ + docker.io/library/mongo:latest +``` + +In that case you have to set "mongo_uri" in the config file to "mongodb://mongo:27017". + +Next you can start the container for the optimade python tools with: + +``` +docker run \ + --rm \ + --detach + --publish 8081:5000 \ + --env MAIN=main \ + --name my-optimade \ + --network optimade \ + --volume /folder/containing/config/:/config \ + --env OPTIMADE_CONFIG_FILE=/config/.optimade.json \ + optimade-traj:0.1 +``` + +You still have to replace /folder/containing/config/ with the path where you have put your config file. +The flag `--detach` causes the docker container to run in the background. If you want to see the output you can remove it. +If everything is running properly. you can now visit the landing page at http://0.0.0.0:8081/ +The first value indicted under the flag `--publish` is the port number that is exposed externally. +You may need to adjust the value of "base_url" in the config file to match this port number. + +If you do not run mongodb in a docker container you should set the `--network` flag to `"host"`. +The publish command is also ignored in that case, so by default the uvicorn server should be available at port 5000. + +For more details see [container.md](https://www.optimade.org/optimade-python-tools/latest/deployment/container/) + +### Register your database + +Once you have finished setting up your server, you can register your API with the OPTIMADE consortium. +You can find instructions on how to do this [in the providers repository](https://github.com/Materials-Consortia/providers#requirements-to-be-listed-in-this-providers-list). + +### Troubleshooting + +#### MongoDB + +##### exit code 14 + +This exit code means that the socket MongoDB wants to use is not available. +This may happen when MongoDB was not terminated properly. +It can be solved by: `$ rm /tmp/mongodb-27017.sock` +(27017 is the default port for mongod) + +##### exit code:100 or exit code 34 or permission denied + +IllegalOperation: Attempted to create a lock file on a read-only directory: /data/db or "permission denied. + +Because mongod was run in a docker container the file permissions were changed. +You can reset them with: +``` +sudo chmod -R 777 /data/db +``` + +and + +``` +sudo chown -R `id -u` /data/db +``` + + +#### (Mini)Conda + +If after you have installed Conda you get the error that the command cannot be found, it may be that the location of Conda has not been added to the PATH variable. +This may be because Conda has not been initialized properly. +You can try to use `conda init bash` to initialize Conda properly. (If you use a shell other than bash you should replace it with the name of your shell.) + +If you get the error message: "ERROR: Error [Errno 2] No such file or directory 'git'" git still needs to be installed with: `conda install git` + +#### Docker + +If you get the error "Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock:" +You have to run the docker commands with elevated privileges, for example by prepending them with `sudo `. + +#### Further help + +General questions about OPTIMADE can be asked on the [Matsci forum](https://matsci.org/c/optimade/29). +Bug reports or feature requests about the optimade-python-tools in general can be posted on the [optimade-python-tools github page](https://github.com/Materials-Consortia/optimade-python-tools/issues). +Issues specific to the trajectory branch of the optimade-python-tools can be posted on [https://github.com/JPBergsma/optimade-python-tools/issues](https://github.com/JPBergsma/optimade-python-tools/issues). diff --git a/openapi/index_openapi.json b/openapi/index_openapi.json index 62c29ef4a..073a9e28e 100644 --- a/openapi/index_openapi.json +++ b/openapi/index_openapi.json @@ -467,6 +467,29 @@ "title": "BaseRelationshipResource", "description": "Minimum requirements to represent a relationship resource" }, + "EntryMetadata": { + "properties": { + "property_metadata": { + "type": "object", + "title": "Property Metadata", + "description": "An object containing per-entry and per-property metadata. The keys are the names of the fields in attributes for which metadata is available. The values belonging to these keys are dictionaries containing the relevant metadata fields. See also [Metadata properties](https://github.com/Materials-Consortia/OPTIMADE/blob/develop/optimade.rst#metadata-properties)" + }, + "partial_data_links": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/PartialDataLink" + }, + "type": "array" + }, + "type": "object", + "title": "Partial Data Links", + "description": "A dictionary, where the keys are the names of the properties in the attributes field for which the value is too large to be shared by default.\n For each property one or more links are provided from which the value of the attribute can be retrieved." + } + }, + "type": "object", + "title": "EntryMetadata", + "description": "Contains the metadata for the attributes of an entry" + }, "EntryRelationships": { "properties": { "references": { @@ -486,6 +509,15 @@ ], "title": "Structures", "description": "Object containing links to relationships with entries of the `structures` type." + }, + "trajectories": { + "allOf": [ + { + "$ref": "#/components/schemas/TrajectoryRelationship" + } + ], + "title": "Trajectories", + "description": "Object containing links to relationships with entries of the `trajectories` type." } }, "type": "object", @@ -520,11 +552,11 @@ "meta": { "allOf": [ { - "$ref": "#/components/schemas/Meta" + "$ref": "#/components/schemas/EntryMetadata" } ], "title": "Meta", - "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." + "description": "A [JSON API meta object](https://jsonapi.org/format/1.1/#document-meta) that is used to communicate metadata." }, "attributes": { "allOf": [ @@ -1129,11 +1161,11 @@ "meta": { "allOf": [ { - "$ref": "#/components/schemas/Meta" + "$ref": "#/components/schemas/EntryMetadata" } ], "title": "Meta", - "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." + "description": "A [JSON API meta object](https://jsonapi.org/format/1.1/#document-meta) that is used to communicate metadata." }, "attributes": { "allOf": [ @@ -1393,6 +1425,29 @@ "title": "OptimadeError", "description": "detail MUST be present" }, + "PartialDataLink": { + "properties": { + "link": { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri", + "title": "Link", + "description": "String. A JSON API link that points to a location from which the omitted data can be fetched. There is no requirement on the syntax or format for the link URL." + }, + "format": { + "type": "string", + "title": "Format", + "description": "String. The name of the format provided via this link. For one of the objects this format field SHOULD have the value \"jsonlines\", which refers to the format in OPTIMADE JSON lines partial data format." + } + }, + "type": "object", + "required": [ + "link", + "format" + ], + "title": "PartialDataLink" + }, "Provider": { "properties": { "name": { @@ -1880,6 +1935,47 @@ "title": "ToplevelLinks", "description": "A set of Links objects, possibly including pagination" }, + "TrajectoryRelationship": { + "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/RelationshipLinks" + } + ], + "title": "Links", + "description": "a links object containing at least one of the following: self, related" + }, + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/BaseRelationshipResource" + }, + { + "items": { + "$ref": "#/components/schemas/BaseRelationshipResource" + }, + "type": "array" + } + ], + "uniqueItems": true, + "title": "Data", + "description": "Resource linkage" + }, + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/Meta" + } + ], + "title": "Meta", + "description": "a meta object that contains non-standard meta-information about the relationship." + } + }, + "type": "object", + "title": "TrajectoryRelationship", + "description": "Similar to normal JSON API relationship, but with addition of OPTIONAL meta field for a resource." + }, "Warnings": { "properties": { "id": { diff --git a/openapi/openapi.json b/openapi/openapi.json index d66e0fc52..9405d684e 100644 --- a/openapi/openapi.json +++ b/openapi/openapi.json @@ -2,7 +2,7 @@ "openapi": "3.1.0", "info": { "title": "OPTIMADE API", - "description": "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.\n\nThis specification is generated using [`optimade-python-tools`](https://github.com/Materials-Consortia/optimade-python-tools/tree/v0.25.3) v0.25.3.", + "description": "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.\nThis specification is generated using [`optimade-python-tools`](https://github.com/Materials-Consortia/optimade-python-tools/tree/v0.25.3) v0.25.3.", "version": "1.1.0" }, "paths": { @@ -784,6 +784,17 @@ }, "name": "api_hint", "in": "query" + }, + { + "description": "A list of lists which contains a range for each dimension of the property.", + "required": false, + "schema": { + "type": "string", + "title": "Property Ranges", + "description": "A list of lists which contains a range for each dimension of the property." + }, + "name": "property_ranges", + "in": "query" } ], "responses": { @@ -1202,6 +1213,17 @@ }, "name": "api_hint", "in": "query" + }, + { + "description": "A list of lists which contains a range for each dimension of the property.", + "required": false, + "schema": { + "type": "string", + "title": "Property Ranges", + "description": "A list of lists which contains a range for each dimension of the property." + }, + "name": "property_ranges", + "in": "query" } ], "responses": { @@ -1288,380 +1310,1452 @@ } } }, - "/versions": { + "/trajectories": { "get": { "tags": [ - "Versions" - ], - "summary": "Get Versions", - "description": "Respond with the text/csv representation for the served versions.", - "operationId": "get_versions_versions_get", - "responses": { - "200": { - "description": "Successful Response", - "content": { - "text/csv; header=present": { - "schema": { - "type": "string" - } - } - } - } - } - } - } - }, - "components": { - "schemas": { - "Aggregate": { - "enum": [ - "ok", - "test", - "staging", - "no" + "Trajectories" ], - "title": "Aggregate", - "description": "Enumeration of aggregate values" - }, - "Assembly": { - "properties": { - "sites_in_groups": { - "items": { - "items": { - "type": "integer" - }, - "type": "array" + "summary": "Get Trajectories", + "operationId": "get_trajectories_trajectories_get", + "parameters": [ + { + "description": "A filter string, in the format described in section API Filtering Format Specification of the specification.", + "required": false, + "schema": { + "type": "string", + "title": "Filter", + "description": "A filter string, in the format described in section API Filtering Format Specification of the specification.", + "default": "" }, - "type": "array", - "title": "Sites In Groups", - "description": "Index of the sites (0-based) that belong to each group for each assembly.\n\n- **Examples**:\n - `[[1], [2]]`: two groups, one with the second site, one with the third.\n - `[[1,2], [3]]`: one group with the second and third site, one with the fourth.", - "x-optimade-support": "must", - "x-optimade-queryable": "optional" + "name": "filter", + "in": "query" }, - "group_probabilities": { - "items": { - "type": "number" + { + "description": "The output format requested (see section Response Format).\nDefaults to the format string 'json', which specifies the standard output format described in this specification.\nExample: `http://example.com/v1/structures?response_format=xml`", + "required": false, + "schema": { + "type": "string", + "title": "Response Format", + "description": "The output format requested (see section Response Format).\nDefaults to the format string 'json', which specifies the standard output format described in this specification.\nExample: `http://example.com/v1/structures?response_format=xml`", + "default": "json" }, - "type": "array", - "title": "Group Probabilities", - "description": "Statistical probability of each group. It MUST have the same length as `sites_in_groups`.\nIt SHOULD sum to one.\nSee below for examples of how to specify the probability of the occurrence of a vacancy.\nThe possible reasons for the values not to sum to one are the same as already specified above for the `concentration` of each `species`.", - "x-optimade-support": "must", - "x-optimade-queryable": "optional" - } - }, - "type": "object", - "required": [ - "sites_in_groups", - "group_probabilities" - ], - "title": "Assembly", - "description": "A description of groups of sites that are statistically correlated.\n\n- **Examples** (for each entry of the assemblies list):\n - `{\"sites_in_groups\": [[0], [1]], \"group_probabilities: [0.3, 0.7]}`: the first site and the second site never occur at the same time in the unit cell.\n Statistically, 30 % of the times the first site is present, while 70 % of the times the second site is present.\n - `{\"sites_in_groups\": [[1,2], [3]], \"group_probabilities: [0.3, 0.7]}`: the second and third site are either present together or not present; they form the first group of atoms for this assembly.\n The second group is formed by the fourth site. Sites of the first group (the second and the third) are never present at the same time as the fourth site.\n 30 % of times sites 1 and 2 are present (and site 3 is absent); 70 % of times site 3 is present (and sites 1 and 2 are absent)." - }, - "Attributes": { - "properties": {}, - "type": "object", - "title": "Attributes", - "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.\nThe keys for Attributes MUST NOT be:\n relationships\n links\n id\n type" - }, - "AvailableApiVersion": { - "properties": { - "url": { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "pattern": ".+/v[0-1](\\.[0-9]+)*/?$", - "format": "uri", - "title": "Url", - "description": "A string specifying a versioned base URL that MUST adhere to the rules in section Base URL" + "name": "response_format", + "in": "query" }, - "version": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", - "title": "Version", - "description": "A string containing the full version number of the API served at that versioned base URL.\nThe version number string MUST NOT be prefixed by, e.g., 'v'.\nExamples: `1.0.0`, `1.0.0-rc.2`.", - "example": [ - "0.10.1", - "1.0.0-rc.2", - "1.2.3-rc.5+develop" - ] - } - }, - "type": "object", - "required": [ - "url", - "version" - ], - "title": "AvailableApiVersion", - "description": "A JSON object containing information about an available API version" - }, - "BaseInfoAttributes": { - "properties": { - "api_version": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", - "title": "Api Version", - "description": "Presently used full version of the OPTIMADE API.\nThe version number string MUST NOT be prefixed by, e.g., \"v\".\nExamples: `1.0.0`, `1.0.0-rc.2`.", - "example": [ - "0.10.1", - "1.0.0-rc.2", - "1.2.3-rc.5+develop" - ] + { + "description": "An email address of the user making the request.\nThe email SHOULD be that of a person and not an automatic system.\nExample: `http://example.com/v1/structures?email_address=user@example.com`", + "required": false, + "schema": { + "type": "string", + "format": "email", + "title": "Email Address", + "description": "An email address of the user making the request.\nThe email SHOULD be that of a person and not an automatic system.\nExample: `http://example.com/v1/structures?email_address=user@example.com`", + "default": "" + }, + "name": "email_address", + "in": "query" }, - "available_api_versions": { - "items": { - "$ref": "#/components/schemas/AvailableApiVersion" + { + "description": "A comma-delimited set of fields to be provided in the output.\nIf provided, these fields MUST be returned along with the REQUIRED fields.\nOther OPTIONAL fields MUST NOT be returned when this parameter is present.\nExample: `http://example.com/v1/structures?response_fields=last_modified,nsites`", + "required": false, + "schema": { + "type": "string", + "pattern": "([a-z_][a-z_0-9]*(,[a-z_][a-z_0-9]*)*)?", + "title": "Response Fields", + "description": "A comma-delimited set of fields to be provided in the output.\nIf provided, these fields MUST be returned along with the REQUIRED fields.\nOther OPTIONAL fields MUST NOT be returned when this parameter is present.\nExample: `http://example.com/v1/structures?response_fields=last_modified,nsites`", + "default": "" }, - "type": "array", - "title": "Available Api Versions", - "description": "A list of dictionaries of available API versions at other base URLs" + "name": "response_fields", + "in": "query" }, - "formats": { - "items": { - "type": "string" + { + "description": "If supporting sortable queries, an implementation MUST use the `sort` query parameter with format as specified by [JSON API 1.0](https://jsonapi.org/format/1.0/#fetching-sorting).\n\nAn implementation MAY support multiple sort fields for a single query.\nIf it does, it again MUST conform to the JSON API 1.0 specification.\n\nIf an implementation supports sorting for an entry listing endpoint, then the `/info/` endpoint MUST include, for each field name `` in its `data.properties.` response value that can be used for sorting, the key `sortable` with value `true`.\nIf a field name under an entry listing endpoint supporting sorting cannot be used for sorting, the server MUST either leave out the `sortable` key or set it equal to `false` for the specific field name.\nThe set of field names, with `sortable` equal to `true` are allowed to be used in the \"sort fields\" list according to its definition in the JSON API 1.0 specification.\nThe field `sortable` is in addition to each property description and other OPTIONAL fields.\nAn example is shown in the section Entry Listing Info Endpoints.", + "required": false, + "schema": { + "type": "string", + "pattern": "([a-z_][a-z_0-9]*(,[a-z_][a-z_0-9]*)*)?", + "title": "Sort", + "description": "If supporting sortable queries, an implementation MUST use the `sort` query parameter with format as specified by [JSON API 1.0](https://jsonapi.org/format/1.0/#fetching-sorting).\n\nAn implementation MAY support multiple sort fields for a single query.\nIf it does, it again MUST conform to the JSON API 1.0 specification.\n\nIf an implementation supports sorting for an entry listing endpoint, then the `/info/` endpoint MUST include, for each field name `` in its `data.properties.` response value that can be used for sorting, the key `sortable` with value `true`.\nIf a field name under an entry listing endpoint supporting sorting cannot be used for sorting, the server MUST either leave out the `sortable` key or set it equal to `false` for the specific field name.\nThe set of field names, with `sortable` equal to `true` are allowed to be used in the \"sort fields\" list according to its definition in the JSON API 1.0 specification.\nThe field `sortable` is in addition to each property description and other OPTIONAL fields.\nAn example is shown in the section Entry Listing Info Endpoints.", + "default": "" }, - "type": "array", - "title": "Formats", - "description": "List of available output formats.", - "default": [ - "json" - ] + "name": "sort", + "in": "query" }, - "available_endpoints": { - "items": { - "type": "string" + { + "description": "Sets a numerical limit on the number of entries returned.\nSee [JSON API 1.0](https://jsonapi.org/format/1.0/#fetching-pagination).\nThe API implementation MUST return no more than the number specified.\nIt MAY return fewer.\nThe database MAY have a maximum limit and not accept larger numbers (in which case an error code -- 403 Forbidden -- MUST be returned).\nThe default limit value is up to the API implementation to decide.\nExample: `http://example.com/optimade/v1/structures?page_limit=100`", + "required": false, + "schema": { + "type": "integer", + "minimum": 0.0, + "title": "Page Limit", + "description": "Sets a numerical limit on the number of entries returned.\nSee [JSON API 1.0](https://jsonapi.org/format/1.0/#fetching-pagination).\nThe API implementation MUST return no more than the number specified.\nIt MAY return fewer.\nThe database MAY have a maximum limit and not accept larger numbers (in which case an error code -- 403 Forbidden -- MUST be returned).\nThe default limit value is up to the API implementation to decide.\nExample: `http://example.com/optimade/v1/structures?page_limit=100`", + "default": 20 }, - "type": "array", - "title": "Available Endpoints", - "description": "List of available endpoints (i.e., the string to be appended to the versioned base URL)." + "name": "page_limit", + "in": "query" }, - "entry_types_by_format": { - "additionalProperties": { - "items": { - "type": "string" - }, - "type": "array" + { + "description": "RECOMMENDED for use with _offset-based_ pagination: using `page_offset` and `page_limit` is RECOMMENDED.\nExample: Skip 50 structures and fetch up to 100: `/structures?page_offset=50&page_limit=100`.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0.0, + "title": "Page Offset", + "description": "RECOMMENDED for use with _offset-based_ pagination: using `page_offset` and `page_limit` is RECOMMENDED.\nExample: Skip 50 structures and fetch up to 100: `/structures?page_offset=50&page_limit=100`.", + "default": 0 }, - "type": "object", - "title": "Entry Types By Format", - "description": "Available entry endpoints as a function of output formats." + "name": "page_offset", + "in": "query" }, - "is_index": { - "type": "boolean", - "title": "Is Index", - "description": "If true, this is an index meta-database base URL (see section Index Meta-Database). If this member is not provided, the client MUST assume this is not an index meta-database base URL (i.e., the default is for `is_index` to be `false`).", - "default": false - } - }, - "type": "object", - "required": [ - "api_version", - "available_api_versions", - "available_endpoints", - "entry_types_by_format" - ], - "title": "BaseInfoAttributes", - "description": "Attributes for Base URL Info endpoint" - }, - "BaseInfoResource": { - "properties": { - "id": { - "type": "string", - "pattern": "^/$", - "title": "Id", - "default": "/" + { + "description": "RECOMMENDED for use with _page-based_ pagination: using `page_number` and `page_limit` is RECOMMENDED.\nIt is RECOMMENDED that the first page has number 1, i.e., that `page_number` is 1-based.\nExample: Fetch page 2 of up to 50 structures per page: `/structures?page_number=2&page_limit=50`.", + "required": false, + "schema": { + "type": "integer", + "title": "Page Number", + "description": "RECOMMENDED for use with _page-based_ pagination: using `page_number` and `page_limit` is RECOMMENDED.\nIt is RECOMMENDED that the first page has number 1, i.e., that `page_number` is 1-based.\nExample: Fetch page 2 of up to 50 structures per page: `/structures?page_number=2&page_limit=50`." + }, + "name": "page_number", + "in": "query" + }, + { + "description": "RECOMMENDED for use with _cursor-based_ pagination: using `page_cursor` and `page_limit` is RECOMMENDED.", + "required": false, + "schema": { + "type": "integer", + "minimum": 0.0, + "title": "Page Cursor", + "description": "RECOMMENDED for use with _cursor-based_ pagination: using `page_cursor` and `page_limit` is RECOMMENDED.", + "default": 0 + }, + "name": "page_cursor", + "in": "query" + }, + { + "description": "RECOMMENDED for use with _value-based_ pagination: using `page_above`/`page_below` and `page_limit` is RECOMMENDED.\nExample: Fetch up to 100 structures above sort-field value 4000 (in this example, server chooses to fetch results sorted by increasing `id`, so `page_above` value refers to an `id` value): `/structures?page_above=4000&page_limit=100`.", + "required": false, + "schema": { + "type": "string", + "title": "Page Above", + "description": "RECOMMENDED for use with _value-based_ pagination: using `page_above`/`page_below` and `page_limit` is RECOMMENDED.\nExample: Fetch up to 100 structures above sort-field value 4000 (in this example, server chooses to fetch results sorted by increasing `id`, so `page_above` value refers to an `id` value): `/structures?page_above=4000&page_limit=100`." + }, + "name": "page_above", + "in": "query" + }, + { + "description": "RECOMMENDED for use with _value-based_ pagination: using `page_above`/`page_below` and `page_limit` is RECOMMENDED.", + "required": false, + "schema": { + "type": "string", + "title": "Page Below", + "description": "RECOMMENDED for use with _value-based_ pagination: using `page_above`/`page_below` and `page_limit` is RECOMMENDED." + }, + "name": "page_below", + "in": "query" + }, + { + "description": "A server MAY implement the JSON API concept of returning [compound documents](https://jsonapi.org/format/1.0/#document-compound-documents) by utilizing the `include` query parameter as specified by [JSON API 1.0](https://jsonapi.org/format/1.0/#fetching-includes).\n\nAll related resource objects MUST be returned as part of an array value for the top-level `included` field, see the section JSON Response Schema: Common Fields.\n\nThe value of `include` MUST be a comma-separated list of \"relationship paths\", as defined in the [JSON API](https://jsonapi.org/format/1.0/#fetching-includes).\nIf relationship paths are not supported, or a server is unable to identify a relationship path a `400 Bad Request` response MUST be made.\n\nThe **default value** for `include` is `references`.\nThis means `references` entries MUST always be included under the top-level field `included` as default, since a server assumes if `include` is not specified by a client in the request, it is still specified as `include=references`.\nNote, if a client explicitly specifies `include` and leaves out `references`, `references` resource objects MUST NOT be included under the top-level field `included`, as per the definition of `included`, see section JSON Response Schema: Common Fields.\n\n> **Note**: A query with the parameter `include` set to the empty string means no related resource objects are to be returned under the top-level field `included`.", + "required": false, + "schema": { + "type": "string", + "title": "Include", + "description": "A server MAY implement the JSON API concept of returning [compound documents](https://jsonapi.org/format/1.0/#document-compound-documents) by utilizing the `include` query parameter as specified by [JSON API 1.0](https://jsonapi.org/format/1.0/#fetching-includes).\n\nAll related resource objects MUST be returned as part of an array value for the top-level `included` field, see the section JSON Response Schema: Common Fields.\n\nThe value of `include` MUST be a comma-separated list of \"relationship paths\", as defined in the [JSON API](https://jsonapi.org/format/1.0/#fetching-includes).\nIf relationship paths are not supported, or a server is unable to identify a relationship path a `400 Bad Request` response MUST be made.\n\nThe **default value** for `include` is `references`.\nThis means `references` entries MUST always be included under the top-level field `included` as default, since a server assumes if `include` is not specified by a client in the request, it is still specified as `include=references`.\nNote, if a client explicitly specifies `include` and leaves out `references`, `references` resource objects MUST NOT be included under the top-level field `included`, as per the definition of `included`, see section JSON Response Schema: Common Fields.\n\n> **Note**: A query with the parameter `include` set to the empty string means no related resource objects are to be returned under the top-level field `included`.", + "default": "references" + }, + "name": "include", + "in": "query" + }, + { + "description": "If the client provides the parameter, the value SHOULD have the format `vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a minor version of the API. For example, if a client appends `api_hint=v1.0` to the query string, the hint provided is for major version 1 and minor version 0.", + "required": false, + "schema": { + "type": "string", + "pattern": "(v[0-9]+(\\.[0-9]+)?)?", + "title": "Api Hint", + "description": "If the client provides the parameter, the value SHOULD have the format `vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a minor version of the API. For example, if a client appends `api_hint=v1.0` to the query string, the hint provided is for major version 1 and minor version 0.", + "default": "" + }, + "name": "api_hint", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/TrajectoryResponseMany" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "553": { + "description": "Version Not Supported", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/trajectories/{entry_id}": { + "get": { + "tags": [ + "Trajectories" + ], + "summary": "Get Single Trajectory", + "operationId": "get_single_trajectory_trajectories__entry_id__get", + "parameters": [ + { + "required": true, + "schema": { + "type": "string", + "title": "Entry Id" + }, + "name": "entry_id", + "in": "path" + }, + { + "description": "The output format requested (see section Response Format).\nDefaults to the format string 'json', which specifies the standard output format described in this specification.\nExample: `http://example.com/v1/structures?response_format=xml`", + "required": false, + "schema": { + "type": "string", + "title": "Response Format", + "description": "The output format requested (see section Response Format).\nDefaults to the format string 'json', which specifies the standard output format described in this specification.\nExample: `http://example.com/v1/structures?response_format=xml`", + "default": "json" + }, + "name": "response_format", + "in": "query" + }, + { + "description": "An email address of the user making the request.\nThe email SHOULD be that of a person and not an automatic system.\nExample: `http://example.com/v1/structures?email_address=user@example.com`", + "required": false, + "schema": { + "type": "string", + "format": "email", + "title": "Email Address", + "description": "An email address of the user making the request.\nThe email SHOULD be that of a person and not an automatic system.\nExample: `http://example.com/v1/structures?email_address=user@example.com`", + "default": "" + }, + "name": "email_address", + "in": "query" + }, + { + "description": "A comma-delimited set of fields to be provided in the output.\nIf provided, these fields MUST be returned along with the REQUIRED fields.\nOther OPTIONAL fields MUST NOT be returned when this parameter is present.\nExample: `http://example.com/v1/structures?response_fields=last_modified,nsites`", + "required": false, + "schema": { + "type": "string", + "pattern": "([a-z_][a-z_0-9]*(,[a-z_][a-z_0-9]*)*)?", + "title": "Response Fields", + "description": "A comma-delimited set of fields to be provided in the output.\nIf provided, these fields MUST be returned along with the REQUIRED fields.\nOther OPTIONAL fields MUST NOT be returned when this parameter is present.\nExample: `http://example.com/v1/structures?response_fields=last_modified,nsites`", + "default": "" + }, + "name": "response_fields", + "in": "query" + }, + { + "description": "A server MAY implement the JSON API concept of returning [compound documents](https://jsonapi.org/format/1.0/#document-compound-documents) by utilizing the `include` query parameter as specified by [JSON API 1.0](https://jsonapi.org/format/1.0/#fetching-includes).\n\nAll related resource objects MUST be returned as part of an array value for the top-level `included` field, see the section JSON Response Schema: Common Fields.\n\nThe value of `include` MUST be a comma-separated list of \"relationship paths\", as defined in the [JSON API](https://jsonapi.org/format/1.0/#fetching-includes).\nIf relationship paths are not supported, or a server is unable to identify a relationship path a `400 Bad Request` response MUST be made.\n\nThe **default value** for `include` is `references`.\nThis means `references` entries MUST always be included under the top-level field `included` as default, since a server assumes if `include` is not specified by a client in the request, it is still specified as `include=references`.\nNote, if a client explicitly specifies `include` and leaves out `references`, `references` resource objects MUST NOT be included under the top-level field `included`, as per the definition of `included`, see section JSON Response Schema: Common Fields.\n\n> **Note**: A query with the parameter `include` set to the empty string means no related resource objects are to be returned under the top-level field `included`.", + "required": false, + "schema": { + "type": "string", + "title": "Include", + "description": "A server MAY implement the JSON API concept of returning [compound documents](https://jsonapi.org/format/1.0/#document-compound-documents) by utilizing the `include` query parameter as specified by [JSON API 1.0](https://jsonapi.org/format/1.0/#fetching-includes).\n\nAll related resource objects MUST be returned as part of an array value for the top-level `included` field, see the section JSON Response Schema: Common Fields.\n\nThe value of `include` MUST be a comma-separated list of \"relationship paths\", as defined in the [JSON API](https://jsonapi.org/format/1.0/#fetching-includes).\nIf relationship paths are not supported, or a server is unable to identify a relationship path a `400 Bad Request` response MUST be made.\n\nThe **default value** for `include` is `references`.\nThis means `references` entries MUST always be included under the top-level field `included` as default, since a server assumes if `include` is not specified by a client in the request, it is still specified as `include=references`.\nNote, if a client explicitly specifies `include` and leaves out `references`, `references` resource objects MUST NOT be included under the top-level field `included`, as per the definition of `included`, see section JSON Response Schema: Common Fields.\n\n> **Note**: A query with the parameter `include` set to the empty string means no related resource objects are to be returned under the top-level field `included`.", + "default": "references" + }, + "name": "include", + "in": "query" + }, + { + "description": "If the client provides the parameter, the value SHOULD have the format `vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a minor version of the API. For example, if a client appends `api_hint=v1.0` to the query string, the hint provided is for major version 1 and minor version 0.", + "required": false, + "schema": { + "type": "string", + "pattern": "(v[0-9]+(\\.[0-9]+)?)?", + "title": "Api Hint", + "description": "If the client provides the parameter, the value SHOULD have the format `vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a minor version of the API. For example, if a client appends `api_hint=v1.0` to the query string, the hint provided is for major version 1 and minor version 0.", + "default": "" + }, + "name": "api_hint", + "in": "query" + }, + { + "description": "A list of lists which contains a range for each dimension of the property.", + "required": false, + "schema": { + "type": "string", + "title": "Property Ranges", + "description": "A list of lists which contains a range for each dimension of the property." + }, + "name": "property_ranges", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/TrajectoryResponseOne" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "553": { + "description": "Version Not Supported", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + }, + "/versions": { + "get": { + "tags": [ + "Versions" + ], + "summary": "Get Versions", + "description": "Respond with the text/csv representation for the served versions.", + "operationId": "get_versions_versions_get", + "responses": { + "200": { + "description": "Successful Response", + "content": { + "text/csv; header=present": { + "schema": { + "type": "string" + } + } + } + } + } + } + }, + "/partial_data/{entry_id}": { + "get": { + "tags": [ + "partial_data" + ], + "summary": "Get Partial Data", + "operationId": "get_partial_data_partial_data__entry_id__get", + "parameters": [ + { + "required": true, + "schema": { + "type": "string", + "title": "Entry Id" + }, + "name": "entry_id", + "in": "path" + }, + { + "description": "The output format requested (see section Response Format).\nDefaults to the format string 'json', which specifies the standard output format described in this specification.\nExample: `http://example.com/v1/structures?response_format=xml`", + "required": false, + "schema": { + "type": "string", + "title": "Response Format", + "description": "The output format requested (see section Response Format).\nDefaults to the format string 'json', which specifies the standard output format described in this specification.\nExample: `http://example.com/v1/structures?response_format=xml`", + "default": "jsonlines" + }, + "name": "response_format", + "in": "query" + }, + { + "description": "An email address of the user making the request.\nThe email SHOULD be that of a person and not an automatic system.\nExample: `http://example.com/v1/structures?email_address=user@example.com`", + "required": false, + "schema": { + "type": "string", + "format": "email", + "title": "Email Address", + "description": "An email address of the user making the request.\nThe email SHOULD be that of a person and not an automatic system.\nExample: `http://example.com/v1/structures?email_address=user@example.com`", + "default": "" + }, + "name": "email_address", + "in": "query" + }, + { + "description": "If the client provides the parameter, the value SHOULD have the format `vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a minor version of the API. For example, if a client appends `api_hint=v1.0` to the query string, the hint provided is for major version 1 and minor version 0.", + "required": false, + "schema": { + "type": "string", + "pattern": "(v[0-9]+(\\.[0-9]+)?)?", + "title": "Api Hint", + "description": "If the client provides the parameter, the value SHOULD have the format `vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a minor version of the API. For example, if a client appends `api_hint=v1.0` to the query string, the hint provided is for major version 1 and minor version 0.", + "default": "" + }, + "name": "api_hint", + "in": "query" + }, + { + "description": "A comma-delimited set of fields to be provided in the output.\nIf provided, these fields MUST be returned along with the REQUIRED fields.\nOther OPTIONAL fields MUST NOT be returned when this parameter is present.\nExample: `http://example.com/v1/structures?response_fields=last_modified,nsites`", + "required": false, + "schema": { + "type": "string", + "pattern": "([a-z_][a-z_0-9]*(,[a-z_][a-z_0-9]*)*)?", + "title": "Response Fields", + "description": "A comma-delimited set of fields to be provided in the output.\nIf provided, these fields MUST be returned along with the REQUIRED fields.\nOther OPTIONAL fields MUST NOT be returned when this parameter is present.\nExample: `http://example.com/v1/structures?response_fields=last_modified,nsites`", + "default": "" + }, + "name": "response_fields", + "in": "query" + }, + { + "description": "A filter string, in the format described in section API Filtering Format Specification of the specification.", + "required": false, + "schema": { + "type": "string", + "title": "Filter", + "description": "A filter string, in the format described in section API Filtering Format Specification of the specification.", + "default": "" + }, + "name": "filter", + "in": "query" + }, + { + "description": "A list of lists which contains a range for each dimension of the property.", + "required": false, + "schema": { + "type": "string", + "title": "Property Ranges", + "description": "A list of lists which contains a range for each dimension of the property.", + "default": "" + }, + "name": "property_ranges", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successful Response", + "content": { + "application/vnd.api+json": { + "schema": { + "title": "Response Get Partial Data Partial Data Entry Id Get" + } + } + } + }, + "400": { + "description": "Bad Request", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "422": { + "description": "Unprocessable Entity", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "500": { + "description": "Internal Server Error", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "501": { + "description": "Not Implemented", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + }, + "553": { + "description": "Version Not Supported", + "content": { + "application/vnd.api+json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponse" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Aggregate": { + "enum": [ + "ok", + "test", + "staging", + "no" + ], + "title": "Aggregate", + "description": "Enumeration of aggregate values" + }, + "Assembly": { + "properties": { + "sites_in_groups": { + "items": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "type": "array", + "title": "Sites In Groups", + "description": "Index of the sites (0-based) that belong to each group for each assembly.\n\n- **Examples**:\n - `[[1], [2]]`: two groups, one with the second site, one with the third.\n - `[[1,2], [3]]`: one group with the second and third site, one with the fourth.", + "x-optimade-support": "must", + "x-optimade-queryable": "optional" + }, + "group_probabilities": { + "items": { + "type": "number" + }, + "type": "array", + "title": "Group Probabilities", + "description": "Statistical probability of each group. It MUST have the same length as `sites_in_groups`.\nIt SHOULD sum to one.\nSee below for examples of how to specify the probability of the occurrence of a vacancy.\nThe possible reasons for the values not to sum to one are the same as already specified above for the `concentration` of each `species`.", + "x-optimade-support": "must", + "x-optimade-queryable": "optional" + } + }, + "type": "object", + "required": [ + "sites_in_groups", + "group_probabilities" + ], + "title": "Assembly", + "description": "A description of groups of sites that are statistically correlated.\n\n- **Examples** (for each entry of the assemblies list):\n - `{\"sites_in_groups\": [[0], [1]], \"group_probabilities: [0.3, 0.7]}`: the first site and the second site never occur at the same time in the unit cell.\n Statistically, 30 % of the times the first site is present, while 70 % of the times the second site is present.\n - `{\"sites_in_groups\": [[1,2], [3]], \"group_probabilities: [0.3, 0.7]}`: the second and third site are either present together or not present; they form the first group of atoms for this assembly.\n The second group is formed by the fourth site. Sites of the first group (the second and the third) are never present at the same time as the fourth site.\n 30 % of times sites 1 and 2 are present (and site 3 is absent); 70 % of times site 3 is present (and sites 1 and 2 are absent)." + }, + "Attributes": { + "properties": {}, + "type": "object", + "title": "Attributes", + "description": "Members of the attributes object (\"attributes\") represent information about the resource object in which it's defined.\nThe keys for Attributes MUST NOT be:\n relationships\n links\n id\n type" + }, + "AvailableApiVersion": { + "properties": { + "url": { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "pattern": ".+/v[0-1](\\.[0-9]+)*/?$", + "format": "uri", + "title": "Url", + "description": "A string specifying a versioned base URL that MUST adhere to the rules in section Base URL" + }, + "version": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "title": "Version", + "description": "A string containing the full version number of the API served at that versioned base URL.\nThe version number string MUST NOT be prefixed by, e.g., 'v'.\nExamples: `1.0.0`, `1.0.0-rc.2`.", + "example": [ + "0.10.1", + "1.0.0-rc.2", + "1.2.3-rc.5+develop" + ] + } + }, + "type": "object", + "required": [ + "url", + "version" + ], + "title": "AvailableApiVersion", + "description": "A JSON object containing information about an available API version" + }, + "BaseInfoAttributes": { + "properties": { + "api_version": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "title": "Api Version", + "description": "Presently used full version of the OPTIMADE API.\nThe version number string MUST NOT be prefixed by, e.g., \"v\".\nExamples: `1.0.0`, `1.0.0-rc.2`.", + "example": [ + "0.10.1", + "1.0.0-rc.2", + "1.2.3-rc.5+develop" + ] + }, + "available_api_versions": { + "items": { + "$ref": "#/components/schemas/AvailableApiVersion" + }, + "type": "array", + "title": "Available Api Versions", + "description": "A list of dictionaries of available API versions at other base URLs" + }, + "formats": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Formats", + "description": "List of available output formats.", + "default": [ + "json" + ] + }, + "available_endpoints": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Available Endpoints", + "description": "List of available endpoints (i.e., the string to be appended to the versioned base URL)." + }, + "entry_types_by_format": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object", + "title": "Entry Types By Format", + "description": "Available entry endpoints as a function of output formats." + }, + "is_index": { + "type": "boolean", + "title": "Is Index", + "description": "If true, this is an index meta-database base URL (see section Index Meta-Database). If this member is not provided, the client MUST assume this is not an index meta-database base URL (i.e., the default is for `is_index` to be `false`).", + "default": false + } + }, + "type": "object", + "required": [ + "api_version", + "available_api_versions", + "available_endpoints", + "entry_types_by_format" + ], + "title": "BaseInfoAttributes", + "description": "Attributes for Base URL Info endpoint" + }, + "BaseInfoResource": { + "properties": { + "id": { + "type": "string", + "pattern": "^/$", + "title": "Id", + "default": "/" + }, + "type": { + "type": "string", + "pattern": "^info$", + "title": "Type", + "default": "info" + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceLinks" + } + ], + "title": "Links", + "description": "a links object containing links related to the resource." + }, + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/Meta" + } + ], + "title": "Meta", + "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." + }, + "attributes": { + "$ref": "#/components/schemas/BaseInfoAttributes" + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/Relationships" + } + ], + "title": "Relationships", + "description": "[Relationships object](https://jsonapi.org/format/1.0/#document-resource-object-relationships)\ndescribing relationships between the resource and other JSON API resources." + } + }, + "type": "object", + "required": [ + "id", + "type", + "attributes" + ], + "title": "BaseInfoResource", + "description": "Resource objects appear in a JSON API document to represent resources." + }, + "BaseRelationshipMeta": { + "properties": { + "description": { + "type": "string", + "title": "Description", + "description": "OPTIONAL human-readable description of the relationship." + } + }, + "type": "object", + "required": [ + "description" + ], + "title": "BaseRelationshipMeta", + "description": "Specific meta field for base relationship resource" + }, + "BaseRelationshipResource": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "Resource ID" + }, + "type": { + "type": "string", + "title": "Type", + "description": "Resource type" + }, + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseRelationshipMeta" + } + ], + "title": "Meta", + "description": "Relationship meta field. MUST contain 'description' if supplied." + } + }, + "type": "object", + "required": [ + "id", + "type" + ], + "title": "BaseRelationshipResource", + "description": "Minimum requirements to represent a relationship resource" + }, + "DataType": { + "enum": [ + "string", + "integer", + "float", + "boolean", + "timestamp", + "list", + "dictionary", + "unknown" + ], + "title": "DataType", + "description": "Optimade Data Types\n\nSee the section \"Data types\" in the OPTIMADE API specification for more information." + }, + "EntryInfoProperty": { + "properties": { + "description": { + "type": "string", + "title": "Description", + "description": "A human-readable description of the entry property" + }, + "unit": { + "type": "string", + "title": "Unit", + "description": "The physical unit of the entry property.\nThis MUST be a valid representation of units according to version 2.1 of [The Unified Code for Units of Measure](https://unitsofmeasure.org/ucum.html).\nIt is RECOMMENDED that non-standard (non-SI) units are described in the description for the property." + }, + "sortable": { + "type": "boolean", + "title": "Sortable", + "description": "Defines whether the entry property can be used for sorting with the \"sort\" parameter.\nIf the entry listing endpoint supports sorting, this key MUST be present for sortable properties with value `true`." + }, + "type": { + "allOf": [ + { + "$ref": "#/components/schemas/DataType" + } + ], + "title": "Type", + "description": "The type of the property's value.\nThis MUST be any of the types defined in the Data types section.\nFor the purpose of compatibility with future versions of this specification, a client MUST accept values that are not `string` values specifying any of the OPTIMADE Data types, but MUST then also disregard the `type` field.\nNote, if the value is a nested type, only the outermost type should be reported.\nE.g., for the entry resource `structures`, the `species` property is defined as a list of dictionaries, hence its `type` value would be `list`." + } + }, + "type": "object", + "required": [ + "description" + ], + "title": "EntryInfoProperty" + }, + "EntryInfoResource": { + "properties": { + "formats": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Formats", + "description": "List of output formats available for this type of entry." + }, + "description": { + "type": "string", + "title": "Description", + "description": "Description of the entry." + }, + "properties": { + "additionalProperties": { + "$ref": "#/components/schemas/EntryInfoProperty" + }, + "type": "object", + "title": "Properties", + "description": "A dictionary describing queryable properties for this entry type, where each key is a property name." + }, + "output_fields_by_format": { + "additionalProperties": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": "object", + "title": "Output Fields By Format", + "description": "Dictionary of available output fields for this entry type, where the keys are the values of the `formats` list and the values are the keys of the `properties` dictionary." + } + }, + "type": "object", + "required": [ + "formats", + "description", + "properties", + "output_fields_by_format" + ], + "title": "EntryInfoResource" + }, + "EntryInfoResponse": { + "properties": { + "data": { + "allOf": [ + { + "$ref": "#/components/schemas/EntryInfoResource" + } + ], + "title": "Data", + "description": "OPTIMADE information for an entry endpoint." + }, + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseMeta" + } + ], + "title": "Meta", + "description": "A meta object containing non-standard information" + }, + "errors": { + "items": { + "$ref": "#/components/schemas/Error" + }, + "type": "array", + "uniqueItems": true, + "title": "Errors", + "description": "A list of unique errors" + }, + "included": { + "items": { + "$ref": "#/components/schemas/Resource" + }, + "type": "array", + "uniqueItems": true, + "title": "Included", + "description": "A list of unique included resources" + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/ToplevelLinks" + } + ], + "title": "Links", + "description": "Links associated with the primary data or errors" + }, + "jsonapi": { + "allOf": [ + { + "$ref": "#/components/schemas/JsonApi" + } + ], + "title": "Jsonapi", + "description": "Information about the JSON API used" + } + }, + "type": "object", + "required": [ + "data", + "meta" + ], + "title": "EntryInfoResponse", + "description": "errors are not allowed" + }, + "EntryMetadata": { + "properties": { + "property_metadata": { + "type": "object", + "title": "Property Metadata", + "description": "An object containing per-entry and per-property metadata. The keys are the names of the fields in attributes for which metadata is available. The values belonging to these keys are dictionaries containing the relevant metadata fields. See also [Metadata properties](https://github.com/Materials-Consortia/OPTIMADE/blob/develop/optimade.rst#metadata-properties)" + }, + "partial_data_links": { + "additionalProperties": { + "items": { + "$ref": "#/components/schemas/PartialDataLink" + }, + "type": "array" + }, + "type": "object", + "title": "Partial Data Links", + "description": "A dictionary, where the keys are the names of the properties in the attributes field for which the value is too large to be shared by default.\n For each property one or more links are provided from which the value of the attribute can be retrieved." + } + }, + "type": "object", + "title": "EntryMetadata", + "description": "Contains the metadata for the attributes of an entry" + }, + "EntryRelationships": { + "properties": { + "references": { + "allOf": [ + { + "$ref": "#/components/schemas/ReferenceRelationship" + } + ], + "title": "References", + "description": "Object containing links to relationships with entries of the `references` type." + }, + "structures": { + "allOf": [ + { + "$ref": "#/components/schemas/StructureRelationship" + } + ], + "title": "Structures", + "description": "Object containing links to relationships with entries of the `structures` type." + }, + "trajectories": { + "allOf": [ + { + "$ref": "#/components/schemas/TrajectoryRelationship" + } + ], + "title": "Trajectories", + "description": "Object containing links to relationships with entries of the `trajectories` type." + } + }, + "type": "object", + "title": "EntryRelationships", + "description": "This model wraps the JSON API Relationships to include type-specific top level keys." + }, + "EntryResource": { + "properties": { + "id": { + "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-support": "must", + "x-optimade-queryable": "must" }, "type": { "type": "string", - "pattern": "^info$", "title": "Type", - "default": "info" + "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 `` and ID `` MUST be returned in response to a request for `//` under the versioned base URL.\n\n- **Example**: `\"structures\"`", + "x-optimade-support": "must", + "x-optimade-queryable": "must" + }, + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/ResourceLinks" + } + ], + "title": "Links", + "description": "a links object containing links related to the resource." + }, + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/EntryMetadata" + } + ], + "title": "Meta", + "description": "A [JSON API meta object](https://jsonapi.org/format/1.1/#document-meta) that is used to communicate metadata." + }, + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/EntryResourceAttributes" + } + ], + "title": "Attributes", + "description": "A dictionary, containing key-value pairs representing the entry's properties, except for `type` and `id`.\nDatabase-provider-specific properties need to include the database-provider-specific prefix (see section on Database-Provider-Specific Namespace Prefixes)." + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/EntryRelationships" + } + ], + "title": "Relationships", + "description": "A dictionary containing references to other entries according to the description in section Relationships encoded as [JSON API Relationships](https://jsonapi.org/format/1.0/#document-resource-object-relationships).\nThe OPTIONAL human-readable description of the relationship MAY be provided in the `description` field inside the `meta` dictionary of the JSON API resource identifier object." + } + }, + "type": "object", + "required": [ + "id", + "type", + "attributes" + ], + "title": "EntryResource", + "description": "The base model for an entry resource." + }, + "EntryResourceAttributes": { + "properties": { + "immutable_id": { + "type": "string", + "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-support": "optional", + "x-optimade-queryable": "must" + }, + "last_modified": { + "type": "string", + "format": "date-time", + "title": "Last Modified", + "description": "Date and time representing when the entry was last modified.\n\n- **Type**: timestamp.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response unless the query parameter `response_fields` is present and does not include this property.\n\n- **Example**:\n - As part of JSON response format: `\"2007-04-05T14:30:20Z\"` (i.e., encoded as an [RFC 3339 Internet Date/Time Format](https://tools.ietf.org/html/rfc3339#section-5.6) string.)", + "x-optimade-support": "should", + "x-optimade-queryable": "must" + } + }, + "type": "object", + "required": [ + "last_modified" + ], + "title": "EntryResourceAttributes", + "description": "Contains key-value pairs representing the entry's properties." + }, + "Error": { + "properties": { + "id": { + "type": "string", + "title": "Id", + "description": "A unique identifier for this particular occurrence of the problem." }, "links": { "allOf": [ { - "$ref": "#/components/schemas/ResourceLinks" + "$ref": "#/components/schemas/ErrorLinks" + } + ], + "title": "Links", + "description": "A links object storing about" + }, + "status": { + "type": "string", + "title": "Status", + "description": "the HTTP status code applicable to this problem, expressed as a string value." + }, + "code": { + "type": "string", + "title": "Code", + "description": "an application-specific error code, expressed as a string value." + }, + "title": { + "type": "string", + "title": "Title", + "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization." + }, + "detail": { + "type": "string", + "title": "Detail", + "description": "A human-readable explanation specific to this occurrence of the problem." + }, + "source": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorSource" + } + ], + "title": "Source", + "description": "An object containing references to the source of the error" + }, + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/Meta" + } + ], + "title": "Meta", + "description": "a meta object containing non-standard meta-information about the error." + } + }, + "type": "object", + "title": "Error", + "description": "An error response" + }, + "ErrorLinks": { + "properties": { + "about": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" } ], - "title": "Links", - "description": "a links object containing links related to the resource." + "title": "About", + "description": "A link that leads to further details about this particular occurrence of the problem." + } + }, + "type": "object", + "title": "ErrorLinks", + "description": "A Links object specific to Error objects" + }, + "ErrorResponse": { + "properties": { + "data": { + "anyOf": [ + { + "$ref": "#/components/schemas/Resource" + }, + { + "items": { + "$ref": "#/components/schemas/Resource" + }, + "type": "array" + } + ], + "uniqueItems": true, + "title": "Data", + "description": "Outputted Data" }, "meta": { "allOf": [ { - "$ref": "#/components/schemas/Meta" + "$ref": "#/components/schemas/ResponseMeta" } ], "title": "Meta", - "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." + "description": "A meta object containing non-standard information." }, - "attributes": { - "$ref": "#/components/schemas/BaseInfoAttributes" + "errors": { + "items": { + "$ref": "#/components/schemas/OptimadeError" + }, + "type": "array", + "uniqueItems": true, + "title": "Errors", + "description": "A list of OPTIMADE-specific JSON API error objects, where the field detail MUST be present." }, - "relationships": { + "included": { + "items": { + "$ref": "#/components/schemas/Resource" + }, + "type": "array", + "uniqueItems": true, + "title": "Included", + "description": "A list of unique included resources" + }, + "links": { "allOf": [ { - "$ref": "#/components/schemas/Relationships" + "$ref": "#/components/schemas/ToplevelLinks" } ], - "title": "Relationships", - "description": "[Relationships object](https://jsonapi.org/format/1.0/#document-resource-object-relationships)\ndescribing relationships between the resource and other JSON API resources." + "title": "Links", + "description": "Links associated with the primary data or errors" + }, + "jsonapi": { + "allOf": [ + { + "$ref": "#/components/schemas/JsonApi" + } + ], + "title": "Jsonapi", + "description": "Information about the JSON API used" } }, "type": "object", "required": [ - "id", - "type", - "attributes" + "meta", + "errors" ], - "title": "BaseInfoResource", - "description": "Resource objects appear in a JSON API document to represent resources." + "title": "ErrorResponse", + "description": "errors MUST be present and data MUST be skipped" }, - "BaseRelationshipMeta": { + "ErrorSource": { "properties": { - "description": { + "pointer": { "type": "string", - "title": "Description", - "description": "OPTIONAL human-readable description of the relationship." + "title": "Pointer", + "description": "a JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute]." + }, + "parameter": { + "type": "string", + "title": "Parameter", + "description": "a string indicating which URI query parameter caused the error." } }, "type": "object", - "required": [ - "description" - ], - "title": "BaseRelationshipMeta", - "description": "Specific meta field for base relationship resource" + "title": "ErrorSource", + "description": "an object containing references to the source of the error" }, - "BaseRelationshipResource": { + "Implementation": { "properties": { - "id": { + "name": { "type": "string", - "title": "Id", - "description": "Resource ID" + "title": "Name", + "description": "name of the implementation" }, - "type": { + "version": { "type": "string", - "title": "Type", - "description": "Resource type" + "title": "Version", + "description": "version string of the current implementation" }, - "meta": { - "allOf": [ + "homepage": { + "anyOf": [ { - "$ref": "#/components/schemas/BaseRelationshipMeta" + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" } ], - "title": "Meta", - "description": "Relationship meta field. MUST contain 'description' if supplied." - } - }, - "type": "object", - "required": [ - "id", - "type" - ], - "title": "BaseRelationshipResource", - "description": "Minimum requirements to represent a relationship resource" - }, - "DataType": { - "enum": [ - "string", - "integer", - "float", - "boolean", - "timestamp", - "list", - "dictionary", - "unknown" - ], - "title": "DataType", - "description": "Optimade Data Types\n\nSee the section \"Data types\" in the OPTIMADE API specification for more information." - }, - "EntryInfoProperty": { - "properties": { - "description": { - "type": "string", - "title": "Description", - "description": "A human-readable description of the entry property" - }, - "unit": { - "type": "string", - "title": "Unit", - "description": "The physical unit of the entry property.\nThis MUST be a valid representation of units according to version 2.1 of [The Unified Code for Units of Measure](https://unitsofmeasure.org/ucum.html).\nIt is RECOMMENDED that non-standard (non-SI) units are described in the description for the property." + "title": "Homepage", + "description": "A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) pointing to the homepage of the implementation." }, - "sortable": { - "type": "boolean", - "title": "Sortable", - "description": "Defines whether the entry property can be used for sorting with the \"sort\" parameter.\nIf the entry listing endpoint supports sorting, this key MUST be present for sortable properties with value `true`." + "source_url": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Source Url", + "description": "A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) pointing to the implementation source, either downloadable archive or version control system." }, - "type": { + "maintainer": { "allOf": [ { - "$ref": "#/components/schemas/DataType" + "$ref": "#/components/schemas/ImplementationMaintainer" } ], - "title": "Type", - "description": "The type of the property's value.\nThis MUST be any of the types defined in the Data types section.\nFor the purpose of compatibility with future versions of this specification, a client MUST accept values that are not `string` values specifying any of the OPTIMADE Data types, but MUST then also disregard the `type` field.\nNote, if the value is a nested type, only the outermost type should be reported.\nE.g., for the entry resource `structures`, the `species` property is defined as a list of dictionaries, hence its `type` value would be `list`." - } - }, - "type": "object", - "required": [ - "description" - ], - "title": "EntryInfoProperty" - }, - "EntryInfoResource": { - "properties": { - "formats": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Formats", - "description": "List of output formats available for this type of entry." - }, - "description": { - "type": "string", - "title": "Description", - "description": "Description of the entry." - }, - "properties": { - "additionalProperties": { - "$ref": "#/components/schemas/EntryInfoProperty" - }, - "type": "object", - "title": "Properties", - "description": "A dictionary describing queryable properties for this entry type, where each key is a property name." + "title": "Maintainer", + "description": "A dictionary providing details about the maintainer of the implementation." }, - "output_fields_by_format": { - "additionalProperties": { - "items": { - "type": "string" + "issue_tracker": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" }, - "type": "array" - }, - "type": "object", - "title": "Output Fields By Format", - "description": "Dictionary of available output fields for this entry type, where the keys are the values of the `formats` list and the values are the keys of the `properties` dictionary." + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Issue Tracker", + "description": "A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) pointing to the implementation's issue tracker." + } + }, + "type": "object", + "title": "Implementation", + "description": "Information on the server implementation" + }, + "ImplementationMaintainer": { + "properties": { + "email": { + "type": "string", + "format": "email", + "title": "Email", + "description": "the maintainer's email address" } }, "type": "object", "required": [ - "formats", - "description", - "properties", - "output_fields_by_format" + "email" ], - "title": "EntryInfoResource" + "title": "ImplementationMaintainer", + "description": "Details about the maintainer of the implementation" }, - "EntryInfoResponse": { + "InfoResponse": { "properties": { "data": { "allOf": [ { - "$ref": "#/components/schemas/EntryInfoResource" + "$ref": "#/components/schemas/BaseInfoResource" } ], "title": "Data", - "description": "OPTIMADE information for an entry endpoint." + "description": "The implementations /info data." }, "meta": { "allOf": [ @@ -1714,35 +2808,69 @@ "data", "meta" ], - "title": "EntryInfoResponse", + "title": "InfoResponse", "description": "errors are not allowed" }, - "EntryRelationships": { + "JsonApi": { "properties": { - "references": { + "version": { + "type": "string", + "title": "Version", + "description": "Version of the json API used", + "default": "1.0" + }, + "meta": { "allOf": [ { - "$ref": "#/components/schemas/ReferenceRelationship" + "$ref": "#/components/schemas/Meta" } ], - "title": "References", - "description": "Object containing links to relationships with entries of the `references` type." + "title": "Meta", + "description": "Non-standard meta information" + } + }, + "type": "object", + "title": "JsonApi", + "description": "An object describing the server's implementation" + }, + "Link": { + "properties": { + "href": { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri", + "title": "Href", + "description": "a string containing the link\u2019s URL." }, - "structures": { + "meta": { "allOf": [ { - "$ref": "#/components/schemas/StructureRelationship" + "$ref": "#/components/schemas/Meta" } ], - "title": "Structures", - "description": "Object containing links to relationships with entries of the `structures` type." + "title": "Meta", + "description": "a meta object containing non-standard meta-information about the link." } }, "type": "object", - "title": "EntryRelationships", - "description": "This model wraps the JSON API Relationships to include type-specific top level keys." + "required": [ + "href" + ], + "title": "Link", + "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object." }, - "EntryResource": { + "LinkType": { + "enum": [ + "child", + "root", + "external", + "providers" + ], + "title": "LinkType", + "description": "Enumeration of link_type values" + }, + "LinksResource": { "properties": { "id": { "type": "string", @@ -1753,10 +2881,10 @@ }, "type": { "type": "string", + "pattern": "^links$", "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 `` and ID `` MUST be returned in response to a request for `//` under the versioned base URL.\n\n- **Example**: `\"structures\"`", - "x-optimade-support": "must", - "x-optimade-queryable": "must" + "description": "These objects are described in detail in the section Links Endpoint", + "default": "links" }, "links": { "allOf": [ @@ -1770,66 +2898,209 @@ "meta": { "allOf": [ { - "$ref": "#/components/schemas/Meta" + "$ref": "#/components/schemas/EntryMetadata" } ], "title": "Meta", - "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." + "description": "A [JSON API meta object](https://jsonapi.org/format/1.1/#document-meta) that is used to communicate metadata." + }, + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/LinksResourceAttributes" + } + ], + "title": "Attributes", + "description": "A dictionary containing key-value pairs representing the Links resource's properties." + }, + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/EntryRelationships" + } + ], + "title": "Relationships", + "description": "A dictionary containing references to other entries according to the description in section Relationships encoded as [JSON API Relationships](https://jsonapi.org/format/1.0/#document-resource-object-relationships).\nThe OPTIONAL human-readable description of the relationship MAY be provided in the `description` field inside the `meta` dictionary of the JSON API resource identifier object." + } + }, + "type": "object", + "required": [ + "id", + "type", + "attributes" + ], + "title": "LinksResource", + "description": "A Links endpoint resource object" + }, + "LinksResourceAttributes": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Human-readable name for the OPTIMADE API implementation, e.g., for use in clients to show the name to the end-user." + }, + "description": { + "type": "string", + "title": "Description", + "description": "Human-readable description for the OPTIMADE API implementation, e.g., for use in clients to show a description to the end-user." + }, + "base_url": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Base Url", + "description": "JSON API links object, pointing to the base URL for this implementation" + }, + "homepage": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Homepage", + "description": "JSON API links object, pointing to a homepage URL for this implementation" + }, + "link_type": { + "allOf": [ + { + "$ref": "#/components/schemas/LinkType" + } + ], + "title": "Link Type", + "description": "The type of the linked relation.\nMUST be one of these values: 'child', 'root', 'external', 'providers'." + }, + "aggregate": { + "allOf": [ + { + "$ref": "#/components/schemas/Aggregate" + } + ], + "title": "Aggregate", + "description": "A string indicating whether a client that is following links to aggregate results from different OPTIMADE implementations should follow this link or not.\nThis flag SHOULD NOT be indicated for links where `link_type` is not `child`.\n\nIf not specified, clients MAY assume that the value is `ok`.\nIf specified, and the value is anything different than `ok`, the client MUST assume that the server is suggesting not to follow the link during aggregation by default (also if the value is not among the known ones, in case a future specification adds new accepted values).\n\nSpecific values indicate the reason why the server is providing the suggestion.\nA client MAY follow the link anyway if it has reason to do so (e.g., if the client is looking for all test databases, it MAY follow the links marked with `aggregate`=`test`).\n\nIf specified, it MUST be one of the values listed in section Link Aggregate Options.", + "default": "ok" + }, + "no_aggregate_reason": { + "type": "string", + "title": "No Aggregate Reason", + "description": "An OPTIONAL human-readable string indicating the reason for suggesting not to aggregate results following the link.\nIt SHOULD NOT be present if `aggregate`=`ok`." + } + }, + "type": "object", + "required": [ + "name", + "description", + "base_url", + "homepage", + "link_type" + ], + "title": "LinksResourceAttributes", + "description": "Links endpoint resource object attributes" + }, + "LinksResponse": { + "properties": { + "data": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/LinksResource" + }, + "type": "array" + }, + { + "items": { + "type": "object" + }, + "type": "array" + } + ], + "uniqueItems": true, + "title": "Data", + "description": "List of unique OPTIMADE links resource objects." + }, + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseMeta" + } + ], + "title": "Meta", + "description": "A meta object containing non-standard information" + }, + "errors": { + "items": { + "$ref": "#/components/schemas/Error" + }, + "type": "array", + "uniqueItems": true, + "title": "Errors", + "description": "A list of unique errors" + }, + "included": { + "anyOf": [ + { + "items": { + "$ref": "#/components/schemas/EntryResource" + }, + "type": "array" + }, + { + "items": { + "type": "object" + }, + "type": "array" + } + ], + "uniqueItems": true, + "title": "Included" }, - "attributes": { + "links": { "allOf": [ { - "$ref": "#/components/schemas/EntryResourceAttributes" + "$ref": "#/components/schemas/ToplevelLinks" } ], - "title": "Attributes", - "description": "A dictionary, containing key-value pairs representing the entry's properties, except for `type` and `id`.\nDatabase-provider-specific properties need to include the database-provider-specific prefix (see section on Database-Provider-Specific Namespace Prefixes)." + "title": "Links", + "description": "Links associated with the primary data or errors" }, - "relationships": { + "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/EntryRelationships" + "$ref": "#/components/schemas/JsonApi" } ], - "title": "Relationships", - "description": "A dictionary containing references to other entries according to the description in section Relationships encoded as [JSON API Relationships](https://jsonapi.org/format/1.0/#document-resource-object-relationships).\nThe OPTIONAL human-readable description of the relationship MAY be provided in the `description` field inside the `meta` dictionary of the JSON API resource identifier object." + "title": "Jsonapi", + "description": "Information about the JSON API used" } }, "type": "object", "required": [ - "id", - "type", - "attributes" + "data", + "meta" ], - "title": "EntryResource", - "description": "The base model for an entry resource." + "title": "LinksResponse", + "description": "errors are not allowed" }, - "EntryResourceAttributes": { - "properties": { - "immutable_id": { - "type": "string", - "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-support": "optional", - "x-optimade-queryable": "must" - }, - "last_modified": { - "type": "string", - "format": "date-time", - "title": "Last Modified", - "description": "Date and time representing when the entry was last modified.\n\n- **Type**: timestamp.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response unless the query parameter `response_fields` is present and does not include this property.\n\n- **Example**:\n - As part of JSON response format: `\"2007-04-05T14:30:20Z\"` (i.e., encoded as an [RFC 3339 Internet Date/Time Format](https://tools.ietf.org/html/rfc3339#section-5.6) string.)", - "x-optimade-support": "should", - "x-optimade-queryable": "must" - } - }, + "Meta": { + "properties": {}, "type": "object", - "required": [ - "last_modified" - ], - "title": "EntryResourceAttributes", - "description": "Contains key-value pairs representing the entry's properties." + "title": "Meta", + "description": "Non-standard meta-information that can not be represented as an attribute or relationship." }, - "Error": { + "OptimadeError": { "properties": { "id": { "type": "string", @@ -1885,12 +3156,94 @@ } }, "type": "object", - "title": "Error", - "description": "An error response" + "required": [ + "detail" + ], + "title": "OptimadeError", + "description": "detail MUST be present" }, - "ErrorLinks": { + "PartialDataLink": { "properties": { - "about": { + "link": { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri", + "title": "Link", + "description": "String. A JSON API link that points to a location from which the omitted data can be fetched. There is no requirement on the syntax or format for the link URL." + }, + "format": { + "type": "string", + "title": "Format", + "description": "String. The name of the format provided via this link. For one of the objects this format field SHOULD have the value \"jsonlines\", which refers to the format in OPTIMADE JSON lines partial data format." + } + }, + "type": "object", + "required": [ + "link", + "format" + ], + "title": "PartialDataLink" + }, + "Periodicity": { + "type": "integer", + "enum": [ + 0, + 1 + ], + "title": "Periodicity", + "description": "Integer enumeration of dimension_types values" + }, + "Person": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "Full name of the person, REQUIRED.", + "x-optimade-support": "must", + "x-optimade-queryable": "optional" + }, + "firstname": { + "type": "string", + "title": "Firstname", + "description": "First name of the person.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "lastname": { + "type": "string", + "title": "Lastname", + "description": "Last name of the person.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + } + }, + "type": "object", + "required": [ + "name" + ], + "title": "Person", + "description": "A person, i.e., an author, editor or other." + }, + "Provider": { + "properties": { + "name": { + "type": "string", + "title": "Name", + "description": "a short name for the database provider" + }, + "description": { + "type": "string", + "title": "Description", + "description": "a longer description of the database provider" + }, + "prefix": { + "type": "string", + "pattern": "^[a-z]([a-z]|[0-9]|_)*$", + "title": "Prefix", + "description": "database-provider-specific prefix as found in section Database-Provider-Specific Namespace Prefixes." + }, + "homepage": { "anyOf": [ { "type": "string", @@ -1902,200 +3255,354 @@ "$ref": "#/components/schemas/Link" } ], - "title": "About", - "description": "A link that leads to further details about this particular occurrence of the problem." + "title": "Homepage", + "description": "a [JSON API links object](http://jsonapi.org/format/1.0#document-links) pointing to homepage of the database provider, either directly as a string, or as a link object." } }, "type": "object", - "title": "ErrorLinks", - "description": "A Links object specific to Error objects" + "required": [ + "name", + "description", + "prefix" + ], + "title": "Provider", + "description": "Information on the database provider of the implementation." }, - "ErrorResponse": { + "ReferenceRelationship": { "properties": { + "links": { + "allOf": [ + { + "$ref": "#/components/schemas/RelationshipLinks" + } + ], + "title": "Links", + "description": "a links object containing at least one of the following: self, related" + }, "data": { "anyOf": [ { - "$ref": "#/components/schemas/Resource" + "$ref": "#/components/schemas/BaseRelationshipResource" }, { "items": { - "$ref": "#/components/schemas/Resource" + "$ref": "#/components/schemas/BaseRelationshipResource" }, "type": "array" } ], "uniqueItems": true, "title": "Data", - "description": "Outputted Data" + "description": "Resource linkage" }, "meta": { "allOf": [ { - "$ref": "#/components/schemas/ResponseMeta" + "$ref": "#/components/schemas/Meta" } ], "title": "Meta", - "description": "A meta object containing non-standard information." - }, - "errors": { - "items": { - "$ref": "#/components/schemas/OptimadeError" - }, - "type": "array", - "uniqueItems": true, - "title": "Errors", - "description": "A list of OPTIMADE-specific JSON API error objects, where the field detail MUST be present." - }, - "included": { - "items": { - "$ref": "#/components/schemas/Resource" - }, - "type": "array", - "uniqueItems": true, - "title": "Included", - "description": "A list of unique included resources" - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/ToplevelLinks" - } - ], - "title": "Links", - "description": "Links associated with the primary data or errors" - }, - "jsonapi": { - "allOf": [ - { - "$ref": "#/components/schemas/JsonApi" - } - ], - "title": "Jsonapi", - "description": "Information about the JSON API used" - } - }, - "type": "object", - "required": [ - "meta", - "errors" - ], - "title": "ErrorResponse", - "description": "errors MUST be present and data MUST be skipped" - }, - "ErrorSource": { - "properties": { - "pointer": { - "type": "string", - "title": "Pointer", - "description": "a JSON Pointer [RFC6901] to the associated entity in the request document [e.g. \"/data\" for a primary data object, or \"/data/attributes/title\" for a specific attribute]." - }, - "parameter": { - "type": "string", - "title": "Parameter", - "description": "a string indicating which URI query parameter caused the error." + "description": "a meta object that contains non-standard meta-information about the relationship." } }, "type": "object", - "title": "ErrorSource", - "description": "an object containing references to the source of the error" - }, - "Implementation": { - "properties": { - "name": { - "type": "string", - "title": "Name", - "description": "name of the implementation" - }, - "version": { - "type": "string", - "title": "Version", - "description": "version string of the current implementation" - }, - "homepage": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, - { - "$ref": "#/components/schemas/Link" - } - ], - "title": "Homepage", - "description": "A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) pointing to the homepage of the implementation." + "title": "ReferenceRelationship", + "description": "Similar to normal JSON API relationship, but with addition of OPTIONAL meta field for a resource." + }, + "ReferenceResource": { + "properties": { + "id": { + "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-support": "must", + "x-optimade-queryable": "must" }, - "source_url": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, + "type": { + "type": "string", + "pattern": "^references$", + "title": "Type", + "description": "The name of the type of an entry.\n- **Type**: string.\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 and ID MUST be returned in response to a request for `//` under the versioned base URL.\n- **Example**: `\"structures\"`", + "default": "references", + "x-optimade-support": "must", + "x-optimade-queryable": "must" + }, + "links": { + "allOf": [ { - "$ref": "#/components/schemas/Link" + "$ref": "#/components/schemas/ResourceLinks" } ], - "title": "Source Url", - "description": "A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) pointing to the implementation source, either downloadable archive or version control system." + "title": "Links", + "description": "a links object containing links related to the resource." }, - "maintainer": { + "meta": { "allOf": [ { - "$ref": "#/components/schemas/ImplementationMaintainer" + "$ref": "#/components/schemas/EntryMetadata" } ], - "title": "Maintainer", - "description": "A dictionary providing details about the maintainer of the implementation." + "title": "Meta", + "description": "A [JSON API meta object](https://jsonapi.org/format/1.1/#document-meta) that is used to communicate metadata." }, - "issue_tracker": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, + "attributes": { + "$ref": "#/components/schemas/ReferenceResourceAttributes" + }, + "relationships": { + "allOf": [ { - "$ref": "#/components/schemas/Link" + "$ref": "#/components/schemas/EntryRelationships" } ], - "title": "Issue Tracker", - "description": "A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) pointing to the implementation's issue tracker." + "title": "Relationships", + "description": "A dictionary containing references to other entries according to the description in section Relationships encoded as [JSON API Relationships](https://jsonapi.org/format/1.0/#document-resource-object-relationships).\nThe OPTIONAL human-readable description of the relationship MAY be provided in the `description` field inside the `meta` dictionary of the JSON API resource identifier object." } }, "type": "object", - "title": "Implementation", - "description": "Information on the server implementation" + "required": [ + "id", + "type", + "attributes" + ], + "title": "ReferenceResource", + "description": "The `references` entries describe bibliographic references.\n\nThe following properties are used to provide the bibliographic details:\n\n- **address**, **annote**, **booktitle**, **chapter**, **crossref**, **edition**, **howpublished**, **institution**, **journal**, **key**, **month**, **note**, **number**, **organization**, **pages**, **publisher**, **school**, **series**, **title**, **volume**, **year**: meanings of these properties match the [BibTeX specification](http://bibtexml.sourceforge.net/btxdoc.pdf), values are strings;\n- **bib_type**: type of the reference, corresponding to **type** property in the BibTeX specification, value is string;\n- **authors** and **editors**: lists of *person objects* which are dictionaries with the following keys:\n - **name**: Full name of the person, REQUIRED.\n - **firstname**, **lastname**: Parts of the person's name, OPTIONAL.\n- **doi** and **url**: values are strings.\n- **Requirements/Conventions**:\n - **Support**: OPTIONAL support in implementations, i.e., any of the properties MAY be `null`.\n - **Query**: Support for queries on any of these properties is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - Every references entry MUST contain at least one of the properties." }, - "ImplementationMaintainer": { + "ReferenceResourceAttributes": { "properties": { - "email": { + "immutable_id": { + "type": "string", + "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-support": "optional", + "x-optimade-queryable": "must" + }, + "last_modified": { + "type": "string", + "format": "date-time", + "title": "Last Modified", + "description": "Date and time representing when the entry was last modified.\n\n- **Type**: timestamp.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response unless the query parameter `response_fields` is present and does not include this property.\n\n- **Example**:\n - As part of JSON response format: `\"2007-04-05T14:30:20Z\"` (i.e., encoded as an [RFC 3339 Internet Date/Time Format](https://tools.ietf.org/html/rfc3339#section-5.6) string.)", + "x-optimade-support": "should", + "x-optimade-queryable": "must" + }, + "authors": { + "items": { + "$ref": "#/components/schemas/Person" + }, + "type": "array", + "title": "Authors", + "description": "List of person objects containing the authors of the reference.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "editors": { + "items": { + "$ref": "#/components/schemas/Person" + }, + "type": "array", + "title": "Editors", + "description": "List of person objects containing the editors of the reference.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "doi": { + "type": "string", + "title": "Doi", + "description": "The digital object identifier of the reference.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "url": { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri", + "title": "Url", + "description": "The URL of the reference.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "address": { + "type": "string", + "title": "Address", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "annote": { + "type": "string", + "title": "Annote", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "booktitle": { + "type": "string", + "title": "Booktitle", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "chapter": { + "type": "string", + "title": "Chapter", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "crossref": { + "type": "string", + "title": "Crossref", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "edition": { + "type": "string", + "title": "Edition", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "howpublished": { + "type": "string", + "title": "Howpublished", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "institution": { + "type": "string", + "title": "Institution", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "journal": { + "type": "string", + "title": "Journal", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "key": { + "type": "string", + "title": "Key", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "month": { + "type": "string", + "title": "Month", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "note": { + "type": "string", + "title": "Note", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "number": { + "type": "string", + "title": "Number", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "organization": { + "type": "string", + "title": "Organization", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "pages": { + "type": "string", + "title": "Pages", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "publisher": { + "type": "string", + "title": "Publisher", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "school": { + "type": "string", + "title": "School", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "series": { + "type": "string", + "title": "Series", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "title": { + "type": "string", + "title": "Title", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "bib_type": { "type": "string", - "format": "email", - "title": "Email", - "description": "the maintainer's email address" + "title": "Bib Type", + "description": "Type of the reference, corresponding to the **type** property in the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "volume": { + "type": "string", + "title": "Volume", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "year": { + "type": "string", + "title": "Year", + "description": "Meaning of property matches the BiBTeX specification.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" } }, "type": "object", "required": [ - "email" + "last_modified" ], - "title": "ImplementationMaintainer", - "description": "Details about the maintainer of the implementation" + "title": "ReferenceResourceAttributes", + "description": "Model that stores the attributes of a reference.\n\nMany properties match the meaning described in the\n[BibTeX specification](http://bibtexml.sourceforge.net/btxdoc.pdf)." }, - "InfoResponse": { + "ReferenceResponseMany": { "properties": { "data": { - "allOf": [ + "anyOf": [ { - "$ref": "#/components/schemas/BaseInfoResource" + "items": { + "$ref": "#/components/schemas/ReferenceResource" + }, + "type": "array" + }, + { + "items": { + "type": "object" + }, + "type": "array" } ], + "uniqueItems": true, "title": "Data", - "description": "The implementations /info data." + "description": "List of unique OPTIMADE references entry resource objects." }, "meta": { "allOf": [ @@ -2116,260 +3623,63 @@ "description": "A list of unique errors" }, "included": { - "items": { - "$ref": "#/components/schemas/Resource" - }, - "type": "array", - "uniqueItems": true, - "title": "Included", - "description": "A list of unique included resources" - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/ToplevelLinks" - } - ], - "title": "Links", - "description": "Links associated with the primary data or errors" - }, - "jsonapi": { - "allOf": [ - { - "$ref": "#/components/schemas/JsonApi" - } - ], - "title": "Jsonapi", - "description": "Information about the JSON API used" - } - }, - "type": "object", - "required": [ - "data", - "meta" - ], - "title": "InfoResponse", - "description": "errors are not allowed" - }, - "JsonApi": { - "properties": { - "version": { - "type": "string", - "title": "Version", - "description": "Version of the json API used", - "default": "1.0" - }, - "meta": { - "allOf": [ - { - "$ref": "#/components/schemas/Meta" - } - ], - "title": "Meta", - "description": "Non-standard meta information" - } - }, - "type": "object", - "title": "JsonApi", - "description": "An object describing the server's implementation" - }, - "Link": { - "properties": { - "href": { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri", - "title": "Href", - "description": "a string containing the link\u2019s URL." - }, - "meta": { - "allOf": [ - { - "$ref": "#/components/schemas/Meta" - } - ], - "title": "Meta", - "description": "a meta object containing non-standard meta-information about the link." - } - }, - "type": "object", - "required": [ - "href" - ], - "title": "Link", - "description": "A link **MUST** be represented as either: a string containing the link's URL or a link object." - }, - "LinkType": { - "enum": [ - "child", - "root", - "external", - "providers" - ], - "title": "LinkType", - "description": "Enumeration of link_type values" - }, - "LinksResource": { - "properties": { - "id": { - "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-support": "must", - "x-optimade-queryable": "must" - }, - "type": { - "type": "string", - "pattern": "^links$", - "title": "Type", - "description": "These objects are described in detail in the section Links Endpoint", - "default": "links" - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/ResourceLinks" - } - ], - "title": "Links", - "description": "a links object containing links related to the resource." - }, - "meta": { - "allOf": [ - { - "$ref": "#/components/schemas/Meta" - } - ], - "title": "Meta", - "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/LinksResourceAttributes" - } - ], - "title": "Attributes", - "description": "A dictionary containing key-value pairs representing the Links resource's properties." - }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/EntryRelationships" - } - ], - "title": "Relationships", - "description": "A dictionary containing references to other entries according to the description in section Relationships encoded as [JSON API Relationships](https://jsonapi.org/format/1.0/#document-resource-object-relationships).\nThe OPTIONAL human-readable description of the relationship MAY be provided in the `description` field inside the `meta` dictionary of the JSON API resource identifier object." - } - }, - "type": "object", - "required": [ - "id", - "type", - "attributes" - ], - "title": "LinksResource", - "description": "A Links endpoint resource object" - }, - "LinksResourceAttributes": { - "properties": { - "name": { - "type": "string", - "title": "Name", - "description": "Human-readable name for the OPTIMADE API implementation, e.g., for use in clients to show the name to the end-user." - }, - "description": { - "type": "string", - "title": "Description", - "description": "Human-readable description for the OPTIMADE API implementation, e.g., for use in clients to show a description to the end-user." - }, - "base_url": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, - { - "$ref": "#/components/schemas/Link" - } - ], - "title": "Base Url", - "description": "JSON API links object, pointing to the base URL for this implementation" - }, - "homepage": { "anyOf": [ { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" + "items": { + "$ref": "#/components/schemas/EntryResource" + }, + "type": "array" }, { - "$ref": "#/components/schemas/Link" + "items": { + "type": "object" + }, + "type": "array" } ], - "title": "Homepage", - "description": "JSON API links object, pointing to a homepage URL for this implementation" + "uniqueItems": true, + "title": "Included" }, - "link_type": { + "links": { "allOf": [ { - "$ref": "#/components/schemas/LinkType" + "$ref": "#/components/schemas/ToplevelLinks" } ], - "title": "Link Type", - "description": "The type of the linked relation.\nMUST be one of these values: 'child', 'root', 'external', 'providers'." + "title": "Links", + "description": "Links associated with the primary data or errors" }, - "aggregate": { + "jsonapi": { "allOf": [ { - "$ref": "#/components/schemas/Aggregate" + "$ref": "#/components/schemas/JsonApi" } ], - "title": "Aggregate", - "description": "A string indicating whether a client that is following links to aggregate results from different OPTIMADE implementations should follow this link or not.\nThis flag SHOULD NOT be indicated for links where `link_type` is not `child`.\n\nIf not specified, clients MAY assume that the value is `ok`.\nIf specified, and the value is anything different than `ok`, the client MUST assume that the server is suggesting not to follow the link during aggregation by default (also if the value is not among the known ones, in case a future specification adds new accepted values).\n\nSpecific values indicate the reason why the server is providing the suggestion.\nA client MAY follow the link anyway if it has reason to do so (e.g., if the client is looking for all test databases, it MAY follow the links marked with `aggregate`=`test`).\n\nIf specified, it MUST be one of the values listed in section Link Aggregate Options.", - "default": "ok" - }, - "no_aggregate_reason": { - "type": "string", - "title": "No Aggregate Reason", - "description": "An OPTIONAL human-readable string indicating the reason for suggesting not to aggregate results following the link.\nIt SHOULD NOT be present if `aggregate`=`ok`." + "title": "Jsonapi", + "description": "Information about the JSON API used" } }, "type": "object", "required": [ - "name", - "description", - "base_url", - "homepage", - "link_type" + "data", + "meta" ], - "title": "LinksResourceAttributes", - "description": "Links endpoint resource object attributes" + "title": "ReferenceResponseMany", + "description": "errors are not allowed" }, - "LinksResponse": { + "ReferenceResponseOne": { "properties": { "data": { "anyOf": [ { - "items": { - "$ref": "#/components/schemas/LinksResource" - }, - "type": "array" + "$ref": "#/components/schemas/ReferenceResource" }, { - "items": { - "type": "object" - }, - "type": "array" + "type": "object" } ], - "uniqueItems": true, "title": "Data", - "description": "List of unique OPTIMADE links resource objects." + "description": "A single references entry resource." }, "meta": { "allOf": [ @@ -2431,161 +3741,340 @@ "data", "meta" ], - "title": "LinksResponse", + "title": "ReferenceResponseOne", "description": "errors are not allowed" }, - "Meta": { + "RelationshipLinks": { + "properties": { + "self": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Self", + "description": "A link for the relationship itself (a 'relationship link').\nThis link allows the client to directly manipulate the relationship.\nWhen fetched successfully, this link returns the [linkage](https://jsonapi.org/format/1.0/#document-resource-object-linkage) for the related resources as its primary data.\n(See [Fetching Relationships](https://jsonapi.org/format/1.0/#fetching-relationships).)" + }, + "related": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Related", + "description": "A [related resource link](https://jsonapi.org/format/1.0/#document-resource-object-related-resource-links)." + } + }, + "type": "object", + "title": "RelationshipLinks", + "description": "A resource object **MAY** contain references to other resource objects (\"relationships\").\nRelationships may be to-one or to-many.\nRelationships can be specified by including a member in a resource's links object." + }, + "Relationships": { "properties": {}, "type": "object", - "title": "Meta", - "description": "Non-standard meta-information that can not be represented as an attribute or relationship." + "title": "Relationships", + "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.\nKeys MUST NOT be:\n type\n id" }, - "OptimadeError": { + "Resource": { "properties": { "id": { "type": "string", "title": "Id", - "description": "A unique identifier for this particular occurrence of the problem." + "description": "Resource ID" + }, + "type": { + "type": "string", + "title": "Type", + "description": "Resource type" }, "links": { "allOf": [ { - "$ref": "#/components/schemas/ErrorLinks" + "$ref": "#/components/schemas/ResourceLinks" } ], "title": "Links", - "description": "A links object storing about" + "description": "a links object containing links related to the resource." }, - "status": { - "type": "string", - "title": "Status", - "description": "the HTTP status code applicable to this problem, expressed as a string value." + "meta": { + "allOf": [ + { + "$ref": "#/components/schemas/Meta" + } + ], + "title": "Meta", + "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." }, - "code": { - "type": "string", - "title": "Code", - "description": "an application-specific error code, expressed as a string value." + "attributes": { + "allOf": [ + { + "$ref": "#/components/schemas/Attributes" + } + ], + "title": "Attributes", + "description": "an attributes object representing some of the resource\u2019s data." }, - "title": { + "relationships": { + "allOf": [ + { + "$ref": "#/components/schemas/Relationships" + } + ], + "title": "Relationships", + "description": "[Relationships object](https://jsonapi.org/format/1.0/#document-resource-object-relationships)\ndescribing relationships between the resource and other JSON API resources." + } + }, + "type": "object", + "required": [ + "id", + "type" + ], + "title": "Resource", + "description": "Resource objects appear in a JSON API document to represent resources." + }, + "ResourceLinks": { + "properties": { + "self": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Self", + "description": "A link that identifies the resource represented by the resource object." + } + }, + "type": "object", + "title": "ResourceLinks", + "description": "A Resource Links object" + }, + "ResponseMeta": { + "properties": { + "query": { + "allOf": [ + { + "$ref": "#/components/schemas/ResponseMetaQuery" + } + ], + "title": "Query", + "description": "Information on the Query that was requested" + }, + "api_version": { "type": "string", - "title": "Title", - "description": "A short, human-readable summary of the problem. It **SHOULD NOT** change from occurrence to occurrence of the problem, except for purposes of localization." + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "title": "Api Version", + "description": "Presently used full version of the OPTIMADE API.\nThe version number string MUST NOT be prefixed by, e.g., \"v\".\nExamples: `1.0.0`, `1.0.0-rc.2`.", + "example": [ + "0.10.1", + "1.0.0-rc.2", + "1.2.3-rc.5+develop" + ] }, - "detail": { + "more_data_available": { + "type": "boolean", + "title": "More Data Available", + "description": "`false` if the response contains all data for the request (e.g., a request issued to a single entry endpoint, or a `filter` query at the last page of a paginated response) and `true` if the response is incomplete in the sense that multiple objects match the request, and not all of them have been included in the response (e.g., a query with multiple pages that is not at the last page)." + }, + "schema": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Schema", + "description": "A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) that points to a schema for the response.\nIf it is a string, or a dictionary containing no `meta` field, the provided URL MUST point at an [OpenAPI](https://swagger.io/specification/) schema.\nIt is possible that future versions of this specification allows for alternative schema types.\nHence, if the `meta` field of the JSON API links object is provided and contains a field `schema_type` that is not equal to the string `OpenAPI` the client MUST not handle failures to parse the schema or to validate the response against the schema as errors." + }, + "time_stamp": { "type": "string", - "title": "Detail", - "description": "A human-readable explanation specific to this occurrence of the problem." + "format": "date-time", + "title": "Time Stamp", + "description": "A timestamp containing the date and time at which the query was executed." }, - "source": { + "data_returned": { + "type": "integer", + "minimum": 0.0, + "title": "Data Returned", + "description": "An integer containing the total number of data resource objects returned for the current `filter` query, independent of pagination." + }, + "provider": { "allOf": [ { - "$ref": "#/components/schemas/ErrorSource" + "$ref": "#/components/schemas/Provider" } ], - "title": "Source", - "description": "An object containing references to the source of the error" + "title": "Provider", + "description": "information on the database provider of the implementation." }, - "meta": { + "data_available": { + "type": "integer", + "title": "Data Available", + "description": "An integer containing the total number of data resource objects available in the database for the endpoint." + }, + "last_id": { + "type": "string", + "title": "Last Id", + "description": "a string containing the last ID returned" + }, + "response_message": { + "type": "string", + "title": "Response Message", + "description": "response string from the server" + }, + "implementation": { "allOf": [ { - "$ref": "#/components/schemas/Meta" + "$ref": "#/components/schemas/Implementation" } ], - "title": "Meta", - "description": "a meta object containing non-standard meta-information about the error." + "title": "Implementation", + "description": "a dictionary describing the server implementation" + }, + "warnings": { + "items": { + "$ref": "#/components/schemas/Warnings" + }, + "type": "array", + "uniqueItems": true, + "title": "Warnings", + "description": "A list of warning resource objects representing non-critical errors or warnings.\nA warning resource object is defined similarly to a [JSON API error object](http://jsonapi.org/format/1.0/#error-objects), but MUST also include the field `type`, which MUST have the value `\"warning\"`.\nThe field `detail` MUST be present and SHOULD contain a non-critical message, e.g., reporting unrecognized search attributes or deprecated features.\nThe field `status`, representing a HTTP response status code, MUST NOT be present for a warning resource object.\nThis is an exclusive field for error resource objects." } }, "type": "object", "required": [ - "detail" + "query", + "api_version", + "more_data_available" ], - "title": "OptimadeError", - "description": "detail MUST be present" + "title": "ResponseMeta", + "description": "A [JSON API meta member](https://jsonapi.org/format/1.0#document-meta)\nthat contains JSON API meta objects of non-standard\nmeta-information.\n\nOPTIONAL additional information global to the query that is not\nspecified in this document, MUST start with a\ndatabase-provider-specific prefix." }, - "Periodicity": { - "type": "integer", - "enum": [ - 0, - 1 + "ResponseMetaQuery": { + "properties": { + "representation": { + "type": "string", + "title": "Representation", + "description": "A string with the part of the URL following the versioned or unversioned base URL that serves the API.\nQuery parameters that have not been used in processing the request MAY be omitted.\nIn particular, if no query parameters have been involved in processing the request, the query part of the URL MAY be excluded.\nExample: `/structures?filter=nelements=2`" + } + }, + "type": "object", + "required": [ + "representation" ], - "title": "Periodicity", - "description": "Integer enumeration of dimension_types values" + "title": "ResponseMetaQuery", + "description": "Information on the query that was requested." }, - "Person": { + "Species": { "properties": { "name": { "type": "string", "title": "Name", - "description": "Full name of the person, REQUIRED.", + "description": "Gives the name of the species; the **name** value MUST be unique in the `species` list.", "x-optimade-support": "must", "x-optimade-queryable": "optional" }, - "firstname": { - "type": "string", - "title": "Firstname", - "description": "First name of the person.", - "x-optimade-support": "optional", + "chemical_symbols": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Chemical Symbols", + "description": "MUST be a list of strings of all chemical elements composing this species. Each item of the list MUST be one of the following:\n\n- a valid chemical-element symbol, or\n- the special value `\"X\"` to represent a non-chemical element, or\n- the special value `\"vacancy\"` to represent that this site has a non-zero probability of having a vacancy (the respective probability is indicated in the `concentration` list, see below).\n\nIf any one entry in the `species` list has a `chemical_symbols` list that is longer than 1 element, the correct flag MUST be set in the list `structure_features`.", + "x-optimade-support": "must", "x-optimade-queryable": "optional" }, - "lastname": { - "type": "string", - "title": "Lastname", - "description": "Last name of the person.", + "concentration": { + "items": { + "type": "number" + }, + "type": "array", + "title": "Concentration", + "description": "MUST be a list of floats, with same length as `chemical_symbols`. The numbers represent the relative concentration of the corresponding chemical symbol in this species. The numbers SHOULD sum to one. Cases in which the numbers do not sum to one typically fall only in the following two categories:\n\n- Numerical errors when representing float numbers in fixed precision, e.g. for two chemical symbols with concentrations `1/3` and `2/3`, the concentration might look something like `[0.33333333333, 0.66666666666]`. If the client is aware that the sum is not one because of numerical precision, it can renormalize the values so that the sum is exactly one.\n- Experimental errors in the data present in the database. In this case, it is the responsibility of the client to decide how to process the data.\n\nNote that concentrations are uncorrelated between different site (even of the same species).", + "x-optimade-support": "must", + "x-optimade-queryable": "optional" + }, + "mass": { + "items": { + "type": "number" + }, + "type": "array", + "title": "Mass", + "description": "If present MUST be a list of floats expressed in a.m.u.\nElements denoting vacancies MUST have masses equal to 0.", "x-optimade-support": "optional", + "x-optimade-unit": "a.m.u.", "x-optimade-queryable": "optional" - } - }, - "type": "object", - "required": [ - "name" - ], - "title": "Person", - "description": "A person, i.e., an author, editor or other." - }, - "Provider": { - "properties": { - "name": { - "type": "string", - "title": "Name", - "description": "a short name for the database provider" }, - "description": { + "original_name": { "type": "string", - "title": "Description", - "description": "a longer description of the database provider" + "title": "Original Name", + "description": "Can be any valid Unicode string, and SHOULD contain (if specified) the name of the species that is used internally in the source database.\n\nNote: With regards to \"source database\", we refer to the immediate source being queried via the OPTIMADE API implementation.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" }, - "prefix": { - "type": "string", - "pattern": "^[a-z]([a-z]|[0-9]|_)*$", - "title": "Prefix", - "description": "database-provider-specific prefix as found in section Database-Provider-Specific Namespace Prefixes." + "attached": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Attached", + "description": "If provided MUST be a list of length 1 or more of strings of chemical symbols for the elements attached to this site, or \"X\" for a non-chemical element.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" }, - "homepage": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, - { - "$ref": "#/components/schemas/Link" - } - ], - "title": "Homepage", - "description": "a [JSON API links object](http://jsonapi.org/format/1.0#document-links) pointing to homepage of the database provider, either directly as a string, or as a link object." + "nattached": { + "items": { + "type": "integer" + }, + "type": "array", + "title": "Nattached", + "description": "If provided MUST be a list of length 1 or more of integers indicating the number of attached atoms of the kind specified in the value of the :field:`attached` key.", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" } }, "type": "object", "required": [ "name", - "description", - "prefix" + "chemical_symbols", + "concentration" ], - "title": "Provider", - "description": "Information on the database provider of the implementation." + "title": "Species", + "description": "A list describing the species of the sites of this structure.\n\nSpecies can represent pure chemical elements, virtual-crystal atoms representing a\nstatistical occupation of a given site by multiple chemical elements, and/or a\nlocation to which there are attached atoms, i.e., atoms whose precise location are\nunknown beyond that they are attached to that position (frequently used to indicate\nhydrogen atoms attached to another element, e.g., a carbon with three attached\nhydrogens might represent a methyl group, -CH3).\n\n- **Examples**:\n - `[ {\"name\": \"Ti\", \"chemical_symbols\": [\"Ti\"], \"concentration\": [1.0]} ]`: any site with this species is occupied by a Ti atom.\n - `[ {\"name\": \"Ti\", \"chemical_symbols\": [\"Ti\", \"vacancy\"], \"concentration\": [0.9, 0.1]} ]`: any site with this species is occupied by a Ti atom with 90 % probability, and has a vacancy with 10 % probability.\n - `[ {\"name\": \"BaCa\", \"chemical_symbols\": [\"vacancy\", \"Ba\", \"Ca\"], \"concentration\": [0.05, 0.45, 0.5], \"mass\": [0.0, 137.327, 40.078]} ]`: any site with this species is occupied by a Ba atom with 45 % probability, a Ca atom with 50 % probability, and by a vacancy with 5 % probability. The mass of this site is (on average) 88.5 a.m.u.\n - `[ {\"name\": \"C12\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"mass\": [12.0]} ]`: any site with this species is occupied by a carbon isotope with mass 12.\n - `[ {\"name\": \"C13\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"mass\": [13.0]} ]`: any site with this species is occupied by a carbon isotope with mass 13.\n - `[ {\"name\": \"CH3\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"attached\": [\"H\"], \"nattached\": [3]} ]`: any site with this species is occupied by a methyl group, -CH3, which is represented without specifying precise positions of the hydrogen atoms." }, - "ReferenceRelationship": { + "StructureFeatures": { + "enum": [ + "disorder", + "implicit_atoms", + "site_attachments", + "assemblies" + ], + "title": "StructureFeatures", + "description": "Enumeration of structure_features values" + }, + "StructureRelationship": { "properties": { "links": { "allOf": [ @@ -2623,10 +4112,10 @@ } }, "type": "object", - "title": "ReferenceRelationship", + "title": "StructureRelationship", "description": "Similar to normal JSON API relationship, but with addition of OPTIONAL meta field for a resource." }, - "ReferenceResource": { + "StructureResource": { "properties": { "id": { "type": "string", @@ -2637,10 +4126,10 @@ }, "type": { "type": "string", - "pattern": "^references$", + "pattern": "^structures$", "title": "Type", - "description": "The name of the type of an entry.\n- **Type**: string.\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 and ID MUST be returned in response to a request for `//` under the versioned base URL.\n- **Example**: `\"structures\"`", - "default": "references", + "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 `` and ID `` MUST be returned in response to a request for `//` under the versioned base URL.\n\n- **Examples**:\n - `\"structures\"`", + "default": "structures", "x-optimade-support": "must", "x-optimade-queryable": "must" }, @@ -2656,14 +4145,14 @@ "meta": { "allOf": [ { - "$ref": "#/components/schemas/Meta" + "$ref": "#/components/schemas/EntryMetadata" } ], "title": "Meta", - "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." + "description": "A [JSON API meta object](https://jsonapi.org/format/1.1/#document-meta) that is used to communicate metadata." }, "attributes": { - "$ref": "#/components/schemas/ReferenceResourceAttributes" + "$ref": "#/components/schemas/StructureResourceAttributes" }, "relationships": { "allOf": [ @@ -2681,232 +4170,227 @@ "type", "attributes" ], - "title": "ReferenceResource", - "description": "The `references` entries describe bibliographic references.\n\nThe following properties are used to provide the bibliographic details:\n\n- **address**, **annote**, **booktitle**, **chapter**, **crossref**, **edition**, **howpublished**, **institution**, **journal**, **key**, **month**, **note**, **number**, **organization**, **pages**, **publisher**, **school**, **series**, **title**, **volume**, **year**: meanings of these properties match the [BibTeX specification](http://bibtexml.sourceforge.net/btxdoc.pdf), values are strings;\n- **bib_type**: type of the reference, corresponding to **type** property in the BibTeX specification, value is string;\n- **authors** and **editors**: lists of *person objects* which are dictionaries with the following keys:\n - **name**: Full name of the person, REQUIRED.\n - **firstname**, **lastname**: Parts of the person's name, OPTIONAL.\n- **doi** and **url**: values are strings.\n- **Requirements/Conventions**:\n - **Support**: OPTIONAL support in implementations, i.e., any of the properties MAY be `null`.\n - **Query**: Support for queries on any of these properties is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - Every references entry MUST contain at least one of the properties." + "title": "StructureResource", + "description": "Representing a structure." }, - "ReferenceResourceAttributes": { + "StructureResourceAttributes": { "properties": { - "immutable_id": { - "type": "string", - "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-support": "optional", - "x-optimade-queryable": "must" - }, - "last_modified": { - "type": "string", - "format": "date-time", - "title": "Last Modified", - "description": "Date and time representing when the entry was last modified.\n\n- **Type**: timestamp.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response unless the query parameter `response_fields` is present and does not include this property.\n\n- **Example**:\n - As part of JSON response format: `\"2007-04-05T14:30:20Z\"` (i.e., encoded as an [RFC 3339 Internet Date/Time Format](https://tools.ietf.org/html/rfc3339#section-5.6) string.)", - "x-optimade-support": "should", - "x-optimade-queryable": "must" - }, - "authors": { - "items": { - "$ref": "#/components/schemas/Person" - }, - "type": "array", - "title": "Authors", - "description": "List of person objects containing the authors of the reference.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" - }, - "editors": { + "elements": { "items": { - "$ref": "#/components/schemas/Person" + "type": "string" }, "type": "array", - "title": "Editors", - "description": "List of person objects containing the editors of the reference.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" - }, - "doi": { - "type": "string", - "title": "Doi", - "description": "The digital object identifier of the reference.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" - }, - "url": { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri", - "title": "Url", - "description": "The URL of the reference.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" - }, - "address": { - "type": "string", - "title": "Address", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" - }, - "annote": { - "type": "string", - "title": "Annote", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" - }, - "booktitle": { - "type": "string", - "title": "Booktitle", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" - }, - "chapter": { - "type": "string", - "title": "Chapter", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" - }, - "crossref": { - "type": "string", - "title": "Crossref", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "title": "Elements", + "description": "The chemical symbols of the different elements present in the structure.\n\n- **Type**: list of strings.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - The strings are the chemical symbols, i.e., either a single uppercase letter or an uppercase letter followed by a number of lowercase letters.\n - The order MUST be alphabetical.\n - MUST refer to the same elements in the same order, and therefore be of the same length, as `elements_ratios`, if the latter is provided.\n - Note: This property SHOULD NOT contain the string \"X\" to indicate non-chemical elements or \"vacancy\" to indicate vacancies (in contrast to the field `chemical_symbols` for the `species` property).\n\n- **Examples**:\n - `[\"Si\"]`\n - `[\"Al\",\"O\",\"Si\"]`\n\n- **Query examples**:\n - A filter that matches all records of structures that contain Si, Al **and** O, and possibly other elements: `elements HAS ALL \"Si\", \"Al\", \"O\"`.\n - To match structures with exactly these three elements, use `elements HAS ALL \"Si\", \"Al\", \"O\" AND elements LENGTH 3`.\n - Note: length queries on this property can be equivalently formulated by filtering on the `nelements`_ property directly.", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" }, - "edition": { - "type": "string", - "title": "Edition", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "nelements": { + "type": "integer", + "title": "Nelements", + "description": "Number of different elements in the structure as an integer.\n\n- **Type**: integer\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - MUST be equal to the lengths of the list properties `elements` and `elements_ratios`, if they are provided.\n\n- **Examples**:\n - `3`\n\n- **Querying**:\n - Note: queries on this property can equivalently be formulated using `elements LENGTH`.\n - A filter that matches structures that have exactly 4 elements: `nelements=4`.\n - A filter that matches structures that have between 2 and 7 elements: `nelements>=2 AND nelements<=7`.", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" }, - "howpublished": { - "type": "string", - "title": "Howpublished", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "elements_ratios": { + "items": { + "type": "number" + }, + "type": "array", + "title": "Elements Ratios", + "description": "Relative proportions of different elements in the structure.\n\n- **Type**: list of floats\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - Composed by the proportions of elements in the structure as a list of floating point numbers.\n - The sum of the numbers MUST be 1.0 (within floating point accuracy)\n - MUST refer to the same elements in the same order, and therefore be of the same length, as `elements`, if the latter is provided.\n\n- **Examples**:\n - `[1.0]`\n - `[0.3333333333333333, 0.2222222222222222, 0.4444444444444444]`\n\n- **Query examples**:\n - Note: Useful filters can be formulated using the set operator syntax for correlated values.\n However, since the values are floating point values, the use of equality comparisons is generally inadvisable.\n - OPTIONAL: a filter that matches structures where approximately 1/3 of the atoms in the structure are the element Al is: `elements:elements_ratios HAS ALL \"Al\":>0.3333, \"Al\":<0.3334`.", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" }, - "institution": { + "chemical_formula_descriptive": { "type": "string", - "title": "Institution", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "title": "Chemical Formula Descriptive", + "description": "The chemical formula for a structure as a string in a form chosen by the API implementation.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - The chemical formula is given as a string consisting of properly capitalized element symbols followed by integers or decimal numbers, balanced parentheses, square, and curly brackets `(`,`)`, `[`,`]`, `{`, `}`, commas, the `+`, `-`, `:` and `=` symbols. The parentheses are allowed to be followed by a number. Spaces are allowed anywhere except within chemical symbols. The order of elements and any groupings indicated by parentheses or brackets are chosen freely by the API implementation.\n - The string SHOULD be arithmetically consistent with the element ratios in the `chemical_formula_reduced` property.\n - It is RECOMMENDED, but not mandatory, that symbols, parentheses and brackets, if used, are used with the meanings prescribed by [IUPAC's Nomenclature of Organic Chemistry](https://www.qmul.ac.uk/sbcs/iupac/bibliog/blue.html).\n\n- **Examples**:\n - `\"(H2O)2 Na\"`\n - `\"NaCl\"`\n - `\"CaCO3\"`\n - `\"CCaO3\"`\n - `\"(CH3)3N+ - [CH2]2-OH = Me3N+ - CH2 - CH2OH\"`\n\n- **Query examples**:\n - Note: the free-form nature of this property is likely to make queries on it across different databases inconsistent.\n - A filter that matches an exactly given formula: `chemical_formula_descriptive=\"(H2O)2 Na\"`.\n - A filter that does a partial match: `chemical_formula_descriptive CONTAINS \"H2O\"`.", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" }, - "journal": { + "chemical_formula_reduced": { "type": "string", - "title": "Journal", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "pattern": "(^$)|^([A-Z][a-z]?([2-9]|[1-9]\\d+)?)+$", + "title": "Chemical Formula Reduced", + "description": "The reduced chemical formula for a structure as a string with element symbols and integer chemical proportion numbers.\nThe proportion number MUST be omitted if it is 1.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property.\n However, support for filters using partial string matching with this property is OPTIONAL (i.e., BEGINS WITH, ENDS WITH, and CONTAINS).\n Intricate queries on formula components are instead suggested to be formulated using set-type filter operators on the multi valued `elements` and `elements_ratios` properties.\n - Element symbols MUST have proper capitalization (e.g., `\"Si\"`, not `\"SI\"` for \"silicon\").\n - Elements MUST be placed in alphabetical order, followed by their integer chemical proportion number.\n - For structures with no partial occupation, the chemical proportion numbers are the smallest integers for which the chemical proportion is exactly correct.\n - For structures with partial occupation, the chemical proportion numbers are integers that within reasonable approximation indicate the correct chemical proportions. The precise details of how to perform the rounding is chosen by the API implementation.\n - No spaces or separators are allowed.\n\n- **Examples**:\n - `\"H2NaO\"`\n - `\"ClNa\"`\n - `\"CCaO3\"`\n\n- **Query examples**:\n - A filter that matches an exactly given formula is `chemical_formula_reduced=\"H2NaO\"`.", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" }, - "key": { + "chemical_formula_hill": { "type": "string", - "title": "Key", - "description": "Meaning of property matches the BiBTeX specification.", + "pattern": "(^$)|^([A-Z][a-z]?([2-9]|[1-9]\\d+)?)+$", + "title": "Chemical Formula Hill", + "description": "The chemical formula for a structure in [Hill form](https://dx.doi.org/10.1021/ja02046a005) with element symbols followed by integer chemical proportion numbers. The proportion number MUST be omitted if it is 1.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n - **Support**: OPTIONAL support in implementations, i.e., MAY be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, only a subset of the filter features MAY be supported.\n - The overall scale factor of the chemical proportions is chosen such that the resulting values are integers that indicate the most chemically relevant unit of which the system is composed.\n For example, if the structure is a repeating unit cell with four hydrogens and four oxygens that represents two hydroperoxide molecules, `chemical_formula_hill` is `\"H2O2\"` (i.e., not `\"HO\"`, nor `\"H4O4\"`).\n - If the chemical insight needed to ascribe a Hill formula to the system is not present, the property MUST be handled as unset.\n - Element symbols MUST have proper capitalization (e.g., `\"Si\"`, not `\"SI\"` for \"silicon\").\n - Elements MUST be placed in [Hill order](https://dx.doi.org/10.1021/ja02046a005), followed by their integer chemical proportion number.\n Hill order means: if carbon is present, it is placed first, and if also present, hydrogen is placed second.\n After that, all other elements are ordered alphabetically.\n If carbon is not present, all elements are ordered alphabetically.\n - If the system has sites with partial occupation and the total occupations of each element do not all sum up to integers, then the Hill formula SHOULD be handled as unset.\n - No spaces or separators are allowed.\n\n- **Examples**:\n - `\"H2O2\"`\n\n- **Query examples**:\n - A filter that matches an exactly given formula is `chemical_formula_hill=\"H2O2\"`.", "x-optimade-support": "optional", "x-optimade-queryable": "optional" }, - "month": { + "chemical_formula_anonymous": { "type": "string", - "title": "Month", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "pattern": "(^$)|^([A-Z][a-z]?([2-9]|[1-9]\\d+)?)+$", + "title": "Chemical Formula Anonymous", + "description": "The anonymous formula is the `chemical_formula_reduced`, but where the elements are instead first ordered by their chemical proportion number, and then, in order left to right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and so on.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property.\n However, support for filters using partial string matching with this property is OPTIONAL (i.e., BEGINS WITH, ENDS WITH, and CONTAINS).\n\n- **Examples**:\n - `\"A2B\"`\n - `\"A42B42C16D12E10F9G5\"`\n\n- **Querying**:\n - A filter that matches an exactly given formula is `chemical_formula_anonymous=\"A2B\"`.", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" }, - "note": { - "type": "string", - "title": "Note", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", + "dimension_types": { + "items": { + "$ref": "#/components/schemas/Periodicity" + }, + "type": "array", + "maxItems": 3, + "minItems": 3, + "title": "Dimension Types", + "description": "List of three integers.\nFor each of the three directions indicated by the three lattice vectors (see property `lattice_vectors`), this list indicates if the direction is periodic (value `1`) or non-periodic (value `0`).\nNote: the elements in this list each refer to the direction of the corresponding entry in `lattice_vectors` and *not* the Cartesian x, y, z directions.\n\n- **Type**: list of integers.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n - MUST be a list of length 3.\n - Each integer element MUST assume only the value 0 or 1.\n\n- **Examples**:\n - For a molecule: `[0, 0, 0]`\n - For a wire along the direction specified by the third lattice vector: `[0, 0, 1]`\n - For a 2D surface/slab, periodic on the plane defined by the first and third lattice vectors: `[1, 0, 1]`\n - For a bulk 3D system: `[1, 1, 1]`", + "x-optimade-support": "should", + "nullable": true, "x-optimade-queryable": "optional" }, - "number": { - "type": "string", - "title": "Number", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "nperiodic_dimensions": { + "type": "integer", + "title": "Nperiodic Dimensions", + "description": "An integer specifying the number of periodic dimensions in the structure, equivalent to the number of non-zero entries in `dimension_types`.\n\n- **Type**: integer\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - The integer value MUST be between 0 and 3 inclusive and MUST be equal to the sum of the items in the `dimension_types` property.\n - This property only reflects the treatment of the lattice vectors provided for the structure, and not any physical interpretation of the dimensionality of its contents.\n\n- **Examples**:\n - `2` should be indicated in cases where `dimension_types` is any of `[1, 1, 0]`, `[1, 0, 1]`, `[0, 1, 1]`.\n\n- **Query examples**:\n - Match only structures with exactly 3 periodic dimensions: `nperiodic_dimensions=3`\n - Match all structures with 2 or fewer periodic dimensions: `nperiodic_dimensions<=2`", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" }, - "organization": { - "type": "string", - "title": "Organization", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "lattice_vectors": { + "items": { + "items": { + "type": "number" + }, + "type": "array", + "maxItems": 3, + "minItems": 3 + }, + "type": "array", + "maxItems": 3, + "minItems": 3, + "title": "Lattice Vectors", + "description": "The three lattice vectors in Cartesian coordinates, in \u00e5ngstr\u00f6m (\u00c5).\n\n- **Type**: list of list of floats or unknown values.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - MUST be a list of three vectors *a*, *b*, and *c*, where each of the vectors MUST BE a list of the vector's coordinates along the x, y, and z Cartesian coordinates.\n (Therefore, the first index runs over the three lattice vectors and the second index runs over the x, y, z Cartesian coordinates).\n - For databases that do not define an absolute Cartesian system (e.g., only defining the length and angles between vectors), the first lattice vector SHOULD be set along *x* and the second on the *xy*-plane.\n - MUST always contain three vectors of three coordinates each, independently of the elements of property `dimension_types`.\n The vectors SHOULD by convention be chosen so the determinant of the `lattice_vectors` matrix is different from zero.\n The vectors in the non-periodic directions have no significance beyond fulfilling these requirements.\n - The coordinates of the lattice vectors of non-periodic dimensions (i.e., those dimensions for which `dimension_types` is `0`) MAY be given as a list of all `null` values.\n If a lattice vector contains the value `null`, all coordinates of that lattice vector MUST be `null`.\n\n- **Examples**:\n - `[[4.0,0.0,0.0],[0.0,4.0,0.0],[0.0,1.0,4.0]]` represents a cell, where the first vector is `(4, 0, 0)`, i.e., a vector aligned along the `x` axis of length 4 \u00c5; the second vector is `(0, 4, 0)`; and the third vector is `(0, 1, 4)`.", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "optional", + "x-optimade-unit": "\u00c5" }, - "pages": { - "type": "string", - "title": "Pages", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "cartesian_site_positions": { + "items": { + "items": { + "type": "number" + }, + "type": "array", + "maxItems": 3, + "minItems": 3 + }, + "type": "array", + "title": "Cartesian Site Positions", + "description": "Cartesian positions of each site in the structure.\nA site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the `species_at_sites` property, and the species themselves are described in the `species` property.\n\n- **Type**: list of list of floats\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - It MUST be a list of length equal to the number of sites in the structure, where every element is a list of the three Cartesian coordinates of a site expressed as float values in the unit angstrom (\u00c5).\n - An entry MAY have multiple sites at the same Cartesian position (for a relevant use of this, see e.g., the property `assemblies`).\n\n- **Examples**:\n - `[[0,0,0],[0,0,2]]` indicates a structure with two sites, one sitting at the origin and one along the (positive) *z*-axis, 2 \u00c5 away from the origin.", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "optional", + "x-optimade-unit": "\u00c5" }, - "publisher": { - "type": "string", - "title": "Publisher", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "nsites": { + "type": "integer", + "title": "Nsites", + "description": "An integer specifying the length of the `cartesian_site_positions` property.\n\n- **Type**: integer\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n\n- **Examples**:\n - `42`\n\n- **Query examples**:\n - Match only structures with exactly 4 sites: `nsites=4`\n - Match structures that have between 2 and 7 sites: `nsites>=2 AND nsites<=7`", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" }, - "school": { - "type": "string", - "title": "School", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", + "species": { + "items": { + "$ref": "#/components/schemas/Species" + }, + "type": "array", + "title": "Species", + "description": "A list describing the species of the sites of this structure.\nSpecies can represent pure chemical elements, virtual-crystal atoms representing a statistical occupation of a given site by multiple chemical elements, and/or a location to which there are attached atoms, i.e., atoms whose precise location are unknown beyond that they are attached to that position (frequently used to indicate hydrogen atoms attached to another element, e.g., a carbon with three attached hydrogens might represent a methyl group, -CH3).\n\n- **Type**: list of dictionary with keys:\n - `name`: string (REQUIRED)\n - `chemical_symbols`: list of strings (REQUIRED)\n - `concentration`: list of float (REQUIRED)\n - `attached`: list of strings (REQUIRED)\n - `nattached`: list of integers (OPTIONAL)\n - `mass`: list of floats (OPTIONAL)\n - `original_name`: string (OPTIONAL).\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - Each list member MUST be a dictionary with the following keys:\n - **name**: REQUIRED; gives the name of the species; the **name** value MUST be unique in the `species` list;\n - **chemical_symbols**: REQUIRED; MUST be a list of strings of all chemical elements composing this species.\n Each item of the list MUST be one of the following:\n - a valid chemical-element symbol, or\n - the special value `\"X\"` to represent a non-chemical element, or\n - the special value `\"vacancy\"` to represent that this site has a non-zero probability of having a vacancy (the respective probability is indicated in the `concentration` list, see below).\n\n If any one entry in the `species` list has a `chemical_symbols` list that is longer than 1 element, the correct flag MUST be set in the list `structure_features`.\n\n - **concentration**: REQUIRED; MUST be a list of floats, with same length as `chemical_symbols`.\n The numbers represent the relative concentration of the corresponding chemical symbol in this species.\n The numbers SHOULD sum to one. Cases in which the numbers do not sum to one typically fall only in the following two categories:\n\n - Numerical errors when representing float numbers in fixed precision, e.g. for two chemical symbols with concentrations `1/3` and `2/3`, the concentration might look something like `[0.33333333333, 0.66666666666]`. If the client is aware that the sum is not one because of numerical precision, it can renormalize the values so that the sum is exactly one.\n - Experimental errors in the data present in the database. In this case, it is the responsibility of the client to decide how to process the data.\n\n Note that concentrations are uncorrelated between different sites (even of the same species).\n\n - **attached**: OPTIONAL; if provided MUST be a list of length 1 or more of strings of chemical symbols for the elements attached to this site, or \"X\" for a non-chemical element.\n\n - **nattached**: OPTIONAL; if provided MUST be a list of length 1 or more of integers indicating the number of attached atoms of the kind specified in the value of the `attached` key.\n\n The implementation MUST include either both or none of the `attached` and `nattached` keys, and if they are provided, they MUST be of the same length.\n Furthermore, if they are provided, the `structure_features` property MUST include the string `site_attachments`.\n\n - **mass**: OPTIONAL. If present MUST be a list of floats, with the same length as `chemical_symbols`, providing element masses expressed in a.m.u.\n Elements denoting vacancies MUST have masses equal to 0.\n\n - **original_name**: OPTIONAL. Can be any valid Unicode string, and SHOULD contain (if specified) the name of the species that is used internally in the source database.\n\n Note: With regards to \"source database\", we refer to the immediate source being queried via the OPTIMADE API implementation.\n\n The main use of this field is for source databases that use species names, containing characters that are not allowed (see description of the list property `species_at_sites`).\n\n - For systems that have only species formed by a single chemical symbol, and that have at most one species per chemical symbol, SHOULD use the chemical symbol as species name (e.g., `\"Ti\"` for titanium, `\"O\"` for oxygen, etc.)\n However, note that this is OPTIONAL, and client implementations MUST NOT assume that the key corresponds to a chemical symbol, nor assume that if the species name is a valid chemical symbol, that it represents a species with that chemical symbol.\n This means that a species `{\"name\": \"C\", \"chemical_symbols\": [\"Ti\"], \"concentration\": [1.0]}` is valid and represents a titanium species (and *not* a carbon species).\n - It is NOT RECOMMENDED that a structure includes species that do not have at least one corresponding site.\n\n- **Examples**:\n - `[ {\"name\": \"Ti\", \"chemical_symbols\": [\"Ti\"], \"concentration\": [1.0]} ]`: any site with this species is occupied by a Ti atom.\n - `[ {\"name\": \"Ti\", \"chemical_symbols\": [\"Ti\", \"vacancy\"], \"concentration\": [0.9, 0.1]} ]`: any site with this species is occupied by a Ti atom with 90 % probability, and has a vacancy with 10 % probability.\n - `[ {\"name\": \"BaCa\", \"chemical_symbols\": [\"vacancy\", \"Ba\", \"Ca\"], \"concentration\": [0.05, 0.45, 0.5], \"mass\": [0.0, 137.327, 40.078]} ]`: any site with this species is occupied by a Ba atom with 45 % probability, a Ca atom with 50 % probability, and by a vacancy with 5 % probability. The mass of this site is (on average) 88.5 a.m.u.\n - `[ {\"name\": \"C12\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"mass\": [12.0]} ]`: any site with this species is occupied by a carbon isotope with mass 12.\n - `[ {\"name\": \"C13\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"mass\": [13.0]} ]`: any site with this species is occupied by a carbon isotope with mass 13.\n - `[ {\"name\": \"CH3\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"attached\": [\"H\"], \"nattached\": [3]} ]`: any site with this species is occupied by a methyl group, -CH3, which is represented without specifying precise positions of the hydrogen atoms.", + "x-optimade-support": "should", + "nullable": true, "x-optimade-queryable": "optional" }, - "series": { - "type": "string", - "title": "Series", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", + "species_at_sites": { + "items": { + "type": "string" + }, + "type": "array", + "title": "Species At Sites", + "description": "Name of the species at each site (where values for sites are specified with the same order of the property `cartesian_site_positions`).\nThe properties of the species are found in the property `species`.\n\n- **Type**: list of strings.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - MUST have length equal to the number of sites in the structure (first dimension of the list property `cartesian_site_positions`).\n - Each species name mentioned in the `species_at_sites` list MUST be described in the list property `species` (i.e. for each value in the `species_at_sites` list there MUST exist exactly one dictionary in the `species` list with the `name` attribute equal to the corresponding `species_at_sites` value).\n - Each site MUST be associated only to a single species.\n **Note**: However, species can represent mixtures of atoms, and multiple species MAY be defined for the same chemical element.\n This latter case is useful when different atoms of the same type need to be grouped or distinguished, for instance in simulation codes to assign different initial spin states.\n\n- **Examples**:\n - `[\"Ti\",\"O2\"]` indicates that the first site is hosting a species labeled `\"Ti\"` and the second a species labeled `\"O2\"`.\n - `[\"Ac\", \"Ac\", \"Ag\", \"Ir\"]` indicating the first two sites contains the `\"Ac\"` species, while the third and fourth sites contain the `\"Ag\"` and `\"Ir\"` species, respectively.", + "x-optimade-support": "should", + "nullable": true, "x-optimade-queryable": "optional" }, - "title": { - "type": "string", - "title": "Title", - "description": "Meaning of property matches the BiBTeX specification.", + "assemblies": { + "items": { + "$ref": "#/components/schemas/Assembly" + }, + "type": "array", + "title": "Assemblies", + "description": "A description of groups of sites that are statistically correlated.\n\n- **Type**: list of dictionary with keys:\n - `sites_in_groups`: list of list of integers (REQUIRED)\n - `group_probabilities`: list of floats (REQUIRED)\n\n- **Requirements/Conventions**:\n - **Support**: OPTIONAL support in implementations, i.e., MAY be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - The property SHOULD be `null` for entries that have no partial occupancies.\n - If present, the correct flag MUST be set in the list `structure_features`.\n - Client implementations MUST check its presence (as its presence changes the interpretation of the structure).\n - If present, it MUST be a list of dictionaries, each of which represents an assembly and MUST have the following two keys:\n - **sites_in_groups**: Index of the sites (0-based) that belong to each group for each assembly.\n\n Example: `[[1], [2]]`: two groups, one with the second site, one with the third.\n Example: `[[1,2], [3]]`: one group with the second and third site, one with the fourth.\n\n - **group_probabilities**: Statistical probability of each group. It MUST have the same length as `sites_in_groups`.\n It SHOULD sum to one.\n See below for examples of how to specify the probability of the occurrence of a vacancy.\n The possible reasons for the values not to sum to one are the same as already specified above for the `concentration` of each `species`.\n\n - If a site is not present in any group, it means that it is present with 100 % probability (as if no assembly was specified).\n - A site MUST NOT appear in more than one group.\n\n- **Examples** (for each entry of the assemblies list):\n - `{\"sites_in_groups\": [[0], [1]], \"group_probabilities: [0.3, 0.7]}`: the first site and the second site never occur at the same time in the unit cell.\n Statistically, 30 % of the times the first site is present, while 70 % of the times the second site is present.\n - `{\"sites_in_groups\": [[1,2], [3]], \"group_probabilities: [0.3, 0.7]}`: the second and third site are either present together or not present; they form the first group of atoms for this assembly.\n The second group is formed by the fourth site.\n Sites of the first group (the second and the third) are never present at the same time as the fourth site.\n 30 % of times sites 1 and 2 are present (and site 3 is absent); 70 % of times site 3 is present (and sites 1 and 2 are absent).\n\n- **Notes**:\n - Assemblies are essential to represent, for instance, the situation where an atom can statistically occupy two different positions (sites).\n\n - By defining groups, it is possible to represent, e.g., the case where a functional molecule (and not just one atom) is either present or absent (or the case where it it is present in two conformations)\n\n - Considerations on virtual alloys and on vacancies: In the special case of a virtual alloy, these specifications allow two different, equivalent ways of specifying them.\n For instance, for a site at the origin with 30 % probability of being occupied by Si, 50 % probability of being occupied by Ge, and 20 % of being a vacancy, the following two representations are possible:\n\n - Using a single species:\n ```json\n {\n \"cartesian_site_positions\": [[0,0,0]],\n \"species_at_sites\": [\"SiGe-vac\"],\n \"species\": [\n {\n \"name\": \"SiGe-vac\",\n \"chemical_symbols\": [\"Si\", \"Ge\", \"vacancy\"],\n \"concentration\": [0.3, 0.5, 0.2]\n }\n ]\n // ...\n }\n ```\n\n - Using multiple species and the assemblies:\n ```json\n {\n \"cartesian_site_positions\": [ [0,0,0], [0,0,0], [0,0,0] ],\n \"species_at_sites\": [\"Si\", \"Ge\", \"vac\"],\n \"species\": [\n { \"name\": \"Si\", \"chemical_symbols\": [\"Si\"], \"concentration\": [1.0] },\n { \"name\": \"Ge\", \"chemical_symbols\": [\"Ge\"], \"concentration\": [1.0] },\n { \"name\": \"vac\", \"chemical_symbols\": [\"vacancy\"], \"concentration\": [1.0] }\n ],\n \"assemblies\": [\n {\n \"sites_in_groups\": [ [0], [1], [2] ],\n \"group_probabilities\": [0.3, 0.5, 0.2]\n }\n ]\n // ...\n }\n ```\n\n - It is up to the database provider to decide which representation to use, typically depending on the internal format in which the structure is stored.\n However, given a structure identified by a unique ID, the API implementation MUST always provide the same representation for it.\n\n - The probabilities of occurrence of different assemblies are uncorrelated.\n So, for instance in the following case with two assemblies:\n ```json\n {\n \"assemblies\": [\n {\n \"sites_in_groups\": [ [0], [1] ],\n \"group_probabilities\": [0.2, 0.8],\n },\n {\n \"sites_in_groups\": [ [2], [3] ],\n \"group_probabilities\": [0.3, 0.7]\n }\n ]\n }\n ```\n\n Site 0 is present with a probability of 20 % and site 1 with a probability of 80 %. These two sites are correlated (either site 0 or 1 is present). Similarly, site 2 is present with a probability of 30 % and site 3 with a probability of 70 %.\n These two sites are correlated (either site 2 or 3 is present).\n However, the presence or absence of sites 0 and 1 is not correlated with the presence or absence of sites 2 and 3 (in the specific example, the pair of sites (0, 2) can occur with 0.2*0.3 = 6 % probability; the pair (0, 3) with 0.2*0.7 = 14 % probability; the pair (1, 2) with 0.8*0.3 = 24 % probability; and the pair (1, 3) with 0.8*0.7 = 56 % probability).", "x-optimade-support": "optional", "x-optimade-queryable": "optional" }, - "bib_type": { - "type": "string", - "title": "Bib Type", - "description": "Type of the reference, corresponding to the **type** property in the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "structure_features": { + "items": { + "$ref": "#/components/schemas/StructureFeatures" + }, + "type": "array", + "title": "Structure Features", + "description": "A list of strings that flag which special features are used by the structure.\n\n- **Type**: list of strings\n\n- **Requirements/Conventions**:\n - **Support**: MUST be supported by all implementations, MUST NOT be `null`.\n - **Query**: MUST be a queryable property.\n Filters on the list MUST support all mandatory HAS-type queries.\n Filter operators for comparisons on the string components MUST support equality, support for other comparison operators are OPTIONAL.\n - MUST be an empty list if no special features are used.\n - MUST be sorted alphabetically.\n - If a special feature listed below is used, the list MUST contain the corresponding string.\n - If a special feature listed below is not used, the list MUST NOT contain the corresponding string.\n - **List of strings used to indicate special structure features**:\n - `disorder`: this flag MUST be present if any one entry in the `species` list has a `chemical_symbols` list that is longer than 1 element.\n - `implicit_atoms`: this flag MUST be present if the structure contains atoms that are not assigned to sites via the property `species_at_sites` (e.g., because their positions are unknown).\n When this flag is present, the properties related to the chemical formula will likely not match the type and count of atoms represented by the `species_at_sites`, `species` and `assemblies` properties.\n - `site_attachments`: this flag MUST be present if any one entry in the `species` list includes `attached` and `nattached`.\n - `assemblies`: this flag MUST be present if the property `assemblies` is present.\n\n- **Examples**: A structure having implicit atoms and using assemblies: `[\"assemblies\", \"implicit_atoms\"]`", + "x-optimade-support": "must", + "x-optimade-queryable": "must" }, - "volume": { + "immutable_id": { "type": "string", - "title": "Volume", - "description": "Meaning of property matches the BiBTeX specification.", + "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-support": "optional", - "x-optimade-queryable": "optional" + "x-optimade-queryable": "must" }, - "year": { + "last_modified": { "type": "string", - "title": "Year", - "description": "Meaning of property matches the BiBTeX specification.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "format": "date-time", + "title": "Last Modified", + "description": "Date and time representing when the entry was last modified.\n\n- **Type**: timestamp.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response unless the query parameter `response_fields` is present and does not include this property.\n\n- **Example**:\n - As part of JSON response format: `\"2007-04-05T14:30:20Z\"` (i.e., encoded as an [RFC 3339 Internet Date/Time Format](https://tools.ietf.org/html/rfc3339#section-5.6) string.)", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" } }, "type": "object", "required": [ + "elements", + "nelements", + "elements_ratios", + "chemical_formula_descriptive", + "chemical_formula_reduced", + "chemical_formula_anonymous", + "nperiodic_dimensions", + "dimension_types", + "cartesian_site_positions", + "lattice_vectors", + "nsites", + "species", + "species_at_sites", + "structure_features", "last_modified" ], - "title": "ReferenceResourceAttributes", - "description": "Model that stores the attributes of a reference.\n\nMany properties match the meaning described in the\n[BibTeX specification](http://bibtexml.sourceforge.net/btxdoc.pdf)." + "title": "StructureResourceAttributes", + "description": "Contains key-value pairs representing the entry's properties." }, - "ReferenceResponseMany": { + "StructureResponseMany": { "properties": { "data": { "anyOf": [ { "items": { - "$ref": "#/components/schemas/ReferenceResource" + "$ref": "#/components/schemas/StructureResource" }, "type": "array" }, @@ -2919,7 +4403,7 @@ ], "uniqueItems": true, "title": "Data", - "description": "List of unique OPTIMADE references entry resource objects." + "description": "List of unique OPTIMADE structures entry resource objects." }, "meta": { "allOf": [ @@ -2981,22 +4465,22 @@ "data", "meta" ], - "title": "ReferenceResponseMany", + "title": "StructureResponseMany", "description": "errors are not allowed" }, - "ReferenceResponseOne": { + "StructureResponseOne": { "properties": { "data": { "anyOf": [ { - "$ref": "#/components/schemas/ReferenceResource" + "$ref": "#/components/schemas/StructureResource" }, { "type": "object" } ], "title": "Data", - "description": "A single references entry resource." + "description": "A single structures entry resource." }, "meta": { "allOf": [ @@ -3058,10 +4542,10 @@ "data", "meta" ], - "title": "ReferenceResponseOne", + "title": "StructureResponseOne", "description": "errors are not allowed" }, - "RelationshipLinks": { + "ToplevelLinks": { "properties": { "self": { "anyOf": [ @@ -3076,7 +4560,7 @@ } ], "title": "Self", - "description": "A link for the relationship itself (a 'relationship link').\nThis link allows the client to directly manipulate the relationship.\nWhen fetched successfully, this link returns the [linkage](https://jsonapi.org/format/1.0/#document-resource-object-linkage) for the related resources as its primary data.\n(See [Fetching Relationships](https://jsonapi.org/format/1.0/#fetching-relationships).)" + "description": "A link to itself" }, "related": { "anyOf": [ @@ -3091,79 +4575,9 @@ } ], "title": "Related", - "description": "A [related resource link](https://jsonapi.org/format/1.0/#document-resource-object-related-resource-links)." - } - }, - "type": "object", - "title": "RelationshipLinks", - "description": "A resource object **MAY** contain references to other resource objects (\"relationships\").\nRelationships may be to-one or to-many.\nRelationships can be specified by including a member in a resource's links object." - }, - "Relationships": { - "properties": {}, - "type": "object", - "title": "Relationships", - "description": "Members of the relationships object (\"relationships\") represent references from the resource object in which it's defined to other resource objects.\nKeys MUST NOT be:\n type\n id" - }, - "Resource": { - "properties": { - "id": { - "type": "string", - "title": "Id", - "description": "Resource ID" - }, - "type": { - "type": "string", - "title": "Type", - "description": "Resource type" - }, - "links": { - "allOf": [ - { - "$ref": "#/components/schemas/ResourceLinks" - } - ], - "title": "Links", - "description": "a links object containing links related to the resource." - }, - "meta": { - "allOf": [ - { - "$ref": "#/components/schemas/Meta" - } - ], - "title": "Meta", - "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." - }, - "attributes": { - "allOf": [ - { - "$ref": "#/components/schemas/Attributes" - } - ], - "title": "Attributes", - "description": "an attributes object representing some of the resource\u2019s data." + "description": "A related resource link" }, - "relationships": { - "allOf": [ - { - "$ref": "#/components/schemas/Relationships" - } - ], - "title": "Relationships", - "description": "[Relationships object](https://jsonapi.org/format/1.0/#document-resource-object-relationships)\ndescribing relationships between the resource and other JSON API resources." - } - }, - "type": "object", - "required": [ - "id", - "type" - ], - "title": "Resource", - "description": "Resource objects appear in a JSON API document to represent resources." - }, - "ResourceLinks": { - "properties": { - "self": { + "first": { "anyOf": [ { "type": "string", @@ -3175,42 +4589,10 @@ "$ref": "#/components/schemas/Link" } ], - "title": "Self", - "description": "A link that identifies the resource represented by the resource object." - } - }, - "type": "object", - "title": "ResourceLinks", - "description": "A Resource Links object" - }, - "ResponseMeta": { - "properties": { - "query": { - "allOf": [ - { - "$ref": "#/components/schemas/ResponseMetaQuery" - } - ], - "title": "Query", - "description": "Information on the Query that was requested" - }, - "api_version": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", - "title": "Api Version", - "description": "Presently used full version of the OPTIMADE API.\nThe version number string MUST NOT be prefixed by, e.g., \"v\".\nExamples: `1.0.0`, `1.0.0-rc.2`.", - "example": [ - "0.10.1", - "1.0.0-rc.2", - "1.2.3-rc.5+develop" - ] - }, - "more_data_available": { - "type": "boolean", - "title": "More Data Available", - "description": "`false` if the response contains all data for the request (e.g., a request issued to a single entry endpoint, or a `filter` query at the last page of a paginated response) and `true` if the response is incomplete in the sense that multiple objects match the request, and not all of them have been included in the response (e.g., a query with multiple pages that is not at the last page)." + "title": "First", + "description": "The first page of data" }, - "schema": { + "last": { "anyOf": [ { "type": "string", @@ -3222,176 +4604,45 @@ "$ref": "#/components/schemas/Link" } ], - "title": "Schema", - "description": "A [JSON API links object](http://jsonapi.org/format/1.0/#document-links) that points to a schema for the response.\nIf it is a string, or a dictionary containing no `meta` field, the provided URL MUST point at an [OpenAPI](https://swagger.io/specification/) schema.\nIt is possible that future versions of this specification allows for alternative schema types.\nHence, if the `meta` field of the JSON API links object is provided and contains a field `schema_type` that is not equal to the string `OpenAPI` the client MUST not handle failures to parse the schema or to validate the response against the schema as errors." - }, - "time_stamp": { - "type": "string", - "format": "date-time", - "title": "Time Stamp", - "description": "A timestamp containing the date and time at which the query was executed." - }, - "data_returned": { - "type": "integer", - "minimum": 0.0, - "title": "Data Returned", - "description": "An integer containing the total number of data resource objects returned for the current `filter` query, independent of pagination." - }, - "provider": { - "allOf": [ - { - "$ref": "#/components/schemas/Provider" - } - ], - "title": "Provider", - "description": "information on the database provider of the implementation." - }, - "data_available": { - "type": "integer", - "title": "Data Available", - "description": "An integer containing the total number of data resource objects available in the database for the endpoint." - }, - "last_id": { - "type": "string", - "title": "Last Id", - "description": "a string containing the last ID returned" - }, - "response_message": { - "type": "string", - "title": "Response Message", - "description": "response string from the server" - }, - "implementation": { - "allOf": [ - { - "$ref": "#/components/schemas/Implementation" - } - ], - "title": "Implementation", - "description": "a dictionary describing the server implementation" - }, - "warnings": { - "items": { - "$ref": "#/components/schemas/Warnings" - }, - "type": "array", - "uniqueItems": true, - "title": "Warnings", - "description": "A list of warning resource objects representing non-critical errors or warnings.\nA warning resource object is defined similarly to a [JSON API error object](http://jsonapi.org/format/1.0/#error-objects), but MUST also include the field `type`, which MUST have the value `\"warning\"`.\nThe field `detail` MUST be present and SHOULD contain a non-critical message, e.g., reporting unrecognized search attributes or deprecated features.\nThe field `status`, representing a HTTP response status code, MUST NOT be present for a warning resource object.\nThis is an exclusive field for error resource objects." - } - }, - "type": "object", - "required": [ - "query", - "api_version", - "more_data_available" - ], - "title": "ResponseMeta", - "description": "A [JSON API meta member](https://jsonapi.org/format/1.0#document-meta)\nthat contains JSON API meta objects of non-standard\nmeta-information.\n\nOPTIONAL additional information global to the query that is not\nspecified in this document, MUST start with a\ndatabase-provider-specific prefix." - }, - "ResponseMetaQuery": { - "properties": { - "representation": { - "type": "string", - "title": "Representation", - "description": "A string with the part of the URL following the versioned or unversioned base URL that serves the API.\nQuery parameters that have not been used in processing the request MAY be omitted.\nIn particular, if no query parameters have been involved in processing the request, the query part of the URL MAY be excluded.\nExample: `/structures?filter=nelements=2`" - } - }, - "type": "object", - "required": [ - "representation" - ], - "title": "ResponseMetaQuery", - "description": "Information on the query that was requested." - }, - "Species": { - "properties": { - "name": { - "type": "string", - "title": "Name", - "description": "Gives the name of the species; the **name** value MUST be unique in the `species` list.", - "x-optimade-support": "must", - "x-optimade-queryable": "optional" - }, - "chemical_symbols": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Chemical Symbols", - "description": "MUST be a list of strings of all chemical elements composing this species. Each item of the list MUST be one of the following:\n\n- a valid chemical-element symbol, or\n- the special value `\"X\"` to represent a non-chemical element, or\n- the special value `\"vacancy\"` to represent that this site has a non-zero probability of having a vacancy (the respective probability is indicated in the `concentration` list, see below).\n\nIf any one entry in the `species` list has a `chemical_symbols` list that is longer than 1 element, the correct flag MUST be set in the list `structure_features`.", - "x-optimade-support": "must", - "x-optimade-queryable": "optional" - }, - "concentration": { - "items": { - "type": "number" - }, - "type": "array", - "title": "Concentration", - "description": "MUST be a list of floats, with same length as `chemical_symbols`. The numbers represent the relative concentration of the corresponding chemical symbol in this species. The numbers SHOULD sum to one. Cases in which the numbers do not sum to one typically fall only in the following two categories:\n\n- Numerical errors when representing float numbers in fixed precision, e.g. for two chemical symbols with concentrations `1/3` and `2/3`, the concentration might look something like `[0.33333333333, 0.66666666666]`. If the client is aware that the sum is not one because of numerical precision, it can renormalize the values so that the sum is exactly one.\n- Experimental errors in the data present in the database. In this case, it is the responsibility of the client to decide how to process the data.\n\nNote that concentrations are uncorrelated between different site (even of the same species).", - "x-optimade-support": "must", - "x-optimade-queryable": "optional" - }, - "mass": { - "items": { - "type": "number" - }, - "type": "array", - "title": "Mass", - "description": "If present MUST be a list of floats expressed in a.m.u.\nElements denoting vacancies MUST have masses equal to 0.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional", - "x-optimade-unit": "a.m.u." - }, - "original_name": { - "type": "string", - "title": "Original Name", - "description": "Can be any valid Unicode string, and SHOULD contain (if specified) the name of the species that is used internally in the source database.\n\nNote: With regards to \"source database\", we refer to the immediate source being queried via the OPTIMADE API implementation.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "title": "Last", + "description": "The last page of data" }, - "attached": { - "items": { - "type": "string" - }, - "type": "array", - "title": "Attached", - "description": "If provided MUST be a list of length 1 or more of strings of chemical symbols for the elements attached to this site, or \"X\" for a non-chemical element.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "prev": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Prev", + "description": "The previous page of data" }, - "nattached": { - "items": { - "type": "integer" - }, - "type": "array", - "title": "Nattached", - "description": "If provided MUST be a list of length 1 or more of integers indicating the number of attached atoms of the kind specified in the value of the :field:`attached` key.", - "x-optimade-support": "optional", - "x-optimade-queryable": "optional" + "next": { + "anyOf": [ + { + "type": "string", + "maxLength": 65536, + "minLength": 1, + "format": "uri" + }, + { + "$ref": "#/components/schemas/Link" + } + ], + "title": "Next", + "description": "The next page of data" } }, "type": "object", - "required": [ - "name", - "chemical_symbols", - "concentration" - ], - "title": "Species", - "description": "A list describing the species of the sites of this structure.\n\nSpecies can represent pure chemical elements, virtual-crystal atoms representing a\nstatistical occupation of a given site by multiple chemical elements, and/or a\nlocation to which there are attached atoms, i.e., atoms whose precise location are\nunknown beyond that they are attached to that position (frequently used to indicate\nhydrogen atoms attached to another element, e.g., a carbon with three attached\nhydrogens might represent a methyl group, -CH3).\n\n- **Examples**:\n - `[ {\"name\": \"Ti\", \"chemical_symbols\": [\"Ti\"], \"concentration\": [1.0]} ]`: any site with this species is occupied by a Ti atom.\n - `[ {\"name\": \"Ti\", \"chemical_symbols\": [\"Ti\", \"vacancy\"], \"concentration\": [0.9, 0.1]} ]`: any site with this species is occupied by a Ti atom with 90 % probability, and has a vacancy with 10 % probability.\n - `[ {\"name\": \"BaCa\", \"chemical_symbols\": [\"vacancy\", \"Ba\", \"Ca\"], \"concentration\": [0.05, 0.45, 0.5], \"mass\": [0.0, 137.327, 40.078]} ]`: any site with this species is occupied by a Ba atom with 45 % probability, a Ca atom with 50 % probability, and by a vacancy with 5 % probability. The mass of this site is (on average) 88.5 a.m.u.\n - `[ {\"name\": \"C12\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"mass\": [12.0]} ]`: any site with this species is occupied by a carbon isotope with mass 12.\n - `[ {\"name\": \"C13\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"mass\": [13.0]} ]`: any site with this species is occupied by a carbon isotope with mass 13.\n - `[ {\"name\": \"CH3\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"attached\": [\"H\"], \"nattached\": [3]} ]`: any site with this species is occupied by a methyl group, -CH3, which is represented without specifying precise positions of the hydrogen atoms." - }, - "StructureFeatures": { - "enum": [ - "disorder", - "implicit_atoms", - "site_attachments", - "assemblies" - ], - "title": "StructureFeatures", - "description": "Enumeration of structure_features values" + "title": "ToplevelLinks", + "description": "A set of Links objects, possibly including pagination" }, - "StructureRelationship": { + "TrajectoryRelationship": { "properties": { "links": { "allOf": [ @@ -3429,10 +4680,10 @@ } }, "type": "object", - "title": "StructureRelationship", + "title": "TrajectoryRelationship", "description": "Similar to normal JSON API relationship, but with addition of OPTIONAL meta field for a resource." }, - "StructureResource": { + "TrajectoryResource": { "properties": { "id": { "type": "string", @@ -3443,10 +4694,11 @@ }, "type": { "type": "string", - "pattern": "^structures$", + "const": "trajectories", + "pattern": "^trajectories$", "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 `` and ID `` MUST be returned in response to a request for `//` under the versioned base URL.\n\n- **Examples**:\n - `\"structures\"`", - "default": "structures", + "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 `` and ID `` MUST be returned in response to a request for `//` under the versioned base URL.\n\n- **Examples**:\n - `\"trajectories\"`", + "default": "trajectories", "x-optimade-support": "must", "x-optimade-queryable": "must" }, @@ -3462,14 +4714,14 @@ "meta": { "allOf": [ { - "$ref": "#/components/schemas/Meta" + "$ref": "#/components/schemas/EntryMetadata" } ], "title": "Meta", - "description": "a meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship." + "description": "A [JSON API meta object](https://jsonapi.org/format/1.1/#document-meta) that is used to communicate metadata." }, "attributes": { - "$ref": "#/components/schemas/StructureResourceAttributes" + "$ref": "#/components/schemas/TrajectoryResourceAttributes" }, "relationships": { "allOf": [ @@ -3487,27 +4739,11 @@ "type", "attributes" ], - "title": "StructureResource", - "description": "Representing a structure." + "title": "TrajectoryResource", + "description": "Representing a trajectory." }, - "StructureResourceAttributes": { + "TrajectoryResourceAttributes": { "properties": { - "immutable_id": { - "type": "string", - "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-support": "optional", - "x-optimade-queryable": "must" - }, - "last_modified": { - "type": "string", - "format": "date-time", - "title": "Last Modified", - "description": "Date and time representing when the entry was last modified.\n\n- **Type**: timestamp.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response unless the query parameter `response_fields` is present and does not include this property.\n\n- **Example**:\n - As part of JSON response format: `\"2007-04-05T14:30:20Z\"` (i.e., encoded as an [RFC 3339 Internet Date/Time Format](https://tools.ietf.org/html/rfc3339#section-5.6) string.)", - "x-optimade-support": "should", - "x-optimade-queryable": "must", - "nullable": true - }, "elements": { "items": { "type": "string" @@ -3516,16 +4752,16 @@ "title": "Elements", "description": "The chemical symbols of the different elements present in the structure.\n\n- **Type**: list of strings.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - The strings are the chemical symbols, i.e., either a single uppercase letter or an uppercase letter followed by a number of lowercase letters.\n - The order MUST be alphabetical.\n - MUST refer to the same elements in the same order, and therefore be of the same length, as `elements_ratios`, if the latter is provided.\n - Note: This property SHOULD NOT contain the string \"X\" to indicate non-chemical elements or \"vacancy\" to indicate vacancies (in contrast to the field `chemical_symbols` for the `species` property).\n\n- **Examples**:\n - `[\"Si\"]`\n - `[\"Al\",\"O\",\"Si\"]`\n\n- **Query examples**:\n - A filter that matches all records of structures that contain Si, Al **and** O, and possibly other elements: `elements HAS ALL \"Si\", \"Al\", \"O\"`.\n - To match structures with exactly these three elements, use `elements HAS ALL \"Si\", \"Al\", \"O\" AND elements LENGTH 3`.\n - Note: length queries on this property can be equivalently formulated by filtering on the `nelements`_ property directly.", "x-optimade-support": "should", - "x-optimade-queryable": "must", - "nullable": true + "nullable": true, + "x-optimade-queryable": "must" }, "nelements": { "type": "integer", "title": "Nelements", "description": "Number of different elements in the structure as an integer.\n\n- **Type**: integer\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - MUST be equal to the lengths of the list properties `elements` and `elements_ratios`, if they are provided.\n\n- **Examples**:\n - `3`\n\n- **Querying**:\n - Note: queries on this property can equivalently be formulated using `elements LENGTH`.\n - A filter that matches structures that have exactly 4 elements: `nelements=4`.\n - A filter that matches structures that have between 2 and 7 elements: `nelements>=2 AND nelements<=7`.", "x-optimade-support": "should", - "x-optimade-queryable": "must", - "nullable": true + "nullable": true, + "x-optimade-queryable": "must" }, "elements_ratios": { "items": { @@ -3535,16 +4771,16 @@ "title": "Elements Ratios", "description": "Relative proportions of different elements in the structure.\n\n- **Type**: list of floats\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - Composed by the proportions of elements in the structure as a list of floating point numbers.\n - The sum of the numbers MUST be 1.0 (within floating point accuracy)\n - MUST refer to the same elements in the same order, and therefore be of the same length, as `elements`, if the latter is provided.\n\n- **Examples**:\n - `[1.0]`\n - `[0.3333333333333333, 0.2222222222222222, 0.4444444444444444]`\n\n- **Query examples**:\n - Note: Useful filters can be formulated using the set operator syntax for correlated values.\n However, since the values are floating point values, the use of equality comparisons is generally inadvisable.\n - OPTIONAL: a filter that matches structures where approximately 1/3 of the atoms in the structure are the element Al is: `elements:elements_ratios HAS ALL \"Al\":>0.3333, \"Al\":<0.3334`.", "x-optimade-support": "should", - "x-optimade-queryable": "must", - "nullable": true + "nullable": true, + "x-optimade-queryable": "must" }, "chemical_formula_descriptive": { "type": "string", "title": "Chemical Formula Descriptive", "description": "The chemical formula for a structure as a string in a form chosen by the API implementation.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - The chemical formula is given as a string consisting of properly capitalized element symbols followed by integers or decimal numbers, balanced parentheses, square, and curly brackets `(`,`)`, `[`,`]`, `{`, `}`, commas, the `+`, `-`, `:` and `=` symbols. The parentheses are allowed to be followed by a number. Spaces are allowed anywhere except within chemical symbols. The order of elements and any groupings indicated by parentheses or brackets are chosen freely by the API implementation.\n - The string SHOULD be arithmetically consistent with the element ratios in the `chemical_formula_reduced` property.\n - It is RECOMMENDED, but not mandatory, that symbols, parentheses and brackets, if used, are used with the meanings prescribed by [IUPAC's Nomenclature of Organic Chemistry](https://www.qmul.ac.uk/sbcs/iupac/bibliog/blue.html).\n\n- **Examples**:\n - `\"(H2O)2 Na\"`\n - `\"NaCl\"`\n - `\"CaCO3\"`\n - `\"CCaO3\"`\n - `\"(CH3)3N+ - [CH2]2-OH = Me3N+ - CH2 - CH2OH\"`\n\n- **Query examples**:\n - Note: the free-form nature of this property is likely to make queries on it across different databases inconsistent.\n - A filter that matches an exactly given formula: `chemical_formula_descriptive=\"(H2O)2 Na\"`.\n - A filter that does a partial match: `chemical_formula_descriptive CONTAINS \"H2O\"`.", "x-optimade-support": "should", - "x-optimade-queryable": "must", - "nullable": true + "nullable": true, + "x-optimade-queryable": "must" }, "chemical_formula_reduced": { "type": "string", @@ -3552,8 +4788,8 @@ "title": "Chemical Formula Reduced", "description": "The reduced chemical formula for a structure as a string with element symbols and integer chemical proportion numbers.\nThe proportion number MUST be omitted if it is 1.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property.\n However, support for filters using partial string matching with this property is OPTIONAL (i.e., BEGINS WITH, ENDS WITH, and CONTAINS).\n Intricate queries on formula components are instead suggested to be formulated using set-type filter operators on the multi valued `elements` and `elements_ratios` properties.\n - Element symbols MUST have proper capitalization (e.g., `\"Si\"`, not `\"SI\"` for \"silicon\").\n - Elements MUST be placed in alphabetical order, followed by their integer chemical proportion number.\n - For structures with no partial occupation, the chemical proportion numbers are the smallest integers for which the chemical proportion is exactly correct.\n - For structures with partial occupation, the chemical proportion numbers are integers that within reasonable approximation indicate the correct chemical proportions. The precise details of how to perform the rounding is chosen by the API implementation.\n - No spaces or separators are allowed.\n\n- **Examples**:\n - `\"H2NaO\"`\n - `\"ClNa\"`\n - `\"CCaO3\"`\n\n- **Query examples**:\n - A filter that matches an exactly given formula is `chemical_formula_reduced=\"H2NaO\"`.", "x-optimade-support": "should", - "x-optimade-queryable": "must", - "nullable": true + "nullable": true, + "x-optimade-queryable": "must" }, "chemical_formula_hill": { "type": "string", @@ -3569,8 +4805,8 @@ "title": "Chemical Formula Anonymous", "description": "The anonymous formula is the `chemical_formula_reduced`, but where the elements are instead first ordered by their chemical proportion number, and then, in order left to right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and so on.\n\n- **Type**: string\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property.\n However, support for filters using partial string matching with this property is OPTIONAL (i.e., BEGINS WITH, ENDS WITH, and CONTAINS).\n\n- **Examples**:\n - `\"A2B\"`\n - `\"A42B42C16D12E10F9G5\"`\n\n- **Querying**:\n - A filter that matches an exactly given formula is `chemical_formula_anonymous=\"A2B\"`.", "x-optimade-support": "should", - "x-optimade-queryable": "must", - "nullable": true + "nullable": true, + "x-optimade-queryable": "must" }, "dimension_types": { "items": { @@ -3582,16 +4818,16 @@ "title": "Dimension Types", "description": "List of three integers.\nFor each of the three directions indicated by the three lattice vectors (see property `lattice_vectors`), this list indicates if the direction is periodic (value `1`) or non-periodic (value `0`).\nNote: the elements in this list each refer to the direction of the corresponding entry in `lattice_vectors` and *not* the Cartesian x, y, z directions.\n\n- **Type**: list of integers.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n - MUST be a list of length 3.\n - Each integer element MUST assume only the value 0 or 1.\n\n- **Examples**:\n - For a molecule: `[0, 0, 0]`\n - For a wire along the direction specified by the third lattice vector: `[0, 0, 1]`\n - For a 2D surface/slab, periodic on the plane defined by the first and third lattice vectors: `[1, 0, 1]`\n - For a bulk 3D system: `[1, 1, 1]`", "x-optimade-support": "should", - "x-optimade-queryable": "optional", - "nullable": true + "nullable": true, + "x-optimade-queryable": "optional" }, "nperiodic_dimensions": { "type": "integer", "title": "Nperiodic Dimensions", "description": "An integer specifying the number of periodic dimensions in the structure, equivalent to the number of non-zero entries in `dimension_types`.\n\n- **Type**: integer\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - The integer value MUST be between 0 and 3 inclusive and MUST be equal to the sum of the items in the `dimension_types` property.\n - This property only reflects the treatment of the lattice vectors provided for the structure, and not any physical interpretation of the dimensionality of its contents.\n\n- **Examples**:\n - `2` should be indicated in cases where `dimension_types` is any of `[1, 1, 0]`, `[1, 0, 1]`, `[0, 1, 1]`.\n\n- **Query examples**:\n - Match only structures with exactly 3 periodic dimensions: `nperiodic_dimensions=3`\n - Match all structures with 2 or fewer periodic dimensions: `nperiodic_dimensions<=2`", "x-optimade-support": "should", - "x-optimade-queryable": "must", - "nullable": true + "nullable": true, + "x-optimade-queryable": "must" }, "lattice_vectors": { "items": { @@ -3608,8 +4844,8 @@ "title": "Lattice Vectors", "description": "The three lattice vectors in Cartesian coordinates, in \u00e5ngstr\u00f6m (\u00c5).\n\n- **Type**: list of list of floats or unknown values.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - MUST be a list of three vectors *a*, *b*, and *c*, where each of the vectors MUST BE a list of the vector's coordinates along the x, y, and z Cartesian coordinates.\n (Therefore, the first index runs over the three lattice vectors and the second index runs over the x, y, z Cartesian coordinates).\n - For databases that do not define an absolute Cartesian system (e.g., only defining the length and angles between vectors), the first lattice vector SHOULD be set along *x* and the second on the *xy*-plane.\n - MUST always contain three vectors of three coordinates each, independently of the elements of property `dimension_types`.\n The vectors SHOULD by convention be chosen so the determinant of the `lattice_vectors` matrix is different from zero.\n The vectors in the non-periodic directions have no significance beyond fulfilling these requirements.\n - The coordinates of the lattice vectors of non-periodic dimensions (i.e., those dimensions for which `dimension_types` is `0`) MAY be given as a list of all `null` values.\n If a lattice vector contains the value `null`, all coordinates of that lattice vector MUST be `null`.\n\n- **Examples**:\n - `[[4.0,0.0,0.0],[0.0,4.0,0.0],[0.0,1.0,4.0]]` represents a cell, where the first vector is `(4, 0, 0)`, i.e., a vector aligned along the `x` axis of length 4 \u00c5; the second vector is `(0, 4, 0)`; and the third vector is `(0, 1, 4)`.", "x-optimade-support": "should", - "x-optimade-queryable": "optional", "nullable": true, + "x-optimade-queryable": "optional", "x-optimade-unit": "\u00c5" }, "cartesian_site_positions": { @@ -3625,8 +4861,8 @@ "title": "Cartesian Site Positions", "description": "Cartesian positions of each site in the structure.\nA site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the `species_at_sites` property, and the species themselves are described in the `species` property.\n\n- **Type**: list of list of floats\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - It MUST be a list of length equal to the number of sites in the structure, where every element is a list of the three Cartesian coordinates of a site expressed as float values in the unit angstrom (\u00c5).\n - An entry MAY have multiple sites at the same Cartesian position (for a relevant use of this, see e.g., the property `assemblies`).\n\n- **Examples**:\n - `[[0,0,0],[0,0,2]]` indicates a structure with two sites, one sitting at the origin and one along the (positive) *z*-axis, 2 \u00c5 away from the origin.", "x-optimade-support": "should", - "x-optimade-queryable": "optional", "nullable": true, + "x-optimade-queryable": "optional", "x-optimade-unit": "\u00c5" }, "nsites": { @@ -3634,8 +4870,8 @@ "title": "Nsites", "description": "An integer specifying the length of the `cartesian_site_positions` property.\n\n- **Type**: integer\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n\n- **Examples**:\n - `42`\n\n- **Query examples**:\n - Match only structures with exactly 4 sites: `nsites=4`\n - Match structures that have between 2 and 7 sites: `nsites>=2 AND nsites<=7`", "x-optimade-support": "should", - "x-optimade-queryable": "must", - "nullable": true + "nullable": true, + "x-optimade-queryable": "must" }, "species": { "items": { @@ -3645,8 +4881,8 @@ "title": "Species", "description": "A list describing the species of the sites of this structure.\nSpecies can represent pure chemical elements, virtual-crystal atoms representing a statistical occupation of a given site by multiple chemical elements, and/or a location to which there are attached atoms, i.e., atoms whose precise location are unknown beyond that they are attached to that position (frequently used to indicate hydrogen atoms attached to another element, e.g., a carbon with three attached hydrogens might represent a methyl group, -CH3).\n\n- **Type**: list of dictionary with keys:\n - `name`: string (REQUIRED)\n - `chemical_symbols`: list of strings (REQUIRED)\n - `concentration`: list of float (REQUIRED)\n - `attached`: list of strings (REQUIRED)\n - `nattached`: list of integers (OPTIONAL)\n - `mass`: list of floats (OPTIONAL)\n - `original_name`: string (OPTIONAL).\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - Each list member MUST be a dictionary with the following keys:\n - **name**: REQUIRED; gives the name of the species; the **name** value MUST be unique in the `species` list;\n - **chemical_symbols**: REQUIRED; MUST be a list of strings of all chemical elements composing this species.\n Each item of the list MUST be one of the following:\n - a valid chemical-element symbol, or\n - the special value `\"X\"` to represent a non-chemical element, or\n - the special value `\"vacancy\"` to represent that this site has a non-zero probability of having a vacancy (the respective probability is indicated in the `concentration` list, see below).\n\n If any one entry in the `species` list has a `chemical_symbols` list that is longer than 1 element, the correct flag MUST be set in the list `structure_features`.\n\n - **concentration**: REQUIRED; MUST be a list of floats, with same length as `chemical_symbols`.\n The numbers represent the relative concentration of the corresponding chemical symbol in this species.\n The numbers SHOULD sum to one. Cases in which the numbers do not sum to one typically fall only in the following two categories:\n\n - Numerical errors when representing float numbers in fixed precision, e.g. for two chemical symbols with concentrations `1/3` and `2/3`, the concentration might look something like `[0.33333333333, 0.66666666666]`. If the client is aware that the sum is not one because of numerical precision, it can renormalize the values so that the sum is exactly one.\n - Experimental errors in the data present in the database. In this case, it is the responsibility of the client to decide how to process the data.\n\n Note that concentrations are uncorrelated between different sites (even of the same species).\n\n - **attached**: OPTIONAL; if provided MUST be a list of length 1 or more of strings of chemical symbols for the elements attached to this site, or \"X\" for a non-chemical element.\n\n - **nattached**: OPTIONAL; if provided MUST be a list of length 1 or more of integers indicating the number of attached atoms of the kind specified in the value of the `attached` key.\n\n The implementation MUST include either both or none of the `attached` and `nattached` keys, and if they are provided, they MUST be of the same length.\n Furthermore, if they are provided, the `structure_features` property MUST include the string `site_attachments`.\n\n - **mass**: OPTIONAL. If present MUST be a list of floats, with the same length as `chemical_symbols`, providing element masses expressed in a.m.u.\n Elements denoting vacancies MUST have masses equal to 0.\n\n - **original_name**: OPTIONAL. Can be any valid Unicode string, and SHOULD contain (if specified) the name of the species that is used internally in the source database.\n\n Note: With regards to \"source database\", we refer to the immediate source being queried via the OPTIMADE API implementation.\n\n The main use of this field is for source databases that use species names, containing characters that are not allowed (see description of the list property `species_at_sites`).\n\n - For systems that have only species formed by a single chemical symbol, and that have at most one species per chemical symbol, SHOULD use the chemical symbol as species name (e.g., `\"Ti\"` for titanium, `\"O\"` for oxygen, etc.)\n However, note that this is OPTIONAL, and client implementations MUST NOT assume that the key corresponds to a chemical symbol, nor assume that if the species name is a valid chemical symbol, that it represents a species with that chemical symbol.\n This means that a species `{\"name\": \"C\", \"chemical_symbols\": [\"Ti\"], \"concentration\": [1.0]}` is valid and represents a titanium species (and *not* a carbon species).\n - It is NOT RECOMMENDED that a structure includes species that do not have at least one corresponding site.\n\n- **Examples**:\n - `[ {\"name\": \"Ti\", \"chemical_symbols\": [\"Ti\"], \"concentration\": [1.0]} ]`: any site with this species is occupied by a Ti atom.\n - `[ {\"name\": \"Ti\", \"chemical_symbols\": [\"Ti\", \"vacancy\"], \"concentration\": [0.9, 0.1]} ]`: any site with this species is occupied by a Ti atom with 90 % probability, and has a vacancy with 10 % probability.\n - `[ {\"name\": \"BaCa\", \"chemical_symbols\": [\"vacancy\", \"Ba\", \"Ca\"], \"concentration\": [0.05, 0.45, 0.5], \"mass\": [0.0, 137.327, 40.078]} ]`: any site with this species is occupied by a Ba atom with 45 % probability, a Ca atom with 50 % probability, and by a vacancy with 5 % probability. The mass of this site is (on average) 88.5 a.m.u.\n - `[ {\"name\": \"C12\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"mass\": [12.0]} ]`: any site with this species is occupied by a carbon isotope with mass 12.\n - `[ {\"name\": \"C13\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"mass\": [13.0]} ]`: any site with this species is occupied by a carbon isotope with mass 13.\n - `[ {\"name\": \"CH3\", \"chemical_symbols\": [\"C\"], \"concentration\": [1.0], \"attached\": [\"H\"], \"nattached\": [3]} ]`: any site with this species is occupied by a methyl group, -CH3, which is represented without specifying precise positions of the hydrogen atoms.", "x-optimade-support": "should", - "x-optimade-queryable": "optional", - "nullable": true + "nullable": true, + "x-optimade-queryable": "optional" }, "species_at_sites": { "items": { @@ -3656,8 +4892,8 @@ "title": "Species At Sites", "description": "Name of the species at each site (where values for sites are specified with the same order of the property `cartesian_site_positions`).\nThe properties of the species are found in the property `species`.\n\n- **Type**: list of strings.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n - MUST have length equal to the number of sites in the structure (first dimension of the list property `cartesian_site_positions`).\n - Each species name mentioned in the `species_at_sites` list MUST be described in the list property `species` (i.e. for each value in the `species_at_sites` list there MUST exist exactly one dictionary in the `species` list with the `name` attribute equal to the corresponding `species_at_sites` value).\n - Each site MUST be associated only to a single species.\n **Note**: However, species can represent mixtures of atoms, and multiple species MAY be defined for the same chemical element.\n This latter case is useful when different atoms of the same type need to be grouped or distinguished, for instance in simulation codes to assign different initial spin states.\n\n- **Examples**:\n - `[\"Ti\",\"O2\"]` indicates that the first site is hosting a species labeled `\"Ti\"` and the second a species labeled `\"O2\"`.\n - `[\"Ac\", \"Ac\", \"Ag\", \"Ir\"]` indicating the first two sites contains the `\"Ac\"` species, while the third and fourth sites contain the `\"Ag\"` and `\"Ir\"` species, respectively.", "x-optimade-support": "should", - "x-optimade-queryable": "optional", - "nullable": true + "nullable": true, + "x-optimade-queryable": "optional" }, "assemblies": { "items": { @@ -3678,36 +4914,67 @@ "description": "A list of strings that flag which special features are used by the structure.\n\n- **Type**: list of strings\n\n- **Requirements/Conventions**:\n - **Support**: MUST be supported by all implementations, MUST NOT be `null`.\n - **Query**: MUST be a queryable property.\n Filters on the list MUST support all mandatory HAS-type queries.\n Filter operators for comparisons on the string components MUST support equality, support for other comparison operators are OPTIONAL.\n - MUST be an empty list if no special features are used.\n - MUST be sorted alphabetically.\n - If a special feature listed below is used, the list MUST contain the corresponding string.\n - If a special feature listed below is not used, the list MUST NOT contain the corresponding string.\n - **List of strings used to indicate special structure features**:\n - `disorder`: this flag MUST be present if any one entry in the `species` list has a `chemical_symbols` list that is longer than 1 element.\n - `implicit_atoms`: this flag MUST be present if the structure contains atoms that are not assigned to sites via the property `species_at_sites` (e.g., because their positions are unknown).\n When this flag is present, the properties related to the chemical formula will likely not match the type and count of atoms represented by the `species_at_sites`, `species` and `assemblies` properties.\n - `site_attachments`: this flag MUST be present if any one entry in the `species` list includes `attached` and `nattached`.\n - `assemblies`: this flag MUST be present if the property `assemblies` is present.\n\n- **Examples**: A structure having implicit atoms and using assemblies: `[\"assemblies\", \"implicit_atoms\"]`", "x-optimade-support": "must", "x-optimade-queryable": "must" + }, + "immutable_id": { + "type": "string", + "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-support": "optional", + "x-optimade-queryable": "must" + }, + "last_modified": { + "type": "string", + "format": "date-time", + "title": "Last Modified", + "description": "Date and time representing when the entry was last modified.\n\n- **Type**: timestamp.\n\n- **Requirements/Conventions**:\n - **Support**: SHOULD be supported by all implementations, i.e., SHOULD NOT be `null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - **Response**: REQUIRED in the response unless the query parameter `response_fields` is present and does not include this property.\n\n- **Example**:\n - As part of JSON response format: `\"2007-04-05T14:30:20Z\"` (i.e., encoded as an [RFC 3339 Internet Date/Time Format](https://tools.ietf.org/html/rfc3339#section-5.6) string.)", + "x-optimade-support": "should", + "nullable": true, + "x-optimade-queryable": "must" + }, + "reference_frames": { + "type": "integer", + "title": "Reference Frames", + "description": "The indexes of a set of frames that give a good but very brief overview of the trajectory.\n The first frame could for example be a starting configuration, the second a transition state and the third the final state.\n- **Type**: list of integers\n- **Requirements/Conventions**: The values MUST be larger than or equal to 0 and less than nframes.\n\n - **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`.\n - **Query**: Support for queries on this property is OPTIONAL.\n If supported, filters MAY support only a subset of comparison operators.\n\n- **Examples**:\n\n - :val:`[0, 397, 1000]`\n\n ", + "x-optimade-support": "optional", + "x-optimade-queryable": "optional" + }, + "nframes": { + "type": "integer", + "title": "Nframes", + "description": " The number of frames in the trajectory as exposed by the API.\n This value may deviate from the number of steps used to calculate the trajectory.\n E.g., for a 10 ps simulation with calculation steps of 1 fs where data is stored once every 50 fs, nframes = 200.\n- **Type**: integer\n- **Requirements/Conventions**:\n\n - **Support**: MUST be supported by all implementations, i.e., MUST NOT be :val:`null`.\n - **Query**: MUST be a queryable property with support for all mandatory filter features.\n - The integer value MUST be equal to the length of the trajectory, that is, the number of frames.\n - The integer MUST be a positive non-zero value.\n\n- **Querying**:\n\n - A filter that matches trajectories that have exactly 100 frames:\n - :filter:`nframes=100`\n - A filter that matches trajectories that have between 100 and 1000 frames:\n - :filter:`nframes>=100 AND nframes<=1000`\n\n- **Examples**:\n\n - :val:`42`\n", + "x-optimade-support": "must", + "x-optimade-queryable": "must" } }, "type": "object", "required": [ - "last_modified", "elements", "nelements", "elements_ratios", "chemical_formula_descriptive", "chemical_formula_reduced", "chemical_formula_anonymous", - "dimension_types", "nperiodic_dimensions", - "lattice_vectors", + "dimension_types", "cartesian_site_positions", + "lattice_vectors", "nsites", "species", "species_at_sites", - "structure_features" + "structure_features", + "last_modified", + "nframes" ], - "title": "StructureResourceAttributes", - "description": "This class contains the Field for the attributes used to represent a structure, e.g. unit cell, atoms, positions." + "title": "TrajectoryResourceAttributes", + "description": "This class contains the Field for the attributes used to represent a trajectory." }, - "StructureResponseMany": { + "TrajectoryResponseMany": { "properties": { "data": { "anyOf": [ { "items": { - "$ref": "#/components/schemas/StructureResource" + "$ref": "#/components/schemas/TrajectoryResource" }, "type": "array" }, @@ -3720,7 +4987,7 @@ ], "uniqueItems": true, "title": "Data", - "description": "List of unique OPTIMADE structures entry resource objects." + "description": "List of unique OPTIMADE trajectories entry resource objects." }, "meta": { "allOf": [ @@ -3782,22 +5049,22 @@ "data", "meta" ], - "title": "StructureResponseMany", + "title": "TrajectoryResponseMany", "description": "errors are not allowed" }, - "StructureResponseOne": { + "TrajectoryResponseOne": { "properties": { "data": { "anyOf": [ { - "$ref": "#/components/schemas/StructureResource" + "$ref": "#/components/schemas/TrajectoryResource" }, { "type": "object" } ], "title": "Data", - "description": "A single structures entry resource." + "description": "A single trajectories entry resource." }, "meta": { "allOf": [ @@ -3859,106 +5126,9 @@ "data", "meta" ], - "title": "StructureResponseOne", + "title": "TrajectoryResponseOne", "description": "errors are not allowed" }, - "ToplevelLinks": { - "properties": { - "self": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, - { - "$ref": "#/components/schemas/Link" - } - ], - "title": "Self", - "description": "A link to itself" - }, - "related": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, - { - "$ref": "#/components/schemas/Link" - } - ], - "title": "Related", - "description": "A related resource link" - }, - "first": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, - { - "$ref": "#/components/schemas/Link" - } - ], - "title": "First", - "description": "The first page of data" - }, - "last": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, - { - "$ref": "#/components/schemas/Link" - } - ], - "title": "Last", - "description": "The last page of data" - }, - "prev": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, - { - "$ref": "#/components/schemas/Link" - } - ], - "title": "Prev", - "description": "The previous page of data" - }, - "next": { - "anyOf": [ - { - "type": "string", - "maxLength": 65536, - "minLength": 1, - "format": "uri" - }, - { - "$ref": "#/components/schemas/Link" - } - ], - "title": "Next", - "description": "The next page of data" - } - }, - "type": "object", - "title": "ToplevelLinks", - "description": "A set of Links objects, possibly including pagination" - }, "Warnings": { "properties": { "id": { diff --git a/optimade/adapters/jsonl.py b/optimade/adapters/jsonl.py new file mode 100644 index 000000000..9f7bfac21 --- /dev/null +++ b/optimade/adapters/jsonl.py @@ -0,0 +1,42 @@ +from io import BufferedReader, BytesIO +from pathlib import Path +from typing import Union + +from jsonlines import Reader, Writer + +from optimade.models.partial_data import PartialDataResource + + +def to_jsonl(input_data: Union[list[dict], PartialDataResource]) -> bytes: + """This function convert a list of dictionaries to the JSONL format which can be sent back in an OPTIMADE partial data response""" + temp_file = BytesIO() + writer = Writer(temp_file) + if isinstance(input_data, PartialDataResource): + writer.write(input_data.header) + input_data = input_data.data + if isinstance(input_data, list): + writer.write_all(input_data) + else: + writer.write(input_data) + writer.close() + file_content = temp_file.getvalue() + temp_file.close() + return file_content + + +def from_jsonl( + jsonl_input: Union[Path, str, bytes] +) -> Union[list, PartialDataResource]: + if isinstance(jsonl_input, (Path, str)): + fp: Union[BytesIO, BufferedReader] = open(jsonl_input, "rb") + else: + fp = BytesIO(jsonl_input) + decoded = [] + reader = Reader(fp) + for obj in reader: + decoded.append( + obj + ) # Appending is slow, so it would be better to use a more efficient method + reader.close() + fp.close() + return decoded diff --git a/optimade/filtertransformers/mongo.py b/optimade/filtertransformers/mongo.py index 1b9ec8324..85dec78cd 100755 --- a/optimade/filtertransformers/mongo.py +++ b/optimade/filtertransformers/mongo.py @@ -379,6 +379,7 @@ def check_for_entry_type(prop, _): return str(prop).count(".") == 1 and str(prop).split(".")[0] in ( "structures", "references", + "trajectories", ) def replace_with_relationship(subdict, prop, expr): diff --git a/optimade/models/__init__.py b/optimade/models/__init__.py index 018560412..c1d0d5a8a 100644 --- a/optimade/models/__init__.py +++ b/optimade/models/__init__.py @@ -5,9 +5,11 @@ from .jsonapi import * # noqa: F403 from .links import * # noqa: F403 from .optimade_json import * # noqa: F403 +from .partial_data import * # noqa: F403 from .references import * # noqa: F403 from .responses import * # noqa: F403 from .structures import * # noqa: F403 +from .trajectories import * # noqa: F403 from .utils import * # noqa: F403 __all__ = ( @@ -18,7 +20,9 @@ + index_metadb.__all__ # type: ignore[name-defined] # noqa: F405 + links.__all__ # type: ignore[name-defined] # noqa: F405 + optimade_json.__all__ # type: ignore[name-defined] # noqa: F405 + + partial_data.__all__ # type: ignore[name-defined] # noqa: F405 + references.__all__ # type: ignore[name-defined] # noqa: F405 + responses.__all__ # type: ignore[name-defined] # noqa: F405 + structures.__all__ # type: ignore[name-defined] # noqa: F405 + + trajectories.__all__ # type: ignore[name-defined] # noqa: F405 ) diff --git a/optimade/models/entries.py b/optimade/models/entries.py index 4bd10a29e..04ec995c1 100644 --- a/optimade/models/entries.py +++ b/optimade/models/entries.py @@ -2,9 +2,9 @@ from datetime import datetime from typing import Optional -from pydantic import BaseModel, validator # pylint: disable=no-name-in-module +from pydantic import AnyUrl, BaseModel, root_validator, validator -from optimade.models.jsonapi import Attributes, Relationships, Resource +from optimade.models.jsonapi import Attributes, Meta, Relationships, Resource from optimade.models.optimade_json import DataType, Relationship from optimade.models.utils import OptimadeField, StrictField, SupportLevel @@ -40,6 +40,10 @@ class StructureRelationship(TypedRelationship): _req_type = "structures" +class TrajectoryRelationship(TypedRelationship): + _req_type = "trajectories" + + class EntryRelationships(Relationships): """This model wraps the JSON API Relationships to include type-specific top level keys.""" @@ -53,6 +57,11 @@ class EntryRelationships(Relationships): description="Object containing links to relationships with entries of the `structures` type.", ) + trajectories: Optional[TrajectoryRelationship] = StrictField( + None, + description="Object containing links to relationships with entries of the `trajectories` type.", + ) + class EntryResourceAttributes(Attributes): """Contains key-value pairs representing the entry's properties.""" @@ -100,6 +109,77 @@ def cast_immutable_id_to_str(cls, value): return value +class PartialDataLink(BaseModel): + link: AnyUrl = OptimadeField( + ..., + description="String. A JSON API link that points to a location from which the omitted data can be fetched. There is no requirement on the syntax or format for the link URL.", + ) + format: str = OptimadeField( + ..., + description='String. The name of the format provided via this link. For one of the objects this format field SHOULD have the value "jsonlines", which refers to the format in OPTIMADE JSON lines partial data format.', + ) + + @validator("format") + def check_if_format_is_supported(cls, value): + from optimade.server.config import CONFIG + + if value not in [form.value for form in CONFIG.partial_data_formats]: + raise ValueError( + f"The format {value} is not one of the enabled_formats{CONFIG.partial_data_formats}." + ) + return value + + +class EntryMetadata(Meta): + """Contains the metadata for the attributes of an entry""" + + property_metadata: dict = StrictField( + None, + description="""An object containing per-entry and per-property metadata. The keys are the names of the fields in attributes for which metadata is available. The values belonging to these keys are dictionaries containing the relevant metadata fields. See also [Metadata properties](https://github.com/Materials-Consortia/OPTIMADE/blob/develop/optimade.rst#metadata-properties)""", + ) + + partial_data_links: dict[str, list[PartialDataLink]] = StrictField( + None, + description="""A dictionary, where the keys are the names of the properties in the attributes field for which the value is too large to be shared by default. + For each property one or more links are provided from which the value of the attribute can be retrieved.""", + ) + + @validator("property_metadata") + def check_property_metadata_subfields(cls, property_metadata): + from optimade.server.mappers.entries import ( + BaseResourceMapper, + ) + + if property_metadata: + for field in property_metadata: + if attribute_meta_dict := property_metadata.get(field): + for subfield in attribute_meta_dict: + BaseResourceMapper.check_starts_with_supported_prefix( + subfield, + "Currently no OPTIMADE fields have been defined for the per attribute metadata, thus only database and domain specific fields are allowed", + ) + return property_metadata + + @validator("partial_data_links") + def check_partial_data_links_subfields(cls, partial_data_links): + from optimade.server.mappers.entries import ( + BaseResourceMapper, + ) + + if partial_data_links: + for field in partial_data_links: + if attribute_partial_data_link := partial_data_links.get(field): + for subdict in attribute_partial_data_link: + for subfield in subdict.__dict__: + if subfield in ("link", "format"): + continue + BaseResourceMapper.check_starts_with_supported_prefix( + subfield, + "The only OPTIMADE fields defined under the 'partial_data_links' field are 'format'and ĺinks' all other database and domain specific fields must have a database/domain specific prefix.", + ) + return partial_data_links + + class EntryResource(Resource): """The base model for an entry resource.""" @@ -147,12 +227,59 @@ class EntryResource(Resource): Database-provider-specific properties need to include the database-provider-specific prefix (see section on Database-Provider-Specific Namespace Prefixes).""", ) + meta: Optional[EntryMetadata] = StrictField( + None, + description="""A [JSON API meta object](https://jsonapi.org/format/1.1/#document-meta) that is used to communicate metadata.""", + ) + relationships: Optional[EntryRelationships] = StrictField( None, description="""A dictionary containing references to other entries according to the description in section Relationships encoded as [JSON API Relationships](https://jsonapi.org/format/1.0/#document-resource-object-relationships). The OPTIONAL human-readable description of the relationship MAY be provided in the `description` field inside the `meta` dictionary of the JSON API resource identifier object.""", ) + @root_validator(pre=True) + def check_meta(cls, values): + """Validator to check whether meta field has been formatted correctly.""" + from optimade.server.mappers.entries import ( + BaseResourceMapper, + ) + + meta = values.get("meta") + if not meta: + return values + + # todo the code for property_metadata and partial_data_links is very similar so it should be possible to reduce the size of the code here. + if property_metadata := meta.pop("property_metadata", None): + # check that all the fields under property metadata are in attributes + attributes = values.get("attributes", {}) + for subfield in property_metadata: + if subfield not in attributes: + raise ValueError( + f"The keys under the field `property_metadata` need to match with the field names in attributes. The field {subfield} is however not in attributes." + ) + + if partial_data_links := meta.pop("partial_data_links", None): + # check that all the fields under property metadata are in attributes + attributes = values.get("attributes", {}) + for subfield in partial_data_links: + if subfield not in attributes: + raise ValueError( + f"The keys under the field `partial_data_links` need to match with the field names in attributes. The field {subfield} is however not in attributes." + ) + + # At this point I am getting ahead of the specification. There is the intention to allow database specific fields(with the database specific prefixes) here in line with the JSON API specification, but it has not been decided yet how this case should be handled in the property definitions. + for field in meta: + BaseResourceMapper.check_starts_with_supported_prefix( + field, + 'Currently no OPTIMADE fields other than "property_metadata" have been defined for the per entry "meta" field, thus only database and domain specific fields are allowed.', + ) + + values["meta"]["property_metadata"] = property_metadata + values["meta"]["partial_data_links"] = partial_data_links + + return values + class EntryInfoProperty(BaseModel): description: str = StrictField( diff --git a/optimade/models/partial_data.py b/optimade/models/partial_data.py new file mode 100644 index 000000000..8630545ff --- /dev/null +++ b/optimade/models/partial_data.py @@ -0,0 +1,182 @@ +from typing import Literal, Optional + +from pydantic import BaseModel + +from optimade.models.entries import EntryResource +from optimade.models.utils import OptimadeField, StrictField, SupportLevel + +__all__ = ("PartialDataHeader", "PartialDataResource", "LinksObject") + + +class LinksObject(BaseModel): + base_url: Optional[str] = OptimadeField( + None, + description="""The base URL of the implementation serving the database to which this property belongs.""", + ) + item_describedby: Optional[ + str + ] = OptimadeField( # The term describedby is used in the json Api, therefore we do not place an underscore between described and by. + None, + description="""A URL to an external JSON Schema that validates the data lines of the response. + The format and requirements on this schema are the same as for the inline schema field :field:`item_schema`. +The format of data lines of the response (i.e., all lines except the first and the last) depends on whether the header object specifies the layout as :val:`"dense"` or :val:`"sparse"`. +""", + ) + + +class PartialDataInfo(BaseModel): + version: str = OptimadeField( + ..., + description="""Specifies the minor version of the partial data format used. + The string MUST be of the format "MAJOR.MINOR", referring to the version of the OPTIMADE standard that describes the format. + The version number string MUST NOT be prefixed by, e.g., "v". In implementations of the present version of the standard, the value MUST be exactly :val:`1.2`. + A client MUST NOT expect to be able to parse the :field:`format` value if the field is not a string of the format MAJOR.MINOR or if the MAJOR version number is unrecognized.""", + ) + + +class PartialDataHeader(BaseModel): + optimade_partial_data: PartialDataInfo = OptimadeField( + ..., + description="""An object identifying the response as being on OPTIMADE partial data format. +It MUST contain the following key: +"version": String. Specifies the minor version of the partial data format used. The string MUST be of the format "MAJOR.MINOR", referring to the version of the OPTIMADE standard that describes the format. The version number string MUST NOT be prefixed by, e.g., "v". In implementations of the present version of the standard, the value MUST be exactly 1.2. A client MUST NOT expect to be able to parse the version value if the field is not a string of the format MAJOR.MINOR or if the MAJOR version number is unrecognized. + +- **Type**: Dictionary. + +- **Requirements/Conventions**: + - **Support**: MUST be supported by all implementations, MUST NOT be `null`. + +- **Examples**: + - `""optimade-partial-data": {"version": "1.2.0"}"`""", + support=SupportLevel.MUST, + ) + layout: Literal["dense", "sparse"] = OptimadeField( + ..., + description="""A string either equal to "dense" or "sparse" to indicate whether the returned format uses a dense or sparse layout. + +- **Type**: string. + +- **Requirements/Conventions**: + - **Support**: MUST be supported by all implementations, MUST NOT be `null`. + +- **Examples**: + - `"dense"` + - `"sparse"`""", + support=SupportLevel.MUST, + ) + returned_ranges: Optional[list[dict]] = OptimadeField( + None, + description="""Array of Objects. For dense layout, and sparse layout of one dimensional list properties, the array contains a single element which is a slice object representing the range of data present in the response. In the specific case of a hierarchy of list properties represented as a sparse multi-dimensional array, if the field "returned_ranges" is given, it MUST contain one slice object per dimension of the multi-dimensional array, representing slices for each dimension that cover the data given in the response. + +- **Type**: List of Dictionaries. + +- **Requirements/Conventions**: + - **Support**: SHOULD be supported by all implementations, SHOULD NOT be `null`. + +- **Examples**: + - `""returned_ranges": [{"start": 10, "stop": 20, "step": 2}]"` + - `""returned_ranges": [{"start": 10, "stop": 20, "step": 2}, {"start": 0, "stop": 9, "step": 1}]"`""", + support=SupportLevel.SHOULD, + ) + property_name: Optional[str] = OptimadeField( + None, + description="""The name of the property being provided. + +- **Type**: string. + +- **Requirements/Conventions**: + - **Support**: OPTIONAL support in implementations, i.e., MAY be `null`.. + +- **Examples**: + - `"cartesian_site_positions"`""", + support=SupportLevel.OPTIONAL, + ) + entry: Optional[dict] = OptimadeField( + None, + description=""" Object. An object that MUST have the following two keys: + + "id": String. The id of the entry of the property being provided. + "type": String. The type of the entry of the property being provided. + + +- **Type**: string. + +- **Requirements/Conventions**: + - **Support**: OPTIONAL support in implementations, i.e., MAY be `null`.. + +- **Examples**: + - `"{"id": "mpf_72", "type": structure"}`""", + support=SupportLevel.OPTIONAL, + ) + has_references: Optional[bool] = OptimadeField( + None, + description=""" An optional boolean to indicate whether any of the data lines in the response contains a reference marker. A value of false means that the client does not have to process any of the lines to detect reference markers, which may speed up the parsing. + +- **Type**: boolean. + +- **Requirements/Conventions**: + - **Support**: OPTIONAL support in implementations, i.e., MAY be `null`.. + +- **Examples**: + - `false`""", + support=SupportLevel.OPTIONAL, + ) + item_schema: Optional[dict] = OptimadeField( + None, + description="""An object that represents a JSON Schema that validates the data lines of the response. The format SHOULD be the relevant partial extract of a valid property definition as described in Property Definitions. If a schema is provided, it MUST be a valid JSON schema using the same version of JSON schema as described in that section. +- **Type**: dictionary. + +- **Requirements/Conventions**: + - **Support**: OPTIONAL support in implementations, i.e., MAY be `null`.. +""", + support=SupportLevel.OPTIONAL, + ) + + links: Optional[LinksObject] = OptimadeField( + None, + description=""" An object to provide relevant links for the property being provided. It MAY contain the following key: + + "base_url": String. The base URL of the implementation serving the database to which this property belongs. + "item_describedby": String. A URL to an external JSON Schema that validates the data lines of the response. The format and requirements on this schema are the same as for the inline schema field item_schema. + +- **Type**: dictionary. + +- **Requirements/Conventions**: + - **Support**: OPTIONAL support in implementations, i.e., MAY be `null`.. +""", + support=SupportLevel.OPTIONAL, + ) + parent_id: Optional[dict] = OptimadeField( + None, + description="""The id of the entry to which this partial data belongs. +""", + support=SupportLevel.OPTIONAL, + ) + + +class PartialDataFormat(BaseModel): + header: PartialDataHeader + data: list + + +class PartialDataResource(EntryResource): + type: str = StrictField( + "partial_data", + 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 `` and ID `` MUST be returned in response to a request for `//` under the versioned base URL. + + - **Examples**: + - `"structures"`""", + regex="^structures$", + support=SupportLevel.MUST, + queryable=SupportLevel.MUST, + ) + + attributes: PartialDataHeader # Todo make a better model for json response diff --git a/optimade/models/responses.py b/optimade/models/responses.py index c883a0093..0ef5278df 100644 --- a/optimade/models/responses.py +++ b/optimade/models/responses.py @@ -9,8 +9,10 @@ from optimade.models.jsonapi import Response from optimade.models.links import LinksResource from optimade.models.optimade_json import OptimadeError, ResponseMeta, Success +from optimade.models.partial_data import PartialDataResource from optimade.models.references import ReferenceResource from optimade.models.structures import StructureResource +from optimade.models.trajectories import TrajectoryResource from optimade.models.utils import StrictField __all__ = ( @@ -23,8 +25,11 @@ "EntryResponseMany", "StructureResponseOne", "StructureResponseMany", + "TrajectoryResponseOne", + "TrajectoryResponseMany", "ReferenceResponseOne", "ReferenceResponseMany", + "PartialDataResponse", ) @@ -103,6 +108,20 @@ class StructureResponseMany(EntryResponseMany): ) +class TrajectoryResponseOne(EntryResponseOne): + data: Union[TrajectoryResource, dict[str, Any], None] = StrictField( + ..., description="A single trajectories entry resource." + ) + + +class TrajectoryResponseMany(EntryResponseMany): + data: Union[list[TrajectoryResource], list[dict[str, Any]]] = StrictField( + ..., + description="List of unique OPTIMADE trajectories entry resource objects.", + uniqueItems=True, + ) + + class ReferenceResponseOne(EntryResponseOne): data: Union[ReferenceResource, dict[str, Any], None] = StrictField( ..., description="A single references entry resource." @@ -115,3 +134,9 @@ class ReferenceResponseMany(EntryResponseMany): description="List of unique OPTIMADE references entry resource objects.", uniqueItems=True, ) + + +class PartialDataResponse(EntryResponseOne): + data: Union[PartialDataResource, dict[str, Any], None] = StrictField( + ..., description="(Part of) the data for a single property of an entry." + ) diff --git a/optimade/models/structures.py b/optimade/models/structures.py index 565c02362..4193e52b9 100644 --- a/optimade/models/structures.py +++ b/optimade/models/structures.py @@ -7,6 +7,7 @@ from pydantic import BaseModel, conlist, root_validator, validator from optimade.models.entries import EntryResource, EntryResourceAttributes +from optimade.models.jsonapi import Attributes from optimade.models.utils import ( ANONYMOUS_ELEMENTS, CHEMICAL_FORMULA_REGEXP, @@ -30,6 +31,7 @@ "Assembly", "StructureResourceAttributes", "StructureResource", + "StructureAttributes", ) @@ -260,7 +262,7 @@ def check_self_consistency(cls, v, values): ) -class StructureResourceAttributes(EntryResourceAttributes): +class StructureAttributes(Attributes): """This class contains the Field for the attributes used to represent a structure, e.g. unit cell, atoms, positions.""" elements: Optional[list[str]] = OptimadeField( @@ -1092,6 +1094,10 @@ def validate_structure_features(cls, v, values): return v +class StructureResourceAttributes(EntryResourceAttributes, StructureAttributes): + pass + + class StructureResource(EntryResource): """Representing a structure.""" diff --git a/optimade/models/trajectories.py b/optimade/models/trajectories.py new file mode 100644 index 000000000..46120153e --- /dev/null +++ b/optimade/models/trajectories.py @@ -0,0 +1,124 @@ +# pylint: disable=no-self-argument,line-too-long,no-name-in-module +# import warnings +from typing import Optional + +from pydantic import validator + +from optimade.models.entries import EntryResource, EntryResourceAttributes +from optimade.models.structures import StructureAttributes + +# from optimade.server.warnings import MissingExpectedField +from optimade.models.utils import ( + CHEMICAL_SYMBOLS, + EXTRA_SYMBOLS, + OptimadeField, + StrictField, + SupportLevel, +) + +EXTENDED_CHEMICAL_SYMBOLS = set(CHEMICAL_SYMBOLS + EXTRA_SYMBOLS) + +__all__ = ( + "TrajectoryResourceAttributes", + "TrajectoryResource", +) + +CORRELATED_TRAJECTORY_FIELDS = ( + {"cartesian_site_positions", "species_at_sites"}, + {"species_at_sites", "species"}, +) + + +class TrajectoryResourceAttributes(EntryResourceAttributes, StructureAttributes): + """This class contains the Field for the attributes used to represent a trajectory.""" + + # todo get a better idea of how to inherrit from the Structure attributes while also allowing them to be list of fields as would be expected for a trajectory. + + reference_frames: Optional[int] = OptimadeField( + None, + description="""The indexes of a set of frames that give a good but very brief overview of the trajectory. + The first frame could for example be a starting configuration, the second a transition state and the third the final state. +- **Type**: list of integers +- **Requirements/Conventions**: The values MUST be larger than or equal to 0 and less than nframes. + + - **Support**: OPTIONAL support in implementations, i.e., MAY be :val:`null`. + - **Query**: Support for queries on this property is OPTIONAL. + If supported, filters MAY support only a subset of comparison operators. + +- **Examples**: + + - :val:`[0, 397, 1000]` + + """, + support=SupportLevel.OPTIONAL, + queryable=SupportLevel.OPTIONAL, + ) + + @validator("reference_frames", each_item=True) + def validate_reference_frames(cls, reference_frame): + if reference_frame < 0: + raise ValueError( + f"All values in reference_frames have to positive integers. {reference_frame} is not a positive integer." + ) + return reference_frame + + nframes: int = OptimadeField( + ..., + description=""" The number of frames in the trajectory as exposed by the API. + This value may deviate from the number of steps used to calculate the trajectory. + E.g., for a 10 ps simulation with calculation steps of 1 fs where data is stored once every 50 fs, nframes = 200. +- **Type**: integer +- **Requirements/Conventions**: + + - **Support**: MUST be supported by all implementations, i.e., MUST NOT be :val:`null`. + - **Query**: MUST be a queryable property with support for all mandatory filter features. + - The integer value MUST be equal to the length of the trajectory, that is, the number of frames. + - The integer MUST be a positive non-zero value. + +- **Querying**: + + - A filter that matches trajectories that have exactly 100 frames: + - :filter:`nframes=100` + - A filter that matches trajectories that have between 100 and 1000 frames: + - :filter:`nframes>=100 AND nframes<=1000` + +- **Examples**: + + - :val:`42` +""", + support=SupportLevel.MUST, + queryable=SupportLevel.MUST, + ) + + @validator("nframes") + def validate_nframes(cls, nframes): + if nframes < 0: + raise ValueError("nframes must be a positive integer.") + return nframes + + +class TrajectoryResource(EntryResource): + """Representing a trajectory.""" + + type: str = StrictField( + "trajectories", + const="trajectories", + 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 `` and ID `` MUST be returned in response to a request for `//` under the versioned base URL. + +- **Examples**: + - `"trajectories"`""", + pattern="^trajectories$", + support=SupportLevel.MUST, + queryable=SupportLevel.MUST, + ) + + attributes: TrajectoryResourceAttributes diff --git a/optimade/server/config.py b/optimade/server/config.py index 4dc360f73..3909913eb 100644 --- a/optimade/server/config.py +++ b/optimade/server/config.py @@ -14,7 +14,7 @@ from pydantic.env_settings import SettingsSourceCallable from optimade import __api_version__, __version__ -from optimade.models import Implementation, Provider +from optimade.models import Implementation, Provider # type: ignore[attr-defined] DEFAULT_CONFIG_FILE_PATH: str = str(Path.home().joinpath(".optimade.json")) """Default configuration file path. @@ -66,6 +66,19 @@ class SupportedBackend(Enum): MONGODB = "mongodb" MONGOMOCK = "mongomock" + +class SupportedResponseFormats(Enum): + """Enumeration of supported response formats. + + - 'JSON': [JSON](https://www.json.org/json-en.html) + - `JSONL`: [JSONL](https://jsonlines.org/) + + """ + + JSON = "json" + JSONL = "jsonlines" + + def config_file_settings(settings: BaseSettings) -> dict[str, Any]: """Configuration file settings source. @@ -87,8 +100,6 @@ def config_file_settings(settings: BaseSettings) -> dict[str, Any]: import json import os - import yaml - encoding = settings.__config__.env_file_encoding config_file = Path(os.getenv("OPTIMADE_CONFIG_FILE", DEFAULT_CONFIG_FILE_PATH)) @@ -100,6 +111,8 @@ def config_file_settings(settings: BaseSettings) -> dict[str, Any]: res = json.loads(config_file_content) except json.JSONDecodeError as json_exc: try: + import yaml + # This can essentially also load JSON files, as JSON is a subset of YAML v1, # but I suspect it is not as rigorous res = yaml.safe_load(config_file_content) @@ -178,6 +191,14 @@ class ServerConfig(BaseSettings): "structures", description="Mongo collection name for /structures endpoint resources", ) + trajectories_collection: str = Field( + "trajectories", + description="Mongo collection name for /trajectories endpoint resources", + ) + partial_data_collection: str = Field( + "fs", + description="Mongo Grid FS system containing the data that needs to be returned via the partial data mechanism.", + ) page_limit: int = Field(20, description="Default number of resources per page") page_limit_max: int = Field( 500, description="Max allowed number of resources per page" @@ -225,7 +246,8 @@ class ServerConfig(BaseSettings): description="General information about the provider of this OPTIMADE implementation", ) provider_fields: dict[ - Literal["links", "references", "structures"], + Literal["links", "references", "structures", "trajectories"], + list[Union[str, dict[Literal["name", "type", "unit", "description"], str]]], ] = Field( {}, @@ -234,7 +256,12 @@ class ServerConfig(BaseSettings): "broken down by endpoint." ), ) - aliases: dict[Literal["links", "references", "structures"], dict[str, str]] = Field( + supported_prefixes: list[str] = Field( + [], description="A list of all the prefixes that are supported by this server." + ) + aliases: dict[ + Literal["links", "references", "structures", "trajectories"], dict[str, str] + ] = Field( {}, description=( "A mapping between field names in the database with their corresponding OPTIMADE field" @@ -242,7 +269,7 @@ class ServerConfig(BaseSettings): ), ) length_aliases: dict[ - Literal["links", "references", "structures"], dict[str, str] + Literal["links", "references", "structures", "trajectories"], dict[str, str] ] = Field( {}, description=( @@ -308,6 +335,20 @@ class ServerConfig(BaseSettings): description="""If False, data from the database will not undergo validation before being emitted by the API, and only the mapping of aliases will occur.""", ) + partial_data_formats: list[SupportedResponseFormats] = Field( + ["json", "jsonlines"], + description="""A list of the response formats that are supported by this server. Must include the "json" format.""", + ) + max_response_size: dict[SupportedResponseFormats, int] = Field( + {"json": 10, "jsonlines": 40}, + description="""This dictionary contains the approximate maximum size for a trajectory response in megabytes for the different response_formats. The keys indicate the response_format and the values the maximum size.""", + ) + + @validator("supported_prefixes") + def add_own_prefix_to_supported_prefixes(value, values): + if values["provider"].prefix not in value: + value.append(values["provider"].prefix) + return value @validator("implementation", pre=True) def set_implementation_version(cls, v): diff --git a/optimade/server/data/6509be05a54743f440a7f36b:cartesian_site_positions.npy b/optimade/server/data/6509be05a54743f440a7f36b:cartesian_site_positions.npy new file mode 100644 index 000000000..edb294388 Binary files /dev/null and b/optimade/server/data/6509be05a54743f440a7f36b:cartesian_site_positions.npy differ diff --git a/optimade/server/data/6516a2990672a155c7351bab:cartesian_site_positions.npy b/optimade/server/data/6516a2990672a155c7351bab:cartesian_site_positions.npy new file mode 100644 index 000000000..9fb6a51f7 Binary files /dev/null and b/optimade/server/data/6516a2990672a155c7351bab:cartesian_site_positions.npy differ diff --git a/optimade/server/data/__init__.py b/optimade/server/data/__init__.py index 87060d387..a5d53e1aa 100644 --- a/optimade/server/data/__init__.py +++ b/optimade/server/data/__init__.py @@ -8,8 +8,41 @@ "references": "test_references.json", "links": "test_links.json", "providers": "providers.json", + "trajectories": "test_trajectories.json", } +data_files = [ + ( + "mpf_551:cartesian_site_positions.npy", + "numpy", + { + "endpoint": "structures", + "parent_id": "mpf_551", + "property_name": "cartesian_site_positions", + "dim_names": ["dim_sites", "dim_cartesian_dimensions"], + }, + ), + ( + "6509be05a54743f440a7f36b:cartesian_site_positions.npy", + "numpy", + { + "endpoint": "trajectories", + "parent_id": "6509be05a54743f440a7f36b", + "property_name": "cartesian_site_positions", + "dim_names": ["dim_frames", "dim_sites", "dim_cartesian_dimensions"], + }, + ), + ( + "6516a2990672a155c7351bab:cartesian_site_positions.npy", + "numpy", + { + "endpoint": "trajectories", + "parent_id": "6516a2990672a155c7351bab", + "property_name": "cartesian_site_positions", + "dim_names": ["dim_frames", "dim_sites", "dim_cartesian_dimensions"], + }, + ), +] for var, path in data_paths.items(): try: diff --git a/optimade/server/data/mpf_551:cartesian_site_positions.npy b/optimade/server/data/mpf_551:cartesian_site_positions.npy new file mode 100644 index 000000000..a996c9266 Binary files /dev/null and b/optimade/server/data/mpf_551:cartesian_site_positions.npy differ diff --git a/optimade/server/data/test_references.json b/optimade/server/data/test_references.json index 05cb7932c..6928fb9b7 100644 --- a/optimade/server/data/test_references.json +++ b/optimade/server/data/test_references.json @@ -59,6 +59,28 @@ "journal": "JACS", "doi": "10.1038/00000" }, + { + "_id": "62696ac7eef0323c842f9f50", + "title": "Molecular Dynamics Simulations Related to SARS-CoV-2", + "organization": "D. E. Shaw Research", + "year": "2020", + "note": "Only a small fraction of trajectory DESRES-ANTON-10875754 is shared here as an example.", + "url": "http://www.deshawresearch.com/resources_sarscov2.html", + "id": "Shaw2020a", + "last_modified": {"$date":"2022-04-27T16:09:41.000Z"} + }, + { + "_id": "62696ac7eef0323c842f9f4f", + "title": "Creative Commons Attribution 4.0 International Public License", + "url": "https://creativecommons.org/licenses/by/4.0/legalcode", + "note": "Entries with a reference to this licence were and are shared under this licence.", + "organization": "Creative Commons", + "id": "CCBy4", + "last_modified": { + "$date": "2022-04-27T16:09:41.000Z" + } + }, + { "_id": { "$oid": "98fb441f053b1744107019e3" diff --git a/optimade/server/data/test_structures.json b/optimade/server/data/test_structures.json index 5a91e3b61..7ef98df69 100644 --- a/optimade/server/data/test_structures.json +++ b/optimade/server/data/test_structures.json @@ -3,6 +3,13 @@ "_id": { "$oid": "5cfb441f053b174410700d02" }, + "meta": { + "property_metadata": { + "elements_ratios": { + "_exmpl_originates_from_project": "Pure Metals" + } + } + }, "assemblies": null, "chemsys": "Ac", "cartesian_site_positions": [ @@ -80,6 +87,13 @@ "_id": { "$oid": "5cfb441f053b174410700d03" }, + "meta": { + "property_metadata": { + "elements_ratios": { + "_exmpl_originates_from_project": "Actinides_Alloys" + } + } + }, "assemblies": null, "chemsys": "Ac-Ag-Ir", "cartesian_site_positions": [ @@ -197,6 +211,13 @@ "_id": { "$oid": "5cfb441f053b174410700d04" }, + "meta": { + "property_metadata": { + "elements_ratios": { + "_exmpl_originates_from_project": "Actinides_Alloys" + } + } + }, "assemblies": null, "chemsys": "Ac-Ag-Pb", "cartesian_site_positions": [ @@ -323,6 +344,13 @@ "_id": { "$oid": "5cfb441f053b174410700d18" }, + "meta": { + "property_metadata": { + "elements_ratios": { + "_exmpl_originates_from_project": "Actinides_Alloys" + } + } + }, "assemblies": null, "chemsys": "Ac-Mg", "cartesian_site_positions": [ @@ -413,6 +441,13 @@ "_id": { "$oid": "5cfb441f053b174410700d1f" }, + "meta": { + "property_metadata": { + "elements_ratios": { + "_exmpl_originates_from_project": null + } + } + }, "assemblies": null, "chemsys": "Ac-O", "cartesian_site_positions": [ @@ -515,6 +550,11 @@ "_id": { "$oid": "5cfb441f053b174410700d6f" }, + "meta": { + "property_metadata": { + "elements_ratios": {} + } + }, "assemblies": null, "chemsys": "Ac-Cu-F-O", "cartesian_site_positions": [ @@ -639,6 +679,13 @@ "_id": { "$oid": "5cfb441f053b174410700dc9" }, + "meta": { + "property_metadata": { + "elements_ratios": { + "_exmpl_originates_from_project": "Pure Metals" + } + } + }, "assemblies": null, "chemsys": "Ag", "cartesian_site_positions": [ @@ -706,6 +753,11 @@ "_id": { "$oid": "5cfb441f053b174410700ddd" }, + "meta": { + "property_metadata": { + "elements_ratios": null + } + }, "assemblies": null, "chemsys": "Ag-Br-Cl-Te", "cartesian_site_positions": [ @@ -896,6 +948,9 @@ "_id": { "$oid": "5cfb441f053b174410700e04" }, + "meta": { + "property_metadata": {} + }, "assemblies": null, "chemsys": "Ag-C-Cl-N-O-S", "cartesian_site_positions": [ @@ -1072,6 +1127,9 @@ "_id": { "$oid": "5cfb441f053b174410700e11" }, + "meta": { + "property_metadata": null + }, "assemblies": null, "chemsys": "Ag-C-Cl-H-N", "cartesian_site_positions": [ @@ -2067,378 +2125,12 @@ }, "assemblies": null, "chemsys": "Ag-B-C-Cl-H-N-O-P", - "cartesian_site_positions": [ - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ], - [ - 0.449480176317956, - 0.449480176317956, - 0.449480176317956 - ] - ], + "cartesian_site_positions": null, + "meta": { + "partial_data_links": { + "cartesian_site_positions": [] + } + }, "dimension_types": [ 1, 1, diff --git a/optimade/server/data/test_trajectories.json b/optimade/server/data/test_trajectories.json new file mode 100644 index 000000000..02d25c9ee --- /dev/null +++ b/optimade/server/data/test_trajectories.json @@ -0,0 +1,416 @@ +[{ + "_id": "6509be05a54743f440a7f36b", + "elements": [ + "Ar" + ], + "nelements": 1, + "elements_ratios": [ + 1 + ], + "chemical_formula_descriptive": "Ar32", + "chemical_formula_reduced": "Ar", + "chemical_formula_anonymous": "A", + "dimension_types": null, + "nperiodic_dimensions": null, + "lattice_vectors": null, + "cartesian_site_positions": null, + "nsites": 32, + "species_at_sites": [ + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar", + "Ar" + ], + "species": [ + { + "name": "Ar", + "concentration": [ + 1 + ], + "chemical_symbols": [ + "Ar" + ], + "_biomol_atom_name": "Ar" + } + ], + "structure_features": [], + "nframes": 50000, + "reference_frames": [ + 50000 + ], + "type": "trajectories", + "last_modified": "2023-09-19T15:28:04.000Z", + "time_step": 0.10000000029814057, + "id": "6509be05a54743f440a7f36b", + "meta": { + "partial_data_links": { + "cartesian_site_positions": [] + } + } +},{ + "_id": "6516a2990672a155c7351bab", + "elements": [ + "C", + "H", + "N", + "O", + "Rh", + "S" + ], + "nelements": 6, + "elements_ratios": [ + 0.04615385, + 0.64102564, + 0.01025641, + 0.29230769, + 0.00512821, + 0.00512821 + ], + "chemical_formula_descriptive": "C9H125N2O57Rh1S1", + "chemical_formula_reduced": "C9H125N2O57RhS", + "chemical_formula_anonymous": "A125B57C9D2EF", + "dimension_types": [ + 1, + 1, + 1 + ], + "nperiodic_dimensions": 3, + "lattice_vectors": null, + "cartesian_site_positions": null, + "nsites": 195, + "species_at_sites": [ + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "H", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "O", + "C", + "C", + "C", + "C", + "C", + "C", + "C", + "C", + "C", + "N", + "N", + "S", + "Rh" + ], + "species": [ + { + "name": "N", + "concentration": [ + 1 + ], + "chemical_symbols": [ + "N" + ] + }, + { + "name": "O", + "concentration": [ + 1 + ], + "chemical_symbols": [ + "O" + ] + }, + { + "name": "Rh", + "concentration": [ + 1 + ], + "chemical_symbols": [ + "Rh" + ] + }, + { + "name": "S", + "concentration": [ + 1 + ], + "chemical_symbols": [ + "S" + ] + }, + { + "name": "H", + "concentration": [ + 1 + ], + "chemical_symbols": [ + "H" + ] + }, + { + "name": "C", + "concentration": [ + 1 + ], + "chemical_symbols": [ + "C" + ] + } + ], + "structure_features": [], + "nframes": 8, + "reference_frames": [ + 8 + ], + "type": "trajectories", + "last_modified": "2023-09-29T10:10:33.000Z", + "relationships": { + "references": { + "data": [ + { + "type": "references", + "id": "Bergsma2010" + } + ] + } + }, + "program": "CPMD", + "id": "6516a2990672a155c7351bab", + "meta": { + "partial_data_links": { + "cartesian_site_positions": null + }, + "property_metadata": { + "cartesian_site_positions": { + "range": { + "layout": "dense", + "indexable_dim": [ + "dim_frames", + "dim_sites", + "dim_cartesian_dimensions" + ], + "data_range": [ + { + "start": 1, + "stop": 8, + "step": 1 + }, + { + "start": 1, + "stop": 195, + "step": 1 + }, + { + "start": 1, + "stop": 3, + "step": 1 + } + ], + "nvalues": 4680 + } + } + } + } +}] diff --git a/optimade/server/entry_collections/entry_collections.py b/optimade/server/entry_collections/entry_collections.py index cbaf59209..a6fbf90f4 100644 --- a/optimade/server/entry_collections/entry_collections.py +++ b/optimade/server/entry_collections/entry_collections.py @@ -10,9 +10,16 @@ from optimade.exceptions import BadRequest, Forbidden, NotFound from optimade.filterparser import LarkParser from optimade.models.entries import EntryResource -from optimade.server.config import CONFIG, SupportedBackend -from optimade.server.mappers import BaseResourceMapper -from optimade.server.query_params import EntryListingQueryParams, SingleEntryQueryParams +from optimade.server.config import CONFIG, SupportedBackend, SupportedResponseFormats +from optimade.server.mappers import ( # type: ignore[attr-defined] + BaseResourceMapper, + PartialDataMapper, +) +from optimade.server.query_params import ( + EntryListingQueryParams, + PartialDataQueryParams, + SingleEntryQueryParams, +) from optimade.warnings import ( FieldValueNotRecognized, QueryParamNotUsed, @@ -41,6 +48,17 @@ def create_collection( SupportedBackend.MONGODB, SupportedBackend.MONGOMOCK, ): + from optimade.models import PartialDataResource + + if resource_cls is PartialDataResource: + from optimade.server.entry_collections.mongo import GridFSCollection + + return GridFSCollection( + name=name, + resource_cls=resource_cls, + resource_mapper=resource_mapper, + ) + from optimade.server.entry_collections.mongo import MongoCollection return MongoCollection( @@ -137,8 +155,11 @@ def count(self, **kwargs: Any) -> Union[int, None]: """ def find( - self, params: Union[EntryListingQueryParams, SingleEntryQueryParams] - ) -> tuple[Union[None, dict, list[dict]], Optional[int], bool, set[str], set[str],]: + self, + params: Union[ + EntryListingQueryParams, SingleEntryQueryParams, PartialDataQueryParams + ], + ) -> tuple[Union[None, dict, list[dict]], Optional[int], bool, set[str], set[str]]: """ Fetches results and indicates if more data is available. @@ -159,7 +180,9 @@ def find( """ criteria = self.handle_query_params(params) - single_entry = isinstance(params, SingleEntryQueryParams) + single_entry = isinstance( + params, (SingleEntryQueryParams, PartialDataQueryParams) + ) response_fields = criteria.pop("fields") raw_results, data_returned, more_data_available = self._run_db_query( @@ -167,40 +190,45 @@ def find( ) exclude_fields = self.all_fields - response_fields - include_fields = ( - response_fields - self.resource_mapper.TOP_LEVEL_NON_ATTRIBUTES_FIELDS - ) - bad_optimade_fields = set() - bad_provider_fields = set() supported_prefixes = self.resource_mapper.SUPPORTED_PREFIXES all_attributes = self.resource_mapper.ALL_ATTRIBUTES - for field in include_fields: - if field not in all_attributes: - if field.startswith("_"): - if any( - field.startswith(f"_{prefix}_") for prefix in supported_prefixes - ): - bad_provider_fields.add(field) - else: - bad_optimade_fields.add(field) - if bad_provider_fields: - warnings.warn( - message=f"Unrecognised field(s) for this provider requested in `response_fields`: {bad_provider_fields}.", - category=UnknownProviderProperty, + if not self.resource_mapper == PartialDataMapper: + include_fields = ( + response_fields - self.resource_mapper.TOP_LEVEL_NON_ATTRIBUTES_FIELDS ) + bad_optimade_fields = set() + bad_provider_fields = set() + + for field in include_fields: + if field not in all_attributes: + if field.startswith("_"): + if any( + field.startswith(f"_{prefix}_") + for prefix in supported_prefixes + ): + bad_provider_fields.add(field) + else: + bad_optimade_fields.add(field) + + if bad_provider_fields: + warnings.warn( + message=f"Unrecognised field(s) for this provider requested in `response_fields`: {bad_provider_fields}.", + category=UnknownProviderProperty, + ) - if bad_optimade_fields: - raise BadRequest( - detail=f"Unrecognised OPTIMADE field(s) in requested `response_fields`: {bad_optimade_fields}." - ) + if bad_optimade_fields: + raise BadRequest( + detail=f"Unrecognised OPTIMADE field(s) in requested `response_fields`: {bad_optimade_fields}." + ) + else: + include_fields = set() results: Union[None, list[dict], dict] = None if raw_results: results = [self.resource_mapper.map_back(doc) for doc in raw_results] - if single_entry: results = results[0] # type: ignore[assignment] @@ -298,7 +326,10 @@ def get_attribute_fields(self) -> set[str]: return set(attributes["properties"].keys()) def handle_query_params( - self, params: Union[EntryListingQueryParams, SingleEntryQueryParams] + self, + params: Union[ + EntryListingQueryParams, SingleEntryQueryParams, PartialDataQueryParams + ], ) -> dict[str, Any]: """Parse and interpret the backend-agnostic query parameter models into a dictionary that can be used by the specific backend. @@ -330,12 +361,11 @@ def handle_query_params( cursor_kwargs["filter"] = {} # response_format - if ( - getattr(params, "response_format", False) - and params.response_format != "json" + if getattr(params, "response_format", False) and params.response_format not in ( + x.value for x in SupportedResponseFormats ): raise BadRequest( - detail=f"Response format {params.response_format} is not supported, please use response_format='json'" + detail=f"Response format {params.response_format} is not supported, please use one of the supported response formats: {', '.join((x.value for x in SupportedResponseFormats))}" ) # page_limit diff --git a/optimade/server/entry_collections/mongo.py b/optimade/server/entry_collections/mongo.py index 3036aa729..5bfb56a25 100644 --- a/optimade/server/entry_collections/mongo.py +++ b/optimade/server/entry_collections/mongo.py @@ -1,12 +1,17 @@ from typing import Any, Optional, Union +from optimade.exceptions import BadRequest from optimade.filtertransformers.mongo import MongoTransformer -from optimade.models import EntryResource -from optimade.server.config import CONFIG, SupportedBackend +from optimade.models import EntryResource # type: ignore[attr-defined] +from optimade.server.config import CONFIG, SupportedBackend, SupportedResponseFormats from optimade.server.entry_collections import EntryCollection from optimade.server.logger import LOGGER -from optimade.server.mappers import BaseResourceMapper -from optimade.server.query_params import EntryListingQueryParams, SingleEntryQueryParams +from optimade.server.mappers import BaseResourceMapper # type: ignore[attr-defined] +from optimade.server.query_params import ( + EntryListingQueryParams, + PartialDataQueryParams, + SingleEntryQueryParams, +) if CONFIG.database_backend.value == "mongodb": from pymongo import MongoClient, version_tuple @@ -21,15 +26,287 @@ LOGGER.info("Using: Real MongoDB (pymongo)") elif CONFIG.database_backend.value == "mongomock": + import mongomock.gridfs from mongomock import MongoClient LOGGER.info("Using: Mock MongoDB (mongomock)") + mongomock.gridfs.enable_gridfs_integration() if CONFIG.database_backend.value in ("mongomock", "mongodb"): CLIENT = MongoClient(CONFIG.mongo_uri) + import gridfs -class MongoCollection(EntryCollection): +class MongoBaseCollection(EntryCollection): + def _check_aliases(self, aliases): + """Check that aliases do not clash with mongo keywords.""" + if any( + alias[0].startswith("$") or alias[1].startswith("$") for alias in aliases + ): + raise RuntimeError(f"Cannot define an alias starting with a '$': {aliases}") + + +class GridFSCollection(MongoBaseCollection): + """Class for querying gridfs collections (implemented by either pymongo or mongomock).""" + + def __init__( + self, + name: str, + resource_cls: type[EntryResource], + resource_mapper: type[BaseResourceMapper], + database: str = CONFIG.mongo_database, + ): + """Initialize the GridFSCollection for the given parameters. + + Parameters: + name: The name of the collection. + resource_cls: The type of entry resource that is stored by the collection. + resource_mapper: A resource mapper object that handles aliases and + format changes between deserialization and response. + database: The name of the underlying MongoDB database to connect to. + + """ + super().__init__( + resource_cls, + resource_mapper, + MongoTransformer(mapper=resource_mapper), + ) + db = MongoClient(CONFIG.mongo_uri)[ + database + ] # Somehow importing the client from optimade.server.entry_collections.mongo gives an error that the type of db is not "Database" even though it is. + + self.collection = gridfs.GridFS(db, name) + + # check aliases do not clash with mongo operators + self._check_aliases(self.resource_mapper.all_aliases()) + self._check_aliases(self.resource_mapper.all_length_aliases()) + + def __len__(self) -> int: + """Returns the total number of entries in the collection.""" + return len(self.collection.list()) + + def count(self, **kwargs: Any) -> int: + """Returns the number of entries matching the query specified + by the keyword arguments. + + Parameters: + **kwargs: Query parameters as keyword arguments. The keys + 'filter', 'skip', 'limit', 'hint' and 'maxTimeMS' will be passed + to the `pymongo.collection.Collection.count_documents` method. + + """ + for k in list(kwargs.keys()): + if k not in ("filter", "skip", "limit", "hint", "maxTimeMS"): + del kwargs[k] + if "filter" not in kwargs: # "filter" is needed for count_documents() + kwargs["filter"] = {} + return len(self.collection.find(**kwargs)) + + def insert(self, data: list) -> None: + """Add the given entries to the underlying database. + + Warning: + No validation is performed on the incoming data. + + Arguments: + data: a list of dictionaries. Each dictionary contains the data belonging to one file. + These dictionaries contain the fields: + data: The file content to add to gridfs. + filename: The filename of the added content. + metadata: extra metadata to add to the gridfs entry. + """ + for entry in data: # todo check whether I can insert multiple files in one go. + self.collection.put(**entry) + + def handle_query_params( + self, params: Union[SingleEntryQueryParams, PartialDataQueryParams] # type: ignore[override] + ) -> dict[str, Any]: + """Parse and interpret the backend-agnostic query parameter models into a dictionary + that can be used by MongoDB. + + This Mongo-specific method calls the base `EntryCollection.handle_query_params` method + and adds additional handling of the MongoDB ObjectID type. + + Parameters: + params: The initialized query parameter model from the server. + + Raises: + Forbidden: If too large of a page limit is provided. + BadRequest: If an invalid request is made, e.g., with incorrect fields + or response format. + + Returns: + A dictionary representation of the query parameters. + + """ + + criteria = super().handle_query_params(params) + # Handle MongoDB ObjectIDs: + # - If they were not requested, then explicitly remove them + # - If they were requested, then cast them to strings in the response + if "_id" not in criteria.get("projection", {}): + criteria["projection"]["_id"] = False + + if "page_above" in criteria: + raise NotImplementedError( + "`page_above` is not implemented for this backend." + ) + + if criteria.get("projection", {}).get("_id"): + criteria["projection"]["_id"] = {"$toString": "$_id"} + + if isinstance(params, PartialDataQueryParams): + entry_id = params.filter.split("=")[1][1:-1] + criteria["filter"] = { + "filename": { + "$eq": f"{entry_id}:{params.response_fields}.npy" + } # todo Should we add support for other file extensions? + } # Todo make sure response fields has only one value + + # response_format + if getattr(params, "response_format", False) and params.response_format not in ( + x.value for x in CONFIG.partial_data_formats + ): + raise BadRequest( + detail=f"Response format {params.response_format} is not supported, please use one of the supported response formats: {', '.join((x.value for x in CONFIG.partial_data_formats))}" + ) + criteria["response_format"] = params.response_format + criteria["property_ranges"] = params.property_ranges + + return criteria + + # todo test if it is more efficient to use the get method of gridfs + def _run_db_query( + self, + criteria: dict[str, Any], + single_entry: bool = False, + ) -> tuple[list[dict[str, Any]], int, bool]: + """Run the query on the backend and collect the results. + + Arguments: + criteria: A dictionary representation of the query parameters. + single_entry: Whether or not the caller is expecting a single entry response. + + Returns: + The list of entries from the database (without any re-mapping), the total number of + entries matching the query and a boolean for whether or not there is more data available. + + """ + + # TODO handle case where the type does not have a fixed width. For example strings or dictionaries. + response_format = criteria.pop("response_format") + max_return_size = ( + CONFIG.max_response_size[SupportedResponseFormats(response_format)] + * 1024 + * 1024 + ) # todo adjust for different output formats(take into account that the number of numbers to read is larger for a text based output format than for a binary format. + results = [] + filterdict = criteria.pop("filter", {}) + + # I have tried to use just **criteria as is mentioned in the documentation but this does not seem to work. + gridcursor = self.collection.find(filterdict) + more_data_available = False + nresults = 0 + # todo add code that can handle very sparse requests where reading individual sections of files is more efficient. + for ( + file_obj + ) in ( + gridcursor + ): # Next throws an error when there are zero files returned, so I use a for loop instead to get one result. + nresults += 1 + metadata = file_obj.metadata + property_ranges = self.parse_property_ranges( + criteria.pop("property_ranges", None), + metadata["slice_obj"], + metadata["dim_names"], + ) + item_size = metadata["dtype"]["itemsize"] + dim_sizes = [ + (i["stop"] - i["start"] + 1) // i["step"] for i in metadata["slice_obj"] + ] + top_stepsize = 1 + for i in dim_sizes[1:]: + top_stepsize *= i + offset = (property_ranges[0]["start"] - 1) * item_size * top_stepsize + np_header = file_obj.readline() + file_obj.seek( + offset + len(np_header) + ) # set the correct starting point fo the read from the gridfs file system. + if (max_return_size / item_size) < ( + 1 + property_ranges[0]["stop"] - property_ranges[0]["start"] + ) * top_stepsize: # case more data requested then fits in the response + more_data_available = True + n_val_return = max_return_size / item_size + n_outer = max( + int(n_val_return / top_stepsize), 1 + ) # always read at least one line for now. + read_size = n_outer * top_stepsize * item_size + shape = [n_outer] + dim_sizes[1:] + else: + read_size = ( + (1 + property_ranges[0]["stop"] - property_ranges[0]["start"]) + * top_stepsize + * item_size + ) + shape = [ + 1 + property_ranges[0]["stop"] - property_ranges[0]["start"] + ] + dim_sizes[1:] + + values = file_obj.read(read_size) + entry = { + "id": metadata.get("parent_id", None), + "type": metadata.get("endpoint", None), + } + results = [ + { + "type": "partial_data", + "id": str(file_obj._id), + "property_name": metadata.get("property_name", None), + "entry": entry, + "data": values, + "dtype": metadata["dtype"], + "shape": shape, + "property_ranges": property_ranges, + } + ] + if more_data_available: + property_ranges_str = f"property_ranges={metadata['dim_names'][0]}:{property_ranges[0]['start']+n_outer}:{property_ranges[0]['stop']}:{property_ranges[0]['step']}" + for i, name in enumerate(metadata["dim_names"][1:]): + property_ranges_str += f",{name}:{property_ranges[i+1]['start']}:{property_ranges[i+1]['stop']}:{property_ranges[i+1]['step']}" + results[0][ + "next" + ] = f"{CONFIG.base_url}/partial_data/{metadata['parent_id']}?response_fields={metadata['property_name']}&response_format={response_format}&{property_ranges_str}" + break + + return results, nresults, more_data_available + + def parse_property_ranges( + self, property_range_str: str, attribute_slice_obj: list, dim_names: list + ) -> list[dict]: + property_range_dict = {} + if property_range_str: + ranges = [dimrange.split(":") for dimrange in property_range_str.split(",")] + + for subrange in ranges: + property_range_dict[subrange[0]] = { + "start": int(subrange[1]) + if subrange[1] + else attribute_slice_obj[dim_names.index(subrange[0])]["start"], + "stop": int(subrange[2]) + if subrange[2] + else attribute_slice_obj[dim_names.index(subrange[0])]["stop"], + "step": int(subrange[3]) + if subrange[3] + else attribute_slice_obj[dim_names.index(subrange[0])]["step"], + } + for i, dim in enumerate(dim_names): + if dim not in property_range_dict: + property_range_dict[dim] = attribute_slice_obj[i] + + return [property_range_dict[dim] for dim in dim_names] + + +class MongoCollection(MongoBaseCollection): """Class for querying MongoDB collections (implemented by either pymongo or mongomock) containing serialized [`EntryResource`][optimade.models.entries.EntryResource]s objects. @@ -182,10 +459,3 @@ def _run_db_query( more_data_available = False return results, data_returned, more_data_available - - def _check_aliases(self, aliases): - """Check that aliases do not clash with mongo keywords.""" - if any( - alias[0].startswith("$") or alias[1].startswith("$") for alias in aliases - ): - raise RuntimeError(f"Cannot define an alias starting with a '$': {aliases}") diff --git a/optimade/server/exception_handlers.py b/optimade/server/exception_handlers.py index c79eaa637..909ad61b8 100644 --- a/optimade/server/exception_handlers.py +++ b/optimade/server/exception_handlers.py @@ -231,7 +231,7 @@ def general_exception_handler(request: Request, exc: Exception) -> JSONAPIRespon (OptimadeHTTPException, http_exception_handler), (RequestValidationError, request_validation_exception_handler), (ValidationError, validation_exception_handler), - (VisitError, grammar_not_implemented_handler), + (VisitError, grammar_not_implemented_handler), # type: ignore[list-item] # not entirely sure why this entry triggers mypy (NotImplementedError, not_implemented_handler), # type: ignore[list-item] # not entirely sure why this entry triggers mypy (Exception, general_exception_handler), ] diff --git a/optimade/server/main.py b/optimade/server/main.py index 4f95dcccb..899bd0cec 100644 --- a/optimade/server/main.py +++ b/optimade/server/main.py @@ -25,8 +25,10 @@ info, landing, links, + partial_data, references, structures, + trajectories, versions, ) from optimade.server.routers.utils import BASE_URL_PREFIXES, JSONAPIResponse @@ -49,7 +51,6 @@ 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__, @@ -68,6 +69,41 @@ from optimade.server.routers import ENTRY_COLLECTIONS from optimade.server.routers.utils import get_providers + # Todo Do we need to check a file is not already stored in gridfs? + # Load test data from files into gridfs + + if CONFIG.database_backend.value in ("mongomock", "mongodb"): + from pathlib import Path + + import numpy + + from optimade.server.routers.partial_data import partial_data_coll + + # todo create seperate function for storing data files in gridfs + # read_array_header function originally from https://stackoverflow.com/a/64226659 by https://stackoverflow.com/users/982257/iguananaut + def read_array_header(fobj): + version = numpy.lib.format.read_magic(fobj) + func_name = "read_array_header_" + "_".join(str(v) for v in version) + func = getattr(numpy.lib.format, func_name) + return func(fobj) + + for filename, filetype, metadata in getattr(data, "data_files", []): + with open(Path(__file__).parent / "data" / filename, "rb") as f: + if filetype == "numpy": + numpy_meta = read_array_header(f) + if "slice_obj" not in metadata: + slice_obj = [ + {"start": 1, "stop": i, "step": 1} for i in numpy_meta[0] + ] + metadata["slice_obj"] = slice_obj + if "dtype" not in metadata: + metadata["dtype"] = { + "name": numpy_meta[2].name, + "itemsize": numpy_meta[2].itemsize, + } + f.seek(0) + partial_data_coll.insert([{"data": f, "filename": filename, "metadata": metadata}]) # type: ignore[list-item] # Todo : Perhaps this can be reduced to a single insert statement. + def load_entries(endpoint_name: str, endpoint_collection: EntryCollection): LOGGER.debug("Loading test %s...", endpoint_name) @@ -103,13 +139,30 @@ def load_entries(endpoint_name: str, endpoint_collection: EntryCollection): app.add_exception_handler(exception, handler) # Add various endpoints to unversioned URL -for endpoint in (info, links, references, structures, landing, versions): +for endpoint in ( + info, + links, + references, + structures, + trajectories, + landing, + versions, + partial_data, +): app.include_router(endpoint.router) def add_major_version_base_url(app: FastAPI): """Add mandatory vMajor endpoints, i.e. all except versions.""" - for endpoint in (info, links, references, structures, landing): + for endpoint in ( + info, + links, + references, + structures, + trajectories, + landing, + partial_data, + ): app.include_router(endpoint.router, prefix=BASE_URL_PREFIXES["major"]) @@ -121,7 +174,15 @@ def add_optional_versioned_base_urls(app: FastAPI): ``` """ for version in ("minor", "patch"): - for endpoint in (info, links, references, structures, landing): + for endpoint in ( + info, + links, + references, + structures, + trajectories, + landing, + partial_data, + ): app.include_router(endpoint.router, prefix=BASE_URL_PREFIXES[version]) diff --git a/optimade/server/mappers/__init__.py b/optimade/server/mappers/__init__.py index c38e6ccd0..bc80cb5c6 100644 --- a/optimade/server/mappers/__init__.py +++ b/optimade/server/mappers/__init__.py @@ -1,12 +1,16 @@ # pylint: disable=undefined-variable from .entries import * # noqa: F403 from .links import * # noqa: F403 +from .partial_data import * # noqa: F403 from .references import * # noqa: F403 from .structures import * # noqa: F403 +from .trajectories import * # noqa: F403 __all__ = ( entries.__all__ # type: ignore[name-defined] # noqa: F405 + links.__all__ # type: ignore[name-defined] # noqa: F405 + + partial_data.__all__ # type: ignore[name-defined] # noqa: F405 + references.__all__ # type: ignore[name-defined] # noqa: F405 + structures.__all__ # type: ignore[name-defined] # noqa: F405 + + trajectories.__all__ # type: ignore[name-defined] # noqa: F405 ) diff --git a/optimade/server/mappers/entries.py b/optimade/server/mappers/entries.py index b14edfc17..2d35c487e 100644 --- a/optimade/server/mappers/entries.py +++ b/optimade/server/mappers/entries.py @@ -9,7 +9,7 @@ # so that the global caches can be set to the correct size. # See https://github.com/Materials-Consortia/optimade-python-tools/issues/1434 # for the details. -NUM_ENTRY_TYPES = 4 +NUM_ENTRY_TYPES = 5 __all__ = ("BaseResourceMapper",) @@ -73,8 +73,14 @@ class BaseResourceMapper: LENGTH_ALIASES: tuple[tuple[str, str], ...] = () PROVIDER_FIELDS: tuple[str, ...] = () ENTRY_RESOURCE_CLASS: type[EntryResource] = EntryResource - RELATIONSHIP_ENTRY_TYPES: set[str] = {"references", "structures"} - TOP_LEVEL_NON_ATTRIBUTES_FIELDS: set[str] = {"id", "type", "relationships", "links"} + RELATIONSHIP_ENTRY_TYPES: set[str] = {"references", "structures", "trajectories"} + TOP_LEVEL_NON_ATTRIBUTES_FIELDS: set[str] = { + "id", + "type", + "relationships", + "links", + "meta", + } @classmethod @lru_cache(maxsize=NUM_ENTRY_TYPES) @@ -118,18 +124,10 @@ def all_aliases(cls) -> Iterable[tuple[str, str]]: @classproperty @lru_cache(maxsize=1) def SUPPORTED_PREFIXES(cls) -> set[str]: - """A set of prefixes handled by this entry type. - - !!! note - This implementation only includes the provider prefix, - but in the future this property may be extended to include other - namespaces (for serving fields from, e.g., other providers or - domain-specific terms). - - """ + """A set of prefixes handled by this entry type.""" from optimade.server.config import CONFIG - return {CONFIG.provider.prefix} + return set(CONFIG.supported_prefixes) @classproperty def ALL_ATTRIBUTES(cls) -> set[str]: @@ -378,3 +376,38 @@ def deserialize( return cls.ENTRY_RESOURCE_CLASS(**cls.map_back(results)) return [cls.ENTRY_RESOURCE_CLASS(**cls.map_back(doc)) for doc in results] + + @staticmethod + def starts_with_supported_prefix(field: str) -> tuple[bool, Union[str, None]]: + """Tests whether the supplied field has a field that is supported by this server. + Parameters: + field: The field/string for which it should be checked that it starts with a supported prefix. + + Returns: + A boolean which is true if the field/string starts with a supported prefix. + A string, containing the prefix if the field has a prefix otherwise it returns 'None'. + """ + + prefix = None + if field.startswith("_"): + prefix = field.split("_")[1] + if prefix in BaseResourceMapper.SUPPORTED_PREFIXES: + return True, prefix + return False, prefix + + @classmethod + def check_starts_with_supported_prefix(cls, field: str, message: str = "") -> None: + """Raises a value error if the field does not start with a supported prefix. + Parameters: + field: The field/string for which it should be checked that it starts with a supported prefix. + message: An additional error message that will be appended to the default error message. + Returns: + Raises a value error when the field has no valid prefix. + """ + + prefixed, prefix = cls.starts_with_supported_prefix(field) + if not prefixed: + raise ValueError( + f"The field {field} either has no prefix or the prefix {prefix} is not supported by this server." + + message + ) diff --git a/optimade/server/mappers/partial_data.py b/optimade/server/mappers/partial_data.py new file mode 100644 index 000000000..c959c6eae --- /dev/null +++ b/optimade/server/mappers/partial_data.py @@ -0,0 +1,9 @@ +from optimade.models.partial_data import PartialDataResource +from optimade.server.mappers.entries import BaseResourceMapper + +__all__ = ("PartialDataMapper",) + + +class PartialDataMapper(BaseResourceMapper): + LENGTH_ALIASES = () + ENTRY_RESOURCE_CLASS = PartialDataResource diff --git a/optimade/server/mappers/trajectories.py b/optimade/server/mappers/trajectories.py new file mode 100644 index 000000000..fdb3326dd --- /dev/null +++ b/optimade/server/mappers/trajectories.py @@ -0,0 +1,29 @@ +from optimade.models.trajectories import TrajectoryResource +from optimade.server.mappers.entries import BaseResourceMapper + +__all__ = ("TrajectoryMapper",) + + +class TrajectoryMapper(BaseResourceMapper): + # TODO add length aliases for trajectory specific properties + LENGTH_ALIASES = ( + ("elements", "nelements"), + ("element_ratios", "nelements"), + ("cartesian_site_positions", "nsites"), + ("species_at_sites", "nsites"), + ) + # HIDDEN_FIELDS = ["_storage_path"] + # + # STANDARD_FIELDS = ( + # {"reference_structure", "reference_frames", "nframes", "available_properties"} + # .union(BaseResourceMapper.get_required_fields()) + # .union(EntryResourceAttributes.__fields__.keys()) + # ) + ENTRY_RESOURCE_CLASS = TrajectoryResource + + # @classmethod + # def map_back(cls, doc: dict) -> dict: + # doc["available_properties"] = cls.add_alias_and_prefix( + # doc["available_properties"] + # ) + # return super().map_back(doc) diff --git a/optimade/server/query_params.py b/optimade/server/query_params.py index fee57f5dc..d96614fe5 100644 --- a/optimade/server/query_params.py +++ b/optimade/server/query_params.py @@ -7,7 +7,7 @@ from optimade.exceptions import BadRequest from optimade.server.config import CONFIG -from optimade.server.mappers import BaseResourceMapper +from optimade.server.mappers import BaseResourceMapper # type: ignore[attr-defined] from optimade.warnings import QueryParamNotUsed, UnknownProviderQueryParameter @@ -324,9 +324,79 @@ def __init__( description="If the client provides the parameter, the value SHOULD have the format `vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a minor version of the API. For example, if a client appends `api_hint=v1.0` to the query string, the hint provided is for major version 1 and minor version 0.", pattern=r"(v[0-9]+(\.[0-9]+)?)?", ), + property_ranges: str = Query( + None, + description="A list of lists which contains a range for each dimension of the property.", + ), ): self.response_format = response_format self.email_address = email_address self.response_fields = response_fields self.include = include self.api_hint = api_hint + self.property_ranges = property_ranges + + +class PartialDataQueryParams(BaseQueryParams): + """ + Common query params for single entry endpoints. + + Attributes: + response_format (str): The output format requested (see section Response Format). + Defaults to the format string 'json', which specifies the standard output format described in this specification. + + **Example**: `http://example.com/v1/structures?response_format=xml` + + email_address (EmailStr): An email address of the user making the request. + The email SHOULD be that of a person and not an automatic system. + + **Example**: `http://example.com/v1/structures?email_address=user@example.com` + + response_fields (str): A comma-delimited set of fields to be provided in the output. + If provided, these fields MUST be returned along with the REQUIRED fields. + Other OPTIONAL fields MUST NOT be returned when this parameter is present. + + **Example**: `http://example.com/v1/structures?response_fields=last_modified,nsites` + + api_hint (str): If the client provides the parameter, the value SHOULD have the format `vMAJOR` or `vMAJOR.MINOR`, + where MAJOR is a major version and MINOR is a minor version of the API. + For example, if a client appends `api_hint=v1.0` to the query string, the hint provided is for major version 1 and minor version 0. + + """ + + def __init__( + self, + *, + response_format: str = Query( + "jsonlines", + description="The output format requested (see section Response Format).\nDefaults to the format string 'json', which specifies the standard output format described in this specification.\nExample: `http://example.com/v1/structures?response_format=xml`", + ), + email_address: EmailStr = Query( + "", + description="An email address of the user making the request.\nThe email SHOULD be that of a person and not an automatic system.\nExample: `http://example.com/v1/structures?email_address=user@example.com`", + ), + api_hint: str = Query( + "", + description="If the client provides the parameter, the value SHOULD have the format `vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a minor version of the API. For example, if a client appends `api_hint=v1.0` to the query string, the hint provided is for major version 1 and minor version 0.", + pattern=r"(v[0-9]+(\.[0-9]+)?)?", + ), + response_fields: str = Query( + "", + description="A comma-delimited set of fields to be provided in the output.\nIf provided, these fields MUST be returned along with the REQUIRED fields.\nOther OPTIONAL fields MUST NOT be returned when this parameter is present.\nExample: `http://example.com/v1/structures?response_fields=last_modified,nsites`", + pattern=r"([a-z_][a-z_0-9]*(,[a-z_][a-z_0-9]*)*)?", + ), + filter: str = Query( # pylint: disable=redefined-builtin + "", + description="A filter string, in the format described in section API Filtering Format Specification of the specification.", + ), + property_ranges: str = Query( + "", + description="A list of lists which contains a range for each dimension of the property.", + ), + ): + self.filter = filter + self.response_format = response_format + self.email_address = email_address + self.response_fields = response_fields + self.api_hint = api_hint + self.property_ranges = property_ranges diff --git a/optimade/server/routers/__init__.py b/optimade/server/routers/__init__.py index 266518dbe..a9eaa64bd 100644 --- a/optimade/server/routers/__init__.py +++ b/optimade/server/routers/__init__.py @@ -1,9 +1,11 @@ from .links import links_coll from .references import references_coll from .structures import structures_coll +from .trajectories import trajectories_coll ENTRY_COLLECTIONS = { "links": links_coll, "references": references_coll, "structures": structures_coll, + "trajectories": trajectories_coll, } diff --git a/optimade/server/routers/partial_data.py b/optimade/server/routers/partial_data.py new file mode 100644 index 000000000..5a9a59436 --- /dev/null +++ b/optimade/server/routers/partial_data.py @@ -0,0 +1,36 @@ +from typing import Any + +from fastapi import APIRouter, Depends, Request + +from optimade.models import PartialDataResource # type: ignore[attr-defined] +from optimade.server.config import CONFIG +from optimade.server.entry_collections import create_collection +from optimade.server.mappers import PartialDataMapper +from optimade.server.query_params import PartialDataQueryParams +from optimade.server.routers.utils import get_partial_entry +from optimade.server.schemas import ERROR_RESPONSES + +router = APIRouter(redirect_slashes=True) + +partial_data_coll = create_collection( + name=CONFIG.partial_data_collection, + resource_cls=PartialDataResource, + resource_mapper=PartialDataMapper, +) + + +@router.get( + "/partial_data/{entry_id:path}", + response_model_exclude_unset=True, + tags=["partial_data"], + responses=ERROR_RESPONSES, +) +def get_partial_data( + request: Request, entry_id: str, params: PartialDataQueryParams = Depends() +) -> Any: + return get_partial_entry( + collection=partial_data_coll, + entry_id=entry_id, + request=request, + params=params, + ) diff --git a/optimade/server/routers/structures.py b/optimade/server/routers/structures.py index 594285a07..1253d53d3 100644 --- a/optimade/server/routers/structures.py +++ b/optimade/server/routers/structures.py @@ -10,8 +10,15 @@ from optimade.server.config import CONFIG from optimade.server.entry_collections import create_collection from optimade.server.mappers import StructureMapper -from optimade.server.query_params import EntryListingQueryParams, SingleEntryQueryParams -from optimade.server.routers.utils import get_entries, get_single_entry +from optimade.server.query_params import ( + EntryListingQueryParams, + SingleEntryQueryParams, +) +from optimade.server.routers.utils import ( + get_entries, + get_partial_entry, + get_single_entry, +) from optimade.server.schemas import ERROR_RESPONSES router = APIRouter(redirect_slashes=True) @@ -49,8 +56,20 @@ def get_structures( responses=ERROR_RESPONSES, ) def get_single_structure( - request: Request, entry_id: str, params: SingleEntryQueryParams = Depends() + request: Request, + entry_id: str, + params: SingleEntryQueryParams = Depends(), ) -> Any: + if params.property_ranges is not None: # todo add test for this + from optimade.server.routers.partial_data import partial_data_coll + + return get_partial_entry( + collection=partial_data_coll, + entry_id=entry_id, + request=request, + params=params, # type: ignore[arg-type] + ) + return get_single_entry( collection=structures_coll, entry_id=entry_id, diff --git a/optimade/server/routers/trajectories.py b/optimade/server/routers/trajectories.py new file mode 100644 index 000000000..e124a349a --- /dev/null +++ b/optimade/server/routers/trajectories.py @@ -0,0 +1,58 @@ +from fastapi import APIRouter, Depends, Request + +from optimade.models import ( + TrajectoryResource, + TrajectoryResponseMany, + TrajectoryResponseOne, +) +from optimade.server.config import CONFIG +from optimade.server.entry_collections import create_collection +from optimade.server.mappers import TrajectoryMapper +from optimade.server.query_params import EntryListingQueryParams, SingleEntryQueryParams +from optimade.server.routers.utils import get_entries, get_single_entry +from optimade.server.schemas import ERROR_RESPONSES + +router = APIRouter(redirect_slashes=True) + +trajectories_coll = create_collection( + name=CONFIG.trajectories_collection, + resource_cls=TrajectoryResource, + resource_mapper=TrajectoryMapper, +) + + +@router.get( + "/trajectories", + response_model=TrajectoryResponseMany, + response_model_exclude_unset=True, + tags=["Trajectories"], + responses=ERROR_RESPONSES, +) +def get_trajectories( + request: Request, params: EntryListingQueryParams = Depends() +) -> TrajectoryResponseMany: + return get_entries( + collection=trajectories_coll, + response=TrajectoryResponseMany, + request=request, + params=params, + ) + + +@router.get( + "/trajectories/{entry_id:path}", + response_model=TrajectoryResponseOne, + response_model_exclude_unset=True, + tags=["Trajectories"], + responses=ERROR_RESPONSES, +) +def get_single_trajectory( + request: Request, entry_id: str, params: SingleEntryQueryParams = Depends() +) -> TrajectoryResponseOne: + return get_single_entry( + collection=trajectories_coll, + entry_id=entry_id, + response=TrajectoryResponseOne, + request=request, + params=params, + ) diff --git a/optimade/server/routers/utils.py b/optimade/server/routers/utils.py index 16928ace8..868bc20c5 100644 --- a/optimade/server/routers/utils.py +++ b/optimade/server/routers/utils.py @@ -1,25 +1,31 @@ # pylint: disable=import-outside-toplevel,too-many-locals +import io import re import urllib.parse from datetime import datetime from typing import Any, Optional, Union -from fastapi import Request +import numpy as np +from fastapi import Request, Response from fastapi.responses import JSONResponse from starlette.datastructures import URL as StarletteURL from optimade import __api_version__ -from optimade.exceptions import BadRequest, InternalServerError -from optimade.models import ( - EntryResource, +from optimade.exceptions import BadRequest, InternalServerError, NotFound +from optimade.models import ( # type: ignore[attr-defined] + EntryResource, # type: ignore[attr-defined] EntryResponseMany, EntryResponseOne, - ResponseMeta, - ToplevelLinks, + ResponseMeta, # type: ignore[attr-defined] + ToplevelLinks, # type: ignore[attr-defined] ) from optimade.server.config import CONFIG from optimade.server.entry_collections import EntryCollection -from optimade.server.query_params import EntryListingQueryParams, SingleEntryQueryParams +from optimade.server.query_params import ( + EntryListingQueryParams, + PartialDataQueryParams, + SingleEntryQueryParams, +) from optimade.utils import PROVIDER_LIST_URLS, get_providers, mongo_id_for_database __all__ = ( @@ -30,6 +36,7 @@ "get_base_url", "get_entries", "get_single_entry", + "get_partial_entry", "mongo_id_for_database", "get_providers", "PROVIDER_LIST_URLS", @@ -62,7 +69,7 @@ def meta_values( **kwargs, ) -> ResponseMeta: """Helper to initialize the meta values""" - from optimade.models import ResponseMetaQuery + from optimade.models import ResponseMetaQuery # type: ignore[attr-defined] if isinstance(url, str): url = urllib.parse.urlparse(url) @@ -125,6 +132,13 @@ def handle_response_fields( for field in exclude_fields: if field in new_entry["attributes"]: del new_entry["attributes"][field] + if new_entry.get("meta") and ( + property_meta_data_fields := new_entry.get("meta").get( # type: ignore[union-attr] + "property_metadata" + ) + ): + if field in property_meta_data_fields: + del new_entry["meta"]["property_metadata"][field] # Include missing fields that were requested in `response_fields` for field in include_fields: @@ -244,6 +258,32 @@ def get_base_url( ) +def generate_links_partial_data( + results, + parsed_url_request: Union[ + urllib.parse.ParseResult, urllib.parse.SplitResult, StarletteURL, str + ], +): + for entry in results: + if entry.get("meta", {}) and entry["meta"].get("partial_data_links", {}): + for property in entry["meta"]["partial_data_links"]: + for response_format in CONFIG.partial_data_formats: + link = { + "format": str(response_format.value), + "link": get_base_url(parsed_url_request) + + "/partial_data/" + + entry["id"] + + "?response_fields=" + + property + + "&response_format=" + + str(response_format.value), + } + if isinstance(entry["meta"]["partial_data_links"][property], list): + entry["meta"]["partial_data_links"][property].append(link) + else: + entry["meta"]["partial_data_links"][property] = [link] + + def get_entries( collection: EntryCollection, response: type[EntryResponseMany], # noqa @@ -268,6 +308,7 @@ def get_entries( included = [] if results is not None: + generate_links_partial_data(results, request.url) included = get_included_relationships(results, ENTRY_COLLECTIONS, include) if more_data_available: @@ -332,6 +373,7 @@ def get_single_entry( included = [] if results is not None: included = get_included_relationships(results, ENTRY_COLLECTIONS, include) + generate_links_partial_data([results], request.url) links = ToplevelLinks(next=None) @@ -352,3 +394,116 @@ def get_single_entry( ), included=included, ) + + +def get_partial_entry( + collection: EntryCollection, + entry_id: str, + request: Request, + params: Union[PartialDataQueryParams], +) -> Union[dict, Response]: + # from optimade.server.routers import ENTRY_COLLECTIONS + from optimade.adapters.jsonl import to_jsonl + + params.check_params(request.query_params) + params.filter = f'parent_id="{entry_id}"' + ( + results, + data_returned, + more_data_available, + fields, + include_fields, + ) = collection.find(params) + + links = ToplevelLinks(next=None) + + if results is None: + raise NotFound( + detail=f"No data available for the combination of entry {entry_id} and property {params.response_fields}", + ) + + array = np.frombuffer( + results["attributes"]["data"], # type: ignore[call-overload] + dtype=getattr(np, results["attributes"]["dtype"]["name"]), # type: ignore[call-overload] + ).reshape( + results["attributes"]["shape"] # type: ignore[call-overload] + ) + # slice array + property_ranges = results["attributes"]["property_ranges"] # type: ignore[call-overload] + slice_ind = [ + slice( + 0, + 1 + property_ranges[0]["stop"] - property_ranges[0]["start"], + property_ranges[0]["step"], + ) + ] + for dim_range in property_ranges[1:]: + slice_ind.append( + slice(dim_range["start"] - 1, dim_range["stop"], dim_range["step"]) + ) + array = array[tuple(slice_ind)] + + if fields or include_fields: + results = handle_response_fields(results, fields, include_fields)[0] # type: ignore[assignment] + + slice_obj = [] + for i, size in enumerate(array.shape): + slice_obj.append( + { + "start": property_ranges[i]["start"], + "stop": min( + size * property_ranges[i]["step"] + property_ranges[i]["start"] - 1, + property_ranges[i]["stop"], + ), + "step": property_ranges[i]["step"], + } + ) + header = { + "optimade-partial-data": {"format": "1.2.0"}, + "layout": "dense", + "property_name": params.response_fields, + "returned_ranges": slice_obj, + # "entry": {"id": entry_id, "type": None}, #Todo add type information to metadata entry + "has_references": False, + } # Todo: add support for non_dense data + if more_data_available: + next_link = ["PARTIAL-DATA-NEXT", [results["attributes"].pop("next")]] # type: ignore[call-overload] + + if params.response_format == "json": + for key in header: + results["attributes"][key] = header[key] # type: ignore[call-overload] + results["attributes"]["data"] = array.tolist() # type: ignore[call-overload] + if more_data_available: + results["attributes"]["next"] = next_link # type: ignore[call-overload] + return dict( + links=links, + data=[results] if results else None, + meta=meta_values( + url=request.url, + data_returned=data_returned, + data_available=len(collection), + more_data_available=more_data_available, + schema=CONFIG.schema_url + if not CONFIG.is_index + else CONFIG.index_schema_url, + ), + # included=included, + ) + + jsonl_content = [header] + [array[i].tolist() for i in range(array.shape[0])] + if more_data_available: + jsonl_content.append(next_link) + return Response( + content=to_jsonl(jsonl_content), + media_type="application/jsonlines", + headers={ + "Content-disposition": f"attachment; filename={entry_id + ':' + params.response_fields}.jsonl" + }, + ) + + +def convert_data_to_str(results): + values = results["attributes"]["data"] + if isinstance(values, bytes): + results["attributes"]["data"] = np.array2string(np.load(io.BytesIO(values))) + return results diff --git a/optimade/server/schemas.py b/optimade/server/schemas.py index fa900a9a3..24ec505f9 100644 --- a/optimade/server/schemas.py +++ b/optimade/server/schemas.py @@ -1,11 +1,12 @@ from collections.abc import Iterable from typing import Callable, Optional -from optimade.models import ( +from optimade.models import ( # type: ignore[attr-defined] DataType, ErrorResponse, ReferenceResource, StructureResource, + TrajectoryResource, ) __all__ = ("ENTRY_INFO_SCHEMAS", "ERROR_RESPONSES", "retrieve_queryable_properties") @@ -13,6 +14,7 @@ ENTRY_INFO_SCHEMAS: dict[str, Callable[[], dict]] = { "structures": StructureResource.schema, "references": ReferenceResource.schema, + "trajectories": TrajectoryResource.schema, } """This dictionary is used to define the `/info/` endpoints.""" diff --git a/optimade/validator/utils.py b/optimade/validator/utils.py index 94db640bf..340962166 100644 --- a/optimade/validator/utils.py +++ b/optimade/validator/utils.py @@ -24,12 +24,13 @@ from pydantic import Field, ValidationError from optimade import __version__ -from optimade.models import ( +from optimade.models import ( # type: ignore[attr-defined] EntryResource, LinksResource, ReferenceResource, ResponseMeta, StructureResource, + TrajectoryResource, ) from optimade.models.optimade_json import Success @@ -408,7 +409,6 @@ class ValidatorLinksResponse(Success): class ValidatorEntryResponseOne(Success): - meta: ResponseMeta = Field(...) data: EntryResource = Field(...) included: Optional[list[dict[str, Any]]] = Field(None) # type: ignore[assignment] @@ -431,5 +431,13 @@ class ValidatorStructureResponseOne(ValidatorEntryResponseOne): data: StructureResource = Field(...) +class ValidatorTrajectoryResponseOne(ValidatorEntryResponseOne): + data: TrajectoryResource = Field(...) + + +class ValidatorTrajectoryResponseMany(ValidatorEntryResponseMany): + data: list[TrajectoryResource] = Field(...) + + class ValidatorStructureResponseMany(ValidatorEntryResponseMany): data: list[StructureResource] = Field(...) diff --git a/optimade_config.json b/optimade_config.json index e22d9e3b0..85a29ff8a 100644 --- a/optimade_config.json +++ b/optimade_config.json @@ -2,6 +2,7 @@ "debug": false, "default_db": "test_server", "base_url": "http://localhost:5000", + "trajectories_collection": "trajectories", "implementation": { "name": "Example implementation", "source_url": "https://github.com/Materials-Consortia/optimade-python-tools", @@ -19,7 +20,8 @@ "structures": [ "band_gap", {"name": "chemsys", "type": "string", "description": "A string representing the chemical system in an ordered fashion"} - ] + ], + "trajectories": [] }, "aliases": { "structures": { diff --git a/pyproject.toml b/pyproject.toml index 1cbfffc3a..536eca497 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -68,6 +68,8 @@ server = [ "fastapi>=0.103.1", "pyyaml~=6.0", "optimade[mongo]", + "numpy>=1.20", + "jsonlines>=3.1", ] # Client minded diff --git a/requirements-dev.txt b/requirements-dev.txt index 33423d46c..4c0c0620c 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -4,6 +4,7 @@ flake8==6.1.0 invoke==2.2.0 isort==5.12.0 jsondiff==2.0.0 +jsonlines>=3.1 mypy==1.5.1 pre-commit==3.4.0 pylint==2.17.5 diff --git a/requirements-server.txt b/requirements-server.txt index 04f5dc957..f486c1d85 100644 --- a/requirements-server.txt +++ b/requirements-server.txt @@ -1,5 +1,7 @@ elasticsearch==7.17.7 elasticsearch-dsl==7.4.0 fastapi==0.103.1 +jsonlines>=3.1 mongomock==4.1.2 +numpy>=1.20 pymongo==4.5.0 diff --git a/tests/adapters/test_jsonl.py b/tests/adapters/test_jsonl.py new file mode 100644 index 000000000..d5843fcc3 --- /dev/null +++ b/tests/adapters/test_jsonl.py @@ -0,0 +1,11 @@ +from pathlib import Path + +from optimade.adapters.jsonl import from_jsonl, to_jsonl + +test_object = from_jsonl(Path(__file__).parent.resolve() / "testdata.jsonl") + + +def test_to_and_from_jsonl(): + file_content = to_jsonl(test_object) + reprocessed_file = from_jsonl(file_content) + assert test_object == reprocessed_file diff --git a/tests/adapters/testdata.jsonl b/tests/adapters/testdata.jsonl new file mode 100644 index 000000000..47eb9346b --- /dev/null +++ b/tests/adapters/testdata.jsonl @@ -0,0 +1,5 @@ +{"optimade_partial_data": {"version": "1.2.0"}, "layout": "dense", "returned_ranges": [{"start": 10, "stop": 20, "step": 2}]} +1243 +345 +-12.6 +["PARTIAL-DATA-NEXT", ["https://example.db.org/value4"]] diff --git a/tests/models/test_data/test_good_structures.json b/tests/models/test_data/test_good_structures.json index b84605832..63c7c6044 100644 --- a/tests/models/test_data/test_good_structures.json +++ b/tests/models/test_data/test_good_structures.json @@ -165,6 +165,13 @@ "last_modified": { "$date": "2019-06-08T05:13:37.331Z" }, + "meta": { + "property_metadata": { + "elements_ratios": { + "_exmpl_originates_from_project":"piezoelectic_perovskites" + } + } + }, "band_gap": 1.23456, "chemsys": "C-H-Cl-N-Na-O-Os-P", "elements": ["C", "Cl", "H", "N", "Na", "O", "Os", "P"], diff --git a/tests/models/test_entries.py b/tests/models/test_entries.py index a3ab7e318..e1a8a492e 100644 --- a/tests/models/test_entries.py +++ b/tests/models/test_entries.py @@ -1,7 +1,7 @@ import pytest from pydantic import ValidationError -from optimade.models.entries import EntryRelationships +from optimade.models.entries import EntryRelationships, EntryResource def test_simple_relationships(): @@ -48,3 +48,53 @@ def test_advanced_relationships(): } with pytest.raises(ValidationError): EntryRelationships(**relationship) + + +def test_meta(): + import copy + + good_entry_resource = { + "id": "goodstruct123", + "type": "structure", + "attributes": { + "last_modified": "2023-07-21T05:13:37.331Z", + "elements": ["Ac"], + "_exmpl_database_specific_property": "value1", + "elements_ratios": [1.0], + }, + "meta": { + "property_metadata": { + "elements_ratios": { + "_exmpl_mearsurement_method": "ICP-OES", + }, + "_exmpl_database_specific_property": { + "_exmpl_metadata_property": "metadata_value" + }, + } + }, + } + + EntryResource(**good_entry_resource) + + bad_entry_resources = [ + good_entry_resource, + copy.deepcopy(good_entry_resource), + copy.deepcopy(good_entry_resource), + copy.deepcopy(good_entry_resource), + ] + bad_entry_resources[0]["meta"]["property_metadata"][ + "_exmpl_database_specific_property" + ] = {"metadata_property": "metadata_value"} + bad_entry_resources[1]["meta"]["property_metadata"][ + "database_specific_property" + ] = {"_exmpl_metadata_property": "metadata_value"} + bad_entry_resources[2]["meta"]["database_specific_property"] = { + "_exmpl_metadata_property": "metadata_value" + } + bad_entry_resources[3]["meta"]["_other_database_specific_property"] = { + "_exmpl_metadata_property": "metadata_value" + } + + for bad_entry in bad_entry_resources: + with pytest.raises(ValueError): + EntryResource(**bad_entry) diff --git a/tests/models/test_partialdata.py b/tests/models/test_partialdata.py new file mode 100644 index 000000000..e6f1b3582 --- /dev/null +++ b/tests/models/test_partialdata.py @@ -0,0 +1,38 @@ +from optimade.models.partial_data import PartialDataFormat + + +def test_partial_data_object_generation(): + test_object = { + "header": { + "optimade_partial_data": {"version": "1.2.0"}, + "layout": "dense", + "returned_ranges": [{"start": 10, "stop": 20, "step": 2}], + }, + "data": [ + 123, + 345, + -12.6, + ["PARTIAL-DATA-NEXT", ["https://example.db.org/value4"]], + ], + } + + PartialDataFormat(**test_object) + + +# todo finish test below +# def test_json_object_generation(): +# test_object = { +# "header": { +# "optimade_partial_data": {"version": "1.2.0"}, +# "layout": "dense", +# "returned_ranges": [{"start": 10, "stop": 20, "step": 2}], +# }, +# "data": [ +# 123, +# 345, +# -12.6, +# ["PARTIAL-DATA-NEXT", ["https://example.db.org/value4"]], +# ], +# } +# +# PartialDataResource(**test_object) diff --git a/tests/server/entry_collections/test_entry_collections.py b/tests/server/entry_collections/test_entry_collections.py index a5f537698..c2d4323cc 100644 --- a/tests/server/entry_collections/test_entry_collections.py +++ b/tests/server/entry_collections/test_entry_collections.py @@ -7,6 +7,7 @@ def test_get_attribute_fields(): LinksResourceAttributes, ReferenceResourceAttributes, StructureResourceAttributes, + TrajectoryResourceAttributes, ) from optimade.server.routers import ENTRY_COLLECTIONS @@ -14,6 +15,7 @@ def test_get_attribute_fields(): "links": LinksResourceAttributes, "references": ReferenceResourceAttributes, "structures": StructureResourceAttributes, + "trajectories": TrajectoryResourceAttributes, } # Make sure we're hitting all collections diff --git a/tests/server/query_params/conftest.py b/tests/server/query_params/conftest.py index b7f0e59f0..02e0de6ce 100644 --- a/tests/server/query_params/conftest.py +++ b/tests/server/query_params/conftest.py @@ -63,6 +63,7 @@ def check_required_fields_response(get_good_response): "links": mappers.LinksMapper, "references": mappers.ReferenceMapper, "structures": mappers.StructureMapper, + "trajectories": mappers.TrajectoryMapper, } def inner( @@ -78,11 +79,13 @@ def inner( response = get_good_response(request, server) expected_fields.add("attributes") - + expected_fields.discard("meta") response_fields = set() for entry in response["data"]: response_fields.update(set(entry.keys())) response_fields.update(set(entry["attributes"].keys())) + # As "meta" is an optional field the response may or may not have it, so we remove it here to prevent problems in the assert below. + response_fields.discard("meta") assert sorted(expected_fields) == sorted(response_fields) return inner diff --git a/tests/server/routers/test_partial_data.py b/tests/server/routers/test_partial_data.py new file mode 100644 index 000000000..5f0773d85 --- /dev/null +++ b/tests/server/routers/test_partial_data.py @@ -0,0 +1,38 @@ +from optimade.models import PartialDataResponse + +from ..utils import NoJsonEndpointTests + + +class TestPartialDataEndpoint(NoJsonEndpointTests): + """Tests for /partial_data/""" + + test_id = "mpf_551" + params = "response_fields=cartesian_site_positions" + request_str = f"/partial_data/{test_id}?{params}" + response_cls = PartialDataResponse + + +def test_property_ranges_link(get_good_response, client): + test_id = "mpf_551" + params = "response_fields=cartesian_site_positions&property_ranges=dim_sites:2:74:1,dim_cartesian_dimensions:1:3:1&response_format=json" + request = f"/partial_data/{test_id}?{params}" + get_good_response( + request, server=client + ) # todo expand test to check content better. + + +def test_wrong_id_partial_data(check_error_response, client): + """If a non-supported versioned base URL is passed, `553 Version Not Supported` should be returned + + A specific JSON response should also occur. + """ + test_id = "mpf_486" + params = "response_fields=cartesian_site_positions" + request = f"/partial_data/{test_id}?{params}" + check_error_response( + request, + expected_status=404, + expected_title="Not Found", + expected_detail="No data available for the combination of entry mpf_486 and property cartesian_site_positions", + server=client, + ) diff --git a/tests/server/routers/test_structures.py b/tests/server/routers/test_structures.py index a0e089f2d..222b526fd 100644 --- a/tests/server/routers/test_structures.py +++ b/tests/server/routers/test_structures.py @@ -1,4 +1,4 @@ -from optimade.models import ( +from optimade.models import ( # type: ignore[attr-defined] ReferenceResource, StructureResponseMany, StructureResponseOne, @@ -69,6 +69,12 @@ def test_structures_endpoint_data(self): assert self.json_response["data"]["type"] == "structures" assert "attributes" in self.json_response["data"] assert "_exmpl_chemsys" in self.json_response["data"]["attributes"] + assert ( + self.json_response["data"]["meta"]["property_metadata"]["elements_ratios"][ + "_exmpl_originates_from_project" + ] + == "Pure Metals" + ) def test_check_response_single_structure(check_response): @@ -220,3 +226,17 @@ def test_structures_endpoint_data(self): assert all( len(doc["attributes"]) == len(keys) for doc in self.json_response["data"] ) + + +class TestStructuresWithNelementsInFilter(RegularEndpointTests): + """Tests that structures with e.g., `'assemblies':null` do get + returned for queries testing for "UNKNOWN" fields. + + """ + + request_str = "/structures?filter=nelements>=6" + response_cls = StructureResponseMany + + def test_structures_nreturned(self): + """Check that all structures are returned.""" + assert len(self.json_response["data"]) == 7 diff --git a/tests/server/routers/test_trajectories.py b/tests/server/routers/test_trajectories.py new file mode 100644 index 000000000..966ebd508 --- /dev/null +++ b/tests/server/routers/test_trajectories.py @@ -0,0 +1,28 @@ +from optimade.models import PartialDataResponse + +from ..utils import NoJsonEndpointTests + + +class Test_traj_data(NoJsonEndpointTests): + request_str = "http://example.org/partial_data/6509be05a54743f440a7f36b?response_fields=cartesian_site_positions&response_format=jsonlines&property_ranges=dim_frames:1:40000:2,dim_sites:1:32:1,dim_cartesian_dimensions:1:3:1" + response_cls = PartialDataResponse + + +def test_trajectories( + check_response, +): + expected_ids = ["6509be05a54743f440a7f36b"] + request = "/trajectories?filter=nelements=1" + check_response(request, expected_ids) + pass + + # request = "/trajectories?filter=nelements==1" + # expected_ids = ["62696ac7eef0323c842f9f51"] + # check_response(request, expected_ids=expected_ids) + # + # # Because the amount of data that will be returned is limited to reduce waiting times. # TODO Once I have created a proper config parameter for this I should use this maximum package size to determine whether one or two entries are returned. + # request = "/trajectories?filter=nelements>=6&response_fields=cartesian_site_positions,_exmpl_time&last_frame=40&first_frame=5" + # expected_ids = ["62696ac7eef0323c842f9f51"] + # check_response( + # request, expected_ids=expected_ids, expected_return=2 + # ) diff --git a/tests/server/utils.py b/tests/server/utils.py index 49b7b9f92..bbba9f615 100644 --- a/tests/server/utils.py +++ b/tests/server/utils.py @@ -229,9 +229,9 @@ class NoJsonEndpointTests: response: Optional[httpx.Response] = None @pytest.fixture(autouse=True) - def get_response(self, both_clients): + def get_response(self, client): """Get response from client""" - self.response = both_clients.get(self.request_str) + self.response = client.get(self.request_str) yield self.response = None