# Standard library imports
import collections
import functools
# Local imports
from uplink.compat import abc
from uplink.converters import interfaces, register_default_converter_factory
__all__ = ["TypingConverter", "ListConverter", "DictConverter"]
class BaseTypeConverter(object):
Builder = collections.namedtuple("Builder", "build")
@classmethod
def freeze(cls, *args, **kwargs):
return cls.Builder(functools.partial(cls, *args, **kwargs))
class ListConverter(BaseTypeConverter, interfaces.Converter):
def __init__(self, elem_type):
self._elem_type = elem_type
self._elem_converter = None
def set_chain(self, chain):
self._elem_converter = chain(self._elem_type) or self._elem_type
def convert(self, value):
if isinstance(value, abc.Sequence):
return list(map(self._elem_converter, value))
else:
# TODO: Handle the case where the value is not an sequence.
return [self._elem_converter(value)]
class DictConverter(BaseTypeConverter, interfaces.Converter):
def __init__(self, key_type, value_type):
self._key_type = key_type
self._value_type = value_type
self._key_converter = None
self._value_converter = None
def set_chain(self, chain):
self._key_converter = chain(self._key_type) or self._key_type
self._value_converter = chain(self._value_type) or self._value_type
def convert(self, value):
if isinstance(value, abc.Mapping):
key_c, val_c = self._key_converter, self._value_converter
return dict((key_c(k), val_c(value[k])) for k in value)
else:
# TODO: Handle the case where the value is not a mapping.
return self._value_converter(value)
class _TypeProxy(object):
def __init__(self, func):
self._func = func
def __getitem__(self, item):
items = item if isinstance(item, tuple) else (item,)
return self._func(*items)
def _get_types(try_typing=True):
if TypingConverter.typing and try_typing:
return TypingConverter.typing.List, TypingConverter.typing.Dict
else:
return (
_TypeProxy(ListConverter.freeze),
_TypeProxy(DictConverter.freeze),
)
[docs]@register_default_converter_factory
class TypingConverter(interfaces.Factory):
"""
.. versionadded: v0.5.0
An adapter that serializes and deserializes collection types from
the :py:mod:`typing` module, such as :py:class:`typing.List`.
Inner types of a collection are recursively resolved, using other
available converters if necessary. For instance, when resolving the
type hint :py:attr:`typing.Sequence[UserSchema]`, where
:py:attr:`UserSchema` is a custom :py:class:`marshmallow.Schema`
subclass, the converter will resolve the inner type using
:py:class:`uplink.converters.MarshmallowConverter`.
.. code-block:: python
@get("/users")
def get_users(self) -> typing.Sequence[UserSchema]:
'''Fetch all users.'''
Note:
The :py:mod:`typing` module is available in the standard library
starting from Python 3.5. For earlier versions of Python, there
is a port of the module available on PyPI.
However, you can utilize this converter without the
:py:mod:`typing` module by using one of the proxies defined by
:py:class:`uplink.returns` (e.g., :py:obj:`uplink.types.List`).
"""
try:
import typing
except ImportError: # pragma: no cover
typing = None
def _check_typing(self, t):
has_origin = hasattr(t, "__origin__")
has_args = hasattr(t, "__args__")
return self.typing and has_origin and has_args
def _base_converter(self, type_):
if isinstance(type_, BaseTypeConverter.Builder):
return type_.build()
elif self._check_typing(type_):
if issubclass(type_.__origin__, self.typing.Sequence):
return ListConverter(*type_.__args__)
elif issubclass(type_.__origin__, self.typing.Mapping):
return DictConverter(*type_.__args__)
def create_response_body_converter(self, type_, *args, **kwargs):
return self._base_converter(type_)
def create_request_body_converter(self, type_, *args, **kwargs):
return self._base_converter(type_)
TypingConverter.List, TypingConverter.Dict = _get_types()