Skip to content

Commit

Permalink
chore(deps): update dacite 1.8.1
Browse files Browse the repository at this point in the history
Signed-off-by: Jack Cherng <[email protected]>
  • Loading branch information
jfcherng committed Feb 15, 2024
1 parent 3ed23f0 commit 4533ae2
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 106 deletions.
4 changes: 4 additions & 0 deletions vendor/dacite/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from .cache import set_cache_size, get_cache_size, clear_cache
from .config import Config
from .core import from_dict
from .exceptions import (
Expand All @@ -12,6 +13,9 @@
)

__all__ = [
"set_cache_size",
"get_cache_size",
"clear_cache",
"Config",
"from_dict",
"DaciteError",
Expand Down
25 changes: 25 additions & 0 deletions vendor/dacite/cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from functools import lru_cache
from typing import TypeVar, Callable, Optional

T = TypeVar("T", bound=Callable)

__MAX_SIZE: Optional[int] = 2048


@lru_cache(maxsize=None)
def cache(function: T) -> T:
return lru_cache(maxsize=get_cache_size())(function) # type: ignore


def set_cache_size(size: Optional[int]) -> None:
global __MAX_SIZE # pylint: disable=global-statement
__MAX_SIZE = size


def get_cache_size() -> Optional[int]:
global __MAX_SIZE # pylint: disable=global-variable-not-assigned
return __MAX_SIZE


def clear_cache() -> None:
cache.cache_clear()
13 changes: 13 additions & 0 deletions vendor/dacite/config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import sys
from dataclasses import dataclass, field
from typing import Dict, Any, Callable, Optional, Type, List

from .frozen_dict import FrozenDict

if sys.version_info.minor >= 8:
from functools import cached_property # type: ignore # pylint: disable=no-name-in-module
else:
# Remove when we drop support for Python<3.8
cached_property = property # type: ignore # pylint: disable=invalid-name


@dataclass
class Config:
Expand All @@ -10,3 +19,7 @@ class Config:
check_types: bool = True
strict: bool = False
strict_unions_match: bool = False

@cached_property
def hashable_forward_references(self) -> Optional[FrozenDict]:
return FrozenDict(self.forward_references) if self.forward_references else None
86 changes: 47 additions & 39 deletions vendor/dacite/core.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import copy
from dataclasses import is_dataclass
from itertools import zip_longest
from typing import TypeVar, Type, Optional, get_type_hints, Mapping, Any
from typing import TypeVar, Type, Optional, get_type_hints, Mapping, Any, Collection, MutableMapping

from .cache import cache
from .config import Config
from .data import Data
from .dataclasses import get_default_value_for_field, create_instance, DefaultValueNotFoundError, get_fields
from .dataclasses import (
get_default_value_for_field,
DefaultValueNotFoundError,
get_fields,
is_frozen,
)
from .exceptions import (
ForwardReferenceError,
WrongTypeError,
Expand All @@ -22,11 +27,10 @@
is_union,
extract_generic,
is_optional,
transform_value,
extract_origin_collection,
is_init_var,
extract_init_var,
is_set,
is_subclass,
)

T = TypeVar("T")
Expand All @@ -40,63 +44,66 @@ def from_dict(data_class: Type[T], data: Data, config: Optional[Config] = None)
:param config: a configuration of the creation process
:return: an instance of a data class
"""
init_values: Data = {}
post_init_values: Data = {}
init_values: MutableMapping[str, Any] = {}
post_init_values: MutableMapping[str, Any] = {}
config = config or Config()
try:
data_class_hints = get_type_hints(data_class, globalns=config.forward_references)
data_class_hints = cache(get_type_hints)(data_class, localns=config.hashable_forward_references)
except NameError as error:
raise ForwardReferenceError(str(error))
data_class_fields = get_fields(data_class)
data_class_fields = cache(get_fields)(data_class)
if config.strict:
extra_fields = set(data.keys()) - {f.name for f in data_class_fields}
if extra_fields:
raise UnexpectedDataError(keys=extra_fields)
for field in data_class_fields:
field = copy.copy(field)
field.type = data_class_hints[field.name]
field_type = data_class_hints[field.name]
if field.name in data:
try:
field_data = data[field.name]
transformed_value = transform_value(
type_hooks=config.type_hooks, cast=config.cast, target_type=field.type, value=field_data
)
value = _build_value(type_=field.type, data=transformed_value, config=config)
value = _build_value(type_=field_type, data=field_data, config=config)
except DaciteFieldError as error:
error.update_path(field.name)
raise
if config.check_types and not is_instance(value, field.type):
raise WrongTypeError(field_path=field.name, field_type=field.type, value=value)
if config.check_types and not is_instance(value, field_type):
raise WrongTypeError(field_path=field.name, field_type=field_type, value=value)
else:
try:
value = get_default_value_for_field(field)
value = get_default_value_for_field(field, field_type)
except DefaultValueNotFoundError:
if not field.init:
continue
raise MissingValueError(field.name)
if field.init:
init_values[field.name] = value
else:
elif not is_frozen(data_class):
post_init_values[field.name] = value

return create_instance(data_class=data_class, init_values=init_values, post_init_values=post_init_values)
instance = data_class(**init_values)
for key, value in post_init_values.items():
setattr(instance, key, value)
return instance


def _build_value(type_: Type, data: Any, config: Config) -> Any:
if is_init_var(type_):
type_ = extract_init_var(type_)
if type_ in config.type_hooks:
data = config.type_hooks[type_](data)
if is_optional(type_) and data is None:
return data
if is_union(type_):
return _build_value_for_union(union=type_, data=data, config=config)
data = _build_value_for_union(union=type_, data=data, config=config)
elif is_generic_collection(type_):
origin = extract_origin_collection(type_)
if is_instance(data, origin):
return _build_value_for_collection(collection=type_, data=data, config=config)
if is_set(origin):
return origin(
_build_value(type_=extract_generic(type_)[0], data=single_val, config=config) for single_val in data
)
elif is_dataclass(type_) and is_instance(data, Data):
return from_dict(data_class=type_, data=data, config=config)
data = _build_value_for_collection(collection=type_, data=data, config=config)
elif cache(is_dataclass)(type_) and isinstance(data, Mapping):
data = from_dict(data_class=type_, data=data, config=config)
for cast_type in config.cast:
if is_subclass(type_, cast_type):
if is_generic_collection(type_):
data = extract_origin_collection(type_)(data)
else:
data = type_(data)
break
return data


Expand All @@ -109,12 +116,9 @@ def _build_value_for_union(union: Type, data: Any, config: Config) -> Any:
try:
# noinspection PyBroadException
try:
data = transform_value(
type_hooks=config.type_hooks, cast=config.cast, target_type=inner_type, value=data
)
value = _build_value(type_=inner_type, data=data, config=config)
except Exception: # pylint: disable=broad-except
continue
value = _build_value(type_=inner_type, data=data, config=config)
if is_instance(value, inner_type):
if config.strict_unions_match:
union_matches[inner_type] = value
Expand All @@ -133,15 +137,19 @@ def _build_value_for_union(union: Type, data: Any, config: Config) -> Any:

def _build_value_for_collection(collection: Type, data: Any, config: Config) -> Any:
data_type = data.__class__
if is_instance(data, Mapping):
if isinstance(data, Mapping) and is_subclass(collection, Mapping):
item_type = extract_generic(collection, defaults=(Any, Any))[1]
return data_type((key, _build_value(type_=item_type, data=value, config=config)) for key, value in data.items())
elif is_instance(data, tuple):
elif isinstance(data, tuple) and is_subclass(collection, tuple):
if not data:
return data_type()
types = extract_generic(collection)
if len(types) == 2 and types[1] == Ellipsis:
return data_type(_build_value(type_=types[0], data=item, config=config) for item in data)
return data_type(
_build_value(type_=type_, data=item, config=config) for item, type_ in zip_longest(data, types)
)
item_type = extract_generic(collection, defaults=(Any,))[0]
return data_type(_build_value(type_=item_type, data=item, config=config) for item in data)
elif isinstance(data, Collection) and is_subclass(collection, Collection):
item_type = extract_generic(collection, defaults=(Any,))[0]
return data_type(_build_value(type_=item_type, data=item, config=config) for item in data)
return data
4 changes: 2 additions & 2 deletions vendor/dacite/data.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from typing import Dict, Any
from typing import Mapping, Any

Data = Dict[str, Any]
Data = Mapping[str, Any]
19 changes: 9 additions & 10 deletions vendor/dacite/dataclasses.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from dataclasses import Field, MISSING, _FIELDS, _FIELD, _FIELD_INITVAR # type: ignore
from typing import Type, Any, TypeVar, List

from .data import Data
from .cache import cache
from .types import is_optional

T = TypeVar("T", bound=Any)
Expand All @@ -11,23 +11,22 @@ class DefaultValueNotFoundError(Exception):
pass


def get_default_value_for_field(field: Field) -> Any:
def get_default_value_for_field(field: Field, type_: Type) -> Any:
if field.default != MISSING:
return field.default
elif field.default_factory != MISSING: # type: ignore
return field.default_factory() # type: ignore
elif is_optional(field.type):
elif is_optional(type_):
return None
raise DefaultValueNotFoundError()


def create_instance(data_class: Type[T], init_values: Data, post_init_values: Data) -> T:
instance = data_class(**init_values)
for key, value in post_init_values.items():
setattr(instance, key, value)
return instance


@cache
def get_fields(data_class: Type[T]) -> List[Field]:
fields = getattr(data_class, _FIELDS)
return [f for f in fields.values() if f._field_type is _FIELD or f._field_type is _FIELD_INITVAR]


@cache
def is_frozen(data_class: Type[T]) -> bool:
return data_class.__dataclass_params__.frozen
34 changes: 34 additions & 0 deletions vendor/dacite/frozen_dict.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from collections.abc import Mapping


class FrozenDict(Mapping):
dict_cls = dict

def __init__(self, *args, **kwargs):
self._dict = self.dict_cls(*args, **kwargs)
self._hash = None

def __getitem__(self, key):
return self._dict[key]

def __contains__(self, key):
return key in self._dict

def copy(self, **add_or_replace):
return self.__class__(self, **add_or_replace)

def __iter__(self):
return iter(self._dict)

def __len__(self):
return len(self._dict)

def __repr__(self):
return f"<{self.__class__.__name__} {repr(self._dict)}>"

def __hash__(self):
if self._hash is None:
self._hash = 0
for key, value in self._dict.items():
self._hash ^= hash((key, value))
return self._hash
Loading

0 comments on commit 4533ae2

Please sign in to comment.