Source code for svarog.svarog
import sys
from functools import lru_cache
from typing import _eval_type # type: ignore
from typing import Any
from typing import Dict
from typing import Hashable
from typing import Type
from typing import TypeVar
from .checks import has_annotated_init
from .checks import is_list
from .checks import is_literal
from .checks import is_mapping
from .checks import is_union
from .compat import ForwardRef
from .dispatchers.functional import PredicatedFilters
from .dispatchers.multi import MultiDispatcher
from .forges import _clean_annotations
from .forges import filter_cammel_case
from .forges import forge_annotated_init
from .forges import forge_list
from .forges import forge_literal
from .forges import forge_mapping
from .forges import forge_none
from .forges import forge_union
from .types import CannotDispatch
from .types import Check
from .types import Filter
from .types import Handler
from .types import NoneType
T = TypeVar("T", bound=Hashable)
[docs]class Svarog:
_refs_owners: Dict[ForwardRef, Type]
def __init__(self, snake_case: bool = False):
self._refs_owners = {}
self._dispatcher = MultiDispatcher()
self.register_forge(NoneType, forge_none) # type: ignore
self.register_mold(has_annotated_init, forge_annotated_init)
self.register_mold(is_list, forge_list)
self.register_mold(is_mapping, forge_mapping)
self.register_mold(is_union, forge_union)
self.register_mold(is_literal, forge_literal)
self._filter = PredicatedFilters()
if snake_case:
self.enable_snake_case()
[docs] def enable_snake_case(self):
self.add_filter(has_annotated_init, filter_cammel_case)
[docs] def add_filter(self, predicate: Check, filter: Filter) -> None:
self._filter.add(predicate)(filter)
[docs] def register_forge(self, type_: Type, forge: Handler) -> None:
self._dispatcher.register_cls(type_, forge)
[docs] def register_mold(self, check: Check, forge: Handler) -> None:
self._dispatcher.register_func(check, forge)
@lru_cache(None)
def _update_forward_ref_owners_registry(self, type_: Type) -> None:
def update_subtype(sub_type: Type) -> None:
if isinstance(sub_type, ForwardRef):
self._refs_owners[sub_type] = type_
if (
not isinstance(sub_type, NoneType) # type: ignore
and sub_type is not Any
and hasattr(sub_type, "__args__")
):
for arg in sub_type.__args__:
update_subtype(arg)
for _, annotation in _clean_annotations(type_.__init__):
update_subtype(annotation)
[docs] def forge(self, type_: Type[T], data: Any) -> T:
if isinstance(type_, ForwardRef):
type_ = self._resolve_forward_ref(type_)
if has_annotated_init(type_):
self._update_forward_ref_owners_registry(type_)
if type_ is Any:
return data
data = self._filter(type_, data)
handler = self._dispatcher.dispatch(type_)
try:
return handler(type_, data, self.forge)
except (CannotDispatch, TypeError):
return type_(data) # type: ignore
def _resolve_forward_ref(self, type_: ForwardRef) -> Type:
try:
owner: Type = self._refs_owners[type_]
except KeyError:
raise RuntimeError(f"Unknown forward ref: {type_}")
return _eval_type(
type_, vars(sys.modules[owner.__module__]), getattr(owner, "__dict__", {})
)