Source code for sqlathanor.declarative._base_configuration_mixin

# -*- coding: utf-8 -*-

# The lack of a module docstring for this module is **INTENTIONAL**.
# The module is imported into the documentation using Sphinx's autodoc
# extension, and its member function documentation is automatically incorporated
# there as needed.

import inspect as inspect_
from collections import OrderedDict

from sqlalchemy.inspection import inspect
from sqlalchemy.exc import InvalidRequestError
from sqlalchemy.ext.associationproxy import AssociationProxy
from validator_collection import checkers

from sqlathanor._compat import dict as dict_
from sqlathanor.attributes import AttributeConfiguration, validate_serialization_config, \
    BLANK_ON_SERIALIZE
from sqlathanor.errors import ConfigurationError, UnsupportedSerializationError


class ConfigurationMixin(object):
    """Mixin that provides base serialization configuration support."""

    @classmethod
    def _get_instance_attributes(cls,
                                 include_private = False,
                                 exclude_methods = True):
        """Retrieve the names of the model's attributes and methods.

        :param include_private: If ``True``, includes properties whose names start
          with an underscore. Defaults to ``False``.
        :type include_private: :class:`bool <python:bool>`

        :param exclude_methods: If ``True``, excludes attributes that correspond to
          methods (are callable). Defaults to ``True``.
        :type exclude_methods: :class:`bool <python:bool>`

        .. note::

          This method will return all attributes, properties, and methods supported
          by the model - whether they map to database columns or not.

        :returns: An iterable of attribute names defined for the model.
        :rtype: :class:`list <python:list>` of :class:`str <python:str>`

        """
        base_attributes = dir(cls)
        instance_attributes = []
        for key in base_attributes:
            if key.startswith('__'):
                continue

            if key.startswith('_') and not key.startswith('__') and not include_private:
                continue

            try:
                item = getattr(cls, key)
            except InvalidRequestError as error:
                is_AssociationProxy = isinstance(inspect(cls).all_orm_descriptors[key], AssociationProxy)
                if not is_AssociationProxy:
                    raise error

                item = None

            if checkers.is_callable(item) and exclude_methods:
                continue

            instance_attributes.append(key)

        return instance_attributes

    @classmethod
    def _get_declarative_serializable_attributes(cls,
                                                 from_csv = None,
                                                 to_csv = None,
                                                 from_json = None,
                                                 to_json = None,
                                                 from_yaml = None,
                                                 to_yaml = None,
                                                 from_dict = None,
                                                 to_dict = None,
                                                 exclude_private = True):
        """Retrieve a list of
        :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
        objects corresponding to attributes whose values can be serialized from/to CSV,
        JSON, YAML, etc.

        .. note::

          This method operates *solely* on attribute configurations that have been
          declared, ignoring any configuration provided in the ``__<format>_support__``
          attribute.

        :param from_csv: If ``True``, includes attribute names that **can** be
          de-serialized from CSV strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from CSV strings. If :obj:`None <python:None>`,
          will not include attributes based on CSV de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_csv: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_csv: If ``True``, includes attribute names that **can** be
          serialized to CSV strings. If ``False``, includes attribute names
          that **cannot** be serialized to CSV strings. If :obj:`None <python:None>`,
          will not include attributes based on CSV serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_csv: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_json: If ``True``, includes attribute names that **can** be
          de-serialized from JSON strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from JSON strings. If :obj:`None <python:None>`,
          will not include attributes based on JSON de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_json: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_json: If ``True``, includes attribute names that **can** be
          serialized to JSON strings. If ``False``, includes attribute names
          that **cannot** be serialized to JSON strings. If :obj:`None <python:None>`,
          will not include attributes based on JSON serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_json: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_yaml: If ``True``, includes attribute names that **can** be
          de-serialized from YAML strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from YAML strings. If :obj:`None <python:None>`,
          will not include attributes based on YAML de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_yaml: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_yaml: If ``True``, includes attribute names that **can** be
          serialized to YAML strings. If ``False``, includes attribute names
          that **cannot** be serialized to YAML strings. If :obj:`None <python:None>`,
          will not include attributes based on YAML serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_yaml: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_dict: If ``True``, includes attribute names that **can** be
          de-serialized from :class:`dict <python:dict>` objects. If ``False``, includes
          attribute names that **cannot** be de-serialized from :class:`dict <python:dict>`
          objects. If :obj:`None <python:None>`, will not include attributes based on
          :class:`dict <python:dict>` de-serialization support (but may include them
          based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_dict: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_dict: If ``True``, includes attribute names that **can** be
          serialized to :class:`dict <python:dict>` objects. If ``False``, includes
          attribute names that **cannot** be serialized to :class:`dict <python:dict>`
          objects. If :obj:`None <python:None>`, will not include attributes based on
          :class:`dict <python:dict>` serialization support (but may include them
          based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_dict: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param exclude_private: If ``True``, will exclude private attributes whose
          names begin with a single underscore. Defaults to ``True``.
        :type exclude_private: :class:`bool <python:bool>`

        :returns: List of attribute configurations.
        :rtype: :class:`list <python:list>` of
        :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`

        """
        # pylint: disable=too-many-branches

        include_private = not exclude_private

        instance_attributes = cls._get_instance_attributes(
            include_private = include_private,
            exclude_methods = True
        )

        attributes = []
        for key in instance_attributes:
            try:
                value = getattr(cls, key)
            except InvalidRequestError as error:
                value = inspect(cls).all_orm_descriptors[key]
                is_AssociationProxy = isinstance(value, AssociationProxy)
                if not is_AssociationProxy:
                    raise error

            config = AttributeConfiguration(attribute = value)
            config.name = key

            if from_csv is not None and to_csv is None:
                if config not in attributes and config.supports_csv[0] == bool(from_csv):
                    attributes.append(config)
                    continue

            if to_csv is not None and from_csv is None:
                if config not in attributes and config.supports_csv[1] == bool(to_csv):
                    attributes.append(config)
                    continue

            if from_csv is not None and to_csv is not None:
                if config not in attributes and config.supports_csv == (bool(from_csv),
                                                                        bool(to_csv)):
                    attributes.append(config)
                    continue

            if from_json is not None and to_json is None:
                if config not in attributes and config.supports_json[0] == bool(from_json):   # pylint: disable=line-too-long
                    attributes.append(config)
                    continue

            if to_json is not None and from_json is None:
                if config not in attributes and config.supports_json[1] == bool(to_json):
                    attributes.append(config)
                    continue

            if from_json is not None and to_json is not None:
                if config not in attributes and config.supports_json == (bool(from_json),
                                                                         bool(to_json)):
                    attributes.append(config)
                    continue

            if from_yaml is not None and to_yaml is None:
                if config not in attributes and config.supports_yaml[0] == bool(from_yaml):   # pylint: disable=line-too-long
                    attributes.append(config)
                    continue

            if to_yaml is not None and from_yaml is None:
                if config not in attributes and config.supports_yaml[1] == bool(to_yaml):
                    attributes.append(config)
                    continue

            if from_yaml is not None and to_yaml is not None:
                if config not in attributes and config.supports_yaml == (bool(from_yaml),
                                                                         bool(to_yaml)):
                    attributes.append(config)
                    continue

            if from_dict is not None and to_dict is None:
                if config not in attributes and config.supports_dict[0] == bool(from_dict):   # pylint: disable=line-too-long
                    attributes.append(config)
                    continue

            if to_dict is not None and from_dict is None:
                if config not in attributes and config.supports_dict[1] == bool(to_dict):
                    attributes.append(config)
                    continue

            if from_dict is not None and to_dict is not None:
                if config not in attributes and config.supports_dict == (bool(from_dict),
                                                                         bool(to_dict)):
                    attributes.append(config)
                    continue

        return attributes

    @classmethod
    def _get_meta_serializable_attributes(cls,
                                          from_csv = None,
                                          to_csv = None,
                                          from_json = None,
                                          to_json = None,
                                          from_yaml = None,
                                          to_yaml = None,
                                          from_dict = None,
                                          to_dict = None,
                                          config_set = None):
        """Retrieve a list of
        :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
        objects corresponding to attributes whose values can be serialized from/to CSV,
        JSON, YAML, etc.

        .. note::

          This method operates *solely* on attribute configurations that have been
          provided in the meta override ``__serialization__`` attribute.

        :param from_csv: If ``True``, includes attribute names that **can** be
          de-serialized from CSV strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from CSV strings. If :obj:`None <python:None>`,
          will not include attributes based on CSV de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_csv: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_csv: If ``True``, includes attribute names that **can** be
          serialized to CSV strings. If ``False``, includes attribute names
          that **cannot** be serialized to CSV strings. If :obj:`None <python:None>`,
          will not include attributes based on CSV serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_csv: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_json: If ``True``, includes attribute names that **can** be
          de-serialized from JSON strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from JSON strings. If :obj:`None <python:None>`,
          will not include attributes based on JSON de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_json: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_json: If ``True``, includes attribute names that **can** be
          serialized to JSON strings. If ``False``, includes attribute names
          that **cannot** be serialized to JSON strings. If :obj:`None <python:None>`,
          will not include attributes based on JSON serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_json: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_yaml: If ``True``, includes attribute names that **can** be
          de-serialized from YAML strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from YAML strings. If :obj:`None <python:None>`,
          will not include attributes based on YAML de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_yaml: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_yaml: If ``True``, includes attribute names that **can** be
          serialized to YAML strings. If ``False``, includes attribute names
          that **cannot** be serialized to YAML strings. If :obj:`None <python:None>`,
          will not include attributes based on YAML serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_yaml: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_dict: If ``True``, includes attribute names that **can** be
          de-serialized from :class:`dict <python:dict>` objects. If ``False``, includes
          attribute names that **cannot** be de-serialized from :class:`dict <python:dict>`
          objects. If :obj:`None <python:None>`, will not include attributes based on
          :class:`dict <python:dict>` de-serialization support (but may include them
          based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_dict: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_dict: If ``True``, includes attribute names that **can** be
          serialized to :class:`dict <python:dict>` objects. If ``False``, includes
          attribute names that **cannot** be serialized to :class:`dict <python:dict>`
          objects. If :obj:`None <python:None>`, will not include attributes based on
          :class:`dict <python:dict>` serialization support (but may include them
          based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_dict: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param config_set: If not :obj:`None <python:None>`, will return those
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
          objects that are contained within the named set. Defaults to
          :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :returns: List of attribute configurations.
        :rtype: :class:`list <python:list>` of :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`

        :raises ConfigurationError: if ``config_set`` is not empty and there are no
          configuration sets defined on ``cls`` or if there are configuration sets defined
          but no ``config_set`` is specified
        :raises ValueError: if ``config_set`` is not defined within ``__serialization__``

        """
        if config_set and not isinstance(cls.__serialization__, (dict, OrderedDict)):
            raise ConfigurationError('__serialization__ is not a dict and therefore no '
                                     'config_set can be found')
        elif not config_set and isinstance(cls.__serialization__, (dict, OrderedDict)):
            raise ConfigurationError('configuration sets defined but no config_set '
                                     ' specified')

        if config_set and config_set not in cls.__serialization__:
            raise ValueError('config set (%s) not found in __serialization__ '
                             ' configuration' % config_set)

        attributes = []

        if not config_set:
            __serialization__ = [x for x in cls.__serialization__]
        else:
            __serialization__ = [x for x in cls.__serialization__[config_set]]

        if from_csv is not None and to_csv is None:
            attributes.extend([x for x in __serialization__
                               if x.supports_csv[0] == bool(from_csv) and \
                                  x not in attributes
                              ])
        if to_csv is not None and from_csv is None:
            attributes.extend([x for x in __serialization__
                               if x.supports_csv[1] == bool(to_csv) and \
                                  x not in attributes
                              ])

        if from_csv is not None and to_csv is not None:
            attributes.extend([x for x in __serialization__
                               if x.supports_csv[0] == bool(from_csv) and \
                                  x.supports_csv[1] == bool(to_csv) and \
                                  x not in attributes
                              ])

        if from_json is not None and to_json is None:
            attributes.extend([x for x in __serialization__
                               if x.supports_json[0] == bool(from_json) and \
                                  x not in attributes
                              ])
        if to_json is not None and from_json is None:
            attributes.extend([x for x in __serialization__
                               if x.supports_json[1] == bool(to_json) and \
                                  x not in attributes
                              ])

        if from_json is not None and to_json is not None:
            attributes.extend([x for x in __serialization__
                               if x.supports_json[0] == bool(from_json) and \
                                  x.supports_json[1] == bool(to_json) and \
                                  x not in attributes
                              ])

        if from_yaml is not None and to_yaml is None:
            attributes.extend([x for x in __serialization__
                               if x.supports_yaml[0] == bool(from_yaml) and \
                                  x not in attributes
                              ])
        if to_yaml is not None and from_yaml is None:
            attributes.extend([x for x in __serialization__
                               if x.supports_yaml[1] == bool(to_yaml) and \
                                  x not in attributes
                              ])

        if from_yaml is not None and to_yaml is not None:
            attributes.extend([x for x in __serialization__
                               if x.supports_yaml[0] == bool(from_yaml) and \
                                  x.supports_yaml[1] == bool(to_yaml) and \
                                  x not in attributes
                              ])

        if from_dict is not None and to_dict is None:
            attributes.extend([x for x in __serialization__
                               if x.supports_dict[0] == bool(from_dict) and \
                                  x not in attributes
                              ])
        if to_dict is not None and from_dict is None:
            attributes.extend([x for x in __serialization__
                               if x.supports_dict[1] == bool(to_dict) and \
                                  x not in attributes
                              ])

        if from_dict is not None and to_dict is not None:
            attributes.extend([x for x in __serialization__
                               if x.supports_dict[0] == bool(from_dict) and \
                                  x.supports_dict[1] == bool(to_dict) and \
                                  x not in attributes
                              ])

        return attributes

    @classmethod
    def _get_attribute_configurations(cls, config_set = None):
        """Retrieve a list of
        :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
        applied to the class.

        :param config_set: If not :obj:`None <python:None>`, will return those
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
          objects that are contained within the named set. Defaults to
          :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :raises ConfigurationError: if ``config_set`` is not empty and there are no
          configuration sets defined on ``cls`` or if there are configuration sets defined
          but no ``config_set`` is specified
        :raises ValueError: if ``config_set`` is not defined within ``__serialization__``

        """
        if config_set and not isinstance(cls.__serialization__, (dict, OrderedDict)):
            raise ConfigurationError('__serialization__ is not a dict and therefore no '
                                     'config_set can be found')
        elif not config_set and isinstance(cls.__serialization__, (dict, OrderedDict)):
            raise ConfigurationError('configuration sets defined but no config_set '
                                     ' specified')

        if config_set and config_set not in cls.__serialization__:
            raise ValueError('config set (%s) not found in __serialization__ '
                             ' configuration' % config_set)

        if not config_set:
            attributes = [x for x in cls.__serialization__]
        else:
            attributes = [x for x in cls.__serialization__[config_set]]

        attributes.extend([x
                           for x in cls._get_declarative_serializable_attributes(
                               from_csv = True,
                               from_json = True,
                               from_yaml = True,
                               from_dict = True
                           )
                           if x not in attributes])

        attributes.extend([x
                           for x in cls._get_declarative_serializable_attributes(
                               to_csv = True,
                               to_json = True,
                               to_yaml = True,
                               to_dict = True
                           )
                           if x not in attributes])

        return attributes

    @classmethod
    def get_serialization_config(cls,
                                 from_csv = None,
                                 to_csv = None,
                                 from_json = None,
                                 to_json = None,
                                 from_yaml = None,
                                 to_yaml = None,
                                 from_dict = None,
                                 to_dict = None,
                                 exclude_private = True,
                                 config_set = None):
        """Retrieve a list of
        :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
        objects corresponding to attributes whose values can be serialized
        from/to CSV, JSON, YAML, etc.

        :param from_csv: If ``True``, includes attribute names that **can** be
          de-serialized from CSV strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from CSV strings. If :obj:`None <python:None>`,
          will not include attributes based on CSV de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_csv: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_csv: If ``True``, includes attribute names that **can** be
          serialized to CSV strings. If ``False``, includes attribute names
          that **cannot** be serialized to CSV strings. If :obj:`None <python:None>`,
          will not include attributes based on CSV serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_csv: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_json: If ``True``, includes attribute names that **can** be
          de-serialized from JSON strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from JSON strings. If :obj:`None <python:None>`,
          will not include attributes based on JSON de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_json: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_json: If ``True``, includes attribute names that **can** be
          serialized to JSON strings. If ``False``, includes attribute names
          that **cannot** be serialized to JSON strings. If :obj:`None <python:None>`,
          will not include attributes based on JSON serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_json: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_yaml: If ``True``, includes attribute names that **can** be
          de-serialized from YAML strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from YAML strings. If :obj:`None <python:None>`,
          will not include attributes based on YAML de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_yaml: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_yaml: If ``True``, includes attribute names that **can** be
          serialized to YAML strings. If ``False``, includes attribute names
          that **cannot** be serialized to YAML strings. If :obj:`None <python:None>`,
          will not include attributes based on YAML serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_yaml: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_dict: If ``True``, includes attribute names that **can** be
          de-serialized from :class:`dict <python:dict>` objects. If ``False``, includes
          attribute names that **cannot** be de-serialized from :class:`dict <python:dict>`
          objects. If :obj:`None <python:None>`, will not include attributes based on
          :class:`dict <python:dict>` de-serialization support (but may include them
          based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_dict: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_dict: If ``True``, includes attribute names that **can** be
          serialized to :class:`dict <python:dict>` objects. If ``False``, includes
          attribute names that **cannot** be serialized to :class:`dict <python:dict>`
          objects. If :obj:`None <python:None>`, will not include attributes based on
          :class:`dict <python:dict>` serialization support (but may include them
          based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_dict: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param exclude_private: If ``True``, will exclude private attributes whose
          names begin with a single underscore. Defaults to ``True``.
        :type exclude_private: :class:`bool <python:bool>`

        :param config_set: If not :obj:`None <python:None>`, will return those
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
          objects that are contained within the named set. Defaults to
          :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :returns: List of attribute configurations.
        :rtype: :class:`list <python:list>` of
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`

        :raises ConfigurationError: if ``cls`` does not use named configuration sets but
          ``config_set`` is not :obj:`None <python:None>`
        :raises ConfigurationError: if ``cls`` uses named configuration sets but
          ``config_set`` is empty
        :raises ValueError: if ``config_set`` is not defined within ``__serialization__``

        """
        def _build_set():
            if config_set and not isinstance(cls.__serialization__, (dict, OrderedDict)):
                raise ConfigurationError('%s object does not use named (%s) configuration sets, '
                                         'but config_set is not None' % (cls, config_set))
            elif not config_set and isinstance(cls.__serialization__, (dict, OrderedDict)):
                raise ConfigurationError('%s configuration sets defined but config_set is empty' % cls)

            if config_set and config_set not in cls.__serialization__:
                raise ValueError('%s config set (%s) not found in __serialization__ '
                                 ' configuration' % (cls, config_set))

            if config_set:
                __serialization__ = [x for x in cls.__serialization__[config_set]]
            else:
                __serialization__ = [x for x in cls.__serialization__]

            declarative_attributes = cls._get_declarative_serializable_attributes(
                from_csv = from_csv,
                to_csv = to_csv,
                from_json = from_json,
                to_json = to_json,
                from_yaml = from_yaml,
                to_yaml = to_yaml,
                from_dict = from_dict,
                to_dict = to_dict,
                exclude_private = exclude_private
            )
            meta_attributes = cls._get_meta_serializable_attributes(
                from_csv = from_csv,
                to_csv = to_csv,
                from_json = from_json,
                to_json = to_json,
                from_yaml = from_yaml,
                to_yaml = to_yaml,
                from_dict = from_dict,
                to_dict = to_dict,
                config_set = config_set
            )

            attributes = [x for x in meta_attributes]
            attributes.extend([x for x in declarative_attributes
                               if x not in attributes and x not in __serialization__])

            return attributes

        k = 'attributes_fc%stc%sfj%stj%sfy%sty%sfd%std%sep%s' % (
        str(from_csv), str(to_csv), str(from_json), str(to_json), str(from_yaml), str(to_yaml), str(from_dict),
        str(to_dict), str(exclude_private))

        return cls._heapable(k, _build_set, config_set)

    @classmethod
    def get_attribute_serialization_config(cls,
                                           attribute,
                                           config_set = None):
        """Retrieve the
        :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
        for ``attribute``.

        :param attribute: The attribute/column name whose serialization
          configuration should be returned.
        :type attribute: :class:`str <python:str>`

        :param config_set: If not :obj:`None <python:None>`, will return the
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
          object for ``attribute`` that is contained within the named set. Defaults to
          :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :returns: The
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
          for ``attribute``.
        :rtype: :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`

        :raises ConfigurationError: if ``config_set`` is not empty and there are no
          configuration sets defined on ``cls`` or if there are configuration sets defined
          but no ``config_set`` is specified
        :raises ValueError: if ``config_set`` is not defined within ``__serialization__``

        """
        attributes = cls._heapable('config_%s' % attribute, lambda: cls._get_attribute_configurations(config_set = config_set), config_set)

        for config in attributes:
            if config.name == attribute or config.display_name == attribute:
                return config.copy()

        return None

    @classmethod
    def set_attribute_serialization_config(cls,
                                           attribute,
                                           config = None,
                                           supports_csv = None,
                                           csv_sequence = None,
                                           supports_json = None,
                                           supports_yaml = None,
                                           supports_dict = None,
                                           on_deserialize = None,
                                           on_serialize = None,
                                           config_set = None):
        """Set the serialization/de-serialization configuration for ``attribute``.

        .. note::

          Supplying keyword arguments like ``supports_csv`` or ``supports_json``
          will override any configuration set in ``config``.

        :param attribute: The name of the :term:`model attribute` whose
          serialization/de-serialization configuration is to be configured.
        :type attribute: :class:`str <python:str>`

        :param config: The
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
          to apply. If :obj:`None <python:None>`, will set particular values based
          on their corresponding keyword arguments.
        :type config: :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
          / :obj:`None <python:None>`

        :param supports_csv: Determines whether the column can be serialized to or
          de-serialized from CSV format.

          If ``True``, can be serialized to CSV and de-serialized from CSV. If
          ``False``, will not be included when serialized to CSV and will be ignored
          if present in a de-serialized CSV.

          Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound)
          which determines de-serialization and serialization support respectively.

          If :obj:`None <python:None>`, will retain whatever configuration is currently
          applied. Defaults to :obj:`None <python:None>`

        :type supports_csv: :class:`bool <python:bool>` / :class:`tuple <python:tuple>`
          of form (inbound: :class:`bool <python:bool>`, outbound:
          :class:`bool <python:bool>`) / :obj:`None <python:None>`

        :param supports_json: Determines whether the column can be serialized to or
          de-serialized from JSON format.

          If ``True``, can be serialized to JSON and de-serialized from JSON.
          If ``False``, will not be included when serialized to JSON and will be
          ignored if present in a de-serialized JSON.

          Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound)
          which determines de-serialization and serialization support respectively.

          If :obj:`None <python:None>`, will retain whatever configuration is currently
          applied. Defaults to :obj:`None <python:None>`

        :type supports_json: :class:`bool <python:bool>` / :class:`tuple <python:tuple>`
          of form (inbound: :class:`bool <python:bool>`, outbound:
          :class:`bool <python:bool>`) / :obj:`None <python:None>`

        :param supports_yaml: Determines whether the column can be serialized to or
          de-serialized from YAML format.

          If ``True``, can be serialized to YAML and de-serialized from YAML.
          If ``False``, will not be included when serialized to YAML and will be
          ignored if present in a de-serialized YAML.

          Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound)
          which determines de-serialization and serialization support respectively.

          If :obj:`None <python:None>`, will retain whatever configuration is currently
          applied. Defaults to :obj:`None <python:None>`

        :type supports_yaml: :class:`bool <python:bool>` / :class:`tuple <python:tuple>`
          of form (inbound: :class:`bool <python:bool>`, outbound:
          :class:`bool <python:bool>`) / :obj:`None <python:None>`

        :param supports_dict: Determines whether the column can be serialized to or
          de-serialized to a Python :class:`dict <python:dict>`.

          If ``True``, can be serialized to :class:`dict <python:dict>` and de-serialized
          from a :class:`dict <python:dict>`. If ``False``, will not be included
          when serialized to :class:`dict <python:dict>` and will be ignored if
          present in a de-serialized :class:`dict <python:dict>`.

          Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound)
          which determines de-serialization and serialization support respectively.

          If :obj:`None <python:None>`, will retain whatever configuration is currently
          applied. Defaults to :obj:`None <python:None>`

        :type supports_dict: :class:`bool <python:bool>` / :class:`tuple <python:tuple>`
          of form (inbound: :class:`bool <python:bool>`, outbound:
          :class:`bool <python:bool>`) / :obj:`None <python:None>`

        :param on_deserialize: A function that will be called when attempting to
          assign a de-serialized value to the column. This is intended to either coerce
          the value being assigned to a form that is acceptable by the column, or
          raise an exception if it cannot be coerced.

          .. tip::

            If you need to execute different ``on_deserialize`` functions for
            different formats, you can also supply a :class:`dict <python:dict>`:

            .. code-block:: python

              on_deserialize = {
                'csv': csv_on_deserialize_callable,
                'json': json_on_deserialize_callable,
                'yaml': yaml_on_deserialize_callable,
                'dict': dict_on_deserialize_callable
              }

          If ``False``, will clear the current configuration to apply the default.

          If :obj:`None <python:None>`, will retain whatever configuration is currently
          applied. Defaults to :obj:`None <python:None>`

          .. tip::

            To clear the ``on_deserialize`` function, you can either supply a value
            of ``False`` or pass a :class:`dict <python:dict>` with particular formats
            set to :obj:`None <python:None>`:

            .. code-block:: python

              on_deserialize = {
                'csv': None,
                'json': None,
                'yaml': None,
                'dict': None
              }

              # is equivalent to:

              on_deserialize = False

            This will revert the `on_deserialize` function to the attribute's
            default `on_deserialize` function based on its data type.

        :type on_deserialize: callable / :class:`dict <python:dict>` with formats
          as keys and values as callables / :obj:`None <python:None>`

        :param on_serialize: A function that will be called when attempting to
          serialize a value from the column.

          .. tip::

            If you need to execute different ``on_serialize`` functions for
            different formats, you can also supply a :class:`dict <python:dict>`:

            .. code-block:: python

              on_serialize = {
                'csv': csv_on_serialize_callable,
                'json': json_on_serialize_callable,
                'yaml': yaml_on_serialize_callable,
                'dict': dict_on_serialize_callable
              }

          If ``False``, will clear the current configuration to apply the default
          configuration.

          If :obj:`None <python:None>`, will retain whatever configuration is currently
          applied. Defaults to :obj:`None <python:None>`

          .. tip::

            To clear the ``on_serialize`` function, you need to pass ``False`` or a
            :class:`dict <python:dict>` with particular formats set to
            :obj:`None <python:None>`:

            .. code-block:: python

              on_serialize = {
                'csv': None,
                'json': None,
                'yaml': None,
                'dict': None
              }

              # is equivalent to

              on_serialize = False

            This will revert the `on_serialize` function to the attribute's
            default `on_serialize` function based on its data type.

        :type on_serialize: callable / :class:`dict <python:dict>` with formats
          as keys and values as callables / :obj:`None <python:None>` / ``False``

        :param csv_sequence: Indicates the numbered position that the column should be in
          in a valid CSV-version of the object.

          .. note::

            If not specified, the column will go after any columns that *do* have a
            ``csv_sequence`` assigned, sorted alphabetically.

            If two columns have the same ``csv_sequence``, they will be sorted
            alphabetically.

          If ``False``, will set the position to :obj:`None` <python:None>` which will
          position the column *after* any explicitly positioned columns in alphabetical
          order.

          If :obj:`None <python:None>`, will retain whatever configuration is currently
          applied. Defaults to :obj:`None <python:None>`

        :type csv_sequence: :class:`int <python:int>` / :obj:`None <python:None>` /
          ``False``

        :raises ConfigurationError: if ``config_set`` is not empty and there are no
          configuration sets defined on ``cls`` or if there are configuration sets defined
          but no ``config_set`` is specified
        :raises ValueError: if ``config_set`` is not defined within ``__serialization__``
        :raises ValueError: if ``attribute`` does not match ``config.name`` if
          ``config`` is not :obj:`None <python:None>`

        """
        # pylint: disable=too-many-branches
        if config_set and not isinstance(cls.__serialization__, (dict, OrderedDict)):
            raise ConfigurationError('__serialization__ is not a dict and therefore no '
                                     'config_set can be found')
        elif not config_set and isinstance(cls.__serialization__, (dict, OrderedDict)):
            raise ConfigurationError('configuration sets defined but no config_set '
                                     ' specified')

        if config_set and config_set not in cls.__serialization__:
            raise ValueError('config set (%s) not found in __serialization__ '
                             ' configuration' % config_set)

        if config_set:
            __serialization__ = [x for x in cls.__serialization__[config_set]]
        else:
            __serialization__ = [x for x in cls.__serialization__]

        original_config = cls.get_attribute_serialization_config(attribute,
                                                                 config_set = config_set)
        if original_config is None:
            original_config = AttributeConfiguration(name = attribute)

        if config is None:
            new_config = AttributeConfiguration(name = attribute)
        else:
            new_config = config

        if attribute != new_config.name:
            raise ValueError(
                'attribute (%s) does not match config.name (%s)' % (attribute,
                                                                    config.name)
            )

        if supports_csv is not None:
            new_config.supports_csv = supports_csv
        elif new_config.get('supports_csv', None) is None:
            new_config.supports_csv = original_config['supports_csv']

        if supports_json is not None:
            new_config.supports_json = supports_json
        elif new_config.get('supports_json', None) is None:
            new_config.supports_json = original_config['supports_json']

        if supports_yaml is not None:
            new_config.supports_yaml = supports_yaml
        elif new_config.get('supports_yaml', None) is None:
            new_config.supports_yaml = original_config['supports_yaml']

        if supports_dict is not None:
            new_config.supports_dict = supports_dict
        elif new_config.get('supports_dict', None) is None:
            new_config.supports_dict = original_config['supports_dict']

        if csv_sequence is not None:
            if csv_sequence is False:
                csv_sequence = None

            new_config.csv_sequence = csv_sequence
        elif new_config.get('csv_sequence', None) is None:
            new_config['csv_sequence'] = original_config['csv_sequence']

        if on_deserialize is not None:
            if on_deserialize is False:
                on_deserialize = BLANK_ON_SERIALIZE

            new_config.on_deserialize = on_deserialize
        elif checkers.are_dicts_equivalent(new_config.on_deserialize,
                                           BLANK_ON_SERIALIZE):
            new_config.on_deserialize = original_config.on_deserialize

        if on_serialize is not None:
            if on_serialize is False:
                on_serialize = BLANK_ON_SERIALIZE

            new_config.on_serialize = on_serialize
        elif checkers.are_dicts_equivalent(new_config.on_serialize,
                                           BLANK_ON_SERIALIZE):
            new_config.on_serialize = original_config.on_serialize

        serialization = [x for x in __serialization__
                         if x.name != attribute]
        serialization.append(new_config)

        if config_set:
            cls.__serialization__[config_set] = [x for x in serialization]
        else:
            cls.__serialization__ = [x for x in serialization]

        cls.clear_serialization_cache()

    @classmethod
    def configure_serialization(cls,
                                configs = None,
                                attributes = None,
                                supports_csv = False,
                                supports_json = False,
                                supports_yaml = False,
                                supports_dict = False,
                                on_serialize = None,
                                on_deserialize = None,
                                config_set = None):
        """Apply configuration settings to the :term:`model class` (overwrites
        entire configuration).

        .. tip::

          For this method, the configuration settings applied in ``configs`` receive
          priority over a configuration specified in keyword arguments.

          This means that if an attribute is configured in both ``configs`` and
          ``attributes``, the configuration in ``configs`` will apply.

        :param configs: Collection of
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
          objects to apply to the class. Defaults to :obj:`None <python:None>`.
        :type configs: iterable of
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>` /
          :obj:`None <python:None>`

        :param attributes: Collection of :term:`model attribute` names to which
          a configuration will be applied. Defaults to :obj:`None <python:None>`.
        :type attributes: iterable of :class:`str <python:str>` /
          :obj:`None <python:None>`

        :param supports_csv: Determines whether the column can be serialized to or
          de-serialized from CSV format.

          If ``True``, can be serialized to CSV and de-serialized from CSV. If
          ``False``, will not be included when serialized to CSV and will be ignored
          if present in a de-serialized CSV.

          Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound)
          which determines de-serialization and serialization support respectively.

          Defaults to ``False``.

        :type supports_csv: :class:`bool <python:bool>` / :class:`tuple <python:tuple>`
          of form (inbound: :class:`bool <python:bool>`, outbound:
          :class:`bool <python:bool>`)

        :param supports_json: Determines whether the column can be serialized to or
          de-serialized from JSON format.

          If ``True``, can be serialized to JSON and de-serialized from JSON.
          If ``False``, will not be included when serialized to JSON and will be
          ignored if present in a de-serialized JSON.

          Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound)
          which determines de-serialization and serialization support respectively.

          Defaults to ``False``.

        :type supports_json: :class:`bool <python:bool>` / :class:`tuple <python:tuple>`
          of form (inbound: :class:`bool <python:bool>`, outbound:
          :class:`bool <python:bool>`)

        :param supports_yaml: Determines whether the column can be serialized to or
          de-serialized from YAML format.

          If ``True``, can be serialized to YAML and de-serialized from YAML.
          If ``False``, will not be included when serialized to YAML and will be
          ignored if present in a de-serialized YAML.

          Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound)
          which determines de-serialization and serialization support respectively.

          Defaults to ``False``.

        :type supports_yaml: :class:`bool <python:bool>` / :class:`tuple <python:tuple>`
          of form (inbound: :class:`bool <python:bool>`, outbound:
          :class:`bool <python:bool>`)

        :param supports_dict: Determines whether the column can be serialized to or
          de-serialized to a Python :class:`dict <python:dict>`.

          If ``True``, can be serialized to :class:`dict <python:dict>` and de-serialized
          from a :class:`dict <python:dict>`. If ``False``, will not be included
          when serialized to :class:`dict <python:dict>` and will be ignored if
          present in a de-serialized :class:`dict <python:dict>`.

          Can also accept a 2-member :class:`tuple <python:tuple>` (inbound / outbound)
          which determines de-serialization and serialization support respectively.

          Defaults to ``False``.

        :type supports_dict: :class:`bool <python:bool>` / :class:`tuple <python:tuple>`
          of form (inbound: :class:`bool <python:bool>`, outbound:
          :class:`bool <python:bool>`)

        :param on_deserialize: A function that will be called when attempting to
          assign a de-serialized value to the column. This is intended to either coerce
          the value being assigned to a form that is acceptable by the column, or
          raise an exception if it cannot be coerced.

          .. tip::

            If you need to execute different ``on_deserialize`` functions for
            different formats, you can also supply a :class:`dict <python:dict>`:

            .. code-block:: python

              on_deserialize = {
                'csv': csv_on_deserialize_callable,
                'json': json_on_deserialize_callable,
                'yaml': yaml_on_deserialize_callable,
                'dict': dict_on_deserialize_callable
              }

          If ``False``, will clear the current configuration to apply the default.

          If :obj:`None <python:None>`, will retain whatever configuration is currently
          applied. Defaults to :obj:`None <python:None>`

          .. tip::

            To clear the ``on_deserialize`` function, you can either supply a value
            of ``False`` or pass a :class:`dict <python:dict>` with particular formats
            set to :obj:`None <python:None>`:

            .. code-block:: python

              on_deserialize = {
                'csv': None,
                'json': None,
                'yaml': None,
                'dict': None
              }

              # is equivalent to:

              on_deserialize = False

            This will revert the `on_deserialize` function to the attribute's
            default `on_deserialize` function based on its data type.

        :type on_deserialize: callable / :class:`dict <python:dict>` with formats
          as keys and values as callables / :obj:`None <python:None>`

        :param on_serialize: A function that will be called when attempting to
          serialize a value from the column.

          .. tip::

            If you need to execute different ``on_serialize`` functions for
            different formats, you can also supply a :class:`dict <python:dict>`:

            .. code-block:: python

              on_serialize = {
                'csv': csv_on_serialize_callable,
                'json': json_on_serialize_callable,
                'yaml': yaml_on_serialize_callable,
                'dict': dict_on_serialize_callable
              }

          If ``False``, will clear the current configuration to apply the default
          configuration.

          If :obj:`None <python:None>`, will retain whatever configuration is currently
          applied. Defaults to :obj:`None <python:None>`

          .. tip::

            To clear the ``on_serialize`` function, you need to pass ``False`` or a
            :class:`dict <python:dict>` with particular formats set to
            :obj:`None <python:None>`:

            .. code-block:: python

              on_serialize = {
                'csv': None,
                'json': None,
                'yaml': None,
                'dict': None
              }

              # is equivalent to

              on_serialize = False

            This will revert the `on_serialize` function to the attribute's
            default `on_serialize` function based on its data type.

        :type on_serialize: callable / :class:`dict <python:dict>` with formats
          as keys and values as callables / :obj:`None <python:None>` / ``False``

        :param config_set: If not :obj:`None <python:None>`, will apply ``configs`` to the
          configuration set named. If the class does not use pre-existing configuration sets,
          will switch the class' meta configuration to use configuration sets, with any
          pre-existing configuration set assigned to a set named ``_original``. Defaults
          to :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        """
        cls.clear_serialization_cache()

        config = validate_serialization_config(configs)
        config_attributes = [x.name for x in config]

        if not attributes:
            attributes = []

        attributes = [AttributeConfiguration(name = x,
                                             supports_csv = supports_csv,
                                             supports_json = supports_json,
                                             supports_yaml = supports_yaml,
                                             supports_dict = supports_dict,
                                             on_serialize = on_serialize,
                                             on_deserialize = on_deserialize)
                      for x in attributes
                      if x not in config_attributes]

        config.extend(attributes)

        if not config_set and not isinstance(cls.__serialization__, (dict, OrderedDict)):
            cls.__serialization__ = [x for x in config]
        elif config_set and isinstance(cls.__serialization__, (dict, OrderedDict)):
            cls.__serialization__[config_set] = [x for x in config]
        elif config_set:
            config_dict = dict_()
            config_dict['_original'] = [x for x in cls.__serialization__]
            config_dict[config_set] = [x for x in config]
            cls.__serialization__ = config_dict

    @classmethod
    def does_support_serialization(cls,
                                   attribute,
                                   from_csv = None,
                                   to_csv = None,
                                   from_json = None,
                                   to_json = None,
                                   from_yaml = None,
                                   to_yaml = None,
                                   from_dict = None,
                                   to_dict = None,
                                   config_set = None):
        """Indicate whether ``attribute`` supports serialization/deserializtion.

        :param attribute: The name of the attribute whose serialization support
          should be confirmed.
        :type attribute: :class:`str <python:str>`

        :param from_csv: If ``True``, includes attribute names that **can** be
          de-serialized from CSV strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from CSV strings. If :obj:`None <python:None>`,
          will not include attributes based on CSV de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_csv: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_csv: If ``True``, includes attribute names that **can** be
          serialized to CSV strings. If ``False``, includes attribute names
          that **cannot** be serialized to CSV strings. If :obj:`None <python:None>`,
          will not include attributes based on CSV serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_csv: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_json: If ``True``, includes attribute names that **can** be
          de-serialized from JSON strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from JSON strings. If :obj:`None <python:None>`,
          will not include attributes based on JSON de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_json: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_json: If ``True``, includes attribute names that **can** be
          serialized to JSON strings. If ``False``, includes attribute names
          that **cannot** be serialized to JSON strings. If :obj:`None <python:None>`,
          will not include attributes based on JSON serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_json: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_yaml: If ``True``, includes attribute names that **can** be
          de-serialized from YAML strings. If ``False``, includes attribute names
          that **cannot** be de-serialized from YAML strings. If :obj:`None <python:None>`,
          will not include attributes based on YAML de-serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_yaml: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_yaml: If ``True``, includes attribute names that **can** be
          serialized to YAML strings. If ``False``, includes attribute names
          that **cannot** be serialized to YAML strings. If :obj:`None <python:None>`,
          will not include attributes based on YAML serialization support (but
          may include them based on other parameters). Defaults to :obj:`None <python:None>`.
        :type to_yaml: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param from_dict: If ``True``, includes attribute names that **can** be
          de-serialized from :class:`dict <python:dict>` objects. If ``False``, includes
          attribute names that **cannot** be de-serialized from :class:`dict <python:dict>`
          objects. If :obj:`None <python:None>`, will not include attributes based on
          :class:`dict <python:dict>` de-serialization support (but may include them
          based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_dict: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param to_dict: If ``True``, includes attribute names that **can** be
          serialized to :class:`dict <python:dict>` objects. If ``False``, includes
          attribute names that **cannot** be serialized to :class:`dict <python:dict>`
          objects. If :obj:`None <python:None>`, will not include attributes based on
          :class:`dict <python:dict>` serialization support (but may include them
          based on other parameters). Defaults to :obj:`None <python:None>`.
        :type from_dict: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param config_set: If not :obj:`None <python:None>`, will determine serialization
          support within the indicated configuration set. Defaults to
          :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :returns: ``True`` if the attribute's serialization support matches,
          ``False`` if not, and :obj:`None <python:None>` if no serialization support was
          specified.
        :rtype: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :raises UnsupportedSerializationError: if ``attribute`` is not present
          on the object
        :raises ValueError: if ``config_set`` is not :obj:`None <python:None>` and
          its value does not match a named configuration set
        :raises ConfigurationError: if ``config_set`` is :obj:`None <python:None>` and the
          object uses named configuration sets
        :raises ConfigurationError: if the object does not use configuration sets but
          ``config_set`` is not None

        """
        # pylint: disable=too-many-boolean-expressions,too-many-branches
        if config_set and not isinstance(cls.__serialization__, (dict, OrderedDict)):
            raise ConfigurationError('object does not use configuration sets, but '
                                     'config_set was not None')
        elif not config_set and isinstance(cls.__serialization__, (dict, OrderedDict)):
            raise ConfigurationError('configuration sets defined but no config_set '
                                     ' specified')

        if config_set and config_set not in cls.__serialization__:
            raise ValueError('config set (%s) not found in __serialization__ '
                             ' configuration' % config_set)

        if from_csv is None and to_csv is None and \
           from_json is None and to_json is None and \
           from_yaml is None and to_yaml is None and \
           from_dict is None and to_dict is None:
            return None

        config = cls.get_attribute_serialization_config(attribute,
                                                        config_set = config_set)
        if config is None:
            if inspect_.isclass(cls):
                class_name = cls.__name__
            else:
                class_name = type(cls).__name__

            raise UnsupportedSerializationError(
                "'%s' has no serializable attribute '%s'" % (class_name,
                                                             attribute)
            )

        csv_check = False
        json_check = False
        yaml_check = False
        dict_check = False

        if from_csv is not None and to_csv is not None:
            csv_check = config.supports_csv == (bool(from_csv), bool(to_csv))
        elif from_csv is not None:
            csv_check = config.supports_csv[0] == bool(from_csv)
        elif to_csv is not None:
            csv_check = config.supports_csv[1] == bool(to_csv)
        else:
            csv_check = True

        if from_json is not None and to_json is not None:
            json_check = config.supports_json == (bool(from_json), bool(to_json))
        elif from_json is not None:
            json_check = config.supports_json[0] == bool(from_json)
        elif to_json is not None:
            json_check = config.supports_json[1] == bool(to_json)
        else:
            json_check = True

        if from_yaml is not None and to_yaml is not None:
            yaml_check = config.supports_yaml == (bool(from_yaml), bool(to_yaml))
        elif from_yaml is not None:
            yaml_check = config.supports_yaml[0] == bool(from_yaml)
        elif to_yaml is not None:
            yaml_check = config.supports_yaml[1] == bool(to_yaml)
        else:
            yaml_check = True

        if from_dict is not None and to_dict is not None:
            dict_check = config.supports_dict == (bool(from_dict), bool(to_dict))
        elif from_dict is not None:
            dict_check = config.supports_dict[0] == bool(from_dict)
        elif to_dict is not None:
            dict_check = config.supports_dict[1] == bool(to_dict)
        else:
            dict_check = True

        return csv_check and json_check and yaml_check and dict_check

    @classmethod
    def get_csv_serialization_config(cls,
                                     deserialize = True,
                                     serialize = True,
                                     config_set = None):
        """Retrieve the CSV serialization configurations that apply for this object.

        :param deserialize: If ``True``, returns configurations for attributes that
          **can** be de-serialized from CSV strings. If ``False``,
          returns configurations for attributes that **cannot** be de-serialized from
          CSV strings. If :obj:`None <python:None>`, ignores de-serialization
          configuration when determining which attribute configurations to return.
          Defaults to :obj:`None <python:None>`.
        :type deserialize: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param serialize: If ``True``, returns configurations for attributes that
          **can** be serialized to CSV strings. If ``False``,
          returns configurations for attributes that **cannot** be serialized to
          CSV strings. If :obj:`None <python:None>`, ignores serialization
          configuration when determining which attribute configurations to return.
          Defaults to :obj:`None <python:None>`.
        :type serialize: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param config_set: If not :obj:`None <python:None>`, the named configuration set
          whose CSV serialization configuration should be returned. Defaults to
          :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :returns: Set of attribute serialization configurations that match the
          arguments supplied.
        :rtype: :class:`list <python:list>` of
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`

        :raises ConfigurationError: if ``cls`` does not use named configuration sets but
          ``config_set`` is not :obj:`None <python:None>`
        :raises ConfigurationError: if ``cls`` uses named configuration sets but
          ``config_set`` is empty
        :raises ValueError: if ``config_set`` is not defined within ``__serialization__``

        """
        def _build_csv():
            attributes = [x.copy()
                          for x in cls.get_serialization_config(from_csv=deserialize,
                                                                to_csv=serialize,
                                                                config_set=config_set)]
            for config in attributes:
                if config.csv_sequence is None:
                    config.csv_sequence = len(attributes) + 1

            return sorted(attributes, key=lambda x: (x.csv_sequence, x.name))

        return cls._heapable('csv_s%sd%s' % (str(serialize), str(deserialize)), _build_csv, config_set)

    @classmethod
    def get_json_serialization_config(cls,
                                      deserialize = True,
                                      serialize = True,
                                      config_set = None):
        """Retrieve the JSON serialization configurations that apply for this object.

        :param deserialize: If ``True``, returns configurations for attributes that
          **can** be de-serialized from JSON strings. If ``False``,
          returns configurations for attributes that **cannot** be de-serialized from
          JSON strings. If :obj:`None <python:None>`, ignores de-serialization
          configuration when determining which attribute configurations to return.
          Defaults to :obj:`None <python:None>`.
        :type deserialize: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param serialize: If ``True``, returns configurations for attributes that
          **can** be serialized to JSON strings. If ``False``,
          returns configurations for attributes that **cannot** be serialized to
          JSON strings. If :obj:`None <python:None>`, ignores serialization
          configuration when determining which attribute configurations to return.
          Defaults to :obj:`None <python:None>`.
        :type serialize: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param config_set: If not :obj:`None <python:None>`, the named configuration set
          whose serialization configuration should be returned. Defaults to
          :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :returns: Set of attribute serialization configurations that match the
          arguments supplied.
        :rtype: :class:`list <python:list>` of
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
        """
        return cls._heapable('json_s%sd%s' % (str(serialize), str(deserialize)),
                             lambda: [x for x in cls.get_serialization_config(from_json = deserialize,
                                                        to_json = serialize,
                                                        config_set = config_set)], config_set)

    @classmethod
    def get_yaml_serialization_config(cls,
                                      deserialize = True,
                                      serialize = True,
                                      config_set = None):
        """Retrieve the YAML serialization configurations that apply for this object.

        :param deserialize: If ``True``, returns configurations for attributes that
          **can** be de-serialized from YAML strings. If ``False``,
          returns configurations for attributes that **cannot** be de-serialized from
          YAML strings. If :obj:`None <python:None>`, ignores de-serialization
          configuration when determining which attribute configurations to return.
          Defaults to :obj:`None <python:None>`.
        :type deserialize: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param serialize: If ``True``, returns configurations for attributes that
          **can** be serialized to YAML strings. If ``False``,
          returns configurations for attributes that **cannot** be serialized to
          YAML strings. If :obj:`None <python:None>`, ignores serialization
          configuration when determining which attribute configurations to return.
          Defaults to :obj:`None <python:None>`.
        :type serialize: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param config_set: If not :obj:`None <python:None>`, the named configuration set
          whose serialization configuration should be returned. Defaults to
          :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :returns: Set of attribute serialization configurations that match the
          arguments supplied.
        :rtype: :class:`list <python:list>` of
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`

        """
        return cls._heapable('yaml_s%sd%s' % (str(serialize), str(deserialize)),
                             lambda: [x for x in cls.get_serialization_config(from_yaml = deserialize,
                                                        to_yaml = serialize,
                                                        config_set = config_set)], config_set)

    @classmethod
    def get_dict_serialization_config(cls,
                                      deserialize = True,
                                      serialize = True,
                                      config_set = None):
        """Retrieve the :class:`dict <python:dict>` serialization configurations that
        apply for this object.

        :param deserialize: If ``True``, returns configurations for attributes that
          **can** be de-serialized from :class:`dict <python:dict>` objects. If ``False``,
          returns configurations for attributes that **cannot** be de-serialized from
          :class:`dict <python:dict>` objects. If :obj:`None <python:None>`, ignores de-serialization
          configuration when determining which attribute configurations to return.
          Defaults to :obj:`None <python:None>`.
        :type deserialize: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param serialize: If ``True``, returns configurations for attributes that
          **can** be serialized to :class:`dict <python:dict>` objects. If ``False``,
          returns configurations for attributes that **cannot** be serialized to
          :class:`dict <python:dict>` objects. If :obj:`None <python:None>`, ignores serialization
          configuration when determining which attribute configurations to return.
          Defaults to :obj:`None <python:None>`.
        :type serialize: :class:`bool <python:bool>` / :obj:`None <python:None>`

        :param config_set: If not :obj:`None <python:None>`, the named configuration set
          whose serialization configuration should be returned. Defaults to
          :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :returns: Set of attribute serialization configurations that match the
          arguments supplied.
        :rtype: :class:`list <python:list>` of
          :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
        """
        return cls._heapable('dict_s%sd%s' % (str(serialize), str(deserialize)), lambda: [x for x in cls.get_serialization_config(from_dict = deserialize,
                                                        to_dict = serialize,
                                                        config_set = config_set)], config_set)

    @classmethod
    def _heapable(cls, name, value, config_set=None):
        _heap_name = '__serialization_cache__%s{name}__%s__' % (name, "default" if config_set is None else config_set)

        if not hasattr(cls, _heap_name):
            _v = value()
            if _v is not None:
                setattr(cls, _heap_name, _v)

        if hasattr(cls, _heap_name):
            return getattr(cls, _heap_name)

    @classmethod
    def clear_serialization_cache(cls):
        for n in list(cls.__dict__.keys()):
            if n.startswith('__serialization_cache__'):
                delattr(cls, n)