fastpycsv 5.2.0__cp313-cp313-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fastpycsv/__init__.py +71 -0
- fastpycsv/_format.py +40 -0
- fastpycsv/_numpy.py +115 -0
- fastpycsv/_reader.py +185 -0
- fastpycsv/_writer.py +38 -0
- fastpycsv/compat.py +5 -0
- fastpycsv/fastpycsv.cp313-win_amd64.pyd +0 -0
- fastpycsv-5.2.0.dist-info/METADATA +336 -0
- fastpycsv-5.2.0.dist-info/RECORD +11 -0
- fastpycsv-5.2.0.dist-info/WHEEL +5 -0
- fastpycsv-5.2.0.dist-info/licenses/LICENSE +21 -0
fastpycsv/__init__.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import importlib.machinery as _machinery
|
|
2
|
+
import importlib.util as _util
|
|
3
|
+
import os as _os
|
|
4
|
+
import sys as _sys
|
|
5
|
+
|
|
6
|
+
__path__ = [_os.path.dirname(__file__)]
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _load_native_module():
|
|
10
|
+
module_name = __name__ + ".fastpycsv"
|
|
11
|
+
existing = _sys.modules.get(module_name)
|
|
12
|
+
if existing is not None:
|
|
13
|
+
return existing
|
|
14
|
+
|
|
15
|
+
for suffix in _machinery.EXTENSION_SUFFIXES:
|
|
16
|
+
path = _os.path.join(__path__[0], "fastpycsv" + suffix)
|
|
17
|
+
if not _os.path.exists(path):
|
|
18
|
+
continue
|
|
19
|
+
|
|
20
|
+
spec = _util.spec_from_file_location(module_name, path)
|
|
21
|
+
if spec is None or spec.loader is None:
|
|
22
|
+
continue
|
|
23
|
+
|
|
24
|
+
module = _util.module_from_spec(spec)
|
|
25
|
+
_sys.modules[module_name] = module
|
|
26
|
+
spec.loader.exec_module(module)
|
|
27
|
+
return module
|
|
28
|
+
|
|
29
|
+
raise ImportError("cannot find the fastpycsv native extension next to the Python package")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_native = _load_native_module()
|
|
33
|
+
|
|
34
|
+
CSVFileInfo = _native.CSVFileInfo
|
|
35
|
+
DataType = _native.DataType
|
|
36
|
+
Field = _native.Field
|
|
37
|
+
Reader = _native.Reader
|
|
38
|
+
Row = _native.Row
|
|
39
|
+
csv_data_types = _native.csv_data_types
|
|
40
|
+
all_of = _native.all_of
|
|
41
|
+
equal = _native.equal
|
|
42
|
+
greater = _native.greater
|
|
43
|
+
greater_equal = _native.greater_equal
|
|
44
|
+
get_file_info = _native.get_file_info
|
|
45
|
+
less = _native.less
|
|
46
|
+
less_equal = _native.less_equal
|
|
47
|
+
parse_no_header = _native.parse_no_header
|
|
48
|
+
from ._numpy import read_numpy, read_numpy_batches
|
|
49
|
+
from ._reader import reader
|
|
50
|
+
from ._writer import write_csv
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"CSVFileInfo",
|
|
54
|
+
"DataType",
|
|
55
|
+
"Field",
|
|
56
|
+
"Reader",
|
|
57
|
+
"Row",
|
|
58
|
+
"all_of",
|
|
59
|
+
"csv_data_types",
|
|
60
|
+
"equal",
|
|
61
|
+
"greater",
|
|
62
|
+
"greater_equal",
|
|
63
|
+
"get_file_info",
|
|
64
|
+
"less",
|
|
65
|
+
"less_equal",
|
|
66
|
+
"parse_no_header",
|
|
67
|
+
"read_numpy",
|
|
68
|
+
"read_numpy_batches",
|
|
69
|
+
"reader",
|
|
70
|
+
"write_csv",
|
|
71
|
+
]
|
fastpycsv/_format.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""CSVFormat construction helpers for the public Python facade."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Sequence
|
|
6
|
+
|
|
7
|
+
from .fastpycsv import Format as _NativeFormat
|
|
8
|
+
from .fastpycsv import VariableColumnPolicy as _NativeVariableColumnPolicy
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _make_format(
|
|
12
|
+
delimiter: str = ",",
|
|
13
|
+
quotechar: Optional[str] = '"',
|
|
14
|
+
doublequote: bool = True,
|
|
15
|
+
skipinitialspace: bool = False,
|
|
16
|
+
strict: bool = False,
|
|
17
|
+
fieldnames: Optional[Sequence[str]] = None,
|
|
18
|
+
no_header: bool = True,
|
|
19
|
+
) -> _NativeFormat:
|
|
20
|
+
if len(delimiter) != 1:
|
|
21
|
+
raise TypeError("delimiter must be a one-character string")
|
|
22
|
+
if quotechar is not None and len(quotechar) != 1:
|
|
23
|
+
raise TypeError("quotechar must be a one-character string or None")
|
|
24
|
+
if not doublequote:
|
|
25
|
+
raise NotImplementedError("fastpycsv.reader does not support doublequote=False")
|
|
26
|
+
|
|
27
|
+
fmt = _NativeFormat().delimiter(delimiter)
|
|
28
|
+
if no_header:
|
|
29
|
+
fmt.no_header()
|
|
30
|
+
if quotechar is None:
|
|
31
|
+
fmt.quote(False)
|
|
32
|
+
else:
|
|
33
|
+
fmt.quote(quotechar)
|
|
34
|
+
if skipinitialspace:
|
|
35
|
+
fmt.trim([" "])
|
|
36
|
+
if strict:
|
|
37
|
+
fmt.variable_columns(_NativeVariableColumnPolicy.THROW)
|
|
38
|
+
if fieldnames is not None:
|
|
39
|
+
fmt.column_names(list(fieldnames))
|
|
40
|
+
return fmt
|
fastpycsv/_numpy.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""NumPy export facade sharing reader() CSV format handling."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Optional, Sequence
|
|
6
|
+
|
|
7
|
+
from ._format import _make_format
|
|
8
|
+
from ._reader import _CSVSource
|
|
9
|
+
from .fastpycsv import _read_numpy as _native_read_numpy
|
|
10
|
+
from .fastpycsv import _read_numpy_batches as _native_read_numpy_batches
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _numpy_format(
|
|
14
|
+
delimiter: str,
|
|
15
|
+
quotechar: Optional[str],
|
|
16
|
+
doublequote: bool,
|
|
17
|
+
skipinitialspace: bool,
|
|
18
|
+
strict: bool,
|
|
19
|
+
consume_header: bool,
|
|
20
|
+
fieldnames: Optional[Sequence[str]],
|
|
21
|
+
):
|
|
22
|
+
return _make_format(
|
|
23
|
+
delimiter,
|
|
24
|
+
quotechar,
|
|
25
|
+
doublequote,
|
|
26
|
+
skipinitialspace,
|
|
27
|
+
strict,
|
|
28
|
+
fieldnames=fieldnames,
|
|
29
|
+
no_header=(not consume_header) or fieldnames is not None,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_numpy(
|
|
34
|
+
csvfile,
|
|
35
|
+
columns=None,
|
|
36
|
+
*,
|
|
37
|
+
cast: bool = True,
|
|
38
|
+
predicate=None,
|
|
39
|
+
delimiter: str = ",",
|
|
40
|
+
quotechar: Optional[str] = '"',
|
|
41
|
+
doublequote: bool = True,
|
|
42
|
+
skipinitialspace: bool = False,
|
|
43
|
+
strict: bool = False,
|
|
44
|
+
consume_header: bool = True,
|
|
45
|
+
fieldnames: Optional[Sequence[str]] = None,
|
|
46
|
+
):
|
|
47
|
+
fmt = _numpy_format(
|
|
48
|
+
delimiter,
|
|
49
|
+
quotechar,
|
|
50
|
+
doublequote,
|
|
51
|
+
skipinitialspace,
|
|
52
|
+
strict,
|
|
53
|
+
consume_header,
|
|
54
|
+
fieldnames,
|
|
55
|
+
)
|
|
56
|
+
source = _CSVSource(csvfile)
|
|
57
|
+
try:
|
|
58
|
+
return _native_read_numpy(source.name, fmt, columns, cast, predicate)
|
|
59
|
+
finally:
|
|
60
|
+
source.cleanup()
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def read_numpy_batches(
|
|
64
|
+
csvfile,
|
|
65
|
+
columns=None,
|
|
66
|
+
*,
|
|
67
|
+
predicate=None,
|
|
68
|
+
cast: bool = True,
|
|
69
|
+
batch_size: int = 50000,
|
|
70
|
+
schema: str = "sample",
|
|
71
|
+
delimiter: str = ",",
|
|
72
|
+
quotechar: Optional[str] = '"',
|
|
73
|
+
doublequote: bool = True,
|
|
74
|
+
skipinitialspace: bool = False,
|
|
75
|
+
strict: bool = False,
|
|
76
|
+
consume_header: bool = True,
|
|
77
|
+
fieldnames: Optional[Sequence[str]] = None,
|
|
78
|
+
):
|
|
79
|
+
fmt = _numpy_format(
|
|
80
|
+
delimiter,
|
|
81
|
+
quotechar,
|
|
82
|
+
doublequote,
|
|
83
|
+
skipinitialspace,
|
|
84
|
+
strict,
|
|
85
|
+
consume_header,
|
|
86
|
+
fieldnames,
|
|
87
|
+
)
|
|
88
|
+
source = _CSVSource(csvfile)
|
|
89
|
+
try:
|
|
90
|
+
reader = _native_read_numpy_batches(source.name, fmt, columns, predicate, cast, batch_size, schema)
|
|
91
|
+
except Exception:
|
|
92
|
+
source.cleanup()
|
|
93
|
+
raise
|
|
94
|
+
return _NumpyBatchIterator(reader, source)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class _NumpyBatchIterator:
|
|
98
|
+
def __init__(self, reader, source: _CSVSource):
|
|
99
|
+
self._reader = reader
|
|
100
|
+
self._source = source
|
|
101
|
+
|
|
102
|
+
def __iter__(self):
|
|
103
|
+
return self
|
|
104
|
+
|
|
105
|
+
def __next__(self):
|
|
106
|
+
try:
|
|
107
|
+
return next(self._reader)
|
|
108
|
+
except StopIteration:
|
|
109
|
+
self._source.cleanup()
|
|
110
|
+
raise
|
|
111
|
+
|
|
112
|
+
def __del__(self):
|
|
113
|
+
source = getattr(self, "_source", None)
|
|
114
|
+
if source is not None:
|
|
115
|
+
source.cleanup()
|
fastpycsv/_reader.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Lazy stdlib-adjacent reader facade over the native fastpycsv extension."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import tempfile
|
|
7
|
+
from typing import Iterable, Optional, Sequence
|
|
8
|
+
|
|
9
|
+
from ._format import _make_format
|
|
10
|
+
from .fastpycsv import _RowsReader
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _is_path(value: object) -> bool:
|
|
14
|
+
return isinstance(value, (str, bytes, os.PathLike))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _at_start(value: object) -> bool:
|
|
18
|
+
try:
|
|
19
|
+
return value.tell() == 0
|
|
20
|
+
except (AttributeError, OSError):
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _TempCSV:
|
|
25
|
+
def __init__(self, rows: Iterable[str]):
|
|
26
|
+
handle = tempfile.NamedTemporaryFile("w", encoding="utf-8", newline="", delete=False)
|
|
27
|
+
self.name = handle.name
|
|
28
|
+
with handle:
|
|
29
|
+
for row in rows:
|
|
30
|
+
handle.write(row)
|
|
31
|
+
|
|
32
|
+
def cleanup(self) -> None:
|
|
33
|
+
try:
|
|
34
|
+
os.unlink(self.name)
|
|
35
|
+
except FileNotFoundError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _CSVSource:
|
|
40
|
+
def __init__(self, csvfile):
|
|
41
|
+
self._temp = None
|
|
42
|
+
self.name = self._filename(csvfile)
|
|
43
|
+
|
|
44
|
+
def cleanup(self) -> None:
|
|
45
|
+
if self._temp is not None:
|
|
46
|
+
self._temp.cleanup()
|
|
47
|
+
|
|
48
|
+
def _filename(self, csvfile) -> str:
|
|
49
|
+
if _is_path(csvfile):
|
|
50
|
+
return os.fspath(csvfile)
|
|
51
|
+
if (
|
|
52
|
+
hasattr(csvfile, "name")
|
|
53
|
+
and _is_path(csvfile.name)
|
|
54
|
+
and not getattr(csvfile, "closed", False)
|
|
55
|
+
and _at_start(csvfile)
|
|
56
|
+
):
|
|
57
|
+
return os.fspath(csvfile.name)
|
|
58
|
+
|
|
59
|
+
self._temp = _TempCSV(csvfile)
|
|
60
|
+
return self._temp.name
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class _MaterializedRows:
|
|
64
|
+
def __init__(self, parent: "_BaseReader", iterator):
|
|
65
|
+
self._parent = parent
|
|
66
|
+
self._iterator = iterator
|
|
67
|
+
|
|
68
|
+
def __iter__(self):
|
|
69
|
+
return self
|
|
70
|
+
|
|
71
|
+
def __next__(self):
|
|
72
|
+
try:
|
|
73
|
+
return next(self._iterator)
|
|
74
|
+
except StopIteration:
|
|
75
|
+
self._parent._source.cleanup()
|
|
76
|
+
raise
|
|
77
|
+
|
|
78
|
+
def all(self):
|
|
79
|
+
try:
|
|
80
|
+
return self._iterator.all()
|
|
81
|
+
finally:
|
|
82
|
+
self._parent._source.cleanup()
|
|
83
|
+
|
|
84
|
+
def chunks(self, size: int):
|
|
85
|
+
if size <= 0:
|
|
86
|
+
raise ValueError("chunk size must be greater than zero")
|
|
87
|
+
|
|
88
|
+
def chunk_iterator():
|
|
89
|
+
while True:
|
|
90
|
+
batch = self._iterator.read_chunk(size)
|
|
91
|
+
if not batch:
|
|
92
|
+
self._parent._source.cleanup()
|
|
93
|
+
return
|
|
94
|
+
yield batch
|
|
95
|
+
|
|
96
|
+
return chunk_iterator()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class _BaseReader:
|
|
100
|
+
def _init_reader(self, csvfile, fmt, cast: bool, batch_size: int) -> None:
|
|
101
|
+
self._source = _CSVSource(csvfile)
|
|
102
|
+
self._iterator = iter(_RowsReader(self._source.name, fmt, cast, batch_size))
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def fieldnames(self):
|
|
106
|
+
return list(self._iterator.fieldnames)
|
|
107
|
+
|
|
108
|
+
def get_col_names(self):
|
|
109
|
+
return self.fieldnames
|
|
110
|
+
|
|
111
|
+
def __iter__(self):
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def __next__(self):
|
|
115
|
+
try:
|
|
116
|
+
row = next(self._iterator)
|
|
117
|
+
except StopIteration:
|
|
118
|
+
self._source.cleanup()
|
|
119
|
+
raise
|
|
120
|
+
return row
|
|
121
|
+
|
|
122
|
+
def filter(self, predicate, *, append: bool = True):
|
|
123
|
+
if predicate is None:
|
|
124
|
+
raise TypeError("reader.filter() expects a fastpycsv predicate; create a fresh reader for an unfiltered pass")
|
|
125
|
+
self._iterator.filter(predicate, append)
|
|
126
|
+
return self
|
|
127
|
+
|
|
128
|
+
def lists(self, columns=None):
|
|
129
|
+
return _MaterializedRows(self, self._iterator.lists(columns))
|
|
130
|
+
|
|
131
|
+
def tuples(self, columns=None):
|
|
132
|
+
return _MaterializedRows(self, self._iterator.tuples(columns))
|
|
133
|
+
|
|
134
|
+
def dicts(self, columns=None):
|
|
135
|
+
return _MaterializedRows(self, self._iterator.dicts(columns))
|
|
136
|
+
|
|
137
|
+
def to_lists(self, columns=None):
|
|
138
|
+
return self.lists(columns).all()
|
|
139
|
+
|
|
140
|
+
def to_tuples(self, columns=None):
|
|
141
|
+
return self.tuples(columns).all()
|
|
142
|
+
|
|
143
|
+
def to_dicts(self, columns=None):
|
|
144
|
+
return self.dicts(columns).all()
|
|
145
|
+
|
|
146
|
+
def __del__(self):
|
|
147
|
+
source = getattr(self, "_source", None)
|
|
148
|
+
if source is not None:
|
|
149
|
+
source.cleanup()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class _Reader(_BaseReader):
|
|
153
|
+
def __init__(
|
|
154
|
+
self,
|
|
155
|
+
csvfile,
|
|
156
|
+
*,
|
|
157
|
+
delimiter: str = ",",
|
|
158
|
+
quotechar: Optional[str] = '"',
|
|
159
|
+
doublequote: bool = True,
|
|
160
|
+
skipinitialspace: bool = False,
|
|
161
|
+
strict: bool = False,
|
|
162
|
+
cast: bool = False,
|
|
163
|
+
typed: Optional[bool] = None,
|
|
164
|
+
consume_header: bool = True,
|
|
165
|
+
fieldnames: Optional[Sequence[str]] = None,
|
|
166
|
+
batch_size: int = 8192,
|
|
167
|
+
):
|
|
168
|
+
if typed is not None:
|
|
169
|
+
cast = typed
|
|
170
|
+
fmt = _make_format(
|
|
171
|
+
delimiter,
|
|
172
|
+
quotechar,
|
|
173
|
+
doublequote,
|
|
174
|
+
skipinitialspace,
|
|
175
|
+
strict,
|
|
176
|
+
fieldnames=fieldnames,
|
|
177
|
+
no_header=(not consume_header) or fieldnames is not None,
|
|
178
|
+
)
|
|
179
|
+
self._init_reader(csvfile, fmt, cast, batch_size)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def reader(csvfile, dialect="excel", **fmtparams) -> _Reader:
|
|
183
|
+
if dialect != "excel":
|
|
184
|
+
raise NotImplementedError("fastpycsv.reader currently supports only the default excel dialect")
|
|
185
|
+
return _Reader(csvfile, **fmtparams)
|
fastpycsv/_writer.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""CSV writer facade over the native fastpycsv extension."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Iterable, Optional, Sequence
|
|
7
|
+
|
|
8
|
+
from . import fastpycsv as _native
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def write_csv(
|
|
12
|
+
csvfile,
|
|
13
|
+
rows: Iterable[object],
|
|
14
|
+
*,
|
|
15
|
+
fieldnames: Optional[Sequence[object]] = None,
|
|
16
|
+
write_header: bool = True,
|
|
17
|
+
quote_minimal: bool = True,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Write rows to a CSV file.
|
|
20
|
+
|
|
21
|
+
`rows` may contain lazy fastpycsv rows, dictionaries, or ordinary Python
|
|
22
|
+
iterables. Fields are stringified before writing; `None` becomes an empty
|
|
23
|
+
CSV field.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
if hasattr(csvfile, "write"):
|
|
27
|
+
write = getattr(_native, "_write_csv_filelike", None)
|
|
28
|
+
if write is None:
|
|
29
|
+
raise AttributeError("fastpycsv native extension has not been rebuilt with file-like write_csv support")
|
|
30
|
+
|
|
31
|
+
write(csvfile, rows, fieldnames, write_header, quote_minimal)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
write = getattr(_native, "_write_csv", None)
|
|
35
|
+
if write is None:
|
|
36
|
+
raise AttributeError("fastpycsv native extension has not been rebuilt with write_csv support")
|
|
37
|
+
|
|
38
|
+
write(os.fspath(csvfile), rows, fieldnames, write_header, quote_minimal)
|
fastpycsv/compat.py
ADDED
|
Binary file
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
|
+
Name: fastpycsv
|
|
3
|
+
Version: 5.2.0
|
|
4
|
+
Summary: Fast ETL-focused CSV parsing for Python, backed by Vince's CSV Parser
|
|
5
|
+
Keywords: csv,parser,etl,dataframe,numpy
|
|
6
|
+
Author: Vincent La
|
|
7
|
+
Maintainer: Vincent La
|
|
8
|
+
License: MIT License
|
|
9
|
+
|
|
10
|
+
Copyright (c) 2017-2026 Vincent La
|
|
11
|
+
|
|
12
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
13
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
14
|
+
in the Software without restriction, including without limitation the rights
|
|
15
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
16
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
17
|
+
furnished to do so, subject to the following conditions:
|
|
18
|
+
|
|
19
|
+
The above copyright notice and this permission notice shall be included in all
|
|
20
|
+
copies or substantial portions of the Software.
|
|
21
|
+
|
|
22
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
23
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
24
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
25
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
26
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
27
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
28
|
+
SOFTWARE.
|
|
29
|
+
|
|
30
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
31
|
+
Classifier: Intended Audience :: Developers
|
|
32
|
+
Classifier: Intended Audience :: Science/Research
|
|
33
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
34
|
+
Classifier: Operating System :: OS Independent
|
|
35
|
+
Classifier: Programming Language :: C++
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
44
|
+
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
45
|
+
Classifier: Topic :: Database
|
|
46
|
+
Classifier: Topic :: Scientific/Engineering
|
|
47
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
48
|
+
Project-URL: Homepage, https://github.com/vincentlaucsb/csv-parser
|
|
49
|
+
Project-URL: Documentation, https://vincentlaucsb.github.io/csv-parser/python/
|
|
50
|
+
Project-URL: Source, https://github.com/vincentlaucsb/csv-parser
|
|
51
|
+
Project-URL: Issues, https://github.com/vincentlaucsb/csv-parser/issues
|
|
52
|
+
Requires-Python: >=3.9
|
|
53
|
+
Provides-Extra: numpy
|
|
54
|
+
Requires-Dist: numpy>=2; extra == "numpy"
|
|
55
|
+
Provides-Extra: pandas
|
|
56
|
+
Requires-Dist: numpy>=2; extra == "pandas"
|
|
57
|
+
Requires-Dist: pandas; extra == "pandas"
|
|
58
|
+
Description-Content-Type: text/markdown
|
|
59
|
+
|
|
60
|
+
# fastpycsv
|
|
61
|
+
|
|
62
|
+
<p align="center">
|
|
63
|
+
<img src="assets/fastpycsv-logo.png" alt="fastpycsv logo" width="240">
|
|
64
|
+
</p>
|
|
65
|
+
|
|
66
|
+
`fastpycsv` is a fast Python CSV toolkit backed by Vince's CSV Parser. It is built
|
|
67
|
+
for ETL-style workflows where users need to scan large CSV files, filter rows,
|
|
68
|
+
extract selected columns, materialize NumPy arrays, or write cleaned CSV output
|
|
69
|
+
without routing every job through pandas or pyarrow.
|
|
70
|
+
|
|
71
|
+
The package is named `fastpycsv`; it does not replace or shadow Python's stdlib
|
|
72
|
+
`csv` module.
|
|
73
|
+
|
|
74
|
+
Long-form Python documentation lives in `python/docs/` and is written in
|
|
75
|
+
Sphinx/MyST-flavored Markdown so it can be published with Read the Docs later.
|
|
76
|
+
|
|
77
|
+
Main API:
|
|
78
|
+
|
|
79
|
+
- `fastpycsv.reader()` yields lazy, list-like row objects over the native parser.
|
|
80
|
+
- `fastpycsv.read_numpy()` exports selected columns as eager NumPy arrays.
|
|
81
|
+
- `fastpycsv.read_numpy_batches()` streams selected columns as bounded-memory NumPy
|
|
82
|
+
batches.
|
|
83
|
+
- `fastpycsv.write_csv()` streams lazy rows or Python iterables through the native
|
|
84
|
+
CSV writer.
|
|
85
|
+
|
|
86
|
+
## Building
|
|
87
|
+
|
|
88
|
+
Install the local package directly from the repository root:
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
python -m pip install -e E:\GitHub\csv-parser
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For a regular non-editable local install:
|
|
95
|
+
|
|
96
|
+
```powershell
|
|
97
|
+
python -m pip install E:\GitHub\csv-parser
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The package build uses CMake through `scikit-build-core` and builds the native
|
|
101
|
+
`fastpycsv` extension for the Python interpreter running `pip`.
|
|
102
|
+
|
|
103
|
+
Build the extension with `BUILD_PYTHON=ON`:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
cmake -S . -B build/fastpycsv -DBUILD_PYTHON=ON -DCMAKE_BUILD_TYPE=Release
|
|
107
|
+
cmake --build build/fastpycsv --target fastpycsv --config Release
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
For an existing top-level build configured without `BUILD_PYTHON=ON`, build the
|
|
111
|
+
`fastpycsv` target directly. It bootstraps a separate `build/fastpycsv` tree with
|
|
112
|
+
`BUILD_PYTHON=ON`, so the main build does not need to be reconfigured:
|
|
113
|
+
|
|
114
|
+
```powershell
|
|
115
|
+
cmake --build build/x64-Release --target fastpycsv --config Release
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
To force a specific interpreter, configure the main build with:
|
|
119
|
+
|
|
120
|
+
```powershell
|
|
121
|
+
cmake -S . -B build/x64-Release -DFASTPYCSV_BOOTSTRAP_PYTHON_EXECUTABLE=C:/Python314/python.exe
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The `fastpycsv` build fetches nanobind with CMake `FetchContent` only when the
|
|
125
|
+
Python binding is requested. A normal C++ library/test build does not require
|
|
126
|
+
the nanobind checkout.
|
|
127
|
+
|
|
128
|
+
## Reader API
|
|
129
|
+
|
|
130
|
+
Use `fastpycsv.reader()` for lazy row objects. By default, it consumes the first row
|
|
131
|
+
as column names:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
import fastpycsv
|
|
135
|
+
|
|
136
|
+
with open("data.csv", newline="", encoding="utf-8") as handle:
|
|
137
|
+
for row in fastpycsv.reader(handle):
|
|
138
|
+
assert isinstance(row["name"], str)
|
|
139
|
+
values = row.as_list() # Materialize explicitly when needed.
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Pass `consume_header=False` when the first input row should be emitted as data:
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
with open("headerless.csv", newline="", encoding="utf-8") as handle:
|
|
146
|
+
for row in fastpycsv.reader(handle, consume_header=False):
|
|
147
|
+
print(row[0])
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Call `row.as_dict()` only when you explicitly want to materialize a row as a
|
|
151
|
+
plain Python dictionary.
|
|
152
|
+
|
|
153
|
+
When plain Python row objects are the desired output, use the materialized row
|
|
154
|
+
iterators instead of converting each lazy row manually:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
predicate = fastpycsv.all_of(
|
|
158
|
+
fastpycsv.equal("region", "el paso", case_sensitive=False),
|
|
159
|
+
fastpycsv.less("price", "15000"),
|
|
160
|
+
)
|
|
161
|
+
reader = fastpycsv.reader("data.csv").filter(predicate)
|
|
162
|
+
|
|
163
|
+
for row in reader.lists(["id", "price"]):
|
|
164
|
+
...
|
|
165
|
+
|
|
166
|
+
for batch in fastpycsv.reader("data.csv").dicts(["id", "price"]).chunks(50_000):
|
|
167
|
+
...
|
|
168
|
+
|
|
169
|
+
rows = fastpycsv.reader("data.csv").tuples(["id", "price"]).all()
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The `.lists()`, `.tuples()`, and `.dicts()` iterators stream one row at a time.
|
|
173
|
+
Their `.chunks(size)` methods keep memory bounded while amortizing native
|
|
174
|
+
conversion overhead, and `.all()` is the eager bulk-export path. Native
|
|
175
|
+
`reader.filter(predicate)` uses the same `fastpycsv.equal()`, `less()`, `greater()`,
|
|
176
|
+
and `all_of()` predicate objects as the NumPy APIs, and validates predicate
|
|
177
|
+
column names before rows are consumed.
|
|
178
|
+
|
|
179
|
+
Most users can ignore `reader(batch_size=...)`. It does not make ordinary
|
|
180
|
+
iteration return batches:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
for row in fastpycsv.reader("data.csv"):
|
|
184
|
+
... # one row at a time
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Use `.chunks(size)` when you want Python lists of rows. `batch_size` only tunes
|
|
188
|
+
how many rows fastpycsv asks the native parser to process at once while doing
|
|
189
|
+
filtered or projected materialization, such as `.filter(...).dicts(...).all()`.
|
|
190
|
+
The default is a good starting point; change it only after benchmarking a large
|
|
191
|
+
filtered export.
|
|
192
|
+
|
|
193
|
+
Use `fastpycsv.write_csv()` to stream lazy rows or ordinary Python iterables back
|
|
194
|
+
out through csv-parser's native writer:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
reader = fastpycsv.reader("vehicles.csv")
|
|
198
|
+
|
|
199
|
+
fastpycsv.write_csv(
|
|
200
|
+
"cheap_el_paso_fords.csv",
|
|
201
|
+
(row for row in reader if row["region"] == "el paso" and row["manufacturer"] == "ford"),
|
|
202
|
+
fieldnames=["id", "price", "year", "region"],
|
|
203
|
+
)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Dictionary rows infer their header from the first row when `fieldnames` is not
|
|
207
|
+
provided. `None` is written as an empty field; all other Python field values are
|
|
208
|
+
stringified.
|
|
209
|
+
|
|
210
|
+
Pass `cast=True` only when you want csv-parser's scalar classification exposed
|
|
211
|
+
as Python values. Empty fields become `None`, boolean fields become `bool`,
|
|
212
|
+
integral fields become `int`, floating point fields become `float`, timestamp
|
|
213
|
+
fields become `datetime.datetime`, and all other fields remain `str`.
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
reader = fastpycsv.reader(["id,amount,active\n", "1,2.5,true\n"], cast=True)
|
|
217
|
+
rows = list(reader)
|
|
218
|
+
assert reader.fieldnames == ["id", "amount", "active"]
|
|
219
|
+
assert rows[0].as_list() == [1, 2.5, True]
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Use `fastpycsv.read_numpy(path, columns=None, *, cast=True, predicate=None)` when
|
|
223
|
+
you want eager column arrays suitable for pandas:
|
|
224
|
+
|
|
225
|
+
```python
|
|
226
|
+
import pandas as pd
|
|
227
|
+
|
|
228
|
+
frame = pd.DataFrame(fastpycsv.read_numpy("data.csv"))
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
`read_numpy()` returns a `dict` keyed by column name. String columns use NumPy
|
|
232
|
+
2.x `StringDType`, nullable numeric and boolean columns widen to `float64` with
|
|
233
|
+
`NaN`, and non-null integer, float, and boolean columns use dense NumPy arrays.
|
|
234
|
+
It does not produce object arrays. The C++ export path batches rows through
|
|
235
|
+
`DataFrame` column views and `DataFrameExecutor`; remaining fixed costs are
|
|
236
|
+
mostly NumPy `StringDType` construction for string-heavy data and pandas'
|
|
237
|
+
DataFrame materialization after the arrays have been built.
|
|
238
|
+
|
|
239
|
+
Use `fastpycsv.read_numpy_batches(path, columns=None, *, predicate=None, cast=True,
|
|
240
|
+
batch_size=50000, schema="sample")` when you want streaming dictionaries of
|
|
241
|
+
NumPy arrays instead of one eager full-file result. `schema="sample"` infers
|
|
242
|
+
dtypes from the first bounded batch and then streams once, `schema="global"`
|
|
243
|
+
does a full pre-scan for stable dtypes matching `read_numpy()`, and
|
|
244
|
+
`schema="batch"` infers each emitted batch independently for true one-pass
|
|
245
|
+
bounded-memory streaming. With `cast=False`, batches are string-only and skip
|
|
246
|
+
schema inference. Explicit `dtypes={column: dtype}` overrides are not implemented
|
|
247
|
+
yet and are tracked as a follow-up.
|
|
248
|
+
|
|
249
|
+
For simple row filters, create native predicates and pass them to
|
|
250
|
+
`read_numpy()` or `read_numpy_batches()`:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
predicate = fastpycsv.equal("region", "el paso", case_sensitive=False)
|
|
254
|
+
arrays = fastpycsv.read_numpy("vehicles.csv", columns=["price", "year"], predicate=predicate)
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The facade supports the common `delimiter`, `quotechar`, `doublequote=True`,
|
|
258
|
+
`skipinitialspace`, `strict`, `consume_header`, and `fieldnames` options.
|
|
259
|
+
Unsupported dialect features intentionally fail fast instead of silently
|
|
260
|
+
diverging from stdlib behavior.
|
|
261
|
+
|
|
262
|
+
Lower-level extension objects remain available where they have a clear Python
|
|
263
|
+
use, but `fastpycsv` intentionally keeps C++ configuration machinery out of the
|
|
264
|
+
stable Python facade.
|
|
265
|
+
|
|
266
|
+
## Benchmarks
|
|
267
|
+
|
|
268
|
+
To compare reader throughput locally:
|
|
269
|
+
|
|
270
|
+
```powershell
|
|
271
|
+
python python/benchmarks/compare_readers.py path/to/input.csv
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
To compare the streaming EDA script against pandas using the pyarrow CSV engine:
|
|
275
|
+
|
|
276
|
+
```powershell
|
|
277
|
+
python python/benchmarks/compare_eda.py path/to/input.csv
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
To compare a streaming `fastpycsv` filter against pyarrow on the Used Cars-style
|
|
281
|
+
`region == "el paso"` workload:
|
|
282
|
+
|
|
283
|
+
```powershell
|
|
284
|
+
python python/benchmarks/compare_filter.py path/to/vehicles.csv
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
To compare selected-column CSV-to-NumPy materialization against pyarrow:
|
|
288
|
+
|
|
289
|
+
```powershell
|
|
290
|
+
python python/benchmarks/compare_numpy_materialization.py path/to/vehicles.csv
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
To compare ordinary Python object materialization against pyarrow and Polars:
|
|
294
|
+
|
|
295
|
+
```powershell
|
|
296
|
+
python python/benchmarks/compare_python_materialization.py path/to/vehicles.csv
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
This compares full CSV and first+last-column subset materialization for
|
|
300
|
+
row-oriented `list[dict]` outputs and column-oriented `dict[str, list]` outputs.
|
|
301
|
+
|
|
302
|
+
The NumPy benchmark includes pyarrow's direct CSV reader, Dataset/Scanner path,
|
|
303
|
+
streaming CSV reader, and Polars lazy scanning. The Dataset row is pyarrow's
|
|
304
|
+
closest API for parallel predicate/projection work, but it can require explicit
|
|
305
|
+
column types: otherwise pyarrow may infer a narrow integer type from early rows
|
|
306
|
+
and later fail on wider values. The benchmark pins obvious numeric columns such
|
|
307
|
+
as `price`, `year`, and `odometer` so the comparison measures throughput instead
|
|
308
|
+
of schema-inference failure handling.
|
|
309
|
+
|
|
310
|
+
This runs each workload in a fresh Python process and reports wall time,
|
|
311
|
+
throughput, and peak resident/working-set memory.
|
|
312
|
+
|
|
313
|
+
The benchmark helper searches `build/` and `out/` for a compatible built
|
|
314
|
+
`fastpycsv` extension and errors clearly if it is missing. Use the same Python
|
|
315
|
+
version that built `fastpycsv`; a `cp310` extension, for example, will not import
|
|
316
|
+
under Python 3.14.
|
|
317
|
+
|
|
318
|
+
The benchmark matrix compares stdlib `csv.reader`, lazy `fastpycsv.reader` rows with
|
|
319
|
+
strings, lazy `fastpycsv.reader` rows with `cast=True`, stdlib `csv.DictReader`, raw
|
|
320
|
+
`fastpycsv.read_numpy()` array export, and `pandas.DataFrame(fastpycsv.read_numpy(...))`.
|
|
321
|
+
It reports file path, size, rows, columns, elapsed seconds, MiB/s, and rows/s.
|
|
322
|
+
|
|
323
|
+
## Exploratory Summary
|
|
324
|
+
|
|
325
|
+
For a small dependency-free EDA pass over a CSV:
|
|
326
|
+
|
|
327
|
+
```powershell
|
|
328
|
+
python python/examples/eda_summary.py path/to/input.csv
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
The script streams lazy `fastpycsv` rows once and reports per-column mean, sample
|
|
332
|
+
standard deviation, null count, and top observed values. Histograms use a
|
|
333
|
+
bounded approximate heavy-hitter sketch by default to keep memory usage stable
|
|
334
|
+
on high-cardinality columns. Use `--top-n N` to change the report size,
|
|
335
|
+
`--top-capacity N` to change the per-column sketch size, `--exact-values` for
|
|
336
|
+
exact value counts, or `--no-header` for headerless files.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
fastpycsv/__init__.py,sha256=KXGyFcQFPW28TzYWcPXq43n9sFrIwOtkwLzFq-zcuUo,1819
|
|
2
|
+
fastpycsv/_format.py,sha256=iGb0F-dj29GiGjmY3NDBrsCUwxxgJ7EnLHGnWC1mIq8,1311
|
|
3
|
+
fastpycsv/_numpy.py,sha256=Y0M4pxvJtdY5n8zT7f-_gjnQ28FdAI5eH9jGZIXIEFc,2874
|
|
4
|
+
fastpycsv/_reader.py,sha256=bdg_fgWY4j_NykD384tNWemwv7DPPOfttDi_QBfpOtA,5264
|
|
5
|
+
fastpycsv/_writer.py,sha256=1bQ6-cvJe-NcL31W87J56vVh2rGX25EMEpHUQMumdpc,1211
|
|
6
|
+
fastpycsv/compat.py,sha256=a2kGFJzFiNScOKwXFZrV5iwbY-7dviYEuMBhAQY1nCM,123
|
|
7
|
+
fastpycsv/fastpycsv.cp313-win_amd64.pyd,sha256=PSBAfePOpO5YshSvxayy_TtNfjpS5ygejLEYRKsrUMQ,619008
|
|
8
|
+
fastpycsv-5.2.0.dist-info/METADATA,sha256=pfyAoo-2KiwE_X4VSmKHYSWBKrM4L1EdjVJFekzdqTU,13452
|
|
9
|
+
fastpycsv-5.2.0.dist-info/WHEEL,sha256=UZrbbE4r80xj7Ncfa6JoeTVe-77bdXLkKUA63V8pKWQ,106
|
|
10
|
+
fastpycsv-5.2.0.dist-info/licenses/LICENSE,sha256=SxqZ9a3mGockrO5T6dlEi7rehS2hmC5C6Afd3jhdMLE,1093
|
|
11
|
+
fastpycsv-5.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017-2026 Vincent La
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|