diff --git a/latest/CHANGELOG/CHANGELOG.md b/latest/CHANGELOG/CHANGELOG.md index 38d4353..f293a3d 100644 --- a/latest/CHANGELOG/CHANGELOG.md +++ b/latest/CHANGELOG/CHANGELOG.md @@ -1,8 +1,8 @@ # Changelog -## [Unreleased changes](https://github.com/SINTEF/oteapi-optimade/tree/Unreleased changes) (2024-09-04) +## [v0.6.0.dev0](https://github.com/SINTEF/oteapi-optimade/tree/v0.6.0.dev0) (2024-09-04) -[Full Changelog](https://github.com/SINTEF/oteapi-optimade/compare/v0.5.1...Unreleased changes) +[Full Changelog](https://github.com/SINTEF/oteapi-optimade/compare/v0.5.1...v0.6.0.dev0) ## Support modern session handling in OTEAPI Core diff --git a/latest/CHANGELOG/index.html b/latest/CHANGELOG/index.html index dd6aaa0..98a7da8 100644 --- a/latest/CHANGELOG/index.html +++ b/latest/CHANGELOG/index.html @@ -426,9 +426,9 @@
From OTEAPI Core v0.7.0, sessions are handled differently in strategies, leading to signature changes in the strategy methods. This development release version matches the current development release version(s) of OTEAPI Core (and OTELib).
diff --git a/latest/search/search_index.json b/latest/search/search_index.json index ad4d6e9..759d225 100644 --- a/latest/search/search_index.json +++ b/latest/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"OTE-API OPTIMADE","text":"An OTE-API Plugin with OTE strategies.
Further reading:
OTE-API OPTIMADE is released under the MIT license with copyright \u00a9 SINTEF.
"},{"location":"#acknowledgment","title":"Acknowledgment","text":"OTE-API OPTIMADE has been created via the cookiecutter template for OTE-API plugins.
OTE-API OPTIMADE has been supported by the following projects:
OntoTrans (2020-2024) that receives funding from the European Union\u2019s Horizon 2020 Research and Innovation Programme, under Grant Agreement n. 862136.
VIPCOAT (2021-2025) receives funding from the European Union\u2019s Horizon 2020 Research and Innovation Programme - DT-NMBP-11-2020 Open Innovation Platform for Materials Modelling, under Grant Agreement no: 952903.
OpenModel (2021-2025) receives funding from the European Union\u2019s Horizon 2020 Research and Innovation Programme - DT-NMBP-11-2020 Open Innovation Platform for Materials Modelling, under Grant Agreement no: 953167.
Full Changelog
"},{"location":"CHANGELOG/#support-modern-session-handling-in-oteapi-core","title":"Support modern session handling in OTEAPI Core","text":"From OTEAPI Core v0.7.0, sessions are handled differently in strategies, leading to signature changes in the strategy methods. This development release version matches the current development release version(s) of OTEAPI Core (and OTELib).
"},{"location":"CHANGELOG/#single-entity-optimade-structure-resource","title":"Single entity OPTIMADE Structure Resource","text":"A single entity has been added to parse an OPTIMADE Structure resource. The DLite parse strategy has been updated to support this new entity.
Furthermore, utility/helper functions to parse the resulting species
and assemblies
data are available at oteapi_optimade.parse_species()
and oteapi_optimade.parse_assemblies()
, respectively.
The permanent dependencies branch has been removed in favor of using Dependabot's groups feature and merging everything directly into main
.
Implemented enhancements:
Merged pull requests:
Full Changelog
"},{"location":"CHANGELOG/#update-to-latest-dependencies","title":"Update to latest dependencies","text":"Update dependencies to support the latest core libraries.
This release is done almost immediately prior to the v0.6.0.dev0 release, which will support the upcoming re-design of OTEAPI Core and the use of sessions.
Fixed bugs:
Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
oteapi
docker image version for testing #187Merged pull requests:
Full Changelog
"},{"location":"CHANGELOG/#v041-2023-10-26","title":"v0.4.1 (2023-10-26)","text":"Full Changelog
Fixed bugs:
Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Segmentation fault
from dlite in CI #115dlite
module #113Closed issues:
Merged pull requests:
Full Changelog
Implemented enhancements:
SINTEF/ci-cd
CI - Tests workflow #71Fixed bugs:
SINTEF/ci-cd
instead of CasperWA/ci-cd
#72Closed issues:
Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Closed issues:
Merged pull requests:
Full Changelog
Closed issues:
test: false
for publish workflow #61Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Closed issues:
optimade
container image in CI #41Merged pull requests:
Full Changelog
Implemented enhancements:
/
) #28Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Closed issues:
Merged pull requests:
ID!
type instead of String!
#7 (CasperWA)* This Changelog was automatically generated by github_changelog_generator
"},{"location":"LICENSE/","title":"License","text":"MIT License
Copyright (c) 2022 SINTEF
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"},{"location":"all_strategies/","title":"OTE-API OPTIMADE Strategies","text":"This page provides documentation for the oteapi_optimade.strategies
submodule, where all the OTE-API OPTIMADE strategies are located.
These strategies will be available when setting up a server in an environment with oteapi-optimade installed.
"},{"location":"api_reference/_utils/","title":"_utils","text":"Utility and helper functions.
"},{"location":"api_reference/_utils/#oteapi_optimade._utils.parse_assemblies","title":"parse_assemblies(instance)
","text":"Parse assemblies properties from the OPTIMADEStructureResource entity into \"proper\" OPT StructureResource 'Assembly's.
Source code inoteapi_optimade/_utils.py
def parse_assemblies(instance: dlite.Instance | dict[str, Any]) -> list[dict[str, Any]]:\n \"\"\"Parse assemblies properties from the OPTIMADEStructureResource entity into \"proper\" OPT\n StructureResource 'Assembly's.\"\"\"\n if not _check_correct_entity(instance, \"OPTIMADEStructureResource\"):\n raise ValueError(\n \"Entity for the instance is not an OPTIMADEStructureResource entity.\"\n )\n\n if isinstance(instance, dlite.Instance):\n instance = instance.asdict(single=True)\n\n if not isinstance(instance, dict):\n raise TypeError(\"Entity instance is not a dictionary.\")\n\n assemblies: list[dict[str, Any]] = []\n\n for index in range(instance[\"dimensions\"][\"nassemblies\"]):\n assemblies.append(\n {\n \"sites_in_groups\": [\n [int(_) for _ in group.split(\",\")]\n for group in instance[\"properties\"][\"assembly_sites_in_groups\"][\n index\n ].split(\";\")\n ],\n \"group_probabilities\": [\n float(_)\n for _ in instance[\"properties\"][\"assembly_group_probabilities\"][\n index\n ].split(\",\")\n ],\n }\n )\n\n return assemblies\n
"},{"location":"api_reference/_utils/#oteapi_optimade._utils.parse_species","title":"parse_species(instance)
","text":"Parse species properties from the OPTIMADEStructureResource entity into \"proper\" OPT StructureResource Species.
Source code inoteapi_optimade/_utils.py
def parse_species(instance: dlite.Instance | dict[str, Any]) -> list[dict[str, Any]]:\n \"\"\"Parse species properties from the OPTIMADEStructureResource entity into \"proper\" OPT\n StructureResource Species.\"\"\"\n if not _check_correct_entity(instance, \"OPTIMADEStructureResource\"):\n raise ValueError(\n \"Entity for the instance is not an OPTIMADEStructureResource entity.\"\n )\n\n if isinstance(instance, dlite.Instance):\n instance = instance.asdict(single=True)\n\n if not isinstance(instance, dict):\n raise TypeError(\"Entity instance is not a dictionary.\")\n\n species: list[dict[str, Any]] = []\n\n for index in range(instance[\"dimensions\"][\"nspecies\"]):\n # Required fields\n new_species = {\n \"name\": instance[\"properties\"][\"species_name\"][index],\n \"chemical_symbols\": instance[\"properties\"][\"species_chemical_symbols\"][\n index\n ].split(\",\"),\n \"concentration\": [\n float(_)\n for _ in instance[\"properties\"][\"species_concentration\"][index].split(\n \",\"\n )\n ],\n }\n\n # Optional fields\n if mass := instance[\"properties\"][\"species_mass\"][index]:\n new_species[\"mass\"] = [float(_) for _ in mass.split(\",\")]\n\n if original_name := instance[\"properties\"][\"species_original_name\"][index]:\n new_species[\"original_name\"] = original_name\n\n if attached := instance[\"properties\"][\"species_attached\"][index]:\n new_species[\"attached\"] = attached.split(\",\")\n\n if \"species_nattached\" not in instance[\"properties\"]:\n raise ValueError(\n \"species_attached is present, but species_nattached is missing.\"\n )\n\n new_species[\"nattached\"] = [\n int(_)\n for _ in instance[\"properties\"][\"species_nattached\"][index].split(\",\")\n ]\n\n species.append(new_species)\n\n return species\n
"},{"location":"api_reference/exceptions/","title":"exceptions","text":"OTE-API OPTIMADE-specific Python exceptions.
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.BaseOteapiOptimadeException","title":"BaseOteapiOptimadeException
","text":" Bases: Exception
Base OTE-API OPTIMADE exception.
Source code inoteapi_optimade/exceptions.py
class BaseOteapiOptimadeException(Exception):\n \"\"\"Base OTE-API OPTIMADE exception.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.ConfigurationError","title":"ConfigurationError
","text":" Bases: BaseOteapiOptimadeException
An error occurred when dealing with strategy configurations.
Source code inoteapi_optimade/exceptions.py
class ConfigurationError(BaseOteapiOptimadeException):\n \"\"\"An error occurred when dealing with strategy configurations.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.MissingDependency","title":"MissingDependency
","text":" Bases: BaseOteapiOptimadeException
A required dependency is missing.
Source code inoteapi_optimade/exceptions.py
class MissingDependency(BaseOteapiOptimadeException):\n \"\"\"A required dependency is missing.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.OPTIMADEParseError","title":"OPTIMADEParseError
","text":" Bases: BaseOteapiOptimadeException
Could not use OPTIMADE Python tools to parse an OPTIMADE API response.
Source code inoteapi_optimade/exceptions.py
class OPTIMADEParseError(BaseOteapiOptimadeException):\n \"\"\"Could not use OPTIMADE Python tools to parse an OPTIMADE API response.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.OPTIMADEResponseError","title":"OPTIMADEResponseError
","text":" Bases: RequestError
An OPTIMADE error was returned from a URL request.
Source code inoteapi_optimade/exceptions.py
class OPTIMADEResponseError(RequestError):\n \"\"\"An OPTIMADE error was returned from a URL request.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.RequestError","title":"RequestError
","text":" Bases: BaseOteapiOptimadeException
A general error occured when performing a URL request.
Source code inoteapi_optimade/exceptions.py
class RequestError(BaseOteapiOptimadeException):\n \"\"\"A general error occured when performing a URL request.\"\"\"\n
"},{"location":"api_reference/dlite/parse/","title":"parse","text":"OTEAPI strategy for parsing OPTIMADE structure resources to DLite instances.
"},{"location":"api_reference/dlite/parse/#oteapi_optimade.dlite.parse.OPTIMADEDLiteParseStrategy","title":"OPTIMADEDLiteParseStrategy
","text":"Parse strategy for JSON.
Implements strategies:
(\"parserType\", \"parser/OPTIMADE/DLite\")
oteapi_optimade/dlite/parse.py
@dataclass\nclass OPTIMADEDLiteParseStrategy:\n \"\"\"Parse strategy for JSON.\n\n **Implements strategies**:\n\n - `(\"parserType\", \"parser/OPTIMADE/DLite\")`\n\n \"\"\"\n\n parse_config: OPTIMADEDLiteParseConfig\n\n def initialize(self) -> DLiteSessionUpdate:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n return DLiteSessionUpdate(\n collection_id=get_collection(\n collection_id=self.parse_config.configuration.collection_id\n ).uuid\n )\n\n def get(self) -> OPTIMADEParseResult:\n \"\"\"Request and parse an OPTIMADE response using OPT.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `downloadUrl`.\n\n Workflow:\n\n 1. Request OPTIMADE response.\n 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model.\n\n ---\n\n The OPTIMADE Structure needs to be parsed into DLite instances inside-out,\n meaning the most nested data structures must first be parsed, and then the ones\n 1 layer up and so on until the most upper layer can be parsed.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n generic_parse_config = self.parse_config.model_copy(\n update={\n \"parserType\": self.parse_config.parserType.replace(\"/dlite\", \"\"),\n \"configuration\": self.parse_config.configuration.model_copy(\n update={\n \"mediaType\": self.parse_config.configuration.get(\n \"mediaType\", \"\"\n ).replace(\"+dlite\", \"+json\")\n }\n ),\n }\n ).model_dump(exclude_unset=True, exclude_defaults=True)\n generic_parse_result = OPTIMADEParseStrategy(generic_parse_config).get()\n\n OPTIMADEStructure = dlite.get_instance(str(self.parse_config.entity))\n\n single_entity = \"OPTIMADEStructureResource\" in OPTIMADEStructure.uri\n\n if not single_entity:\n nested_entity_mapping: dict[str, dlite.Instance] = self._get_nested_entity(\n OPTIMADEStructure\n )\n\n if not (\n generic_parse_result.optimade_response\n and generic_parse_result.optimade_response_model\n ):\n base_error_message = (\n \"Could not retrieve response from OPTIMADE parse strategy.\"\n )\n LOGGER.error(\n \"%s\\n\"\n \"optimade_response=%r\\n\"\n \"optimade_response_model=%r\\n\"\n \"session fields=%r\",\n base_error_message,\n generic_parse_result.get(\"optimade_response\"),\n generic_parse_result.get(\"optimade_response_model\"),\n list(generic_parse_result),\n )\n raise OPTIMADEParseError(base_error_message)\n\n optimade_response_model_module, optimade_response_model_name = (\n generic_parse_result.optimade_response_model\n )\n\n # Parse response using the provided model\n try:\n optimade_response_model: type[OPTIMADEResponse] = getattr(\n importlib.import_module(optimade_response_model_module),\n optimade_response_model_name,\n )\n optimade_response = optimade_response_model(\n **generic_parse_result.optimade_response\n )\n except (ImportError, AttributeError) as exc:\n base_error_message = \"Could not import the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ImportError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n except ValidationError as exc:\n base_error_message = \"Could not validate the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ValidationError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n\n # Currently, only \"structures\" entries are supported and handled\n if isinstance(optimade_response, StructureResponseMany):\n structures: list[StructureResource] = [\n (\n StructureResource(**entry)\n if isinstance(entry, dict)\n else entry.model_copy(deep=True)\n )\n for entry in optimade_response.data\n ]\n\n elif isinstance(optimade_response, StructureResponseOne):\n structures = (\n [\n (\n StructureResource(**optimade_response.data)\n if isinstance(optimade_response.data, dict)\n else optimade_response.data.model_copy(deep=True)\n )\n ]\n if optimade_response.data is not None\n else []\n )\n\n elif isinstance(optimade_response, Success):\n if isinstance(optimade_response.data, dict):\n structures = [StructureResource(**optimade_response.data)]\n elif isinstance(optimade_response.data, BaseModel):\n structures = [StructureResource(**optimade_response.data.model_dump())]\n elif isinstance(optimade_response.data, list):\n structures = [\n (\n StructureResource(**entry)\n if isinstance(entry, dict)\n else StructureResource(**entry.model_dump())\n )\n for entry in optimade_response.data\n ]\n elif optimade_response.data is None:\n structures = []\n else:\n LOGGER.error(\n \"Could not determine what to do with `data`. Type %s.\",\n type(optimade_response.data),\n )\n raise OPTIMADEParseError(\"Could not parse `data` entry in response.\")\n\n else:\n LOGGER.error(\n \"Got currently unsupported response type %s. Only structures are \"\n \"supported.\",\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(\n \"The DLite OPTIMADE Parser currently only supports structures entities.\"\n )\n\n if not structures:\n LOGGER.warning(\"No structures found in the response.\")\n return generic_parse_result\n\n # DLite-fy OPTIMADE structures\n dlite_collection = get_collection(\n collection_id=self.parse_config.configuration.collection_id\n )\n\n for structure in structures:\n new_structure_attributes: dict[str, Any] = {}\n single_entity_dimensions: dict[str, int] = {\n \"nassemblies\": 0,\n \"nspecies\": 0,\n }\n\n ## For OPTIMADEStructure (multiple) entities\n # Most inner layer: assemblies & species\n if structure.attributes.assemblies:\n if single_entity:\n single_entity_dimensions[\"nassemblies\"] = len(\n structure.attributes.assemblies\n )\n new_structure_attributes.update(\n {\n \"assemblies_sites_in_groups\": [],\n \"assemblies_group_probabilities\": [],\n }\n )\n else:\n new_structure_attributes[\"assemblies\"] = []\n\n for assembly in structure.attributes.assemblies:\n if single_entity:\n new_structure_attributes[\"assemblies_sites_in_groups\"].append(\n \";\".join(\n [\n \",\".join(str(_) for _ in group)\n for group in assembly.sites_in_groups\n ]\n )\n )\n new_structure_attributes[\n \"assemblies_group_probabilities\"\n ].append(\",\".join(str(_) for _ in assembly.group_probabilities))\n else:\n if \"attributes.assemblies\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes.assemblies'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\n \"Could not find entity for 'attributes.assemblies'.\"\n )\n\n dimensions = {\n \"ngroups\": len(assembly.group_probabilities),\n \"nsites\": len(assembly.sites_in_groups),\n }\n new_structure_attributes[\"assemblies\"].append(\n nested_entity_mapping[\"attributes.assemblies\"](\n dimensions=dimensions, properties=assembly.model_dump()\n )\n )\n\n if structure.attributes.species:\n if single_entity:\n single_entity_dimensions[\"nspecies\"] = len(\n structure.attributes.species\n )\n new_structure_attributes.update(\n {\n \"species_name\": [],\n \"species_chemical_symbols\": [],\n \"species_concentration\": [],\n \"species_mass\": [],\n \"species_original_name\": [],\n \"species_attached\": [],\n \"species_nattached\": [],\n }\n )\n else:\n new_structure_attributes[\"species\"] = []\n\n for species in structure.attributes.species:\n if single_entity:\n new_structure_attributes[\"species_name\"].append(species.name)\n new_structure_attributes[\"species_chemical_symbols\"].append(\n \",\".join(species.chemical_symbols)\n )\n new_structure_attributes[\"species_concentration\"].append(\n \",\".join(str(_) for _ in species.concentration)\n )\n new_structure_attributes[\"species_mass\"].append(\n \",\".join(str(_) for _ in (species.mass or []))\n )\n new_structure_attributes[\"species_original_name\"].append(\n species.original_name or \"\"\n )\n new_structure_attributes[\"species_attached\"].append(\n \",\".join(species.attached or [])\n )\n new_structure_attributes[\"species_nattached\"].append(\n \",\".join(str(_) for _ in (species.nattached or []))\n )\n else:\n if \"attributes.species\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes.species'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\n \"Could not find entity for 'attributes.species'.\"\n )\n\n dimensions = {\n \"nelements\": len(species.chemical_symbols),\n \"nattached_elements\": len(species.attached or []),\n }\n new_structure_attributes[\"species\"].append(\n nested_entity_mapping[\"attributes.species\"](\n dimensions=dimensions,\n properties=species.model_dump(exclude_none=True),\n )\n )\n\n # Attributes\n new_structure_attributes.update(\n structure.attributes.model_dump(\n exclude={\n \"species\",\n \"assemblies\",\n \"nelements\",\n \"nsites\",\n \"structure_features\",\n },\n exclude_unset=True,\n exclude_defaults=True,\n exclude_none=True,\n )\n )\n for key in list(new_structure_attributes):\n if key.startswith(\"_\"):\n new_structure_attributes.pop(key)\n\n # Structure features values are Enum values, so we need to convert them to\n # their string (true) values\n new_structure_attributes[\"structure_features\"] = [\n _.value for _ in structure.attributes.structure_features\n ]\n\n if single_entity:\n new_structure_attributes[\"id\"] = structure.id\n new_structure_attributes[\"type\"] = structure.type\n\n new_structure = OPTIMADEStructure(\n dimensions={\n \"nelements\": structure.attributes.nelements or 0,\n \"dimensionality\": 3,\n \"nsites\": structure.attributes.nsites or 0,\n \"nstructure_features\": len(\n structure.attributes.structure_features\n ),\n **single_entity_dimensions,\n },\n properties=new_structure_attributes,\n )\n else:\n if \"attributes\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\"Could not find entity for 'attributes'.\")\n\n new_structure = OPTIMADEStructure(\n dimensions={},\n properties={\n \"attributes\": nested_entity_mapping[\"attributes\"](\n dimensions={\n \"nelements\": structure.attributes.nelements or 0,\n \"dimensionality\": 3,\n \"nsites\": structure.attributes.nsites or 0,\n \"nspecies\": (\n len(structure.attributes.species)\n if structure.attributes.species\n else 0\n ),\n \"nstructure_features\": len(\n structure.attributes.structure_features\n ),\n },\n properties=new_structure_attributes,\n ),\n \"type\": structure.type,\n \"id\": structure.id,\n },\n )\n\n dlite_collection.add(label=structure.id, inst=new_structure)\n\n update_collection(collection=dlite_collection)\n\n return generic_parse_result\n\n def _get_nested_entity(self, entity: dlite.Instance) -> dict[str, dlite.Instance]:\n \"\"\"Get nested entity from DLite instance.\"\"\"\n nested_entities: dict[str, dlite.Instance] = {}\n\n for prop in entity.properties[\"properties\"]:\n if prop.type == \"ref\":\n nested_entities[prop.name] = dlite.get_instance(prop.ref)\n\n for name, nested_entity in tuple(nested_entities.items()):\n futher_nested_entities = self._get_nested_entity(nested_entity)\n\n for nested_name, further_nested_entity in futher_nested_entities.items():\n nested_entities[f\"{name}.{nested_name}\"] = further_nested_entity\n\n return nested_entities\n
"},{"location":"api_reference/dlite/parse/#oteapi_optimade.dlite.parse.OPTIMADEDLiteParseStrategy.get","title":"get()
","text":"Request and parse an OPTIMADE response using OPT.
This method will be called through the strategy-specific endpoint of the OTE-API Services.
Configuration values provided in resource_config.configuration
take precedence over the derived values from downloadUrl
.
Workflow:
The OPTIMADE Structure needs to be parsed into DLite instances inside-out, meaning the most nested data structures must first be parsed, and then the ones 1 layer up and so on until the most upper layer can be parsed.
Returns:
Type DescriptionOPTIMADEParseResult
An update model of key/value-pairs to be stored in the session-specific
OPTIMADEParseResult
context from services.
Source code inoteapi_optimade/dlite/parse.py
def get(self) -> OPTIMADEParseResult:\n \"\"\"Request and parse an OPTIMADE response using OPT.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `downloadUrl`.\n\n Workflow:\n\n 1. Request OPTIMADE response.\n 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model.\n\n ---\n\n The OPTIMADE Structure needs to be parsed into DLite instances inside-out,\n meaning the most nested data structures must first be parsed, and then the ones\n 1 layer up and so on until the most upper layer can be parsed.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n generic_parse_config = self.parse_config.model_copy(\n update={\n \"parserType\": self.parse_config.parserType.replace(\"/dlite\", \"\"),\n \"configuration\": self.parse_config.configuration.model_copy(\n update={\n \"mediaType\": self.parse_config.configuration.get(\n \"mediaType\", \"\"\n ).replace(\"+dlite\", \"+json\")\n }\n ),\n }\n ).model_dump(exclude_unset=True, exclude_defaults=True)\n generic_parse_result = OPTIMADEParseStrategy(generic_parse_config).get()\n\n OPTIMADEStructure = dlite.get_instance(str(self.parse_config.entity))\n\n single_entity = \"OPTIMADEStructureResource\" in OPTIMADEStructure.uri\n\n if not single_entity:\n nested_entity_mapping: dict[str, dlite.Instance] = self._get_nested_entity(\n OPTIMADEStructure\n )\n\n if not (\n generic_parse_result.optimade_response\n and generic_parse_result.optimade_response_model\n ):\n base_error_message = (\n \"Could not retrieve response from OPTIMADE parse strategy.\"\n )\n LOGGER.error(\n \"%s\\n\"\n \"optimade_response=%r\\n\"\n \"optimade_response_model=%r\\n\"\n \"session fields=%r\",\n base_error_message,\n generic_parse_result.get(\"optimade_response\"),\n generic_parse_result.get(\"optimade_response_model\"),\n list(generic_parse_result),\n )\n raise OPTIMADEParseError(base_error_message)\n\n optimade_response_model_module, optimade_response_model_name = (\n generic_parse_result.optimade_response_model\n )\n\n # Parse response using the provided model\n try:\n optimade_response_model: type[OPTIMADEResponse] = getattr(\n importlib.import_module(optimade_response_model_module),\n optimade_response_model_name,\n )\n optimade_response = optimade_response_model(\n **generic_parse_result.optimade_response\n )\n except (ImportError, AttributeError) as exc:\n base_error_message = \"Could not import the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ImportError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n except ValidationError as exc:\n base_error_message = \"Could not validate the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ValidationError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n\n # Currently, only \"structures\" entries are supported and handled\n if isinstance(optimade_response, StructureResponseMany):\n structures: list[StructureResource] = [\n (\n StructureResource(**entry)\n if isinstance(entry, dict)\n else entry.model_copy(deep=True)\n )\n for entry in optimade_response.data\n ]\n\n elif isinstance(optimade_response, StructureResponseOne):\n structures = (\n [\n (\n StructureResource(**optimade_response.data)\n if isinstance(optimade_response.data, dict)\n else optimade_response.data.model_copy(deep=True)\n )\n ]\n if optimade_response.data is not None\n else []\n )\n\n elif isinstance(optimade_response, Success):\n if isinstance(optimade_response.data, dict):\n structures = [StructureResource(**optimade_response.data)]\n elif isinstance(optimade_response.data, BaseModel):\n structures = [StructureResource(**optimade_response.data.model_dump())]\n elif isinstance(optimade_response.data, list):\n structures = [\n (\n StructureResource(**entry)\n if isinstance(entry, dict)\n else StructureResource(**entry.model_dump())\n )\n for entry in optimade_response.data\n ]\n elif optimade_response.data is None:\n structures = []\n else:\n LOGGER.error(\n \"Could not determine what to do with `data`. Type %s.\",\n type(optimade_response.data),\n )\n raise OPTIMADEParseError(\"Could not parse `data` entry in response.\")\n\n else:\n LOGGER.error(\n \"Got currently unsupported response type %s. Only structures are \"\n \"supported.\",\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(\n \"The DLite OPTIMADE Parser currently only supports structures entities.\"\n )\n\n if not structures:\n LOGGER.warning(\"No structures found in the response.\")\n return generic_parse_result\n\n # DLite-fy OPTIMADE structures\n dlite_collection = get_collection(\n collection_id=self.parse_config.configuration.collection_id\n )\n\n for structure in structures:\n new_structure_attributes: dict[str, Any] = {}\n single_entity_dimensions: dict[str, int] = {\n \"nassemblies\": 0,\n \"nspecies\": 0,\n }\n\n ## For OPTIMADEStructure (multiple) entities\n # Most inner layer: assemblies & species\n if structure.attributes.assemblies:\n if single_entity:\n single_entity_dimensions[\"nassemblies\"] = len(\n structure.attributes.assemblies\n )\n new_structure_attributes.update(\n {\n \"assemblies_sites_in_groups\": [],\n \"assemblies_group_probabilities\": [],\n }\n )\n else:\n new_structure_attributes[\"assemblies\"] = []\n\n for assembly in structure.attributes.assemblies:\n if single_entity:\n new_structure_attributes[\"assemblies_sites_in_groups\"].append(\n \";\".join(\n [\n \",\".join(str(_) for _ in group)\n for group in assembly.sites_in_groups\n ]\n )\n )\n new_structure_attributes[\n \"assemblies_group_probabilities\"\n ].append(\",\".join(str(_) for _ in assembly.group_probabilities))\n else:\n if \"attributes.assemblies\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes.assemblies'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\n \"Could not find entity for 'attributes.assemblies'.\"\n )\n\n dimensions = {\n \"ngroups\": len(assembly.group_probabilities),\n \"nsites\": len(assembly.sites_in_groups),\n }\n new_structure_attributes[\"assemblies\"].append(\n nested_entity_mapping[\"attributes.assemblies\"](\n dimensions=dimensions, properties=assembly.model_dump()\n )\n )\n\n if structure.attributes.species:\n if single_entity:\n single_entity_dimensions[\"nspecies\"] = len(\n structure.attributes.species\n )\n new_structure_attributes.update(\n {\n \"species_name\": [],\n \"species_chemical_symbols\": [],\n \"species_concentration\": [],\n \"species_mass\": [],\n \"species_original_name\": [],\n \"species_attached\": [],\n \"species_nattached\": [],\n }\n )\n else:\n new_structure_attributes[\"species\"] = []\n\n for species in structure.attributes.species:\n if single_entity:\n new_structure_attributes[\"species_name\"].append(species.name)\n new_structure_attributes[\"species_chemical_symbols\"].append(\n \",\".join(species.chemical_symbols)\n )\n new_structure_attributes[\"species_concentration\"].append(\n \",\".join(str(_) for _ in species.concentration)\n )\n new_structure_attributes[\"species_mass\"].append(\n \",\".join(str(_) for _ in (species.mass or []))\n )\n new_structure_attributes[\"species_original_name\"].append(\n species.original_name or \"\"\n )\n new_structure_attributes[\"species_attached\"].append(\n \",\".join(species.attached or [])\n )\n new_structure_attributes[\"species_nattached\"].append(\n \",\".join(str(_) for _ in (species.nattached or []))\n )\n else:\n if \"attributes.species\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes.species'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\n \"Could not find entity for 'attributes.species'.\"\n )\n\n dimensions = {\n \"nelements\": len(species.chemical_symbols),\n \"nattached_elements\": len(species.attached or []),\n }\n new_structure_attributes[\"species\"].append(\n nested_entity_mapping[\"attributes.species\"](\n dimensions=dimensions,\n properties=species.model_dump(exclude_none=True),\n )\n )\n\n # Attributes\n new_structure_attributes.update(\n structure.attributes.model_dump(\n exclude={\n \"species\",\n \"assemblies\",\n \"nelements\",\n \"nsites\",\n \"structure_features\",\n },\n exclude_unset=True,\n exclude_defaults=True,\n exclude_none=True,\n )\n )\n for key in list(new_structure_attributes):\n if key.startswith(\"_\"):\n new_structure_attributes.pop(key)\n\n # Structure features values are Enum values, so we need to convert them to\n # their string (true) values\n new_structure_attributes[\"structure_features\"] = [\n _.value for _ in structure.attributes.structure_features\n ]\n\n if single_entity:\n new_structure_attributes[\"id\"] = structure.id\n new_structure_attributes[\"type\"] = structure.type\n\n new_structure = OPTIMADEStructure(\n dimensions={\n \"nelements\": structure.attributes.nelements or 0,\n \"dimensionality\": 3,\n \"nsites\": structure.attributes.nsites or 0,\n \"nstructure_features\": len(\n structure.attributes.structure_features\n ),\n **single_entity_dimensions,\n },\n properties=new_structure_attributes,\n )\n else:\n if \"attributes\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\"Could not find entity for 'attributes'.\")\n\n new_structure = OPTIMADEStructure(\n dimensions={},\n properties={\n \"attributes\": nested_entity_mapping[\"attributes\"](\n dimensions={\n \"nelements\": structure.attributes.nelements or 0,\n \"dimensionality\": 3,\n \"nsites\": structure.attributes.nsites or 0,\n \"nspecies\": (\n len(structure.attributes.species)\n if structure.attributes.species\n else 0\n ),\n \"nstructure_features\": len(\n structure.attributes.structure_features\n ),\n },\n properties=new_structure_attributes,\n ),\n \"type\": structure.type,\n \"id\": structure.id,\n },\n )\n\n dlite_collection.add(label=structure.id, inst=new_structure)\n\n update_collection(collection=dlite_collection)\n\n return generic_parse_result\n
"},{"location":"api_reference/dlite/parse/#oteapi_optimade.dlite.parse.OPTIMADEDLiteParseStrategy.initialize","title":"initialize()
","text":"Initialize strategy.
This method will be called through the /initialize
endpoint of the OTE-API Services.
Returns:
Type DescriptionDLiteSessionUpdate
An update model of key/value-pairs to be stored in the session-specific
DLiteSessionUpdate
context from services.
Source code inoteapi_optimade/dlite/parse.py
def initialize(self) -> DLiteSessionUpdate:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n return DLiteSessionUpdate(\n collection_id=get_collection(\n collection_id=self.parse_config.configuration.collection_id\n ).uuid\n )\n
"},{"location":"api_reference/models/config/","title":"config","text":"General OPTIMADE configuration models.
"},{"location":"api_reference/models/config/#oteapi_optimade.models.config.DEFAULT_CACHE_CONFIG_VALUES","title":"DEFAULT_CACHE_CONFIG_VALUES = {'expireTime': 60 * 60 * 24, 'tag': 'optimade'}
module-attribute
","text":"Set the expireTime
and tag
to default values for the data cache.
OPTIMADEConfig
","text":" Bases: AttrDict
OPTIMADE configuration.
Source code inoteapi_optimade/models/config.py
class OPTIMADEConfig(AttrDict):\n \"\"\"OPTIMADE configuration.\"\"\"\n\n # OTEAPI-specific attributes\n downloadUrl: Annotated[\n Optional[OPTIMADEUrl],\n Field(description=\"Either a base OPTIMADE URL or a full OPTIMADE URL.\"),\n ] = None\n\n mediaType: Annotated[\n Optional[Literal[\"application/vnd.optimade+json\", \"application/vnd.optimade\"]],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=\"The registered strategy name for OPTIMADEParseStrategy.\",\n ),\n ] = None\n\n # OPTIMADE parse result attributes\n optimade_config: Annotated[\n Optional[OPTIMADEConfig],\n Field(description=\"A pre-existing instance of this OPTIMADE configuration.\"),\n ] = None\n\n # OPTIMADE-specific attributes\n version: Annotated[\n str,\n Field(\n description=\"The version part of the OPTIMADE versioned base URL.\",\n pattern=r\"^v[0-9]+(\\.[0-9]+){0,2}$\",\n ),\n ] = \"v1\"\n\n endpoint: Annotated[\n Literal[\"references\", \"structures\"],\n Field(\n description=\"Supported OPTIMADE entry resource endpoint.\",\n ),\n ] = \"structures\"\n\n query_parameters: Annotated[\n Optional[OPTIMADEQueryParameters],\n Field(\n description=\"URL query parameters to be used in the OPTIMADE query.\",\n ),\n ] = None\n\n datacache_config: Annotated[\n DataCacheConfig,\n Field(\n description=\"Configuration options for the local data cache.\",\n ),\n ] = DataCacheConfig(**DEFAULT_CACHE_CONFIG_VALUES)\n\n use_dlite: Annotated[\n bool,\n Field(\n description=\"Whether or not to store the results in a DLite Collection.\",\n ),\n ] = False\n\n @field_validator(\"datacache_config\", mode=\"after\")\n @classmethod\n def _default_datacache_config(\n cls, datacache_config: DataCacheConfig\n ) -> DataCacheConfig:\n \"\"\"Use default values for `DataCacheConfig` if not supplied.\"\"\"\n original_set_values = len(datacache_config.model_fields_set)\n\n for field, default_value in DEFAULT_CACHE_CONFIG_VALUES.items():\n if field in datacache_config.model_fields_set:\n # Use the set value instead of the default\n continue\n setattr(datacache_config, field, default_value)\n\n if len(datacache_config.model_fields_set) > original_set_values:\n # Re-validate model and return it\n return datacache_config.model_validate(\n {\n field: field_value\n for field, field_value in datacache_config.model_dump().items()\n if field in datacache_config.model_fields_set\n }\n )\n return datacache_config\n
"},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.datacache_config","title":"datacache_config: Annotated[DataCacheConfig, Field(description='Configuration options for the local data cache.')] = DataCacheConfig(**DEFAULT_CACHE_CONFIG_VALUES)
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.downloadUrl","title":"downloadUrl: Annotated[Optional[OPTIMADEUrl], Field(description='Either a base OPTIMADE URL or a full OPTIMADE URL.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.endpoint","title":"endpoint: Annotated[Literal['references', 'structures'], Field(description='Supported OPTIMADE entry resource endpoint.')] = 'structures'
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.mediaType","title":"mediaType: Annotated[Optional[Literal['application/vnd.optimade+json', 'application/vnd.optimade']], BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x), Field(description='The registered strategy name for OPTIMADEParseStrategy.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.optimade_config","title":"optimade_config: Annotated[Optional[OPTIMADEConfig], Field(description='A pre-existing instance of this OPTIMADE configuration.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.query_parameters","title":"query_parameters: Annotated[Optional[OPTIMADEQueryParameters], Field(description='URL query parameters to be used in the OPTIMADE query.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.use_dlite","title":"use_dlite: Annotated[bool, Field(description='Whether or not to store the results in a DLite Collection.')] = False
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.version","title":"version: Annotated[str, Field(description='The version part of the OPTIMADE versioned base URL.', pattern='^v[0-9]+(\\\\.[0-9]+){0,2}$')] = 'v1'
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEDLiteConfig","title":"OPTIMADEDLiteConfig
","text":" Bases: OPTIMADEConfig
OPTIMADE configuration when using the DLite-specific strategies.
Source code inoteapi_optimade/models/config.py
class OPTIMADEDLiteConfig(OPTIMADEConfig):\n \"\"\"OPTIMADE configuration when using the DLite-specific strategies.\"\"\"\n\n # OTEAPI-specific attributes\n mediaType: Annotated[\n Optional[Literal[\"application/vnd.optimade+dlite\"]],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=\"The registered strategy name for OPTIMADEDLiteParseStrategy.\",\n ),\n ] = None # type: ignore[assignment]\n\n # Dlite specific attributes\n collection_id: Annotated[\n Optional[str],\n Field(description=\"A reference to a DLite Collection.\"),\n ] = None\n
"},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEDLiteConfig.collection_id","title":"collection_id: Annotated[Optional[str], Field(description='A reference to a DLite Collection.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEDLiteConfig.mediaType","title":"mediaType: Annotated[Optional[Literal['application/vnd.optimade+dlite']], BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x), Field(description='The registered strategy name for OPTIMADEDLiteParseStrategy.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/","title":"custom_types","text":"Custom \"pydantic\" types used in OTEAPI-OPTIMADE.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.LOGGER","title":"LOGGER = logging.getLogger(__name__)
module-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts","title":"OPTIMADEParts
","text":" Bases: TypedDict
Similar to pydantic.networks.Parts
.
oteapi_optimade/models/custom_types.py
class OPTIMADEParts(TypedDict, total=False):\n \"\"\"Similar to `pydantic.networks.Parts`.\"\"\"\n\n base_url: str\n version: str | None\n endpoint: str | None\n query: str | None\n
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts.base_url","title":"base_url: str
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts.endpoint","title":"endpoint: str | None
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts.query","title":"query: str | None
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts.version","title":"version: str | None
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl","title":"OPTIMADEUrl
","text":" Bases: str
A deconstructed OPTIMADE URL.
An OPTIMADE URL is made up in the following way:
<BASE URL>[/<VERSION>]/<ENDPOINT>?[<QUERY PARAMETERS>]\n
Where parts in square brackets ([]
) are optional.
oteapi_optimade/models/custom_types.py
class OPTIMADEUrl(str):\n \"\"\"A deconstructed OPTIMADE URL.\n\n An OPTIMADE URL is made up in the following way:\n\n <BASE URL>[/<VERSION>]/<ENDPOINT>?[<QUERY PARAMETERS>]\n\n Where parts in square brackets (`[]`) are optional.\n \"\"\"\n\n # https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers\n max_length = 2083\n allowed_schemes: ClassVar[list[str]] = [\"http\", \"https\"]\n host_required = True\n\n @no_type_check\n def __new__(cls, url: str | None = None, **kwargs) -> object:\n return str.__new__(\n cls,\n url if url else cls._build(**kwargs),\n )\n\n def __init__(\n self,\n url: str,\n *,\n base_url: Optional[str] = None,\n version: Optional[str] = None,\n endpoint: Optional[str] = None,\n query: Optional[str] = None,\n ) -> None:\n str.__init__(url)\n\n # Parse as URL\n try:\n pydantic_url = AnyHttpUrl(url)\n except ValidationError:\n try:\n pydantic_url = AnyHttpUrl(\n self._build(\n base_url=base_url or \"\",\n version=version,\n endpoint=endpoint,\n query=query,\n )\n )\n except ValidationError:\n pydantic_url = None\n\n # Build OPTIMADE URL parts\n optimade_parts: Union[OPTIMADEParts, dict[str, Any]] = {}\n if pydantic_url:\n optimade_parts = self._build_optimade_parts(pydantic_url)\n\n self._base_url = base_url or optimade_parts.get(\"base_url\", None)\n self._version = version or optimade_parts.get(\"version\", None)\n self._endpoint = endpoint or optimade_parts.get(\"endpoint\", None)\n self._query = query or optimade_parts.get(\"query\", None)\n self._scheme = self._base_url.split(\"://\")[0] if self._base_url else None\n\n def __str__(self) -> str:\n return self._build(\n base_url=self.base_url,\n version=self.version,\n endpoint=self.endpoint,\n query=self.query,\n )\n\n def __repr__(self) -> str:\n extra = \", \".join(\n f\"{n}={getattr(self, n)!r}\"\n for n in (\"scheme\", \"base_url\", \"version\", \"endpoint\", \"query\")\n if getattr(self, n) is not None\n )\n return f\"{self.__class__.__name__}({super().__repr__()}, {extra})\"\n\n @staticmethod\n def _build(\n *,\n base_url: str,\n version: Optional[str] = None,\n endpoint: Optional[str] = None,\n query: Optional[str] = None,\n ) -> str:\n \"\"\"Build complete OPTIMADE URL from URL parts.\"\"\"\n url = base_url.rstrip(\"/\")\n if version:\n url += f\"/{version}\"\n if endpoint:\n url += f\"/{endpoint}\"\n if query:\n url += f\"?{query}\"\n return url\n\n @property\n def scheme(self) -> str:\n \"\"\"The scheme of the OPTIMADE URL.\"\"\"\n if self._scheme is None:\n error_message = \"OPTIMADE URL has no scheme.\"\n raise ValueError(error_message)\n return self._scheme\n\n @property\n def base_url(self) -> str:\n \"\"\"The base URL of the OPTIMADE URL.\"\"\"\n if self._base_url is None:\n error_message = \"OPTIMADE URL has no base URL.\"\n raise ValueError(error_message)\n return self._base_url\n\n @property\n def version(self) -> Optional[str]:\n \"\"\"The version part of the OPTIMADE URL.\"\"\"\n return self._version\n\n @property\n def endpoint(self) -> Optional[str]:\n \"\"\"The endpoint part of the OPTIMADE URL.\"\"\"\n return self._endpoint\n\n @property\n def query(self) -> Optional[str]:\n \"\"\"The query part of the OPTIMADE URL.\"\"\"\n return self._query\n\n def response_model(self) -> Union[tuple[Success, Success], Success, None]:\n \"\"\"Return the endpoint's corresponding response model(s) (from OPT).\"\"\"\n if not self.endpoint or self.endpoint == \"versions\":\n return None\n\n return {\n \"info\": (InfoResponse, EntryInfoResponse),\n \"links\": LinksResponse,\n \"structures\": (StructureResponseMany, StructureResponseOne),\n \"references\": (ReferenceResponseMany, ReferenceResponseOne),\n \"calculations\": (EntryResponseMany, EntryResponseOne),\n }.get(self.endpoint, Success)\n\n # Pydantic-related methods\n @classmethod\n def __get_pydantic_core_schema__(\n cls, _source_type: Any, _handler: GetCoreSchemaHandler\n ) -> CoreSchema:\n \"\"\"Pydantic core schema for an OPTIMADE URL.\n\n Behaviour:\n - strings and `Url` instances will be parsed as `pydantic.AnyHttpUrl` instances\n and then converted to `OPTIMADEUrl` instances.\n - `OPTIMADEUrl` instances will be parsed as `OPTIMADEUrl` instances without any changes.\n - Nothing else will pass validation\n - Serialization will always return just a str.\n \"\"\"\n from_str_schema = core_schema.chain_schema(\n [\n core_schema.url_schema(\n max_length=cls.max_length,\n host_required=cls.host_required,\n allowed_schemes=cls.allowed_schemes,\n ),\n core_schema.no_info_plain_validator_function(\n cls._validate_from_str_or_url\n ),\n ],\n )\n\n from_url_schema = core_schema.chain_schema(\n [\n core_schema.is_instance_schema(Url),\n core_schema.no_info_plain_validator_function(\n cls._validate_from_str_or_url\n ),\n ],\n )\n\n return core_schema.json_or_python_schema(\n json_schema=from_str_schema,\n python_schema=core_schema.union_schema(\n [\n core_schema.is_instance_schema(cls),\n from_url_schema,\n from_str_schema,\n ],\n ),\n serialization=core_schema.plain_serializer_function_ser_schema(str),\n )\n\n @classmethod\n def __get_pydantic_json_schema__(\n cls, _core_schema: CoreSchema, handler: GetJsonSchemaHandler\n ) -> JsonSchemaValue:\n # Use the same schema that would be used for an AnyHttpUrl\n return handler(\n core_schema.url_schema(\n max_length=cls.max_length,\n host_required=cls.host_required,\n allowed_schemes=cls.allowed_schemes,\n )\n )\n\n @classmethod\n def _validate_from_str_or_url(cls, value: Union[Url, str]) -> OPTIMADEUrl:\n \"\"\"Pydantic validation of an OPTIMADE URL.\"\"\"\n # Parse as URL\n url = AnyHttpUrl(str(value))\n\n # Build OPTIMADE URL parts\n optimade_parts = cls._build_optimade_parts(url)\n\n return cls( # type: ignore[no-any-return]\n None,\n base_url=optimade_parts[\"base_url\"],\n version=optimade_parts[\"version\"],\n endpoint=optimade_parts[\"endpoint\"],\n query=optimade_parts[\"query\"],\n )\n\n @classmethod\n def _build_optimade_parts(cls, url: AnyHttpUrl) -> OPTIMADEParts:\n \"\"\"Convert URL parts to equivalent OPTIMADE URL parts.\"\"\"\n base_url = f\"{url.scheme}://\"\n\n if url.username:\n base_url += url.username\n\n if url.password:\n base_url += f\":{url.password}\"\n\n if url.username and url.password:\n base_url += \"@\"\n\n # This check is done to satisfy type checker.\n # Since the url has been parsed as a `AnyHttpUrl`, it must always have a host.\n if url.host is None:\n error_message = \"Could not parse given string as a URL.\"\n raise ValueError(error_message)\n\n base_url += url.host\n\n # Hide port if it's a standard HTTP (80) or HTTPS (443) port.\n if url.port and url.port not in (80, 443):\n base_url += f\":{url.port}\"\n\n if url.path:\n base_url += url.path\n\n base_url_match = _OPTIMADE_BASE_URL_REGEX.fullmatch(base_url)\n LOGGER.debug(\n \"OPTIMADE base URL regex match groups: %s\",\n base_url_match.groupdict() if base_url_match else base_url_match,\n )\n if base_url_match is None:\n error_message = \"Could not match given string with OPTIMADE base URL regex.\"\n raise ValueError(error_message)\n\n endpoint_match = _OPTIMADE_ENDPOINT_REGEX.findall(\n base_url_match.group(\"path\") if base_url_match.group(\"path\") else \"\"\n )\n LOGGER.debug(\"OPTIMADE endpoint regex matches: %s\", endpoint_match)\n for path_version, path_endpoint in endpoint_match: # noqa: B007\n if path_endpoint:\n break\n else:\n LOGGER.debug(\"Could not match given string with OPTIMADE endpoint regex.\")\n path_version, path_endpoint = \"\", \"\"\n\n base_url = base_url_match.group(\"base_url\")\n if path_version:\n base_url = base_url[: -(len(path_version) + len(path_endpoint) + 2)]\n elif path_endpoint:\n base_url = base_url[: -(len(path_endpoint) + 1)]\n\n optimade_parts = {\n \"base_url\": base_url.rstrip(\"/\"),\n \"version\": path_version or None,\n \"endpoint\": path_endpoint or None,\n \"query\": url.query,\n }\n return cast(\"OPTIMADEParts\", optimade_parts)\n
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.allowed_schemes","title":"allowed_schemes: list[str] = ['http', 'https']
class-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.base_url","title":"base_url: str
property
","text":"The base URL of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.endpoint","title":"endpoint: Optional[str]
property
","text":"The endpoint part of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.host_required","title":"host_required = True
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.max_length","title":"max_length = 2083
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.query","title":"query: Optional[str]
property
","text":"The query part of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.scheme","title":"scheme: str
property
","text":"The scheme of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.version","title":"version: Optional[str]
property
","text":"The version part of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.__init__","title":"__init__(url, *, base_url=None, version=None, endpoint=None, query=None)
","text":"Source code in oteapi_optimade/models/custom_types.py
def __init__(\n self,\n url: str,\n *,\n base_url: Optional[str] = None,\n version: Optional[str] = None,\n endpoint: Optional[str] = None,\n query: Optional[str] = None,\n) -> None:\n str.__init__(url)\n\n # Parse as URL\n try:\n pydantic_url = AnyHttpUrl(url)\n except ValidationError:\n try:\n pydantic_url = AnyHttpUrl(\n self._build(\n base_url=base_url or \"\",\n version=version,\n endpoint=endpoint,\n query=query,\n )\n )\n except ValidationError:\n pydantic_url = None\n\n # Build OPTIMADE URL parts\n optimade_parts: Union[OPTIMADEParts, dict[str, Any]] = {}\n if pydantic_url:\n optimade_parts = self._build_optimade_parts(pydantic_url)\n\n self._base_url = base_url or optimade_parts.get(\"base_url\", None)\n self._version = version or optimade_parts.get(\"version\", None)\n self._endpoint = endpoint or optimade_parts.get(\"endpoint\", None)\n self._query = query or optimade_parts.get(\"query\", None)\n self._scheme = self._base_url.split(\"://\")[0] if self._base_url else None\n
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.response_model","title":"response_model()
","text":"Return the endpoint's corresponding response model(s) (from OPT).
Source code inoteapi_optimade/models/custom_types.py
def response_model(self) -> Union[tuple[Success, Success], Success, None]:\n \"\"\"Return the endpoint's corresponding response model(s) (from OPT).\"\"\"\n if not self.endpoint or self.endpoint == \"versions\":\n return None\n\n return {\n \"info\": (InfoResponse, EntryInfoResponse),\n \"links\": LinksResponse,\n \"structures\": (StructureResponseMany, StructureResponseOne),\n \"references\": (ReferenceResponseMany, ReferenceResponseOne),\n \"calculations\": (EntryResponseMany, EntryResponseOne),\n }.get(self.endpoint, Success)\n
"},{"location":"api_reference/models/query/","title":"query","text":"Data models related to OPTIMADE queries.
"},{"location":"api_reference/models/query/#oteapi_optimade.models.query.QUERY_PARAMETERS","title":"QUERY_PARAMETERS = {'annotations': {name: FieldInfo.from_annotation(parameter.annotation)for (name, parameter) in inspect.signature(EntryListingQueryParams).parameters.items()}, 'defaults': EntryListingQueryParams()}
module-attribute
","text":"Entry listing URL query parameters from the optimade
package (EntryListingQueryParams
).
OPTIMADEQueryParameters
","text":" Bases: BaseModel
Common OPTIMADE entry listing endpoint query parameters.
Source code inoteapi_optimade/models/query.py
class OPTIMADEQueryParameters(BaseModel, validate_assignment=True):\n \"\"\"Common OPTIMADE entry listing endpoint query parameters.\"\"\"\n\n filter: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"filter\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].filter or None\n )\n response_format: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"response_format\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].response_format or None\n )\n email_address: Annotated[\n Optional[EmailStr],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"email_address\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].email_address or None\n )\n response_fields: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"response_fields\"].description,\n pattern=QUERY_PARAMETERS[\"annotations\"][\"response_fields\"]\n .metadata[0]\n .pattern,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].response_fields or None\n )\n sort: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"sort\"].description,\n pattern=QUERY_PARAMETERS[\"annotations\"][\"sort\"].metadata[0].pattern,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].sort or None\n )\n page_limit: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_limit\"].description,\n ge=QUERY_PARAMETERS[\"annotations\"][\"page_limit\"].metadata[0].ge,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_limit or None\n )\n page_offset: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_offset\"].description,\n ge=QUERY_PARAMETERS[\"annotations\"][\"page_offset\"].metadata[0].ge,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_offset or None\n )\n page_number: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_number\"].description,\n # ge=QUERY_PARAMETERS[\"annotations\"][\"page_number\"].metadata[0].ge,\n # This constraint is only 'RECOMMENDED' in the specification, so should not\n # be included here or in the OpenAPI schema.\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_number or None\n )\n page_cursor: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_cursor\"].description,\n ge=QUERY_PARAMETERS[\"annotations\"][\"page_cursor\"].metadata[0].ge,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_cursor or None\n )\n page_above: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_above\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_above or None\n )\n page_below: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_below\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_below or None\n )\n include: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"include\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].include or None\n )\n # api_hint is not yet initialized in `EntryListingQueryParams`.\n # These values are copied verbatim from `optimade==0.16.10`.\n api_hint: Annotated[\n Optional[str],\n Field(\n description=(\n \"If the client provides the parameter, the value SHOULD have the format \"\n \"`vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a\"\n \" minor version of the API. For example, if a client appends \"\n \"`api_hint=v1.0` to the query string, the hint provided is for major \"\n \"version 1 and minor version 0.\"\n ),\n pattern=r\"(v[0-9]+(\\.[0-9]+)?)?\",\n ),\n ] = \"\"\n\n def generate_query_string(self) -> str:\n \"\"\"Generate a valid URL query string based on the set fields.\"\"\"\n res = {}\n for field, value in self.model_dump().items():\n if value or field in self.model_fields_set:\n res[field] = unquote(value) if isinstance(value, str) else value\n return urlencode(res, quote_via=quote)\n
"},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.api_hint","title":"api_hint: Annotated[Optional[str], Field(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='(v[0-9]+(\\\\.[0-9]+)?)?')] = ''
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.email_address","title":"email_address: Annotated[Optional[EmailStr], Field(description=QUERY_PARAMETERS['annotations']['email_address'].description)] = QUERY_PARAMETERS['defaults'].email_address or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.filter","title":"filter: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['filter'].description)] = QUERY_PARAMETERS['defaults'].filter or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.include","title":"include: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['include'].description)] = QUERY_PARAMETERS['defaults'].include or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_above","title":"page_above: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_above'].description)] = QUERY_PARAMETERS['defaults'].page_above or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_below","title":"page_below: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_below'].description)] = QUERY_PARAMETERS['defaults'].page_below or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_cursor","title":"page_cursor: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_cursor'].description, ge=QUERY_PARAMETERS['annotations']['page_cursor'].metadata[0].ge)] = QUERY_PARAMETERS['defaults'].page_cursor or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_limit","title":"page_limit: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_limit'].description, ge=QUERY_PARAMETERS['annotations']['page_limit'].metadata[0].ge)] = QUERY_PARAMETERS['defaults'].page_limit or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_number","title":"page_number: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_number'].description)] = QUERY_PARAMETERS['defaults'].page_number or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_offset","title":"page_offset: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_offset'].description, ge=QUERY_PARAMETERS['annotations']['page_offset'].metadata[0].ge)] = QUERY_PARAMETERS['defaults'].page_offset or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.response_fields","title":"response_fields: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['response_fields'].description, pattern=QUERY_PARAMETERS['annotations']['response_fields'].metadata[0].pattern)] = QUERY_PARAMETERS['defaults'].response_fields or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.response_format","title":"response_format: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['response_format'].description)] = QUERY_PARAMETERS['defaults'].response_format or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.sort","title":"sort: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['sort'].description, pattern=QUERY_PARAMETERS['annotations']['sort'].metadata[0].pattern)] = QUERY_PARAMETERS['defaults'].sort or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.generate_query_string","title":"generate_query_string()
","text":"Generate a valid URL query string based on the set fields.
Source code inoteapi_optimade/models/query.py
def generate_query_string(self) -> str:\n \"\"\"Generate a valid URL query string based on the set fields.\"\"\"\n res = {}\n for field, value in self.model_dump().items():\n if value or field in self.model_fields_set:\n res[field] = unquote(value) if isinstance(value, str) else value\n return urlencode(res, quote_via=quote)\n
"},{"location":"api_reference/models/strategies/filter/","title":"filter","text":"Models specific to the filter strategy.
"},{"location":"api_reference/models/strategies/filter/#oteapi_optimade.models.strategies.filter.OPTIMADEFilterConfig","title":"OPTIMADEFilterConfig
","text":" Bases: FilterConfig
OPTIMADE-specific filter strategy config.
NoteThe condition
parameter is not taken into account.
oteapi_optimade/models/strategies/filter.py
class OPTIMADEFilterConfig(FilterConfig):\n \"\"\"OPTIMADE-specific filter strategy config.\n\n Note:\n The `condition` parameter is not taken into account.\n\n \"\"\"\n\n filterType: Annotated[\n Literal[\"optimade\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=\"The registered strategy name for OPTIMADEFilterStrategy.\",\n ),\n ]\n query: Annotated[\n Optional[str],\n Field(\n description=(\n \"The `filter` OPTIMADE query parameter value. This parameter value can \"\n \"also be provided through the [`configuration.query_parameters.filter`]\"\n \"[oteapi_optimade.models.query.OPTIMADEQueryParameters.filter] parameter. \"\n \"Note, this value takes precedence over [`configuration`][oteapi_optimade.\"\n \"models.strategies.filter.OPTIMADEFilterConfig.configuration] values.\"\n ),\n ),\n ] = None\n limit: Annotated[\n Optional[int],\n Field(\n description=(\n \"The `page_limit` OPTIMADE query parameter value. This parameter value can\"\n \" also be provided through the [`configuration.query_parameters.\"\n \"page_limit`][oteapi_optimade.models.query.OPTIMADEQueryParameters.\"\n \"page_limit] parameter. Note, this value takes precedence over \"\n \"[`configuration`][oteapi_optimade.models.strategies.filter.\"\n \"OPTIMADEFilterConfig.configuration] values.\"\n ),\n ),\n ] = None\n configuration: Annotated[\n OPTIMADEConfig,\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = OPTIMADEConfig()\n
"},{"location":"api_reference/models/strategies/filter/#oteapi_optimade.models.strategies.filter.OPTIMADEFilterResult","title":"OPTIMADEFilterResult
","text":" Bases: AttrDict
OPTIMADE session for the filter strategy.
Source code inoteapi_optimade/models/strategies/filter.py
class OPTIMADEFilterResult(AttrDict):\n \"\"\"OPTIMADE session for the filter strategy.\"\"\"\n\n model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)\n\n optimade_config: Annotated[\n Optional[OPTIMADEConfig],\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = None\n optimade_response_model: Annotated[\n Optional[tuple[str, str]],\n Field(\n description=(\n \"An OPTIMADE Python tools (OPT) pydantic successful response model. \"\n \"More specifically, a tuple of the module and name of the pydantic \"\n \"model.\"\n ),\n ),\n ] = None\n optimade_response: Annotated[\n Optional[dict[str, Any]],\n Field(\n description=\"An OPTIMADE response as a Python dictionary.\",\n ),\n ] = None\n
"},{"location":"api_reference/models/strategies/parse/","title":"parse","text":"Models specific to the parse strategy.
"},{"location":"api_reference/models/strategies/parse/#oteapi_optimade.models.strategies.parse.SUPPORTED_ENTITIES","title":"SUPPORTED_ENTITIES = [re.compile(_) for _ in ['http://onto-ns.com/meta/[0-9]+(\\\\.[0-9]+)?(\\\\.[0-9]+)?/OPTIMADEStructure', 'http://onto-ns\\\\.com/meta/[0-9]+(\\\\.[0-9]+)?(\\\\.[0-9]+)?/OPTIMADEStructureResource']]
module-attribute
","text":"Supported entities for the OPTIMADE parse strategy.
The default entity is \"OPTIMADEStructure\". This means, if no entity is provided, the default entity will be used.
"},{"location":"api_reference/models/strategies/parse/#oteapi_optimade.models.strategies.parse.OPTIMADEDLiteParseConfig","title":"OPTIMADEDLiteParseConfig
","text":" Bases: OPTIMADEParseConfig
OPTIMADE-specific parse strategy config when using DLite.
Source code inoteapi_optimade/models/strategies/parse.py
class OPTIMADEDLiteParseConfig(OPTIMADEParseConfig):\n \"\"\"OPTIMADE-specific parse strategy config when using DLite.\"\"\"\n\n parserType: Annotated[ # type: ignore[assignment]\n Literal[\"parser/optimade/dlite\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(description=ParserConfig.model_fields[\"parserType\"].description),\n ]\n\n configuration: Annotated[ # type: ignore[assignment]\n OPTIMADEDLiteConfig,\n Field(\n description=(\n \"OPTIMADE configuration when using the DLite-specific strategies. \"\n \"Contains relevant information necessary to perform OPTIMADE queries.\"\n ),\n ),\n ] = OPTIMADEDLiteConfig()\n
"},{"location":"api_reference/models/strategies/parse/#oteapi_optimade.models.strategies.parse.OPTIMADEParseConfig","title":"OPTIMADEParseConfig
","text":" Bases: ParserConfig
OPTIMADE-specific parse strategy config.
Source code inoteapi_optimade/models/strategies/parse.py
class OPTIMADEParseConfig(ParserConfig):\n \"\"\"OPTIMADE-specific parse strategy config.\"\"\"\n\n parserType: Annotated[\n Literal[\"parser/optimade\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=ParserConfig.model_fields[\"parserType\"].description,\n ),\n ]\n\n configuration: Annotated[\n OPTIMADEConfig,\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = OPTIMADEConfig()\n\n @field_validator(\"entity\", mode=\"after\")\n def _validate_entity(cls, value: AnyHttpUrl) -> AnyHttpUrl:\n \"\"\"Validate entity.\"\"\"\n test_value = str(value).rstrip(\"/\")\n\n for entity_pattern in SUPPORTED_ENTITIES:\n if entity_pattern.fullmatch(test_value):\n return value\n\n raise ValueError(\n f\"Unsupported entity: {value}. Supported entities: {SUPPORTED_ENTITIES}\"\n )\n
"},{"location":"api_reference/models/strategies/parse/#oteapi_optimade.models.strategies.parse.OPTIMADEParseResult","title":"OPTIMADEParseResult
","text":" Bases: AttrDict
OPTIMADE parse strategy result.
Source code inoteapi_optimade/models/strategies/parse.py
class OPTIMADEParseResult(AttrDict):\n \"\"\"OPTIMADE parse strategy result.\"\"\"\n\n model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)\n\n optimade_config: Annotated[\n Optional[OPTIMADEConfig],\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = None\n optimade_response_model: Annotated[\n Optional[tuple[str, str]],\n Field(\n description=(\n \"An OPTIMADE Python tools (OPT) pydantic successful response model. \"\n \"More specifically, a tuple of the module and name of the pydantic \"\n \"model.\"\n ),\n ),\n ] = None\n optimade_response: Annotated[\n Optional[dict[str, Any]],\n Field(\n description=\"An OPTIMADE response as a Python dictionary.\",\n ),\n ] = None\n
"},{"location":"api_reference/models/strategies/resource/","title":"resource","text":"Models specific to the resource strategy.
"},{"location":"api_reference/models/strategies/resource/#oteapi_optimade.models.strategies.resource.OPTIMADEResourceConfig","title":"OPTIMADEResourceConfig
","text":" Bases: ResourceConfig
OPTIMADE-specific resource strategy config.
Source code inoteapi_optimade/models/strategies/resource.py
class OPTIMADEResourceConfig(ResourceConfig):\n \"\"\"OPTIMADE-specific resource strategy config.\"\"\"\n\n resourceType: Annotated[\n # later OPTIMADE/references and more should be added and other resources\n Literal[\"optimade/structures\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(description=ResourceConfig.model_fields[\"resourceType\"].description),\n ]\n accessUrl: Annotated[\n OPTIMADEUrl,\n Field(description=\"Either a base OPTIMADE URL or a full OPTIMADE URL.\"),\n ]\n accessService: Annotated[\n Literal[\"optimade\", \"optimade+dlite\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=\"The registered strategy name for OPTIMADEResourceStrategy.\",\n ),\n ]\n configuration: Annotated[\n Union[OPTIMADEConfig | OPTIMADEDLiteConfig],\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = OPTIMADEConfig()\n
"},{"location":"api_reference/models/strategies/resource/#oteapi_optimade.models.strategies.resource.OPTIMADEResourceResult","title":"OPTIMADEResourceResult
","text":" Bases: AttrDict
OPTIMADE session for the resource strategy.
Source code inoteapi_optimade/models/strategies/resource.py
class OPTIMADEResourceResult(AttrDict):\n \"\"\"OPTIMADE session for the resource strategy.\"\"\"\n\n model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)\n\n optimade_config: Annotated[\n Optional[OPTIMADEConfig],\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = None\n optimade_resources: Annotated[\n list[dict[str, Any]],\n Field(\n description=(\n \"List of OPTIMADE resources (structures, references, errors, ...) returned\"\n \" from the OPTIMADE request.\"\n ),\n ),\n ] = [] # noqa: RUF012\n optimade_resource_model: Annotated[\n str,\n Field(\n description=(\n \"Importable path to the resource model to be used to parse the OPTIMADE \"\n \"resources in `optimade_resource`. The importable path should be a fully \"\n \"importable path to a module separated by a colon (`:`) to then define the \"\n \"resource model class name. This means one can then do:\\n\\n```python\\n\"\n \"from PACKAGE.MODULE import RESOURCE_CLS\\n```\\nFrom the value \"\n \"`PACKAGE.MODULE:RESOURCE_CLS`\"\n ),\n pattern=(\n r\"^([a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)*\" # package.module\n r\":[a-zA-Z][a-zA-Z0-9_]*)?$\" # class\n ),\n ),\n ] = \"\"\n
"},{"location":"api_reference/strategies/filter/","title":"filter","text":"Demo filter strategy.
"},{"location":"api_reference/strategies/filter/#oteapi_optimade.strategies.filter.OPTIMADEFilterStrategy","title":"OPTIMADEFilterStrategy
","text":"Filter Strategy.
Implements strategies:
(\"filterType\", \"OPTIMADE\")
oteapi_optimade/strategies/filter.py
@dataclass\nclass OPTIMADEFilterStrategy:\n \"\"\"Filter Strategy.\n\n **Implements strategies**:\n\n - `(\"filterType\", \"OPTIMADE\")`\n\n \"\"\"\n\n filter_config: OPTIMADEFilterConfig\n\n def initialize(self) -> OPTIMADEFilterResult:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Configuration values, specifically URL query parameters, can be provided to the\n OPTIMADE resource strategy through this filter strategy.\n\n Workflow:\n\n 1. Compile received information.\n 2. Update session with compiled information.\n\n Returns:\n An update model of key/value-pairs to be stored in the\n session-specific context from services.\n\n \"\"\"\n if self.filter_config.configuration.optimade_config:\n self.filter_config.configuration.update(\n self.filter_config.configuration.optimade_config.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n )\n )\n\n optimade_config = self.filter_config.configuration.model_copy()\n\n if not optimade_config.query_parameters:\n optimade_config.query_parameters = OPTIMADEQueryParameters()\n\n if self.filter_config.query:\n LOGGER.debug(\"Setting filter from query.\")\n optimade_config.query_parameters.filter = self.filter_config.query\n\n if self.filter_config.limit:\n LOGGER.debug(\"Setting page_limit from limit.\")\n optimade_config.query_parameters.page_limit = self.filter_config.limit\n\n return OPTIMADEFilterResult(\n optimade_config=optimade_config.model_dump(\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n exclude_unset=True,\n exclude_defaults=True,\n )\n )\n\n def get(self) -> AttrDict:\n \"\"\"Execute the strategy.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the\n session-specific context from services.\n\n \"\"\"\n return AttrDict()\n
"},{"location":"api_reference/strategies/filter/#oteapi_optimade.strategies.filter.OPTIMADEFilterStrategy.get","title":"get()
","text":"Execute the strategy.
This method will be called through the strategy-specific endpoint of the OTE-API Services.
Returns:
Type DescriptionAttrDict
An update model of key/value-pairs to be stored in the
AttrDict
session-specific context from services.
Source code inoteapi_optimade/strategies/filter.py
def get(self) -> AttrDict:\n \"\"\"Execute the strategy.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the\n session-specific context from services.\n\n \"\"\"\n return AttrDict()\n
"},{"location":"api_reference/strategies/filter/#oteapi_optimade.strategies.filter.OPTIMADEFilterStrategy.initialize","title":"initialize()
","text":"Initialize strategy.
This method will be called through the /initialize
endpoint of the OTE-API Services.
Configuration values, specifically URL query parameters, can be provided to the OPTIMADE resource strategy through this filter strategy.
Workflow:
Returns:
Type DescriptionOPTIMADEFilterResult
An update model of key/value-pairs to be stored in the
OPTIMADEFilterResult
session-specific context from services.
Source code inoteapi_optimade/strategies/filter.py
def initialize(self) -> OPTIMADEFilterResult:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Configuration values, specifically URL query parameters, can be provided to the\n OPTIMADE resource strategy through this filter strategy.\n\n Workflow:\n\n 1. Compile received information.\n 2. Update session with compiled information.\n\n Returns:\n An update model of key/value-pairs to be stored in the\n session-specific context from services.\n\n \"\"\"\n if self.filter_config.configuration.optimade_config:\n self.filter_config.configuration.update(\n self.filter_config.configuration.optimade_config.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n )\n )\n\n optimade_config = self.filter_config.configuration.model_copy()\n\n if not optimade_config.query_parameters:\n optimade_config.query_parameters = OPTIMADEQueryParameters()\n\n if self.filter_config.query:\n LOGGER.debug(\"Setting filter from query.\")\n optimade_config.query_parameters.filter = self.filter_config.query\n\n if self.filter_config.limit:\n LOGGER.debug(\"Setting page_limit from limit.\")\n optimade_config.query_parameters.page_limit = self.filter_config.limit\n\n return OPTIMADEFilterResult(\n optimade_config=optimade_config.model_dump(\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n exclude_unset=True,\n exclude_defaults=True,\n )\n )\n
"},{"location":"api_reference/strategies/parse/","title":"parse","text":"Demo strategy class for text/json.
"},{"location":"api_reference/strategies/parse/#oteapi_optimade.strategies.parse.OPTIMADEParseStrategy","title":"OPTIMADEParseStrategy
","text":"Parse strategy for JSON.
Implements strategies:
(\"parserType\", \"parser/OPTIMADE\")
oteapi_optimade/strategies/parse.py
@dataclass\nclass OPTIMADEParseStrategy:\n \"\"\"Parse strategy for JSON.\n\n **Implements strategies**:\n\n - `(\"parserType\", \"parser/OPTIMADE\")`\n\n \"\"\"\n\n parse_config: OPTIMADEParseConfig\n\n def initialize(self) -> AttrDict:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n return AttrDict()\n\n def get(self) -> OPTIMADEParseResult:\n \"\"\"Request and parse an OPTIMADE response using OPT.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `downloadUrl`.\n\n Workflow:\n\n 1. Request OPTIMADE response.\n 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if (\n self.parse_config.configuration.downloadUrl is None\n or self.parse_config.configuration.mediaType is None\n ):\n raise OPTIMADEParseError(\n \"Missing downloadUrl or mediaType in configuration.\"\n )\n\n cache = DataCache(self.parse_config.configuration.datacache_config)\n if self.parse_config.configuration.downloadUrl in cache:\n response: dict[str, Any] = cache.get(\n self.parse_config.configuration.downloadUrl\n )\n elif (\n self.parse_config.configuration.datacache_config.accessKey\n and self.parse_config.configuration.datacache_config.accessKey in cache\n ):\n response = cache.get(\n self.parse_config.configuration.datacache_config.accessKey\n )\n else:\n download_config = self.parse_config.configuration.model_copy()\n download_output = create_strategy(\"download\", download_config).get()\n response = {\"json\": json.loads(cache.get(download_output.pop(\"key\")))}\n\n if (\n not response.get(\"ok\", True)\n or (\n response.get(\"status_code\", 200) < 200\n or response.get(\"status_code\", 200) >= 300\n )\n or \"errors\" in response.get(\"json\", {})\n ):\n # Error response\n try:\n response_object = ErrorResponse(**response.get(\"json\", {}))\n except ValidationError as exc:\n error_message = \"Could not validate an error response.\"\n LOGGER.error(\n \"%s\\nValidationError: \" \"%s\\nresponse=%r\",\n error_message,\n exc,\n response,\n )\n raise OPTIMADEParseError(error_message) from exc\n else:\n # Successful response\n response_model = (\n self.parse_config.configuration.downloadUrl.response_model()\n )\n LOGGER.debug(\"response_model=%r\", response_model)\n if response_model:\n if not isinstance(response_model, tuple):\n response_model = (response_model,)\n\n for model_cls in response_model:\n try:\n response_object = model_cls(**response.get(\"json\", {}))\n except ValidationError:\n pass\n else:\n break\n else:\n error_message = \"Could not validate for an expected response model.\"\n LOGGER.error(\n \"%s\\nURL=%r\\n\" \"response_models=%r\\nresponse=%s\",\n error_message,\n self.parse_config.configuration.downloadUrl,\n response_model,\n response,\n )\n raise OPTIMADEParseError(error_message)\n else:\n # No \"endpoint\" or unknown\n LOGGER.debug(\"No response_model, using Success response model.\")\n try:\n response_object = Success(**response.get(\"json\", {}))\n except ValidationError as exc:\n error_message = \"Unknown or unparseable endpoint.\"\n LOGGER.error(\n \"%s\\nValidatonError: %s\\n\"\n \"URL=%r\\nendpoint=%r\\nresponse_model=%r\\nresponse=%s\",\n error_message,\n exc,\n self.parse_config.configuration.downloadUrl,\n self.parse_config.configuration.downloadUrl.endpoint,\n response_model,\n response,\n )\n raise OPTIMADEParseError(error_message) from exc\n\n result = OPTIMADEParseResult(\n model_config=self.parse_config.configuration.model_dump(),\n optimade_response_model=(\n response_object.__class__.__module__,\n response_object.__class__.__name__,\n ),\n optimade_response=response_object.model_dump(exclude_unset=True),\n )\n\n if (\n self.parse_config.configuration.optimade_config\n and self.parse_config.configuration.optimade_config.query_parameters\n ):\n result = result.model_copy(\n update={\n \"optimade_config\": self.parse_config.configuration.optimade_config.model_copy(\n update={\n \"query_parameters\": self.parse_config.configuration.optimade_config.query_parameters.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n )\n }\n )\n }\n )\n\n return result\n
"},{"location":"api_reference/strategies/parse/#oteapi_optimade.strategies.parse.OPTIMADEParseStrategy.get","title":"get()
","text":"Request and parse an OPTIMADE response using OPT.
This method will be called through the strategy-specific endpoint of the OTE-API Services.
Configuration values provided in resource_config.configuration
take precedence over the derived values from downloadUrl
.
Workflow:
Returns:
Type DescriptionOPTIMADEParseResult
An update model of key/value-pairs to be stored in the session-specific
OPTIMADEParseResult
context from services.
Source code inoteapi_optimade/strategies/parse.py
def get(self) -> OPTIMADEParseResult:\n \"\"\"Request and parse an OPTIMADE response using OPT.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `downloadUrl`.\n\n Workflow:\n\n 1. Request OPTIMADE response.\n 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if (\n self.parse_config.configuration.downloadUrl is None\n or self.parse_config.configuration.mediaType is None\n ):\n raise OPTIMADEParseError(\n \"Missing downloadUrl or mediaType in configuration.\"\n )\n\n cache = DataCache(self.parse_config.configuration.datacache_config)\n if self.parse_config.configuration.downloadUrl in cache:\n response: dict[str, Any] = cache.get(\n self.parse_config.configuration.downloadUrl\n )\n elif (\n self.parse_config.configuration.datacache_config.accessKey\n and self.parse_config.configuration.datacache_config.accessKey in cache\n ):\n response = cache.get(\n self.parse_config.configuration.datacache_config.accessKey\n )\n else:\n download_config = self.parse_config.configuration.model_copy()\n download_output = create_strategy(\"download\", download_config).get()\n response = {\"json\": json.loads(cache.get(download_output.pop(\"key\")))}\n\n if (\n not response.get(\"ok\", True)\n or (\n response.get(\"status_code\", 200) < 200\n or response.get(\"status_code\", 200) >= 300\n )\n or \"errors\" in response.get(\"json\", {})\n ):\n # Error response\n try:\n response_object = ErrorResponse(**response.get(\"json\", {}))\n except ValidationError as exc:\n error_message = \"Could not validate an error response.\"\n LOGGER.error(\n \"%s\\nValidationError: \" \"%s\\nresponse=%r\",\n error_message,\n exc,\n response,\n )\n raise OPTIMADEParseError(error_message) from exc\n else:\n # Successful response\n response_model = (\n self.parse_config.configuration.downloadUrl.response_model()\n )\n LOGGER.debug(\"response_model=%r\", response_model)\n if response_model:\n if not isinstance(response_model, tuple):\n response_model = (response_model,)\n\n for model_cls in response_model:\n try:\n response_object = model_cls(**response.get(\"json\", {}))\n except ValidationError:\n pass\n else:\n break\n else:\n error_message = \"Could not validate for an expected response model.\"\n LOGGER.error(\n \"%s\\nURL=%r\\n\" \"response_models=%r\\nresponse=%s\",\n error_message,\n self.parse_config.configuration.downloadUrl,\n response_model,\n response,\n )\n raise OPTIMADEParseError(error_message)\n else:\n # No \"endpoint\" or unknown\n LOGGER.debug(\"No response_model, using Success response model.\")\n try:\n response_object = Success(**response.get(\"json\", {}))\n except ValidationError as exc:\n error_message = \"Unknown or unparseable endpoint.\"\n LOGGER.error(\n \"%s\\nValidatonError: %s\\n\"\n \"URL=%r\\nendpoint=%r\\nresponse_model=%r\\nresponse=%s\",\n error_message,\n exc,\n self.parse_config.configuration.downloadUrl,\n self.parse_config.configuration.downloadUrl.endpoint,\n response_model,\n response,\n )\n raise OPTIMADEParseError(error_message) from exc\n\n result = OPTIMADEParseResult(\n model_config=self.parse_config.configuration.model_dump(),\n optimade_response_model=(\n response_object.__class__.__module__,\n response_object.__class__.__name__,\n ),\n optimade_response=response_object.model_dump(exclude_unset=True),\n )\n\n if (\n self.parse_config.configuration.optimade_config\n and self.parse_config.configuration.optimade_config.query_parameters\n ):\n result = result.model_copy(\n update={\n \"optimade_config\": self.parse_config.configuration.optimade_config.model_copy(\n update={\n \"query_parameters\": self.parse_config.configuration.optimade_config.query_parameters.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n )\n }\n )\n }\n )\n\n return result\n
"},{"location":"api_reference/strategies/parse/#oteapi_optimade.strategies.parse.OPTIMADEParseStrategy.initialize","title":"initialize()
","text":"Initialize strategy.
This method will be called through the /initialize
endpoint of the OTE-API Services.
Returns:
Type DescriptionAttrDict
An update model of key/value-pairs to be stored in the session-specific
AttrDict
context from services.
Source code inoteapi_optimade/strategies/parse.py
def initialize(self) -> AttrDict:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n return AttrDict()\n
"},{"location":"api_reference/strategies/resource/","title":"resource","text":"OPTIMADE resource strategy.
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.OPTIMADEResourceStrategy","title":"OPTIMADEResourceStrategy
","text":"OPTIMADE Resource Strategy.
Implements strategies:
(\"accessService\", \"OPTIMADE\")
(\"accessService\", \"OPTIMADE+DLite\")
oteapi_optimade/strategies/resource.py
@dataclass\nclass OPTIMADEResourceStrategy:\n \"\"\"OPTIMADE Resource Strategy.\n\n **Implements strategies**:\n\n - `(\"accessService\", \"OPTIMADE\")`\n - `(\"accessService\", \"OPTIMADE+DLite\")`\n\n \"\"\"\n\n resource_config: OPTIMADEResourceConfig\n\n def initialize(self) -> AttrDict | DLiteSessionUpdate:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if use_dlite(\n self.resource_config.accessService,\n self.resource_config.configuration.use_dlite,\n ):\n collection_id = self.resource_config.configuration.get(\n \"collection_id\", None\n )\n return DLiteSessionUpdate(\n collection_id=get_collection(collection_id=collection_id).uuid\n )\n\n return AttrDict()\n\n def get(self) -> OPTIMADEResourceResult:\n \"\"\"Execute an OPTIMADE query to `accessUrl`.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `accessUrl`.\n\n Workflow:\n 1. Update configuration according to session.\n 2. Deconstruct `accessUrl` (done partly by\n `oteapi_optimade.models.custom_types.OPTIMADEUrl`).\n 3. Reconstruct the complete query URL.\n 4. Send query.\n 5. Store result in data cache.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if self.resource_config.configuration.optimade_config:\n self.resource_config.configuration.update(\n self.resource_config.configuration.optimade_config.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n )\n )\n\n optimade_endpoint = self.resource_config.accessUrl.endpoint or \"structures\"\n optimade_query = (\n self.resource_config.configuration.query_parameters\n or OPTIMADEQueryParameters()\n )\n LOGGER.debug(\"resource_config: %r\", self.resource_config)\n\n if self.resource_config.accessUrl.query:\n parsed_query = parse_qs(self.resource_config.accessUrl.query)\n for field, value in parsed_query.items():\n # Only use the latest defined value for any parameter\n if field not in optimade_query.model_fields_set:\n LOGGER.debug(\n \"Setting %r from accessUrl (value=%r)\", field, value[-1]\n )\n setattr(optimade_query, field, value[-1])\n\n LOGGER.debug(\"optimade_query after update: %r\", optimade_query)\n\n optimade_url = OPTIMADEUrl(\n f\"{self.resource_config.accessUrl.base_url}\"\n f\"/{self.resource_config.accessUrl.version or 'v1'}\"\n f\"/{optimade_endpoint}?{optimade_query.generate_query_string()}\"\n )\n LOGGER.debug(\"OPTIMADE URL to be requested: %s\", optimade_url)\n\n # Set cache access key to the full OPTIMADE URL.\n self.resource_config.configuration.datacache_config.accessKey = optimade_url\n\n # Perform query\n response = requests.get(\n optimade_url,\n allow_redirects=True,\n timeout=(3, 27), # timeout in seconds (connect, read)\n )\n\n if optimade_query.response_format and optimade_query.response_format != \"json\":\n error_message = (\n \"Can only handle JSON responses for now. Requested response format: \"\n f\"{optimade_query.response_format!r}\"\n )\n raise NotImplementedError(error_message)\n\n cache = DataCache(config=self.resource_config.configuration.datacache_config)\n cache.add(\n {\n \"status_code\": response.status_code,\n \"ok\": response.ok,\n \"json\": response.json(),\n }\n )\n\n parse_with_dlite = use_dlite(\n self.resource_config.accessService,\n self.resource_config.configuration.use_dlite,\n )\n\n parse_parserType = \"parser/OPTIMADE\"\n parse_mediaType = (\n \"application/vnd.\"\n f\"{self.resource_config.accessService.split('+', maxsplit=1)[0]}\"\n )\n if parse_with_dlite:\n parse_parserType += \"/DLite\"\n parse_mediaType += \"+DLite\"\n elif optimade_query.response_format:\n parse_mediaType += f\"+{optimade_query.response_format}\"\n\n parse_config: ParseConfigDict = {\n \"entity\": \"http://onto-ns.com/meta/1.0.1/OPTIMADEStructure\",\n \"parserType\": parse_parserType,\n \"configuration\": {\n \"datacache_config\": self.resource_config.configuration.datacache_config.model_copy(),\n \"downloadUrl\": str(optimade_url),\n \"mediaType\": parse_mediaType,\n \"optimade_config\": self.resource_config.configuration.model_dump(\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n exclude_unset=True,\n exclude_defaults=True,\n ),\n },\n }\n\n LOGGER.debug(\"parse_config: %r\", parse_config)\n\n parse_config[\"configuration\"].update(\n create_strategy(\"parse\", parse_config).initialize()\n )\n parse_result = create_strategy(\"parse\", parse_config).get()\n\n if not all(\n _ in parse_result for _ in (\"optimade_response\", \"optimade_response_model\")\n ):\n base_error_message = (\n \"Could not retrieve response from OPTIMADE parse strategy.\"\n )\n LOGGER.error(\n \"%s\\n\"\n \"optimade_response=%r\\n\"\n \"optimade_response_model=%r\\n\"\n \"session fields=%r\",\n base_error_message,\n parse_result.get(\"optimade_response\"),\n parse_result.get(\"optimade_response_model\"),\n list(parse_result.keys()),\n )\n raise OPTIMADEParseError(base_error_message)\n\n optimade_response_model_module, optimade_response_model_name = parse_result.pop(\n \"optimade_response_model\"\n )\n optimade_response_dict = parse_result.pop(\"optimade_response\")\n\n # Parse response using the provided model\n try:\n optimade_response_model: type[OPTIMADEResponse] = getattr(\n importlib.import_module(optimade_response_model_module),\n optimade_response_model_name,\n )\n optimade_response = optimade_response_model(**optimade_response_dict)\n except (ImportError, AttributeError) as exc:\n base_error_message = \"Could not import the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ImportError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n except ValidationError as exc:\n base_error_message = \"Could not validate the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ValidationError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n\n result = OPTIMADEResourceResult()\n\n if isinstance(optimade_response, ErrorResponse):\n optimade_resources = optimade_response.errors\n result.optimade_resource_model = f\"{OptimadeError.__module__}:OptimadeError\"\n elif isinstance(optimade_response, ReferenceResponseMany):\n optimade_resources = [\n (\n Reference(entry).as_dict\n if isinstance(entry, dict)\n else Reference(entry.model_dump()).as_dict\n )\n for entry in optimade_response.data\n ]\n result.optimade_resource_model = f\"{Reference.__module__}:Reference\"\n elif isinstance(optimade_response, ReferenceResponseOne):\n optimade_resources = [\n (\n Reference(optimade_response.data).as_dict\n if isinstance(optimade_response.data, dict)\n else Reference(optimade_response.data.model_dump()).as_dict\n )\n ]\n result.optimade_resource_model = f\"{Reference.__module__}:Reference\"\n elif isinstance(optimade_response, StructureResponseMany):\n optimade_resources = [\n (\n Structure(entry).as_dict\n if isinstance(entry, dict)\n else Structure(entry.model_dump()).as_dict\n )\n for entry in optimade_response.data\n ]\n result.optimade_resource_model = f\"{Structure.__module__}:Structure\"\n elif isinstance(optimade_response, StructureResponseOne):\n optimade_resources = [\n (\n Structure(optimade_response.data).as_dict\n if isinstance(optimade_response.data, dict)\n else Structure(optimade_response.data.model_dump()).as_dict\n )\n ]\n result.optimade_resource_model = f\"{Structure.__module__}:Structure\"\n else:\n LOGGER.error(\n \"Could not parse response as errors, references or structures. \"\n \"Response:\\n%r\",\n optimade_response,\n )\n error_message = (\n \"Could not retrieve errors, references or structures from response \"\n f\"from {optimade_url}. It could be a valid OPTIMADE API response, \"\n \"however it may not be supported by OTEAPI-OPTIMADE. It may also be an \"\n \"invalid response completely.\"\n )\n raise OPTIMADEParseError(error_message)\n\n result.optimade_resources = [\n resource if isinstance(resource, dict) else resource.model_dump()\n for resource in optimade_resources\n ]\n\n if (\n self.resource_config.configuration.optimade_config\n and self.resource_config.configuration.optimade_config.query_parameters\n ):\n result = result.model_copy(\n update={\n \"optimade_config\": self.resource_config.configuration.optimade_config.model_copy(\n update={\n \"query_parameters\": self.resource_config.configuration.optimade_config.query_parameters.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n )\n }\n )\n }\n )\n\n return result\n
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.OPTIMADEResourceStrategy.get","title":"get()
","text":"Execute an OPTIMADE query to accessUrl
.
This method will be called through the strategy-specific endpoint of the OTE-API Services.
Configuration values provided in resource_config.configuration
take precedence over the derived values from accessUrl
.
Workflow: 1. Update configuration according to session. 2. Deconstruct accessUrl
(done partly by oteapi_optimade.models.custom_types.OPTIMADEUrl
). 3. Reconstruct the complete query URL. 4. Send query. 5. Store result in data cache.
Returns:
Type DescriptionOPTIMADEResourceResult
An update model of key/value-pairs to be stored in the session-specific
OPTIMADEResourceResult
context from services.
Source code inoteapi_optimade/strategies/resource.py
def get(self) -> OPTIMADEResourceResult:\n \"\"\"Execute an OPTIMADE query to `accessUrl`.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `accessUrl`.\n\n Workflow:\n 1. Update configuration according to session.\n 2. Deconstruct `accessUrl` (done partly by\n `oteapi_optimade.models.custom_types.OPTIMADEUrl`).\n 3. Reconstruct the complete query URL.\n 4. Send query.\n 5. Store result in data cache.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if self.resource_config.configuration.optimade_config:\n self.resource_config.configuration.update(\n self.resource_config.configuration.optimade_config.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n )\n )\n\n optimade_endpoint = self.resource_config.accessUrl.endpoint or \"structures\"\n optimade_query = (\n self.resource_config.configuration.query_parameters\n or OPTIMADEQueryParameters()\n )\n LOGGER.debug(\"resource_config: %r\", self.resource_config)\n\n if self.resource_config.accessUrl.query:\n parsed_query = parse_qs(self.resource_config.accessUrl.query)\n for field, value in parsed_query.items():\n # Only use the latest defined value for any parameter\n if field not in optimade_query.model_fields_set:\n LOGGER.debug(\n \"Setting %r from accessUrl (value=%r)\", field, value[-1]\n )\n setattr(optimade_query, field, value[-1])\n\n LOGGER.debug(\"optimade_query after update: %r\", optimade_query)\n\n optimade_url = OPTIMADEUrl(\n f\"{self.resource_config.accessUrl.base_url}\"\n f\"/{self.resource_config.accessUrl.version or 'v1'}\"\n f\"/{optimade_endpoint}?{optimade_query.generate_query_string()}\"\n )\n LOGGER.debug(\"OPTIMADE URL to be requested: %s\", optimade_url)\n\n # Set cache access key to the full OPTIMADE URL.\n self.resource_config.configuration.datacache_config.accessKey = optimade_url\n\n # Perform query\n response = requests.get(\n optimade_url,\n allow_redirects=True,\n timeout=(3, 27), # timeout in seconds (connect, read)\n )\n\n if optimade_query.response_format and optimade_query.response_format != \"json\":\n error_message = (\n \"Can only handle JSON responses for now. Requested response format: \"\n f\"{optimade_query.response_format!r}\"\n )\n raise NotImplementedError(error_message)\n\n cache = DataCache(config=self.resource_config.configuration.datacache_config)\n cache.add(\n {\n \"status_code\": response.status_code,\n \"ok\": response.ok,\n \"json\": response.json(),\n }\n )\n\n parse_with_dlite = use_dlite(\n self.resource_config.accessService,\n self.resource_config.configuration.use_dlite,\n )\n\n parse_parserType = \"parser/OPTIMADE\"\n parse_mediaType = (\n \"application/vnd.\"\n f\"{self.resource_config.accessService.split('+', maxsplit=1)[0]}\"\n )\n if parse_with_dlite:\n parse_parserType += \"/DLite\"\n parse_mediaType += \"+DLite\"\n elif optimade_query.response_format:\n parse_mediaType += f\"+{optimade_query.response_format}\"\n\n parse_config: ParseConfigDict = {\n \"entity\": \"http://onto-ns.com/meta/1.0.1/OPTIMADEStructure\",\n \"parserType\": parse_parserType,\n \"configuration\": {\n \"datacache_config\": self.resource_config.configuration.datacache_config.model_copy(),\n \"downloadUrl\": str(optimade_url),\n \"mediaType\": parse_mediaType,\n \"optimade_config\": self.resource_config.configuration.model_dump(\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n exclude_unset=True,\n exclude_defaults=True,\n ),\n },\n }\n\n LOGGER.debug(\"parse_config: %r\", parse_config)\n\n parse_config[\"configuration\"].update(\n create_strategy(\"parse\", parse_config).initialize()\n )\n parse_result = create_strategy(\"parse\", parse_config).get()\n\n if not all(\n _ in parse_result for _ in (\"optimade_response\", \"optimade_response_model\")\n ):\n base_error_message = (\n \"Could not retrieve response from OPTIMADE parse strategy.\"\n )\n LOGGER.error(\n \"%s\\n\"\n \"optimade_response=%r\\n\"\n \"optimade_response_model=%r\\n\"\n \"session fields=%r\",\n base_error_message,\n parse_result.get(\"optimade_response\"),\n parse_result.get(\"optimade_response_model\"),\n list(parse_result.keys()),\n )\n raise OPTIMADEParseError(base_error_message)\n\n optimade_response_model_module, optimade_response_model_name = parse_result.pop(\n \"optimade_response_model\"\n )\n optimade_response_dict = parse_result.pop(\"optimade_response\")\n\n # Parse response using the provided model\n try:\n optimade_response_model: type[OPTIMADEResponse] = getattr(\n importlib.import_module(optimade_response_model_module),\n optimade_response_model_name,\n )\n optimade_response = optimade_response_model(**optimade_response_dict)\n except (ImportError, AttributeError) as exc:\n base_error_message = \"Could not import the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ImportError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n except ValidationError as exc:\n base_error_message = \"Could not validate the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ValidationError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n\n result = OPTIMADEResourceResult()\n\n if isinstance(optimade_response, ErrorResponse):\n optimade_resources = optimade_response.errors\n result.optimade_resource_model = f\"{OptimadeError.__module__}:OptimadeError\"\n elif isinstance(optimade_response, ReferenceResponseMany):\n optimade_resources = [\n (\n Reference(entry).as_dict\n if isinstance(entry, dict)\n else Reference(entry.model_dump()).as_dict\n )\n for entry in optimade_response.data\n ]\n result.optimade_resource_model = f\"{Reference.__module__}:Reference\"\n elif isinstance(optimade_response, ReferenceResponseOne):\n optimade_resources = [\n (\n Reference(optimade_response.data).as_dict\n if isinstance(optimade_response.data, dict)\n else Reference(optimade_response.data.model_dump()).as_dict\n )\n ]\n result.optimade_resource_model = f\"{Reference.__module__}:Reference\"\n elif isinstance(optimade_response, StructureResponseMany):\n optimade_resources = [\n (\n Structure(entry).as_dict\n if isinstance(entry, dict)\n else Structure(entry.model_dump()).as_dict\n )\n for entry in optimade_response.data\n ]\n result.optimade_resource_model = f\"{Structure.__module__}:Structure\"\n elif isinstance(optimade_response, StructureResponseOne):\n optimade_resources = [\n (\n Structure(optimade_response.data).as_dict\n if isinstance(optimade_response.data, dict)\n else Structure(optimade_response.data.model_dump()).as_dict\n )\n ]\n result.optimade_resource_model = f\"{Structure.__module__}:Structure\"\n else:\n LOGGER.error(\n \"Could not parse response as errors, references or structures. \"\n \"Response:\\n%r\",\n optimade_response,\n )\n error_message = (\n \"Could not retrieve errors, references or structures from response \"\n f\"from {optimade_url}. It could be a valid OPTIMADE API response, \"\n \"however it may not be supported by OTEAPI-OPTIMADE. It may also be an \"\n \"invalid response completely.\"\n )\n raise OPTIMADEParseError(error_message)\n\n result.optimade_resources = [\n resource if isinstance(resource, dict) else resource.model_dump()\n for resource in optimade_resources\n ]\n\n if (\n self.resource_config.configuration.optimade_config\n and self.resource_config.configuration.optimade_config.query_parameters\n ):\n result = result.model_copy(\n update={\n \"optimade_config\": self.resource_config.configuration.optimade_config.model_copy(\n update={\n \"query_parameters\": self.resource_config.configuration.optimade_config.query_parameters.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n )\n }\n )\n }\n )\n\n return result\n
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.OPTIMADEResourceStrategy.initialize","title":"initialize()
","text":"Initialize strategy.
This method will be called through the /initialize
endpoint of the OTE-API Services.
Returns:
Type DescriptionAttrDict | DLiteSessionUpdate
An update model of key/value-pairs to be stored in the session-specific
AttrDict | DLiteSessionUpdate
context from services.
Source code inoteapi_optimade/strategies/resource.py
def initialize(self) -> AttrDict | DLiteSessionUpdate:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if use_dlite(\n self.resource_config.accessService,\n self.resource_config.configuration.use_dlite,\n ):\n collection_id = self.resource_config.configuration.get(\n \"collection_id\", None\n )\n return DLiteSessionUpdate(\n collection_id=get_collection(collection_id=collection_id).uuid\n )\n\n return AttrDict()\n
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.ParseConfigDict","title":"ParseConfigDict
","text":" Bases: TypedDict
Type definition for the parse_config
dictionary.
oteapi_optimade/strategies/resource.py
class ParseConfigDict(TypedDict):\n \"\"\"Type definition for the `parse_config` dictionary.\"\"\"\n\n entity: str\n parserType: str\n configuration: dict[str, Any]\n
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.use_dlite","title":"use_dlite(access_service, use_dlite_flag)
","text":"Determine whether DLite should be utilized in the Resource strategy.
Parameters:
Name Type Description Defaultaccess_service
str
The accessService value from the resource's configuration.
requireduse_dlite_flag
bool
The strategy-specific use_dlite
configuration option.
Returns:
Type Descriptionbool
Based on the accessService value, then whether DLite should be used or not.
Source code inoteapi_optimade/strategies/resource.py
def use_dlite(access_service: str, use_dlite_flag: bool) -> bool:\n \"\"\"Determine whether DLite should be utilized in the Resource strategy.\n\n Parameters:\n access_service: The accessService value from the resource's configuration.\n use_dlite_flag: The strategy-specific `use_dlite` configuration option.\n\n Returns:\n Based on the accessService value, then whether DLite should be used or not.\n\n \"\"\"\n if (\n any(dlite_form in access_service for dlite_form in [\"DLite\", \"dlite\"])\n or use_dlite_flag\n ):\n if oteapi_dlite_version is None:\n error_message = (\n \"OTEAPI-DLite is not found on the system. This is required to use \"\n \"DLite with the OTEAPI-OPTIMADE strategies.\"\n )\n raise MissingDependency(error_message)\n return True\n return False\n
"},{"location":"examples/","title":"Overview","text":"This section provides examples of how to use this OTEAPI plugin to perform OPTIMADE queries and handle the results.
In Use OTEAPI-OPTIMADE with OTElib you can find an example of how to use this plugin with the OTElib client.
It is worth noting that there are several different ways to use the strategies in this plugin. For example, an OPTIMADE query can be provided using the OPTIMADE
filter strategy, but it can also be provided directly in the URL value of the OPTIMADE
data resource strategy's accessUlr
parameter. Further, it could be set through a configuration
parameter entry to either of these strategies.
In the examples only one of these options are given, and this is the same for other aspects: What we believe is the most common and transparent use case is given.
Finally, it is important to note that using OTElib directly is not intended for end users. Using OTElib should be done as a backend task in a web application, and the results should be presented to the end user in a more user friendly way.
"},{"location":"examples/#setup-for-examples","title":"Setup for examples","text":""},{"location":"examples/#prerequisites","title":"Prerequisites","text":"To run the examples locally, you need to have the following tools available (in addition to a working Python 3.9+ installation):
To install Jupyter, please refer to the Jupyter documentation. If you want to use pip
to install Jupyter, you can do so by installing the examples
extra for this plugin package:
pip install oteapi-optimade[examples]\n
This will also install OTElib and any other Python packages you may need for the examples.
"},{"location":"examples/#docker-installation","title":"Docker installation","text":"To install Docker, please refer to the Docker documentation.
"},{"location":"examples/#start-a-local-oteapi-server","title":"Start a local OTEAPI server","text":"When running a local OTEAPI server, you need to ensure the OTEAPI-OPTIMADE plugin is installed. This can be done by using the OTEAPI_PLUGIN_PACKAGES
environment variables as described in the OTEAPI Services README.
There are two methods of starting the server:
No matter the method, the server will be available at http://localhost:80/
. To check it, go to the /docs
endpoint: localhost:80/docs.
There are no extra files needed to start the server using Docker. However, several commands need to be run to start the server, which is a collection of different microservices running in different containers on the same Docker network.
The general setup is outlined in the OTEAPI Services README.
For convenience, the following commands can be used to start the services:
docker network create otenet\ndocker volume create redis-persist\ndocker run \\\n --detach \\\n --name redis \\\n --volume redis-persist:/data \\\n --network otenet \\\n redis:latest\ndocker run \\\n --rm \\\n --network otenet \\\n --detach \\\n --publish 80:8080 \\\n --env OTEAPI_REDIS_TYPE=redis \\\n --env OTEAPI_REDIS_HOST=redis \\\n --env OTEAPI_REDIS_PORT=6379 \\\n --env OTEAPI_INCLUDE_REDISADMIN=False \\\n --env OTEAPI_EXPOSE_SECRETS=True \\\n --env OTEAPI_PLUGIN_PACKAGES=oteapi-optimade \\\n ghcr.io/emmc-asbl/oteapi:1.20231108.329\n
Note
To use the /triples
endpoint, an AllegroGraph triplestore needs to be running. For more information see the OTEAPI Services README to see how to set this up and run it.
Important
Pinning to version '1.20231108.329' of the OTEAPI image is important, as the latest version is currently not compatible with this plugin. To follow this issue, please see GitHub issue #187 and GitHub issue #163.
"},{"location":"examples/#using-docker-compose","title":"Using Docker Compose","text":"Download the Docker Compose file from the OTEAPI Services repository:
curl -O https://raw.githubusercontent.com/EMMC-ASBL/oteapi-services/master/docker-compose.yml\n
And either update the OTEAPI_PLUGIN_PACKAGES
environment variable in the file to include oteapi-optimade
:
# ...\n OTEAPI_PLUGIN_PACKAGES: oteapi-optimade\n# ...\n
Or set the environment variable when starting the services by prefixing it to the Docker Compose command.
Then start the services:
docker compose pull\ndocker compose up --detach\n
Note
When setting the environment variables as a prefix to the docker compose
command, it is only needed for the command that runs the services:
OTEAPI_PLUGIN_PACKAGES=oteapi-optimade docker compose up --detach\n
"},{"location":"examples/dlite/","title":"Use DLite strategies from OTEAPI-OPTIMADE","text":"In\u00a0[37]: Copied! from otelib import OTEClient\n\nclient = OTEClient(\"python\")\nfrom otelib import OTEClient client = OTEClient(\"python\") In\u00a0[38]: Copied!
data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE+DLite\",\n accessUrl=\"https://optimade.materialsproject.org\",\n)\n\n# This is equivalent to:\n# data_resource_strategy = client.create_dataresource(\n# accessService=\"OPTIMADE\",\n# accessUrl=\"https://optimade.materialsproject.org\",\n# configuration={\"use_dlite\": True},\n# )\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE+DLite\", accessUrl=\"https://optimade.materialsproject.org\", ) # This is equivalent to: # data_resource_strategy = client.create_dataresource( # accessService=\"OPTIMADE\", # accessUrl=\"https://optimade.materialsproject.org\", # configuration={\"use_dlite\": True}, # ) In\u00a0[39]: Copied!
filter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='elements HAS ALL \"Si\",\"O\" AND nelements<=4',\n)\nfilter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='elements HAS ALL \"Si\",\"O\" AND nelements<=4', ) In\u00a0[40]: Copied!
import json\n\npipeline = filter_strategy >> data_resource_strategy\nsession = pipeline.get()\nparsed_session = json.loads(session)\nparsed_session.keys()\nimport json pipeline = filter_strategy >> data_resource_strategy session = pipeline.get() parsed_session = json.loads(session) parsed_session.keys()
Setting filter from query.\nSetting filter from query.\nSetting filter from query.\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE+DLite', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE+DLite', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE+DLite', license=None, accessRights=None, publisher=None)\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Si%22%2C%22O%22%20AND%20nelements%3C%3D4&response_format=json&page_limit=20&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Si%22%2C%22O%22%20AND%20nelements%3C%3D4&response_format=json&page_limit=20&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Si%22%2C%22O%22%20AND%20nelements%3C%3D4&response_format=json&page_limit=20&include=references\nOut[40]:
dict_keys(['optimade_config', 'optimade_resources', 'optimade_resource_model', 'collection_id'])In\u00a0[41]: Copied!
from importlib import import_module\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\n\nparsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\")\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")\nfrom importlib import import_module import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) parsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\") print(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")
The query resulted in 20 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1033911', 'mp-757887', 'mp-1219366', 'mp-733539', 'mp-542090', 'mp-758465', 'mp-560675', 'mp-774171', 'mp-1304778', 'mp-1016821', 'mp-1363556', 'mp-757013', 'mp-683953', 'mp-1020609', 'mp-17612', 'mp-558129', 'mp-1210628', 'mp-1197149', 'mp-21791', 'mp-752892']\nIn\u00a0[42]: Copied!
from oteapi_dlite.utils import get_collection\n\ncollection = get_collection(session=parsed_session)\nprint(collection)\nfrom oteapi_dlite.utils import get_collection collection = get_collection(session=parsed_session) print(collection)
{\n \"bfd78bf8-31fe-4487-a4c3-8cc5351ca87d\": {\n \"meta\": \"http://onto-ns.com/meta/0.1/Collection\",\n \"dimensions\": {\n \"nrelations\": 60\n },\n \"properties\": {\n \"relations\": [[\"mp-1033911\", \"_is-a\", \"Instance\"], [\"mp-1033911\", \"_has-uuid\", \"f0676948-f620-4057-9291-b1851f519d66\"], [\"mp-1033911\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-757887\", \"_is-a\", \"Instance\"], [\"mp-757887\", \"_has-uuid\", \"76add43f-bb1e-4bdb-984e-dc6c16b9205d\"], [\"mp-757887\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1219366\", \"_is-a\", \"Instance\"], [\"mp-1219366\", \"_has-uuid\", \"d0e7c4f7-1dd7-4206-aa53-2a67b755259d\"], [\"mp-1219366\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-733539\", \"_is-a\", \"Instance\"], [\"mp-733539\", \"_has-uuid\", \"e460fb2d-1639-4b65-9e61-f4c2869ef412\"], [\"mp-733539\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-542090\", \"_is-a\", \"Instance\"], [\"mp-542090\", \"_has-uuid\", \"4625be3d-cad7-42a2-afdd-3c01715c0905\"], [\"mp-542090\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-758465\", \"_is-a\", \"Instance\"], [\"mp-758465\", \"_has-uuid\", \"d041f6c2-4770-41ec-870c-66d07f9aafff\"], [\"mp-758465\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-560675\", \"_is-a\", \"Instance\"], [\"mp-560675\", \"_has-uuid\", \"d6dfd477-cea9-46c1-b56f-487a77fb615e\"], [\"mp-560675\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-774171\", \"_is-a\", \"Instance\"], [\"mp-774171\", \"_has-uuid\", \"ac42160c-9661-4180-812b-4c2b2e44145e\"], [\"mp-774171\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1304778\", \"_is-a\", \"Instance\"], [\"mp-1304778\", \"_has-uuid\", \"d840939b-9b95-48b9-9be7-4f0d8bdf480d\"], [\"mp-1304778\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1016821\", \"_is-a\", \"Instance\"], [\"mp-1016821\", \"_has-uuid\", \"d023ba43-f725-4a8d-b38c-ff5021729f9d\"], [\"mp-1016821\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1363556\", \"_is-a\", \"Instance\"], [\"mp-1363556\", \"_has-uuid\", \"8d011cb4-fa24-44ac-960d-b027588924d3\"], [\"mp-1363556\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-757013\", \"_is-a\", \"Instance\"], [\"mp-757013\", \"_has-uuid\", \"9d7928e0-1209-4251-ab67-92f5cc1cb1a4\"], [\"mp-757013\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-683953\", \"_is-a\", \"Instance\"], [\"mp-683953\", \"_has-uuid\", \"05eae7e9-2e01-4e21-bc86-0e4004221f76\"], [\"mp-683953\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1020609\", \"_is-a\", \"Instance\"], [\"mp-1020609\", \"_has-uuid\", \"99468a6c-3875-436c-8025-0af9d4b53be5\"], [\"mp-1020609\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-17612\", \"_is-a\", \"Instance\"], [\"mp-17612\", \"_has-uuid\", \"7b266e95-b56a-4fc3-921c-407daa7d1369\"], [\"mp-17612\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-558129\", \"_is-a\", \"Instance\"], [\"mp-558129\", \"_has-uuid\", \"5c4d6757-866a-482b-83ba-cb8aa035b180\"], [\"mp-558129\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1210628\", \"_is-a\", \"Instance\"], [\"mp-1210628\", \"_has-uuid\", \"a32c7f8c-a4b8-40f8-96e5-92a642c0e644\"], [\"mp-1210628\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1197149\", \"_is-a\", \"Instance\"], [\"mp-1197149\", \"_has-uuid\", \"68ec190e-7ff5-497d-a6e5-de1bb568205c\"], [\"mp-1197149\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-21791\", \"_is-a\", \"Instance\"], [\"mp-21791\", \"_has-uuid\", \"b9805c87-808a-48b8-be04-58b18ebafb4e\"], [\"mp-21791\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-752892\", \"_is-a\", \"Instance\"], [\"mp-752892\", \"_has-uuid\", \"e2674e42-465f-4991-b7f5-15a65fb07f6d\"], [\"mp-752892\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"]]\n }\n }\n}\nIn\u00a0[43]: Copied!
for inst in collection.get_instances():\n print(inst)\nfor inst in collection.get_instances(): print(inst)
{\n \"f0676948-f620-4057-9291-b1851f519d66\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"46900658-13b0-4072-8fc1-7e20de603765\",\n \"id\": \"mp-1033911\"\n }\n }\n}\n{\n \"76add43f-bb1e-4bdb-984e-dc6c16b9205d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"62d27df4-ac40-4784-a5fd-ce48cbf12a3a\",\n \"id\": \"mp-757887\"\n }\n }\n}\n{\n \"d0e7c4f7-1dd7-4206-aa53-2a67b755259d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c890c277-74b5-42a9-8bed-d0687b34bbd6\",\n \"id\": \"mp-1219366\"\n }\n }\n}\n{\n \"e460fb2d-1639-4b65-9e61-f4c2869ef412\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"4d2dc20a-671f-4c26-90a2-d9f6bc6bbe0c\",\n \"id\": \"mp-733539\"\n }\n }\n}\n{\n \"4625be3d-cad7-42a2-afdd-3c01715c0905\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c1e0bc0e-bc18-4ebe-b0df-c8ab4cba83ae\",\n \"id\": \"mp-542090\"\n }\n }\n}\n{\n \"d041f6c2-4770-41ec-870c-66d07f9aafff\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"0592aaad-c896-464a-b368-82f62bbe42f9\",\n \"id\": \"mp-758465\"\n }\n }\n}\n{\n \"d6dfd477-cea9-46c1-b56f-487a77fb615e\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"1baa18c6-fec5-4720-8554-5593ca656f06\",\n \"id\": \"mp-560675\"\n }\n }\n}\n{\n \"ac42160c-9661-4180-812b-4c2b2e44145e\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"d9d59dc2-3f83-4f96-ae5c-6d3ad5a241c6\",\n \"id\": \"mp-774171\"\n }\n }\n}\n{\n \"d840939b-9b95-48b9-9be7-4f0d8bdf480d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"cdbfd5ec-9a84-4a09-94b8-8ec989f09019\",\n \"id\": \"mp-1304778\"\n }\n }\n}\n{\n \"d023ba43-f725-4a8d-b38c-ff5021729f9d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"0d46f58f-2744-4c4f-882b-57da2e1c9cdf\",\n \"id\": \"mp-1016821\"\n }\n }\n}\n{\n \"8d011cb4-fa24-44ac-960d-b027588924d3\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c951ff9b-688e-40f3-b15d-b0acfede5e0a\",\n \"id\": \"mp-1363556\"\n }\n }\n}\n{\n \"9d7928e0-1209-4251-ab67-92f5cc1cb1a4\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"5053a07b-f15b-437a-9bde-ce646a669abe\",\n \"id\": \"mp-757013\"\n }\n }\n}\n{\n \"05eae7e9-2e01-4e21-bc86-0e4004221f76\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c2f151ff-6c3c-45ed-a7b0-3ee5117f0035\",\n \"id\": \"mp-683953\"\n }\n }\n}\n{\n \"99468a6c-3875-436c-8025-0af9d4b53be5\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"22cd1e19-e45f-47b5-a62d-4027f61f9667\",\n \"id\": \"mp-1020609\"\n }\n }\n}\n{\n \"7b266e95-b56a-4fc3-921c-407daa7d1369\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"0aeef165-da7e-4681-8052-e95b484d637f\",\n \"id\": \"mp-17612\"\n }\n }\n}\n{\n \"5c4d6757-866a-482b-83ba-cb8aa035b180\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"ddb31fe8-6885-4c80-bf2e-044dad60f8c3\",\n \"id\": \"mp-558129\"\n }\n }\n}\n{\n \"a32c7f8c-a4b8-40f8-96e5-92a642c0e644\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"62840cd5-2f20-481f-8967-43476ecec964\",\n \"id\": \"mp-1210628\"\n }\n }\n}\n{\n \"68ec190e-7ff5-497d-a6e5-de1bb568205c\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c0674560-bbde-4e93-aa8a-bf9260ef6975\",\n \"id\": \"mp-1197149\"\n }\n }\n}\n{\n \"b9805c87-808a-48b8-be04-58b18ebafb4e\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"aec646fb-b54f-4526-ba97-5cae2d3a528a\",\n \"id\": \"mp-21791\"\n }\n }\n}\n{\n \"e2674e42-465f-4991-b7f5-15a65fb07f6d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"606e5ca8-0fb1-45a4-941a-45461d19eceb\",\n \"id\": \"mp-752892\"\n }\n }\n}\nIn\u00a0[44]: Copied!
import dlite\n\nstructure_instances: list[dlite.Instance] = list(collection.get_instances())\n\nstructure_attributes_instance: dlite.Instance = dlite.get_instance(structure_instances[0].attributes)\n\nprint(structure_attributes_instance)\nimport dlite structure_instances: list[dlite.Instance] = list(collection.get_instances()) structure_attributes_instance: dlite.Instance = dlite.get_instance(structure_instances[0].attributes) print(structure_attributes_instance)
{\n \"46900658-13b0-4072-8fc1-7e20de603765\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes\",\n \"dimensions\": {\n \"nelements\": 4,\n \"dimensionality\": 3,\n \"nsites\": 32,\n \"nspecies\": 4,\n \"nstructure_features\": 0\n },\n \"properties\": {\n \"elements\": [\"K\", \"Mg\", \"O\", \"Si\"],\n \"nelements\": 4,\n \"elements_ratios\": [0.03125, 0.4375, 0.03125, 0.5],\n \"chemical_formula_descriptive\": \"KMg14O16Si\",\n \"chemical_formula_reduced\": \"KMg14O16Si\",\n \"chemical_formula_hill\": \"KMg14O16Si\",\n \"chemical_formula_anonymous\": \"A16B14CD\",\n \"dimension_types\": [1, 1, 1],\n \"nperiodic_dimensions\": 3,\n \"lattice_vectors\": [[8.59318, 0, 0], [0, 8.59318, 0], [0, 0, 4.41201]],\n \"cartesian_site_positions\": [[0, 0, 0], [0, 4.29659, 0], [4.29659, 0, 0], [0, 2.1438, 2.20601], [0, 6.44938, 2.20601], [4.29659, 2.13079, 2.20601], [4.29659, 6.46238, 2.20601], [2.1438, 0, 2.20601], [2.13079, 4.29659, 2.20601], [6.44938, 0, 2.20601], [6.46238, 4.29659, 2.20601], [2.12673, 2.12673, 0], [2.12673, 6.46645, 0], [6.46645, 2.12673, 0], [6.46645, 6.46645, 0], [4.29659, 4.29659, 0], [2.37689, 0, 0], [2.32997, 4.29659, 0], [6.21628, 0, 0], [6.2632, 4.29659, 0], [2.16162, 2.16162, 2.20601], [2.16162, 6.43155, 2.20601], [6.43155, 2.16162, 2.20601], [6.43155, 6.43155, 2.20601], [0, 0, 2.20601], [0, 4.29659, 2.20601], [4.29659, 0, 2.20601], [4.29659, 4.29659, 2.20601], [0, 2.37689, 0], [0, 6.21628, 0], [4.29659, 2.32997, 0], [4.29659, 6.2632, 0]],\n \"nsites\": 32,\n \"species\": [\"b60b7266-2807-4d89-bdd7-776fc7bd84d0\", \"faa90667-ab90-484d-b0f5-17685b3c9d5b\", \"880e1de2-1608-4432-986c-8b199cff5018\", \"9ab25660-3d30-4034-a9f5-ba0e7a7a8adc\"],\n \"species_at_sites\": [\"K\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Si\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\"],\n \"assemblies\": null,\n \"structure_features\": [],\n \"immutable_id\": \"645d2b74bcd30f748b4746a3\",\n \"last_modified\": \"2019-10-23 12:27:13+00:00\"\n }\n }\n}\n"},{"location":"examples/dlite/#use-dlite-strategies-from-oteapi-optimade","title":"Use DLite strategies from OTEAPI-OPTIMADE\u00b6","text":"
This example shows how to use the DLite strategies from the OTEAPI-OPTIMADE plugin.
DLite is a Python library for working with data models and semantics. It is considered to be the default semantic data model backend for use with OTEAPI.
To see more fundamental examples of how to use OTEAPI-OPTIMADE, see the example Use OTEAPI-OPTIMADE with OTElib. OTElib will also be used as a client in the current example. Furthermore, only the HTTP requests-based backend will be used in this example.
"},{"location":"examples/dlite/#setup","title":"Setup\u00b6","text":"Please see the setup instructions in Use OTEAPI-OPTIMADE with OTElib for how to ensure you have a proper environment to run the example in.
"},{"location":"examples/dlite/#example","title":"Example\u00b6","text":"In this example, we will use the DLite strategies to query the Materials Project OPTIMADE API. We are interested in finding all structures that include the elements Si
and O
, and that have a maximum of 4 elements in total.
Let's start by initializing a client:
"},{"location":"examples/dlite/#data-resource-strategy","title":"Data Resource strategy\u00b6","text":"The general pipeline is the same as it was for Use OTEAPI-OPTIMADE with OTElib:
However, in order to use DLite we need to either reference a specific accessService
or set a flag in the configuration of the data resource strategy.
Note, it is only for the data resource strategy we need to specify the usage of DLite, as the filter strategy is generic and merely a helper for more explicitly setting the query parameters to be used for the underlying OPTIMADE query.
"},{"location":"examples/dlite/#filter-strategy","title":"Filter strategy\u00b6","text":"The OPTIMADE filter query to fulfill the interests outlined above should look like this:
elements HAS ALL \"Si\",\"O\" AND nelements<=4\n
"},{"location":"examples/dlite/#setup-execute-and-inspect-the-pipeline","title":"Setup, execute and inspect the pipeline\u00b6","text":""},{"location":"examples/otelib/","title":"Use OTEAPI-OPTIMADE with OTElib","text":"In\u00a0[1]: Copied! from otelib import OTEClient\n\nclient = OTEClient(\"http://localhost:80\")\nfrom otelib import OTEClient client = OTEClient(\"http://localhost:80\") In\u00a0[2]: Copied!
data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE\",\n accessUrl=\"https://optimade.materialsproject.org/\",\n)\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE\", accessUrl=\"https://optimade.materialsproject.org/\", )
For the filter strategy, we need to know the OPTIMADE query that we want to execute.
For retrieving all structures with the formula Al2O3
, we can use the following OPTIMADE filter query:
chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"\n
In\u00a0[3]: Copied! filter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"',\n)\nfilter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', )
Now we can create the OTE pipeline shown above and execute it.
In\u00a0[4]: Copied!pipeline = filter_strategy >> data_resource_strategy\nsession = pipeline.get()\npipeline = filter_strategy >> data_resource_strategy session = pipeline.get()
The returned session is a JSON object we can parse and investigate.
In\u00a0[5]: Copied!import json\n\nparsed_session: dict = json.loads(session)\nprint(parsed_session.keys())\nimport json parsed_session: dict = json.loads(session) print(parsed_session.keys())
dict_keys(['optimade_resources', 'optimade_config', 'optimade_resource_model'])\n
As can be seen, there are three keys in the returned session. optimade_config
summarizes the query that has been performed to Materials Project. The OPTIMADE structures are listed under the optimade_resources
. It is named as such due to there being different OPTIMADE resources, e.g., structures
, references
, links
, etc. The OPTIMADE Python tools has a useful OPTIMADE Structure model class that can be used to parse the OPTIMADE structures into Python objects as well as validating them according to the OPTIMADE specification. Again, since one can query for different OPTIMADE resources, the specific Python class to use is given in optimade_resource_model
.
from importlib import import_module\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\n\nparsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\")\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")\nfrom importlib import import_module import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) parsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\") print(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")
The query resulted in 20 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1228448', 'mp-755483', 'mp-1245081', 'mp-1244878', 'mp-1105018', 'mp-754401', 'mp-1245063', 'mp-1245265', 'mp-684591', 'mp-1244898', 'mp-1245211', 'mp-1245056', 'mp-642363', 'mp-1244930', 'mp-1244937', 'mp-1244967', 'mp-1244954', 'mp-1244874', 'mp-1245008', 'mp-1245023']\n
To find them on the Materials Project website, go to materialsproject.org/materials/<ID>
, for example: materialsproject.org/materials/mp-1228448.
What is more, we can investigate the structure according to the well-defined OPTIMADE structure model attributes. For example, so assert the chemical formula is what we expected, we can check the different chemical formula attributes:
In\u00a0[7]: Copied!structure = parsed_structures[0]\nprint(structure.id)\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nstructure = parsed_structures[0] print(structure.id) for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
mp-1228448\nchemical_formula_descriptive: Al2O3\nchemical_formula_reduced: Al2O3\nchemical_formula_hill: Al2O3\nchemical_formula_anonymous: A3B2\nIn\u00a0[8]: Copied!
filter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='elements HAS ALL \"Al\",\"O\"',\n limit=5,\n)\nfilter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='elements HAS ALL \"Al\",\"O\"', limit=5, )
For this query, we have added the limit
parameter to the filter configuration, which will pass a page_limit
query parameter to the OPTIMADE API ensuring that we only retrieve the first 5 structures (it limits each result's page to 5 resources).
Let us investigate the result again, checking the list of elements and the chemical formula attributes:
In\u00a0[9]: Copied!pipeline = filter_strategy >> data_resource_strategy\nparsed_session = json.loads(pipeline.get())\nprint(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\")\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\nstructures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in structures]}\")\npipeline = filter_strategy >> data_resource_strategy parsed_session = json.loads(pipeline.get()) print(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\") import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"Their Materials Project IDs are: {[structure.id for structure in structures]}\")
The query resulted in 5 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1038042', 'mp-1182891', 'mp-1208627', 'mp-1247835', 'mp-1521059']\n
Let us also check the query parameters used for the request to the OPTIMADE API to ensure that the page_limit
query parameter was passed:
parsed_session[\"optimade_config\"]\nparsed_session[\"optimade_config\"] Out[10]:
{'query_parameters': {'filter': 'elements HAS ALL \"Al\",\"O\"', 'page_limit': 5}}In\u00a0[11]: Copied!
structure = structures[0]\nprint(structure.id)\nprint(f\"elements: {structure.elements}\")\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nstructure = structures[0] print(structure.id) print(f\"elements: {structure.elements}\") for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
mp-1038042\nelements: ['Al', 'Cr', 'Mg', 'O']\nchemical_formula_descriptive: AlCrMg30O32\nchemical_formula_reduced: AlCrMg30O32\nchemical_formula_hill: AlCrMg30O32\nchemical_formula_anonymous: A32B30CD\nIn\u00a0[12]: Copied!
data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE\",\n accessUrl=\"https://aiida.materialscloud.org/mc3d/optimade\",\n)\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE\", accessUrl=\"https://aiida.materialscloud.org/mc3d/optimade\", ) In\u00a0[13]: Copied!
pipeline = filter_strategy >> data_resource_strategy\nparsed_session = json.loads(pipeline.get())\nprint(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\")\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\nstructures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"Their Materials Cloud (AiiDA) IDs are: {[structure.id for structure in structures]}\")\npipeline = filter_strategy >> data_resource_strategy parsed_session = json.loads(pipeline.get()) print(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\") import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"Their Materials Cloud (AiiDA) IDs are: {[structure.id for structure in structures]}\")
The query resulted in 5 structures found (on page 1) of the returned data.\nTheir Materials Cloud (AiiDA) IDs are: ['13952', '43703', '43800', '56101', '56270']\n
Again, let's check the query parameters used for the request to the OPTIMADE API to ensure it is equivalent to the previous search:
In\u00a0[14]: Copied!parsed_session[\"optimade_config\"]\nparsed_session[\"optimade_config\"] Out[14]:
{'query_parameters': {'filter': 'elements HAS ALL \"Al\",\"O\"', 'page_limit': 5}}In\u00a0[15]: Copied!
structure = structures[0]\nprint(structure.id)\nprint(f\"elements: {structure.elements}\")\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nstructure = structures[0] print(structure.id) print(f\"elements: {structure.elements}\") for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
13952\nelements: ['Al', 'O']\nchemical_formula_descriptive: Al2O6\nchemical_formula_reduced: AlO3\nchemical_formula_hill: Al2O6\nchemical_formula_anonymous: A3B\n
The MC3D structures can, unfortunately, not be found easily in the Materials Cloud website, as the ID in the DISCOVER section does not match the ID in the OPTIMADE structure. However, the full OPTIMADE structure can be found at <OPTIMADE_BASE_URL>/structures/<ID>
, for example: aiida.materialscloud.org/mc3d/optimade/structures/13952.
Note
The structure with the OPTIMADE ID 13952 is found with the Materials Cloud DISCOVER ID mc3d-76896
and can be found here.
client = OTEClient(\"python\")\nclient = OTEClient(\"python\")
Now we can go through the same searches as we did with the HTTP requests-based backend. The result should not change.
In\u00a0[17]: Copied!data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE\",\n accessUrl=\"https://optimade.materialsproject.org/\",\n)\n\nfilter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"',\n)\n\npipeline = filter_strategy >> data_resource_strategy\nsession = pipeline.get()\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE\", accessUrl=\"https://optimade.materialsproject.org/\", ) filter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', ) pipeline = filter_strategy >> data_resource_strategy session = pipeline.get()
Setting filter from query.\nSetting filter from query.\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\noptimade_query after update: OPTIMADEQueryParameters(filter='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=chemical_formula_descriptive%20%3D%20%22Al2O3%22%20OR%20chemical_formula_reduced%20%3D%20%22Al2O3%22%20OR%20chemical_formula_hill%20%3D%20%22Al2O3%22&response_format=json&page_limit=20&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=chemical_formula_descriptive%20%3D%20%22Al2O3%22%20OR%20chemical_formula_reduced%20%3D%20%22Al2O3%22%20OR%20chemical_formula_hill%20%3D%20%22Al2O3%22&response_format=json&page_limit=20&include=references\n
/home/cwa/.venvs/oteapi-optimade/lib/python3.9/site-packages/optimade/server/config.py:113: UserWarning: Unable to find config file at /home/cwa/.optimade.json, using the default settings instead.\n warnings.warn(\n
As one can see, this backend runs locally within the same Python environment as the notebook. This is useful for development purposes, where the logging messages are shown directly in the output.
In\u00a0[18]: Copied!parsed_session: dict = json.loads(session)\nprint(parsed_session.keys())\nparsed_session: dict = json.loads(session) print(parsed_session.keys())
dict_keys(['optimade_config', 'optimade_resources', 'optimade_resource_model'])\n
We get the same keys in the returned session as we did with the HTTP requests-based backend. If we again import the OPTIMADE Structure model class from the OPTIMADE Python tools, we can parse the OPTIMADE structures into Python objects as well as validating them according to the OPTIMADE specification, just as we did with the HTTP requests-based backend.
In\u00a0[19]: Copied!import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\n\nparsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\")\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")\n\nstructure = parsed_structures[0]\nprint(structure.id)\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) parsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\") print(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\") structure = parsed_structures[0] print(structure.id) for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
The query resulted in 20 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1228448', 'mp-755483', 'mp-1245081', 'mp-1244878', 'mp-1105018', 'mp-754401', 'mp-1245063', 'mp-1245265', 'mp-684591', 'mp-1244898', 'mp-1245211', 'mp-1245056', 'mp-642363', 'mp-1244930', 'mp-1244937', 'mp-1244967', 'mp-1244954', 'mp-1244874', 'mp-1245008', 'mp-1245023']\nmp-1228448\nchemical_formula_descriptive: Al2O3\nchemical_formula_reduced: Al2O3\nchemical_formula_hill: Al2O3\nchemical_formula_anonymous: A3B2\nIn\u00a0[20]: Copied!
filter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='elements HAS ALL \"Al\",\"O\"',\n limit=5,\n)\n\npipeline = filter_strategy >> data_resource_strategy\nparsed_session = json.loads(pipeline.get())\nprint(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\")\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\nstructures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in structures]}\")\nfilter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='elements HAS ALL \"Al\",\"O\"', limit=5, ) pipeline = filter_strategy >> data_resource_strategy parsed_session = json.loads(pipeline.get()) print(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\") import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"Their Materials Project IDs are: {[structure.id for structure in structures]}\")
Setting filter from query.\nSetting filter from query.\nSetting filter from query.\nSetting page_limit from limit.\nSetting page_limit from limit.\nSetting page_limit from limit.\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nThe query resulted in 5 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1038042', 'mp-1182891', 'mp-1208627', 'mp-1247835', 'mp-1521059']\n
The configuration in optimade_config
is quite more extensive than with the HTTP requests-based backend, as it includes more information, automatically setting the default values so they are shown in the config:
parsed_session[\"optimade_config\"]\nparsed_session[\"optimade_config\"] Out[21]:
{'version': 'v1',\n 'endpoint': 'structures',\n 'query_parameters': {'filter': 'elements HAS ALL \"Al\",\"O\"', 'page_limit': 5},\n 'datacache_config': {'cacheDir': 'oteapi',\n 'accessKey': None,\n 'hashType': 'md5',\n 'expireTime': 86400,\n 'tag': 'optimade'},\n 'return_object': False,\n 'use_dlite': False}
Let's look at the first structure again:
In\u00a0[22]: Copied!structure = structures[0]\nprint(structure.id)\nprint(f\"elements: {structure.elements}\")\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nstructure = structures[0] print(structure.id) print(f\"elements: {structure.elements}\") for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
mp-1038042\nelements: ['Al', 'Cr', 'Mg', 'O']\nchemical_formula_descriptive: AlCrMg30O32\nchemical_formula_reduced: AlCrMg30O32\nchemical_formula_hill: AlCrMg30O32\nchemical_formula_anonymous: A32B30CD\nIn\u00a0[23]: Copied!
data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE\",\n accessUrl=\"https://aiida.materialscloud.org/mc3d/optimade\",\n)\n\npipeline = filter_strategy >> data_resource_strategy\nparsed_session = json.loads(pipeline.get())\nprint(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\")\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\nstructures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"Their Materials Cloud (AiiDA) IDs are: {[structure.id for structure in structures]}\")\n\nprint(parsed_session[\"optimade_config\"])\n\nstructure = structures[0]\nprint(structure.id)\nprint(f\"elements: {structure.elements}\")\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE\", accessUrl=\"https://aiida.materialscloud.org/mc3d/optimade\", ) pipeline = filter_strategy >> data_resource_strategy parsed_session = json.loads(pipeline.get()) print(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\") import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"Their Materials Cloud (AiiDA) IDs are: {[structure.id for structure in structures]}\") print(parsed_session[\"optimade_config\"]) structure = structures[0] print(structure.id) print(f\"elements: {structure.elements}\") for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
Setting filter from query.\nSetting filter from query.\nSetting filter from query.\nSetting page_limit from limit.\nSetting page_limit from limit.\nSetting page_limit from limit.\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://aiida.materialscloud.org/mc3d/optimade', base_url='https://aiida.materialscloud.org/mc3d/optimade', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://aiida.materialscloud.org/mc3d/optimade', base_url='https://aiida.materialscloud.org/mc3d/optimade', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://aiida.materialscloud.org/mc3d/optimade', base_url='https://aiida.materialscloud.org/mc3d/optimade', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\nOPTIMADE URL to be requested: https://aiida.materialscloud.org/mc3d/optimade/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nOPTIMADE URL to be requested: https://aiida.materialscloud.org/mc3d/optimade/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nOPTIMADE URL to be requested: https://aiida.materialscloud.org/mc3d/optimade/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nThe query resulted in 5 structures found (on page 1) of the returned data.\nTheir Materials Cloud (AiiDA) IDs are: ['13952', '43703', '43800', '56101', '56270']\n{'version': 'v1', 'endpoint': 'structures', 'query_parameters': {'filter': 'elements HAS ALL \"Al\",\"O\"', 'page_limit': 5}, 'datacache_config': {'cacheDir': 'oteapi', 'accessKey': None, 'hashType': 'md5', 'expireTime': 86400, 'tag': 'optimade'}, 'return_object': False, 'use_dlite': False}\n13952\nelements: ['Al', 'O']\nchemical_formula_descriptive: Al2O6\nchemical_formula_reduced: AlO3\nchemical_formula_hill: Al2O6\nchemical_formula_anonymous: A3B\n"},{"location":"examples/otelib/#use-oteapi-optimade-with-otelib","title":"Use OTEAPI-OPTIMADE with OTElib\u00b6","text":"
OTElib is a Python client library for the OTEAPI system. It has two usable backends:
This notebook demonstrates how to use OTEAPI-OPTIMADE with OTElib.
"},{"location":"examples/otelib/#example","title":"Example\u00b6","text":"Using OTElib we will do different OPTIMADE queries. First, we will search through the Materials Project database for all structures with the formula Al2O3
. Then, we will search through the Materials Project database for all structures that include Al
and O
in their chemical formula. Finally, we will search through the Materials Cloud MC3D - Materials Cloud three-dimensional crystals database for all structures that include Al
and O
in their chemical formula.
This backend is equivalent to running in production. It requires a running OTEAPI server.
"},{"location":"examples/otelib/#setup","title":"Setup\u00b6","text":"First, we need to have a running OTEAPI server. If one is not available, we can start one using Docker. See the overview for instructions on how to start a server.
Further documentation about the OTEAPI Service is available as a README on GitHub.
Note
It is advisable to run the OTEAPI server in a separate terminal window, so that we can see the logs from the server. Furthermore, we can stop the server by pressing Ctrl+C
in the terminal window.
After following the instructions, we should have a running OTEAPI server at localhost:80.
"},{"location":"examples/otelib/#create-a-client","title":"Create a client\u00b6","text":""},{"location":"examples/otelib/#search-through-materials-project-for-all-structures-with-the-formula-al2o3","title":"Search through Materials Project for all structures with the formulaAl2O3
\u00b6","text":"To search through Materials Project for all structures with the formula Al2O3
, we need to create the OTE strategies that we want to use for creating the OTE pipeline:
To create the strategies, we need to know how to configure them. This information is available in the OTEAPI Core documentation, specifically for Data Resource strategies and for Filter strategies.
For the data resource strategy, i.e., the OPTIMADE DB strategy, we need to know the base URL of the OPTIMADE API for Materials Project. OPTIMADE has a useful providers dashboard that lists all known OPTIMADE providers and their (sub-)databases. Here we can find the base URL for the Materials Project: https://optimade.materialsproject.org
.
Al
and O
in their chemical formula\u00b6","text":"We must use a different OPTIMADE filter query for this search:
elements HAS ALL \"Al\",\"O\"\n
elements
is a structure attribute that lists all elements in the structure. The HAS ALL
operator matches if, for each value, there is at least one element in elements
equal to that value. (This implements the set operator >=
.)
To do this search, we can reuse the OTE pipeline from the previous search, but change the filter strategy.
"},{"location":"examples/otelib/#search-through-mc3d-database-in-materials-cloud-for-all-structures-that-include-al-and-o-in-their-chemical-formula","title":"Search through MC3D database in Materials Cloud for all structures that includeAl
and O
in their chemical formula\u00b6","text":"Finally, let us reuse the same filter strategy we used for the previous search, but change the data resource strategy to point to the MC3D database in Materials Cloud.
The base URL for the MC3D database is https://aiida.materialscloud.org/mc3d/optimade
as found in the providers dashboard.
With this backend, we do not need a running OTEAPI server.
"},{"location":"examples/otelib/#create-a-client","title":"Create a client\u00b6","text":""},{"location":"examples/otelib/#search-through-materials-project-for-all-structures-with-the-formula-al2o3","title":"Search through Materials Project for all structures with the formulaAl2O3
\u00b6","text":""},{"location":"examples/otelib/#search-through-materials-project-for-all-structures-that-include-al-and-o-in-their-chemical-formula","title":"Search through Materials Project for all structures that include Al
and O
in their chemical formula\u00b6","text":""},{"location":"examples/otelib/#search-through-mc3d-database-in-materials-cloud-for-all-structures-that-include-al-and-o-in-their-chemical-formula","title":"Search through MC3D database in Materials Cloud for all structures that include Al
and O
in their chemical formula\u00b6","text":""}]}
\ No newline at end of file
+{"config":{"lang":["en"],"separator":"[\\s\\-]+","pipeline":["stopWordFilter"]},"docs":[{"location":"","title":"OTE-API OPTIMADE","text":"An OTE-API Plugin with OTE strategies.
Further reading:
OTE-API OPTIMADE is released under the MIT license with copyright \u00a9 SINTEF.
"},{"location":"#acknowledgment","title":"Acknowledgment","text":"OTE-API OPTIMADE has been created via the cookiecutter template for OTE-API plugins.
OTE-API OPTIMADE has been supported by the following projects:
OntoTrans (2020-2024) that receives funding from the European Union\u2019s Horizon 2020 Research and Innovation Programme, under Grant Agreement n. 862136.
VIPCOAT (2021-2025) receives funding from the European Union\u2019s Horizon 2020 Research and Innovation Programme - DT-NMBP-11-2020 Open Innovation Platform for Materials Modelling, under Grant Agreement no: 952903.
OpenModel (2021-2025) receives funding from the European Union\u2019s Horizon 2020 Research and Innovation Programme - DT-NMBP-11-2020 Open Innovation Platform for Materials Modelling, under Grant Agreement no: 953167.
Full Changelog
"},{"location":"CHANGELOG/#support-modern-session-handling-in-oteapi-core","title":"Support modern session handling in OTEAPI Core","text":"From OTEAPI Core v0.7.0, sessions are handled differently in strategies, leading to signature changes in the strategy methods. This development release version matches the current development release version(s) of OTEAPI Core (and OTELib).
"},{"location":"CHANGELOG/#single-entity-optimade-structure-resource","title":"Single entity OPTIMADE Structure Resource","text":"A single entity has been added to parse an OPTIMADE Structure resource. The DLite parse strategy has been updated to support this new entity.
Furthermore, utility/helper functions to parse the resulting species
and assemblies
data are available at oteapi_optimade.parse_species()
and oteapi_optimade.parse_assemblies()
, respectively.
The permanent dependencies branch has been removed in favor of using Dependabot's groups feature and merging everything directly into main
.
Implemented enhancements:
Merged pull requests:
Full Changelog
"},{"location":"CHANGELOG/#update-to-latest-dependencies","title":"Update to latest dependencies","text":"Update dependencies to support the latest core libraries.
This release is done almost immediately prior to the v0.6.0.dev0 release, which will support the upcoming re-design of OTEAPI Core and the use of sessions.
Fixed bugs:
Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
oteapi
docker image version for testing #187Merged pull requests:
Full Changelog
"},{"location":"CHANGELOG/#v041-2023-10-26","title":"v0.4.1 (2023-10-26)","text":"Full Changelog
Fixed bugs:
Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Segmentation fault
from dlite in CI #115dlite
module #113Closed issues:
Merged pull requests:
Full Changelog
Implemented enhancements:
SINTEF/ci-cd
CI - Tests workflow #71Fixed bugs:
SINTEF/ci-cd
instead of CasperWA/ci-cd
#72Closed issues:
Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Closed issues:
Merged pull requests:
Full Changelog
Closed issues:
test: false
for publish workflow #61Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Closed issues:
optimade
container image in CI #41Merged pull requests:
Full Changelog
Implemented enhancements:
/
) #28Merged pull requests:
Full Changelog
Implemented enhancements:
Fixed bugs:
Closed issues:
Merged pull requests:
ID!
type instead of String!
#7 (CasperWA)* This Changelog was automatically generated by github_changelog_generator
"},{"location":"LICENSE/","title":"License","text":"MIT License
Copyright (c) 2022 SINTEF
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"},{"location":"all_strategies/","title":"OTE-API OPTIMADE Strategies","text":"This page provides documentation for the oteapi_optimade.strategies
submodule, where all the OTE-API OPTIMADE strategies are located.
These strategies will be available when setting up a server in an environment with oteapi-optimade installed.
"},{"location":"api_reference/_utils/","title":"_utils","text":"Utility and helper functions.
"},{"location":"api_reference/_utils/#oteapi_optimade._utils.parse_assemblies","title":"parse_assemblies(instance)
","text":"Parse assemblies properties from the OPTIMADEStructureResource entity into \"proper\" OPT StructureResource 'Assembly's.
Source code inoteapi_optimade/_utils.py
def parse_assemblies(instance: dlite.Instance | dict[str, Any]) -> list[dict[str, Any]]:\n \"\"\"Parse assemblies properties from the OPTIMADEStructureResource entity into \"proper\" OPT\n StructureResource 'Assembly's.\"\"\"\n if not _check_correct_entity(instance, \"OPTIMADEStructureResource\"):\n raise ValueError(\n \"Entity for the instance is not an OPTIMADEStructureResource entity.\"\n )\n\n if isinstance(instance, dlite.Instance):\n instance = instance.asdict(single=True)\n\n if not isinstance(instance, dict):\n raise TypeError(\"Entity instance is not a dictionary.\")\n\n assemblies: list[dict[str, Any]] = []\n\n for index in range(instance[\"dimensions\"][\"nassemblies\"]):\n assemblies.append(\n {\n \"sites_in_groups\": [\n [int(_) for _ in group.split(\",\")]\n for group in instance[\"properties\"][\"assembly_sites_in_groups\"][\n index\n ].split(\";\")\n ],\n \"group_probabilities\": [\n float(_)\n for _ in instance[\"properties\"][\"assembly_group_probabilities\"][\n index\n ].split(\",\")\n ],\n }\n )\n\n return assemblies\n
"},{"location":"api_reference/_utils/#oteapi_optimade._utils.parse_species","title":"parse_species(instance)
","text":"Parse species properties from the OPTIMADEStructureResource entity into \"proper\" OPT StructureResource Species.
Source code inoteapi_optimade/_utils.py
def parse_species(instance: dlite.Instance | dict[str, Any]) -> list[dict[str, Any]]:\n \"\"\"Parse species properties from the OPTIMADEStructureResource entity into \"proper\" OPT\n StructureResource Species.\"\"\"\n if not _check_correct_entity(instance, \"OPTIMADEStructureResource\"):\n raise ValueError(\n \"Entity for the instance is not an OPTIMADEStructureResource entity.\"\n )\n\n if isinstance(instance, dlite.Instance):\n instance = instance.asdict(single=True)\n\n if not isinstance(instance, dict):\n raise TypeError(\"Entity instance is not a dictionary.\")\n\n species: list[dict[str, Any]] = []\n\n for index in range(instance[\"dimensions\"][\"nspecies\"]):\n # Required fields\n new_species = {\n \"name\": instance[\"properties\"][\"species_name\"][index],\n \"chemical_symbols\": instance[\"properties\"][\"species_chemical_symbols\"][\n index\n ].split(\",\"),\n \"concentration\": [\n float(_)\n for _ in instance[\"properties\"][\"species_concentration\"][index].split(\n \",\"\n )\n ],\n }\n\n # Optional fields\n if mass := instance[\"properties\"][\"species_mass\"][index]:\n new_species[\"mass\"] = [float(_) for _ in mass.split(\",\")]\n\n if original_name := instance[\"properties\"][\"species_original_name\"][index]:\n new_species[\"original_name\"] = original_name\n\n if attached := instance[\"properties\"][\"species_attached\"][index]:\n new_species[\"attached\"] = attached.split(\",\")\n\n if \"species_nattached\" not in instance[\"properties\"]:\n raise ValueError(\n \"species_attached is present, but species_nattached is missing.\"\n )\n\n new_species[\"nattached\"] = [\n int(_)\n for _ in instance[\"properties\"][\"species_nattached\"][index].split(\",\")\n ]\n\n species.append(new_species)\n\n return species\n
"},{"location":"api_reference/exceptions/","title":"exceptions","text":"OTE-API OPTIMADE-specific Python exceptions.
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.BaseOteapiOptimadeException","title":"BaseOteapiOptimadeException
","text":" Bases: Exception
Base OTE-API OPTIMADE exception.
Source code inoteapi_optimade/exceptions.py
class BaseOteapiOptimadeException(Exception):\n \"\"\"Base OTE-API OPTIMADE exception.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.ConfigurationError","title":"ConfigurationError
","text":" Bases: BaseOteapiOptimadeException
An error occurred when dealing with strategy configurations.
Source code inoteapi_optimade/exceptions.py
class ConfigurationError(BaseOteapiOptimadeException):\n \"\"\"An error occurred when dealing with strategy configurations.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.MissingDependency","title":"MissingDependency
","text":" Bases: BaseOteapiOptimadeException
A required dependency is missing.
Source code inoteapi_optimade/exceptions.py
class MissingDependency(BaseOteapiOptimadeException):\n \"\"\"A required dependency is missing.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.OPTIMADEParseError","title":"OPTIMADEParseError
","text":" Bases: BaseOteapiOptimadeException
Could not use OPTIMADE Python tools to parse an OPTIMADE API response.
Source code inoteapi_optimade/exceptions.py
class OPTIMADEParseError(BaseOteapiOptimadeException):\n \"\"\"Could not use OPTIMADE Python tools to parse an OPTIMADE API response.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.OPTIMADEResponseError","title":"OPTIMADEResponseError
","text":" Bases: RequestError
An OPTIMADE error was returned from a URL request.
Source code inoteapi_optimade/exceptions.py
class OPTIMADEResponseError(RequestError):\n \"\"\"An OPTIMADE error was returned from a URL request.\"\"\"\n
"},{"location":"api_reference/exceptions/#oteapi_optimade.exceptions.RequestError","title":"RequestError
","text":" Bases: BaseOteapiOptimadeException
A general error occured when performing a URL request.
Source code inoteapi_optimade/exceptions.py
class RequestError(BaseOteapiOptimadeException):\n \"\"\"A general error occured when performing a URL request.\"\"\"\n
"},{"location":"api_reference/dlite/parse/","title":"parse","text":"OTEAPI strategy for parsing OPTIMADE structure resources to DLite instances.
"},{"location":"api_reference/dlite/parse/#oteapi_optimade.dlite.parse.OPTIMADEDLiteParseStrategy","title":"OPTIMADEDLiteParseStrategy
","text":"Parse strategy for JSON.
Implements strategies:
(\"parserType\", \"parser/OPTIMADE/DLite\")
oteapi_optimade/dlite/parse.py
@dataclass\nclass OPTIMADEDLiteParseStrategy:\n \"\"\"Parse strategy for JSON.\n\n **Implements strategies**:\n\n - `(\"parserType\", \"parser/OPTIMADE/DLite\")`\n\n \"\"\"\n\n parse_config: OPTIMADEDLiteParseConfig\n\n def initialize(self) -> DLiteSessionUpdate:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n return DLiteSessionUpdate(\n collection_id=get_collection(\n collection_id=self.parse_config.configuration.collection_id\n ).uuid\n )\n\n def get(self) -> OPTIMADEParseResult:\n \"\"\"Request and parse an OPTIMADE response using OPT.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `downloadUrl`.\n\n Workflow:\n\n 1. Request OPTIMADE response.\n 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model.\n\n ---\n\n The OPTIMADE Structure needs to be parsed into DLite instances inside-out,\n meaning the most nested data structures must first be parsed, and then the ones\n 1 layer up and so on until the most upper layer can be parsed.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n generic_parse_config = self.parse_config.model_copy(\n update={\n \"parserType\": self.parse_config.parserType.replace(\"/dlite\", \"\"),\n \"configuration\": self.parse_config.configuration.model_copy(\n update={\n \"mediaType\": self.parse_config.configuration.get(\n \"mediaType\", \"\"\n ).replace(\"+dlite\", \"+json\")\n }\n ),\n }\n ).model_dump(exclude_unset=True, exclude_defaults=True)\n generic_parse_result = OPTIMADEParseStrategy(generic_parse_config).get()\n\n OPTIMADEStructure = dlite.get_instance(str(self.parse_config.entity))\n\n single_entity = \"OPTIMADEStructureResource\" in OPTIMADEStructure.uri\n\n if not single_entity:\n nested_entity_mapping: dict[str, dlite.Instance] = self._get_nested_entity(\n OPTIMADEStructure\n )\n\n if not (\n generic_parse_result.optimade_response\n and generic_parse_result.optimade_response_model\n ):\n base_error_message = (\n \"Could not retrieve response from OPTIMADE parse strategy.\"\n )\n LOGGER.error(\n \"%s\\n\"\n \"optimade_response=%r\\n\"\n \"optimade_response_model=%r\\n\"\n \"session fields=%r\",\n base_error_message,\n generic_parse_result.get(\"optimade_response\"),\n generic_parse_result.get(\"optimade_response_model\"),\n list(generic_parse_result),\n )\n raise OPTIMADEParseError(base_error_message)\n\n optimade_response_model_module, optimade_response_model_name = (\n generic_parse_result.optimade_response_model\n )\n\n # Parse response using the provided model\n try:\n optimade_response_model: type[OPTIMADEResponse] = getattr(\n importlib.import_module(optimade_response_model_module),\n optimade_response_model_name,\n )\n optimade_response = optimade_response_model(\n **generic_parse_result.optimade_response\n )\n except (ImportError, AttributeError) as exc:\n base_error_message = \"Could not import the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ImportError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n except ValidationError as exc:\n base_error_message = \"Could not validate the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ValidationError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n\n # Currently, only \"structures\" entries are supported and handled\n if isinstance(optimade_response, StructureResponseMany):\n structures: list[StructureResource] = [\n (\n StructureResource(**entry)\n if isinstance(entry, dict)\n else entry.model_copy(deep=True)\n )\n for entry in optimade_response.data\n ]\n\n elif isinstance(optimade_response, StructureResponseOne):\n structures = (\n [\n (\n StructureResource(**optimade_response.data)\n if isinstance(optimade_response.data, dict)\n else optimade_response.data.model_copy(deep=True)\n )\n ]\n if optimade_response.data is not None\n else []\n )\n\n elif isinstance(optimade_response, Success):\n if isinstance(optimade_response.data, dict):\n structures = [StructureResource(**optimade_response.data)]\n elif isinstance(optimade_response.data, BaseModel):\n structures = [StructureResource(**optimade_response.data.model_dump())]\n elif isinstance(optimade_response.data, list):\n structures = [\n (\n StructureResource(**entry)\n if isinstance(entry, dict)\n else StructureResource(**entry.model_dump())\n )\n for entry in optimade_response.data\n ]\n elif optimade_response.data is None:\n structures = []\n else:\n LOGGER.error(\n \"Could not determine what to do with `data`. Type %s.\",\n type(optimade_response.data),\n )\n raise OPTIMADEParseError(\"Could not parse `data` entry in response.\")\n\n else:\n LOGGER.error(\n \"Got currently unsupported response type %s. Only structures are \"\n \"supported.\",\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(\n \"The DLite OPTIMADE Parser currently only supports structures entities.\"\n )\n\n if not structures:\n LOGGER.warning(\"No structures found in the response.\")\n return generic_parse_result\n\n # DLite-fy OPTIMADE structures\n dlite_collection = get_collection(\n collection_id=self.parse_config.configuration.collection_id\n )\n\n for structure in structures:\n new_structure_attributes: dict[str, Any] = {}\n single_entity_dimensions: dict[str, int] = {\n \"nassemblies\": 0,\n \"nspecies\": 0,\n }\n\n ## For OPTIMADEStructure (multiple) entities\n # Most inner layer: assemblies & species\n if structure.attributes.assemblies:\n if single_entity:\n single_entity_dimensions[\"nassemblies\"] = len(\n structure.attributes.assemblies\n )\n new_structure_attributes.update(\n {\n \"assemblies_sites_in_groups\": [],\n \"assemblies_group_probabilities\": [],\n }\n )\n else:\n new_structure_attributes[\"assemblies\"] = []\n\n for assembly in structure.attributes.assemblies:\n if single_entity:\n new_structure_attributes[\"assemblies_sites_in_groups\"].append(\n \";\".join(\n [\n \",\".join(str(_) for _ in group)\n for group in assembly.sites_in_groups\n ]\n )\n )\n new_structure_attributes[\n \"assemblies_group_probabilities\"\n ].append(\",\".join(str(_) for _ in assembly.group_probabilities))\n else:\n if \"attributes.assemblies\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes.assemblies'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\n \"Could not find entity for 'attributes.assemblies'.\"\n )\n\n dimensions = {\n \"ngroups\": len(assembly.group_probabilities),\n \"nsites\": len(assembly.sites_in_groups),\n }\n new_structure_attributes[\"assemblies\"].append(\n nested_entity_mapping[\"attributes.assemblies\"](\n dimensions=dimensions, properties=assembly.model_dump()\n )\n )\n\n if structure.attributes.species:\n if single_entity:\n single_entity_dimensions[\"nspecies\"] = len(\n structure.attributes.species\n )\n new_structure_attributes.update(\n {\n \"species_name\": [],\n \"species_chemical_symbols\": [],\n \"species_concentration\": [],\n \"species_mass\": [],\n \"species_original_name\": [],\n \"species_attached\": [],\n \"species_nattached\": [],\n }\n )\n else:\n new_structure_attributes[\"species\"] = []\n\n for species in structure.attributes.species:\n if single_entity:\n new_structure_attributes[\"species_name\"].append(species.name)\n new_structure_attributes[\"species_chemical_symbols\"].append(\n \",\".join(species.chemical_symbols)\n )\n new_structure_attributes[\"species_concentration\"].append(\n \",\".join(str(_) for _ in species.concentration)\n )\n new_structure_attributes[\"species_mass\"].append(\n \",\".join(str(_) for _ in (species.mass or []))\n )\n new_structure_attributes[\"species_original_name\"].append(\n species.original_name or \"\"\n )\n new_structure_attributes[\"species_attached\"].append(\n \",\".join(species.attached or [])\n )\n new_structure_attributes[\"species_nattached\"].append(\n \",\".join(str(_) for _ in (species.nattached or []))\n )\n else:\n if \"attributes.species\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes.species'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\n \"Could not find entity for 'attributes.species'.\"\n )\n\n dimensions = {\n \"nelements\": len(species.chemical_symbols),\n \"nattached_elements\": len(species.attached or []),\n }\n new_structure_attributes[\"species\"].append(\n nested_entity_mapping[\"attributes.species\"](\n dimensions=dimensions,\n properties=species.model_dump(exclude_none=True),\n )\n )\n\n # Attributes\n new_structure_attributes.update(\n structure.attributes.model_dump(\n exclude={\n \"species\",\n \"assemblies\",\n \"nelements\",\n \"nsites\",\n \"structure_features\",\n },\n exclude_unset=True,\n exclude_defaults=True,\n exclude_none=True,\n )\n )\n for key in list(new_structure_attributes):\n if key.startswith(\"_\"):\n new_structure_attributes.pop(key)\n\n # Structure features values are Enum values, so we need to convert them to\n # their string (true) values\n new_structure_attributes[\"structure_features\"] = [\n _.value for _ in structure.attributes.structure_features\n ]\n\n if single_entity:\n new_structure_attributes[\"id\"] = structure.id\n new_structure_attributes[\"type\"] = structure.type\n\n new_structure = OPTIMADEStructure(\n dimensions={\n \"nelements\": structure.attributes.nelements or 0,\n \"dimensionality\": 3,\n \"nsites\": structure.attributes.nsites or 0,\n \"nstructure_features\": len(\n structure.attributes.structure_features\n ),\n **single_entity_dimensions,\n },\n properties=new_structure_attributes,\n )\n else:\n if \"attributes\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\"Could not find entity for 'attributes'.\")\n\n new_structure = OPTIMADEStructure(\n dimensions={},\n properties={\n \"attributes\": nested_entity_mapping[\"attributes\"](\n dimensions={\n \"nelements\": structure.attributes.nelements or 0,\n \"dimensionality\": 3,\n \"nsites\": structure.attributes.nsites or 0,\n \"nspecies\": (\n len(structure.attributes.species)\n if structure.attributes.species\n else 0\n ),\n \"nstructure_features\": len(\n structure.attributes.structure_features\n ),\n },\n properties=new_structure_attributes,\n ),\n \"type\": structure.type,\n \"id\": structure.id,\n },\n )\n\n dlite_collection.add(label=structure.id, inst=new_structure)\n\n update_collection(collection=dlite_collection)\n\n return generic_parse_result\n\n def _get_nested_entity(self, entity: dlite.Instance) -> dict[str, dlite.Instance]:\n \"\"\"Get nested entity from DLite instance.\"\"\"\n nested_entities: dict[str, dlite.Instance] = {}\n\n for prop in entity.properties[\"properties\"]:\n if prop.type == \"ref\":\n nested_entities[prop.name] = dlite.get_instance(prop.ref)\n\n for name, nested_entity in tuple(nested_entities.items()):\n futher_nested_entities = self._get_nested_entity(nested_entity)\n\n for nested_name, further_nested_entity in futher_nested_entities.items():\n nested_entities[f\"{name}.{nested_name}\"] = further_nested_entity\n\n return nested_entities\n
"},{"location":"api_reference/dlite/parse/#oteapi_optimade.dlite.parse.OPTIMADEDLiteParseStrategy.get","title":"get()
","text":"Request and parse an OPTIMADE response using OPT.
This method will be called through the strategy-specific endpoint of the OTE-API Services.
Configuration values provided in resource_config.configuration
take precedence over the derived values from downloadUrl
.
Workflow:
The OPTIMADE Structure needs to be parsed into DLite instances inside-out, meaning the most nested data structures must first be parsed, and then the ones 1 layer up and so on until the most upper layer can be parsed.
Returns:
Type DescriptionOPTIMADEParseResult
An update model of key/value-pairs to be stored in the session-specific
OPTIMADEParseResult
context from services.
Source code inoteapi_optimade/dlite/parse.py
def get(self) -> OPTIMADEParseResult:\n \"\"\"Request and parse an OPTIMADE response using OPT.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `downloadUrl`.\n\n Workflow:\n\n 1. Request OPTIMADE response.\n 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model.\n\n ---\n\n The OPTIMADE Structure needs to be parsed into DLite instances inside-out,\n meaning the most nested data structures must first be parsed, and then the ones\n 1 layer up and so on until the most upper layer can be parsed.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n generic_parse_config = self.parse_config.model_copy(\n update={\n \"parserType\": self.parse_config.parserType.replace(\"/dlite\", \"\"),\n \"configuration\": self.parse_config.configuration.model_copy(\n update={\n \"mediaType\": self.parse_config.configuration.get(\n \"mediaType\", \"\"\n ).replace(\"+dlite\", \"+json\")\n }\n ),\n }\n ).model_dump(exclude_unset=True, exclude_defaults=True)\n generic_parse_result = OPTIMADEParseStrategy(generic_parse_config).get()\n\n OPTIMADEStructure = dlite.get_instance(str(self.parse_config.entity))\n\n single_entity = \"OPTIMADEStructureResource\" in OPTIMADEStructure.uri\n\n if not single_entity:\n nested_entity_mapping: dict[str, dlite.Instance] = self._get_nested_entity(\n OPTIMADEStructure\n )\n\n if not (\n generic_parse_result.optimade_response\n and generic_parse_result.optimade_response_model\n ):\n base_error_message = (\n \"Could not retrieve response from OPTIMADE parse strategy.\"\n )\n LOGGER.error(\n \"%s\\n\"\n \"optimade_response=%r\\n\"\n \"optimade_response_model=%r\\n\"\n \"session fields=%r\",\n base_error_message,\n generic_parse_result.get(\"optimade_response\"),\n generic_parse_result.get(\"optimade_response_model\"),\n list(generic_parse_result),\n )\n raise OPTIMADEParseError(base_error_message)\n\n optimade_response_model_module, optimade_response_model_name = (\n generic_parse_result.optimade_response_model\n )\n\n # Parse response using the provided model\n try:\n optimade_response_model: type[OPTIMADEResponse] = getattr(\n importlib.import_module(optimade_response_model_module),\n optimade_response_model_name,\n )\n optimade_response = optimade_response_model(\n **generic_parse_result.optimade_response\n )\n except (ImportError, AttributeError) as exc:\n base_error_message = \"Could not import the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ImportError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n except ValidationError as exc:\n base_error_message = \"Could not validate the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ValidationError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n\n # Currently, only \"structures\" entries are supported and handled\n if isinstance(optimade_response, StructureResponseMany):\n structures: list[StructureResource] = [\n (\n StructureResource(**entry)\n if isinstance(entry, dict)\n else entry.model_copy(deep=True)\n )\n for entry in optimade_response.data\n ]\n\n elif isinstance(optimade_response, StructureResponseOne):\n structures = (\n [\n (\n StructureResource(**optimade_response.data)\n if isinstance(optimade_response.data, dict)\n else optimade_response.data.model_copy(deep=True)\n )\n ]\n if optimade_response.data is not None\n else []\n )\n\n elif isinstance(optimade_response, Success):\n if isinstance(optimade_response.data, dict):\n structures = [StructureResource(**optimade_response.data)]\n elif isinstance(optimade_response.data, BaseModel):\n structures = [StructureResource(**optimade_response.data.model_dump())]\n elif isinstance(optimade_response.data, list):\n structures = [\n (\n StructureResource(**entry)\n if isinstance(entry, dict)\n else StructureResource(**entry.model_dump())\n )\n for entry in optimade_response.data\n ]\n elif optimade_response.data is None:\n structures = []\n else:\n LOGGER.error(\n \"Could not determine what to do with `data`. Type %s.\",\n type(optimade_response.data),\n )\n raise OPTIMADEParseError(\"Could not parse `data` entry in response.\")\n\n else:\n LOGGER.error(\n \"Got currently unsupported response type %s. Only structures are \"\n \"supported.\",\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(\n \"The DLite OPTIMADE Parser currently only supports structures entities.\"\n )\n\n if not structures:\n LOGGER.warning(\"No structures found in the response.\")\n return generic_parse_result\n\n # DLite-fy OPTIMADE structures\n dlite_collection = get_collection(\n collection_id=self.parse_config.configuration.collection_id\n )\n\n for structure in structures:\n new_structure_attributes: dict[str, Any] = {}\n single_entity_dimensions: dict[str, int] = {\n \"nassemblies\": 0,\n \"nspecies\": 0,\n }\n\n ## For OPTIMADEStructure (multiple) entities\n # Most inner layer: assemblies & species\n if structure.attributes.assemblies:\n if single_entity:\n single_entity_dimensions[\"nassemblies\"] = len(\n structure.attributes.assemblies\n )\n new_structure_attributes.update(\n {\n \"assemblies_sites_in_groups\": [],\n \"assemblies_group_probabilities\": [],\n }\n )\n else:\n new_structure_attributes[\"assemblies\"] = []\n\n for assembly in structure.attributes.assemblies:\n if single_entity:\n new_structure_attributes[\"assemblies_sites_in_groups\"].append(\n \";\".join(\n [\n \",\".join(str(_) for _ in group)\n for group in assembly.sites_in_groups\n ]\n )\n )\n new_structure_attributes[\n \"assemblies_group_probabilities\"\n ].append(\",\".join(str(_) for _ in assembly.group_probabilities))\n else:\n if \"attributes.assemblies\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes.assemblies'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\n \"Could not find entity for 'attributes.assemblies'.\"\n )\n\n dimensions = {\n \"ngroups\": len(assembly.group_probabilities),\n \"nsites\": len(assembly.sites_in_groups),\n }\n new_structure_attributes[\"assemblies\"].append(\n nested_entity_mapping[\"attributes.assemblies\"](\n dimensions=dimensions, properties=assembly.model_dump()\n )\n )\n\n if structure.attributes.species:\n if single_entity:\n single_entity_dimensions[\"nspecies\"] = len(\n structure.attributes.species\n )\n new_structure_attributes.update(\n {\n \"species_name\": [],\n \"species_chemical_symbols\": [],\n \"species_concentration\": [],\n \"species_mass\": [],\n \"species_original_name\": [],\n \"species_attached\": [],\n \"species_nattached\": [],\n }\n )\n else:\n new_structure_attributes[\"species\"] = []\n\n for species in structure.attributes.species:\n if single_entity:\n new_structure_attributes[\"species_name\"].append(species.name)\n new_structure_attributes[\"species_chemical_symbols\"].append(\n \",\".join(species.chemical_symbols)\n )\n new_structure_attributes[\"species_concentration\"].append(\n \",\".join(str(_) for _ in species.concentration)\n )\n new_structure_attributes[\"species_mass\"].append(\n \",\".join(str(_) for _ in (species.mass or []))\n )\n new_structure_attributes[\"species_original_name\"].append(\n species.original_name or \"\"\n )\n new_structure_attributes[\"species_attached\"].append(\n \",\".join(species.attached or [])\n )\n new_structure_attributes[\"species_nattached\"].append(\n \",\".join(str(_) for _ in (species.nattached or []))\n )\n else:\n if \"attributes.species\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes.species'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\n \"Could not find entity for 'attributes.species'.\"\n )\n\n dimensions = {\n \"nelements\": len(species.chemical_symbols),\n \"nattached_elements\": len(species.attached or []),\n }\n new_structure_attributes[\"species\"].append(\n nested_entity_mapping[\"attributes.species\"](\n dimensions=dimensions,\n properties=species.model_dump(exclude_none=True),\n )\n )\n\n # Attributes\n new_structure_attributes.update(\n structure.attributes.model_dump(\n exclude={\n \"species\",\n \"assemblies\",\n \"nelements\",\n \"nsites\",\n \"structure_features\",\n },\n exclude_unset=True,\n exclude_defaults=True,\n exclude_none=True,\n )\n )\n for key in list(new_structure_attributes):\n if key.startswith(\"_\"):\n new_structure_attributes.pop(key)\n\n # Structure features values are Enum values, so we need to convert them to\n # their string (true) values\n new_structure_attributes[\"structure_features\"] = [\n _.value for _ in structure.attributes.structure_features\n ]\n\n if single_entity:\n new_structure_attributes[\"id\"] = structure.id\n new_structure_attributes[\"type\"] = structure.type\n\n new_structure = OPTIMADEStructure(\n dimensions={\n \"nelements\": structure.attributes.nelements or 0,\n \"dimensionality\": 3,\n \"nsites\": structure.attributes.nsites or 0,\n \"nstructure_features\": len(\n structure.attributes.structure_features\n ),\n **single_entity_dimensions,\n },\n properties=new_structure_attributes,\n )\n else:\n if \"attributes\" not in nested_entity_mapping:\n LOGGER.error(\n \"Could not find entity for 'attributes'.\\nnested_entity_mapping=%r\",\n nested_entity_mapping,\n )\n raise OPTIMADEParseError(\"Could not find entity for 'attributes'.\")\n\n new_structure = OPTIMADEStructure(\n dimensions={},\n properties={\n \"attributes\": nested_entity_mapping[\"attributes\"](\n dimensions={\n \"nelements\": structure.attributes.nelements or 0,\n \"dimensionality\": 3,\n \"nsites\": structure.attributes.nsites or 0,\n \"nspecies\": (\n len(structure.attributes.species)\n if structure.attributes.species\n else 0\n ),\n \"nstructure_features\": len(\n structure.attributes.structure_features\n ),\n },\n properties=new_structure_attributes,\n ),\n \"type\": structure.type,\n \"id\": structure.id,\n },\n )\n\n dlite_collection.add(label=structure.id, inst=new_structure)\n\n update_collection(collection=dlite_collection)\n\n return generic_parse_result\n
"},{"location":"api_reference/dlite/parse/#oteapi_optimade.dlite.parse.OPTIMADEDLiteParseStrategy.initialize","title":"initialize()
","text":"Initialize strategy.
This method will be called through the /initialize
endpoint of the OTE-API Services.
Returns:
Type DescriptionDLiteSessionUpdate
An update model of key/value-pairs to be stored in the session-specific
DLiteSessionUpdate
context from services.
Source code inoteapi_optimade/dlite/parse.py
def initialize(self) -> DLiteSessionUpdate:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n return DLiteSessionUpdate(\n collection_id=get_collection(\n collection_id=self.parse_config.configuration.collection_id\n ).uuid\n )\n
"},{"location":"api_reference/models/config/","title":"config","text":"General OPTIMADE configuration models.
"},{"location":"api_reference/models/config/#oteapi_optimade.models.config.DEFAULT_CACHE_CONFIG_VALUES","title":"DEFAULT_CACHE_CONFIG_VALUES = {'expireTime': 60 * 60 * 24, 'tag': 'optimade'}
module-attribute
","text":"Set the expireTime
and tag
to default values for the data cache.
OPTIMADEConfig
","text":" Bases: AttrDict
OPTIMADE configuration.
Source code inoteapi_optimade/models/config.py
class OPTIMADEConfig(AttrDict):\n \"\"\"OPTIMADE configuration.\"\"\"\n\n # OTEAPI-specific attributes\n downloadUrl: Annotated[\n Optional[OPTIMADEUrl],\n Field(description=\"Either a base OPTIMADE URL or a full OPTIMADE URL.\"),\n ] = None\n\n mediaType: Annotated[\n Optional[Literal[\"application/vnd.optimade+json\", \"application/vnd.optimade\"]],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=\"The registered strategy name for OPTIMADEParseStrategy.\",\n ),\n ] = None\n\n # OPTIMADE parse result attributes\n optimade_config: Annotated[\n Optional[OPTIMADEConfig],\n Field(description=\"A pre-existing instance of this OPTIMADE configuration.\"),\n ] = None\n\n # OPTIMADE-specific attributes\n version: Annotated[\n str,\n Field(\n description=\"The version part of the OPTIMADE versioned base URL.\",\n pattern=r\"^v[0-9]+(\\.[0-9]+){0,2}$\",\n ),\n ] = \"v1\"\n\n endpoint: Annotated[\n Literal[\"references\", \"structures\"],\n Field(\n description=\"Supported OPTIMADE entry resource endpoint.\",\n ),\n ] = \"structures\"\n\n query_parameters: Annotated[\n Optional[OPTIMADEQueryParameters],\n Field(\n description=\"URL query parameters to be used in the OPTIMADE query.\",\n ),\n ] = None\n\n datacache_config: Annotated[\n DataCacheConfig,\n Field(\n description=\"Configuration options for the local data cache.\",\n ),\n ] = DataCacheConfig(**DEFAULT_CACHE_CONFIG_VALUES)\n\n use_dlite: Annotated[\n bool,\n Field(\n description=\"Whether or not to store the results in a DLite Collection.\",\n ),\n ] = False\n\n @field_validator(\"datacache_config\", mode=\"after\")\n @classmethod\n def _default_datacache_config(\n cls, datacache_config: DataCacheConfig\n ) -> DataCacheConfig:\n \"\"\"Use default values for `DataCacheConfig` if not supplied.\"\"\"\n original_set_values = len(datacache_config.model_fields_set)\n\n for field, default_value in DEFAULT_CACHE_CONFIG_VALUES.items():\n if field in datacache_config.model_fields_set:\n # Use the set value instead of the default\n continue\n setattr(datacache_config, field, default_value)\n\n if len(datacache_config.model_fields_set) > original_set_values:\n # Re-validate model and return it\n return datacache_config.model_validate(\n {\n field: field_value\n for field, field_value in datacache_config.model_dump().items()\n if field in datacache_config.model_fields_set\n }\n )\n return datacache_config\n
"},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.datacache_config","title":"datacache_config: Annotated[DataCacheConfig, Field(description='Configuration options for the local data cache.')] = DataCacheConfig(**DEFAULT_CACHE_CONFIG_VALUES)
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.downloadUrl","title":"downloadUrl: Annotated[Optional[OPTIMADEUrl], Field(description='Either a base OPTIMADE URL or a full OPTIMADE URL.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.endpoint","title":"endpoint: Annotated[Literal['references', 'structures'], Field(description='Supported OPTIMADE entry resource endpoint.')] = 'structures'
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.mediaType","title":"mediaType: Annotated[Optional[Literal['application/vnd.optimade+json', 'application/vnd.optimade']], BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x), Field(description='The registered strategy name for OPTIMADEParseStrategy.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.optimade_config","title":"optimade_config: Annotated[Optional[OPTIMADEConfig], Field(description='A pre-existing instance of this OPTIMADE configuration.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.query_parameters","title":"query_parameters: Annotated[Optional[OPTIMADEQueryParameters], Field(description='URL query parameters to be used in the OPTIMADE query.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.use_dlite","title":"use_dlite: Annotated[bool, Field(description='Whether or not to store the results in a DLite Collection.')] = False
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEConfig.version","title":"version: Annotated[str, Field(description='The version part of the OPTIMADE versioned base URL.', pattern='^v[0-9]+(\\\\.[0-9]+){0,2}$')] = 'v1'
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEDLiteConfig","title":"OPTIMADEDLiteConfig
","text":" Bases: OPTIMADEConfig
OPTIMADE configuration when using the DLite-specific strategies.
Source code inoteapi_optimade/models/config.py
class OPTIMADEDLiteConfig(OPTIMADEConfig):\n \"\"\"OPTIMADE configuration when using the DLite-specific strategies.\"\"\"\n\n # OTEAPI-specific attributes\n mediaType: Annotated[\n Optional[Literal[\"application/vnd.optimade+dlite\"]],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=\"The registered strategy name for OPTIMADEDLiteParseStrategy.\",\n ),\n ] = None # type: ignore[assignment]\n\n # Dlite specific attributes\n collection_id: Annotated[\n Optional[str],\n Field(description=\"A reference to a DLite Collection.\"),\n ] = None\n
"},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEDLiteConfig.collection_id","title":"collection_id: Annotated[Optional[str], Field(description='A reference to a DLite Collection.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/config/#oteapi_optimade.models.config.OPTIMADEDLiteConfig.mediaType","title":"mediaType: Annotated[Optional[Literal['application/vnd.optimade+dlite']], BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x), Field(description='The registered strategy name for OPTIMADEDLiteParseStrategy.')] = None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/","title":"custom_types","text":"Custom \"pydantic\" types used in OTEAPI-OPTIMADE.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.LOGGER","title":"LOGGER = logging.getLogger(__name__)
module-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts","title":"OPTIMADEParts
","text":" Bases: TypedDict
Similar to pydantic.networks.Parts
.
oteapi_optimade/models/custom_types.py
class OPTIMADEParts(TypedDict, total=False):\n \"\"\"Similar to `pydantic.networks.Parts`.\"\"\"\n\n base_url: str\n version: str | None\n endpoint: str | None\n query: str | None\n
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts.base_url","title":"base_url: str
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts.endpoint","title":"endpoint: str | None
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts.query","title":"query: str | None
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEParts.version","title":"version: str | None
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl","title":"OPTIMADEUrl
","text":" Bases: str
A deconstructed OPTIMADE URL.
An OPTIMADE URL is made up in the following way:
<BASE URL>[/<VERSION>]/<ENDPOINT>?[<QUERY PARAMETERS>]\n
Where parts in square brackets ([]
) are optional.
oteapi_optimade/models/custom_types.py
class OPTIMADEUrl(str):\n \"\"\"A deconstructed OPTIMADE URL.\n\n An OPTIMADE URL is made up in the following way:\n\n <BASE URL>[/<VERSION>]/<ENDPOINT>?[<QUERY PARAMETERS>]\n\n Where parts in square brackets (`[]`) are optional.\n \"\"\"\n\n # https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers\n max_length = 2083\n allowed_schemes: ClassVar[list[str]] = [\"http\", \"https\"]\n host_required = True\n\n @no_type_check\n def __new__(cls, url: str | None = None, **kwargs) -> object:\n return str.__new__(\n cls,\n url if url else cls._build(**kwargs),\n )\n\n def __init__(\n self,\n url: str,\n *,\n base_url: Optional[str] = None,\n version: Optional[str] = None,\n endpoint: Optional[str] = None,\n query: Optional[str] = None,\n ) -> None:\n str.__init__(url)\n\n # Parse as URL\n try:\n pydantic_url = AnyHttpUrl(url)\n except ValidationError:\n try:\n pydantic_url = AnyHttpUrl(\n self._build(\n base_url=base_url or \"\",\n version=version,\n endpoint=endpoint,\n query=query,\n )\n )\n except ValidationError:\n pydantic_url = None\n\n # Build OPTIMADE URL parts\n optimade_parts: Union[OPTIMADEParts, dict[str, Any]] = {}\n if pydantic_url:\n optimade_parts = self._build_optimade_parts(pydantic_url)\n\n self._base_url = base_url or optimade_parts.get(\"base_url\", None)\n self._version = version or optimade_parts.get(\"version\", None)\n self._endpoint = endpoint or optimade_parts.get(\"endpoint\", None)\n self._query = query or optimade_parts.get(\"query\", None)\n self._scheme = self._base_url.split(\"://\")[0] if self._base_url else None\n\n def __str__(self) -> str:\n return self._build(\n base_url=self.base_url,\n version=self.version,\n endpoint=self.endpoint,\n query=self.query,\n )\n\n def __repr__(self) -> str:\n extra = \", \".join(\n f\"{n}={getattr(self, n)!r}\"\n for n in (\"scheme\", \"base_url\", \"version\", \"endpoint\", \"query\")\n if getattr(self, n) is not None\n )\n return f\"{self.__class__.__name__}({super().__repr__()}, {extra})\"\n\n @staticmethod\n def _build(\n *,\n base_url: str,\n version: Optional[str] = None,\n endpoint: Optional[str] = None,\n query: Optional[str] = None,\n ) -> str:\n \"\"\"Build complete OPTIMADE URL from URL parts.\"\"\"\n url = base_url.rstrip(\"/\")\n if version:\n url += f\"/{version}\"\n if endpoint:\n url += f\"/{endpoint}\"\n if query:\n url += f\"?{query}\"\n return url\n\n @property\n def scheme(self) -> str:\n \"\"\"The scheme of the OPTIMADE URL.\"\"\"\n if self._scheme is None:\n error_message = \"OPTIMADE URL has no scheme.\"\n raise ValueError(error_message)\n return self._scheme\n\n @property\n def base_url(self) -> str:\n \"\"\"The base URL of the OPTIMADE URL.\"\"\"\n if self._base_url is None:\n error_message = \"OPTIMADE URL has no base URL.\"\n raise ValueError(error_message)\n return self._base_url\n\n @property\n def version(self) -> Optional[str]:\n \"\"\"The version part of the OPTIMADE URL.\"\"\"\n return self._version\n\n @property\n def endpoint(self) -> Optional[str]:\n \"\"\"The endpoint part of the OPTIMADE URL.\"\"\"\n return self._endpoint\n\n @property\n def query(self) -> Optional[str]:\n \"\"\"The query part of the OPTIMADE URL.\"\"\"\n return self._query\n\n def response_model(self) -> Union[tuple[Success, Success], Success, None]:\n \"\"\"Return the endpoint's corresponding response model(s) (from OPT).\"\"\"\n if not self.endpoint or self.endpoint == \"versions\":\n return None\n\n return {\n \"info\": (InfoResponse, EntryInfoResponse),\n \"links\": LinksResponse,\n \"structures\": (StructureResponseMany, StructureResponseOne),\n \"references\": (ReferenceResponseMany, ReferenceResponseOne),\n \"calculations\": (EntryResponseMany, EntryResponseOne),\n }.get(self.endpoint, Success)\n\n # Pydantic-related methods\n @classmethod\n def __get_pydantic_core_schema__(\n cls, _source_type: Any, _handler: GetCoreSchemaHandler\n ) -> CoreSchema:\n \"\"\"Pydantic core schema for an OPTIMADE URL.\n\n Behaviour:\n - strings and `Url` instances will be parsed as `pydantic.AnyHttpUrl` instances\n and then converted to `OPTIMADEUrl` instances.\n - `OPTIMADEUrl` instances will be parsed as `OPTIMADEUrl` instances without any changes.\n - Nothing else will pass validation\n - Serialization will always return just a str.\n \"\"\"\n from_str_schema = core_schema.chain_schema(\n [\n core_schema.url_schema(\n max_length=cls.max_length,\n host_required=cls.host_required,\n allowed_schemes=cls.allowed_schemes,\n ),\n core_schema.no_info_plain_validator_function(\n cls._validate_from_str_or_url\n ),\n ],\n )\n\n from_url_schema = core_schema.chain_schema(\n [\n core_schema.is_instance_schema(Url),\n core_schema.no_info_plain_validator_function(\n cls._validate_from_str_or_url\n ),\n ],\n )\n\n return core_schema.json_or_python_schema(\n json_schema=from_str_schema,\n python_schema=core_schema.union_schema(\n [\n core_schema.is_instance_schema(cls),\n from_url_schema,\n from_str_schema,\n ],\n ),\n serialization=core_schema.plain_serializer_function_ser_schema(str),\n )\n\n @classmethod\n def __get_pydantic_json_schema__(\n cls, _core_schema: CoreSchema, handler: GetJsonSchemaHandler\n ) -> JsonSchemaValue:\n # Use the same schema that would be used for an AnyHttpUrl\n return handler(\n core_schema.url_schema(\n max_length=cls.max_length,\n host_required=cls.host_required,\n allowed_schemes=cls.allowed_schemes,\n )\n )\n\n @classmethod\n def _validate_from_str_or_url(cls, value: Union[Url, str]) -> OPTIMADEUrl:\n \"\"\"Pydantic validation of an OPTIMADE URL.\"\"\"\n # Parse as URL\n url = AnyHttpUrl(str(value))\n\n # Build OPTIMADE URL parts\n optimade_parts = cls._build_optimade_parts(url)\n\n return cls( # type: ignore[no-any-return]\n None,\n base_url=optimade_parts[\"base_url\"],\n version=optimade_parts[\"version\"],\n endpoint=optimade_parts[\"endpoint\"],\n query=optimade_parts[\"query\"],\n )\n\n @classmethod\n def _build_optimade_parts(cls, url: AnyHttpUrl) -> OPTIMADEParts:\n \"\"\"Convert URL parts to equivalent OPTIMADE URL parts.\"\"\"\n base_url = f\"{url.scheme}://\"\n\n if url.username:\n base_url += url.username\n\n if url.password:\n base_url += f\":{url.password}\"\n\n if url.username and url.password:\n base_url += \"@\"\n\n # This check is done to satisfy type checker.\n # Since the url has been parsed as a `AnyHttpUrl`, it must always have a host.\n if url.host is None:\n error_message = \"Could not parse given string as a URL.\"\n raise ValueError(error_message)\n\n base_url += url.host\n\n # Hide port if it's a standard HTTP (80) or HTTPS (443) port.\n if url.port and url.port not in (80, 443):\n base_url += f\":{url.port}\"\n\n if url.path:\n base_url += url.path\n\n base_url_match = _OPTIMADE_BASE_URL_REGEX.fullmatch(base_url)\n LOGGER.debug(\n \"OPTIMADE base URL regex match groups: %s\",\n base_url_match.groupdict() if base_url_match else base_url_match,\n )\n if base_url_match is None:\n error_message = \"Could not match given string with OPTIMADE base URL regex.\"\n raise ValueError(error_message)\n\n endpoint_match = _OPTIMADE_ENDPOINT_REGEX.findall(\n base_url_match.group(\"path\") if base_url_match.group(\"path\") else \"\"\n )\n LOGGER.debug(\"OPTIMADE endpoint regex matches: %s\", endpoint_match)\n for path_version, path_endpoint in endpoint_match: # noqa: B007\n if path_endpoint:\n break\n else:\n LOGGER.debug(\"Could not match given string with OPTIMADE endpoint regex.\")\n path_version, path_endpoint = \"\", \"\"\n\n base_url = base_url_match.group(\"base_url\")\n if path_version:\n base_url = base_url[: -(len(path_version) + len(path_endpoint) + 2)]\n elif path_endpoint:\n base_url = base_url[: -(len(path_endpoint) + 1)]\n\n optimade_parts = {\n \"base_url\": base_url.rstrip(\"/\"),\n \"version\": path_version or None,\n \"endpoint\": path_endpoint or None,\n \"query\": url.query,\n }\n return cast(\"OPTIMADEParts\", optimade_parts)\n
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.allowed_schemes","title":"allowed_schemes: list[str] = ['http', 'https']
class-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.base_url","title":"base_url: str
property
","text":"The base URL of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.endpoint","title":"endpoint: Optional[str]
property
","text":"The endpoint part of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.host_required","title":"host_required = True
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.max_length","title":"max_length = 2083
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.query","title":"query: Optional[str]
property
","text":"The query part of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.scheme","title":"scheme: str
property
","text":"The scheme of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.version","title":"version: Optional[str]
property
","text":"The version part of the OPTIMADE URL.
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.__init__","title":"__init__(url, *, base_url=None, version=None, endpoint=None, query=None)
","text":"Source code in oteapi_optimade/models/custom_types.py
def __init__(\n self,\n url: str,\n *,\n base_url: Optional[str] = None,\n version: Optional[str] = None,\n endpoint: Optional[str] = None,\n query: Optional[str] = None,\n) -> None:\n str.__init__(url)\n\n # Parse as URL\n try:\n pydantic_url = AnyHttpUrl(url)\n except ValidationError:\n try:\n pydantic_url = AnyHttpUrl(\n self._build(\n base_url=base_url or \"\",\n version=version,\n endpoint=endpoint,\n query=query,\n )\n )\n except ValidationError:\n pydantic_url = None\n\n # Build OPTIMADE URL parts\n optimade_parts: Union[OPTIMADEParts, dict[str, Any]] = {}\n if pydantic_url:\n optimade_parts = self._build_optimade_parts(pydantic_url)\n\n self._base_url = base_url or optimade_parts.get(\"base_url\", None)\n self._version = version or optimade_parts.get(\"version\", None)\n self._endpoint = endpoint or optimade_parts.get(\"endpoint\", None)\n self._query = query or optimade_parts.get(\"query\", None)\n self._scheme = self._base_url.split(\"://\")[0] if self._base_url else None\n
"},{"location":"api_reference/models/custom_types/#oteapi_optimade.models.custom_types.OPTIMADEUrl.response_model","title":"response_model()
","text":"Return the endpoint's corresponding response model(s) (from OPT).
Source code inoteapi_optimade/models/custom_types.py
def response_model(self) -> Union[tuple[Success, Success], Success, None]:\n \"\"\"Return the endpoint's corresponding response model(s) (from OPT).\"\"\"\n if not self.endpoint or self.endpoint == \"versions\":\n return None\n\n return {\n \"info\": (InfoResponse, EntryInfoResponse),\n \"links\": LinksResponse,\n \"structures\": (StructureResponseMany, StructureResponseOne),\n \"references\": (ReferenceResponseMany, ReferenceResponseOne),\n \"calculations\": (EntryResponseMany, EntryResponseOne),\n }.get(self.endpoint, Success)\n
"},{"location":"api_reference/models/query/","title":"query","text":"Data models related to OPTIMADE queries.
"},{"location":"api_reference/models/query/#oteapi_optimade.models.query.QUERY_PARAMETERS","title":"QUERY_PARAMETERS = {'annotations': {name: FieldInfo.from_annotation(parameter.annotation)for (name, parameter) in inspect.signature(EntryListingQueryParams).parameters.items()}, 'defaults': EntryListingQueryParams()}
module-attribute
","text":"Entry listing URL query parameters from the optimade
package (EntryListingQueryParams
).
OPTIMADEQueryParameters
","text":" Bases: BaseModel
Common OPTIMADE entry listing endpoint query parameters.
Source code inoteapi_optimade/models/query.py
class OPTIMADEQueryParameters(BaseModel, validate_assignment=True):\n \"\"\"Common OPTIMADE entry listing endpoint query parameters.\"\"\"\n\n filter: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"filter\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].filter or None\n )\n response_format: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"response_format\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].response_format or None\n )\n email_address: Annotated[\n Optional[EmailStr],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"email_address\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].email_address or None\n )\n response_fields: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"response_fields\"].description,\n pattern=QUERY_PARAMETERS[\"annotations\"][\"response_fields\"]\n .metadata[0]\n .pattern,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].response_fields or None\n )\n sort: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"sort\"].description,\n pattern=QUERY_PARAMETERS[\"annotations\"][\"sort\"].metadata[0].pattern,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].sort or None\n )\n page_limit: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_limit\"].description,\n ge=QUERY_PARAMETERS[\"annotations\"][\"page_limit\"].metadata[0].ge,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_limit or None\n )\n page_offset: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_offset\"].description,\n ge=QUERY_PARAMETERS[\"annotations\"][\"page_offset\"].metadata[0].ge,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_offset or None\n )\n page_number: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_number\"].description,\n # ge=QUERY_PARAMETERS[\"annotations\"][\"page_number\"].metadata[0].ge,\n # This constraint is only 'RECOMMENDED' in the specification, so should not\n # be included here or in the OpenAPI schema.\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_number or None\n )\n page_cursor: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_cursor\"].description,\n ge=QUERY_PARAMETERS[\"annotations\"][\"page_cursor\"].metadata[0].ge,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_cursor or None\n )\n page_above: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_above\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_above or None\n )\n page_below: Annotated[\n Optional[int],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"page_below\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].page_below or None\n )\n include: Annotated[\n Optional[str],\n Field(\n description=QUERY_PARAMETERS[\"annotations\"][\"include\"].description,\n ),\n ] = (\n QUERY_PARAMETERS[\"defaults\"].include or None\n )\n # api_hint is not yet initialized in `EntryListingQueryParams`.\n # These values are copied verbatim from `optimade==0.16.10`.\n api_hint: Annotated[\n Optional[str],\n Field(\n description=(\n \"If the client provides the parameter, the value SHOULD have the format \"\n \"`vMAJOR` or `vMAJOR.MINOR`, where MAJOR is a major version and MINOR is a\"\n \" minor version of the API. For example, if a client appends \"\n \"`api_hint=v1.0` to the query string, the hint provided is for major \"\n \"version 1 and minor version 0.\"\n ),\n pattern=r\"(v[0-9]+(\\.[0-9]+)?)?\",\n ),\n ] = \"\"\n\n def generate_query_string(self) -> str:\n \"\"\"Generate a valid URL query string based on the set fields.\"\"\"\n res = {}\n for field, value in self.model_dump().items():\n if value or field in self.model_fields_set:\n res[field] = unquote(value) if isinstance(value, str) else value\n return urlencode(res, quote_via=quote)\n
"},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.api_hint","title":"api_hint: Annotated[Optional[str], Field(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='(v[0-9]+(\\\\.[0-9]+)?)?')] = ''
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.email_address","title":"email_address: Annotated[Optional[EmailStr], Field(description=QUERY_PARAMETERS['annotations']['email_address'].description)] = QUERY_PARAMETERS['defaults'].email_address or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.filter","title":"filter: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['filter'].description)] = QUERY_PARAMETERS['defaults'].filter or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.include","title":"include: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['include'].description)] = QUERY_PARAMETERS['defaults'].include or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_above","title":"page_above: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_above'].description)] = QUERY_PARAMETERS['defaults'].page_above or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_below","title":"page_below: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_below'].description)] = QUERY_PARAMETERS['defaults'].page_below or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_cursor","title":"page_cursor: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_cursor'].description, ge=QUERY_PARAMETERS['annotations']['page_cursor'].metadata[0].ge)] = QUERY_PARAMETERS['defaults'].page_cursor or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_limit","title":"page_limit: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_limit'].description, ge=QUERY_PARAMETERS['annotations']['page_limit'].metadata[0].ge)] = QUERY_PARAMETERS['defaults'].page_limit or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_number","title":"page_number: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_number'].description)] = QUERY_PARAMETERS['defaults'].page_number or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.page_offset","title":"page_offset: Annotated[Optional[int], Field(description=QUERY_PARAMETERS['annotations']['page_offset'].description, ge=QUERY_PARAMETERS['annotations']['page_offset'].metadata[0].ge)] = QUERY_PARAMETERS['defaults'].page_offset or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.response_fields","title":"response_fields: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['response_fields'].description, pattern=QUERY_PARAMETERS['annotations']['response_fields'].metadata[0].pattern)] = QUERY_PARAMETERS['defaults'].response_fields or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.response_format","title":"response_format: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['response_format'].description)] = QUERY_PARAMETERS['defaults'].response_format or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.sort","title":"sort: Annotated[Optional[str], Field(description=QUERY_PARAMETERS['annotations']['sort'].description, pattern=QUERY_PARAMETERS['annotations']['sort'].metadata[0].pattern)] = QUERY_PARAMETERS['defaults'].sort or None
class-attribute
instance-attribute
","text":""},{"location":"api_reference/models/query/#oteapi_optimade.models.query.OPTIMADEQueryParameters.generate_query_string","title":"generate_query_string()
","text":"Generate a valid URL query string based on the set fields.
Source code inoteapi_optimade/models/query.py
def generate_query_string(self) -> str:\n \"\"\"Generate a valid URL query string based on the set fields.\"\"\"\n res = {}\n for field, value in self.model_dump().items():\n if value or field in self.model_fields_set:\n res[field] = unquote(value) if isinstance(value, str) else value\n return urlencode(res, quote_via=quote)\n
"},{"location":"api_reference/models/strategies/filter/","title":"filter","text":"Models specific to the filter strategy.
"},{"location":"api_reference/models/strategies/filter/#oteapi_optimade.models.strategies.filter.OPTIMADEFilterConfig","title":"OPTIMADEFilterConfig
","text":" Bases: FilterConfig
OPTIMADE-specific filter strategy config.
NoteThe condition
parameter is not taken into account.
oteapi_optimade/models/strategies/filter.py
class OPTIMADEFilterConfig(FilterConfig):\n \"\"\"OPTIMADE-specific filter strategy config.\n\n Note:\n The `condition` parameter is not taken into account.\n\n \"\"\"\n\n filterType: Annotated[\n Literal[\"optimade\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=\"The registered strategy name for OPTIMADEFilterStrategy.\",\n ),\n ]\n query: Annotated[\n Optional[str],\n Field(\n description=(\n \"The `filter` OPTIMADE query parameter value. This parameter value can \"\n \"also be provided through the [`configuration.query_parameters.filter`]\"\n \"[oteapi_optimade.models.query.OPTIMADEQueryParameters.filter] parameter. \"\n \"Note, this value takes precedence over [`configuration`][oteapi_optimade.\"\n \"models.strategies.filter.OPTIMADEFilterConfig.configuration] values.\"\n ),\n ),\n ] = None\n limit: Annotated[\n Optional[int],\n Field(\n description=(\n \"The `page_limit` OPTIMADE query parameter value. This parameter value can\"\n \" also be provided through the [`configuration.query_parameters.\"\n \"page_limit`][oteapi_optimade.models.query.OPTIMADEQueryParameters.\"\n \"page_limit] parameter. Note, this value takes precedence over \"\n \"[`configuration`][oteapi_optimade.models.strategies.filter.\"\n \"OPTIMADEFilterConfig.configuration] values.\"\n ),\n ),\n ] = None\n configuration: Annotated[\n OPTIMADEConfig,\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = OPTIMADEConfig()\n
"},{"location":"api_reference/models/strategies/filter/#oteapi_optimade.models.strategies.filter.OPTIMADEFilterResult","title":"OPTIMADEFilterResult
","text":" Bases: AttrDict
OPTIMADE session for the filter strategy.
Source code inoteapi_optimade/models/strategies/filter.py
class OPTIMADEFilterResult(AttrDict):\n \"\"\"OPTIMADE session for the filter strategy.\"\"\"\n\n model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)\n\n optimade_config: Annotated[\n Optional[OPTIMADEConfig],\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = None\n optimade_response_model: Annotated[\n Optional[tuple[str, str]],\n Field(\n description=(\n \"An OPTIMADE Python tools (OPT) pydantic successful response model. \"\n \"More specifically, a tuple of the module and name of the pydantic \"\n \"model.\"\n ),\n ),\n ] = None\n optimade_response: Annotated[\n Optional[dict[str, Any]],\n Field(\n description=\"An OPTIMADE response as a Python dictionary.\",\n ),\n ] = None\n
"},{"location":"api_reference/models/strategies/parse/","title":"parse","text":"Models specific to the parse strategy.
"},{"location":"api_reference/models/strategies/parse/#oteapi_optimade.models.strategies.parse.SUPPORTED_ENTITIES","title":"SUPPORTED_ENTITIES = [re.compile(_) for _ in ['http://onto-ns.com/meta/[0-9]+(\\\\.[0-9]+)?(\\\\.[0-9]+)?/OPTIMADEStructure', 'http://onto-ns\\\\.com/meta/[0-9]+(\\\\.[0-9]+)?(\\\\.[0-9]+)?/OPTIMADEStructureResource']]
module-attribute
","text":"Supported entities for the OPTIMADE parse strategy.
The default entity is \"OPTIMADEStructure\". This means, if no entity is provided, the default entity will be used.
"},{"location":"api_reference/models/strategies/parse/#oteapi_optimade.models.strategies.parse.OPTIMADEDLiteParseConfig","title":"OPTIMADEDLiteParseConfig
","text":" Bases: OPTIMADEParseConfig
OPTIMADE-specific parse strategy config when using DLite.
Source code inoteapi_optimade/models/strategies/parse.py
class OPTIMADEDLiteParseConfig(OPTIMADEParseConfig):\n \"\"\"OPTIMADE-specific parse strategy config when using DLite.\"\"\"\n\n parserType: Annotated[ # type: ignore[assignment]\n Literal[\"parser/optimade/dlite\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(description=ParserConfig.model_fields[\"parserType\"].description),\n ]\n\n configuration: Annotated[ # type: ignore[assignment]\n OPTIMADEDLiteConfig,\n Field(\n description=(\n \"OPTIMADE configuration when using the DLite-specific strategies. \"\n \"Contains relevant information necessary to perform OPTIMADE queries.\"\n ),\n ),\n ] = OPTIMADEDLiteConfig()\n
"},{"location":"api_reference/models/strategies/parse/#oteapi_optimade.models.strategies.parse.OPTIMADEParseConfig","title":"OPTIMADEParseConfig
","text":" Bases: ParserConfig
OPTIMADE-specific parse strategy config.
Source code inoteapi_optimade/models/strategies/parse.py
class OPTIMADEParseConfig(ParserConfig):\n \"\"\"OPTIMADE-specific parse strategy config.\"\"\"\n\n parserType: Annotated[\n Literal[\"parser/optimade\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=ParserConfig.model_fields[\"parserType\"].description,\n ),\n ]\n\n configuration: Annotated[\n OPTIMADEConfig,\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = OPTIMADEConfig()\n\n @field_validator(\"entity\", mode=\"after\")\n def _validate_entity(cls, value: AnyHttpUrl) -> AnyHttpUrl:\n \"\"\"Validate entity.\"\"\"\n test_value = str(value).rstrip(\"/\")\n\n for entity_pattern in SUPPORTED_ENTITIES:\n if entity_pattern.fullmatch(test_value):\n return value\n\n raise ValueError(\n f\"Unsupported entity: {value}. Supported entities: {SUPPORTED_ENTITIES}\"\n )\n
"},{"location":"api_reference/models/strategies/parse/#oteapi_optimade.models.strategies.parse.OPTIMADEParseResult","title":"OPTIMADEParseResult
","text":" Bases: AttrDict
OPTIMADE parse strategy result.
Source code inoteapi_optimade/models/strategies/parse.py
class OPTIMADEParseResult(AttrDict):\n \"\"\"OPTIMADE parse strategy result.\"\"\"\n\n model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)\n\n optimade_config: Annotated[\n Optional[OPTIMADEConfig],\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = None\n optimade_response_model: Annotated[\n Optional[tuple[str, str]],\n Field(\n description=(\n \"An OPTIMADE Python tools (OPT) pydantic successful response model. \"\n \"More specifically, a tuple of the module and name of the pydantic \"\n \"model.\"\n ),\n ),\n ] = None\n optimade_response: Annotated[\n Optional[dict[str, Any]],\n Field(\n description=\"An OPTIMADE response as a Python dictionary.\",\n ),\n ] = None\n
"},{"location":"api_reference/models/strategies/resource/","title":"resource","text":"Models specific to the resource strategy.
"},{"location":"api_reference/models/strategies/resource/#oteapi_optimade.models.strategies.resource.OPTIMADEResourceConfig","title":"OPTIMADEResourceConfig
","text":" Bases: ResourceConfig
OPTIMADE-specific resource strategy config.
Source code inoteapi_optimade/models/strategies/resource.py
class OPTIMADEResourceConfig(ResourceConfig):\n \"\"\"OPTIMADE-specific resource strategy config.\"\"\"\n\n resourceType: Annotated[\n # later OPTIMADE/references and more should be added and other resources\n Literal[\"optimade/structures\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(description=ResourceConfig.model_fields[\"resourceType\"].description),\n ]\n accessUrl: Annotated[\n OPTIMADEUrl,\n Field(description=\"Either a base OPTIMADE URL or a full OPTIMADE URL.\"),\n ]\n accessService: Annotated[\n Literal[\"optimade\", \"optimade+dlite\"],\n BeforeValidator(lambda x: x.lower() if isinstance(x, str) else x),\n Field(\n description=\"The registered strategy name for OPTIMADEResourceStrategy.\",\n ),\n ]\n configuration: Annotated[\n Union[OPTIMADEConfig | OPTIMADEDLiteConfig],\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = OPTIMADEConfig()\n
"},{"location":"api_reference/models/strategies/resource/#oteapi_optimade.models.strategies.resource.OPTIMADEResourceResult","title":"OPTIMADEResourceResult
","text":" Bases: AttrDict
OPTIMADE session for the resource strategy.
Source code inoteapi_optimade/models/strategies/resource.py
class OPTIMADEResourceResult(AttrDict):\n \"\"\"OPTIMADE session for the resource strategy.\"\"\"\n\n model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)\n\n optimade_config: Annotated[\n Optional[OPTIMADEConfig],\n Field(\n description=(\n \"OPTIMADE configuration. Contains relevant information necessary to \"\n \"perform OPTIMADE queries.\"\n ),\n ),\n ] = None\n optimade_resources: Annotated[\n list[dict[str, Any]],\n Field(\n description=(\n \"List of OPTIMADE resources (structures, references, errors, ...) returned\"\n \" from the OPTIMADE request.\"\n ),\n ),\n ] = [] # noqa: RUF012\n optimade_resource_model: Annotated[\n str,\n Field(\n description=(\n \"Importable path to the resource model to be used to parse the OPTIMADE \"\n \"resources in `optimade_resource`. The importable path should be a fully \"\n \"importable path to a module separated by a colon (`:`) to then define the \"\n \"resource model class name. This means one can then do:\\n\\n```python\\n\"\n \"from PACKAGE.MODULE import RESOURCE_CLS\\n```\\nFrom the value \"\n \"`PACKAGE.MODULE:RESOURCE_CLS`\"\n ),\n pattern=(\n r\"^([a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)*\" # package.module\n r\":[a-zA-Z][a-zA-Z0-9_]*)?$\" # class\n ),\n ),\n ] = \"\"\n
"},{"location":"api_reference/strategies/filter/","title":"filter","text":"Demo filter strategy.
"},{"location":"api_reference/strategies/filter/#oteapi_optimade.strategies.filter.OPTIMADEFilterStrategy","title":"OPTIMADEFilterStrategy
","text":"Filter Strategy.
Implements strategies:
(\"filterType\", \"OPTIMADE\")
oteapi_optimade/strategies/filter.py
@dataclass\nclass OPTIMADEFilterStrategy:\n \"\"\"Filter Strategy.\n\n **Implements strategies**:\n\n - `(\"filterType\", \"OPTIMADE\")`\n\n \"\"\"\n\n filter_config: OPTIMADEFilterConfig\n\n def initialize(self) -> OPTIMADEFilterResult:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Configuration values, specifically URL query parameters, can be provided to the\n OPTIMADE resource strategy through this filter strategy.\n\n Workflow:\n\n 1. Compile received information.\n 2. Update session with compiled information.\n\n Returns:\n An update model of key/value-pairs to be stored in the\n session-specific context from services.\n\n \"\"\"\n if self.filter_config.configuration.optimade_config:\n self.filter_config.configuration.update(\n self.filter_config.configuration.optimade_config.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n )\n )\n\n optimade_config = self.filter_config.configuration.model_copy()\n\n if not optimade_config.query_parameters:\n optimade_config.query_parameters = OPTIMADEQueryParameters()\n\n if self.filter_config.query:\n LOGGER.debug(\"Setting filter from query.\")\n optimade_config.query_parameters.filter = self.filter_config.query\n\n if self.filter_config.limit:\n LOGGER.debug(\"Setting page_limit from limit.\")\n optimade_config.query_parameters.page_limit = self.filter_config.limit\n\n return OPTIMADEFilterResult(\n optimade_config=optimade_config.model_dump(\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n exclude_unset=True,\n exclude_defaults=True,\n )\n )\n\n def get(self) -> AttrDict:\n \"\"\"Execute the strategy.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the\n session-specific context from services.\n\n \"\"\"\n return AttrDict()\n
"},{"location":"api_reference/strategies/filter/#oteapi_optimade.strategies.filter.OPTIMADEFilterStrategy.get","title":"get()
","text":"Execute the strategy.
This method will be called through the strategy-specific endpoint of the OTE-API Services.
Returns:
Type DescriptionAttrDict
An update model of key/value-pairs to be stored in the
AttrDict
session-specific context from services.
Source code inoteapi_optimade/strategies/filter.py
def get(self) -> AttrDict:\n \"\"\"Execute the strategy.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the\n session-specific context from services.\n\n \"\"\"\n return AttrDict()\n
"},{"location":"api_reference/strategies/filter/#oteapi_optimade.strategies.filter.OPTIMADEFilterStrategy.initialize","title":"initialize()
","text":"Initialize strategy.
This method will be called through the /initialize
endpoint of the OTE-API Services.
Configuration values, specifically URL query parameters, can be provided to the OPTIMADE resource strategy through this filter strategy.
Workflow:
Returns:
Type DescriptionOPTIMADEFilterResult
An update model of key/value-pairs to be stored in the
OPTIMADEFilterResult
session-specific context from services.
Source code inoteapi_optimade/strategies/filter.py
def initialize(self) -> OPTIMADEFilterResult:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Configuration values, specifically URL query parameters, can be provided to the\n OPTIMADE resource strategy through this filter strategy.\n\n Workflow:\n\n 1. Compile received information.\n 2. Update session with compiled information.\n\n Returns:\n An update model of key/value-pairs to be stored in the\n session-specific context from services.\n\n \"\"\"\n if self.filter_config.configuration.optimade_config:\n self.filter_config.configuration.update(\n self.filter_config.configuration.optimade_config.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n )\n )\n\n optimade_config = self.filter_config.configuration.model_copy()\n\n if not optimade_config.query_parameters:\n optimade_config.query_parameters = OPTIMADEQueryParameters()\n\n if self.filter_config.query:\n LOGGER.debug(\"Setting filter from query.\")\n optimade_config.query_parameters.filter = self.filter_config.query\n\n if self.filter_config.limit:\n LOGGER.debug(\"Setting page_limit from limit.\")\n optimade_config.query_parameters.page_limit = self.filter_config.limit\n\n return OPTIMADEFilterResult(\n optimade_config=optimade_config.model_dump(\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n exclude_unset=True,\n exclude_defaults=True,\n )\n )\n
"},{"location":"api_reference/strategies/parse/","title":"parse","text":"Demo strategy class for text/json.
"},{"location":"api_reference/strategies/parse/#oteapi_optimade.strategies.parse.OPTIMADEParseStrategy","title":"OPTIMADEParseStrategy
","text":"Parse strategy for JSON.
Implements strategies:
(\"parserType\", \"parser/OPTIMADE\")
oteapi_optimade/strategies/parse.py
@dataclass\nclass OPTIMADEParseStrategy:\n \"\"\"Parse strategy for JSON.\n\n **Implements strategies**:\n\n - `(\"parserType\", \"parser/OPTIMADE\")`\n\n \"\"\"\n\n parse_config: OPTIMADEParseConfig\n\n def initialize(self) -> AttrDict:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n return AttrDict()\n\n def get(self) -> OPTIMADEParseResult:\n \"\"\"Request and parse an OPTIMADE response using OPT.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `downloadUrl`.\n\n Workflow:\n\n 1. Request OPTIMADE response.\n 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if (\n self.parse_config.configuration.downloadUrl is None\n or self.parse_config.configuration.mediaType is None\n ):\n raise OPTIMADEParseError(\n \"Missing downloadUrl or mediaType in configuration.\"\n )\n\n cache = DataCache(self.parse_config.configuration.datacache_config)\n if self.parse_config.configuration.downloadUrl in cache:\n response: dict[str, Any] = cache.get(\n self.parse_config.configuration.downloadUrl\n )\n elif (\n self.parse_config.configuration.datacache_config.accessKey\n and self.parse_config.configuration.datacache_config.accessKey in cache\n ):\n response = cache.get(\n self.parse_config.configuration.datacache_config.accessKey\n )\n else:\n download_config = self.parse_config.configuration.model_copy()\n download_output = create_strategy(\"download\", download_config).get()\n response = {\"json\": json.loads(cache.get(download_output.pop(\"key\")))}\n\n if (\n not response.get(\"ok\", True)\n or (\n response.get(\"status_code\", 200) < 200\n or response.get(\"status_code\", 200) >= 300\n )\n or \"errors\" in response.get(\"json\", {})\n ):\n # Error response\n try:\n response_object = ErrorResponse(**response.get(\"json\", {}))\n except ValidationError as exc:\n error_message = \"Could not validate an error response.\"\n LOGGER.error(\n \"%s\\nValidationError: \" \"%s\\nresponse=%r\",\n error_message,\n exc,\n response,\n )\n raise OPTIMADEParseError(error_message) from exc\n else:\n # Successful response\n response_model = (\n self.parse_config.configuration.downloadUrl.response_model()\n )\n LOGGER.debug(\"response_model=%r\", response_model)\n if response_model:\n if not isinstance(response_model, tuple):\n response_model = (response_model,)\n\n for model_cls in response_model:\n try:\n response_object = model_cls(**response.get(\"json\", {}))\n except ValidationError:\n pass\n else:\n break\n else:\n error_message = \"Could not validate for an expected response model.\"\n LOGGER.error(\n \"%s\\nURL=%r\\n\" \"response_models=%r\\nresponse=%s\",\n error_message,\n self.parse_config.configuration.downloadUrl,\n response_model,\n response,\n )\n raise OPTIMADEParseError(error_message)\n else:\n # No \"endpoint\" or unknown\n LOGGER.debug(\"No response_model, using Success response model.\")\n try:\n response_object = Success(**response.get(\"json\", {}))\n except ValidationError as exc:\n error_message = \"Unknown or unparseable endpoint.\"\n LOGGER.error(\n \"%s\\nValidatonError: %s\\n\"\n \"URL=%r\\nendpoint=%r\\nresponse_model=%r\\nresponse=%s\",\n error_message,\n exc,\n self.parse_config.configuration.downloadUrl,\n self.parse_config.configuration.downloadUrl.endpoint,\n response_model,\n response,\n )\n raise OPTIMADEParseError(error_message) from exc\n\n result = OPTIMADEParseResult(\n model_config=self.parse_config.configuration.model_dump(),\n optimade_response_model=(\n response_object.__class__.__module__,\n response_object.__class__.__name__,\n ),\n optimade_response=response_object.model_dump(exclude_unset=True),\n )\n\n if (\n self.parse_config.configuration.optimade_config\n and self.parse_config.configuration.optimade_config.query_parameters\n ):\n result = result.model_copy(\n update={\n \"optimade_config\": self.parse_config.configuration.optimade_config.model_copy(\n update={\n \"query_parameters\": self.parse_config.configuration.optimade_config.query_parameters.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n )\n }\n )\n }\n )\n\n return result\n
"},{"location":"api_reference/strategies/parse/#oteapi_optimade.strategies.parse.OPTIMADEParseStrategy.get","title":"get()
","text":"Request and parse an OPTIMADE response using OPT.
This method will be called through the strategy-specific endpoint of the OTE-API Services.
Configuration values provided in resource_config.configuration
take precedence over the derived values from downloadUrl
.
Workflow:
Returns:
Type DescriptionOPTIMADEParseResult
An update model of key/value-pairs to be stored in the session-specific
OPTIMADEParseResult
context from services.
Source code inoteapi_optimade/strategies/parse.py
def get(self) -> OPTIMADEParseResult:\n \"\"\"Request and parse an OPTIMADE response using OPT.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `downloadUrl`.\n\n Workflow:\n\n 1. Request OPTIMADE response.\n 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if (\n self.parse_config.configuration.downloadUrl is None\n or self.parse_config.configuration.mediaType is None\n ):\n raise OPTIMADEParseError(\n \"Missing downloadUrl or mediaType in configuration.\"\n )\n\n cache = DataCache(self.parse_config.configuration.datacache_config)\n if self.parse_config.configuration.downloadUrl in cache:\n response: dict[str, Any] = cache.get(\n self.parse_config.configuration.downloadUrl\n )\n elif (\n self.parse_config.configuration.datacache_config.accessKey\n and self.parse_config.configuration.datacache_config.accessKey in cache\n ):\n response = cache.get(\n self.parse_config.configuration.datacache_config.accessKey\n )\n else:\n download_config = self.parse_config.configuration.model_copy()\n download_output = create_strategy(\"download\", download_config).get()\n response = {\"json\": json.loads(cache.get(download_output.pop(\"key\")))}\n\n if (\n not response.get(\"ok\", True)\n or (\n response.get(\"status_code\", 200) < 200\n or response.get(\"status_code\", 200) >= 300\n )\n or \"errors\" in response.get(\"json\", {})\n ):\n # Error response\n try:\n response_object = ErrorResponse(**response.get(\"json\", {}))\n except ValidationError as exc:\n error_message = \"Could not validate an error response.\"\n LOGGER.error(\n \"%s\\nValidationError: \" \"%s\\nresponse=%r\",\n error_message,\n exc,\n response,\n )\n raise OPTIMADEParseError(error_message) from exc\n else:\n # Successful response\n response_model = (\n self.parse_config.configuration.downloadUrl.response_model()\n )\n LOGGER.debug(\"response_model=%r\", response_model)\n if response_model:\n if not isinstance(response_model, tuple):\n response_model = (response_model,)\n\n for model_cls in response_model:\n try:\n response_object = model_cls(**response.get(\"json\", {}))\n except ValidationError:\n pass\n else:\n break\n else:\n error_message = \"Could not validate for an expected response model.\"\n LOGGER.error(\n \"%s\\nURL=%r\\n\" \"response_models=%r\\nresponse=%s\",\n error_message,\n self.parse_config.configuration.downloadUrl,\n response_model,\n response,\n )\n raise OPTIMADEParseError(error_message)\n else:\n # No \"endpoint\" or unknown\n LOGGER.debug(\"No response_model, using Success response model.\")\n try:\n response_object = Success(**response.get(\"json\", {}))\n except ValidationError as exc:\n error_message = \"Unknown or unparseable endpoint.\"\n LOGGER.error(\n \"%s\\nValidatonError: %s\\n\"\n \"URL=%r\\nendpoint=%r\\nresponse_model=%r\\nresponse=%s\",\n error_message,\n exc,\n self.parse_config.configuration.downloadUrl,\n self.parse_config.configuration.downloadUrl.endpoint,\n response_model,\n response,\n )\n raise OPTIMADEParseError(error_message) from exc\n\n result = OPTIMADEParseResult(\n model_config=self.parse_config.configuration.model_dump(),\n optimade_response_model=(\n response_object.__class__.__module__,\n response_object.__class__.__name__,\n ),\n optimade_response=response_object.model_dump(exclude_unset=True),\n )\n\n if (\n self.parse_config.configuration.optimade_config\n and self.parse_config.configuration.optimade_config.query_parameters\n ):\n result = result.model_copy(\n update={\n \"optimade_config\": self.parse_config.configuration.optimade_config.model_copy(\n update={\n \"query_parameters\": self.parse_config.configuration.optimade_config.query_parameters.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n )\n }\n )\n }\n )\n\n return result\n
"},{"location":"api_reference/strategies/parse/#oteapi_optimade.strategies.parse.OPTIMADEParseStrategy.initialize","title":"initialize()
","text":"Initialize strategy.
This method will be called through the /initialize
endpoint of the OTE-API Services.
Returns:
Type DescriptionAttrDict
An update model of key/value-pairs to be stored in the session-specific
AttrDict
context from services.
Source code inoteapi_optimade/strategies/parse.py
def initialize(self) -> AttrDict:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n return AttrDict()\n
"},{"location":"api_reference/strategies/resource/","title":"resource","text":"OPTIMADE resource strategy.
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.OPTIMADEResourceStrategy","title":"OPTIMADEResourceStrategy
","text":"OPTIMADE Resource Strategy.
Implements strategies:
(\"accessService\", \"OPTIMADE\")
(\"accessService\", \"OPTIMADE+DLite\")
oteapi_optimade/strategies/resource.py
@dataclass\nclass OPTIMADEResourceStrategy:\n \"\"\"OPTIMADE Resource Strategy.\n\n **Implements strategies**:\n\n - `(\"accessService\", \"OPTIMADE\")`\n - `(\"accessService\", \"OPTIMADE+DLite\")`\n\n \"\"\"\n\n resource_config: OPTIMADEResourceConfig\n\n def initialize(self) -> AttrDict | DLiteSessionUpdate:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if use_dlite(\n self.resource_config.accessService,\n self.resource_config.configuration.use_dlite,\n ):\n collection_id = self.resource_config.configuration.get(\n \"collection_id\", None\n )\n return DLiteSessionUpdate(\n collection_id=get_collection(collection_id=collection_id).uuid\n )\n\n return AttrDict()\n\n def get(self) -> OPTIMADEResourceResult:\n \"\"\"Execute an OPTIMADE query to `accessUrl`.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `accessUrl`.\n\n Workflow:\n 1. Update configuration according to session.\n 2. Deconstruct `accessUrl` (done partly by\n `oteapi_optimade.models.custom_types.OPTIMADEUrl`).\n 3. Reconstruct the complete query URL.\n 4. Send query.\n 5. Store result in data cache.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if self.resource_config.configuration.optimade_config:\n self.resource_config.configuration.update(\n self.resource_config.configuration.optimade_config.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n )\n )\n\n optimade_endpoint = self.resource_config.accessUrl.endpoint or \"structures\"\n optimade_query = (\n self.resource_config.configuration.query_parameters\n or OPTIMADEQueryParameters()\n )\n LOGGER.debug(\"resource_config: %r\", self.resource_config)\n\n if self.resource_config.accessUrl.query:\n parsed_query = parse_qs(self.resource_config.accessUrl.query)\n for field, value in parsed_query.items():\n # Only use the latest defined value for any parameter\n if field not in optimade_query.model_fields_set:\n LOGGER.debug(\n \"Setting %r from accessUrl (value=%r)\", field, value[-1]\n )\n setattr(optimade_query, field, value[-1])\n\n LOGGER.debug(\"optimade_query after update: %r\", optimade_query)\n\n optimade_url = OPTIMADEUrl(\n f\"{self.resource_config.accessUrl.base_url}\"\n f\"/{self.resource_config.accessUrl.version or 'v1'}\"\n f\"/{optimade_endpoint}?{optimade_query.generate_query_string()}\"\n )\n LOGGER.debug(\"OPTIMADE URL to be requested: %s\", optimade_url)\n\n # Set cache access key to the full OPTIMADE URL.\n self.resource_config.configuration.datacache_config.accessKey = optimade_url\n\n # Perform query\n response = requests.get(\n optimade_url,\n allow_redirects=True,\n timeout=(3, 27), # timeout in seconds (connect, read)\n )\n\n if optimade_query.response_format and optimade_query.response_format != \"json\":\n error_message = (\n \"Can only handle JSON responses for now. Requested response format: \"\n f\"{optimade_query.response_format!r}\"\n )\n raise NotImplementedError(error_message)\n\n cache = DataCache(config=self.resource_config.configuration.datacache_config)\n cache.add(\n {\n \"status_code\": response.status_code,\n \"ok\": response.ok,\n \"json\": response.json(),\n }\n )\n\n parse_with_dlite = use_dlite(\n self.resource_config.accessService,\n self.resource_config.configuration.use_dlite,\n )\n\n parse_parserType = \"parser/OPTIMADE\"\n parse_mediaType = (\n \"application/vnd.\"\n f\"{self.resource_config.accessService.split('+', maxsplit=1)[0]}\"\n )\n if parse_with_dlite:\n parse_parserType += \"/DLite\"\n parse_mediaType += \"+DLite\"\n elif optimade_query.response_format:\n parse_mediaType += f\"+{optimade_query.response_format}\"\n\n parse_config: ParseConfigDict = {\n \"entity\": \"http://onto-ns.com/meta/1.0.1/OPTIMADEStructure\",\n \"parserType\": parse_parserType,\n \"configuration\": {\n \"datacache_config\": self.resource_config.configuration.datacache_config.model_copy(),\n \"downloadUrl\": str(optimade_url),\n \"mediaType\": parse_mediaType,\n \"optimade_config\": self.resource_config.configuration.model_dump(\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n exclude_unset=True,\n exclude_defaults=True,\n ),\n },\n }\n\n LOGGER.debug(\"parse_config: %r\", parse_config)\n\n parse_config[\"configuration\"].update(\n create_strategy(\"parse\", parse_config).initialize()\n )\n parse_result = create_strategy(\"parse\", parse_config).get()\n\n if not all(\n _ in parse_result for _ in (\"optimade_response\", \"optimade_response_model\")\n ):\n base_error_message = (\n \"Could not retrieve response from OPTIMADE parse strategy.\"\n )\n LOGGER.error(\n \"%s\\n\"\n \"optimade_response=%r\\n\"\n \"optimade_response_model=%r\\n\"\n \"session fields=%r\",\n base_error_message,\n parse_result.get(\"optimade_response\"),\n parse_result.get(\"optimade_response_model\"),\n list(parse_result.keys()),\n )\n raise OPTIMADEParseError(base_error_message)\n\n optimade_response_model_module, optimade_response_model_name = parse_result.pop(\n \"optimade_response_model\"\n )\n optimade_response_dict = parse_result.pop(\"optimade_response\")\n\n # Parse response using the provided model\n try:\n optimade_response_model: type[OPTIMADEResponse] = getattr(\n importlib.import_module(optimade_response_model_module),\n optimade_response_model_name,\n )\n optimade_response = optimade_response_model(**optimade_response_dict)\n except (ImportError, AttributeError) as exc:\n base_error_message = \"Could not import the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ImportError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n except ValidationError as exc:\n base_error_message = \"Could not validate the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ValidationError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n\n result = OPTIMADEResourceResult()\n\n if isinstance(optimade_response, ErrorResponse):\n optimade_resources = optimade_response.errors\n result.optimade_resource_model = f\"{OptimadeError.__module__}:OptimadeError\"\n elif isinstance(optimade_response, ReferenceResponseMany):\n optimade_resources = [\n (\n Reference(entry).as_dict\n if isinstance(entry, dict)\n else Reference(entry.model_dump()).as_dict\n )\n for entry in optimade_response.data\n ]\n result.optimade_resource_model = f\"{Reference.__module__}:Reference\"\n elif isinstance(optimade_response, ReferenceResponseOne):\n optimade_resources = [\n (\n Reference(optimade_response.data).as_dict\n if isinstance(optimade_response.data, dict)\n else Reference(optimade_response.data.model_dump()).as_dict\n )\n ]\n result.optimade_resource_model = f\"{Reference.__module__}:Reference\"\n elif isinstance(optimade_response, StructureResponseMany):\n optimade_resources = [\n (\n Structure(entry).as_dict\n if isinstance(entry, dict)\n else Structure(entry.model_dump()).as_dict\n )\n for entry in optimade_response.data\n ]\n result.optimade_resource_model = f\"{Structure.__module__}:Structure\"\n elif isinstance(optimade_response, StructureResponseOne):\n optimade_resources = [\n (\n Structure(optimade_response.data).as_dict\n if isinstance(optimade_response.data, dict)\n else Structure(optimade_response.data.model_dump()).as_dict\n )\n ]\n result.optimade_resource_model = f\"{Structure.__module__}:Structure\"\n else:\n LOGGER.error(\n \"Could not parse response as errors, references or structures. \"\n \"Response:\\n%r\",\n optimade_response,\n )\n error_message = (\n \"Could not retrieve errors, references or structures from response \"\n f\"from {optimade_url}. It could be a valid OPTIMADE API response, \"\n \"however it may not be supported by OTEAPI-OPTIMADE. It may also be an \"\n \"invalid response completely.\"\n )\n raise OPTIMADEParseError(error_message)\n\n result.optimade_resources = [\n resource if isinstance(resource, dict) else resource.model_dump()\n for resource in optimade_resources\n ]\n\n if (\n self.resource_config.configuration.optimade_config\n and self.resource_config.configuration.optimade_config.query_parameters\n ):\n result = result.model_copy(\n update={\n \"optimade_config\": self.resource_config.configuration.optimade_config.model_copy(\n update={\n \"query_parameters\": self.resource_config.configuration.optimade_config.query_parameters.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n )\n }\n )\n }\n )\n\n return result\n
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.OPTIMADEResourceStrategy.get","title":"get()
","text":"Execute an OPTIMADE query to accessUrl
.
This method will be called through the strategy-specific endpoint of the OTE-API Services.
Configuration values provided in resource_config.configuration
take precedence over the derived values from accessUrl
.
Workflow: 1. Update configuration according to session. 2. Deconstruct accessUrl
(done partly by oteapi_optimade.models.custom_types.OPTIMADEUrl
). 3. Reconstruct the complete query URL. 4. Send query. 5. Store result in data cache.
Returns:
Type DescriptionOPTIMADEResourceResult
An update model of key/value-pairs to be stored in the session-specific
OPTIMADEResourceResult
context from services.
Source code inoteapi_optimade/strategies/resource.py
def get(self) -> OPTIMADEResourceResult:\n \"\"\"Execute an OPTIMADE query to `accessUrl`.\n\n This method will be called through the strategy-specific endpoint of the\n OTE-API Services.\n\n Configuration values provided in `resource_config.configuration` take\n precedence over the derived values from `accessUrl`.\n\n Workflow:\n 1. Update configuration according to session.\n 2. Deconstruct `accessUrl` (done partly by\n `oteapi_optimade.models.custom_types.OPTIMADEUrl`).\n 3. Reconstruct the complete query URL.\n 4. Send query.\n 5. Store result in data cache.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if self.resource_config.configuration.optimade_config:\n self.resource_config.configuration.update(\n self.resource_config.configuration.optimade_config.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n )\n )\n\n optimade_endpoint = self.resource_config.accessUrl.endpoint or \"structures\"\n optimade_query = (\n self.resource_config.configuration.query_parameters\n or OPTIMADEQueryParameters()\n )\n LOGGER.debug(\"resource_config: %r\", self.resource_config)\n\n if self.resource_config.accessUrl.query:\n parsed_query = parse_qs(self.resource_config.accessUrl.query)\n for field, value in parsed_query.items():\n # Only use the latest defined value for any parameter\n if field not in optimade_query.model_fields_set:\n LOGGER.debug(\n \"Setting %r from accessUrl (value=%r)\", field, value[-1]\n )\n setattr(optimade_query, field, value[-1])\n\n LOGGER.debug(\"optimade_query after update: %r\", optimade_query)\n\n optimade_url = OPTIMADEUrl(\n f\"{self.resource_config.accessUrl.base_url}\"\n f\"/{self.resource_config.accessUrl.version or 'v1'}\"\n f\"/{optimade_endpoint}?{optimade_query.generate_query_string()}\"\n )\n LOGGER.debug(\"OPTIMADE URL to be requested: %s\", optimade_url)\n\n # Set cache access key to the full OPTIMADE URL.\n self.resource_config.configuration.datacache_config.accessKey = optimade_url\n\n # Perform query\n response = requests.get(\n optimade_url,\n allow_redirects=True,\n timeout=(3, 27), # timeout in seconds (connect, read)\n )\n\n if optimade_query.response_format and optimade_query.response_format != \"json\":\n error_message = (\n \"Can only handle JSON responses for now. Requested response format: \"\n f\"{optimade_query.response_format!r}\"\n )\n raise NotImplementedError(error_message)\n\n cache = DataCache(config=self.resource_config.configuration.datacache_config)\n cache.add(\n {\n \"status_code\": response.status_code,\n \"ok\": response.ok,\n \"json\": response.json(),\n }\n )\n\n parse_with_dlite = use_dlite(\n self.resource_config.accessService,\n self.resource_config.configuration.use_dlite,\n )\n\n parse_parserType = \"parser/OPTIMADE\"\n parse_mediaType = (\n \"application/vnd.\"\n f\"{self.resource_config.accessService.split('+', maxsplit=1)[0]}\"\n )\n if parse_with_dlite:\n parse_parserType += \"/DLite\"\n parse_mediaType += \"+DLite\"\n elif optimade_query.response_format:\n parse_mediaType += f\"+{optimade_query.response_format}\"\n\n parse_config: ParseConfigDict = {\n \"entity\": \"http://onto-ns.com/meta/1.0.1/OPTIMADEStructure\",\n \"parserType\": parse_parserType,\n \"configuration\": {\n \"datacache_config\": self.resource_config.configuration.datacache_config.model_copy(),\n \"downloadUrl\": str(optimade_url),\n \"mediaType\": parse_mediaType,\n \"optimade_config\": self.resource_config.configuration.model_dump(\n exclude={\"optimade_config\", \"downloadUrl\", \"mediaType\"},\n exclude_unset=True,\n exclude_defaults=True,\n ),\n },\n }\n\n LOGGER.debug(\"parse_config: %r\", parse_config)\n\n parse_config[\"configuration\"].update(\n create_strategy(\"parse\", parse_config).initialize()\n )\n parse_result = create_strategy(\"parse\", parse_config).get()\n\n if not all(\n _ in parse_result for _ in (\"optimade_response\", \"optimade_response_model\")\n ):\n base_error_message = (\n \"Could not retrieve response from OPTIMADE parse strategy.\"\n )\n LOGGER.error(\n \"%s\\n\"\n \"optimade_response=%r\\n\"\n \"optimade_response_model=%r\\n\"\n \"session fields=%r\",\n base_error_message,\n parse_result.get(\"optimade_response\"),\n parse_result.get(\"optimade_response_model\"),\n list(parse_result.keys()),\n )\n raise OPTIMADEParseError(base_error_message)\n\n optimade_response_model_module, optimade_response_model_name = parse_result.pop(\n \"optimade_response_model\"\n )\n optimade_response_dict = parse_result.pop(\"optimade_response\")\n\n # Parse response using the provided model\n try:\n optimade_response_model: type[OPTIMADEResponse] = getattr(\n importlib.import_module(optimade_response_model_module),\n optimade_response_model_name,\n )\n optimade_response = optimade_response_model(**optimade_response_dict)\n except (ImportError, AttributeError) as exc:\n base_error_message = \"Could not import the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ImportError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n except ValidationError as exc:\n base_error_message = \"Could not validate the response model.\"\n LOGGER.error(\n \"%s\\n\"\n \"ValidationError: %s\\n\"\n \"optimade_response_model_module=%r\\n\"\n \"optimade_response_model_name=%r\",\n base_error_message,\n exc,\n optimade_response_model_module,\n optimade_response_model_name,\n )\n raise OPTIMADEParseError(base_error_message) from exc\n\n result = OPTIMADEResourceResult()\n\n if isinstance(optimade_response, ErrorResponse):\n optimade_resources = optimade_response.errors\n result.optimade_resource_model = f\"{OptimadeError.__module__}:OptimadeError\"\n elif isinstance(optimade_response, ReferenceResponseMany):\n optimade_resources = [\n (\n Reference(entry).as_dict\n if isinstance(entry, dict)\n else Reference(entry.model_dump()).as_dict\n )\n for entry in optimade_response.data\n ]\n result.optimade_resource_model = f\"{Reference.__module__}:Reference\"\n elif isinstance(optimade_response, ReferenceResponseOne):\n optimade_resources = [\n (\n Reference(optimade_response.data).as_dict\n if isinstance(optimade_response.data, dict)\n else Reference(optimade_response.data.model_dump()).as_dict\n )\n ]\n result.optimade_resource_model = f\"{Reference.__module__}:Reference\"\n elif isinstance(optimade_response, StructureResponseMany):\n optimade_resources = [\n (\n Structure(entry).as_dict\n if isinstance(entry, dict)\n else Structure(entry.model_dump()).as_dict\n )\n for entry in optimade_response.data\n ]\n result.optimade_resource_model = f\"{Structure.__module__}:Structure\"\n elif isinstance(optimade_response, StructureResponseOne):\n optimade_resources = [\n (\n Structure(optimade_response.data).as_dict\n if isinstance(optimade_response.data, dict)\n else Structure(optimade_response.data.model_dump()).as_dict\n )\n ]\n result.optimade_resource_model = f\"{Structure.__module__}:Structure\"\n else:\n LOGGER.error(\n \"Could not parse response as errors, references or structures. \"\n \"Response:\\n%r\",\n optimade_response,\n )\n error_message = (\n \"Could not retrieve errors, references or structures from response \"\n f\"from {optimade_url}. It could be a valid OPTIMADE API response, \"\n \"however it may not be supported by OTEAPI-OPTIMADE. It may also be an \"\n \"invalid response completely.\"\n )\n raise OPTIMADEParseError(error_message)\n\n result.optimade_resources = [\n resource if isinstance(resource, dict) else resource.model_dump()\n for resource in optimade_resources\n ]\n\n if (\n self.resource_config.configuration.optimade_config\n and self.resource_config.configuration.optimade_config.query_parameters\n ):\n result = result.model_copy(\n update={\n \"optimade_config\": self.resource_config.configuration.optimade_config.model_copy(\n update={\n \"query_parameters\": self.resource_config.configuration.optimade_config.query_parameters.model_dump(\n exclude_defaults=True,\n exclude_unset=True,\n )\n }\n )\n }\n )\n\n return result\n
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.OPTIMADEResourceStrategy.initialize","title":"initialize()
","text":"Initialize strategy.
This method will be called through the /initialize
endpoint of the OTE-API Services.
Returns:
Type DescriptionAttrDict | DLiteSessionUpdate
An update model of key/value-pairs to be stored in the session-specific
AttrDict | DLiteSessionUpdate
context from services.
Source code inoteapi_optimade/strategies/resource.py
def initialize(self) -> AttrDict | DLiteSessionUpdate:\n \"\"\"Initialize strategy.\n\n This method will be called through the `/initialize` endpoint of the OTE-API\n Services.\n\n Returns:\n An update model of key/value-pairs to be stored in the session-specific\n context from services.\n\n \"\"\"\n if use_dlite(\n self.resource_config.accessService,\n self.resource_config.configuration.use_dlite,\n ):\n collection_id = self.resource_config.configuration.get(\n \"collection_id\", None\n )\n return DLiteSessionUpdate(\n collection_id=get_collection(collection_id=collection_id).uuid\n )\n\n return AttrDict()\n
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.ParseConfigDict","title":"ParseConfigDict
","text":" Bases: TypedDict
Type definition for the parse_config
dictionary.
oteapi_optimade/strategies/resource.py
class ParseConfigDict(TypedDict):\n \"\"\"Type definition for the `parse_config` dictionary.\"\"\"\n\n entity: str\n parserType: str\n configuration: dict[str, Any]\n
"},{"location":"api_reference/strategies/resource/#oteapi_optimade.strategies.resource.use_dlite","title":"use_dlite(access_service, use_dlite_flag)
","text":"Determine whether DLite should be utilized in the Resource strategy.
Parameters:
Name Type Description Defaultaccess_service
str
The accessService value from the resource's configuration.
requireduse_dlite_flag
bool
The strategy-specific use_dlite
configuration option.
Returns:
Type Descriptionbool
Based on the accessService value, then whether DLite should be used or not.
Source code inoteapi_optimade/strategies/resource.py
def use_dlite(access_service: str, use_dlite_flag: bool) -> bool:\n \"\"\"Determine whether DLite should be utilized in the Resource strategy.\n\n Parameters:\n access_service: The accessService value from the resource's configuration.\n use_dlite_flag: The strategy-specific `use_dlite` configuration option.\n\n Returns:\n Based on the accessService value, then whether DLite should be used or not.\n\n \"\"\"\n if (\n any(dlite_form in access_service for dlite_form in [\"DLite\", \"dlite\"])\n or use_dlite_flag\n ):\n if oteapi_dlite_version is None:\n error_message = (\n \"OTEAPI-DLite is not found on the system. This is required to use \"\n \"DLite with the OTEAPI-OPTIMADE strategies.\"\n )\n raise MissingDependency(error_message)\n return True\n return False\n
"},{"location":"examples/","title":"Overview","text":"This section provides examples of how to use this OTEAPI plugin to perform OPTIMADE queries and handle the results.
In Use OTEAPI-OPTIMADE with OTElib you can find an example of how to use this plugin with the OTElib client.
It is worth noting that there are several different ways to use the strategies in this plugin. For example, an OPTIMADE query can be provided using the OPTIMADE
filter strategy, but it can also be provided directly in the URL value of the OPTIMADE
data resource strategy's accessUlr
parameter. Further, it could be set through a configuration
parameter entry to either of these strategies.
In the examples only one of these options are given, and this is the same for other aspects: What we believe is the most common and transparent use case is given.
Finally, it is important to note that using OTElib directly is not intended for end users. Using OTElib should be done as a backend task in a web application, and the results should be presented to the end user in a more user friendly way.
"},{"location":"examples/#setup-for-examples","title":"Setup for examples","text":""},{"location":"examples/#prerequisites","title":"Prerequisites","text":"To run the examples locally, you need to have the following tools available (in addition to a working Python 3.9+ installation):
To install Jupyter, please refer to the Jupyter documentation. If you want to use pip
to install Jupyter, you can do so by installing the examples
extra for this plugin package:
pip install oteapi-optimade[examples]\n
This will also install OTElib and any other Python packages you may need for the examples.
"},{"location":"examples/#docker-installation","title":"Docker installation","text":"To install Docker, please refer to the Docker documentation.
"},{"location":"examples/#start-a-local-oteapi-server","title":"Start a local OTEAPI server","text":"When running a local OTEAPI server, you need to ensure the OTEAPI-OPTIMADE plugin is installed. This can be done by using the OTEAPI_PLUGIN_PACKAGES
environment variables as described in the OTEAPI Services README.
There are two methods of starting the server:
No matter the method, the server will be available at http://localhost:80/
. To check it, go to the /docs
endpoint: localhost:80/docs.
There are no extra files needed to start the server using Docker. However, several commands need to be run to start the server, which is a collection of different microservices running in different containers on the same Docker network.
The general setup is outlined in the OTEAPI Services README.
For convenience, the following commands can be used to start the services:
docker network create otenet\ndocker volume create redis-persist\ndocker run \\\n --detach \\\n --name redis \\\n --volume redis-persist:/data \\\n --network otenet \\\n redis:latest\ndocker run \\\n --rm \\\n --network otenet \\\n --detach \\\n --publish 80:8080 \\\n --env OTEAPI_REDIS_TYPE=redis \\\n --env OTEAPI_REDIS_HOST=redis \\\n --env OTEAPI_REDIS_PORT=6379 \\\n --env OTEAPI_INCLUDE_REDISADMIN=False \\\n --env OTEAPI_EXPOSE_SECRETS=True \\\n --env OTEAPI_PLUGIN_PACKAGES=oteapi-optimade \\\n ghcr.io/emmc-asbl/oteapi:1.20231108.329\n
Note
To use the /triples
endpoint, an AllegroGraph triplestore needs to be running. For more information see the OTEAPI Services README to see how to set this up and run it.
Important
Pinning to version '1.20231108.329' of the OTEAPI image is important, as the latest version is currently not compatible with this plugin. To follow this issue, please see GitHub issue #187 and GitHub issue #163.
"},{"location":"examples/#using-docker-compose","title":"Using Docker Compose","text":"Download the Docker Compose file from the OTEAPI Services repository:
curl -O https://raw.githubusercontent.com/EMMC-ASBL/oteapi-services/master/docker-compose.yml\n
And either update the OTEAPI_PLUGIN_PACKAGES
environment variable in the file to include oteapi-optimade
:
# ...\n OTEAPI_PLUGIN_PACKAGES: oteapi-optimade\n# ...\n
Or set the environment variable when starting the services by prefixing it to the Docker Compose command.
Then start the services:
docker compose pull\ndocker compose up --detach\n
Note
When setting the environment variables as a prefix to the docker compose
command, it is only needed for the command that runs the services:
OTEAPI_PLUGIN_PACKAGES=oteapi-optimade docker compose up --detach\n
"},{"location":"examples/dlite/","title":"Use DLite strategies from OTEAPI-OPTIMADE","text":"In\u00a0[37]: Copied! from otelib import OTEClient\n\nclient = OTEClient(\"python\")\nfrom otelib import OTEClient client = OTEClient(\"python\") In\u00a0[38]: Copied!
data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE+DLite\",\n accessUrl=\"https://optimade.materialsproject.org\",\n)\n\n# This is equivalent to:\n# data_resource_strategy = client.create_dataresource(\n# accessService=\"OPTIMADE\",\n# accessUrl=\"https://optimade.materialsproject.org\",\n# configuration={\"use_dlite\": True},\n# )\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE+DLite\", accessUrl=\"https://optimade.materialsproject.org\", ) # This is equivalent to: # data_resource_strategy = client.create_dataresource( # accessService=\"OPTIMADE\", # accessUrl=\"https://optimade.materialsproject.org\", # configuration={\"use_dlite\": True}, # ) In\u00a0[39]: Copied!
filter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='elements HAS ALL \"Si\",\"O\" AND nelements<=4',\n)\nfilter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='elements HAS ALL \"Si\",\"O\" AND nelements<=4', ) In\u00a0[40]: Copied!
import json\n\npipeline = filter_strategy >> data_resource_strategy\nsession = pipeline.get()\nparsed_session = json.loads(session)\nparsed_session.keys()\nimport json pipeline = filter_strategy >> data_resource_strategy session = pipeline.get() parsed_session = json.loads(session) parsed_session.keys()
Setting filter from query.\nSetting filter from query.\nSetting filter from query.\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE+DLite', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE+DLite', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE+DLite', license=None, accessRights=None, publisher=None)\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Si\",\"O\" AND nelements<=4', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Si%22%2C%22O%22%20AND%20nelements%3C%3D4&response_format=json&page_limit=20&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Si%22%2C%22O%22%20AND%20nelements%3C%3D4&response_format=json&page_limit=20&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Si%22%2C%22O%22%20AND%20nelements%3C%3D4&response_format=json&page_limit=20&include=references\nOut[40]:
dict_keys(['optimade_config', 'optimade_resources', 'optimade_resource_model', 'collection_id'])In\u00a0[41]: Copied!
from importlib import import_module\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\n\nparsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\")\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")\nfrom importlib import import_module import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) parsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\") print(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")
The query resulted in 20 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1033911', 'mp-757887', 'mp-1219366', 'mp-733539', 'mp-542090', 'mp-758465', 'mp-560675', 'mp-774171', 'mp-1304778', 'mp-1016821', 'mp-1363556', 'mp-757013', 'mp-683953', 'mp-1020609', 'mp-17612', 'mp-558129', 'mp-1210628', 'mp-1197149', 'mp-21791', 'mp-752892']\nIn\u00a0[42]: Copied!
from oteapi_dlite.utils import get_collection\n\ncollection = get_collection(session=parsed_session)\nprint(collection)\nfrom oteapi_dlite.utils import get_collection collection = get_collection(session=parsed_session) print(collection)
{\n \"bfd78bf8-31fe-4487-a4c3-8cc5351ca87d\": {\n \"meta\": \"http://onto-ns.com/meta/0.1/Collection\",\n \"dimensions\": {\n \"nrelations\": 60\n },\n \"properties\": {\n \"relations\": [[\"mp-1033911\", \"_is-a\", \"Instance\"], [\"mp-1033911\", \"_has-uuid\", \"f0676948-f620-4057-9291-b1851f519d66\"], [\"mp-1033911\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-757887\", \"_is-a\", \"Instance\"], [\"mp-757887\", \"_has-uuid\", \"76add43f-bb1e-4bdb-984e-dc6c16b9205d\"], [\"mp-757887\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1219366\", \"_is-a\", \"Instance\"], [\"mp-1219366\", \"_has-uuid\", \"d0e7c4f7-1dd7-4206-aa53-2a67b755259d\"], [\"mp-1219366\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-733539\", \"_is-a\", \"Instance\"], [\"mp-733539\", \"_has-uuid\", \"e460fb2d-1639-4b65-9e61-f4c2869ef412\"], [\"mp-733539\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-542090\", \"_is-a\", \"Instance\"], [\"mp-542090\", \"_has-uuid\", \"4625be3d-cad7-42a2-afdd-3c01715c0905\"], [\"mp-542090\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-758465\", \"_is-a\", \"Instance\"], [\"mp-758465\", \"_has-uuid\", \"d041f6c2-4770-41ec-870c-66d07f9aafff\"], [\"mp-758465\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-560675\", \"_is-a\", \"Instance\"], [\"mp-560675\", \"_has-uuid\", \"d6dfd477-cea9-46c1-b56f-487a77fb615e\"], [\"mp-560675\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-774171\", \"_is-a\", \"Instance\"], [\"mp-774171\", \"_has-uuid\", \"ac42160c-9661-4180-812b-4c2b2e44145e\"], [\"mp-774171\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1304778\", \"_is-a\", \"Instance\"], [\"mp-1304778\", \"_has-uuid\", \"d840939b-9b95-48b9-9be7-4f0d8bdf480d\"], [\"mp-1304778\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1016821\", \"_is-a\", \"Instance\"], [\"mp-1016821\", \"_has-uuid\", \"d023ba43-f725-4a8d-b38c-ff5021729f9d\"], [\"mp-1016821\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1363556\", \"_is-a\", \"Instance\"], [\"mp-1363556\", \"_has-uuid\", \"8d011cb4-fa24-44ac-960d-b027588924d3\"], [\"mp-1363556\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-757013\", \"_is-a\", \"Instance\"], [\"mp-757013\", \"_has-uuid\", \"9d7928e0-1209-4251-ab67-92f5cc1cb1a4\"], [\"mp-757013\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-683953\", \"_is-a\", \"Instance\"], [\"mp-683953\", \"_has-uuid\", \"05eae7e9-2e01-4e21-bc86-0e4004221f76\"], [\"mp-683953\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1020609\", \"_is-a\", \"Instance\"], [\"mp-1020609\", \"_has-uuid\", \"99468a6c-3875-436c-8025-0af9d4b53be5\"], [\"mp-1020609\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-17612\", \"_is-a\", \"Instance\"], [\"mp-17612\", \"_has-uuid\", \"7b266e95-b56a-4fc3-921c-407daa7d1369\"], [\"mp-17612\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-558129\", \"_is-a\", \"Instance\"], [\"mp-558129\", \"_has-uuid\", \"5c4d6757-866a-482b-83ba-cb8aa035b180\"], [\"mp-558129\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1210628\", \"_is-a\", \"Instance\"], [\"mp-1210628\", \"_has-uuid\", \"a32c7f8c-a4b8-40f8-96e5-92a642c0e644\"], [\"mp-1210628\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-1197149\", \"_is-a\", \"Instance\"], [\"mp-1197149\", \"_has-uuid\", \"68ec190e-7ff5-497d-a6e5-de1bb568205c\"], [\"mp-1197149\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-21791\", \"_is-a\", \"Instance\"], [\"mp-21791\", \"_has-uuid\", \"b9805c87-808a-48b8-be04-58b18ebafb4e\"], [\"mp-21791\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"], [\"mp-752892\", \"_is-a\", \"Instance\"], [\"mp-752892\", \"_has-uuid\", \"e2674e42-465f-4991-b7f5-15a65fb07f6d\"], [\"mp-752892\", \"_has-meta\", \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\"]]\n }\n }\n}\nIn\u00a0[43]: Copied!
for inst in collection.get_instances():\n print(inst)\nfor inst in collection.get_instances(): print(inst)
{\n \"f0676948-f620-4057-9291-b1851f519d66\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"46900658-13b0-4072-8fc1-7e20de603765\",\n \"id\": \"mp-1033911\"\n }\n }\n}\n{\n \"76add43f-bb1e-4bdb-984e-dc6c16b9205d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"62d27df4-ac40-4784-a5fd-ce48cbf12a3a\",\n \"id\": \"mp-757887\"\n }\n }\n}\n{\n \"d0e7c4f7-1dd7-4206-aa53-2a67b755259d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c890c277-74b5-42a9-8bed-d0687b34bbd6\",\n \"id\": \"mp-1219366\"\n }\n }\n}\n{\n \"e460fb2d-1639-4b65-9e61-f4c2869ef412\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"4d2dc20a-671f-4c26-90a2-d9f6bc6bbe0c\",\n \"id\": \"mp-733539\"\n }\n }\n}\n{\n \"4625be3d-cad7-42a2-afdd-3c01715c0905\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c1e0bc0e-bc18-4ebe-b0df-c8ab4cba83ae\",\n \"id\": \"mp-542090\"\n }\n }\n}\n{\n \"d041f6c2-4770-41ec-870c-66d07f9aafff\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"0592aaad-c896-464a-b368-82f62bbe42f9\",\n \"id\": \"mp-758465\"\n }\n }\n}\n{\n \"d6dfd477-cea9-46c1-b56f-487a77fb615e\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"1baa18c6-fec5-4720-8554-5593ca656f06\",\n \"id\": \"mp-560675\"\n }\n }\n}\n{\n \"ac42160c-9661-4180-812b-4c2b2e44145e\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"d9d59dc2-3f83-4f96-ae5c-6d3ad5a241c6\",\n \"id\": \"mp-774171\"\n }\n }\n}\n{\n \"d840939b-9b95-48b9-9be7-4f0d8bdf480d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"cdbfd5ec-9a84-4a09-94b8-8ec989f09019\",\n \"id\": \"mp-1304778\"\n }\n }\n}\n{\n \"d023ba43-f725-4a8d-b38c-ff5021729f9d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"0d46f58f-2744-4c4f-882b-57da2e1c9cdf\",\n \"id\": \"mp-1016821\"\n }\n }\n}\n{\n \"8d011cb4-fa24-44ac-960d-b027588924d3\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c951ff9b-688e-40f3-b15d-b0acfede5e0a\",\n \"id\": \"mp-1363556\"\n }\n }\n}\n{\n \"9d7928e0-1209-4251-ab67-92f5cc1cb1a4\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"5053a07b-f15b-437a-9bde-ce646a669abe\",\n \"id\": \"mp-757013\"\n }\n }\n}\n{\n \"05eae7e9-2e01-4e21-bc86-0e4004221f76\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c2f151ff-6c3c-45ed-a7b0-3ee5117f0035\",\n \"id\": \"mp-683953\"\n }\n }\n}\n{\n \"99468a6c-3875-436c-8025-0af9d4b53be5\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"22cd1e19-e45f-47b5-a62d-4027f61f9667\",\n \"id\": \"mp-1020609\"\n }\n }\n}\n{\n \"7b266e95-b56a-4fc3-921c-407daa7d1369\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"0aeef165-da7e-4681-8052-e95b484d637f\",\n \"id\": \"mp-17612\"\n }\n }\n}\n{\n \"5c4d6757-866a-482b-83ba-cb8aa035b180\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"ddb31fe8-6885-4c80-bf2e-044dad60f8c3\",\n \"id\": \"mp-558129\"\n }\n }\n}\n{\n \"a32c7f8c-a4b8-40f8-96e5-92a642c0e644\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"62840cd5-2f20-481f-8967-43476ecec964\",\n \"id\": \"mp-1210628\"\n }\n }\n}\n{\n \"68ec190e-7ff5-497d-a6e5-de1bb568205c\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"c0674560-bbde-4e93-aa8a-bf9260ef6975\",\n \"id\": \"mp-1197149\"\n }\n }\n}\n{\n \"b9805c87-808a-48b8-be04-58b18ebafb4e\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"aec646fb-b54f-4526-ba97-5cae2d3a528a\",\n \"id\": \"mp-21791\"\n }\n }\n}\n{\n \"e2674e42-465f-4991-b7f5-15a65fb07f6d\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructure\",\n \"dimensions\": {\n },\n \"properties\": {\n \"type\": \"structures\",\n \"attributes\": \"606e5ca8-0fb1-45a4-941a-45461d19eceb\",\n \"id\": \"mp-752892\"\n }\n }\n}\nIn\u00a0[44]: Copied!
import dlite\n\nstructure_instances: list[dlite.Instance] = list(collection.get_instances())\n\nstructure_attributes_instance: dlite.Instance = dlite.get_instance(structure_instances[0].attributes)\n\nprint(structure_attributes_instance)\nimport dlite structure_instances: list[dlite.Instance] = list(collection.get_instances()) structure_attributes_instance: dlite.Instance = dlite.get_instance(structure_instances[0].attributes) print(structure_attributes_instance)
{\n \"46900658-13b0-4072-8fc1-7e20de603765\": {\n \"meta\": \"http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes\",\n \"dimensions\": {\n \"nelements\": 4,\n \"dimensionality\": 3,\n \"nsites\": 32,\n \"nspecies\": 4,\n \"nstructure_features\": 0\n },\n \"properties\": {\n \"elements\": [\"K\", \"Mg\", \"O\", \"Si\"],\n \"nelements\": 4,\n \"elements_ratios\": [0.03125, 0.4375, 0.03125, 0.5],\n \"chemical_formula_descriptive\": \"KMg14O16Si\",\n \"chemical_formula_reduced\": \"KMg14O16Si\",\n \"chemical_formula_hill\": \"KMg14O16Si\",\n \"chemical_formula_anonymous\": \"A16B14CD\",\n \"dimension_types\": [1, 1, 1],\n \"nperiodic_dimensions\": 3,\n \"lattice_vectors\": [[8.59318, 0, 0], [0, 8.59318, 0], [0, 0, 4.41201]],\n \"cartesian_site_positions\": [[0, 0, 0], [0, 4.29659, 0], [4.29659, 0, 0], [0, 2.1438, 2.20601], [0, 6.44938, 2.20601], [4.29659, 2.13079, 2.20601], [4.29659, 6.46238, 2.20601], [2.1438, 0, 2.20601], [2.13079, 4.29659, 2.20601], [6.44938, 0, 2.20601], [6.46238, 4.29659, 2.20601], [2.12673, 2.12673, 0], [2.12673, 6.46645, 0], [6.46645, 2.12673, 0], [6.46645, 6.46645, 0], [4.29659, 4.29659, 0], [2.37689, 0, 0], [2.32997, 4.29659, 0], [6.21628, 0, 0], [6.2632, 4.29659, 0], [2.16162, 2.16162, 2.20601], [2.16162, 6.43155, 2.20601], [6.43155, 2.16162, 2.20601], [6.43155, 6.43155, 2.20601], [0, 0, 2.20601], [0, 4.29659, 2.20601], [4.29659, 0, 2.20601], [4.29659, 4.29659, 2.20601], [0, 2.37689, 0], [0, 6.21628, 0], [4.29659, 2.32997, 0], [4.29659, 6.2632, 0]],\n \"nsites\": 32,\n \"species\": [\"b60b7266-2807-4d89-bdd7-776fc7bd84d0\", \"faa90667-ab90-484d-b0f5-17685b3c9d5b\", \"880e1de2-1608-4432-986c-8b199cff5018\", \"9ab25660-3d30-4034-a9f5-ba0e7a7a8adc\"],\n \"species_at_sites\": [\"K\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Mg\", \"Si\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\", \"O\"],\n \"assemblies\": null,\n \"structure_features\": [],\n \"immutable_id\": \"645d2b74bcd30f748b4746a3\",\n \"last_modified\": \"2019-10-23 12:27:13+00:00\"\n }\n }\n}\n"},{"location":"examples/dlite/#use-dlite-strategies-from-oteapi-optimade","title":"Use DLite strategies from OTEAPI-OPTIMADE\u00b6","text":"
This example shows how to use the DLite strategies from the OTEAPI-OPTIMADE plugin.
DLite is a Python library for working with data models and semantics. It is considered to be the default semantic data model backend for use with OTEAPI.
To see more fundamental examples of how to use OTEAPI-OPTIMADE, see the example Use OTEAPI-OPTIMADE with OTElib. OTElib will also be used as a client in the current example. Furthermore, only the HTTP requests-based backend will be used in this example.
"},{"location":"examples/dlite/#setup","title":"Setup\u00b6","text":"Please see the setup instructions in Use OTEAPI-OPTIMADE with OTElib for how to ensure you have a proper environment to run the example in.
"},{"location":"examples/dlite/#example","title":"Example\u00b6","text":"In this example, we will use the DLite strategies to query the Materials Project OPTIMADE API. We are interested in finding all structures that include the elements Si
and O
, and that have a maximum of 4 elements in total.
Let's start by initializing a client:
"},{"location":"examples/dlite/#data-resource-strategy","title":"Data Resource strategy\u00b6","text":"The general pipeline is the same as it was for Use OTEAPI-OPTIMADE with OTElib:
However, in order to use DLite we need to either reference a specific accessService
or set a flag in the configuration of the data resource strategy.
Note, it is only for the data resource strategy we need to specify the usage of DLite, as the filter strategy is generic and merely a helper for more explicitly setting the query parameters to be used for the underlying OPTIMADE query.
"},{"location":"examples/dlite/#filter-strategy","title":"Filter strategy\u00b6","text":"The OPTIMADE filter query to fulfill the interests outlined above should look like this:
elements HAS ALL \"Si\",\"O\" AND nelements<=4\n
"},{"location":"examples/dlite/#setup-execute-and-inspect-the-pipeline","title":"Setup, execute and inspect the pipeline\u00b6","text":""},{"location":"examples/otelib/","title":"Use OTEAPI-OPTIMADE with OTElib","text":"In\u00a0[1]: Copied! from otelib import OTEClient\n\nclient = OTEClient(\"http://localhost:80\")\nfrom otelib import OTEClient client = OTEClient(\"http://localhost:80\") In\u00a0[2]: Copied!
data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE\",\n accessUrl=\"https://optimade.materialsproject.org/\",\n)\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE\", accessUrl=\"https://optimade.materialsproject.org/\", )
For the filter strategy, we need to know the OPTIMADE query that we want to execute.
For retrieving all structures with the formula Al2O3
, we can use the following OPTIMADE filter query:
chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"\n
In\u00a0[3]: Copied! filter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"',\n)\nfilter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', )
Now we can create the OTE pipeline shown above and execute it.
In\u00a0[4]: Copied!pipeline = filter_strategy >> data_resource_strategy\nsession = pipeline.get()\npipeline = filter_strategy >> data_resource_strategy session = pipeline.get()
The returned session is a JSON object we can parse and investigate.
In\u00a0[5]: Copied!import json\n\nparsed_session: dict = json.loads(session)\nprint(parsed_session.keys())\nimport json parsed_session: dict = json.loads(session) print(parsed_session.keys())
dict_keys(['optimade_resources', 'optimade_config', 'optimade_resource_model'])\n
As can be seen, there are three keys in the returned session. optimade_config
summarizes the query that has been performed to Materials Project. The OPTIMADE structures are listed under the optimade_resources
. It is named as such due to there being different OPTIMADE resources, e.g., structures
, references
, links
, etc. The OPTIMADE Python tools has a useful OPTIMADE Structure model class that can be used to parse the OPTIMADE structures into Python objects as well as validating them according to the OPTIMADE specification. Again, since one can query for different OPTIMADE resources, the specific Python class to use is given in optimade_resource_model
.
from importlib import import_module\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\n\nparsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\")\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")\nfrom importlib import import_module import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) parsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\") print(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")
The query resulted in 20 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1228448', 'mp-755483', 'mp-1245081', 'mp-1244878', 'mp-1105018', 'mp-754401', 'mp-1245063', 'mp-1245265', 'mp-684591', 'mp-1244898', 'mp-1245211', 'mp-1245056', 'mp-642363', 'mp-1244930', 'mp-1244937', 'mp-1244967', 'mp-1244954', 'mp-1244874', 'mp-1245008', 'mp-1245023']\n
To find them on the Materials Project website, go to materialsproject.org/materials/<ID>
, for example: materialsproject.org/materials/mp-1228448.
What is more, we can investigate the structure according to the well-defined OPTIMADE structure model attributes. For example, so assert the chemical formula is what we expected, we can check the different chemical formula attributes:
In\u00a0[7]: Copied!structure = parsed_structures[0]\nprint(structure.id)\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nstructure = parsed_structures[0] print(structure.id) for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
mp-1228448\nchemical_formula_descriptive: Al2O3\nchemical_formula_reduced: Al2O3\nchemical_formula_hill: Al2O3\nchemical_formula_anonymous: A3B2\nIn\u00a0[8]: Copied!
filter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='elements HAS ALL \"Al\",\"O\"',\n limit=5,\n)\nfilter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='elements HAS ALL \"Al\",\"O\"', limit=5, )
For this query, we have added the limit
parameter to the filter configuration, which will pass a page_limit
query parameter to the OPTIMADE API ensuring that we only retrieve the first 5 structures (it limits each result's page to 5 resources).
Let us investigate the result again, checking the list of elements and the chemical formula attributes:
In\u00a0[9]: Copied!pipeline = filter_strategy >> data_resource_strategy\nparsed_session = json.loads(pipeline.get())\nprint(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\")\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\nstructures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in structures]}\")\npipeline = filter_strategy >> data_resource_strategy parsed_session = json.loads(pipeline.get()) print(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\") import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"Their Materials Project IDs are: {[structure.id for structure in structures]}\")
The query resulted in 5 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1038042', 'mp-1182891', 'mp-1208627', 'mp-1247835', 'mp-1521059']\n
Let us also check the query parameters used for the request to the OPTIMADE API to ensure that the page_limit
query parameter was passed:
parsed_session[\"optimade_config\"]\nparsed_session[\"optimade_config\"] Out[10]:
{'query_parameters': {'filter': 'elements HAS ALL \"Al\",\"O\"', 'page_limit': 5}}In\u00a0[11]: Copied!
structure = structures[0]\nprint(structure.id)\nprint(f\"elements: {structure.elements}\")\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nstructure = structures[0] print(structure.id) print(f\"elements: {structure.elements}\") for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
mp-1038042\nelements: ['Al', 'Cr', 'Mg', 'O']\nchemical_formula_descriptive: AlCrMg30O32\nchemical_formula_reduced: AlCrMg30O32\nchemical_formula_hill: AlCrMg30O32\nchemical_formula_anonymous: A32B30CD\nIn\u00a0[12]: Copied!
data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE\",\n accessUrl=\"https://aiida.materialscloud.org/mc3d/optimade\",\n)\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE\", accessUrl=\"https://aiida.materialscloud.org/mc3d/optimade\", ) In\u00a0[13]: Copied!
pipeline = filter_strategy >> data_resource_strategy\nparsed_session = json.loads(pipeline.get())\nprint(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\")\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\nstructures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"Their Materials Cloud (AiiDA) IDs are: {[structure.id for structure in structures]}\")\npipeline = filter_strategy >> data_resource_strategy parsed_session = json.loads(pipeline.get()) print(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\") import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"Their Materials Cloud (AiiDA) IDs are: {[structure.id for structure in structures]}\")
The query resulted in 5 structures found (on page 1) of the returned data.\nTheir Materials Cloud (AiiDA) IDs are: ['13952', '43703', '43800', '56101', '56270']\n
Again, let's check the query parameters used for the request to the OPTIMADE API to ensure it is equivalent to the previous search:
In\u00a0[14]: Copied!parsed_session[\"optimade_config\"]\nparsed_session[\"optimade_config\"] Out[14]:
{'query_parameters': {'filter': 'elements HAS ALL \"Al\",\"O\"', 'page_limit': 5}}In\u00a0[15]: Copied!
structure = structures[0]\nprint(structure.id)\nprint(f\"elements: {structure.elements}\")\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nstructure = structures[0] print(structure.id) print(f\"elements: {structure.elements}\") for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
13952\nelements: ['Al', 'O']\nchemical_formula_descriptive: Al2O6\nchemical_formula_reduced: AlO3\nchemical_formula_hill: Al2O6\nchemical_formula_anonymous: A3B\n
The MC3D structures can, unfortunately, not be found easily in the Materials Cloud website, as the ID in the DISCOVER section does not match the ID in the OPTIMADE structure. However, the full OPTIMADE structure can be found at <OPTIMADE_BASE_URL>/structures/<ID>
, for example: aiida.materialscloud.org/mc3d/optimade/structures/13952.
Note
The structure with the OPTIMADE ID 13952 is found with the Materials Cloud DISCOVER ID mc3d-76896
and can be found here.
client = OTEClient(\"python\")\nclient = OTEClient(\"python\")
Now we can go through the same searches as we did with the HTTP requests-based backend. The result should not change.
In\u00a0[17]: Copied!data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE\",\n accessUrl=\"https://optimade.materialsproject.org/\",\n)\n\nfilter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"',\n)\n\npipeline = filter_strategy >> data_resource_strategy\nsession = pipeline.get()\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE\", accessUrl=\"https://optimade.materialsproject.org/\", ) filter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', ) pipeline = filter_strategy >> data_resource_strategy session = pipeline.get()
Setting filter from query.\nSetting filter from query.\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\noptimade_query after update: OPTIMADEQueryParameters(filter='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='chemical_formula_descriptive = \"Al2O3\" OR chemical_formula_reduced = \"Al2O3\" OR chemical_formula_hill = \"Al2O3\"', response_format='json', email_address='', response_fields='', sort='', page_limit=20, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=chemical_formula_descriptive%20%3D%20%22Al2O3%22%20OR%20chemical_formula_reduced%20%3D%20%22Al2O3%22%20OR%20chemical_formula_hill%20%3D%20%22Al2O3%22&response_format=json&page_limit=20&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=chemical_formula_descriptive%20%3D%20%22Al2O3%22%20OR%20chemical_formula_reduced%20%3D%20%22Al2O3%22%20OR%20chemical_formula_hill%20%3D%20%22Al2O3%22&response_format=json&page_limit=20&include=references\n
/home/cwa/.venvs/oteapi-optimade/lib/python3.9/site-packages/optimade/server/config.py:113: UserWarning: Unable to find config file at /home/cwa/.optimade.json, using the default settings instead.\n warnings.warn(\n
As one can see, this backend runs locally within the same Python environment as the notebook. This is useful for development purposes, where the logging messages are shown directly in the output.
In\u00a0[18]: Copied!parsed_session: dict = json.loads(session)\nprint(parsed_session.keys())\nparsed_session: dict = json.loads(session) print(parsed_session.keys())
dict_keys(['optimade_config', 'optimade_resources', 'optimade_resource_model'])\n
We get the same keys in the returned session as we did with the HTTP requests-based backend. If we again import the OPTIMADE Structure model class from the OPTIMADE Python tools, we can parse the OPTIMADE structures into Python objects as well as validating them according to the OPTIMADE specification, just as we did with the HTTP requests-based backend.
In\u00a0[19]: Copied!import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\n\nparsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\")\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\")\n\nstructure = parsed_structures[0]\nprint(structure.id)\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) parsed_structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"The query resulted in {len(parsed_structures)} structures found (on page 1) of the returned data.\") print(f\"Their Materials Project IDs are: {[structure.id for structure in parsed_structures]}\") structure = parsed_structures[0] print(structure.id) for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
The query resulted in 20 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1228448', 'mp-755483', 'mp-1245081', 'mp-1244878', 'mp-1105018', 'mp-754401', 'mp-1245063', 'mp-1245265', 'mp-684591', 'mp-1244898', 'mp-1245211', 'mp-1245056', 'mp-642363', 'mp-1244930', 'mp-1244937', 'mp-1244967', 'mp-1244954', 'mp-1244874', 'mp-1245008', 'mp-1245023']\nmp-1228448\nchemical_formula_descriptive: Al2O3\nchemical_formula_reduced: Al2O3\nchemical_formula_hill: Al2O3\nchemical_formula_anonymous: A3B2\nIn\u00a0[20]: Copied!
filter_strategy = client.create_filter(\n filterType=\"OPTIMADE\",\n query='elements HAS ALL \"Al\",\"O\"',\n limit=5,\n)\n\npipeline = filter_strategy >> data_resource_strategy\nparsed_session = json.loads(pipeline.get())\nprint(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\")\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\nstructures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"Their Materials Project IDs are: {[structure.id for structure in structures]}\")\nfilter_strategy = client.create_filter( filterType=\"OPTIMADE\", query='elements HAS ALL \"Al\",\"O\"', limit=5, ) pipeline = filter_strategy >> data_resource_strategy parsed_session = json.loads(pipeline.get()) print(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\") import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"Their Materials Project IDs are: {[structure.id for structure in structures]}\")
Setting filter from query.\nSetting filter from query.\nSetting filter from query.\nSetting page_limit from limit.\nSetting page_limit from limit.\nSetting page_limit from limit.\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://optimade.materialsproject.org/', base_url='https://optimade.materialsproject.org', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nOPTIMADE URL to be requested: https://optimade.materialsproject.org/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nThe query resulted in 5 structures found (on page 1) of the returned data.\nTheir Materials Project IDs are: ['mp-1038042', 'mp-1182891', 'mp-1208627', 'mp-1247835', 'mp-1521059']\n
The configuration in optimade_config
is quite more extensive than with the HTTP requests-based backend, as it includes more information, automatically setting the default values so they are shown in the config:
parsed_session[\"optimade_config\"]\nparsed_session[\"optimade_config\"] Out[21]:
{'version': 'v1',\n 'endpoint': 'structures',\n 'query_parameters': {'filter': 'elements HAS ALL \"Al\",\"O\"', 'page_limit': 5},\n 'datacache_config': {'cacheDir': 'oteapi',\n 'accessKey': None,\n 'hashType': 'md5',\n 'expireTime': 86400,\n 'tag': 'optimade'},\n 'return_object': False,\n 'use_dlite': False}
Let's look at the first structure again:
In\u00a0[22]: Copied!structure = structures[0]\nprint(structure.id)\nprint(f\"elements: {structure.elements}\")\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\nstructure = structures[0] print(structure.id) print(f\"elements: {structure.elements}\") for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
mp-1038042\nelements: ['Al', 'Cr', 'Mg', 'O']\nchemical_formula_descriptive: AlCrMg30O32\nchemical_formula_reduced: AlCrMg30O32\nchemical_formula_hill: AlCrMg30O32\nchemical_formula_anonymous: A32B30CD\nIn\u00a0[23]: Copied!
data_resource_strategy = client.create_dataresource(\n accessService=\"OPTIMADE\",\n accessUrl=\"https://aiida.materialscloud.org/mc3d/optimade\",\n)\n\npipeline = filter_strategy >> data_resource_strategy\nparsed_session = json.loads(pipeline.get())\nprint(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\")\n\nimport_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1)\nResourceClass = getattr(import_module(import_path), class_name)\nstructures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]]\nprint(f\"Their Materials Cloud (AiiDA) IDs are: {[structure.id for structure in structures]}\")\n\nprint(parsed_session[\"optimade_config\"])\n\nstructure = structures[0]\nprint(structure.id)\nprint(f\"elements: {structure.elements}\")\nfor attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"):\n print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")\ndata_resource_strategy = client.create_dataresource( accessService=\"OPTIMADE\", accessUrl=\"https://aiida.materialscloud.org/mc3d/optimade\", ) pipeline = filter_strategy >> data_resource_strategy parsed_session = json.loads(pipeline.get()) print(f\"The query resulted in {len(parsed_session['optimade_resources'])} structures found (on page 1) of the returned data.\") import_path, class_name = parsed_session[\"optimade_resource_model\"].split(\":\", maxsplit=1) ResourceClass = getattr(import_module(import_path), class_name) structures = [ResourceClass(structure) for structure in parsed_session[\"optimade_resources\"]] print(f\"Their Materials Cloud (AiiDA) IDs are: {[structure.id for structure in structures]}\") print(parsed_session[\"optimade_config\"]) structure = structures[0] print(structure.id) print(f\"elements: {structure.elements}\") for attribute in (\"descriptive\", \"reduced\", \"hill\", \"anonymous\"): print(f\"chemical_formula_{attribute}: {getattr(structure, f'chemical_formula_{attribute}', '(not defined)')}\")
Setting filter from query.\nSetting filter from query.\nSetting filter from query.\nSetting page_limit from limit.\nSetting page_limit from limit.\nSetting page_limit from limit.\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://aiida.materialscloud.org/mc3d/optimade', base_url='https://aiida.materialscloud.org/mc3d/optimade', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://aiida.materialscloud.org/mc3d/optimade', base_url='https://aiida.materialscloud.org/mc3d/optimade', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\nresource_config: OPTIMADEResourceConfig(user=None, password=None, token=None, client_id=None, client_secret=None, configuration=OPTIMADEConfig(version='v1', endpoint='structures', query_parameters=OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint=''), datacache_config=DataCacheConfig(cacheDir=PosixPath('oteapi'), accessKey=None, hashType='md5', expireTime=86400, tag='optimade'), return_object=False, use_dlite=False), description='Resource Strategy Data Configuration.\\n\\n Important:\\n Either of the pairs of attributes `downloadUrl`/`mediaType` or\\n `accessUrl`/`accessService` MUST be specified.\\n\\n ', downloadUrl=None, mediaType=None, accessUrl=OPTIMADEUrl('https://aiida.materialscloud.org/mc3d/optimade', base_url='https://aiida.materialscloud.org/mc3d/optimade', scheme='https', tld='org', host_type='domain'), accessService='OPTIMADE', license=None, accessRights=None, publisher=None)\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\noptimade_query after update: OPTIMADEQueryParameters(filter='elements HAS ALL \"Al\",\"O\"', response_format='json', email_address='', response_fields='', sort='', page_limit=5, page_offset=0, page_number=None, page_cursor=0, page_above=None, page_below=None, include='references', api_hint='')\nOPTIMADE URL to be requested: https://aiida.materialscloud.org/mc3d/optimade/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nOPTIMADE URL to be requested: https://aiida.materialscloud.org/mc3d/optimade/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nOPTIMADE URL to be requested: https://aiida.materialscloud.org/mc3d/optimade/v1/structures?filter=elements%20HAS%20ALL%20%22Al%22%2C%22O%22&response_format=json&page_limit=5&include=references\nThe query resulted in 5 structures found (on page 1) of the returned data.\nTheir Materials Cloud (AiiDA) IDs are: ['13952', '43703', '43800', '56101', '56270']\n{'version': 'v1', 'endpoint': 'structures', 'query_parameters': {'filter': 'elements HAS ALL \"Al\",\"O\"', 'page_limit': 5}, 'datacache_config': {'cacheDir': 'oteapi', 'accessKey': None, 'hashType': 'md5', 'expireTime': 86400, 'tag': 'optimade'}, 'return_object': False, 'use_dlite': False}\n13952\nelements: ['Al', 'O']\nchemical_formula_descriptive: Al2O6\nchemical_formula_reduced: AlO3\nchemical_formula_hill: Al2O6\nchemical_formula_anonymous: A3B\n"},{"location":"examples/otelib/#use-oteapi-optimade-with-otelib","title":"Use OTEAPI-OPTIMADE with OTElib\u00b6","text":"
OTElib is a Python client library for the OTEAPI system. It has two usable backends:
This notebook demonstrates how to use OTEAPI-OPTIMADE with OTElib.
"},{"location":"examples/otelib/#example","title":"Example\u00b6","text":"Using OTElib we will do different OPTIMADE queries. First, we will search through the Materials Project database for all structures with the formula Al2O3
. Then, we will search through the Materials Project database for all structures that include Al
and O
in their chemical formula. Finally, we will search through the Materials Cloud MC3D - Materials Cloud three-dimensional crystals database for all structures that include Al
and O
in their chemical formula.
This backend is equivalent to running in production. It requires a running OTEAPI server.
"},{"location":"examples/otelib/#setup","title":"Setup\u00b6","text":"First, we need to have a running OTEAPI server. If one is not available, we can start one using Docker. See the overview for instructions on how to start a server.
Further documentation about the OTEAPI Service is available as a README on GitHub.
Note
It is advisable to run the OTEAPI server in a separate terminal window, so that we can see the logs from the server. Furthermore, we can stop the server by pressing Ctrl+C
in the terminal window.
After following the instructions, we should have a running OTEAPI server at localhost:80.
"},{"location":"examples/otelib/#create-a-client","title":"Create a client\u00b6","text":""},{"location":"examples/otelib/#search-through-materials-project-for-all-structures-with-the-formula-al2o3","title":"Search through Materials Project for all structures with the formulaAl2O3
\u00b6","text":"To search through Materials Project for all structures with the formula Al2O3
, we need to create the OTE strategies that we want to use for creating the OTE pipeline:
To create the strategies, we need to know how to configure them. This information is available in the OTEAPI Core documentation, specifically for Data Resource strategies and for Filter strategies.
For the data resource strategy, i.e., the OPTIMADE DB strategy, we need to know the base URL of the OPTIMADE API for Materials Project. OPTIMADE has a useful providers dashboard that lists all known OPTIMADE providers and their (sub-)databases. Here we can find the base URL for the Materials Project: https://optimade.materialsproject.org
.
Al
and O
in their chemical formula\u00b6","text":"We must use a different OPTIMADE filter query for this search:
elements HAS ALL \"Al\",\"O\"\n
elements
is a structure attribute that lists all elements in the structure. The HAS ALL
operator matches if, for each value, there is at least one element in elements
equal to that value. (This implements the set operator >=
.)
To do this search, we can reuse the OTE pipeline from the previous search, but change the filter strategy.
"},{"location":"examples/otelib/#search-through-mc3d-database-in-materials-cloud-for-all-structures-that-include-al-and-o-in-their-chemical-formula","title":"Search through MC3D database in Materials Cloud for all structures that includeAl
and O
in their chemical formula\u00b6","text":"Finally, let us reuse the same filter strategy we used for the previous search, but change the data resource strategy to point to the MC3D database in Materials Cloud.
The base URL for the MC3D database is https://aiida.materialscloud.org/mc3d/optimade
as found in the providers dashboard.
With this backend, we do not need a running OTEAPI server.
"},{"location":"examples/otelib/#create-a-client","title":"Create a client\u00b6","text":""},{"location":"examples/otelib/#search-through-materials-project-for-all-structures-with-the-formula-al2o3","title":"Search through Materials Project for all structures with the formulaAl2O3
\u00b6","text":""},{"location":"examples/otelib/#search-through-materials-project-for-all-structures-that-include-al-and-o-in-their-chemical-formula","title":"Search through Materials Project for all structures that include Al
and O
in their chemical formula\u00b6","text":""},{"location":"examples/otelib/#search-through-mc3d-database-in-materials-cloud-for-all-structures-that-include-al-and-o-in-their-chemical-formula","title":"Search through MC3D database in Materials Cloud for all structures that include Al
and O
in their chemical formula\u00b6","text":""}]}
\ No newline at end of file