Source code for sqlathanor.declarative._json_support

# -*- 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.

from validator_collection import checkers

from sqlathanor._compat import json
from sqlathanor.utilities import parse_json

class JSONSupportMixin(object):
    """Mixin that provides JSON serialization/de-serialization support."""

    def to_json(self,
                max_nesting = 0,
                current_nesting = 0,
                serialize_function = None,
                config_set = None,
                **kwargs):
        """Return a JSON representation of the object.

        :param max_nesting: The maximum number of levels that the resulting
          JSON object can be nested. If set to ``0``, will
          not nest other serializable objects. Defaults to ``0``.
        :type max_nesting: :class:`int <python:int>`

        :param current_nesting: The current nesting level at which the
          :class:`dict <python:dict>` representation will reside. Defaults to ``0``.
        :type current_nesting: :class:`int <python:int>`

        :param serialize_function: Optionally override the default JSON serializer.
          Defaults to :obj:`None <python:None>`, which applies the default
          :doc:`simplejson <simplejson:index>` JSON serializer.

          .. note::

            Use the ``serialize_function`` parameter to override the default
            JSON serializer.

            A valid ``serialize_function`` is expected to accept a single
            :class:`dict <python:dict>` and return a :class:`str <python:str>`,
            similar to :func:`simplejson.dumps() <simplejson:simplejson.dumps>`.

            If you wish to pass additional arguments to your ``serialize_function``
            pass them as keyword arguments (in ``kwargs``).

        :type serialize_function: callable / :obj:`None <python:None>`

        :param config_set: If not :obj:`None <python:None>`, the named configuration set
          to use. Defaults to :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :param kwargs: Optional keyword parameters that are passed to the
          JSON serializer function. By default, these are options which are passed
          to :func:`simplejson.dumps() <simplejson:simplejson.dumps>`.
        :type kwargs: keyword arguments

        :returns: A :class:`str <python:str>` with the JSON representation of the
          object.
        :rtype: :class:`str <python:str>`

        :raises SerializableAttributeError: if attributes is empty
        :raises MaximumNestingExceededError: if ``current_nesting`` is greater
          than ``max_nesting``
        :raises MaximumNestingExceededWarning: if an attribute requires nesting
          beyond ``max_nesting``

        """
        if serialize_function is None:
            serialize_function = json.dumps
        else:
            if checkers.is_callable(serialize_function) is False:
                raise ValueError(
                    'serialize_function (%s) is not callable' % serialize_function
                )

        as_dict = self._to_dict('json',
                                max_nesting = max_nesting,
                                current_nesting = current_nesting,
                                is_dumping = False,
                                config_set = config_set)

        as_json = serialize_function(as_dict, **kwargs)

        return as_json

    def dump_to_json(self,
                     max_nesting = 0,
                     current_nesting = 0,
                     serialize_function = None,
                     config_set = None,
                     **kwargs):
        """Return a :term:`JSON <JavaScript Object Notation (JSON)>`
        representation of the object, *with all attributes* regardless of
        configuration.

        .. caution::

          Nested objects (such as :term:`relationships <relationship>` or
          :term:`association proxies <association proxy>`) will **not**
          be serialized.

        :param max_nesting: The maximum number of levels that the resulting
          JSON object can be nested. If set to ``0``, will
          not nest other serializable objects. Defaults to ``0``.
        :type max_nesting: :class:`int <python:int>`

        :param current_nesting: The current nesting level at which the
          :class:`dict <python:dict>` representation will reside. Defaults to ``0``.
        :type current_nesting: :class:`int <python:int>`

        :param serialize_function: Optionally override the default JSON serializer.
          Defaults to :obj:`None <python:None>`, which applies the default
          :doc:`simplejson <simplejson:index>` JSON serializer.

          .. note::

            Use the ``serialize_function`` parameter to override the default
            JSON serializer.

            A valid ``serialize_function`` is expected to accept a single
            :class:`dict <python:dict>` and return a :class:`str <python:str>`,
            similar to :func:`simplejson.dumps() <simplejson:simplejson.dumps>`.

            If you wish to pass additional arguments to your ``serialize_function``
            pass them as keyword arguments (in ``kwargs``).

        :type serialize_function: callable / :obj:`None <python:None>`

        :param config_set: If not :obj:`None <python:None>`, the named configuration set
          to use. Defaults to :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :param kwargs: Optional keyword parameters that are passed to the
          JSON serializer function. By default, these are options which are passed
          to :func:`simplejson.dumps() <simplejson:simplejson.dumps>`.
        :type kwargs: keyword arguments

        :returns: A :class:`str <python:str>` with the JSON representation of the
          object.
        :rtype: :class:`str <python:str>`

        :raises SerializableAttributeError: if attributes is empty
        :raises MaximumNestingExceededError: if ``current_nesting`` is greater
          than ``max_nesting``
        :raises MaximumNestingExceededWarning: if an attribute requires nesting
          beyond ``max_nesting``

        """
        if serialize_function is None:
            serialize_function = json.dumps
        else:
            if checkers.is_callable(serialize_function) is False:
                raise ValueError(
                    'serialize_function (%s) is not callable' % serialize_function
                )

        as_dict = self._to_dict('json',
                                max_nesting = max_nesting,
                                current_nesting = current_nesting,
                                is_dumping = True,
                                config_set = config_set)

        as_json = serialize_function(as_dict, **kwargs)

        return as_json

    def update_from_json(self,
                         input_data,
                         deserialize_function = None,
                         error_on_extra_keys = True,
                         drop_extra_keys = False,
                         config_set = None,
                         **kwargs):
        """Update the model instance from data in a JSON string.

        :param input_data: The JSON data to de-serialize.

          .. note::

            If ``input_data`` points to a file, and the file contains a list of
            JSON objects, the first JSON object will be considered.

        :type input_data: :class:`str <python:str>` or Path-like object

        :param deserialize_function: Optionally override the default JSON deserializer.
          Defaults to :obj:`None <python:None>`, which calls the default
          :func:`simplejson.loads() <simplejson:simplejson.loads>` function from
          the :doc:`simplejson <simplejson:index>` library.

          .. note::

            Use the ``deserialize_function`` parameter to override the default
            JSON deserializer.

            A valid ``deserialize_function`` is expected to
            accept a single :class:`str <python:str>` and return a
            :class:`dict <python:dict>`, similar to
            :func:`simplejson.loads() <simplejson:simplejson.loads>`.

            If you wish to pass additional arguments to your ``deserialize_function``
            pass them as keyword arguments (in ``kwargs``).

        :type deserialize_function: callable / :obj:`None <python:None>`

        :param error_on_extra_keys: If ``True``, will raise an error if an
          unrecognized key is found in ``input_data``. If ``False``, will
          either drop or include the extra key in the result, as configured in
          the ``drop_extra_keys`` parameter. Defaults to ``True``.

          .. warning::

            Be careful setting ``error_on_extra_keys`` to ``False``.

            This method's last step attempts to set an attribute on the model
            instance for every top-level key in the parsed/processed input data.

            If there is an extra key that cannot be set as an attribute on your
            model instance, it *will* raise
            :class:`AttributeError <python:AttributeError>`.

        :type error_on_extra_keys: :class:`bool <python:bool>`

        :param drop_extra_keys: If ``True``, will ignore unrecognized keys in the
          input data. If ``False``, will include unrecognized keys or raise an
          error based on the configuration of the ``error_on_extra_keys`` parameter.
          Defaults to ``False``.
        :type drop_extra_keys: :class:`bool <python:bool>`

        :param config_set: If not :obj:`None <python:None>`, the named configuration set
          to use. Defaults to :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :param kwargs: Optional keyword parameters that are passed to the
          JSON deserializer function.By default, these are options which are passed
          to :func:`simplejson.loads() <simplejson:simplejson.loads>`.
        :type kwargs: keyword arguments

        :raises ExtraKeyError: if ``error_on_extra_keys`` is ``True`` and
          ``input_data`` contains top-level keys that are not recognized as
          attributes for the instance model.
        :raises DeserializationError: if ``input_data`` is
          not a :class:`str <python:str>` JSON de-serializable object to a
          :class:`dict <python:dict>` or if ``input_data`` is empty.

        """
        from_json = parse_json(input_data,
                               deserialize_function = deserialize_function,
                               **kwargs)

        if isinstance(from_json, list):
            from_json = from_json[0]

        data = self._parse_dict(from_json,
                                'json',
                                error_on_extra_keys = error_on_extra_keys,
                                drop_extra_keys = drop_extra_keys,
                                config_set = config_set)

        for key in data:
            setattr(self, key, data[key])

    @classmethod
    def new_from_json(cls,
                      input_data,
                      deserialize_function = None,
                      error_on_extra_keys = True,
                      drop_extra_keys = False,
                      config_set = None,
                      **kwargs):
        """Create a new model instance from data in JSON.

        :param input_data: The JSON data to de-serialize.

          .. note::

            If ``input_data`` points to a file, and the file contains a list of
            JSON objects, the first JSON object will be considered.

        :type input_data: :class:`str <python:str>` or Path-like object

        :param deserialize_function: Optionally override the default JSON deserializer.
          Defaults to :obj:`None <python:None>`, which calls the default
          :func:`simplejson.loads() <simplejson:simplejson.loads>`
          function from the doc:`simplejson <simplejson:index>` library.

          .. note::

            Use the ``deserialize_function`` parameter to override the default
            JSON deserializer.

            A valid ``deserialize_function`` is expected to accept a single
            :class:`str <python:str>` and return a :class:`dict <python:dict>`,
            similar to :func:`simplejson.loads() <simplejson:simplejson.loads>`.

            If you wish to pass additional arguments to your ``deserialize_function``
            pass them as keyword arguments (in ``kwargs``).

        :type deserialize_function: callable / :obj:`None <python:None>`

        :param error_on_extra_keys: If ``True``, will raise an error if an
          unrecognized key is found in ``input_data``. If ``False``, will
          either drop or include the extra key in the result, as configured in
          the ``drop_extra_keys`` parameter. Defaults to ``True``.

          .. warning::

            Be careful setting ``error_on_extra_keys`` to ``False``.

            This method's last step passes the keys/values of the processed input
            data to your model's ``__init__()`` method.

            If your instance's ``__init__()`` method does not support your extra keys,
            it will likely raise a :class:`TypeError <python:TypeError>`.

        :type error_on_extra_keys: :class:`bool <python:bool>`

        :param drop_extra_keys: If ``True``, will ignore unrecognized top-level
          keys in ``input_data``. If ``False``, will include unrecognized keys
          or raise an error based on the configuration of
          the ``error_on_extra_keys`` parameter. Defaults to ``False``.
        :type drop_extra_keys: :class:`bool <python:bool>`

        :param config_set: If not :obj:`None <python:None>`, the named configuration set
          to use. Defaults to :obj:`None <python:None>`.
        :type config_set: :class:`str <python:str>` / :obj:`None <python:None>`

        :param kwargs: Optional keyword parameters that are passed to the
          JSON deserializer function. By default, these are options which are passed
          to :func:`simplejson.loads() <simplejson:simplejson.loads>`.
        :type kwargs: keyword arguments

        :raises ExtraKeyError: if ``error_on_extra_keys`` is ``True`` and
          ``input_data`` contains top-level keys that are not recognized as
          attributes for the instance model.
        :raises DeserializationError: if ``input_data`` is
          not a :class:`dict <python:dict>` or JSON object serializable to a
          :class:`dict <python:dict>` or if ``input_data`` is empty.

        """
        from_json = parse_json(input_data,
                               deserialize_function = deserialize_function,
                               **kwargs)

        if isinstance(from_json, list):
            from_json = from_json[0]

        data = cls._parse_dict(from_json,
                               'json',
                               error_on_extra_keys = error_on_extra_keys,
                               drop_extra_keys = drop_extra_keys,
                               config_set = config_set)

        return cls(**data)