Skip to content

Commit

Permalink
Ensure minimized fields works
Browse files Browse the repository at this point in the history
Build attributes only with supplied "desired" fields.
This ensures the `-m/--minimized-fields` option for `aiida-optimade
init` works as intended.
  • Loading branch information
CasperWA committed Jan 9, 2021
1 parent bdb80b2 commit ecc9bf1
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 24 deletions.
17 changes: 10 additions & 7 deletions aiida_optimade/cli/cmd_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,17 @@ def init(obj: dict, force: bool, silent: bool, minimized_fields: bool):

STRUCTURES._filter_fields = set()
if minimized_fields:
STRUCTURES._alias_filter(
dict.fromkeys(
[
"structure_features", # required (will create species)
],
None,
)
minimized_keys = (
STRUCTURES.resource_mapper.TOP_LEVEL_NON_ATTRIBUTES_FIELDS.copy()
)
minimized_keys |= STRUCTURES.get_attribute_fields()
minimized_keys |= {
f"_{STRUCTURES.provider}_" + _ for _ in STRUCTURES.provider_fields
}
minimized_keys.difference_update(
{"cartesian_site_positions", "nsites", "species_at_sites"}
)
STRUCTURES._alias_filter(dict.fromkeys(minimized_keys, None))
else:
STRUCTURES._alias_filter({"nsites": None})

Expand Down
9 changes: 7 additions & 2 deletions aiida_optimade/entry_collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def find(
all_fields = criteria.pop("fields")
if getattr(params, "response_fields", False):
fields = set(params.response_fields.split(","))
fields |= self.resource_mapper.get_required_fields()
fields |= self.resource_mapper.TOP_LEVEL_NON_ATTRIBUTES_FIELDS
else:
fields = all_fields.copy()

Expand Down Expand Up @@ -362,7 +362,7 @@ def _update_entities(entities: list, fields: list):
necessary_entity_ids = [pk[0] for pk in necessary_entities_qb]

# Create the missing OPTIMADE fields:
fields = {"id", "type"}
fields = self.resource_mapper.TOP_LEVEL_NON_ATTRIBUTES_FIELDS.copy()
if all_fields:
# All OPTIMADE fields
fields |= self.get_attribute_fields()
Expand All @@ -373,6 +373,11 @@ def _update_entities(entities: list, fields: list):
# "id" and "type" are ALWAYS needed though, hence `fields` is initiated
# with these values
fields |= self._get_extras_filter_fields()
fields |= {
f"_{self.provider}_" + _
for _ in self._filter_fields
if _ in self.provider_fields
}
fields = list({self.resource_mapper.alias_for(f) for f in fields})

entities = self._find_all(
Expand Down
12 changes: 11 additions & 1 deletion aiida_optimade/mappers/entries.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ class ResourceMapper(OptimadeResourceMapper):

TRANSLATORS: Dict[str, AiidaEntityTranslator]
ALL_ATTRIBUTES: set = set()
REQUIRED_ATTRIBUTES: set = set()

@classmethod
def all_aliases(cls) -> Tuple[Tuple[str, str]]:
Expand All @@ -40,6 +39,8 @@ def map_back(cls, entity_properties: dict) -> dict:
:return: A resource object in OPTIMADE format
:rtype: dict
"""
from optimade.server.config import CONFIG

new_object_attributes = {}
new_object = {}

Expand All @@ -64,8 +65,13 @@ def map_back(cls, entity_properties: dict) -> dict:
if value is not None:
new_object[field] = value

mapping = {aiida: optimade for optimade, aiida in cls.all_aliases()}

new_object["attributes"] = cls.build_attributes(
retrieved_attributes=new_object_attributes,
desired_attributes={mapping.get(_, _) for _ in entity_properties}
- cls.TOP_LEVEL_NON_ATTRIBUTES_FIELDS
- set(CONFIG.aliases.get(cls.ENDPOINT, {}).keys()),
entry_pk=new_object["id"],
node_type=new_object["type"],
)
Expand All @@ -77,6 +83,7 @@ def map_back(cls, entity_properties: dict) -> dict:
def build_attributes(
cls,
retrieved_attributes: dict,
desired_attributes: list,
entry_pk: int,
node_type: str,
) -> dict:
Expand All @@ -85,6 +92,9 @@ def build_attributes(
:param retrieved_attributes: Dict of new attributes, will be updated accordingly
:type retrieved_attributes: dict
:param desired_attributes: Set of attributes to be built.
:type desired_attributes: set
:param entry_pk: The AiiDA Node's PK
:type entry_pk: int
Expand Down
42 changes: 32 additions & 10 deletions aiida_optimade/mappers/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,30 @@ class StructureMapper(ResourceMapper):
"data.structure.StructureData.": StructureDataTranslator,
}
ALL_ATTRIBUTES = set(StructureResourceAttributes.schema().get("properties").keys())
REQUIRED_ATTRIBUTES = set(StructureResourceAttributes.schema().get("required"))
# This should be REQUIRED_FIELDS, but should be set as such in `optimade`
REQUIRED_FIELDS = set(StructureResourceAttributes.schema().get("required"))

# pylint: disable=too-many-locals
@classmethod
def build_attributes(
cls, retrieved_attributes: dict, entry_pk: int, node_type: str
cls,
retrieved_attributes: dict,
desired_attributes: set,
entry_pk: int,
node_type: str,
) -> dict:
"""Build attributes dictionary for OPTIMADE structure resource
:param retrieved_attributes: Dict of new attributes, will be updated accordingly
:type retrieved_attributes: dict
:param desired_attributes: List of attributes to be built.
:type desired_attributes: set
:param entry_pk: The AiiDA Node's PK
:type entry_pk: int
:param node_type: The AiiDA Node's type
:type node_type: str
"""
float_fields = {
"elements_ratios",
Expand All @@ -49,22 +58,30 @@ def build_attributes(
}

# Add existing attributes
missing_attributes = cls.ALL_ATTRIBUTES.copy()
existing_attributes = set(retrieved_attributes.keys())
missing_attributes.difference_update(existing_attributes)
desired_attributes.difference_update(existing_attributes)
for field in float_fields:
if field in existing_attributes and retrieved_attributes.get(field):
retrieved_attributes[field] = hex_to_floats(retrieved_attributes[field])
res = retrieved_attributes.copy()

none_value_attributes = cls.REQUIRED_FIELDS - desired_attributes.union(
existing_attributes
)
none_value_attributes = {
_ for _ in none_value_attributes if not _.startswith("_")
}
res.update({field: None for field in none_value_attributes})

# Create and add new attributes
if missing_attributes:
if desired_attributes:
translator = cls.TRANSLATORS[node_type](entry_pk)
for attribute in missing_attributes:

for attribute in desired_attributes:
try:
create_attribute = getattr(translator, attribute)
except AttributeError as exc:
if attribute in cls.REQUIRED_ATTRIBUTES:
if attribute in cls.get_required_fields():
translator = None
raise NotImplementedError(
f"Parsing required attribute {attribute!r} from "
Expand All @@ -80,10 +97,11 @@ def build_attributes(
)
else:
res[attribute] = create_attribute()

# Special post-treatment for `structure_features`
all_fields = (
translator._get_optimade_extras()
) # pylint: disable=protected-access
translator._get_optimade_extras() # pylint: disable=protected-access
)
all_fields.update(translator.new_attributes)
structure_features = all_fields.get("structure_features", [])
if all_fields.get("species", None) is None:
Expand All @@ -97,6 +115,10 @@ def build_attributes(
# Some fields were removed
translator.new_attributes["structure_features"] = structure_features

translator.new_attributes.update(
{field: None for field in none_value_attributes}
)

# Store new attributes in `extras`
translator.store_attributes()
del translator
Expand Down
11 changes: 7 additions & 4 deletions aiida_optimade/models/structures.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# pylint: disable=missing-class-docstring,too-few-public-methods
from datetime import datetime

from pydantic import Field
from typing import Optional

from optimade.models import (
StructureResource as OptimadeStructureResource,
StructureResourceAttributes as OptimadeStructureResourceAttributes,
)
from optimade.models.utils import OptimadeField, SupportLevel


def prefix_provider(string: str) -> str:
Expand All @@ -21,8 +21,11 @@ def prefix_provider(string: str) -> str:
class StructureResourceAttributes(OptimadeStructureResourceAttributes):
"""Extended StructureResourceAttributes for AiiDA-specific fields"""

ctime: datetime = Field(
..., description="Creation time of the Node in the AiiDA database."
ctime: Optional[datetime] = OptimadeField(
...,
description="Creation time of the Node in the AiiDA database.",
support=SupportLevel.SHOULD,
queryable=SupportLevel.MUST,
)

class Config:
Expand Down

0 comments on commit ecc9bf1

Please sign in to comment.