utils¶
ANONYMOUS_ELEMENTS
¶
Returns the first 150 values of the anonymous element generator.
ATOMIC_NUMBERS
¶
CHEMICAL_FORMULA_REGEXP
¶
CHEMICAL_SYMBOLS
¶
EXTRA_SYMBOLS
¶
OPTIMADE_SCHEMA_EXTENSION_KEYS
¶
OPTIMADE_SCHEMA_EXTENSION_PREFIX
¶
SemanticVersion (str)
¶
A custom type for a semantic version, using the recommended semver regexp from https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string.
Source code in optimade/models/utils.py
class SemanticVersion(str):
"""A custom type for a semantic version, using the recommended
semver regexp from
https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string.
"""
regex = re.compile(
r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
)
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(
pattern=cls.regex.pattern,
example=["0.10.1", "1.0.0-rc.2", "1.2.3-rc.5+develop"],
)
@classmethod
def validate(cls, v: str):
if not cls.regex.match(v):
raise ValueError(
f"Unable to validate the version string {v!r} as a semantic version (expected <major>.<minor>.<patch>)."
"See https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string for more information."
)
return v
@property
def _match(self):
"""The result of the regex match."""
return self.regex.match(self)
@property
def major(self) -> int:
"""The major version number."""
return int(self._match.group(1))
@property
def minor(self) -> int:
"""The minor version number."""
return int(self._match.group(2))
@property
def patch(self) -> int:
"""The patch version number."""
return int(self._match.group(3))
@property
def prerelease(self) -> str:
"""The pre-release tag."""
return self._match.group(4)
@property
def build_metadata(self) -> str:
"""The build metadata."""
return self._match.group(5)
@property
def base_version(self) -> str:
"""The base version string without patch and metadata info."""
return f"{self.major}.{self.minor}.{self.patch}"
base_version: str
property
readonly
¶
The base version string without patch and metadata info.
build_metadata: str
property
readonly
¶
The build metadata.
major: int
property
readonly
¶
The major version number.
minor: int
property
readonly
¶
The minor version number.
patch: int
property
readonly
¶
The patch version number.
prerelease: str
property
readonly
¶
The pre-release tag.
regex
¶
__get_validators__()
classmethod
special
¶
Source code in optimade/models/utils.py
@classmethod
def __get_validators__(cls):
yield cls.validate
__modify_schema__(field_schema)
classmethod
special
¶
Source code in optimade/models/utils.py
@classmethod
def __modify_schema__(cls, field_schema):
field_schema.update(
pattern=cls.regex.pattern,
example=["0.10.1", "1.0.0-rc.2", "1.2.3-rc.5+develop"],
)
validate(v)
classmethod
¶
Source code in optimade/models/utils.py
@classmethod
def validate(cls, v: str):
if not cls.regex.match(v):
raise ValueError(
f"Unable to validate the version string {v!r} as a semantic version (expected <major>.<minor>.<patch>)."
"See https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string for more information."
)
return v
StrictFieldInfo (FieldInfo)
¶
Wraps the standard pydantic FieldInfo
in order
to prefix any custom keys from StrictField
.
Source code in optimade/models/utils.py
class StrictFieldInfo(FieldInfo):
"""Wraps the standard pydantic `FieldInfo` in order
to prefix any custom keys from `StrictField`.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for key in OPTIMADE_SCHEMA_EXTENSION_KEYS:
if key in self.extra:
self.extra[f"{OPTIMADE_SCHEMA_EXTENSION_PREFIX}{key}"] = self.extra.pop(
key
)
__init__(self, *args, **kwargs)
special
¶
Initialize self. See help(type(self)) for accurate signature.
Source code in optimade/models/utils.py
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for key in OPTIMADE_SCHEMA_EXTENSION_KEYS:
if key in self.extra:
self.extra[f"{OPTIMADE_SCHEMA_EXTENSION_PREFIX}{key}"] = self.extra.pop(
key
)
SupportLevel (Enum)
¶
OptimadeField(*args, *, support=None, queryable=None, unit=None, **kwargs)
¶
A wrapper around pydantic.Field
that adds OPTIMADE-specific
field paramters queryable
, support
and unit
, indicating
the corresponding support level in the specification and the
physical unit of the field.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
support |
Union[str, optimade.models.utils.SupportLevel] |
The support level of the field itself, i.e. whether the field can be null or omitted by an implementation. |
None |
queryable |
Union[str, optimade.models.utils.SupportLevel] |
The support level corresponding to the queryablility of this field. |
None |
unit |
Optional[str] |
A string describing the unit of the field. |
None |
Returns:
Type | Description |
---|---|
<cyfunction Field at 0x7fad4228e810> |
The pydantic field with extra validation provided by |
Source code in optimade/models/utils.py
def OptimadeField(
*args,
support: Optional[Union[str, SupportLevel]] = None,
queryable: Optional[Union[str, SupportLevel]] = None,
unit: Optional[str] = None,
**kwargs,
) -> Field:
"""A wrapper around `pydantic.Field` that adds OPTIMADE-specific
field paramters `queryable`, `support` and `unit`, indicating
the corresponding support level in the specification and the
physical unit of the field.
Arguments:
support: The support level of the field itself, i.e. whether the field
can be null or omitted by an implementation.
queryable: The support level corresponding to the queryablility
of this field.
unit: A string describing the unit of the field.
Returns:
The pydantic field with extra validation provided by [`StrictField`][optimade.models.utils.StrictField].
"""
# Collect non-null keyword arguments to add to the Field schema
if unit is not None:
kwargs["unit"] = unit
if queryable is not None:
if isinstance(queryable, str):
queryable = SupportLevel(queryable.lower())
kwargs["queryable"] = queryable
if support is not None:
if isinstance(support, str):
support = SupportLevel(support.lower())
kwargs["support"] = support
return StrictField(*args, **kwargs)
StrictField(*args, *, description=None, **kwargs)
¶
A wrapper around pydantic.Field
that does the following:
- Forbids any "extra" keys that would be passed to
pydantic.Field
, except those used elsewhere to modify the schema in-place, e.g. "uniqueItems", "pattern" and those added by OptimadeField, e.g. "unit", "queryable" and "sortable". - Emits a warning when no description is provided.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
*args |
Any |
Positional arguments passed through to |
() |
description |
Optional[str] |
The description of the |
None |
**kwargs |
Any |
Extra keyword arguments to be passed to |
{} |
Exceptions:
Type | Description |
---|---|
RuntimeError |
If |
Returns:
Type | Description |
---|---|
StrictFieldInfo |
The pydantic |
Source code in optimade/models/utils.py
def StrictField(
*args: "Any",
description: Optional[str] = None,
**kwargs: "Any",
) -> StrictFieldInfo:
"""A wrapper around `pydantic.Field` that does the following:
- Forbids any "extra" keys that would be passed to `pydantic.Field`,
except those used elsewhere to modify the schema in-place,
e.g. "uniqueItems", "pattern" and those added by OptimadeField, e.g.
"unit", "queryable" and "sortable".
- Emits a warning when no description is provided.
Arguments:
*args: Positional arguments passed through to `Field`.
description: The description of the `Field`; if this is not
specified then a `UserWarning` will be emitted.
**kwargs: Extra keyword arguments to be passed to `Field`.
Raises:
RuntimeError: If `**kwargs` contains a key not found in the
function signature of `Field`, or in the extensions used
by models in this package (see above).
Returns:
The pydantic `Field`.
"""
allowed_keys = [
"pattern",
"uniqueItems",
"nullable",
] + OPTIMADE_SCHEMA_EXTENSION_KEYS
_banned = [k for k in kwargs if k not in set(_PYDANTIC_FIELD_KWARGS + allowed_keys)]
if _banned:
raise RuntimeError(
f"Not creating StrictField({args}, {kwargs}) with forbidden keywords {_banned}."
)
if description is not None:
kwargs["description"] = description
if description is None:
warnings.warn(
f"No description provided for StrictField specified by {args}, {kwargs}."
)
return StrictPydanticField(*args, **kwargs)
StrictPydanticField(*args, **kwargs)
¶
Wrapper for Field
that uses StrictFieldInfo
instead of
the pydantic FieldInfo
.
Source code in optimade/models/utils.py
def StrictPydanticField(*args, **kwargs):
"""Wrapper for `Field` that uses `StrictFieldInfo` instead of
the pydantic `FieldInfo`.
"""
field_info = StrictFieldInfo(*args, **kwargs)
field_info._validate()
return field_info
anonymize_formula(formula)
¶
Takes a string representation of a chemical formula of the form [A-Z][a-z]*[0-9]*
(potentially with whitespace) and
returns the OPTIMADE chemical_formula_anonymous
representation, i.e., a reduced chemical formula comprising of element symbols
drawn from A, B, C... ordered from largest proportion to smallest.
Returns:
Type | Description |
---|---|
str |
The anonymous chemical formula in the OPTIMADE representation. |
Source code in optimade/models/utils.py
def anonymize_formula(formula: str) -> str:
"""Takes a string representation of a chemical formula of the form `[A-Z][a-z]*[0-9]*` (potentially with whitespace) and
returns the OPTIMADE `chemical_formula_anonymous` representation, i.e., a reduced chemical formula comprising of element symbols
drawn from A, B, C... ordered from largest proportion to smallest.
Returns:
The anonymous chemical formula in the OPTIMADE representation.
"""
return _reduce_or_anonymize_formula(formula, alphabetize=False, anonymize=True)
anonymous_element_generator()
¶
Generator that yields the next symbol in the A, B, Aa, ... Az naming scheme.
Source code in optimade/models/utils.py
def anonymous_element_generator():
"""Generator that yields the next symbol in the A, B, Aa, ... Az naming scheme."""
from string import ascii_lowercase
for size in itertools.count(1):
for s in itertools.product(ascii_lowercase, repeat=size):
s = list(s)
s[0] = s[0].upper()
yield "".join(s)
reduce_formula(formula)
¶
Takes a string representation of a chemical formula of the form [A-Z][a-z]*[0-9]*
(potentially with whitespace) and
reduces it by the GCD of the proportion integers present in the formula, stripping any leftover "1" values.
Returns:
Type | Description |
---|---|
str |
The reduced chemical formula in the OPTIMADE representation. |
Source code in optimade/models/utils.py
def reduce_formula(formula: str) -> str:
"""Takes a string representation of a chemical formula of the form `[A-Z][a-z]*[0-9]*` (potentially with whitespace) and
reduces it by the GCD of the proportion integers present in the formula, stripping any leftover "1" values.
Returns:
The reduced chemical formula in the OPTIMADE representation.
"""
return _reduce_or_anonymize_formula(formula, alphabetize=True, anonymize=False)