Exemple #1
0
def test_parse_csv(input_value, kwargs, expected_result, error):
    if not error:
        if kwargs:
            result = parse_csv(input_value, **kwargs)
        else:
            result = parse_csv(input_value)

        print(result)

        assert isinstance(result, dict)
        assert checkers.are_dicts_equivalent(result, expected_result)
    else:
        with pytest.raises(error):
            if kwargs:
                result = parse_csv(input_value, **kwargs)
            else:
                result = parse_csv(input_value)
Exemple #2
0
    def from_csv(cls,
                 serialized,
                 tablename,
                 metadata,
                 primary_key,
                 column_kwargs=None,
                 skip_nested=True,
                 default_to_str=False,
                 type_mapping=None,
                 delimiter='|',
                 wrap_all_strings=False,
                 null_text='None',
                 wrapper_character="'",
                 double_wrapper_character_when_nested=False,
                 escape_character="\\",
                 line_terminator='\r\n',
                 **kwargs):
        """Generate a :class:`Table` object from a
        :term:`CSV <Comma-Separated Value (CSV)>` string.

        .. versionadded: 0.3.0

        :param serialized: The CSV data whose column headers will be treated as column
          names, while value data types will determine :term:`model attribute` data
          types.

          .. note::

            If a Path-like object, will read the file contents from a file that is assumed
            to include a header row. If a :class:`str <python:str>` and has more than
            one record (line), will assume the first line is a header row. If a
            :class:`list <python:list>`, will assume the first item is the header row.

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

        :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 delimiter: The delimiter used between columns. Defaults to ``|``.
        :type delimiter: :class:`str <python:str>`

        :param wrapper_character: The string used to wrap string values when
          wrapping is applied. Defaults to ``'``.
        :type wrapper_character: :class:`str <python:str>`

        :param null_text: The string used to indicate an empty value if empty
          values are wrapped. Defaults to `None`.
        :type null_text: :class:`str <python:str>`

        :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 DeserializationError: if ``serialized`` is not a valid
          :class:`str <python:str>`
        :raises UnsupportedValueTypeError: when a value in ``serialized`` does not
          have a corresponding key in ``type_mapping``
        :raises ValueError: if ``tablename`` is empty
        :raises ValueError: if ``primary_key`` is empty
        :raises CSVStructureError: if there are less than 2 (two) rows in ``serialized``
          or if column headers are not valid Python variable names

        """
        # pylint: disable=line-too-long,invalid-name,too-many-arguments

        if not checkers.is_file(serialized):
            serialized = read_csv_data(serialized, single_record=False)

        from_csv = parse_csv(serialized,
                             delimiter=delimiter,
                             wrap_all_strings=wrap_all_strings,
                             null_text=null_text,
                             wrapper_character=wrapper_character,
                             double_wrapper_character_when_nested=
                             double_wrapper_character_when_nested,
                             escape_character=escape_character,
                             line_terminator=line_terminator)

        table = cls.from_dict(from_csv,
                              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
def generate_model_from_csv(serialized,
                            tablename,
                            primary_key,
                            cls = BaseModel,
                            serialization_config = None,
                            skip_nested = True,
                            default_to_str = False,
                            type_mapping = None,
                            base_model_attrs = None,
                            delimiter = '|',
                            wrap_all_strings = False,
                            null_text = 'None',
                            wrapper_character = "'",
                            double_wrapper_character_when_nested = False,
                            escape_character = "\\",
                            line_terminator = '\r\n',
                            **kwargs):
    """Generate a :term:`model class` from a serialized
    :term:`CSV <Comma-Separated Value (CSV)>` 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 CSV data whose column headers will be treated as column
      names, while value data types will determine :term:`model attribute` data
      types.

      .. note::

        If a Path-like object, will read the file contents from a file that is assumed
        to include a header row. If a :class:`str <python:str>` and has more than
        one record (line), will assume the first line is a header row. If a
        :class:`list <python:list>`, will assume the first item is the header row.

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

    :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 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 delimiter: The delimiter used between columns. Defaults to ``|``.
    :type delimiter: :class:`str <python:str>`

    :param wrapper_character: The string used to wrap string values when
      wrapping is applied. Defaults to ``'``.
    :type wrapper_character: :class:`str <python:str>`

    :param null_text: The string used to indicate an empty value if empty
      values are wrapped. Defaults to `None`.
    :type null_text: :class:`str <python:str>`

    :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
    :raises DeserializationError: if ``serialized`` is not a valid
      :class:`str <python:str>`
    :raises CSVStructureError: if there are less than 2 (two) rows in ``serialized``
      or if column headers are not valid Python variable names

    """
    # pylint: disable=line-too-long,too-many-arguments

    if not checkers.is_file(serialized):
        serialized = read_csv_data(serialized, single_record = False)

    from_csv = parse_csv(serialized,
                         delimiter = delimiter,
                         wrap_all_strings = wrap_all_strings,
                         null_text = null_text,
                         wrapper_character = wrapper_character,
                         double_wrapper_character_when_nested = double_wrapper_character_when_nested,
                         escape_character = escape_character,
                         line_terminator = line_terminator)

    generated_model = generate_model_from_dict(from_csv,
                                               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