custom_types¶
Custom "pydantic" types used in OTEAPI-OPTIMADE.
LOGGER
¶
OPTIMADEUrl (str)
¶
A deconstructed OPTIMADE URL.
An OPTIMADE URL is made up in the following way:
<BASE URL>[/<VERSION>]/<ENDPOINT>?[<QUERY PARAMETERS>]
Where parts in square brackets ([]) are optional.
Source code in oteapi_optimade/models/custom_types.py
class OPTIMADEUrl(str):
"""A deconstructed OPTIMADE URL.
An OPTIMADE URL is made up in the following way:
<BASE URL>[/<VERSION>]/<ENDPOINT>?[<QUERY PARAMETERS>]
Where parts in square brackets (`[]`) are optional.
"""
# https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
max_length = 2083
allowed_schemes: ClassVar[list[str]] = ["http", "https"]
host_required = True
@no_type_check
def __new__(cls, url: str | None = None, **kwargs) -> object:
return str.__new__(
cls,
url if url else cls._build(**kwargs),
)
def __init__(
self,
url: str,
*,
base_url: Optional[str] = None,
version: Optional[str] = None,
endpoint: Optional[str] = None,
query: Optional[str] = None,
) -> None:
str.__init__(url)
# Parse as URL
try:
pydantic_url = AnyHttpUrl(url)
except ValidationError:
try:
pydantic_url = AnyHttpUrl(
self._build(
base_url=base_url or "",
version=version,
endpoint=endpoint,
query=query,
)
)
except ValidationError:
pydantic_url = None
# Build OPTIMADE URL parts
optimade_parts: Union[OPTIMADEParts, dict[str, Any]] = {}
if pydantic_url:
optimade_parts = self._build_optimade_parts(pydantic_url)
self._base_url = base_url or optimade_parts.get("base_url", None)
self._version = version or optimade_parts.get("version", None)
self._endpoint = endpoint or optimade_parts.get("endpoint", None)
self._query = query or optimade_parts.get("query", None)
self._scheme = self._base_url.split("://")[0] if self._base_url else None
def __str__(self) -> str:
return self._build(
base_url=self.base_url,
version=self.version,
endpoint=self.endpoint,
query=self.query,
)
def __repr__(self) -> str:
extra = ", ".join(
f"{n}={getattr(self, n)!r}"
for n in ("scheme", "base_url", "version", "endpoint", "query")
if getattr(self, n) is not None
)
return f"{self.__class__.__name__}({super().__repr__()}, {extra})"
@staticmethod
def _build(
*,
base_url: str,
version: Optional[str] = None,
endpoint: Optional[str] = None,
query: Optional[str] = None,
) -> str:
"""Build complete OPTIMADE URL from URL parts."""
url = base_url.rstrip("/")
if version:
url += f"/{version}"
if endpoint:
url += f"/{endpoint}"
if query:
url += f"?{query}"
return url
@property
def scheme(self) -> str:
"""The scheme of the OPTIMADE URL."""
if self._scheme is None:
error_message = "OPTIMADE URL has no scheme."
raise ValueError(error_message)
return self._scheme
@property
def base_url(self) -> str:
"""The base URL of the OPTIMADE URL."""
if self._base_url is None:
error_message = "OPTIMADE URL has no base URL."
raise ValueError(error_message)
return self._base_url
@property
def version(self) -> Optional[str]:
"""The version part of the OPTIMADE URL."""
return self._version
@property
def endpoint(self) -> Optional[str]:
"""The endpoint part of the OPTIMADE URL."""
return self._endpoint
@property
def query(self) -> Optional[str]:
"""The query part of the OPTIMADE URL."""
return self._query
def response_model(self) -> Union[tuple[Success, Success], Success, None]:
"""Return the endpoint's corresponding response model(s) (from OPT)."""
if not self.endpoint or self.endpoint == "versions":
return None
return {
"info": (InfoResponse, EntryInfoResponse),
"links": LinksResponse,
"structures": (StructureResponseMany, StructureResponseOne),
"references": (ReferenceResponseMany, ReferenceResponseOne),
"calculations": (EntryResponseMany, EntryResponseOne),
}.get(self.endpoint, Success)
# Pydantic-related methods
@classmethod
def __get_pydantic_core_schema__(
cls, _source_type: Any, _handler: GetCoreSchemaHandler
) -> CoreSchema:
"""Pydantic core schema for an OPTIMADE URL.
Behaviour:
- strings and `Url` instances will be parsed as `pydantic.AnyHttpUrl` instances
and then converted to `OPTIMADEUrl` instances.
- `OPTIMADEUrl` instances will be parsed as `OPTIMADEUrl` instances without any changes.
- Nothing else will pass validation
- Serialization will always return just a str.
"""
from_str_schema = core_schema.chain_schema(
[
core_schema.url_schema(
max_length=cls.max_length,
host_required=cls.host_required,
allowed_schemes=cls.allowed_schemes,
),
core_schema.no_info_plain_validator_function(
cls._validate_from_str_or_url
),
],
)
from_url_schema = core_schema.chain_schema(
[
core_schema.is_instance_schema(Url),
core_schema.no_info_plain_validator_function(
cls._validate_from_str_or_url
),
],
)
return core_schema.json_or_python_schema(
json_schema=from_str_schema,
python_schema=core_schema.union_schema(
[
core_schema.is_instance_schema(cls),
from_url_schema,
from_str_schema,
],
),
serialization=core_schema.plain_serializer_function_ser_schema(str),
)
@classmethod
def __get_pydantic_json_schema__(
cls, _core_schema: CoreSchema, handler: GetJsonSchemaHandler
) -> JsonSchemaValue:
# Use the same schema that would be used for an AnyHttpUrl
return handler(
core_schema.url_schema(
max_length=cls.max_length,
host_required=cls.host_required,
allowed_schemes=cls.allowed_schemes,
)
)
@classmethod
def _validate_from_str_or_url(cls, value: Union[Url, str]) -> OPTIMADEUrl:
"""Pydantic validation of an OPTIMADE URL."""
# Parse as URL
url = AnyHttpUrl(str(value))
# Build OPTIMADE URL parts
optimade_parts = cls._build_optimade_parts(url)
return cls( # type: ignore[no-any-return]
None,
base_url=optimade_parts["base_url"],
version=optimade_parts["version"],
endpoint=optimade_parts["endpoint"],
query=optimade_parts["query"],
)
@classmethod
def _build_optimade_parts(cls, url: AnyHttpUrl) -> OPTIMADEParts:
"""Convert URL parts to equivalent OPTIMADE URL parts."""
base_url = f"{url.scheme}://"
if url.username:
base_url += url.username
if url.password:
base_url += f":{url.password}"
if url.username and url.password:
base_url += "@"
# This check is done to satisfy type checker.
# Since the url has been parsed as a `AnyHttpUrl`, it must always have a host.
if url.host is None:
error_message = "Could not parse given string as a URL."
raise ValueError(error_message)
base_url += url.host
# Hide port if it's a standard HTTP (80) or HTTPS (443) port.
if url.port and url.port not in (80, 443):
base_url += f":{url.port}"
if url.path:
base_url += url.path
base_url_match = _OPTIMADE_BASE_URL_REGEX.fullmatch(base_url)
LOGGER.debug(
"OPTIMADE base URL regex match groups: %s",
base_url_match.groupdict() if base_url_match else base_url_match,
)
if base_url_match is None:
error_message = "Could not match given string with OPTIMADE base URL regex."
raise ValueError(error_message)
endpoint_match = _OPTIMADE_ENDPOINT_REGEX.findall(
base_url_match.group("path") if base_url_match.group("path") else ""
)
LOGGER.debug("OPTIMADE endpoint regex matches: %s", endpoint_match)
for path_version, path_endpoint in endpoint_match: # noqa: B007
if path_endpoint:
break
else:
LOGGER.debug("Could not match given string with OPTIMADE endpoint regex.")
path_version, path_endpoint = "", ""
base_url = base_url_match.group("base_url")
if path_version:
base_url = base_url[: -(len(path_version) + len(path_endpoint) + 2)]
elif path_endpoint:
base_url = base_url[: -(len(path_endpoint) + 1)]
optimade_parts = {
"base_url": base_url.rstrip("/"),
"version": path_version or None,
"endpoint": path_endpoint or None,
"query": url.query,
}
return cast("OPTIMADEParts", optimade_parts)
allowed_schemes: ClassVar[list[str]]
¶
base_url: str
property
readonly
¶
The base URL of the OPTIMADE URL.
endpoint: Optional[str]
property
readonly
¶
The endpoint part of the OPTIMADE URL.
host_required
¶
max_length
¶
query: Optional[str]
property
readonly
¶
The query part of the OPTIMADE URL.
scheme: str
property
readonly
¶
The scheme of the OPTIMADE URL.
version: Optional[str]
property
readonly
¶
The version part of the OPTIMADE URL.
__init__(self, url, *, base_url=None, version=None, endpoint=None, query=None)
special
¶
Source code in oteapi_optimade/models/custom_types.py
def __init__(
self,
url: str,
*,
base_url: Optional[str] = None,
version: Optional[str] = None,
endpoint: Optional[str] = None,
query: Optional[str] = None,
) -> None:
str.__init__(url)
# Parse as URL
try:
pydantic_url = AnyHttpUrl(url)
except ValidationError:
try:
pydantic_url = AnyHttpUrl(
self._build(
base_url=base_url or "",
version=version,
endpoint=endpoint,
query=query,
)
)
except ValidationError:
pydantic_url = None
# Build OPTIMADE URL parts
optimade_parts: Union[OPTIMADEParts, dict[str, Any]] = {}
if pydantic_url:
optimade_parts = self._build_optimade_parts(pydantic_url)
self._base_url = base_url or optimade_parts.get("base_url", None)
self._version = version or optimade_parts.get("version", None)
self._endpoint = endpoint or optimade_parts.get("endpoint", None)
self._query = query or optimade_parts.get("query", None)
self._scheme = self._base_url.split("://")[0] if self._base_url else None
response_model(self)
¶
Return the endpoint's corresponding response model(s) (from OPT).
Source code in oteapi_optimade/models/custom_types.py
def response_model(self) -> Union[tuple[Success, Success], Success, None]:
"""Return the endpoint's corresponding response model(s) (from OPT)."""
if not self.endpoint or self.endpoint == "versions":
return None
return {
"info": (InfoResponse, EntryInfoResponse),
"links": LinksResponse,
"structures": (StructureResponseMany, StructureResponseOne),
"references": (ReferenceResponseMany, ReferenceResponseOne),
"calculations": (EntryResponseMany, EntryResponseOne),
}.get(self.endpoint, Success)