1#!/usr/bin/env python
2
3import io
4import itertools
5
6import six
7
8
9@classmethod
10def from_csv(cls, path, column_names=None, column_types=None, row_names=None, skip_lines=0, header=True, sniff_limit=0,
11             encoding='utf-8', row_limit=None, **kwargs):
12    """
13    Create a new table from a CSV.
14
15    This method uses agate's builtin CSV reader, which supplies encoding
16    support for both Python 2 and Python 3.
17
18    :code:`kwargs` will be passed through to the CSV reader.
19
20    :param path:
21        Filepath or file-like object from which to read CSV data. If a file-like
22        object is specified, it must be seekable. If using Python 2, the file
23        should be opened in binary mode (`rb`).
24    :param column_names:
25        See :meth:`.Table.__init__`.
26    :param column_types:
27        See :meth:`.Table.__init__`.
28    :param row_names:
29        See :meth:`.Table.__init__`.
30    :param skip_lines:
31        The number of lines to skip from the top of the file.
32    :param header:
33        If :code:`True`, the first row of the CSV is assumed to contain column
34        names. If :code:`header` and :code:`column_names` are both specified
35        then a row will be skipped, but :code:`column_names` will be used.
36    :param sniff_limit:
37        Limit CSV dialect sniffing to the specified number of bytes. Set to
38        None to sniff the entire file. Defaults to 0 (no sniffing).
39    :param encoding:
40        Character encoding of the CSV file. Note: if passing in a file
41        handle it is assumed you have already opened it with the correct
42        encoding specified.
43    :param row_limit:
44        Limit how many rows of data will be read.
45    """
46    from agate import csv
47    from agate.table import Table
48
49    close = False
50
51    try:
52        if hasattr(path, 'read'):
53            f = path
54        else:
55            if six.PY2:
56                f = open(path, 'Urb')
57            else:
58                f = io.open(path, encoding=encoding)
59
60            close = True
61
62        if isinstance(skip_lines, int):
63            while skip_lines > 0:
64                f.readline()
65                skip_lines -= 1
66        else:
67            raise ValueError('skip_lines argument must be an int')
68
69        contents = six.StringIO(f.read())
70
71        if sniff_limit is None:
72            kwargs['dialect'] = csv.Sniffer().sniff(contents.getvalue())
73        elif sniff_limit > 0:
74            kwargs['dialect'] = csv.Sniffer().sniff(contents.getvalue()[:sniff_limit])
75
76        if six.PY2:
77            kwargs['encoding'] = encoding
78
79        reader = csv.reader(contents, header=header, **kwargs)
80
81        if header:
82            if column_names is None:
83                column_names = next(reader)
84            else:
85                next(reader)
86
87        if row_limit is None:
88            rows = tuple(reader)
89        else:
90            rows = tuple(itertools.islice(reader, row_limit))
91
92    finally:
93        if close:
94            f.close()
95
96    return Table(rows, column_names, column_types, row_names=row_names)
97