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)
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