text: from enum import Enum
  from typing import Mapping, Sequence, Optional, TypeVar, Any
  from ._compat import (
      bytes,
      is_bare,
      is_frozenset,
      is_mapping,
      is_mutable_set,
      is_py2,
      is_sequence,
      is_tuple,
      is_union_type,
      lru_cache,
      unicode,
  )
  from .disambiguators import create_uniq_field_dis_func
  from .multistrategy_dispatch import MultiStrategyDispatch
  
  
  NoneType = type(None)
  T = TypeVar("T")
  V = TypeVar("V")
  
  
  class UnstructureStrategy(Enum):
      """`attrs` classes unstructuring strategies."""
  
      AS_DICT = "asdict"
      AS_TUPLE = "astuple"
  
  
  def _is_attrs_class(cls):
      return getattr(cls, "__attrs_attrs__", None) is not None
  
  
  def _subclass(typ):
      """ a shortcut """
      return lambda cls: issubclass(cls, typ)
  
  
  class Converter(object):
      """Converts between structured and unstructured data."""
  
      __slots__ = (
          "_dis_func_cache",
          "_unstructure_func",
          "_unstructure_attrs",
          "_structure_attrs",
          "_dict_factory",
          "_union_registry",
          "_structure_func",
      )
  
      def __init__(
          self, dict_factory=dict, unstruct_strat=UnstructureStrategy.AS_DICT
      ):
          unstruct_strat = UnstructureStrategy(unstruct_strat)
  
          # Create a per-instance cache.
          if unstruct_strat is UnstructureStrategy.AS_DICT:
              self._unstructure_attrs = self.unstructure_attrs_asdict
              self._structure_attrs = self.structure_attrs_fromdict
          else:
              self._unstructure_attrs = self.unstructure_attrs_astuple
              self._structure_attrs = self.structure_attrs_fromtuple
  
          self._dis_func_cache = lru_cache()(self._get_dis_func)
  
          self._unstructure_func = MultiStrategyDispatch(
              self._unstructure_identity
          )
          self._unstructure_func.register_cls_list(
              [
                  (bytes, self._unstructure_identity),
                  (unicode, self._unstructure_identity),
              ]
          )
          self._unstructure_func.register_func_list(
              [
                  (_subclass(Mapping), self._unstructure_mapping),
                  (_subclass(Sequence), self._unstructure_seq),
                  (_subclass(Enum), self._unstructure_enum),
                  (_is_attrs_class, self._unstructure_attrs),
              ]
          )
  
          # Per-instance register of to-attrs converters.
          # Singledispatch dispatches based on the first argument, so we
          # store the function and switch the arguments in self.loads.
          self._structure_func = MultiStrategyDispatch(self._structure_default)
          self._structure_func.register_func_list(
              [
                  (is_sequence, self._structure_list),
                  (is_mutable_set, self._structure_set),
                  (is_frozenset, self._structure_frozenset),
                  (is_tuple, self._structure_tuple),
                  (is_mapping, self._structure_dict),
                  (is_union_type, self._structure_union),
                  (_is_attrs_class, self._structure_attrs),
              ]
          )
          # Strings are sequences.
          self._structure_func.register_cls_list(
              [
                  (
                      unicode,
                      self._structure_unicode
                      if is_py2
                      else self._structure_call,
                  ),
                  (bytes, self._structure_call),
                  (int, self._structure_call),
                  (float, self._structure_call),
                  (Enum, self._structure_call),
              ]
          )
  
          self._dict_factory = dict_factory
  
          # Unions are instances now, not classes. We use different registry.
          self._union_registry = {}
  
      def unstructure(self, obj):
          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
          """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):
          """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):
          """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):
          """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
          # stay OrderedDicts.
          dispatch = self._unstructure_func.dispatch
          return mapping.__class__(
              (dispatch(k.__class__)(k), dispatch(v.__class__)(v))
              for k, v in mapping.items()
          )
  
      # Python primitives to classes.
  
      def _structure_default(self, obj, cl):
          """This is the fallthrough case. Everything is a subclass of `Any`.
  
          A special condition here handles ``attrs`` classes.
  
          Bare optionals end here too (optionals with arguments are unions.) We
          treat bare optionals as Any.
          """
          if cl is Any or cl is Optional:
              return obj
          # We don't know what this is, so we complain loudly.
          msg = (
              "Unsupported type: {0}. Register a structure hook for "
              "it.".format(cl)
          )
          raise ValueError(msg)
  
      def _structure_call(self, obj, cl):
          """Just call ``cl`` with the given ``obj``.
  
          This is just an optimization on the ``_structure_default`` case, when
          we know we can skip the ``if`` s. Use for ``str``, ``bytes``, ``enum``,
          etc.
          """
          return cl(obj)
  
      def _structure_unicode(self, obj, cl):
          """Just call ``cl`` with the given ``obj``"""
          if not isinstance(obj, (bytes, unicode)):
              return cl(str(obj))
          else:
              return obj
  
      # Attrs classes.
  
      def structure_attrs_fromtuple(self, obj, cl):
          # type: (Sequence[Any], Type) -> Any
          """Load an attrs class from a sequence (tuple)."""
          conv_obj = []  # A list of converter parameters.
          for a, value in zip(cl.__attrs_attrs__, obj):
              # 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)
  
      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
          """Instantiate an attrs class from a mapping (dict)."""
          # For public use.
          conv_obj = obj.copy()  # Dict of converted parameters.
          dispatch = self._structure_func.dispatch
          for a in cl.__attrs_attrs__:
              # 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)
  
      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 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
                      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:
              # 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)
  
          if not all(hasattr(e, "__attrs_attrs__") for e in union_types):
              raise ValueError(
                  "Only unions of attr classes supported "
                  "currently. Register a loads hook manually."
              )
          return create_uniq_field_dis_func(*union_types)
  