# Path: src/related/functions.py
# def to_model(cls, value):
#     """
#     Coerce a value into a model object based on a class-type (cls).
#     :param cls: class type to coerce into
#     :param value: value to be coerced
#     :return: original value or coerced value (value')
#     """
# 
#     if isinstance(value, cls) or value is None:
#         pass  # skip if right type or value is None
# 
#     elif issubclass(cls, Enum):
#         value = cls(value)
# 
#     elif is_model(cls) and isinstance(value, dict):
#         value = convert_key_to_attr_names(cls, value)
#         value = cls(**value)
# 
#     else:
#         value = cls(value)
# 
#     return value
# 
# @singledispatch
# def to_dict(obj, **kwargs):
#     """
#     Convert an object into dictionary. Uses singledispatch to allow for
#     clean extensions for custom class types.
# 
#     Reference: https://pypi.python.org/pypi/singledispatch
# 
#     :param obj: object instance
#     :param kwargs: keyword arguments such as suppress_private_attr,
#                    suppress_empty_values, dict_factory
#     :return: converted dictionary.
#     """
# 
#     # if is_related, then iterate attrs.
#     if is_model(obj.__class__):
#         return related_obj_to_dict(obj, **kwargs)
# 
#     # else, return obj directly. register a custom to_dict if you need to!
#     #   reference: https://pypi.python.org/pypi/singledispatch
#     else:
#         return obj
# 
# def is_model(cls):
#     """
#     Check whether *cls* is a class with ``attrs`` attributes.
# 
#     :param type cls: Class to introspect.
#     :raise TypeError: If *cls* is not a class.
# 
#     :rtype: :class:`bool`
#     """
#     return getattr(cls, "__attrs_attrs__", None) is not None


import inspect
from attr import attrs
from .functions import to_model, to_dict, is_model


def mutable(maybe_cls=None, strict=False):

    def wrap(cls):
        wrapped = attrs(cls)
        wrapped.__related_strict__ = strict
        return wrapped

    return wrap(maybe_cls) if maybe_cls is not None else wrap


def immutable(maybe_cls=None, strict=False):

    def wrap(cls):
        wrapped = attrs(cls, frozen=True, slots=True)
        wrapped.__related_strict__ = strict
        return wrapped

    return wrap(maybe_cls) if maybe_cls is not None else wrap


def _get_annotation_map(func, **kwargs):
    annotation_map = kwargs.copy()

    try:
        func_sig = inspect.signature(func)
        for index, arg in enumerate(func_sig.parameters.values()):
            if is_model(arg.annotation):
                annotation_map[arg.name] = arg.annotation

# Path: src/related/functions.py
# def to_model(cls, value):
#     """
#     Coerce a value into a model object based on a class-type (cls).
#     :param cls: class type to coerce into
#     :param value: value to be coerced
#     :return: original value or coerced value (value')
#     """
# 
#     if isinstance(value, cls) or value is None:
#         pass  # skip if right type or value is None
# 
#     elif issubclass(cls, Enum):
#         value = cls(value)
# 
#     elif is_model(cls) and isinstance(value, dict):
#         value = convert_key_to_attr_names(cls, value)
#         value = cls(**value)
# 
#     else:
#         value = cls(value)
# 
#     return value
# 
# @singledispatch
# def to_dict(obj, **kwargs):
#     """
#     Convert an object into dictionary. Uses singledispatch to allow for
#     clean extensions for custom class types.
# 
#     Reference: https://pypi.python.org/pypi/singledispatch
# 
#     :param obj: object instance
#     :param kwargs: keyword arguments such as suppress_private_attr,
#                    suppress_empty_values, dict_factory
#     :return: converted dictionary.
#     """
# 
#     # if is_related, then iterate attrs.
#     if is_model(obj.__class__):
#         return related_obj_to_dict(obj, **kwargs)
# 
#     # else, return obj directly. register a custom to_dict if you need to!
#     #   reference: https://pypi.python.org/pypi/singledispatch
#     else:
#         return obj
# 
# def is_model(cls):
#     """
#     Check whether *cls* is a class with ``attrs`` attributes.
# 
#     :param type cls: Class to introspect.
#     :raise TypeError: If *cls* is not a class.
# 
#     :rtype: :class:`bool`
#     """
#     return getattr(cls, "__attrs_attrs__", None) is not None


# Path: src/related/decorators.py
import inspect
from attr import attrs
from .functions import to_model, to_dict, is_model



def mutable(maybe_cls=None, strict=False):

    def wrap(cls):
        wrapped = attrs(cls)
        wrapped.__related_strict__ = strict
        return wrapped

    return wrap(maybe_cls) if maybe_cls is not None else wrap


def immutable(maybe_cls=None, strict=False):

    def wrap(cls):
        wrapped = attrs(cls, frozen=True, slots=True)
        wrapped.__related_strict__ = strict
        return wrapped

    return wrap(maybe_cls) if maybe_cls is not None else wrap


def _get_annotation_map(func, **kwargs):
    annotation_map = kwargs.copy()

    try:
        func_sig = inspect.signature(func)
        for index, arg in enumerate(func_sig.parameters.values()):
            if is_model(arg.annotation):
                annotation_map[arg.name] = arg.annotation

    except AttributeError: