Esempio n. 1
0
def test_parse_json(input_value, deserialize_function, expected_result, error):
    if not error:
        result = parse_json(input_value,
                            deserialize_function=deserialize_function)

        assert isinstance(result, dict)
        assert checkers.are_dicts_equivalent(result, expected_result)
    else:
        with pytest.raises(error):
            result = parse_json(input_value)
Esempio n. 2
0
def test_parse_json(input_files, input_value, deserialize_function,
                    expected_result, error):
    input_value = check_input_file(input_files, input_value)

    if not error:
        result = parse_json(input_value,
                            deserialize_function=deserialize_function)

        assert isinstance(result, (dict, list))
        assert checkers.are_equivalent(result, expected_result)
    else:
        with pytest.raises(error):
            result = parse_json(input_value)
Esempio n. 3
0
    def from_json(cls,
                  serialized,
                  tablename,
                  metadata,
                  primary_key,
                  column_kwargs=None,
                  skip_nested=True,
                  default_to_str=False,
                  type_mapping=None,
                  deserialize_function=None,
                  deserialize_kwargs=None,
                  **kwargs):
        """Generate a :class:`Table` object from a
        :term:`JSON <JavaScript Object Notation (JSON)>` string.

        .. versionadded: 0.3.0

        :param serialized: The :term:`JSON <JavaScript Object Notation (JSON)>`
          data to use. Keys will be treated as column names, while value data
          types will determine :class:`Column` data types.

          .. note::

            If providing a path to a file, and if the file contains more than one JSON
            object, will only use the first JSON object listed.

        :type serialized: :class:`str <python:str>` / Path-like object

        :param tablename: The name of the SQL table to which the model corresponds.
        :type tablename: :class:`str <python:str>`

        :param metadata: a :class:`MetaData <sqlalchemy:sqlalchemy.schema.MetaData>`
          object which will contain this table. The metadata is used as a point of
          association of this table with other tables which are referenced via foreign
          key. It also may be used to associate this table with a particular
          :class:`Connectable <sqlalchemy:sqlalchemy.engine.Connectable>`.
        :type metadata: :class:`MetaData <sqlalchemy:sqlalchemy.schema.MetaData>`

        :param primary_key: The name of the column/key that should be used as the table's
          primary key.
        :type primary_key: :class:`str <python:str>`

        :param column_kwargs: An optional dictionary whose keys correspond to
          column/key, and whose values are themselves dictionaries with keyword
          arguments that will be passed ot the applicable :class:`Column`
          constructor. Defaults to :obj:`None <python:None>`.
        :type column_kwargs: :class:`dict <python:dict>` / :obj:`None <python:None>`

        :param skip_nested: If ``True`` then any keys in ``serialized`` that
          feature nested items (e.g. iterables, :class:`dict <python:dict>` objects,
          etc.) will be ignored. If ``False``, will treat nested items as
          :class:`str <python:str>`. Defaults to ``True``.
        :type skip_nested: :class:`bool <python:bool>`

        :param default_to_str: If ``True``, will automatically set a key/column whose
          value type cannot be determined to ``str``
          (:class:`Text <sqlalchemy:sqlalchemy.types.Text>`). If ``False``, will
          use the value type's ``__name__`` attribute and attempt to find a mapping.
          Defaults to ``False``.
        :type default_to_str: :class:`bool <python:bool>`

        :param type_mapping: Determines how value types in ``serialized`` map to
          SQL column data types. To add a new mapping or override a default, set a
          key to the name of the value type in Python, and set the value to a
          :doc:`SQLAlchemy Data Type <sqlalchemy:core/types>`. The following are the
          default mappings applied:

          .. list-table::
             :widths: 30 30
             :header-rows: 1

             * - Python Literal
               - SQL Column Type
             * - ``bool``
               - :class:`Boolean <sqlalchemy:sqlalchemy.types.Boolean>`
             * - ``str``
               - :class:`Text <sqlalchemy:sqlalchemy.types.Text>`
             * - ``int``
               - :class:`Integer <sqlalchemy:sqlalchemy.types.Integer>`
             * - ``float``
               - :class:`Float <sqlalchemy:sqlalchemy.types.Float>`
             * - ``date``
               - :class:`Date <sqlalchemy:sqlalchemy.types.Date>`
             * - ``datetime``
               - :class:`DateTime <sqlalchemy:sqlalchemy.types.DateTime>`
             * - ``time``
               - :class:`Time <sqlalchemy:sqlalchemy.types.Time>`

        :type type_mapping: :class:`dict <python:dict>` with type names as keys and
          column data types as values.

        :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 ``deserialize_kwargs``).

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

        :param deserialize_kwargs: Optional additional keyword arguments that will be
          passed to the deserialize function. Defaults to :obj:`None <python:None>`.
        :type deserialize_kwargs: :class:`dict <python:dict>` / :obj:`None <python:None>`

        :param kwargs: Any additional keyword arguments will be passed to the
          :class:`Table` constructor. For a full list of options, please see
          :class:`sqlalchemy.schema.Table <sqlalchemy:sqlalchemy.schema.Table>`.

        :returns: A :class:`Table` object.
        :rtype: :class:`Table`

        :raises UnsupportedValueTypeError: when a value in ``serialized`` does not
          have a corresponding key in ``type_mapping``
        :raises DeserializationError: if ``serialized`` is not a valid
          :class:`str <python:str>`
        :raises ValueError: if ``tablename`` is empty
        :raises ValueError: if ``primary_key`` is empty

        """
        # pylint: disable=line-too-long
        if deserialize_kwargs:
            from_json = parse_json(serialized,
                                   deserialize_function=deserialize_function,
                                   **deserialize_kwargs)
        else:
            from_json = parse_json(serialized,
                                   deserialize_function=deserialize_function)

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

        table = cls.from_dict(from_json,
                              tablename,
                              metadata,
                              primary_key,
                              column_kwargs=column_kwargs,
                              skip_nested=skip_nested,
                              default_to_str=default_to_str,
                              type_mapping=type_mapping,
                              **kwargs)

        return table
