6 changed files with 900 additions and 12 deletions
@ -0,0 +1,25 @@
|
||||
# Maintainer: Jan Alexander Steffens (heftig) <jan.steffens@gmail.com> |
||||
|
||||
pkgname=cantarell-fonts |
||||
pkgver=0.200 |
||||
pkgrel=1 |
||||
epoch=1 |
||||
pkgdesc="Humanist sans serif font" |
||||
url="https://gitlab.gnome.org/GNOME/cantarell-fonts" |
||||
arch=(any) |
||||
license=(custom:SIL) |
||||
makedepends=(meson appstream-glib git) |
||||
source=("https://download.gnome.org/sources/$pkgname/$pkgver/$pkgname-$pkgver.tar.xz") |
||||
sha256sums=('a30131f7ab9d78e70415a7f601cbcc443ff5bcc44b0332ddc84d8624ba177661') |
||||
|
||||
build() { |
||||
arch-meson $pkgname-$pkgver build -D useprebuilt=true |
||||
ninja -C build |
||||
} |
||||
|
||||
package() { |
||||
DESTDIR="$pkgdir" meson install -C build |
||||
install -Dt "$pkgdir/usr/share/licenses/$pkgname" -m644 $pkgname-$pkgver/COPYING |
||||
} |
||||
|
||||
# vim:set ts=2 sw=2 et: |
@ -0,0 +1,415 @@
|
||||
diff --git a/src/cattr/_compat.py b/src/cattr/_compat.py
|
||||
index 73d81d5..7cd4c54 100644
|
||||
--- a/src/cattr/_compat.py
|
||||
+++ b/src/cattr/_compat.py
|
||||
@@ -13,20 +13,21 @@ version_info = sys.version_info[0:3]
|
||||
is_py2 = version_info[0] == 2
|
||||
is_py3 = version_info[0] == 3
|
||||
is_py37 = version_info[:2] == (3, 7)
|
||||
+is_py38 = version_info[:2] == (3, 8)
|
||||
|
||||
if is_py2:
|
||||
from functools32 import lru_cache
|
||||
from singledispatch import singledispatch
|
||||
|
||||
unicode = unicode # noqa
|
||||
bytes = str
|
||||
else:
|
||||
from functools import lru_cache, singledispatch # noqa
|
||||
|
||||
unicode = str
|
||||
bytes = bytes
|
||||
|
||||
-if is_py37:
|
||||
+if is_py37 or is_py38:
|
||||
from typing import List, Union, _GenericAlias
|
||||
|
||||
def is_union_type(obj):
|
||||
@@ -96,7 +97,11 @@ else:
|
||||
return issubclass(type, MutableSet)
|
||||
|
||||
def is_sequence(type):
|
||||
- return issubclass(type, Sequence)
|
||||
+ if is_py2:
|
||||
+ is_string = issubclass(type, basestring) # noqa:F821
|
||||
+ else:
|
||||
+ is_string = issubclass(type, str)
|
||||
+ return issubclass(type, Sequence) and not is_string
|
||||
|
||||
def is_tuple(type):
|
||||
return issubclass(type, Tuple)
|
||||
diff --git a/src/cattr/converters.py b/src/cattr/converters.py
|
||||
index 57b9f4d..24b039c 100644
|
||||
--- a/src/cattr/converters.py
|
||||
+++ b/src/cattr/converters.py
|
||||
@@ -1,5 +1,17 @@
|
||||
from enum import Enum
|
||||
-from typing import Mapping, Sequence, Optional, TypeVar, Any
|
||||
+from typing import ( # noqa: F401, imported for Mypy.
|
||||
+ Any,
|
||||
+ Callable,
|
||||
+ Dict,
|
||||
+ FrozenSet,
|
||||
+ Mapping,
|
||||
+ Optional,
|
||||
+ Sequence,
|
||||
+ Set,
|
||||
+ Tuple,
|
||||
+ Type,
|
||||
+ TypeVar,
|
||||
+)
|
||||
from ._compat import (
|
||||
bytes,
|
||||
is_bare,
|
||||
@@ -79,6 +91,8 @@ class Converter(object):
|
||||
[
|
||||
(_subclass(Mapping), self._unstructure_mapping),
|
||||
(_subclass(Sequence), self._unstructure_seq),
|
||||
+ (_subclass(Set), self._unstructure_seq),
|
||||
+ (_subclass(FrozenSet), self._unstructure_seq),
|
||||
(_subclass(Enum), self._unstructure_enum),
|
||||
(_is_attrs_class, self._unstructure_attrs),
|
||||
]
|
||||
@@ -121,95 +135,96 @@ class Converter(object):
|
||||
self._union_registry = {}
|
||||
|
||||
def unstructure(self, obj):
|
||||
+ # type: (Any) -> Any
|
||||
return self._unstructure_func.dispatch(obj.__class__)(obj)
|
||||
|
||||
@property
|
||||
def unstruct_strat(self):
|
||||
# type: () -> UnstructureStrategy
|
||||
"""The default way of unstructuring ``attrs`` classes."""
|
||||
return (
|
||||
UnstructureStrategy.AS_DICT
|
||||
if self._unstructure_attrs == self.unstructure_attrs_asdict
|
||||
else UnstructureStrategy.AS_TUPLE
|
||||
)
|
||||
|
||||
def register_unstructure_hook(self, cls, func):
|
||||
# type: (Type[T], Callable[[T], Any]) -> None
|
||||
"""Register a class-to-primitive converter function for a class.
|
||||
|
||||
The converter function should take an instance of the class and return
|
||||
its Python equivalent.
|
||||
"""
|
||||
self._unstructure_func.register_cls_list([(cls, func)])
|
||||
|
||||
def register_unstructure_hook_func(self, check_func, func):
|
||||
"""Register a class-to-primitive converter function for a class, using
|
||||
a function to check if it's a match.
|
||||
"""
|
||||
- # type: (Callable[Any], Callable[T], Any]) -> None
|
||||
self._unstructure_func.register_func_list([(check_func, func)])
|
||||
|
||||
def register_structure_hook(self, cl, func):
|
||||
"""Register a primitive-to-class converter function for a type.
|
||||
|
||||
The converter function should take two arguments:
|
||||
* a Python object to be converted,
|
||||
* the type to convert to
|
||||
|
||||
and return the instance of the class. The type may seem redundant, but
|
||||
is sometimes needed (for example, when dealing with generic classes).
|
||||
"""
|
||||
- # type: (Type[T], Callable[[Any, Type], T) -> None
|
||||
if is_union_type(cl):
|
||||
self._union_registry[cl] = func
|
||||
else:
|
||||
self._structure_func.register_cls_list([(cl, func)])
|
||||
|
||||
def register_structure_hook_func(self, check_func, func):
|
||||
- # type: (Callable[Any], Callable[T], Any]) -> None
|
||||
+ # type: (Callable[[Any], Any], Callable[[T], Any]) -> None
|
||||
"""Register a class-to-primitive converter function for a class, using
|
||||
a function to check if it's a match.
|
||||
"""
|
||||
self._structure_func.register_func_list([(check_func, func)])
|
||||
|
||||
def structure(self, obj, cl):
|
||||
+ # type: (Any, Type[T]) -> T
|
||||
"""Convert unstructured Python data structures to structured data."""
|
||||
- # type: (Any, Type) -> Any
|
||||
+
|
||||
return self._structure_func.dispatch(cl)(obj, cl)
|
||||
|
||||
# Classes to Python primitives.
|
||||
def unstructure_attrs_asdict(self, obj):
|
||||
+ # type: (Any) -> Dict[str, Any]
|
||||
"""Our version of `attrs.asdict`, so we can call back to us."""
|
||||
attrs = obj.__class__.__attrs_attrs__
|
||||
dispatch = self._unstructure_func.dispatch
|
||||
rv = self._dict_factory()
|
||||
for a in attrs:
|
||||
name = a.name
|
||||
v = getattr(obj, name)
|
||||
rv[name] = dispatch(v.__class__)(v)
|
||||
return rv
|
||||
|
||||
def unstructure_attrs_astuple(self, obj):
|
||||
+ # type: (Any) -> Tuple
|
||||
"""Our version of `attrs.astuple`, so we can call back to us."""
|
||||
attrs = obj.__class__.__attrs_attrs__
|
||||
return tuple(self.unstructure(getattr(obj, a.name)) for a in attrs)
|
||||
|
||||
def _unstructure_enum(self, obj):
|
||||
"""Convert an enum to its value."""
|
||||
return obj.value
|
||||
|
||||
def _unstructure_identity(self, obj):
|
||||
"""Just pass it through."""
|
||||
return obj
|
||||
|
||||
def _unstructure_seq(self, seq):
|
||||
"""Convert a sequence to primitive equivalents."""
|
||||
# We can reuse the sequence class, so tuples stay tuples.
|
||||
dispatch = self._unstructure_func.dispatch
|
||||
return seq.__class__(dispatch(e.__class__)(e) for e in seq)
|
||||
|
||||
def _unstructure_mapping(self, mapping):
|
||||
- # type: (Mapping) -> Any
|
||||
"""Convert a mapping of attr classes to primitive equivalents."""
|
||||
|
||||
# We can reuse the mapping class, so dicts stay dicts and OrderedDicts
|
||||
@@ -258,159 +273,159 @@ class Converter(object):
|
||||
# Attrs classes.
|
||||
|
||||
def structure_attrs_fromtuple(self, obj, cl):
|
||||
- # type: (Sequence[Any], Type) -> Any
|
||||
+ # type: (Tuple, Type[T]) -> T
|
||||
"""Load an attrs class from a sequence (tuple)."""
|
||||
conv_obj = [] # A list of converter parameters.
|
||||
- for a, value in zip(cl.__attrs_attrs__, obj):
|
||||
+ for a, value in zip(cl.__attrs_attrs__, obj): # type: ignore
|
||||
# We detect the type by the metadata.
|
||||
converted = self._structure_attr_from_tuple(a, a.name, value)
|
||||
conv_obj.append(converted)
|
||||
|
||||
- return cl(*conv_obj)
|
||||
+ return cl(*conv_obj) # type: ignore
|
||||
|
||||
def _structure_attr_from_tuple(self, a, name, value):
|
||||
"""Handle an individual attrs attribute."""
|
||||
type_ = a.type
|
||||
if type_ is None:
|
||||
# No type metadata.
|
||||
return value
|
||||
return self._structure_func.dispatch(type_)(value, type_)
|
||||
|
||||
def structure_attrs_fromdict(self, obj, cl):
|
||||
- # type: (Mapping, Type) -> Any
|
||||
+ # type: (Mapping[str, Any], Type[T]) -> T
|
||||
"""Instantiate an attrs class from a mapping (dict)."""
|
||||
# For public use.
|
||||
- conv_obj = obj.copy() # Dict of converted parameters.
|
||||
+ conv_obj = {} # Start with a fresh dict, to ignore extra keys.
|
||||
dispatch = self._structure_func.dispatch
|
||||
- for a in cl.__attrs_attrs__:
|
||||
+ for a in cl.__attrs_attrs__: # type: ignore
|
||||
# We detect the type by metadata.
|
||||
type_ = a.type
|
||||
- if type_ is None:
|
||||
- # No type.
|
||||
- continue
|
||||
name = a.name
|
||||
+
|
||||
try:
|
||||
val = obj[name]
|
||||
except KeyError:
|
||||
continue
|
||||
- conv_obj[name] = dispatch(type_)(val, type_)
|
||||
|
||||
- return cl(**conv_obj)
|
||||
+ if name[0] == "_":
|
||||
+ name = name[1:]
|
||||
+
|
||||
+ conv_obj[name] = (
|
||||
+ dispatch(type_)(val, type_) if type_ is not None else val
|
||||
+ )
|
||||
+
|
||||
+ return cl(**conv_obj) # type: ignore
|
||||
|
||||
def _structure_list(self, obj, cl):
|
||||
- # type: (Type[GenericMeta], Iterable[T]) -> List[T]
|
||||
"""Convert an iterable to a potentially generic list."""
|
||||
if is_bare(cl) or cl.__args__[0] is Any:
|
||||
return [e for e in obj]
|
||||
else:
|
||||
elem_type = cl.__args__[0]
|
||||
return [
|
||||
self._structure_func.dispatch(elem_type)(e, elem_type)
|
||||
for e in obj
|
||||
]
|
||||
|
||||
def _structure_set(self, obj, cl):
|
||||
- # type: (Type[GenericMeta], Iterable[T]) -> MutableSet[T]
|
||||
"""Convert an iterable into a potentially generic set."""
|
||||
if is_bare(cl) or cl.__args__[0] is Any:
|
||||
return set(obj)
|
||||
else:
|
||||
elem_type = cl.__args__[0]
|
||||
return {
|
||||
self._structure_func.dispatch(elem_type)(e, elem_type)
|
||||
for e in obj
|
||||
}
|
||||
|
||||
def _structure_frozenset(self, obj, cl):
|
||||
- # type: (Type[GenericMeta], Iterable[T]) -> FrozenSet[T]
|
||||
"""Convert an iterable into a potentially generic frozenset."""
|
||||
if is_bare(cl) or cl.__args__[0] is Any:
|
||||
return frozenset(obj)
|
||||
else:
|
||||
elem_type = cl.__args__[0]
|
||||
dispatch = self._structure_func.dispatch
|
||||
return frozenset(dispatch(elem_type)(e, elem_type) for e in obj)
|
||||
|
||||
def _structure_dict(self, obj, cl):
|
||||
- # type: (Type[GenericMeta], Mapping[T, V]) -> Dict[T, V]
|
||||
"""Convert a mapping into a potentially generic dict."""
|
||||
if is_bare(cl) or cl.__args__ == (Any, Any):
|
||||
return dict(obj)
|
||||
else:
|
||||
key_type, val_type = cl.__args__
|
||||
if key_type is Any:
|
||||
val_conv = self._structure_func.dispatch(val_type)
|
||||
return {k: val_conv(v, val_type) for k, v in obj.items()}
|
||||
elif val_type is Any:
|
||||
key_conv = self._structure_func.dispatch(key_type)
|
||||
return {key_conv(k, key_type): v for k, v in obj.items()}
|
||||
else:
|
||||
key_conv = self._structure_func.dispatch(key_type)
|
||||
val_conv = self._structure_func.dispatch(val_type)
|
||||
return {
|
||||
key_conv(k, key_type): val_conv(v, val_type)
|
||||
for k, v in obj.items()
|
||||
}
|
||||
|
||||
def _structure_union(self, obj, union):
|
||||
- # type: (_Union, Any): -> Any
|
||||
"""Deal with converting a union."""
|
||||
# Unions with NoneType in them are basically optionals.
|
||||
# We check for NoneType early and handle the case of obj being None,
|
||||
# so disambiguation functions don't need to handle NoneType.
|
||||
union_params = union.__args__
|
||||
- if NoneType in union_params:
|
||||
+ if NoneType in union_params: # type: ignore
|
||||
if obj is None:
|
||||
return None
|
||||
if len(union_params) == 2:
|
||||
# This is just a NoneType and something else.
|
||||
other = (
|
||||
union_params[0]
|
||||
- if union_params[1] is NoneType
|
||||
+ if union_params[1] is NoneType # type: ignore
|
||||
else union_params[1]
|
||||
)
|
||||
# We can't actually have a Union of a Union, so this is safe.
|
||||
return self._structure_func.dispatch(other)(obj, other)
|
||||
|
||||
# Check the union registry first.
|
||||
handler = self._union_registry.get(union)
|
||||
if handler is not None:
|
||||
return handler(obj, union)
|
||||
|
||||
# Getting here means either this is not an optional, or it's an
|
||||
# optional with more than one parameter.
|
||||
# Let's support only unions of attr classes for now.
|
||||
cl = self._dis_func_cache(union)(obj)
|
||||
return self._structure_func.dispatch(cl)(obj, cl)
|
||||
|
||||
def _structure_tuple(self, obj, tup):
|
||||
- # type: (Type[Tuple], Iterable) -> Any
|
||||
"""Deal with converting to a tuple."""
|
||||
tup_params = tup.__args__
|
||||
has_ellipsis = tup_params and tup_params[-1] is Ellipsis
|
||||
if tup_params is None or (has_ellipsis and tup_params[0] is Any):
|
||||
# Just a Tuple. (No generic information.)
|
||||
return tuple(obj)
|
||||
if has_ellipsis:
|
||||
# We're dealing with a homogenous tuple, Tuple[int, ...]
|
||||
tup_type = tup_params[0]
|
||||
conv = self._structure_func.dispatch(tup_type)
|
||||
return tuple(conv(e, tup_type) for e in obj)
|
||||
else:
|
||||
# We're dealing with a heterogenous tuple.
|
||||
return tuple(
|
||||
self._structure_func.dispatch(t)(e, t)
|
||||
for t, e in zip(tup_params, obj)
|
||||
)
|
||||
|
||||
def _get_dis_func(self, union):
|
||||
# type: (Type) -> Callable[..., Type]
|
||||
"""Fetch or try creating a disambiguation function for a union."""
|
||||
union_types = union.__args__
|
||||
- if NoneType in union_types:
|
||||
+ if NoneType in union_types: # type: ignore
|
||||
# We support unions of attrs classes and NoneType higher in the
|
||||
# logic.
|
||||
- union_types = tuple(e for e in union_types if e is not NoneType)
|
||||
+ union_types = tuple(
|
||||
+ e for e in union_types if e is not NoneType # type: ignore
|
||||
+ )
|
||||
|
||||
if not all(hasattr(e, "__attrs_attrs__") for e in union_types):
|
||||
raise ValueError(
|
||||
diff --git a/src/cattr/disambiguators.py b/src/cattr/disambiguators.py
|
||||
index 4daa078..765a630 100644
|
||||
--- a/src/cattr/disambiguators.py
|
||||
+++ b/src/cattr/disambiguators.py
|
||||
@@ -3,42 +3,48 @@ from collections import OrderedDict
|
||||
from functools import reduce
|
||||
from operator import or_
|
||||
|
||||
-from typing import Mapping
|
||||
+from typing import ( # noqa: F401, imported for Mypy.
|
||||
+ Callable,
|
||||
+ Dict,
|
||||
+ Mapping,
|
||||
+ Optional,
|
||||
+ Type,
|
||||
+)
|
||||
|
||||
from attr import fields
|
||||
|
||||
|
||||
-def create_uniq_field_dis_func(*cls):
|
||||
- # type: (*Sequence[Type]) -> Callable
|
||||
+def create_uniq_field_dis_func(*classes):
|
||||
+ # type: (*Type) -> Callable
|
||||
"""Given attr classes, generate a disambiguation function.
|
||||
|
||||
The function is based on unique fields."""
|
||||
- if len(cls) < 2:
|
||||
+ if len(classes) < 2:
|
||||
raise ValueError("At least two classes required.")
|
||||
- cls_and_attrs = [(cl, set(at.name for at in fields(cl))) for cl in cls]
|
||||
+ cls_and_attrs = [(cl, set(at.name for at in fields(cl))) for cl in classes]
|
||||
if len([attrs for _, attrs in cls_and_attrs if len(attrs) == 0]) > 1:
|
||||
raise ValueError("At least two classes have no attributes.")
|
||||
# TODO: Deal with a single class having no required attrs.
|
||||
# For each class, attempt to generate a single unique required field.
|
||||
- uniq_attrs_dict = OrderedDict()
|
||||
+ uniq_attrs_dict = OrderedDict() # type: Dict[str, Type]
|
||||
cls_and_attrs.sort(key=lambda c_a: -len(c_a[1]))
|
||||
|
||||
fallback = None # If none match, try this.
|
||||
|
||||
for i, (cl, cl_reqs) in enumerate(cls_and_attrs):
|
||||
other_classes = cls_and_attrs[i + 1 :]
|
||||
if other_classes:
|
||||
other_reqs = reduce(or_, (c_a[1] for c_a in other_classes))
|
||||
uniq = cl_reqs - other_reqs
|
||||
if not uniq:
|
||||
m = "{} has no usable unique attributes.".format(cl)
|
||||
raise ValueError(m)
|
||||
uniq_attrs_dict[next(iter(uniq))] = cl
|
||||
else:
|
||||
fallback = cl
|
||||
|
||||
def dis_func(data):
|
||||
- # type: (Mapping) -> Union
|
||||
+ # type: (Mapping) -> Optional[Type]
|
||||
if not isinstance(data, Mapping):
|
||||
raise ValueError("Only input mappings are supported.")
|
||||
for k, v in uniq_attrs_dict.items():
|
@ -0,0 +1,25 @@
|
||||
# Maintainer: Jan Alexander Steffens (heftig) <jan.steffens@gmail.com> |
||||
|
||||
pkgname=cantarell-fonts |
||||
pkgver=0.200 |
||||
pkgrel=1 |
||||
epoch=1 |
||||
pkgdesc="Humanist sans serif font" |
||||
url="https://gitlab.gnome.org/GNOME/cantarell-fonts" |
||||
arch=(any) |
||||
license=(custom:SIL) |
||||
makedepends=(meson appstream-glib git) |
||||
source=("https://download.gnome.org/sources/$pkgname/$pkgver/$pkgname-$pkgver.tar.xz") |
||||
sha256sums=('a30131f7ab9d78e70415a7f601cbcc443ff5bcc44b0332ddc84d8624ba177661') |
||||
|
||||
build() { |
||||
arch-meson $pkgname-$pkgver build -D useprebuilt=true |
||||
ninja -C build |
||||
} |
||||
|
||||
package() { |
||||
DESTDIR="$pkgdir" meson install -C build |
||||
install -Dt "$pkgdir/usr/share/licenses/$pkgname" -m644 $pkgname-$pkgver/COPYING |
||||
} |
||||
|
||||
# vim:set ts=2 sw=2 et: |
@ -0,0 +1,415 @@
|
||||
diff --git a/src/cattr/_compat.py b/src/cattr/_compat.py
|
||||
index 73d81d5..7cd4c54 100644
|
||||
--- a/src/cattr/_compat.py
|
||||
+++ b/src/cattr/_compat.py
|
||||
@@ -13,20 +13,21 @@ version_info = sys.version_info[0:3]
|
||||
is_py2 = version_info[0] == 2
|
||||
is_py3 = version_info[0] == 3
|
||||
is_py37 = version_info[:2] == (3, 7)
|
||||
+is_py38 = version_info[:2] == (3, 8)
|
||||
|
||||
if is_py2:
|
||||
from functools32 import lru_cache
|
||||
from singledispatch import singledispatch
|
||||
|
||||
unicode = unicode # noqa
|
||||
bytes = str
|
||||
else:
|
||||
from functools import lru_cache, singledispatch # noqa
|
||||
|
||||
unicode = str
|
||||
bytes = bytes
|
||||
|
||||
-if is_py37:
|
||||
+if is_py37 or is_py38:
|
||||
from typing import List, Union, _GenericAlias
|
||||
|
||||
def is_union_type(obj):
|
||||
@@ -96,7 +97,11 @@ else:
|
||||
return issubclass(type, MutableSet)
|
||||
|
||||
def is_sequence(type):
|
||||
- return issubclass(type, Sequence)
|
||||
+ if is_py2:
|
||||
+ is_string = issubclass(type, basestring) # noqa:F821
|
||||
+ else:
|
||||
+ is_string = issubclass(type, str)
|
||||
+ return issubclass(type, Sequence) and not is_string
|
||||
|
||||
def is_tuple(type):
|
||||
return issubclass(type, Tuple)
|
||||
diff --git a/src/cattr/converters.py b/src/cattr/converters.py
|
||||
index 57b9f4d..24b039c 100644
|
||||
--- a/src/cattr/converters.py
|
||||
+++ b/src/cattr/converters.py
|
||||
@@ -1,5 +1,17 @@
|
||||
from enum import Enum
|
||||
-from typing import Mapping, Sequence, Optional, TypeVar, Any
|
||||
+from typing import ( # noqa: F401, imported for Mypy.
|
||||
+ Any,
|
||||
+ Callable,
|
||||
+ Dict,
|
||||
+ FrozenSet,
|
||||
+ Mapping,
|
||||
+ Optional,
|
||||
+ Sequence,
|
||||
+ Set,
|
||||
+ Tuple,
|
||||
+ Type,
|
||||
+ TypeVar,
|
||||
+)
|
||||
from ._compat import (
|
||||
bytes,
|
||||
is_bare,
|
||||
@@ -79,6 +91,8 @@ class Converter(object):
|
||||
[
|
||||
(_subclass(Mapping), self._unstructure_mapping),
|
||||
(_subclass(Sequence), self._unstructure_seq),
|
||||
+ (_subclass(Set), self._unstructure_seq),
|
||||
+ (_subclass(FrozenSet), self._unstructure_seq),
|
||||
(_subclass(Enum), self._unstructure_enum),
|
||||
(_is_attrs_class, self._unstructure_attrs),
|
||||
]
|
||||
@@ -121,95 +135,96 @@ class Converter(object):
|
||||
self._union_registry = {}
|
||||
|
||||
def unstructure(self, obj):
|
||||
+ # type: (Any) -> Any
|
||||
return self._unstructure_func.dispatch(obj.__class__)(obj)
|
||||
|
||||
@property
|
||||
def unstruct_strat(self):
|
||||
# type: () -> UnstructureStrategy
|
||||
"""The default way of unstructuring ``attrs`` classes."""
|
||||
return (
|
||||
UnstructureStrategy.AS_DICT
|
||||
if self._unstructure_attrs == self.unstructure_attrs_asdict
|
||||
else UnstructureStrategy.AS_TUPLE
|
||||
)
|
||||
|
||||
def register_unstructure_hook(self, cls, func):
|
||||
# type: (Type[T], Callable[[T], Any]) -> None
|
||||
"""Register a class-to-primitive converter function for a class.
|
||||
|
||||
The converter function should take an instance of the class and return
|
||||
its Python equivalent.
|
||||
"""
|
||||
self._unstructure_func.register_cls_list([(cls, func)])
|
||||
|
||||
def register_unstructure_hook_func(self, check_func, func):
|
||||
"""Register a class-to-primitive converter function for a class, using
|
||||
a function to check if it's a match.
|
||||
"""
|
||||
- # type: (Callable[Any], Callable[T], Any]) -> None
|
||||
self._unstructure_func.register_func_list([(check_func, func)])
|
||||
|
||||
def register_structure_hook(self, cl, func):
|
||||
"""Register a primitive-to-class converter function for a type.
|
||||
|
||||
The converter function should take two arguments:
|
||||
* a Python object to be converted,
|
||||
* the type to convert to
|
||||
|
||||
and return the instance of the class. The type may seem redundant, but
|
||||
is sometimes needed (for example, when dealing with generic classes).
|
||||
"""
|
||||
- # type: (Type[T], Callable[[Any, Type], T) -> None
|
||||
if is_union_type(cl):
|
||||
self._union_registry[cl] = func
|
||||
else:
|
||||
self._structure_func.register_cls_list([(cl, func)])
|
||||
|
||||
def register_structure_hook_func(self, check_func, func):
|
||||
- # type: (Callable[Any], Callable[T], Any]) -> None
|
||||
+ # type: (Callable[[Any], Any], Callable[[T], Any]) -> None
|
||||
"""Register a class-to-primitive converter function for a class, using
|
||||
a function to check if it's a match.
|
||||
"""
|
||||
self._structure_func.register_func_list([(check_func, func)])
|
||||
|
||||
def structure(self, obj, cl):
|
||||
+ # type: (Any, Type[T]) -> T
|
||||
"""Convert unstructured Python data structures to structured data."""
|
||||
- # type: (Any, Type) -> Any
|
||||
+
|
||||
return self._structure_func.dispatch(cl)(obj, cl)
|
||||
|
||||
# Classes to Python primitives.
|
||||
def unstructure_attrs_asdict(self, obj):
|
||||
+ # type: (Any) -> Dict[str, Any]
|
||||
"""Our version of `attrs.asdict`, so we can call back to us."""
|
||||
attrs = obj.__class__.__attrs_attrs__
|
||||
dispatch = self._unstructure_func.dispatch
|
||||
rv = self._dict_factory()
|
||||
for a in attrs:
|
||||
name = a.name
|
||||
v = getattr(obj, name)
|
||||
rv[name] = dispatch(v.__class__)(v)
|
||||
return rv
|
||||
|
||||
def unstructure_attrs_astuple(self, obj):
|
||||
+ # type: (Any) -> Tuple
|
||||
"""Our version of `attrs.astuple`, so we can call back to us."""
|
||||
attrs = obj.__class__.__attrs_attrs__
|
||||
return tuple(self.unstructure(getattr(obj, a.name)) for a in attrs)
|
||||
|
||||
def _unstructure_enum(self, obj):
|
||||
"""Convert an enum to its value."""
|
||||
return obj.value
|
||||
|
||||
def _unstructure_identity(self, obj):
|
||||
"""Just pass it through."""
|
||||
return obj
|
||||
|
||||
def _unstructure_seq(self, seq):
|
||||
"""Convert a sequence to primitive equivalents."""
|
||||
# We can reuse the sequence class, so tuples stay tuples.
|
||||
dispatch = self._unstructure_func.dispatch
|
||||
return seq.__class__(dispatch(e.__class__)(e) for e in seq)
|
||||
|
||||
def _unstructure_mapping(self, mapping):
|
||||
- # type: (Mapping) -> Any
|
||||
"""Convert a mapping of attr classes to primitive equivalents."""
|
||||
|
||||
# We can reuse the mapping class, so dicts stay dicts and OrderedDicts
|
||||
@@ -258,159 +273,159 @@ class Converter(object):
|
||||
# Attrs classes.
|
||||
|
||||
def structure_attrs_fromtuple(self, obj, cl):
|
||||
- # type: (Sequence[Any], Type) -> Any
|
||||
+ # type: (Tuple, Type[T]) -> T
|
||||
"""Load an attrs class from a sequence (tuple)."""
|
||||
conv_obj = [] # A list of converter parameters.
|
||||
- for a, value in zip(cl.__attrs_attrs__, obj):
|
||||
+ for a, value in zip(cl.__attrs_attrs__, obj): # type: ignore
|
||||
# We detect the type by the metadata.
|
||||
converted = self._structure_attr_from_tuple(a, a.name, value)
|
||||
conv_obj.append(converted)
|
||||
|
||||
- return cl(*conv_obj)
|
||||
+ return cl(*conv_obj) # type: ignore
|
||||
|
||||
def _structure_attr_from_tuple(self, a, name, value):
|
||||
"""Handle an individual attrs attribute."""
|
||||
type_ = a.type
|
||||
if type_ is None:
|
||||
# No type metadata.
|
||||
return value
|
||||
return self._structure_func.dispatch(type_)(value, type_)
|
||||
|
||||
def structure_attrs_fromdict(self, obj, cl):
|
||||
- # type: (Mapping, Type) -> Any
|
||||
+ # type: (Mapping[str, Any], Type[T]) -> T
|
||||
"""Instantiate an attrs class from a mapping (dict)."""
|
||||
# For public use.
|
||||
- conv_obj = obj.copy() # Dict of converted parameters.
|
||||
+ conv_obj = {} # Start with a fresh dict, to ignore extra keys.
|
||||
dispatch = self._structure_func.dispatch
|
||||
- for a in cl.__attrs_attrs__:
|
||||
+ for a in cl.__attrs_attrs__: # type: ignore
|
||||
# We detect the type by metadata.
|
||||
type_ = a.type
|
||||
- if type_ is None:
|
||||
- # No type.
|
||||
- continue
|
||||
name = a.name
|
||||
+
|
||||
try:
|
||||
val = obj[name]
|
||||
except KeyError:
|
||||
continue
|
||||
- conv_obj[name] = dispatch(type_)(val, type_)
|
||||
|
||||
- return cl(**conv_obj)
|
||||
+ if name[0] == "_":
|
||||
+ name = name[1:]
|
||||
+
|
||||
+ conv_obj[name] = (
|
||||
+ dispatch(type_)(val, type_) if type_ is not None else val
|
||||
+ )
|
||||
+
|
||||
+ return cl(**conv_obj) # type: ignore
|
||||
|
||||
def _structure_list(self, obj, cl):
|
||||
- # type: (Type[GenericMeta], Iterable[T]) -> List[T]
|
||||
"""Convert an iterable to a potentially generic list."""
|
||||
if is_bare(cl) or cl.__args__[0] is Any:
|
||||
return [e for e in obj]
|
||||
else:
|
||||
elem_type = cl.__args__[0]
|
||||
return [
|
||||
self._structure_func.dispatch(elem_type)(e, elem_type)
|
||||
for e in obj
|
||||
]
|
||||
|
||||
def _structure_set(self, obj, cl):
|
||||
- # type: (Type[GenericMeta], Iterable[T]) -> MutableSet[T]
|
||||
"""Convert an iterable into a potentially generic set."""
|
||||
if is_bare(cl) or cl.__args__[0] is Any:
|
||||
return set(obj)
|
||||
else:
|
||||
elem_type = cl.__args__[0]
|
||||
return {
|
||||
self._structure_func.dispatch(elem_type)(e, elem_type)
|
||||
for e in obj
|
||||
}
|
||||
|
||||
def _structure_frozenset(self, obj, cl):
|
||||
- # type: (Type[GenericMeta], Iterable[T]) -> FrozenSet[T]
|
||||
"""Convert an iterable into a potentially generic frozenset."""
|
||||
if is_bare(cl) or cl.__args__[0] is Any:
|
||||
return frozenset(obj)
|
||||
else:
|
||||
elem_type = cl.__args__[0]
|
||||
dispatch = self._structure_func.dispatch
|
||||
return frozenset(dispatch(elem_type)(e, elem_type) for e in obj)
|
||||
|
||||
def _structure_dict(self, obj, cl):
|
||||
- # type: (Type[GenericMeta], Mapping[T, V]) -> Dict[T, V]
|
||||
"""Convert a mapping into a potentially generic dict."""
|
||||
if is_bare(cl) or cl.__args__ == (Any, Any):
|
||||
return dict(obj)
|
||||
else:
|
||||
key_type, val_type = cl.__args__
|
||||
if key_type is Any:
|
||||
val_conv = self._structure_func.dispatch(val_type)
|
||||
return {k: val_conv(v, val_type) for k, v in obj.items()}
|
||||
elif val_type is Any:
|
||||
key_conv = self._structure_func.dispatch(key_type)
|
||||
return {key_conv(k, key_type): v for k, v in obj.items()}
|
||||
else:
|
||||
key_conv = self._structure_func.dispatch(key_type)
|
||||
val_conv = self._structure_func.dispatch(val_type)
|
||||
return {
|
||||
key_conv(k, key_type): val_conv(v, val_type)
|
||||
for k, v in obj.items()
|
||||
}
|
||||
|
||||
def _structure_union(self, obj, union):
|
||||
- # type: (_Union, Any): -> Any
|
||||
"""Deal with converting a union."""
|
||||
# Unions with NoneType in them are basically optionals.
|
||||
# We check for NoneType early and handle the case of obj being None,
|
||||
# so disambiguation functions don't need to handle NoneType.
|
||||
union_params = union.__args__
|
||||
- if NoneType in union_params:
|
||||
+ if NoneType in union_params: # type: ignore
|
||||
if obj is None:
|
||||
return None
|
||||
if len(union_params) == 2:
|
||||
# This is just a NoneType and something else.
|
||||
other = (
|
||||
union_params[0]
|
||||
- if union_params[1] is NoneType
|
||||
+ if union_params[1] is NoneType # type: ignore
|
||||
else union_params[1]
|
||||
)
|
||||
# We can't actually have a Union of a Union, so this is safe.
|
||||
return self._structure_func.dispatch(other)(obj, other)
|
||||
|
||||
# Check the union registry first.
|
||||
handler = self._union_registry.get(union)
|
||||
if handler is not None:
|
||||
return handler(obj, union)
|
||||
|
||||
# Getting here means either this is not an optional, or it's an
|
||||
# optional with more than one parameter.
|
||||
# Let's support only unions of attr classes for now.
|
||||
cl = self._dis_func_cache(union)(obj)
|
||||
return self._structure_func.dispatch(cl)(obj, cl)
|
||||
|
||||
def _structure_tuple(self, obj, tup):
|
||||
- # type: (Type[Tuple], Iterable) -> Any
|
||||
"""Deal with converting to a tuple."""
|
||||
tup_params = tup.__args__
|
||||
has_ellipsis = tup_params and tup_params[-1] is Ellipsis
|
||||
if tup_params is None or (has_ellipsis and tup_params[0] is Any):
|
||||
# Just a Tuple. (No generic information.)
|
||||
return tuple(obj)
|
||||
if has_ellipsis:
|
||||
# We're dealing with a homogenous tuple, Tuple[int, ...]
|
||||
tup_type = tup_params[0]
|
||||
conv = self._structure_func.dispatch(tup_type)
|
||||
return tuple(conv(e, tup_type) for e in obj)
|
||||
else:
|
||||
# We're dealing with a heterogenous tuple.
|
||||
return tuple(
|
||||
self._structure_func.dispatch(t)(e, t)
|
||||
for t, e in zip(tup_params, obj)
|
||||
)
|
||||
|
||||
def _get_dis_func(self, union):
|
||||
# type: (Type) -> Callable[..., Type]
|
||||
"""Fetch or try creating a disambiguation function for a union."""
|
||||
union_types = union.__args__
|
||||
- if NoneType in union_types:
|
||||
+ if NoneType in union_types: # type: ignore
|
||||
# We support unions of attrs classes and NoneType higher in the
|
||||
# logic.
|
||||
- union_types = tuple(e for e in union_types if e is not NoneType)
|
||||
+ union_types = tuple(
|
||||
+ e for e in union_types if e is not NoneType # type: ignore
|
||||
+ )
|
||||
|
||||
if not all(hasattr(e, "__attrs_attrs__") for e in union_types):
|
||||
raise ValueError(
|
||||
diff --git a/src/cattr/disambiguators.py b/src/cattr/disambiguators.py
|
||||
index 4daa078..765a630 100644
|
||||
--- a/src/cattr/disambiguators.py
|
||||
+++ b/src/cattr/disambiguators.py
|
||||
@@ -3,42 +3,48 @@ from collections import OrderedDict
|
||||
from functools import reduce
|
||||
from operator import or_
|
||||
|
||||
-from typing import Mapping
|
||||
+from typing import ( # noqa: F401, imported for Mypy.
|
||||
+ Callable,
|
||||
+ Dict,
|
||||
+ Mapping,
|
||||
+ Optional,
|
||||
+ Type,
|
||||
+)
|
||||
|
||||
from attr import fields
|
||||
|
||||
|
||||
-def create_uniq_field_dis_func(*cls):
|
||||
- # type: (*Sequence[Type]) -> Callable
|
||||
+def create_uniq_field_dis_func(*classes):
|
||||
+ # type: (*Type) -> Callable
|
||||
"""Given attr classes, generate a disambiguation function.
|
||||
|
||||
The function is based on unique fields."""
|
||||
- if len(cls) < 2:
|
||||
+ if len(classes) < 2:
|
||||
raise ValueError("At least two classes required.")
|
||||
- cls_and_attrs = [(cl, set(at.name for at in fields(cl))) for cl in cls]
|
||||
+ cls_and_attrs = [(cl, set(at.name for at in fields(cl))) for cl in classes]
|
||||
if len([attrs for _, attrs in cls_and_attrs if len(attrs) == 0]) > 1:
|
||||
raise ValueError("At least two classes have no attributes.")
|
||||
# TODO: Deal with a single class having no required attrs.
|
||||
# For each class, attempt to generate a single unique required field.
|
||||
- uniq_attrs_dict = OrderedDict()
|
||||
+ uniq_attrs_dict = OrderedDict() # type: Dict[str, Type]
|
||||
cls_and_attrs.sort(key=lambda c_a: -len(c_a[1]))
|
||||
|
||||
fallback = None # If none match, try this.
|
||||
|
||||
for i, (cl, cl_reqs) in enumerate(cls_and_attrs):
|
||||
other_classes = cls_and_attrs[i + 1 :]
|
||||
if other_classes:
|
||||
other_reqs = reduce(or_, (c_a[1] for c_a in other_classes))
|
||||
uniq = cl_reqs - other_reqs
|
||||
if not uniq:
|
||||
m = "{} has no usable unique attributes.".format(cl)
|
||||
raise ValueError(m)
|
||||
uniq_attrs_dict[next(iter(uniq))] = cl
|
||||
else:
|
||||
fallback = cl
|
||||
|
||||
def dis_func(data):
|
||||
- # type: (Mapping) -> Union
|
||||
+ # type: (Mapping) -> Optional[Type]
|
||||
if not isinstance(data, Mapping):
|
||||
raise ValueError("Only input mappings are supported.")
|
||||
for k, v in uniq_attrs_dict.items():
|
Loading…
Reference in new issue