From fba7bf9817dbb961f9964e7e94d3663126259dab Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Wed, 15 Feb 2023 14:27:57 +0100 Subject: [PATCH 01/16] Use latest SINTEF/ci-cd callable workflows --- .github/workflows/cd_release.yml | 2 +- .github/workflows/ci_automerge_dependency_prs.yml | 2 +- .github/workflows/ci_cd_updated_main.yml | 2 +- .github/workflows/ci_check_dependencies.yml | 2 +- .github/workflows/ci_tests.yml | 2 +- .github/workflows/ci_update_dependencies.yml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/cd_release.yml b/.github/workflows/cd_release.yml index 54a8463..35f4020 100644 --- a/.github/workflows/cd_release.yml +++ b/.github/workflows/cd_release.yml @@ -8,7 +8,7 @@ on: jobs: publish-package-and-docs: name: Call external workflow - uses: SINTEF/ci-cd/.github/workflows/cd_release.yml@v1 + uses: SINTEF/ci-cd/.github/workflows/cd_release.yml@v2.1.0 if: github.repository == 'SINTEF/oteapi-optimade' && startsWith(github.ref, 'refs/tags/v') with: git_username: "TEAM 4.0[bot]" diff --git a/.github/workflows/ci_automerge_dependency_prs.yml b/.github/workflows/ci_automerge_dependency_prs.yml index f34852d..f529e34 100644 --- a/.github/workflows/ci_automerge_dependency_prs.yml +++ b/.github/workflows/ci_automerge_dependency_prs.yml @@ -7,7 +7,7 @@ on: jobs: update-dependencies-branch: name: Call external workflow - uses: SINTEF/ci-cd/.github/workflows/ci_automerge_prs.yml@v1 + uses: SINTEF/ci-cd/.github/workflows/ci_automerge_prs.yml@v2.1.0 if: github.repository_owner == 'SINTEF' && ( ( startsWith(github.event.pull_request.head.ref, 'dependabot/') && github.actor == 'dependabot[bot]' ) || ( github.event.pull_request.head.ref == 'ci/update-pyproject' && github.actor == 'TEAM4-0' ) ) secrets: PAT: ${{ secrets.RELEASE_PAT }} diff --git a/.github/workflows/ci_cd_updated_main.yml b/.github/workflows/ci_cd_updated_main.yml index 4834f9f..36813ab 100644 --- a/.github/workflows/ci_cd_updated_main.yml +++ b/.github/workflows/ci_cd_updated_main.yml @@ -7,7 +7,7 @@ on: jobs: update-deps-branch-and-docs: name: Call external workflow - uses: SINTEF/ci-cd/.github/workflows/ci_cd_updated_default_branch.yml@v1 + uses: SINTEF/ci-cd/.github/workflows/ci_cd_updated_default_branch.yml@v2.1.0 if: github.repository_owner == 'SINTEF' with: git_username: "TEAM 4.0[bot]" diff --git a/.github/workflows/ci_check_dependencies.yml b/.github/workflows/ci_check_dependencies.yml index 3da05a9..fca263b 100644 --- a/.github/workflows/ci_check_dependencies.yml +++ b/.github/workflows/ci_check_dependencies.yml @@ -9,7 +9,7 @@ on: jobs: check-dependencies: name: Call external workflow - uses: SINTEF/ci-cd/.github/workflows/ci_check_pyproject_dependencies.yml@v1 + uses: SINTEF/ci-cd/.github/workflows/ci_check_pyproject_dependencies.yml@v2.1.0 if: github.repository_owner == 'SINTEF' with: git_username: "TEAM 4.0[bot]" diff --git a/.github/workflows/ci_tests.yml b/.github/workflows/ci_tests.yml index 3eb43d5..9500678 100644 --- a/.github/workflows/ci_tests.yml +++ b/.github/workflows/ci_tests.yml @@ -10,7 +10,7 @@ on: jobs: basic-tests: name: Call external workflow - uses: SINTEF/ci-cd/.github/workflows/ci_tests.yml@v1 + uses: SINTEF/ci-cd/.github/workflows/ci_tests.yml@v2.1.0 with: # General setup install_extras: "[dev]" diff --git a/.github/workflows/ci_update_dependencies.yml b/.github/workflows/ci_update_dependencies.yml index 94bed4c..e893f4e 100644 --- a/.github/workflows/ci_update_dependencies.yml +++ b/.github/workflows/ci_update_dependencies.yml @@ -9,7 +9,7 @@ on: jobs: create-collected-pr: name: Call external workflow - uses: SINTEF/ci-cd/.github/workflows/ci_update_dependencies.yml@v1 + uses: SINTEF/ci-cd/.github/workflows/ci_update_dependencies.yml@v2.1.0 if: github.repository_owner == 'SINTEF' with: git_username: "TEAM 4.0[bot]" From 0ce0d1d42d9abed75641beacf37796b6e8cdc24f Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Wed, 15 Feb 2023 14:47:57 +0100 Subject: [PATCH 02/16] Minor code optimization --- oteapi_optimade/strategies/resource.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oteapi_optimade/strategies/resource.py b/oteapi_optimade/strategies/resource.py index 176807e..c0dbd1a 100644 --- a/oteapi_optimade/strategies/resource.py +++ b/oteapi_optimade/strategies/resource.py @@ -133,7 +133,7 @@ def get( # pylint: disable=too-many-branches,too-many-statements optimade_url = OPTIMADEUrl( f"{self.resource_config.accessUrl.base_url}" - f"{'/' + self.resource_config.accessUrl.version if self.resource_config.accessUrl.version else '/v1'}" # pylint: disable=line-too-long + f"/{self.resource_config.accessUrl.version or 'v1'}" f"/{optimade_endpoint}?{optimade_query.generate_query_string()}" ) LOGGER.debug("OPTIMADE URL to be requested: %s", optimade_url) From aea03536d9b1e4ae6ec0c9b218ea79b284051063 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Wed, 15 Feb 2023 14:53:39 +0100 Subject: [PATCH 03/16] Avoid TYPE_CHECKING blocks for coverage --- oteapi_optimade/models/custom_types.py | 2 +- oteapi_optimade/strategies/filter.py | 2 +- oteapi_optimade/strategies/parse.py | 2 +- oteapi_optimade/strategies/resource.py | 2 +- oteapi_optimade/utils.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/oteapi_optimade/models/custom_types.py b/oteapi_optimade/models/custom_types.py index edd2b76..5a826f7 100644 --- a/oteapi_optimade/models/custom_types.py +++ b/oteapi_optimade/models/custom_types.py @@ -21,7 +21,7 @@ from pydantic.utils import update_not_none from pydantic.validators import constr_length_validator, str_validator -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Dict, Optional, Pattern, Tuple, TypedDict, Union from pydantic.config import BaseConfig diff --git a/oteapi_optimade/strategies/filter.py b/oteapi_optimade/strategies/filter.py index 8cf3d7c..3643ef7 100644 --- a/oteapi_optimade/strategies/filter.py +++ b/oteapi_optimade/strategies/filter.py @@ -10,7 +10,7 @@ from oteapi_optimade.models.query import OPTIMADEQueryParameters from oteapi_optimade.utils import model2dict -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Dict, Optional, Union diff --git a/oteapi_optimade/strategies/parse.py b/oteapi_optimade/strategies/parse.py index e51e710..ba7aab4 100644 --- a/oteapi_optimade/strategies/parse.py +++ b/oteapi_optimade/strategies/parse.py @@ -16,7 +16,7 @@ from oteapi_optimade.models import OPTIMADEParseConfig, OPTIMADEParseSession from oteapi_optimade.utils import model2dict -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Dict, Optional, Union diff --git a/oteapi_optimade/strategies/resource.py b/oteapi_optimade/strategies/resource.py index c0dbd1a..c7b3391 100644 --- a/oteapi_optimade/strategies/resource.py +++ b/oteapi_optimade/strategies/resource.py @@ -26,7 +26,7 @@ from oteapi_optimade.models.query import OPTIMADEQueryParameters from oteapi_optimade.utils import model2dict -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Dict, Optional, Union from optimade.models import Response as OPTIMADEResponse diff --git a/oteapi_optimade/utils.py b/oteapi_optimade/utils.py index 4b7408d..9a089c8 100644 --- a/oteapi_optimade/utils.py +++ b/oteapi_optimade/utils.py @@ -4,7 +4,7 @@ from pydantic import BaseModel -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Union From ccc57a509f4049c0fc45eed915002be2804690c3 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Wed, 15 Feb 2023 16:10:41 +0100 Subject: [PATCH 04/16] Simple implementation start for using OTEAPI-DLite --- oteapi_optimade/exceptions.py | 4 ++++ oteapi_optimade/strategies/resource.py | 33 ++++++++++++++++++++++++-- pyproject.toml | 6 +++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/oteapi_optimade/exceptions.py b/oteapi_optimade/exceptions.py index ef2c787..03079ee 100644 --- a/oteapi_optimade/exceptions.py +++ b/oteapi_optimade/exceptions.py @@ -5,6 +5,10 @@ class BaseOteapiOptimadeException(Exception): """Base OTE-API OPTIMADE exception.""" +class MissingDependency(BaseOteapiOptimadeException): + """A required dependency is missing.""" + + class ConfigurationError(BaseOteapiOptimadeException): """An error occurred when dealing with strategy configurations.""" diff --git a/oteapi_optimade/strategies/resource.py b/oteapi_optimade/strategies/resource.py index c7b3391..4324955 100644 --- a/oteapi_optimade/strategies/resource.py +++ b/oteapi_optimade/strategies/resource.py @@ -20,7 +20,14 @@ from oteapi.plugins.entry_points import StrategyType from pydantic.dataclasses import dataclass -from oteapi_optimade.exceptions import OPTIMADEParseError +try: + from oteapi_dlite import __version__ as oteapi_dlite_version + from oteapi_dlite.models import DLiteSessionUpdate + from oteapi_dlite.utils import get_collection +except ImportError: + oteapi_dlite_version = None + +from oteapi_optimade.exceptions import MissingDependency, OPTIMADEParseError from oteapi_optimade.models import OPTIMADEResourceConfig, OPTIMADEResourceSession from oteapi_optimade.models.custom_types import OPTIMADEUrl from oteapi_optimade.models.query import OPTIMADEQueryParameters @@ -37,6 +44,26 @@ LOGGER.addHandler(logging.StreamHandler(sys.stdout)) +def use_dlite(access_service: str) -> bool: + """Determine whether DLite should be utilized in the Resource strategy. + + Parameters: + access_service: The accessService value from the resource's configuration. + + Returns: + Based on the accessService value, then whether DLite should be used or not. + + """ + if any(dlite_form in access_service for dlite_form in ["DLite", "dlite", "Dlite"]): + if oteapi_dlite_version is not None: + raise MissingDependency( + "OTEAPI-DLite is not found on the system. This is required to use " + "DLite with the OTEAPI-OPTIMADE strategies." + ) + return True + return False + + @dataclass class OPTIMADEResourceStrategy: """OPTIMADE Resource Strategy. @@ -53,7 +80,7 @@ class OPTIMADEResourceStrategy: def initialize( # pylint: disable=unused-argument self, session: "Optional[Dict[str, Any]]" = None - ) -> SessionUpdate: + ) -> "Union[SessionUpdate, DLiteSessionUpdate]": """Initialize strategy. This method will be called through the `/initialize` endpoint of the OTE-API @@ -67,6 +94,8 @@ def initialize( # pylint: disable=unused-argument context from services. """ + if use_dlite(self.resource_config.accessService): + return DLiteSessionUpdate(collection_id=get_collection(session).uuid) return SessionUpdate() def get( # pylint: disable=too-many-branches,too-many-statements diff --git a/pyproject.toml b/pyproject.toml index fba78a2..1801f65 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,12 +49,18 @@ pre-commit = [ "pre-commit ~=3.0", "pylint ~=2.15", ] +dlite = [ + "DLite-Python >=0.3.17,<1", + "oteapi-dlite ~=0.1.1", +] dev = [ + "DLite-Python >=0.3.17,<1", "mike ~=1.1", "mkdocs ~=1.4", "mkdocs-awesome-pages-plugin ~=2.8", "mkdocs-material ~=9.0", "mkdocstrings[python-legacy] ~=0.20.0", + "oteapi-dlite ~=0.1.1", "otelib ~=0.2", "pre-commit ~=3.0", "pylint ~=2.15", From 538afc685b8d278f8e5be0beacb0fa64c39f5452 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Wed, 15 Mar 2023 13:40:02 +0100 Subject: [PATCH 05/16] Fix mypy to v1.0.1 - minor code base fixes Minor code base fixes: - Fix documentation rendering for lists in API doc-strings. - Update end2end_test.py according to updated dev tools guidelines. Concerning mypy. Pydantic v1 and mypy are not being updated properly. The latest working version of mypy with Pydantic v1 is v1.0.1, hence, mypy should henceforth be fixed to this version. See https://github.com/pydantic/pydantic/issues/5192 for more information. --- .github/utils/end2end_test.py | 14 +++++++------- .pre-commit-config.yaml | 2 +- mkdocs.yml | 5 +++-- oteapi_optimade/strategies/filter.py | 1 + oteapi_optimade/strategies/parse.py | 1 + 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.github/utils/end2end_test.py b/.github/utils/end2end_test.py index a4c3a3f..d205a58 100644 --- a/.github/utils/end2end_test.py +++ b/.github/utils/end2end_test.py @@ -17,8 +17,8 @@ def _check_import(package_: str) -> str: f"Please install {package_} by running:\n\n" f" pip install {package_}\n\n" ) - else: - return "" + + return "" def _check_service_availability(service_url: str) -> None: @@ -33,9 +33,9 @@ def _check_service_availability(service_url: str) -> None: response = requests.get(f"{service_url}/docs", allow_redirects=True, timeout=30) except (requests.ConnectionError, requests.ConnectTimeout) as exc_: raise RuntimeError(f"Cannot connect to {service_url} !") from exc_ - else: - if not response.ok: - raise RuntimeError(f"Cannot connect to {service_url} !") + + if not response.ok: + raise RuntimeError(f"Cannot connect to {service_url} !") def main(oteapi_url: str) -> None: @@ -62,7 +62,7 @@ def main(oteapi_url: str) -> None: } } - source = client.create_dataresource( + source = client.create_dataresource( # pylint: disable=no-member accessService="OPTIMADE", accessUrl=f"http://localhost:{os.getenv('OPTIMADE_PORT', '5000')}/", configuration=config, @@ -85,7 +85,7 @@ def main(oteapi_url: str) -> None: assert parsed_resource.id in ["mpf_1", "mpf_110"] # Use a filter strategy - query = client.create_filter( + query = client.create_filter( # pylint: disable=no-member filterType="OPTIMADE", query='NOT( elements HAS "Ag" AND nelements>1 )', limit=4, diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 1684b78..925043b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -34,7 +34,7 @@ repos: files: ^oteapi_optimade/.*$ - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.1.1 + rev: v1.0.1 hooks: - id: mypy exclude: ^tests/.*$ diff --git a/mkdocs.yml b/mkdocs.yml index 4ece966..119f2b1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -84,8 +84,6 @@ plugins: setup_commands: - import os - os.environ["MKDOCS_BUILD"] = "1" - watch: - - "oteapi_optimade" - awesome-pages nav: @@ -94,3 +92,6 @@ nav: - Changelog: CHANGELOG.md - all_strategies.md - ... | api_reference/** + +watch: + - "oteapi_optimade" diff --git a/oteapi_optimade/strategies/filter.py b/oteapi_optimade/strategies/filter.py index 8cf3d7c..bca7998 100644 --- a/oteapi_optimade/strategies/filter.py +++ b/oteapi_optimade/strategies/filter.py @@ -45,6 +45,7 @@ def initialize( OPTIMADE resource strategy through this filter strategy. Workflow: + 1. Compile received information. 2. Update session with compiled information. diff --git a/oteapi_optimade/strategies/parse.py b/oteapi_optimade/strategies/parse.py index e51e710..270a58a 100644 --- a/oteapi_optimade/strategies/parse.py +++ b/oteapi_optimade/strategies/parse.py @@ -75,6 +75,7 @@ def get( # pylint: disable=too-many-branches precedence over the derived values from `downloadUrl`. Workflow: + 1. Request OPTIMADE response. 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model. From 728d204553ccdfbc8534037dacc9786e1a4357f6 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Thu, 16 Mar 2023 09:41:44 +0100 Subject: [PATCH 06/16] Create DLite entities for OPTIMADE Structures --- .../dlite/entities/JSONAPIResourceLinks.yaml | 8 ++ .../dlite/entities/OPTIMADERelationships.yaml | 13 ++++ .../dlite/entities/OPTIMADEStructure.yaml | 30 ++++++++ .../entities/OPTIMADEStructureAssembly.yaml | 15 ++++ .../entities/OPTIMADEStructureAttributes.yaml | 75 +++++++++++++++++++ .../entities/OPTIMADEStructureSpecies.yaml | 34 +++++++++ 6 files changed, 175 insertions(+) create mode 100644 oteapi_optimade/dlite/entities/JSONAPIResourceLinks.yaml create mode 100644 oteapi_optimade/dlite/entities/OPTIMADERelationships.yaml create mode 100644 oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml create mode 100644 oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.yaml create mode 100644 oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.yaml create mode 100644 oteapi_optimade/dlite/entities/OPTIMADEStructureSpecies.yaml diff --git a/oteapi_optimade/dlite/entities/JSONAPIResourceLinks.yaml b/oteapi_optimade/dlite/entities/JSONAPIResourceLinks.yaml new file mode 100644 index 0000000..c566982 --- /dev/null +++ b/oteapi_optimade/dlite/entities/JSONAPIResourceLinks.yaml @@ -0,0 +1,8 @@ +uri: http://onto-ns.com/meta/1.0/JSONAPIResourceLinks +meta: http://onto-ns.com/meta/0.3/EntitySchema +description: A Resource Links object. +dimensions: [] +properties: + self: + type: string + description: A link that identifies the resource represented by the resource object. diff --git a/oteapi_optimade/dlite/entities/OPTIMADERelationships.yaml b/oteapi_optimade/dlite/entities/OPTIMADERelationships.yaml new file mode 100644 index 0000000..db72979 --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADERelationships.yaml @@ -0,0 +1,13 @@ +uri: http://onto-ns.com/meta/1.0/OPTIMADERelationships +meta: http://onto-ns.com/meta/0.3/EntitySchema +description: This model wraps the JSON API Relationships to include type-specific top level keys. +dimensions: [] +properties: + references: + type: ref + $ref: http://onto-ns.com/meta/1.0/JSONAPIRelationship + description: Object containing links to relationships with entries of the `references` type. + structures: + type: ref + $ref: http://onto-ns.com/meta/1.0/JSONAPIRelationship + description: Object containing links to relationships with entries of the `structures` type. diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml b/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml new file mode 100644 index 0000000..68f7ea5 --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml @@ -0,0 +1,30 @@ +uri: http://onto-ns.com/meta/1.0/OPTIMADEStructure +meta: http://onto-ns.com/meta/0.3/EntitySchema +description: An OPTIMADE structure. +dimensions: [] +properties: + type: + type: string + description: The name of the type of an entry. Must always be 'structures'. + attributes: + type: ref + $ref: http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes + description: The attributes used to represent a structure, e.g. unit cell, atoms, positions. + id: + type: str + description: An entry's ID as defined in section Definition of Terms. + # relationships: + # type: ref + # $ref: http://onto-ns.com/meta/1.0/OPTIMADERelationships + # description: A dictionary containing references to other entries according to the description in section Relationships encoded as [JSON API Relationships](https://jsonapi.org/format/1.0/#document-resource-object-relationships). The OPTIONAL human-readable description of the relationship MAY be provided in the `description` field inside the `meta` dictionary of the JSON API resource identifier object. + links: + type: ref + $ref: http://onto-ns.com/meta/1.0/JSONAPIResourceLinks + description: A links object containing links related to the resource. + # meta: + # type: ref + # $ref: http://onto-ns.com/meta/1.0/JSONAPIMeta + # description: A meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship. + meta: + type: blob + description: A meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship. diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.yaml b/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.yaml new file mode 100644 index 0000000..bed60c6 --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.yaml @@ -0,0 +1,15 @@ +uri: http://onto-ns.com/meta/1.0/OPTIMADEStructureAssembly +meta: http://onto-ns.com/meta/0.3/EntitySchema +description: A description of groups of sites that are statistically correlated. +dimensions: + ngroups: Number of assembly groups. + nsites: Maximum number of sites in any given group. +properties: + sites_in_groups: + type: int + shape: [ngroups, nsites] + description: Index of the sites (0-based) that belong to each group for each assembly. + group_probabilities: + type: float + shape: [ngroups] + description: Statistical probability of each group. It MUST have the same length as 'sites_in_groups'. diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.yaml b/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.yaml new file mode 100644 index 0000000..d16e82e --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.yaml @@ -0,0 +1,75 @@ +uri: http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes +meta: http://onto-ns.com/meta/0.3/EntitySchema +description: The attributes used to represent a structure, e.g. unit cell, atoms, positions. +dimensions: + nelements: Number of different elements in the structure as an integer. + dimensionality: Number of spatial dimensions. Must always be 3. + nsites: An integer specifying the length of the `cartesian_site_positions` property. + nspecies: Number of species. + nstructure_features: Number of structure features. +properties: + elements: + type: string + shape: [nelements] + description: The chemical symbols of the different elements present in the structure. + nelements: + type: int + description: Number of different elements in the structure as an integer. + elements_ratios: + type: float + shape: [nelements] + description: Relative proportions of different elements in the structure. + chemical_formula_descriptive: + type: string + description: The chemical formula for a structure as a string in a form chosen by the API implementation. + chemical_formula_reduced: + type: string + description: The reduced chemical formula for a structure as a string with element symbols and integer chemical proportion numbers. + chemical_formula_hill: + type: string + description: The chemical formula for a structure in [Hill form](https://dx.doi.org/10.1021/ja02046a005) with element symbols followed by integer chemical proportion numbers. + chemical_formula_anonymous: + type: string + description: The anonymous formula is the `chemical_formula_reduced`, but where the elements are instead first ordered by their chemical proportion number, and then, in order left to right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and so on. + dimension_types: + type: int + shape: [dimensionality] + description: "List of three integers. For each of the three directions indicated by the three lattice vectors (see property `lattice_vectors`), this list indicates if the direction is periodic (value `1`) or non-periodic (value `0`). Note: the elements in this list each refer to the direction of the corresponding entry in `lattice_vectors` and *not* the Cartesian x, y, z directions." + nperiodic_dimensions: + type: int + description: An integer specifying the number of periodic dimensions in the structure, equivalent to the number of non-zero entries in `dimension_types`. + lattice_vectors: + type: float + shape: [dimensionality, dimensionality] + unit: Ångström + description: The three lattice vectors in Cartesian coordinates, in ångström (Å). + cartesian_site_positions: + type: float + shape: [dimensionality, nsites] + description: Cartesian positions of each site in the structure. A site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the `species_at_sites` property, and the species themselves are described in the `species` property. + nsites: + type: int + description: An integer specifying the length of the `cartesian_site_positions` property. + species: + type: ref + $ref: http://onto-ns.com/meta/1.0/OPTIMADEStructureSpecies + shape: [nspecies] + description: A list describing the species of the sites of this structure. Species can represent pure chemical elements, virtual-crystal atoms representing a statistical occupation of a given site by multiple chemical elements, and/or a location to which there are attached atoms, i.e., atoms whose precise location are unknown beyond that they are attached to that position (frequently used to indicate hydrogen atoms attached to another element, e.g., a carbon with three attached hydrogens might represent a methyl group, -CH3). + species_at_sites: + type: string + shape: [nsites] + description: Name of the species at each site (where values for sites are specified with the same order of the property `cartesian_site_positions`). + assemblies: + type: ref + $ref: http://onto-ns.com/meta/1.0/OPTIMADEStructureAssembly + description: A description of groups of sites that are statistically correlated. + structure_features: + type: string + shape: [nstructure_features] + description: A list of strings that flag which special features are used by the structure. + immutable_id: + type: string + description: The entry's immutable ID (e.g., an UUID). This is important for databases having preferred IDs that point to "the latest version" of a record, but still offer access to older variants. This ID maps to the version-specific record, in case it changes in the future. + last_modified: + type: string + description: Date and time representing when the entry was last modified. diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureSpecies.yaml b/oteapi_optimade/dlite/entities/OPTIMADEStructureSpecies.yaml new file mode 100644 index 0000000..db19b1d --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureSpecies.yaml @@ -0,0 +1,34 @@ +uri: http://onto-ns.com/meta/1.0/OPTIMADEStructureSpecies +meta: http://onto-ns.com/meta/0.3/EntitySchema +description: Species can represent pure chemical elements, virtual-crystal atoms representing a statistical occupation of a given site by multiple chemical elements, and/or a location to which there are attached atoms, i.e., atoms whose precise location are unknown beyond that they are attached to that position (frequently used to indicate hydrogen atoms attached to another element, e.g., a carbon with three attached hydrogens might represent a methyl group, -CH3). +dimensions: + nelements: Number of chemical elements composing this species. + nattached_elements: Number of checmical symbols for the elements attached to this species. +properties: + name: + type: string + description: Gives the name of the species; the **name** value MUST be unique in the `species` list. + chemical_symbols: + type: string + shape: [nelements] + description: MUST be a list of strings of all chemical elements composing this species. + concentration: + type: float + shape: [nelements] + description: The numbers represent the relative concentration of the corresponding chemical symbol in this species. The numbers SHOULD sum to one. + mass: + type: float + shape: [nelements] + description: If present MUST be a list of floats expressed in a.m.u. Elements denoting vacancies MUST have masses equal to 0. + unit: a.m.u. + original_name: + type: string + description: Can be any valid Unicode string, and SHOULD contain (if specified) the name of the species that is used internally in the source database. + attached: + type: string + shape: [nattached_elements] + description: If provided MUST be a list of length 1 or more of strings of chemical symbols for the elements attached to this site, or "X" for a non-chemical element. + nattached: + type: int + shape: [nattached_elements] + description: If provided MUST be a list of length 1 or more of integers indicating the number of attached atoms of the kind specified in the value of the 'attached' property. From 5997c6beefbe7c4859c00529ed8dd9dfdd65ab16 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Thu, 16 Mar 2023 09:52:02 +0100 Subject: [PATCH 07/16] Exclude TYPE_CHECKING blocks from code coverage --- oteapi_optimade/models/custom_types.py | 2 +- oteapi_optimade/strategies/filter.py | 2 +- oteapi_optimade/strategies/parse.py | 2 +- oteapi_optimade/strategies/resource.py | 2 +- oteapi_optimade/utils.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/oteapi_optimade/models/custom_types.py b/oteapi_optimade/models/custom_types.py index edd2b76..5a826f7 100644 --- a/oteapi_optimade/models/custom_types.py +++ b/oteapi_optimade/models/custom_types.py @@ -21,7 +21,7 @@ from pydantic.utils import update_not_none from pydantic.validators import constr_length_validator, str_validator -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Dict, Optional, Pattern, Tuple, TypedDict, Union from pydantic.config import BaseConfig diff --git a/oteapi_optimade/strategies/filter.py b/oteapi_optimade/strategies/filter.py index bca7998..840608c 100644 --- a/oteapi_optimade/strategies/filter.py +++ b/oteapi_optimade/strategies/filter.py @@ -10,7 +10,7 @@ from oteapi_optimade.models.query import OPTIMADEQueryParameters from oteapi_optimade.utils import model2dict -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Dict, Optional, Union diff --git a/oteapi_optimade/strategies/parse.py b/oteapi_optimade/strategies/parse.py index 270a58a..68b1c3f 100644 --- a/oteapi_optimade/strategies/parse.py +++ b/oteapi_optimade/strategies/parse.py @@ -16,7 +16,7 @@ from oteapi_optimade.models import OPTIMADEParseConfig, OPTIMADEParseSession from oteapi_optimade.utils import model2dict -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Dict, Optional, Union diff --git a/oteapi_optimade/strategies/resource.py b/oteapi_optimade/strategies/resource.py index 176807e..f54f696 100644 --- a/oteapi_optimade/strategies/resource.py +++ b/oteapi_optimade/strategies/resource.py @@ -26,7 +26,7 @@ from oteapi_optimade.models.query import OPTIMADEQueryParameters from oteapi_optimade.utils import model2dict -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Dict, Optional, Union from optimade.models import Response as OPTIMADEResponse diff --git a/oteapi_optimade/utils.py b/oteapi_optimade/utils.py index 4b7408d..9a089c8 100644 --- a/oteapi_optimade/utils.py +++ b/oteapi_optimade/utils.py @@ -4,7 +4,7 @@ from pydantic import BaseModel -if TYPE_CHECKING: +if TYPE_CHECKING: # pragma: no cover from typing import Any, Union From bb350070017fe66d9b5bb50f95c035e411a9c267 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Fri, 17 Mar 2023 15:58:35 +0100 Subject: [PATCH 08/16] Comment out meta as blob requires size --- oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml b/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml index 68f7ea5..7f0f8c8 100644 --- a/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml @@ -25,6 +25,6 @@ properties: # type: ref # $ref: http://onto-ns.com/meta/1.0/JSONAPIMeta # description: A meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship. - meta: - type: blob - description: A meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship. + # meta: + # type: blob + # description: A meta object containing non-standard meta-information about a resource that can not be represented as an attribute or relationship. From f7d315c2c8e204f9f0fe4a169b8c1b7a78c008ad Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Fri, 17 Mar 2023 16:55:03 +0100 Subject: [PATCH 09/16] Store entities as JSON and start dlite parse strat --- .../dlite/entities/JSONAPIResourceLinks.json | 1 + .../dlite/entities/OPTIMADERelationships.json | 1 + .../dlite/entities/OPTIMADEStructure.json | 1 + .../entities/OPTIMADEStructureAssembly.json | 1 + .../entities/OPTIMADEStructureAttributes.json | 1 + .../entities/OPTIMADEStructureSpecies.json | 1 + oteapi_optimade/dlite/parse.py | 178 ++++++++++++++++++ pyproject.toml | 15 ++ 8 files changed, 199 insertions(+) create mode 100644 oteapi_optimade/dlite/entities/JSONAPIResourceLinks.json create mode 100644 oteapi_optimade/dlite/entities/OPTIMADERelationships.json create mode 100644 oteapi_optimade/dlite/entities/OPTIMADEStructure.json create mode 100644 oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.json create mode 100644 oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.json create mode 100644 oteapi_optimade/dlite/entities/OPTIMADEStructureSpecies.json create mode 100644 oteapi_optimade/dlite/parse.py diff --git a/oteapi_optimade/dlite/entities/JSONAPIResourceLinks.json b/oteapi_optimade/dlite/entities/JSONAPIResourceLinks.json new file mode 100644 index 0000000..75041f2 --- /dev/null +++ b/oteapi_optimade/dlite/entities/JSONAPIResourceLinks.json @@ -0,0 +1 @@ +{"uri": "http://onto-ns.com/meta/1.0/JSONAPIResourceLinks", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "A Resource Links object.", "dimensions": [], "properties": {"self": {"type": "string", "description": "A link that identifies the resource represented by the resource object."}}} diff --git a/oteapi_optimade/dlite/entities/OPTIMADERelationships.json b/oteapi_optimade/dlite/entities/OPTIMADERelationships.json new file mode 100644 index 0000000..026329d --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADERelationships.json @@ -0,0 +1 @@ +{"uri": "http://onto-ns.com/meta/1.0/OPTIMADERelationships", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "This model wraps the JSON API Relationships to include type-specific top level keys.", "dimensions": [], "properties": {"references": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/JSONAPIRelationship", "description": "Object containing links to relationships with entries of the `references` type."}, "structures": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/JSONAPIRelationship", "description": "Object containing links to relationships with entries of the `structures` type."}}} diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructure.json b/oteapi_optimade/dlite/entities/OPTIMADEStructure.json new file mode 100644 index 0000000..532636e --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructure.json @@ -0,0 +1 @@ +{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructure", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "An OPTIMADE structure.", "dimensions": [], "properties": {"type": {"type": "string", "description": "The name of the type of an entry. Must always be 'structures'."}, "attributes": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes", "description": "The attributes used to represent a structure, e.g. unit cell, atoms, positions."}, "id": {"type": "str", "description": "An entry's ID as defined in section Definition of Terms."}, "links": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/JSONAPIResourceLinks", "description": "A links object containing links related to the resource."}}} diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.json b/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.json new file mode 100644 index 0000000..14fcefa --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.json @@ -0,0 +1 @@ +{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAssembly", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "A description of groups of sites that are statistically correlated.", "dimensions": {"ngroups": "Number of assembly groups.", "nsites": "Maximum number of sites in any given group."}, "properties": {"sites_in_groups": {"type": "int", "shape": ["ngroups", "nsites"], "description": "Index of the sites (0-based) that belong to each group for each assembly."}, "group_probabilities": {"type": "float", "shape": ["ngroups"], "description": "Statistical probability of each group. It MUST have the same length as 'sites_in_groups'."}}} diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.json b/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.json new file mode 100644 index 0000000..b6abc0f --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.json @@ -0,0 +1 @@ +{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "The attributes used to represent a structure, e.g. unit cell, atoms, positions.", "dimensions": {"nelements": "Number of different elements in the structure as an integer.", "dimensionality": "Number of spatial dimensions. Must always be 3.", "nsites": "An integer specifying the length of the `cartesian_site_positions` property.", "nspecies": "Number of species.", "nstructure_features": "Number of structure features."}, "properties": {"elements": {"type": "string", "shape": ["nelements"], "description": "The chemical symbols of the different elements present in the structure."}, "nelements": {"type": "int", "description": "Number of different elements in the structure as an integer."}, "elements_ratios": {"type": "float", "shape": ["nelements"], "description": "Relative proportions of different elements in the structure."}, "chemical_formula_descriptive": {"type": "string", "description": "The chemical formula for a structure as a string in a form chosen by the API implementation."}, "chemical_formula_reduced": {"type": "string", "description": "The reduced chemical formula for a structure as a string with element symbols and integer chemical proportion numbers."}, "chemical_formula_hill": {"type": "string", "description": "The chemical formula for a structure in [Hill form](https://dx.doi.org/10.1021/ja02046a005) with element symbols followed by integer chemical proportion numbers."}, "chemical_formula_anonymous": {"type": "string", "description": "The anonymous formula is the `chemical_formula_reduced`, but where the elements are instead first ordered by their chemical proportion number, and then, in order left to right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and so on."}, "dimension_types": {"type": "int", "shape": ["dimensionality"], "description": "List of three integers. For each of the three directions indicated by the three lattice vectors (see property `lattice_vectors`), this list indicates if the direction is periodic (value `1`) or non-periodic (value `0`). Note: the elements in this list each refer to the direction of the corresponding entry in `lattice_vectors` and *not* the Cartesian x, y, z directions."}, "nperiodic_dimensions": {"type": "int", "description": "An integer specifying the number of periodic dimensions in the structure, equivalent to the number of non-zero entries in `dimension_types`."}, "lattice_vectors": {"type": "float", "shape": ["dimensionality", "dimensionality"], "unit": "\u00c5ngstr\u00f6m", "description": "The three lattice vectors in Cartesian coordinates, in \u00e5ngstr\u00f6m (\u00c5)."}, "cartesian_site_positions": {"type": "float", "shape": ["dimensionality", "nsites"], "description": "Cartesian positions of each site in the structure. A site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the `species_at_sites` property, and the species themselves are described in the `species` property."}, "nsites": {"type": "int", "description": "An integer specifying the length of the `cartesian_site_positions` property."}, "species": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/OPTIMADEStructureSpecies", "shape": ["nspecies"], "description": "A list describing the species of the sites of this structure. Species can represent pure chemical elements, virtual-crystal atoms representing a statistical occupation of a given site by multiple chemical elements, and/or a location to which there are attached atoms, i.e., atoms whose precise location are unknown beyond that they are attached to that position (frequently used to indicate hydrogen atoms attached to another element, e.g., a carbon with three attached hydrogens might represent a methyl group, -CH3)."}, "species_at_sites": {"type": "string", "shape": ["nsites"], "description": "Name of the species at each site (where values for sites are specified with the same order of the property `cartesian_site_positions`)."}, "assemblies": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAssembly", "description": "A description of groups of sites that are statistically correlated."}, "structure_features": {"type": "string", "shape": ["nstructure_features"], "description": "A list of strings that flag which special features are used by the structure."}, "immutable_id": {"type": "string", "description": "The entry's immutable ID (e.g., an UUID). This is important for databases having preferred IDs that point to \"the latest version\" of a record, but still offer access to older variants. This ID maps to the version-specific record, in case it changes in the future."}, "last_modified": {"type": "string", "description": "Date and time representing when the entry was last modified."}}} diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureSpecies.json b/oteapi_optimade/dlite/entities/OPTIMADEStructureSpecies.json new file mode 100644 index 0000000..68c025e --- /dev/null +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureSpecies.json @@ -0,0 +1 @@ +{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructureSpecies", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "Species can represent pure chemical elements, virtual-crystal atoms representing a statistical occupation of a given site by multiple chemical elements, and/or a location to which there are attached atoms, i.e., atoms whose precise location are unknown beyond that they are attached to that position (frequently used to indicate hydrogen atoms attached to another element, e.g., a carbon with three attached hydrogens might represent a methyl group, -CH3).", "dimensions": {"nelements": "Number of chemical elements composing this species.", "nattached_elements": "Number of checmical symbols for the elements attached to this species."}, "properties": {"name": {"type": "string", "description": "Gives the name of the species; the **name** value MUST be unique in the `species` list."}, "chemical_symbols": {"type": "string", "shape": ["nelements"], "description": "MUST be a list of strings of all chemical elements composing this species."}, "concentration": {"type": "float", "shape": ["nelements"], "description": "The numbers represent the relative concentration of the corresponding chemical symbol in this species. The numbers SHOULD sum to one."}, "mass": {"type": "float", "shape": ["nelements"], "description": "If present MUST be a list of floats expressed in a.m.u. Elements denoting vacancies MUST have masses equal to 0.", "unit": "a.m.u."}, "original_name": {"type": "string", "description": "Can be any valid Unicode string, and SHOULD contain (if specified) the name of the species that is used internally in the source database."}, "attached": {"type": "string", "shape": ["nattached_elements"], "description": "If provided MUST be a list of length 1 or more of strings of chemical symbols for the elements attached to this site, or \"X\" for a non-chemical element."}, "nattached": {"type": "int", "shape": ["nattached_elements"], "description": "If provided MUST be a list of length 1 or more of integers indicating the number of attached atoms of the kind specified in the value of the 'attached' property."}}} diff --git a/oteapi_optimade/dlite/parse.py b/oteapi_optimade/dlite/parse.py new file mode 100644 index 0000000..f098a48 --- /dev/null +++ b/oteapi_optimade/dlite/parse.py @@ -0,0 +1,178 @@ +"""OTEAPI strategy for parsing OPTIMADE structure resources to DLite instances.""" +import logging +import sys +from pathlib import Path +from typing import TYPE_CHECKING + +import dlite +from optimade.adapters import Structure +from optimade.models import StructureResponseMany, StructureResponseOne +from oteapi.models import SessionUpdate +from oteapi_dlite.models import DLiteSessionUpdate +from oteapi_dlite.utils import get_collection +from pydantic import ValidationError +from pydantic.dataclasses import dataclass + +from oteapi_optimade.exceptions import OPTIMADEParseError +from oteapi_optimade.models import OPTIMADEParseConfig, OPTIMADEParseSession +from oteapi_optimade.strategies.parse import OPTIMADEParseStrategy + +if TYPE_CHECKING: # pragma: no cover + from typing import Any, Dict, Optional, Union + + +LOGGER = logging.getLogger("oteapi_optimade.dlite") +LOGGER.setLevel(logging.DEBUG) +LOGGER.addHandler(logging.StreamHandler(sys.stdout)) + + +@dataclass +class OPTIMADEDLiteParseStrategy: + """Parse strategy for JSON. + + **Implements strategies**: + + - `("mediaType", "application/vnd.optimade+dlite")` + - `("mediaType", "application/vnd.OPTIMADE+dlite")` + - `("mediaType", "application/vnd.OPTiMaDe+dlite")` + - `("mediaType", "application/vnd.optimade+DLite")` + - `("mediaType", "application/vnd.OPTIMADE+DLite")` + - `("mediaType", "application/vnd.OPTiMaDe+DLite")` + + """ + + parse_config: OPTIMADEParseConfig + + def initialize( + self, session: "Optional[Dict[str, Any]]" = None + ) -> DLiteSessionUpdate: + """Initialize strategy. + + This method will be called through the `/initialize` endpoint of the OTE-API + Services. + + Parameters: + session: A session-specific dictionary context. + + Returns: + An update model of key/value-pairs to be stored in the session-specific + context from services. + + """ + return DLiteSessionUpdate(collection_id=get_collection(session).uuid) + + def get( + self, session: "Optional[Union[SessionUpdate, Dict[str, Any]]]" = None + ) -> OPTIMADEParseSession: + """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: + + 1. Request OPTIMADE response. + 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model. + + Parameters: + session: A session-specific dictionary-like context. + + Returns: + An update model of key/value-pairs to be stored in the session-specific + context from services. + + """ + session = OPTIMADEParseStrategy(self.parse_config).get(session) + + entities_path = Path(__file__).resolve().parent.resolve() / "entities" + + dlite.storage_path.append(str(entities_path / "*.json")) + + JSONAPIResourceLinks = dlite.Instance.from_url( + f"json://{entities_path}/JSONAPIResourceLinks.json" + ) + OPTIMADEStructure = dlite.Instance.from_url( + f"json://{entities_path}/OPTIMADEStructure.json" + ) + OPTIMADEStructureAssembly = dlite.Instance.from_url( + f"json://{entities_path}/OPTIMADEStructureAssembly.json" + ) + OPTIMADEStructureAttributes = dlite.Instance.from_url( + f"json://{entities_path}/OPTIMADEStructureAttributes.json" + ) + OPTIMADEStructureSpecies = dlite.Instance.from_url( + f"json://{entities_path}/OPTIMADEStructureSpecies.json" + ) + + dlite_collection = get_collection(session) + + if self.parse_config.configuration.return_object: + if "optimade_response_object" not in session: + raise ValueError( + "'optimade_response_object' was expected to be present in the session." + ) + + # Currently, only structures entries are supported and handled + if isinstance(session.optimade_response_object, StructureResponseMany): + structures = [ + Structure(entry) + if isinstance(entry, dict) + else Structure(entry.dict()) + for entry in session.optimade_response_object.data + ] + elif isinstance(session.optimade_response_object, StructureResponseOne): + structures = [ + Structure(session.optimade_response_object.data) + if isinstance(session.optimade_response_object.data, dict) + else Structure(session.optimade_response_object.data.dict()) + ] + else: + LOGGER.debug( + "Got currently unsupported response type %s. Only structures are supported.", + session.optimade_response_object.__class__.__name__, + ) + raise OPTIMADEParseError( + "The DLite OPTIMADE Parser currently only supports structures entities." + ) + else: + if "optimade_response" not in session: + raise ValueError( + "'optimade_response' was expected to be present in the session." + ) + + if not "data" in session.optimade_response: + LOGGER.debug("Not a successful response - no 'data' entry found.") + return session + + if isinstance(session.optimade_response["data"], list): + try: + structures = [ + Structure(entry) for entry in session.optimade_response["data"] + ] + except ValidationError: + LOGGER.debug( + "Could not parse list of 'data' entries as structures." + ) + raise OPTIMADEParseError( + "The DLite OPTIMADE Parser currently only supports structures entities." + ) + elif session.optimade_response is not None: + try: + structures = [Structure(session.optimade_response["data"])] + except ValidationError: + LOGGER.debug("Could not parse single 'data' entry as a structure.") + raise OPTIMADEParseError( + "The DLite OPTIMADE Parser currently only supports structures entities." + ) + else: + LOGGER.debug("Could not parse 'data' entries as structures.") + raise OPTIMADEParseError( + "The DLite OPTIMADE Parser currently only supports structures entities." + ) + + # TODO: Deal with 'structures' variable + + return session diff --git a/pyproject.toml b/pyproject.toml index 87ed36e..6798643 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ dynamic = ["version"] dependencies = [ "optimade[server] ~=0.22", "oteapi-core ~=0.4", + "oteapi-dlite ~=0.1", "requests ~=2.28", ] @@ -89,6 +90,12 @@ Package = "https://pypi.org/project/oteapi-optimade" "oteapi_optimade.application/vnd.optimade" = "oteapi_optimade.strategies.parse:OPTIMADEParseStrategy" "oteapi_optimade.application/vnd.OPTIMADE" = "oteapi_optimade.strategies.parse:OPTIMADEParseStrategy" "oteapi_optimade.application/vnd.OPTiMaDe" = "oteapi_optimade.strategies.parse:OPTIMADEParseStrategy" +"oteapi_optimade.application/vnd.optimade+dlite" = "oteapi_optimade.dlite.parse:OPTIMADEDLiteParseStrategy" +"oteapi_optimade.application/vnd.OPTIMADE+dlite" = "oteapi_optimade.dlite.parse:OPTIMADEDLiteParseStrategy" +"oteapi_optimade.application/vnd.OPTiMaDe+dlite" = "oteapi_optimade.dlite.parse:OPTIMADEDLiteParseStrategy" +"oteapi_optimade.application/vnd.optimade+DLite" = "oteapi_optimade.dlite.parse:OPTIMADEDLiteParseStrategy" +"oteapi_optimade.application/vnd.OPTIMADE+DLite" = "oteapi_optimade.dlite.parse:OPTIMADEDLiteParseStrategy" +"oteapi_optimade.application/vnd.OPTiMaDe+DLite" = "oteapi_optimade.dlite.parse:OPTIMADEDLiteParseStrategy" [project.entry-points."oteapi.resource"] "oteapi_optimade.optimade" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" "oteapi_optimade.OPTIMADE" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" @@ -99,6 +106,14 @@ Package = "https://pypi.org/project/oteapi-optimade" [tool.flit.module] name = "oteapi_optimade" +[tool.flit.sdist] +exclude = [ + ".github/", + "tests/", + ".gitignore", + ".pre-commit-config.yaml", +] + [tool.mypy] python_version = "3.9" ignore_missing_imports = true From 2ec30011adf1a77ad117167b4c3726c52bb6c25d Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Tue, 28 Mar 2023 11:48:06 +0200 Subject: [PATCH 10/16] Finalize DLite-fying structures --- .../dlite/entities/OPTIMADEStructure.json | 2 +- .../dlite/entities/OPTIMADEStructure.yaml | 8 +- oteapi_optimade/dlite/parse.py | 106 +++++++++++++++--- pyproject.toml | 4 +- 4 files changed, 95 insertions(+), 25 deletions(-) diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructure.json b/oteapi_optimade/dlite/entities/OPTIMADEStructure.json index 532636e..97bfdfa 100644 --- a/oteapi_optimade/dlite/entities/OPTIMADEStructure.json +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructure.json @@ -1 +1 @@ -{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructure", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "An OPTIMADE structure.", "dimensions": [], "properties": {"type": {"type": "string", "description": "The name of the type of an entry. Must always be 'structures'."}, "attributes": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes", "description": "The attributes used to represent a structure, e.g. unit cell, atoms, positions."}, "id": {"type": "str", "description": "An entry's ID as defined in section Definition of Terms."}, "links": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/JSONAPIResourceLinks", "description": "A links object containing links related to the resource."}}} +{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructure", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "An OPTIMADE structure.", "dimensions": [], "properties": {"type": {"type": "string", "description": "The name of the type of an entry. Must always be 'structures'."}, "attributes": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes", "description": "The attributes used to represent a structure, e.g. unit cell, atoms, positions."}, "id": {"type": "str", "description": "An entry's ID as defined in section Definition of Terms."}}} diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml b/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml index 7f0f8c8..c37dbe5 100644 --- a/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructure.yaml @@ -17,10 +17,10 @@ properties: # type: ref # $ref: http://onto-ns.com/meta/1.0/OPTIMADERelationships # description: A dictionary containing references to other entries according to the description in section Relationships encoded as [JSON API Relationships](https://jsonapi.org/format/1.0/#document-resource-object-relationships). The OPTIONAL human-readable description of the relationship MAY be provided in the `description` field inside the `meta` dictionary of the JSON API resource identifier object. - links: - type: ref - $ref: http://onto-ns.com/meta/1.0/JSONAPIResourceLinks - description: A links object containing links related to the resource. + # links: + # type: ref + # $ref: http://onto-ns.com/meta/1.0/JSONAPIResourceLinks + # description: A links object containing links related to the resource. # meta: # type: ref # $ref: http://onto-ns.com/meta/1.0/JSONAPIMeta diff --git a/oteapi_optimade/dlite/parse.py b/oteapi_optimade/dlite/parse.py index f098a48..da5366e 100644 --- a/oteapi_optimade/dlite/parse.py +++ b/oteapi_optimade/dlite/parse.py @@ -1,6 +1,8 @@ """OTEAPI strategy for parsing OPTIMADE structure resources to DLite instances.""" +# pylint: disable=invalid-name,too-many-branches,too-many-statements import logging import sys +from copy import deepcopy from pathlib import Path from typing import TYPE_CHECKING @@ -77,6 +79,12 @@ def get( 1. Request OPTIMADE response. 2. Parse as an OPTIMADE Python tools (OPT) pydantic response model. + --- + + 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. + Parameters: session: A session-specific dictionary-like context. @@ -91,9 +99,9 @@ def get( dlite.storage_path.append(str(entities_path / "*.json")) - JSONAPIResourceLinks = dlite.Instance.from_url( - f"json://{entities_path}/JSONAPIResourceLinks.json" - ) + # JSONAPIResourceLinks = dlite.Instance.from_url( + # f"json://{entities_path}/JSONAPIResourceLinks.json" + # ) OPTIMADEStructure = dlite.Instance.from_url( f"json://{entities_path}/OPTIMADEStructure.json" ) @@ -107,15 +115,16 @@ def get( f"json://{entities_path}/OPTIMADEStructureSpecies.json" ) - dlite_collection = get_collection(session) - if self.parse_config.configuration.return_object: + # The response is given as a "proper" pydantic data model instance + if "optimade_response_object" not in session: raise ValueError( - "'optimade_response_object' was expected to be present in the session." + "'optimade_response_object' was expected to be present in the " + "session." ) - # Currently, only structures entries are supported and handled + # Currently, only "structures" entries are supported and handled if isinstance(session.optimade_response_object, StructureResponseMany): structures = [ Structure(entry) @@ -131,19 +140,23 @@ def get( ] else: LOGGER.debug( - "Got currently unsupported response type %s. Only structures are supported.", + "Got currently unsupported response type %s. Only structures are " + "supported.", session.optimade_response_object.__class__.__name__, ) raise OPTIMADEParseError( - "The DLite OPTIMADE Parser currently only supports structures entities." + "The DLite OPTIMADE Parser currently only supports structures " + "entities." ) else: + # The response is given as pure Python dictionary + if "optimade_response" not in session: raise ValueError( "'optimade_response' was expected to be present in the session." ) - if not "data" in session.optimade_response: + if not session.optimade_response or not "data" in session.optimade_response: LOGGER.debug("Not a successful response - no 'data' entry found.") return session @@ -152,27 +165,84 @@ def get( structures = [ Structure(entry) for entry in session.optimade_response["data"] ] - except ValidationError: + except ValidationError as exc: LOGGER.debug( "Could not parse list of 'data' entries as structures." ) raise OPTIMADEParseError( - "The DLite OPTIMADE Parser currently only supports structures entities." - ) + "The DLite OPTIMADE Parser currently only supports structures " + "entities." + ) from exc elif session.optimade_response is not None: try: structures = [Structure(session.optimade_response["data"])] - except ValidationError: + except ValidationError as exc: LOGGER.debug("Could not parse single 'data' entry as a structure.") raise OPTIMADEParseError( - "The DLite OPTIMADE Parser currently only supports structures entities." - ) + "The DLite OPTIMADE Parser currently only supports structures " + "entities." + ) from exc else: LOGGER.debug("Could not parse 'data' entries as structures.") raise OPTIMADEParseError( - "The DLite OPTIMADE Parser currently only supports structures entities." + "The DLite OPTIMADE Parser currently only supports structures " + "entities." + ) + + dlite_collection = get_collection(session) + + # DLite-fy OPTIMADE structures + for structure in structures: + new_structure_attributes = {} + + # Most inner layer: assemblies & species + if structure.attributes.assemblies: + dimensions = { + "ngroups": len(structure.attributes.assemblies), + "nsites": max(len(_) for _ in structure.attributes.assemblies), + } + new_structure_attributes["assemblies"] = OPTIMADEStructureAssembly( + dimensions=dimensions, properties=structure.attributes.assemblies + ) + if structure.attributes.species: + dimensions = { + "nelements": structure.attributes.nelements, + "nattached_elements": max( + _.nattached for _ in structure.attributes.species + ), + } + new_structure_attributes["species"] = OPTIMADEStructureSpecies( + dimensions=dimensions, + properties=structure.attributes.species, ) - # TODO: Deal with 'structures' variable + # Attributes + attributes = deepcopy(structure.attributes) + attributes.pop("species", None) + attributes.pop("assemblies", None) + new_structure_attributes.update(attributes) + + new_structure = OPTIMADEStructure( + dimensions={}, + properties={ + "attributes": OPTIMADEStructureAttributes( + dimensions={ + "nelements": structure.attributes.nelements or 0, + "dimensionality": 3, + "nsites": structure.attributes.nsites or 0, + "nspecies": len(structure.attributes.species) + if structure.attributes.species + else 0, + "nstructure_features": len( + structure.attributes.structure_features + ), + }, + properties=new_structure_attributes, + ), + "type": structure.entry.type, + "id": structure.entry.id, + }, + ) + dlite_collection.add(label=structure.entry.id, inst=new_structure) return session diff --git a/pyproject.toml b/pyproject.toml index 4654de9..d1b934a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -51,11 +51,11 @@ pre-commit = [ "pylint ~=2.17", ] dlite = [ - "DLite-Python >=0.3.17,<1", + "DLite-Python >=0.3.18,<1", "oteapi-dlite ~=0.1.1", ] dev = [ - "DLite-Python >=0.3.17,<1", + "DLite-Python >=0.3.18,<1", "mike ~=1.1", "mkdocs ~=1.4", "mkdocs-awesome-pages-plugin ~=2.8", From 28f5103c36d7f8b4b0991c6bad6bc6d1a7294ce6 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Tue, 28 Mar 2023 11:49:12 +0200 Subject: [PATCH 11/16] Ensure DLite Collection is updated --- oteapi_optimade/dlite/parse.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/oteapi_optimade/dlite/parse.py b/oteapi_optimade/dlite/parse.py index da5366e..6cb14c2 100644 --- a/oteapi_optimade/dlite/parse.py +++ b/oteapi_optimade/dlite/parse.py @@ -11,7 +11,7 @@ from optimade.models import StructureResponseMany, StructureResponseOne from oteapi.models import SessionUpdate from oteapi_dlite.models import DLiteSessionUpdate -from oteapi_dlite.utils import get_collection +from oteapi_dlite.utils import get_collection, update_collection from pydantic import ValidationError from pydantic.dataclasses import dataclass @@ -245,4 +245,6 @@ def get( ) dlite_collection.add(label=structure.entry.id, inst=new_structure) + update_collection(collection=dlite_collection) + return session From 52e3fe97380d54066a5b00dfda7fa9e52a55deaa Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Tue, 28 Mar 2023 13:05:32 +0200 Subject: [PATCH 12/16] Add test and fix DLite parser accordingly --- .../entities/OPTIMADEStructureAssembly.json | 2 +- .../entities/OPTIMADEStructureAssembly.yaml | 2 +- .../entities/OPTIMADEStructureAttributes.json | 2 +- .../entities/OPTIMADEStructureAttributes.yaml | 2 +- oteapi_optimade/dlite/parse.py | 58 ++++++++++++++----- oteapi_optimade/models/__init__.py | 2 + oteapi_optimade/models/strategies/__init__.py | 3 +- oteapi_optimade/models/strategies/parse.py | 16 +++++ tests/conftest.py | 8 +++ tests/dlite/test_parse.py | 56 ++++++++++++++++++ tests/strategies/conftest.py | 10 ---- 11 files changed, 132 insertions(+), 29 deletions(-) create mode 100644 tests/dlite/test_parse.py delete mode 100644 tests/strategies/conftest.py diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.json b/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.json index 14fcefa..ab0ea9b 100644 --- a/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.json +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.json @@ -1 +1 @@ -{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAssembly", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "A description of groups of sites that are statistically correlated.", "dimensions": {"ngroups": "Number of assembly groups.", "nsites": "Maximum number of sites in any given group."}, "properties": {"sites_in_groups": {"type": "int", "shape": ["ngroups", "nsites"], "description": "Index of the sites (0-based) that belong to each group for each assembly."}, "group_probabilities": {"type": "float", "shape": ["ngroups"], "description": "Statistical probability of each group. It MUST have the same length as 'sites_in_groups'."}}} +{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAssembly", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "A description of groups of sites that are statistically correlated.", "dimensions": {"ngroups": "Number of assembly groups.", "nsites": "Maximum number of sites in any given group."}, "properties": {"sites_in_groups": {"type": "int", "shape": ["nsites", "ngroups"], "description": "Index of the sites (0-based) that belong to each group for each assembly."}, "group_probabilities": {"type": "float", "shape": ["ngroups"], "description": "Statistical probability of each group. It MUST have the same length as 'sites_in_groups'."}}} diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.yaml b/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.yaml index bed60c6..b046519 100644 --- a/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.yaml +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureAssembly.yaml @@ -7,7 +7,7 @@ dimensions: properties: sites_in_groups: type: int - shape: [ngroups, nsites] + shape: [nsites, ngroups] description: Index of the sites (0-based) that belong to each group for each assembly. group_probabilities: type: float diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.json b/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.json index b6abc0f..c3c5750 100644 --- a/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.json +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.json @@ -1 +1 @@ -{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "The attributes used to represent a structure, e.g. unit cell, atoms, positions.", "dimensions": {"nelements": "Number of different elements in the structure as an integer.", "dimensionality": "Number of spatial dimensions. Must always be 3.", "nsites": "An integer specifying the length of the `cartesian_site_positions` property.", "nspecies": "Number of species.", "nstructure_features": "Number of structure features."}, "properties": {"elements": {"type": "string", "shape": ["nelements"], "description": "The chemical symbols of the different elements present in the structure."}, "nelements": {"type": "int", "description": "Number of different elements in the structure as an integer."}, "elements_ratios": {"type": "float", "shape": ["nelements"], "description": "Relative proportions of different elements in the structure."}, "chemical_formula_descriptive": {"type": "string", "description": "The chemical formula for a structure as a string in a form chosen by the API implementation."}, "chemical_formula_reduced": {"type": "string", "description": "The reduced chemical formula for a structure as a string with element symbols and integer chemical proportion numbers."}, "chemical_formula_hill": {"type": "string", "description": "The chemical formula for a structure in [Hill form](https://dx.doi.org/10.1021/ja02046a005) with element symbols followed by integer chemical proportion numbers."}, "chemical_formula_anonymous": {"type": "string", "description": "The anonymous formula is the `chemical_formula_reduced`, but where the elements are instead first ordered by their chemical proportion number, and then, in order left to right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and so on."}, "dimension_types": {"type": "int", "shape": ["dimensionality"], "description": "List of three integers. For each of the three directions indicated by the three lattice vectors (see property `lattice_vectors`), this list indicates if the direction is periodic (value `1`) or non-periodic (value `0`). Note: the elements in this list each refer to the direction of the corresponding entry in `lattice_vectors` and *not* the Cartesian x, y, z directions."}, "nperiodic_dimensions": {"type": "int", "description": "An integer specifying the number of periodic dimensions in the structure, equivalent to the number of non-zero entries in `dimension_types`."}, "lattice_vectors": {"type": "float", "shape": ["dimensionality", "dimensionality"], "unit": "\u00c5ngstr\u00f6m", "description": "The three lattice vectors in Cartesian coordinates, in \u00e5ngstr\u00f6m (\u00c5)."}, "cartesian_site_positions": {"type": "float", "shape": ["dimensionality", "nsites"], "description": "Cartesian positions of each site in the structure. A site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the `species_at_sites` property, and the species themselves are described in the `species` property."}, "nsites": {"type": "int", "description": "An integer specifying the length of the `cartesian_site_positions` property."}, "species": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/OPTIMADEStructureSpecies", "shape": ["nspecies"], "description": "A list describing the species of the sites of this structure. Species can represent pure chemical elements, virtual-crystal atoms representing a statistical occupation of a given site by multiple chemical elements, and/or a location to which there are attached atoms, i.e., atoms whose precise location are unknown beyond that they are attached to that position (frequently used to indicate hydrogen atoms attached to another element, e.g., a carbon with three attached hydrogens might represent a methyl group, -CH3)."}, "species_at_sites": {"type": "string", "shape": ["nsites"], "description": "Name of the species at each site (where values for sites are specified with the same order of the property `cartesian_site_positions`)."}, "assemblies": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAssembly", "description": "A description of groups of sites that are statistically correlated."}, "structure_features": {"type": "string", "shape": ["nstructure_features"], "description": "A list of strings that flag which special features are used by the structure."}, "immutable_id": {"type": "string", "description": "The entry's immutable ID (e.g., an UUID). This is important for databases having preferred IDs that point to \"the latest version\" of a record, but still offer access to older variants. This ID maps to the version-specific record, in case it changes in the future."}, "last_modified": {"type": "string", "description": "Date and time representing when the entry was last modified."}}} +{"uri": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAttributes", "meta": "http://onto-ns.com/meta/0.3/EntitySchema", "description": "The attributes used to represent a structure, e.g. unit cell, atoms, positions.", "dimensions": {"nelements": "Number of different elements in the structure as an integer.", "dimensionality": "Number of spatial dimensions. Must always be 3.", "nsites": "An integer specifying the length of the `cartesian_site_positions` property.", "nspecies": "Number of species.", "nstructure_features": "Number of structure features."}, "properties": {"elements": {"type": "string", "shape": ["nelements"], "description": "The chemical symbols of the different elements present in the structure."}, "nelements": {"type": "int", "description": "Number of different elements in the structure as an integer."}, "elements_ratios": {"type": "float", "shape": ["nelements"], "description": "Relative proportions of different elements in the structure."}, "chemical_formula_descriptive": {"type": "string", "description": "The chemical formula for a structure as a string in a form chosen by the API implementation."}, "chemical_formula_reduced": {"type": "string", "description": "The reduced chemical formula for a structure as a string with element symbols and integer chemical proportion numbers."}, "chemical_formula_hill": {"type": "string", "description": "The chemical formula for a structure in [Hill form](https://dx.doi.org/10.1021/ja02046a005) with element symbols followed by integer chemical proportion numbers."}, "chemical_formula_anonymous": {"type": "string", "description": "The anonymous formula is the `chemical_formula_reduced`, but where the elements are instead first ordered by their chemical proportion number, and then, in order left to right, replaced by anonymous symbols A, B, C, ..., Z, Aa, Ba, ..., Za, Ab, Bb, ... and so on."}, "dimension_types": {"type": "int", "shape": ["dimensionality"], "description": "List of three integers. For each of the three directions indicated by the three lattice vectors (see property `lattice_vectors`), this list indicates if the direction is periodic (value `1`) or non-periodic (value `0`). Note: the elements in this list each refer to the direction of the corresponding entry in `lattice_vectors` and *not* the Cartesian x, y, z directions."}, "nperiodic_dimensions": {"type": "int", "description": "An integer specifying the number of periodic dimensions in the structure, equivalent to the number of non-zero entries in `dimension_types`."}, "lattice_vectors": {"type": "float", "shape": ["dimensionality", "dimensionality"], "unit": "\u00c5ngstr\u00f6m", "description": "The three lattice vectors in Cartesian coordinates, in \u00e5ngstr\u00f6m (\u00c5)."}, "cartesian_site_positions": {"type": "float", "shape": ["nsites", "dimensionality"], "description": "Cartesian positions of each site in the structure. A site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the `species_at_sites` property, and the species themselves are described in the `species` property."}, "nsites": {"type": "int", "description": "An integer specifying the length of the `cartesian_site_positions` property."}, "species": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/OPTIMADEStructureSpecies", "shape": ["nspecies"], "description": "A list describing the species of the sites of this structure. Species can represent pure chemical elements, virtual-crystal atoms representing a statistical occupation of a given site by multiple chemical elements, and/or a location to which there are attached atoms, i.e., atoms whose precise location are unknown beyond that they are attached to that position (frequently used to indicate hydrogen atoms attached to another element, e.g., a carbon with three attached hydrogens might represent a methyl group, -CH3)."}, "species_at_sites": {"type": "string", "shape": ["nsites"], "description": "Name of the species at each site (where values for sites are specified with the same order of the property `cartesian_site_positions`)."}, "assemblies": {"type": "ref", "$ref": "http://onto-ns.com/meta/1.0/OPTIMADEStructureAssembly", "description": "A description of groups of sites that are statistically correlated."}, "structure_features": {"type": "string", "shape": ["nstructure_features"], "description": "A list of strings that flag which special features are used by the structure."}, "immutable_id": {"type": "string", "description": "The entry's immutable ID (e.g., an UUID). This is important for databases having preferred IDs that point to \"the latest version\" of a record, but still offer access to older variants. This ID maps to the version-specific record, in case it changes in the future."}, "last_modified": {"type": "string", "description": "Date and time representing when the entry was last modified."}}} diff --git a/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.yaml b/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.yaml index d16e82e..2de8fc1 100644 --- a/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.yaml +++ b/oteapi_optimade/dlite/entities/OPTIMADEStructureAttributes.yaml @@ -45,7 +45,7 @@ properties: description: The three lattice vectors in Cartesian coordinates, in ångström (Å). cartesian_site_positions: type: float - shape: [dimensionality, nsites] + shape: [nsites, dimensionality] description: Cartesian positions of each site in the structure. A site is usually used to describe positions of atoms; what atoms can be encountered at a given site is conveyed by the `species_at_sites` property, and the species themselves are described in the `species` property. nsites: type: int diff --git a/oteapi_optimade/dlite/parse.py b/oteapi_optimade/dlite/parse.py index 6cb14c2..1f11e52 100644 --- a/oteapi_optimade/dlite/parse.py +++ b/oteapi_optimade/dlite/parse.py @@ -2,21 +2,20 @@ # pylint: disable=invalid-name,too-many-branches,too-many-statements import logging import sys -from copy import deepcopy from pathlib import Path from typing import TYPE_CHECKING import dlite from optimade.adapters import Structure -from optimade.models import StructureResponseMany, StructureResponseOne +from optimade.models import StructureResponseMany, StructureResponseOne, Success from oteapi.models import SessionUpdate from oteapi_dlite.models import DLiteSessionUpdate from oteapi_dlite.utils import get_collection, update_collection -from pydantic import ValidationError +from pydantic import BaseModel, ValidationError from pydantic.dataclasses import dataclass from oteapi_optimade.exceptions import OPTIMADEParseError -from oteapi_optimade.models import OPTIMADEParseConfig, OPTIMADEParseSession +from oteapi_optimade.models import OPTIMADEDLiteParseConfig, OPTIMADEParseSession from oteapi_optimade.strategies.parse import OPTIMADEParseStrategy if TYPE_CHECKING: # pragma: no cover @@ -43,7 +42,7 @@ class OPTIMADEDLiteParseStrategy: """ - parse_config: OPTIMADEParseConfig + parse_config: OPTIMADEDLiteParseConfig def initialize( self, session: "Optional[Dict[str, Any]]" = None @@ -138,6 +137,28 @@ def get( if isinstance(session.optimade_response_object.data, dict) else Structure(session.optimade_response_object.data.dict()) ] + elif isinstance(session.optimade_response_object, Success): + if isinstance(session.optimade_response_object.data, dict): + structures = [Structure(session.optimade_response_object.data)] + elif isinstance(session.optimade_response_object.data, BaseModel): + structures = [ + Structure(session.optimade_response_object.data.dict()) + ] + elif isinstance(session.optimade_response_object.data, list): + structures = [ + Structure(entry) + if isinstance(entry, dict) + else Structure(entry.dict()) + for entry in session.optimade_response_object.data + ] + else: + LOGGER.debug( + "Could not determine what to do with `data`. Type %s.", + type(session.optimade_response_object.data), + ) + raise OPTIMADEParseError( + "Could not parse `data` entry in response." + ) else: LOGGER.debug( "Got currently unsupported response type %s. Only structures are " @@ -208,19 +229,28 @@ def get( dimensions = { "nelements": structure.attributes.nelements, "nattached_elements": max( - _.nattached for _ in structure.attributes.species + _.nattached or 0 for _ in structure.attributes.species ), } - new_structure_attributes["species"] = OPTIMADEStructureSpecies( - dimensions=dimensions, - properties=structure.attributes.species, - ) + new_structure_attributes["species"] = [ + OPTIMADEStructureSpecies( + dimensions=dimensions, + properties=species, + ) + for species in structure.attributes.species + ] # Attributes - attributes = deepcopy(structure.attributes) - attributes.pop("species", None) - attributes.pop("assemblies", None) - new_structure_attributes.update(attributes) + new_structure_attributes.update( + structure.attributes.dict(exclude={"species", "assemblies"}) + ) + for key in list(new_structure_attributes): + if key.startswith("_"): + new_structure_attributes.pop(key) + + new_structure_attributes["structure_features"] = [ + _.value for _ in new_structure_attributes["structure_features"] + ] new_structure = OPTIMADEStructure( dimensions={}, diff --git a/oteapi_optimade/models/__init__.py b/oteapi_optimade/models/__init__.py index 4b0aceb..b5cc5d9 100644 --- a/oteapi_optimade/models/__init__.py +++ b/oteapi_optimade/models/__init__.py @@ -1,5 +1,6 @@ """`oteapi_optimade.models` module - pydantic data models.""" from .strategies import ( + OPTIMADEDLiteParseConfig, OPTIMADEFilterConfig, OPTIMADEFilterSession, OPTIMADEParseConfig, @@ -9,6 +10,7 @@ ) __all__ = ( + "OPTIMADEDLiteParseConfig", "OPTIMADEFilterConfig", "OPTIMADEFilterSession", "OPTIMADEParseConfig", diff --git a/oteapi_optimade/models/strategies/__init__.py b/oteapi_optimade/models/strategies/__init__.py index 52657ac..bf2cae4 100644 --- a/oteapi_optimade/models/strategies/__init__.py +++ b/oteapi_optimade/models/strategies/__init__.py @@ -1,9 +1,10 @@ """`oteapi_optimade.models` module - pydantic data models.""" from .filter import OPTIMADEFilterConfig, OPTIMADEFilterSession -from .parse import OPTIMADEParseConfig, OPTIMADEParseSession +from .parse import OPTIMADEDLiteParseConfig, OPTIMADEParseConfig, OPTIMADEParseSession from .resource import OPTIMADEResourceConfig, OPTIMADEResourceSession __all__ = ( + "OPTIMADEDLiteParseConfig", "OPTIMADEFilterConfig", "OPTIMADEFilterSession", "OPTIMADEParseConfig", diff --git a/oteapi_optimade/models/strategies/parse.py b/oteapi_optimade/models/strategies/parse.py index c90149d..74728fd 100644 --- a/oteapi_optimade/models/strategies/parse.py +++ b/oteapi_optimade/models/strategies/parse.py @@ -63,3 +63,19 @@ class Config: validate_assignment = True arbitrary_types_allowed = True + + +class OPTIMADEDLiteParseConfig(OPTIMADEParseConfig): + """OPTIMADE-specific parse strategy config.""" + + mediaType: Literal[ + "application/vnd.optimade+dlite", + "application/vnd.OPTIMADE+dlite", + "application/vnd.OPTiMaDe+dlite", + "application/vnd.optimade+DLite", + "application/vnd.OPTIMADE+DLite", + "application/vnd.OPTiMaDe+Dlite", + ] = Field( # type: ignore[assignment] + ..., + description="The registered strategy name for OPTIMADEDLiteParseStrategy.", + ) diff --git a/tests/conftest.py b/tests/conftest.py index e211d90..08291c1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,3 +30,11 @@ def top_dir() -> "Path": def static_files(top_dir: "Path") -> "Path": """Return Path object to the `static` folder.""" return (top_dir / "tests" / "static").resolve() + + +@pytest.fixture(scope="session", autouse=True) +def load_strategies() -> None: + """Load entry points strategies.""" + from oteapi.plugins import load_strategies + + load_strategies(test_for_uniqueness=False) diff --git a/tests/dlite/test_parse.py b/tests/dlite/test_parse.py new file mode 100644 index 0000000..2e0855e --- /dev/null +++ b/tests/dlite/test_parse.py @@ -0,0 +1,56 @@ +"""Test `oteapi_optimade.dlite.parse` module.""" +from typing import TYPE_CHECKING + +import pytest + +if TYPE_CHECKING: + from pathlib import Path + + +@pytest.mark.parametrize("return_object", [True, False]) +def test_parse(static_files: "Path", return_object: bool) -> None: + """Test parsing.""" + import json + + from oteapi.datacache import DataCache + from oteapi_dlite.utils import get_collection + + from oteapi_optimade.dlite.parse import OPTIMADEDLiteParseStrategy + from oteapi_optimade.models import OPTIMADEDLiteParseConfig + from oteapi_optimade.models.custom_types import OPTIMADEUrl + + url = OPTIMADEUrl( + "https://example.org/some/base/v0.1/optimade/v1/structures" + '?filter=elements HAS ALL "Si","O"&sort=nelements&page_limit=2' + ) + config = OPTIMADEDLiteParseConfig( + **{ + "mediaType": "application/vnd.OPTIMADE+DLite", + "downloadUrl": url, + "configuration": { + "datacache_config": { + "expireTime": 60 * 60 * 24, + "tag": "optimade", + "accessKey": url, + }, + "return_object": return_object, + }, + } + ) + + cache = DataCache(config.configuration.datacache_config) + sample_file = static_files / "optimade_response.json" + cache.add( + { + "status_code": 200, + "ok": True, + "json": json.loads(sample_file.read_bytes()), + } + ) + + session = OPTIMADEDLiteParseStrategy(config).initialize({}) + session = OPTIMADEDLiteParseStrategy(config).get(session) + + dlite_collection = get_collection(session) + assert dlite_collection + print(dlite_collection) diff --git a/tests/strategies/conftest.py b/tests/strategies/conftest.py deleted file mode 100644 index f568ee6..0000000 --- a/tests/strategies/conftest.py +++ /dev/null @@ -1,10 +0,0 @@ -"""Pytest fixtures for `strategies/`.""" -import pytest - - -@pytest.fixture(scope="session", autouse=True) -def load_strategies() -> None: - """Load entry points strategies.""" - from oteapi.plugins import load_strategies - - load_strategies(test_for_uniqueness=False) From 309699a8640db3305028a695852841dba6233e68 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Tue, 28 Mar 2023 13:32:26 +0200 Subject: [PATCH 13/16] Support DLite for resource strategy --- oteapi_optimade/models/config.py | 4 ++ oteapi_optimade/models/strategies/parse.py | 2 +- oteapi_optimade/models/strategies/resource.py | 12 ++++- oteapi_optimade/strategies/resource.py | 39 +++++++++++--- pyproject.toml | 6 +++ tests/dlite/test_parse.py | 1 - tests/strategies/test_resource.py | 54 +++++++++++++++++++ 7 files changed, 107 insertions(+), 11 deletions(-) diff --git a/oteapi_optimade/models/config.py b/oteapi_optimade/models/config.py index 312db09..5bf84d0 100644 --- a/oteapi_optimade/models/config.py +++ b/oteapi_optimade/models/config.py @@ -41,6 +41,10 @@ class OPTIMADEConfig(AttrDict): "directly and not via an OTEAPI REST API service." ), ) + use_dlite: bool = Field( + False, + description="Whether or not to store the results in a DLite Collection.", + ) @validator("datacache_config") def default_datacache_config(cls, value: DataCacheConfig) -> DataCacheConfig: diff --git a/oteapi_optimade/models/strategies/parse.py b/oteapi_optimade/models/strategies/parse.py index 74728fd..a1215c4 100644 --- a/oteapi_optimade/models/strategies/parse.py +++ b/oteapi_optimade/models/strategies/parse.py @@ -74,7 +74,7 @@ class OPTIMADEDLiteParseConfig(OPTIMADEParseConfig): "application/vnd.OPTiMaDe+dlite", "application/vnd.optimade+DLite", "application/vnd.OPTIMADE+DLite", - "application/vnd.OPTiMaDe+Dlite", + "application/vnd.OPTiMaDe+DLite", ] = Field( # type: ignore[assignment] ..., description="The registered strategy name for OPTIMADEDLiteParseStrategy.", diff --git a/oteapi_optimade/models/strategies/resource.py b/oteapi_optimade/models/strategies/resource.py index 284397a..bbf9763 100644 --- a/oteapi_optimade/models/strategies/resource.py +++ b/oteapi_optimade/models/strategies/resource.py @@ -15,7 +15,17 @@ class OPTIMADEResourceConfig(ResourceConfig): ..., description="Either a base OPTIMADE URL or a full OPTIMADE URL.", ) - accessService: Literal["optimade", "OPTIMADE", "OPTiMaDe"] = Field( + accessService: Literal[ + "optimade", + "OPTIMADE", + "OPTiMaDe", + "optimade+dlite", + "OPTIMADE+dlite", + "OPTiMaDe+dlite", + "optimade+DLite", + "OPTIMADE+DLite", + "OPTiMaDe+DLite", + ] = Field( ..., description="The registered strategy name for OPTIMADEResourceStrategy.", ) diff --git a/oteapi_optimade/strategies/resource.py b/oteapi_optimade/strategies/resource.py index 4324955..7a3d03b 100644 --- a/oteapi_optimade/strategies/resource.py +++ b/oteapi_optimade/strategies/resource.py @@ -44,18 +44,22 @@ LOGGER.addHandler(logging.StreamHandler(sys.stdout)) -def use_dlite(access_service: str) -> bool: +def use_dlite(access_service: str, use_dlite_flag: bool) -> bool: """Determine whether DLite should be utilized in the Resource strategy. Parameters: access_service: The accessService value from the resource's configuration. + use_dlite_flag: The strategy-specific `use_dlite` configuration option. Returns: Based on the accessService value, then whether DLite should be used or not. """ - if any(dlite_form in access_service for dlite_form in ["DLite", "dlite", "Dlite"]): - if oteapi_dlite_version is not None: + if ( + any(dlite_form in access_service for dlite_form in ["DLite", "dlite"]) + or use_dlite_flag + ): + if oteapi_dlite_version is None: raise MissingDependency( "OTEAPI-DLite is not found on the system. This is required to use " "DLite with the OTEAPI-OPTIMADE strategies." @@ -73,6 +77,12 @@ class OPTIMADEResourceStrategy: - `("accessService", "optimade")` - `("accessService", "OPTIMADE")` - `("accessService", "OPTiMaDe")` + - `("accessService", "optimade+dlite")` + - `("accessService", "OPTIMADE+dlite")` + - `("accessService", "OPTiMaDe+dlite")` + - `("accessService", "optimade+DLite")` + - `("accessService", "OPTIMADE+DLite")` + - `("accessService", "OPTiMaDe+DLite")` """ @@ -94,7 +104,10 @@ def initialize( # pylint: disable=unused-argument context from services. """ - if use_dlite(self.resource_config.accessService): + if use_dlite( + self.resource_config.accessService, + self.resource_config.configuration.use_dlite, + ): return DLiteSessionUpdate(collection_id=get_collection(session).uuid) return SessionUpdate() @@ -192,12 +205,22 @@ def get( # pylint: disable=too-many-branches,too-many-statements } ) + parse_with_dlite = use_dlite( + self.resource_config.accessService, + self.resource_config.configuration.use_dlite, + ) + + parse_mediaType = f"application/vnd.{self.resource_config.accessService.split('+', maxsplit=1)[0]}" # pylint: disable=invalid-name,line-too-long + if parse_with_dlite: + parse_mediaType += "+DLite" # pylint: disable=invalid-name + elif optimade_query.response_format: + parse_mediaType += ( # pylint: disable=invalid-name + f"+{optimade_query.response_format}" + ) + parse_config = { "downloadUrl": optimade_url, - "mediaType": ( - f"application/vnd.{self.resource_config.accessService}" - f"{'+' + optimade_query.response_format if optimade_query.response_format else ''}" # pylint: disable=line-too-long - ), + "mediaType": parse_mediaType, "configuration": { "datacache_config": self.resource_config.configuration.datacache_config, "return_object": True, diff --git a/pyproject.toml b/pyproject.toml index d1b934a..69d4c2d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -106,6 +106,12 @@ Package = "https://pypi.org/project/oteapi-optimade" "oteapi_optimade.optimade" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" "oteapi_optimade.OPTIMADE" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" "oteapi_optimade.OPTiMaDe" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" +"oteapi_optimade.optimade+dlite" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" +"oteapi_optimade.OPTIMADE+dlite" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" +"oteapi_optimade.OPTiMaDe+dlite" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" +"oteapi_optimade.optimade+DLite" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" +"oteapi_optimade.OPTIMADE+DLite" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" +"oteapi_optimade.OPTiMaDe+DLite" = "oteapi_optimade.strategies.resource:OPTIMADEResourceStrategy" [project.entry-points."oteapi.transformation"] diff --git a/tests/dlite/test_parse.py b/tests/dlite/test_parse.py index 2e0855e..038c587 100644 --- a/tests/dlite/test_parse.py +++ b/tests/dlite/test_parse.py @@ -53,4 +53,3 @@ def test_parse(static_files: "Path", return_object: bool) -> None: dlite_collection = get_collection(session) assert dlite_collection - print(dlite_collection) diff --git a/tests/strategies/test_resource.py b/tests/strategies/test_resource.py index b26bfd3..d9b0ef2 100644 --- a/tests/strategies/test_resource.py +++ b/tests/strategies/test_resource.py @@ -59,3 +59,57 @@ def test_get_no_session( assert session.optimade_resources for resource in session.optimade_resources: assert Structure(resource) + + +@pytest.mark.parametrize( + "accessService,use_dlite", + [ + ("optimade+dlite", False), + ("OPTIMADE+dlite", False), + ("OPTiMaDe+dlite", False), + ("optimade+DLite", False), + ("OPTIMADE+DLite", False), + ("OPTiMaDe+DLite", False), + ("optimade", True), + ("OPTIMADE", True), + ("OPTiMaDe", True), + ("optimade+dlite", True), + ("OPTIMADE+dlite", True), + ("OPTiMaDe+dlite", True), + ("optimade+DLite", True), + ("OPTIMADE+DLite", True), + ("OPTiMaDe+DLite", True), + ], +) +def test_use_dlite( + resource_config: dict[str, str], + static_files: "Path", + requests_mock: "Mocker", + accessService: str, # pylint: disable=invalid-name + use_dlite: bool, +) -> None: + """Test the `get()` method - session is `None` and use_dlite is True.""" + from optimade.adapters import Structure + from oteapi_dlite.utils import get_collection + + from oteapi_optimade.models.strategies.resource import OPTIMADEResourceSession + from oteapi_optimade.strategies.resource import OPTIMADEResourceStrategy + + sample_file = static_files / "optimade_response.json" + requests_mock.get(resource_config["accessUrl"], content=sample_file.read_bytes()) + + resource_config["accessService"] = accessService + resource_config["configuration"] = {"use_dlite": use_dlite} + + session = OPTIMADEResourceStrategy(resource_config).get() + + assert isinstance(session, OPTIMADEResourceSession) + assert session.optimade_config is None + assert session.optimade_resource_model == f"{Structure.__module__}:Structure" + assert session.optimade_resources + for resource in session.optimade_resources: + assert Structure(resource) + + assert "collection_id" in session + dlite_collection = get_collection(session) + assert dlite_collection From f610106731a95c443ea411420bcccb7cf0cc64da Mon Sep 17 00:00:00 2001 From: Thomas Hagelien Date: Thu, 30 Mar 2023 11:22:49 +0200 Subject: [PATCH 14/16] Updated oteapi-dlite version dependency --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 69d4c2d..266db1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dynamic = ["version"] dependencies = [ "optimade[server] ~=0.22", "oteapi-core ~=0.4", - "oteapi-dlite ~=0.1", + "oteapi-dlite ~=0.1.1", "requests ~=2.28", ] From c04b8b027375d85214bed41688c2f867dc98a7b9 Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Thu, 30 Mar 2023 20:40:49 +0200 Subject: [PATCH 15/16] Only install oteapi-dlite if so desired Also, align the version to be the same. --- pyproject.toml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 266db1d..66902e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,6 @@ dynamic = ["version"] dependencies = [ "optimade[server] ~=0.22", "oteapi-core ~=0.4", - "oteapi-dlite ~=0.1.1", "requests ~=2.28", ] @@ -52,7 +51,7 @@ pre-commit = [ ] dlite = [ "DLite-Python >=0.3.18,<1", - "oteapi-dlite ~=0.1.1", + "oteapi-dlite >=0.1.1,<1", ] dev = [ "DLite-Python >=0.3.18,<1", @@ -61,7 +60,7 @@ dev = [ "mkdocs-awesome-pages-plugin ~=2.8", "mkdocs-material ~=9.1", "mkdocstrings[python-legacy] ~=0.20.0", - "oteapi-dlite ~=0.1.1", + "oteapi-dlite >=0.1.1,<1", "otelib ~=0.2", "pre-commit ~=3.1", "pylint ~=2.17", From 26e7860caba2b41ba785188e3908fa15661cad9c Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Thu, 30 Mar 2023 20:49:11 +0200 Subject: [PATCH 16/16] Move dlite to required dependency --- pyproject.toml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 66902e8..c3b49da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,8 +26,10 @@ requires-python = "~=3.9" dynamic = ["version"] dependencies = [ + "DLite-Python >=0.3.18,<1", "optimade[server] ~=0.22", "oteapi-core ~=0.4", + "oteapi-dlite >=0.1.1,<1", "requests ~=2.28", ] @@ -49,18 +51,12 @@ pre-commit = [ "pre-commit ~=3.1", "pylint ~=2.17", ] -dlite = [ - "DLite-Python >=0.3.18,<1", - "oteapi-dlite >=0.1.1,<1", -] dev = [ - "DLite-Python >=0.3.18,<1", "mike ~=1.1", "mkdocs ~=1.4", "mkdocs-awesome-pages-plugin ~=2.8", "mkdocs-material ~=9.1", "mkdocstrings[python-legacy] ~=0.20.0", - "oteapi-dlite >=0.1.1,<1", "otelib ~=0.2", "pre-commit ~=3.1", "pylint ~=2.17",