Esempio n. 4
0
    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)
def generate_model_from_json(serialized,
                             tablename,
                             primary_key,
                             deserialize_function = None,
                             cls = BaseModel,
                             serialization_config = None,
                             skip_nested = True,
                             default_to_str = False,
                             type_mapping = None,
                             base_model_attrs = None,
                             deserialize_kwargs = None,
                             **kwargs):
    """Generate a :term:`model class` from a serialized
    :term:`JSON <JavaScript Object Notation (JSON)>` string.

    .. versionadded: 0.3.0

    .. note::

      This function *cannot* programmatically create
      :term:`relationships <relationship>`, :term:`hybrid properties <hybrid property>`,
      or :term:`association proxies <association proxy>`.

    :param serialized: The JSON data whose keys will be treated as column
      names, while value data types will determine :term:`model attribute` data
      types, or the path to a file whose contents will be the JSON object in
      question.

      .. note::

        If providing a path to a file, and if the file contains more than one JSON
        object, will only use the first JSON object listed.

    :type serialized: :class:`str <python:str>` / Path-like object

    :param tablename: The name of the SQL table to which the model corresponds.
    :type tablename: :class:`str <python:str>`

    :param primary_key: The name of the column/key that should be used as the table's
      primary key.
    :type primary_key: :class:`str <python:str>`

    :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 ``deserialize_kwargs``).

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

    :param cls: The base class to use when generating a new :term:`model class`.
      Defaults to :class:`BaseModel` to provide serialization/de-serialization
      support.

      If a :class:`tuple <python:tuple>` of classes, will include :class:`BaseModel`
      in that list of classes to mixin serialization/de-serialization support.

      If not :obj:`None <python:None>` and not a :class:`tuple <python:tuple>`, will mixin
      :class:`BaseModel` with the value passed to provide
      serialization/de-serialization support.
    :type cls: :obj:`None <python:None>` / :class:`tuple <python:tuple>` of
      classes / class object

    :param serialization_config: Collection of
      :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
      that determine the generated model's
      :term:`serialization`/:term:`de-serialization`
      :ref:`configuration <configuration>`. If :obj:`None <python:None>`, will
      support serialization and de-serialization across all keys in
      ``serialized_dict``. Defaults to :obj:`None <python:None>`.
    :type serialization_config: Iterable of
      :class:`AttributeConfiguration <sqlathanor.attributes.AttributeConfiguration>`
      or coercable :class:`dict <python:dict>` objects / :obj:`None <python:None>`

    :param skip_nested: If ``True`` then any keys in ``serialized_json`` that
      feature nested items (e.g. iterables, JSON objects, etc.) will be ignored.
      If ``False``, will treat serialized items as :class:`str <python:str>`.
      Defaults to ``True``.
    :type skip_nested: :class:`bool <python:bool>`

    :param default_to_str: If ``True``, will automatically set a key/column whose
      value type cannot be determined to ``str``
      (:class:`Text <sqlalchemy:sqlalchemy.types.Text>`). If ``False``, will
      use the value type's ``__name__`` attribute and attempt to find a mapping.
      Defaults to ``False``.
    :type default_to_str: :class:`bool <python:bool>`

    :param type_mapping: Determines how value types in ``serialized`` map to
      SQL column data types. To add a new mapping or override a default, set a
      key to the name of the value type in Python, and set the value to a
      :doc:`SQLAlchemy Data Type <sqlalchemy:core/types>`. The following are the
      default mappings applied:

      .. list-table::
         :widths: 30 30
         :header-rows: 1

         * - Python Literal
           - SQL Column Type
         * - ``bool``
           - :class:`Boolean <sqlalchemy:sqlalchemy.types.Boolean>`
         * - ``str``
           - :class:`Text <sqlalchemy:sqlalchemy.types.Text>`
         * - ``int``
           - :class:`Integer <sqlalchemy:sqlalchemy.types.Integer>`
         * - ``float``
           - :class:`Float <sqlalchemy:sqlalchemy.types.Float>`
         * - ``date``
           - :class:`Date <sqlalchemy:sqlalchemy.types.Date>`
         * - ``datetime``
           - :class:`DateTime <sqlalchemy:sqlalchemy.types.DateTime>`
         * - ``time``
           - :class:`Time <sqlalchemy:sqlalchemy.types.Time>`

    :type type_mapping: :class:`dict <python:dict>` with type names as keys and
      column data types as values.

    :param base_model_attrs: Optional :class:`dict <python:dict>` of special
      attributes that will be applied to the generated
      :class:`BaseModel <sqlathanor.declarative.BaseModel>` (e.g.
      ``__table_args__``). Keys will correspond to the attribute name, while the
      value is the value that will be applied. Defaults to :obj:`None <python:None>`.
    :type base_model_attrs: :class:`dict <python:dict>` / :obj:`None <python:None>`

    :param deserialize_kwargs: Optional additional keyword arguments that will be
      passed to the deserialize function. Defaults to :obj:`None <python:None>`.
    :type deserialize_kwargs: :class:`dict <python:dict>` / :obj:`None <python:None>`

    :param kwargs: Any additional keyword arguments will be passed to
      :func:`declarative_base() <sqlathanor.declarative.declarative_base>` when
      generating the programmatic :class:`BaseModel <sqlathanor.declarative.BaseModel>`.

    :returns: :term:`Model class` whose structure matches ``serialized``.
    :rtype: :class:`BaseModel`

    :raises UnsupportedValueTypeError: when a value in ``serialized`` does not
      have a corresponding key in ``type_mapping``
    :raises ValueError: if ``tablename`` is empty

    """
    # pylint: disable=line-too-long
    if deserialize_kwargs:
        from_json = parse_json(serialized,
                               deserialize_function = deserialize_function,
                               **deserialize_kwargs)
    else:
        from_json = parse_json(serialized,
                               deserialize_function = deserialize_function)

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

    generated_model = generate_model_from_dict(from_json,
                                               tablename,
                                               primary_key,
                                               cls = cls,
                                               serialization_config = serialization_config,
                                               skip_nested = skip_nested,
                                               default_to_str = default_to_str,
                                               type_mapping = type_mapping,
                                               base_model_attrs = base_model_attrs,
                                               **kwargs)

    return generated_model