diff --git a/optimade/validator/config.py b/optimade/validator/config.py index d311b94e0..498ac7ab2 100644 --- a/optimade/validator/config.py +++ b/optimade/validator/config.py @@ -7,10 +7,16 @@ """ -from typing import Dict, Any, Set, List +from typing import Dict, Any, Set, List, Container from pydantic import BaseSettings, Field -from optimade.models import InfoResponse, IndexInfoResponse, DataType, StructureFeatures +from optimade.models import ( + InfoResponse, + IndexInfoResponse, + DataType, + StructureFeatures, + SupportLevel, +) from optimade.validator.utils import ( ValidatorLinksResponse, ValidatorReferenceResponseOne, @@ -61,17 +67,11 @@ _UNIQUE_PROPERTIES = ("id", "immutable_id") +inclusive_ops = ("=", "<=", ">=") +substring_operators = ("CONTAINS", "STARTS WITH", "STARTS", "ENDS WITH", "ENDS") + _INCLUSIVE_OPERATORS = { - DataType.STRING: ( - "=", - "<=", - ">=", - "CONTAINS", - "STARTS WITH", - "STARTS", - "ENDS WITH", - "ENDS", - ), + DataType.STRING: inclusive_ops + substring_operators, DataType.TIMESTAMP: ( # N.B. "=" and "<=" are disabled due to issue with microseconds stored in database vs API response (see Materials-Consortia/optimade-python-tools/#606) # ">=" is fine as all microsecond trimming will round times down @@ -79,11 +79,7 @@ # "<=", ">=", ), - DataType.INTEGER: ( - "=", - "<=", - ">=", - ), + DataType.INTEGER: inclusive_ops, DataType.FLOAT: (), DataType.LIST: ("HAS", "HAS ALL", "HAS ANY"), } @@ -99,6 +95,16 @@ } +_FIELD_SPECIFIC_OVERRIDES = { + "chemical_formula_anonymous": { + SupportLevel.OPTIONAL: substring_operators, + }, + "chemical_formula_reduced": { + SupportLevel.OPTIONAL: substring_operators, + }, +} + + class ValidatorConfig(BaseSettings): """This class stores validator config parameters in a way that can be easily modified for testing niche implementations. Many @@ -150,6 +156,16 @@ class ValidatorConfig(BaseSettings): ), ) + field_specific_overrides: Dict[str, Dict[SupportLevel, Container[str]]] = Field( + _FIELD_SPECIFIC_OVERRIDES, + description=( + "Some fields do not require all type comparison operators to be supported. " + "This dictionary allows overriding the list of supported operators for a field, using " + "the field name as a key, and the support level of different operators with a subkey. " + "Queries on fields listed in this way will pass the validator provided the server returns a 501 status." + ), + ) + links_endpoint: str = Field("links", description="The name of the links endpoint") versions_endpoint: str = Field( "versions", description="The name of the versions endpoint" diff --git a/optimade/validator/validator.py b/optimade/validator/validator.py index a4f03f4f9..82065c7c4 100644 --- a/optimade/validator/validator.py +++ b/optimade/validator/validator.py @@ -778,11 +778,18 @@ def _construct_single_property_filters( inclusive_operators = CONF.inclusive_operators[prop_type] exclusive_operators = CONF.exclusive_operators[prop_type] + field_specific_support_overrides = CONF.field_specific_overrides.get(prop, {}) for operator in inclusive_operators | exclusive_operators: # Need to pre-format list and string test values for the query _test_value = self._format_test_value(test_value, prop_type, operator) + query_optional = ( + query_optional + or operator + in field_specific_support_overrides.get(SupportLevel.OPTIONAL, []) + ) + query = f"{prop} {operator} {_test_value}" request = f"{endp}?filter={query}" response, message = self._get_endpoint(