1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

166 lines
4.1 KiB
Python
Raw Normal View History

"""Utility Functions for Pydantic Models."""
2020-02-03 02:35:11 -07:00
# Standard Library
import os
import re
from pathlib import Path
2020-02-03 02:35:11 -07:00
# Third Party
from pydantic import HttpUrl, BaseModel
# Project
from hyperglass.util import clean_name
2019-10-04 16:54:32 -07:00
2020-01-20 10:17:52 -07:00
class HyperglassModel(BaseModel):
"""Base model for all hyperglass configuration models."""
2019-10-04 16:54:32 -07:00
class Config:
"""Default Pydantic configuration.
See https://pydantic-docs.helpmanual.io/usage/model_config
"""
2019-10-04 16:54:32 -07:00
validate_all = True
extra = "forbid"
validate_assignment = True
alias_generator = clean_name
json_encoders = {HttpUrl: lambda v: str(v)}
def export_json(self, *args, **kwargs):
"""Return instance as JSON.
Returns:
{str} -- Stringified JSON.
"""
return self.json(by_alias=True, exclude_unset=False, *args, **kwargs)
def export_dict(self, *args, **kwargs):
"""Return instance as dictionary.
Returns:
{dict} -- Python dictionary.
"""
return self.dict(by_alias=True, exclude_unset=False, *args, **kwargs)
def export_yaml(self, *args, **kwargs):
"""Return instance as YAML.
Returns:
{str} -- Stringified YAML.
"""
import json
import yaml
return yaml.safe_dump(json.loads(self.export_json(*args, **kwargs)))
class HyperglassModelExtra(HyperglassModel):
"""Model for hyperglass configuration models with dynamic fields."""
pass
class Config:
"""Default pydantic configuration."""
extra = "allow"
2020-01-20 10:17:52 -07:00
class AnyUri(str):
"""Custom field type for HTTP URI, e.g. /example."""
@classmethod
def __get_validators__(cls):
"""Pydantic custim field method."""
yield cls.validate
@classmethod
def validate(cls, value):
"""Ensure URI string contains a leading forward-slash."""
uri_regex = re.compile(r"^(\/.*)$")
if not isinstance(value, str):
raise TypeError("AnyUri type must be a string")
match = uri_regex.fullmatch(value)
if not match:
raise ValueError(
"Invalid format. A URI must begin with a forward slash, e.g. '/example'"
)
return cls(match.group())
def __repr__(self):
"""Stringify custom field representation."""
return f"AnyUri({super().__repr__()})"
class StrictBytes(bytes):
"""Custom data type for a strict byte string.
Used for validating the encoded JWT request payload.
"""
@classmethod
def __get_validators__(cls):
"""Yield Pydantic validator function.
See: https://pydantic-docs.helpmanual.io/usage/types/#custom-data-types
Yields:
{function} -- Validator
"""
yield cls.validate
@classmethod
def validate(cls, value):
"""Validate type.
Arguments:
value {Any} -- Pre-validated input
Raises:
TypeError: Raised if value is not bytes
Returns:
{object} -- Instantiated class
"""
if not isinstance(value, bytes):
raise TypeError("bytes required")
return cls()
def __repr__(self):
"""Return representation of object.
Returns:
{str} -- Representation
"""
return f"StrictBytes({super().__repr__()})"
def validate_image(value):
"""Convert file path to URL path.
Arguments:
value {FilePath} -- Path to logo file.
Returns:
{str} -- Formatted logo path
"""
base_path = value.split("/")
if base_path[0] == "/":
value = "/".join(base_path[1:])
if base_path[0] not in ("images", "custom"):
raise ValueError(
"Logo files must be in the 'custom/' directory of your hyperglass directory. Got: {f}",
f=value,
)
if base_path[0] == "custom":
config_path = Path(os.environ["hyperglass_directory"])
custom_file = config_path / "static" / value
if not custom_file.exists():
raise ValueError("'{f}' does not exist", f=str(custom_file))
return value