rapcsv 0.0.2__cp38-cp38-manylinux_2_28_x86_64.whl → 0.2.0__cp38-cp38-manylinux_2_28_x86_64.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.
- rapcsv/__init__.py +415 -8
- rapcsv/_rapcsv.cpython-38-x86_64-linux-gnu.so +0 -0
- rapcsv/_rapcsv.pyi +448 -6
- rapcsv-0.2.0.dist-info/METADATA +194 -0
- rapcsv-0.2.0.dist-info/RECORD +7 -0
- rapcsv-0.0.2.dist-info/METADATA +0 -226
- rapcsv-0.0.2.dist-info/RECORD +0 -7
- {rapcsv-0.0.2.dist-info → rapcsv-0.2.0.dist-info}/WHEEL +0 -0
rapcsv/__init__.py
CHANGED
|
@@ -1,16 +1,423 @@
|
|
|
1
|
-
"""Streaming async CSV — no fake async, no GIL stalls.
|
|
1
|
+
"""Streaming async CSV — no fake async, no GIL stalls.
|
|
2
|
+
|
|
3
|
+
rapcsv provides true async CSV reading and writing for Python, backed by Rust and Tokio.
|
|
4
|
+
Unlike libraries that wrap blocking I/O in async syntax, rapcsv guarantees that all CSV
|
|
5
|
+
operations execute **outside the Python GIL**, ensuring event loops never stall under load.
|
|
6
|
+
|
|
7
|
+
Features
|
|
8
|
+
--------
|
|
9
|
+
- True async CSV reading and writing
|
|
10
|
+
- Streaming support for large files (incremental reading, no full file load)
|
|
11
|
+
- Context manager support (``async with``)
|
|
12
|
+
- aiocsv compatibility (AsyncReader/AsyncWriter aliases)
|
|
13
|
+
- CSV-specific exception types (CSVError, CSVFieldCountError)
|
|
14
|
+
- RFC 4180 compliant CSV parsing and writing
|
|
15
|
+
|
|
16
|
+
Example
|
|
17
|
+
-------
|
|
18
|
+
.. code-block:: python
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
from rapcsv import Reader, Writer
|
|
22
|
+
|
|
23
|
+
async def main():
|
|
24
|
+
async with Writer("output.csv") as writer:
|
|
25
|
+
await writer.write_row(["name", "age"])
|
|
26
|
+
await writer.write_row(["Alice", "30"])
|
|
27
|
+
|
|
28
|
+
async with Reader("output.csv") as reader:
|
|
29
|
+
row = await reader.read_row()
|
|
30
|
+
print(row) # ['name', 'age']
|
|
31
|
+
|
|
32
|
+
asyncio.run(main())
|
|
33
|
+
|
|
34
|
+
For more information, see: https://github.com/eddiethedean/rapcsv
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@runtime_checkable
|
|
41
|
+
class WithAsyncRead(Protocol):
|
|
42
|
+
"""Protocol for async file-like objects with read method.
|
|
43
|
+
|
|
44
|
+
This protocol defines the interface for async file-like objects that can be used
|
|
45
|
+
with rapcsv's Reader and AsyncDictReader classes. Objects implementing this
|
|
46
|
+
protocol must provide an async ``read()`` method.
|
|
47
|
+
|
|
48
|
+
Examples
|
|
49
|
+
--------
|
|
50
|
+
Objects from ``aiofiles`` and ``rapfiles`` implement this protocol:
|
|
51
|
+
|
|
52
|
+
.. code-block:: python
|
|
53
|
+
|
|
54
|
+
import aiofiles
|
|
55
|
+
from rapcsv import Reader
|
|
56
|
+
|
|
57
|
+
async with aiofiles.open("data.csv", mode="r") as f:
|
|
58
|
+
reader = Reader(f) # f implements WithAsyncRead
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
async def read(self, size: int) -> str:
|
|
62
|
+
"""Read up to size bytes/characters from the file.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
size: Number of bytes/characters to read.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
String containing the read data.
|
|
69
|
+
"""
|
|
70
|
+
...
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@runtime_checkable
|
|
74
|
+
class WithAsyncWrite(Protocol):
|
|
75
|
+
"""Protocol for async file-like objects with write method.
|
|
76
|
+
|
|
77
|
+
This protocol defines the interface for async file-like objects that can be used
|
|
78
|
+
with rapcsv's Writer and AsyncDictWriter classes. Objects implementing this
|
|
79
|
+
protocol must provide an async ``write()`` method.
|
|
80
|
+
|
|
81
|
+
Examples
|
|
82
|
+
--------
|
|
83
|
+
Objects from ``aiofiles`` and ``rapfiles`` implement this protocol:
|
|
84
|
+
|
|
85
|
+
.. code-block:: python
|
|
86
|
+
|
|
87
|
+
import aiofiles
|
|
88
|
+
from rapcsv import Writer
|
|
89
|
+
|
|
90
|
+
async with aiofiles.open("output.csv", mode="w") as f:
|
|
91
|
+
writer = Writer(f) # f implements WithAsyncWrite
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
async def write(self, data: str) -> None:
|
|
95
|
+
"""Write data to the file.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
data: String data to write to the file.
|
|
99
|
+
"""
|
|
100
|
+
...
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
# Internal helper function to call async file methods from any thread
|
|
104
|
+
# This schedules the call on the event loop using run_coroutine_threadsafe
|
|
105
|
+
def _call_file_method_threadsafe(file_handle, method_name, event_loop, *args):
|
|
106
|
+
"""Call an async file method from any thread by scheduling it on the event loop.
|
|
107
|
+
|
|
108
|
+
This is used by the Rust extension to call rapfiles methods from spawn_blocking threads.
|
|
109
|
+
The function schedules the async method call on the provided event loop using
|
|
110
|
+
``asyncio.run_coroutine_threadsafe()``.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
file_handle: The file handle object with async methods.
|
|
114
|
+
method_name: Name of the method to call (e.g., "write", "read").
|
|
115
|
+
event_loop: The event loop to schedule the call on.
|
|
116
|
+
*args: Arguments to pass to the method.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
The result of the async method call.
|
|
120
|
+
|
|
121
|
+
Note:
|
|
122
|
+
This is an internal helper function and should not be called directly
|
|
123
|
+
by user code.
|
|
124
|
+
"""
|
|
125
|
+
import asyncio
|
|
126
|
+
|
|
127
|
+
# Create a coroutine that calls the method
|
|
128
|
+
async def _call_method():
|
|
129
|
+
method = getattr(file_handle, method_name)
|
|
130
|
+
result = method(*args)
|
|
131
|
+
# If it returns a Future or coroutine, await it
|
|
132
|
+
if hasattr(result, "__await__"):
|
|
133
|
+
return await result
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
# Schedule the coroutine on the event loop
|
|
137
|
+
# This will execute on the event loop thread, not the current thread
|
|
138
|
+
future = asyncio.run_coroutine_threadsafe(_call_method(), event_loop)
|
|
139
|
+
return future.result()
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
# Internal helper function to wrap Futures into coroutines for run_coroutine_threadsafe
|
|
143
|
+
# This is used by the Rust extension to convert rapfiles Futures to coroutines
|
|
144
|
+
def _await_wrapper(fut):
|
|
145
|
+
"""Wrap a Future into a coroutine using types.coroutine.
|
|
146
|
+
|
|
147
|
+
This function is called from Rust code to convert asyncio.Future objects
|
|
148
|
+
(like those returned by rapfiles) into coroutines that can be used with
|
|
149
|
+
``asyncio.run_coroutine_threadsafe()``.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
fut: A Future or coroutine object to wrap.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
A coroutine object that can be awaited.
|
|
156
|
+
|
|
157
|
+
Note:
|
|
158
|
+
This function must work even when called from a thread without an event loop.
|
|
159
|
+
This is an internal helper function and should not be called directly
|
|
160
|
+
by user code.
|
|
161
|
+
"""
|
|
162
|
+
import inspect
|
|
163
|
+
import types
|
|
164
|
+
|
|
165
|
+
# Check if already a coroutine using inspect (doesn't need event loop)
|
|
166
|
+
if inspect.iscoroutine(fut):
|
|
167
|
+
return fut
|
|
168
|
+
|
|
169
|
+
# It's a Future - wrap it in a generator function, then use types.coroutine
|
|
170
|
+
# This approach doesn't require an event loop to be running
|
|
171
|
+
# The generator will be executed on the event loop via run_coroutine_threadsafe
|
|
172
|
+
def _gen_wrapper(fut):
|
|
173
|
+
# yield from will work when the coroutine is executed on the event loop
|
|
174
|
+
# This doesn't execute immediately - it just creates a generator
|
|
175
|
+
return (yield from fut)
|
|
176
|
+
|
|
177
|
+
# Wrap the generator function with types.coroutine to make it a coroutine function
|
|
178
|
+
coro_func = types.coroutine(_gen_wrapper)
|
|
179
|
+
|
|
180
|
+
# Call it to get a coroutine object (doesn't execute yet, no event loop needed)
|
|
181
|
+
# The coroutine will be executed later on the event loop
|
|
182
|
+
return coro_func(fut)
|
|
2
183
|
|
|
3
|
-
from typing import List
|
|
4
184
|
|
|
5
185
|
try:
|
|
6
|
-
from _rapcsv import
|
|
186
|
+
from _rapcsv import (
|
|
187
|
+
AsyncDictReader,
|
|
188
|
+
AsyncDictWriter,
|
|
189
|
+
CSVError,
|
|
190
|
+
CSVFieldCountError,
|
|
191
|
+
Reader,
|
|
192
|
+
Writer,
|
|
193
|
+
) # type: ignore[import-not-found]
|
|
7
194
|
except ImportError:
|
|
8
195
|
try:
|
|
9
|
-
from rapcsv._rapcsv import
|
|
10
|
-
|
|
196
|
+
from rapcsv._rapcsv import (
|
|
197
|
+
AsyncDictReader,
|
|
198
|
+
AsyncDictWriter,
|
|
199
|
+
CSVError,
|
|
200
|
+
CSVFieldCountError,
|
|
201
|
+
Reader,
|
|
202
|
+
Writer,
|
|
203
|
+
)
|
|
204
|
+
except ImportError as err:
|
|
11
205
|
raise ImportError(
|
|
12
206
|
"Could not import _rapcsv. Make sure rapcsv is built with maturin."
|
|
13
|
-
)
|
|
207
|
+
) from err
|
|
208
|
+
|
|
209
|
+
# API compatibility with aiocsv
|
|
210
|
+
# aiocsv uses AsyncReader and AsyncWriter as class names
|
|
211
|
+
AsyncReader = Reader
|
|
212
|
+
"""Alias for :class:`Reader` for aiocsv compatibility.
|
|
213
|
+
|
|
214
|
+
This alias allows drop-in replacement of aiocsv code.
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
.. code-block:: python
|
|
218
|
+
|
|
219
|
+
from rapcsv import AsyncReader # Instead of from aiocsv import AsyncReader
|
|
220
|
+
reader = AsyncReader("data.csv")
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
AsyncWriter = Writer
|
|
224
|
+
"""Alias for :class:`Writer` for aiocsv compatibility.
|
|
225
|
+
|
|
226
|
+
This alias allows drop-in replacement of aiocsv code.
|
|
227
|
+
|
|
228
|
+
Example:
|
|
229
|
+
.. code-block:: python
|
|
230
|
+
|
|
231
|
+
from rapcsv import AsyncWriter # Instead of from aiocsv import AsyncWriter
|
|
232
|
+
writer = AsyncWriter("output.csv")
|
|
233
|
+
"""
|
|
234
|
+
|
|
235
|
+
__version__: str = "0.2.0"
|
|
236
|
+
|
|
237
|
+
# Dialect presets for common CSV formats
|
|
238
|
+
EXCEL_DIALECT: Dict[str, Any] = {
|
|
239
|
+
"delimiter": ",",
|
|
240
|
+
"quotechar": '"',
|
|
241
|
+
"lineterminator": "\r\n",
|
|
242
|
+
"quoting": 1, # QUOTE_MINIMAL
|
|
243
|
+
"double_quote": True,
|
|
244
|
+
}
|
|
245
|
+
"""Excel-compatible CSV dialect preset.
|
|
246
|
+
|
|
247
|
+
This dialect matches Microsoft Excel's CSV format:
|
|
248
|
+
- Delimiter: comma (``,``)
|
|
249
|
+
- Quote character: double quote (``"``)
|
|
250
|
+
- Line terminator: CRLF (``\\r\\n``)
|
|
251
|
+
- Quoting: QUOTE_MINIMAL (1)
|
|
252
|
+
- Double quote: enabled
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
.. code-block:: python
|
|
256
|
+
|
|
257
|
+
from rapcsv import Writer, EXCEL_DIALECT
|
|
258
|
+
|
|
259
|
+
writer = Writer("output.csv", **EXCEL_DIALECT)
|
|
260
|
+
await writer.write_row(["col1", "col2"])
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
UNIX_DIALECT: Dict[str, Any] = {
|
|
264
|
+
"delimiter": ",",
|
|
265
|
+
"quotechar": '"',
|
|
266
|
+
"lineterminator": "\n",
|
|
267
|
+
"quoting": 1, # QUOTE_MINIMAL
|
|
268
|
+
"double_quote": True,
|
|
269
|
+
}
|
|
270
|
+
"""Unix-compatible CSV dialect preset.
|
|
271
|
+
|
|
272
|
+
This dialect uses Unix-style line endings:
|
|
273
|
+
- Delimiter: comma (``,``)
|
|
274
|
+
- Quote character: double quote (``"``)
|
|
275
|
+
- Line terminator: LF (``\\n``)
|
|
276
|
+
- Quoting: QUOTE_MINIMAL (1)
|
|
277
|
+
- Double quote: enabled
|
|
278
|
+
|
|
279
|
+
Example:
|
|
280
|
+
.. code-block:: python
|
|
281
|
+
|
|
282
|
+
from rapcsv import Writer, UNIX_DIALECT
|
|
283
|
+
|
|
284
|
+
writer = Writer("output.csv", **UNIX_DIALECT)
|
|
285
|
+
await writer.write_row(["col1", "col2"])
|
|
286
|
+
"""
|
|
287
|
+
|
|
288
|
+
RFC4180_DIALECT: Dict[str, Any] = {
|
|
289
|
+
"delimiter": ",",
|
|
290
|
+
"quotechar": '"',
|
|
291
|
+
"lineterminator": "\r\n",
|
|
292
|
+
"quoting": 1, # QUOTE_MINIMAL
|
|
293
|
+
"double_quote": True,
|
|
294
|
+
}
|
|
295
|
+
"""RFC 4180 compliant CSV dialect preset.
|
|
296
|
+
|
|
297
|
+
This dialect strictly follows RFC 4180 specification:
|
|
298
|
+
- Delimiter: comma (``,``)
|
|
299
|
+
- Quote character: double quote (``"``)
|
|
300
|
+
- Line terminator: CRLF (``\\r\\n``)
|
|
301
|
+
- Quoting: QUOTE_MINIMAL (1)
|
|
302
|
+
- Double quote: enabled
|
|
303
|
+
|
|
304
|
+
Example:
|
|
305
|
+
.. code-block:: python
|
|
306
|
+
|
|
307
|
+
from rapcsv import Writer, RFC4180_DIALECT
|
|
308
|
+
|
|
309
|
+
writer = Writer("output.csv", **RFC4180_DIALECT)
|
|
310
|
+
await writer.write_row(["col1", "col2"])
|
|
311
|
+
"""
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def convert_types(row: List[str], converters: Optional[Dict[int, Any]] = None) -> List[Any]:
|
|
315
|
+
"""Convert row fields to appropriate types.
|
|
316
|
+
|
|
317
|
+
Automatically infers types (int, float, bool) or applies custom converter functions
|
|
318
|
+
per column. If a converter fails, the original string value is preserved.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
row: List of string values from CSV row.
|
|
322
|
+
converters: Optional dictionary mapping column index (int) to converter
|
|
323
|
+
function (callable). If provided, only specified columns are converted
|
|
324
|
+
using the provided functions. Other columns use automatic inference.
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
List with converted values. Types may be int, float, bool, or str.
|
|
328
|
+
|
|
329
|
+
Examples
|
|
330
|
+
--------
|
|
331
|
+
.. code-block:: python
|
|
332
|
+
|
|
333
|
+
from rapcsv import Reader, convert_types
|
|
334
|
+
|
|
335
|
+
reader = Reader("data.csv")
|
|
336
|
+
row = await reader.read_row() # ['Alice', '30', 'NYC']
|
|
337
|
+
|
|
338
|
+
# Automatic type conversion
|
|
339
|
+
converted = convert_types(row)
|
|
340
|
+
# ['Alice', 30, 'NYC'] # age converted to int
|
|
341
|
+
|
|
342
|
+
# Per-column converters
|
|
343
|
+
converters = {1: int, 2: str.upper}
|
|
344
|
+
converted = convert_types(row, converters)
|
|
345
|
+
# ['Alice', 30, 'NYC'] # column 1 to int, column 2 to uppercase
|
|
346
|
+
"""
|
|
347
|
+
if converters:
|
|
348
|
+
result = []
|
|
349
|
+
for i, value in enumerate(row):
|
|
350
|
+
if i in converters:
|
|
351
|
+
try:
|
|
352
|
+
result.append(converters[i](value))
|
|
353
|
+
except (ValueError, TypeError):
|
|
354
|
+
result.append(value) # Keep original if conversion fails
|
|
355
|
+
else:
|
|
356
|
+
result.append(_auto_convert(value))
|
|
357
|
+
return result
|
|
358
|
+
else:
|
|
359
|
+
return [_auto_convert(v) for v in row]
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def _auto_convert(value: str) -> Any:
|
|
363
|
+
"""Automatically convert a string value to appropriate type.
|
|
364
|
+
|
|
365
|
+
Attempts type conversion in the following order:
|
|
366
|
+
1. Integer (if no decimal point or scientific notation)
|
|
367
|
+
2. Float
|
|
368
|
+
3. Boolean (true/false, yes/no, 1/0, on/off)
|
|
369
|
+
4. Original string (if no conversion succeeds)
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
value: String value to convert.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Converted value (int, float, bool, or str).
|
|
376
|
+
|
|
377
|
+
Note:
|
|
378
|
+
This is an internal helper function used by ``convert_types()``.
|
|
379
|
+
Empty strings are returned as-is.
|
|
380
|
+
"""
|
|
381
|
+
if not value or value.strip() == "":
|
|
382
|
+
return value
|
|
383
|
+
|
|
384
|
+
# Try int
|
|
385
|
+
try:
|
|
386
|
+
if "." not in value and "e" not in value.lower():
|
|
387
|
+
return int(value)
|
|
388
|
+
except ValueError:
|
|
389
|
+
pass
|
|
390
|
+
|
|
391
|
+
# Try float
|
|
392
|
+
try:
|
|
393
|
+
return float(value)
|
|
394
|
+
except ValueError:
|
|
395
|
+
pass
|
|
396
|
+
|
|
397
|
+
# Try bool
|
|
398
|
+
lower = value.lower().strip()
|
|
399
|
+
if lower in ("true", "yes", "1", "on"):
|
|
400
|
+
return True
|
|
401
|
+
if lower in ("false", "no", "0", "off", ""):
|
|
402
|
+
return False
|
|
403
|
+
|
|
404
|
+
# Return as string
|
|
405
|
+
return value
|
|
406
|
+
|
|
14
407
|
|
|
15
|
-
|
|
16
|
-
|
|
408
|
+
__all__: List[str] = [
|
|
409
|
+
"Reader",
|
|
410
|
+
"Writer",
|
|
411
|
+
"AsyncDictReader",
|
|
412
|
+
"AsyncDictWriter",
|
|
413
|
+
"AsyncReader", # aiocsv compatibility
|
|
414
|
+
"AsyncWriter", # aiocsv compatibility
|
|
415
|
+
"CSVError",
|
|
416
|
+
"CSVFieldCountError",
|
|
417
|
+
"WithAsyncRead", # Protocol for type checking
|
|
418
|
+
"WithAsyncWrite", # Protocol for type checking
|
|
419
|
+
"EXCEL_DIALECT", # Dialect preset
|
|
420
|
+
"UNIX_DIALECT", # Dialect preset
|
|
421
|
+
"RFC4180_DIALECT", # Dialect preset
|
|
422
|
+
"convert_types", # Type conversion utility
|
|
423
|
+
]
|
|
Binary file
|
rapcsv/_rapcsv.pyi
CHANGED
|
@@ -1,11 +1,453 @@
|
|
|
1
|
-
"""Type stubs for _rapcsv Rust extension module.
|
|
1
|
+
"""Type stubs for _rapcsv Rust extension module.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
This module provides type information for the Rust-compiled extension module.
|
|
4
|
+
All classes and methods are implemented in Rust and exposed to Python via PyO3.
|
|
5
|
+
|
|
6
|
+
Note:
|
|
7
|
+
This is a type stub file (``.pyi``) used for static type checking.
|
|
8
|
+
The actual implementation is in the compiled Rust extension module.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Any, Coroutine, Dict, List, Optional
|
|
4
12
|
|
|
5
13
|
class Reader:
|
|
6
|
-
|
|
7
|
-
|
|
14
|
+
"""Async CSV reader for streaming CSV files.
|
|
15
|
+
|
|
16
|
+
Provides true async CSV reading with GIL-independent operations.
|
|
17
|
+
Files are streamed incrementally without loading the entire file into memory.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
path: Path to CSV file or async file-like object (WithAsyncRead).
|
|
21
|
+
delimiter: Field delimiter character (default: ',').
|
|
22
|
+
quotechar: Quote character (default: '"').
|
|
23
|
+
escapechar: Escape character (default: None).
|
|
24
|
+
quoting: Quoting style: 0=QUOTE_NONE, 1=QUOTE_MINIMAL, 2=QUOTE_ALL,
|
|
25
|
+
3=QUOTE_NONNUMERIC, 4=QUOTE_NOTNULL, 6=QUOTE_STRINGS (default: 1).
|
|
26
|
+
lineterminator: Line terminator string (default: '\\r\\n').
|
|
27
|
+
skipinitialspace: Skip whitespace after delimiter (default: False).
|
|
28
|
+
strict: Strict mode for field count validation (default: False).
|
|
29
|
+
double_quote: Handle doubled quotes (default: True).
|
|
30
|
+
read_size: Buffer size for reading chunks in bytes (default: 8192).
|
|
31
|
+
field_size_limit: Maximum field size in bytes (default: None).
|
|
32
|
+
|
|
33
|
+
Examples
|
|
34
|
+
--------
|
|
35
|
+
.. code-block:: python
|
|
36
|
+
|
|
37
|
+
from rapcsv import Reader
|
|
38
|
+
|
|
39
|
+
# Read from file path
|
|
40
|
+
reader = Reader("data.csv")
|
|
41
|
+
row = await reader.read_row()
|
|
42
|
+
|
|
43
|
+
# Read from async file handle
|
|
44
|
+
import aiofiles
|
|
45
|
+
async with aiofiles.open("data.csv", mode="r") as f:
|
|
46
|
+
reader = Reader(f)
|
|
47
|
+
row = await reader.read_row()
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
def __init__(
|
|
51
|
+
self,
|
|
52
|
+
path: str,
|
|
53
|
+
delimiter: Optional[str] = None,
|
|
54
|
+
quotechar: Optional[str] = None,
|
|
55
|
+
escapechar: Optional[str] = None,
|
|
56
|
+
quoting: Optional[int] = None,
|
|
57
|
+
lineterminator: Optional[str] = None,
|
|
58
|
+
skipinitialspace: Optional[bool] = None,
|
|
59
|
+
strict: Optional[bool] = None,
|
|
60
|
+
double_quote: Optional[bool] = None,
|
|
61
|
+
read_size: Optional[int] = None,
|
|
62
|
+
field_size_limit: Optional[int] = None,
|
|
63
|
+
) -> None: ...
|
|
64
|
+
def read_row(self) -> Coroutine[Any, Any, List[str]]:
|
|
65
|
+
"""Read the next row from the CSV file.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
List of string values for the row, or empty list if EOF.
|
|
69
|
+
|
|
70
|
+
Raises:
|
|
71
|
+
IOError: If the file cannot be read.
|
|
72
|
+
CSVError: If the CSV file is malformed or cannot be parsed.
|
|
73
|
+
|
|
74
|
+
Note:
|
|
75
|
+
The Reader maintains position state across calls, reading sequentially.
|
|
76
|
+
Files are streamed incrementally without loading the entire file.
|
|
77
|
+
"""
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
def read_rows(self, n: int) -> Coroutine[Any, Any, List[List[str]]]:
|
|
81
|
+
"""Read multiple rows at once.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
n: Number of rows to read.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
List of rows, where each row is a list of string values.
|
|
88
|
+
"""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
def skip_rows(self, n: int) -> Coroutine[Any, Any, None]:
|
|
92
|
+
"""Skip multiple rows efficiently without parsing.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
n: Number of rows to skip.
|
|
96
|
+
"""
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def line_num(self) -> int:
|
|
101
|
+
"""Current line number (1-based).
|
|
102
|
+
|
|
103
|
+
For multi-line records, this counts actual lines, not just records.
|
|
104
|
+
"""
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
def __aiter__(self) -> Reader:
|
|
108
|
+
"""Async iterator protocol - returns self."""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
def __anext__(self) -> Coroutine[Any, Any, List[str]]:
|
|
112
|
+
"""Async iterator next - returns next row or raises StopAsyncIteration."""
|
|
113
|
+
...
|
|
114
|
+
|
|
115
|
+
def __aenter__(self) -> Coroutine[Any, Any, Reader]:
|
|
116
|
+
"""Async context manager entry."""
|
|
117
|
+
...
|
|
118
|
+
|
|
119
|
+
def __aexit__(
|
|
120
|
+
self,
|
|
121
|
+
exc_type: Optional[Any],
|
|
122
|
+
exc_val: Optional[Any],
|
|
123
|
+
exc_tb: Optional[Any],
|
|
124
|
+
) -> Coroutine[Any, Any, None]:
|
|
125
|
+
"""Async context manager exit - closes the file handle."""
|
|
126
|
+
...
|
|
8
127
|
|
|
9
128
|
class Writer:
|
|
10
|
-
|
|
11
|
-
|
|
129
|
+
"""Async CSV writer for streaming CSV files.
|
|
130
|
+
|
|
131
|
+
Provides true async CSV writing with GIL-independent operations.
|
|
132
|
+
The Writer reuses file handles across multiple write operations for efficiency.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
path: Path to CSV file or async file-like object (WithAsyncWrite).
|
|
136
|
+
delimiter: Field delimiter character (default: ',').
|
|
137
|
+
quotechar: Quote character (default: '"').
|
|
138
|
+
escapechar: Escape character (default: None).
|
|
139
|
+
quoting: Quoting style: 0=QUOTE_NONE, 1=QUOTE_MINIMAL, 2=QUOTE_ALL,
|
|
140
|
+
3=QUOTE_NONNUMERIC, 4=QUOTE_NOTNULL, 6=QUOTE_STRINGS (default: 1).
|
|
141
|
+
lineterminator: Line terminator string (default: '\\r\\n').
|
|
142
|
+
double_quote: Handle doubled quotes (default: True).
|
|
143
|
+
write_size: Buffer size for writing chunks in bytes (default: 8192).
|
|
144
|
+
|
|
145
|
+
Examples
|
|
146
|
+
--------
|
|
147
|
+
.. code-block:: python
|
|
148
|
+
|
|
149
|
+
from rapcsv import Writer
|
|
150
|
+
|
|
151
|
+
# Write to file path
|
|
152
|
+
writer = Writer("output.csv")
|
|
153
|
+
await writer.write_row(["name", "age"])
|
|
154
|
+
|
|
155
|
+
# Write to async file handle
|
|
156
|
+
import aiofiles
|
|
157
|
+
async with aiofiles.open("output.csv", mode="w") as f:
|
|
158
|
+
writer = Writer(f)
|
|
159
|
+
await writer.write_row(["name", "age"])
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
path: str,
|
|
165
|
+
delimiter: Optional[str] = None,
|
|
166
|
+
quotechar: Optional[str] = None,
|
|
167
|
+
escapechar: Optional[str] = None,
|
|
168
|
+
quoting: Optional[int] = None,
|
|
169
|
+
lineterminator: Optional[str] = None,
|
|
170
|
+
double_quote: Optional[bool] = None,
|
|
171
|
+
write_size: Optional[int] = None,
|
|
172
|
+
) -> None: ...
|
|
173
|
+
def write_row(self, row: List[str]) -> Coroutine[Any, Any, None]:
|
|
174
|
+
"""Write a row to the CSV file.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
row: List of string values to write as a CSV row.
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
IOError: If the file cannot be written.
|
|
181
|
+
|
|
182
|
+
Note:
|
|
183
|
+
The Writer reuses the file handle across multiple calls for efficiency.
|
|
184
|
+
Proper RFC 4180 compliant CSV escaping and quoting is applied automatically.
|
|
185
|
+
"""
|
|
186
|
+
...
|
|
187
|
+
|
|
188
|
+
def writerows(self, rows: List[List[str]]) -> Coroutine[Any, Any, None]:
|
|
189
|
+
"""Write multiple rows to the CSV file efficiently.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
rows: List of rows, where each row is a list of string values.
|
|
193
|
+
"""
|
|
194
|
+
...
|
|
195
|
+
|
|
196
|
+
def close(self) -> Coroutine[Any, Any, None]:
|
|
197
|
+
"""Explicitly close the file handle and flush any pending writes."""
|
|
198
|
+
...
|
|
199
|
+
|
|
200
|
+
def __aenter__(self) -> Coroutine[Any, Any, Writer]:
|
|
201
|
+
"""Async context manager entry."""
|
|
202
|
+
...
|
|
203
|
+
|
|
204
|
+
def __aexit__(
|
|
205
|
+
self,
|
|
206
|
+
exc_type: Optional[Any],
|
|
207
|
+
exc_val: Optional[Any],
|
|
208
|
+
exc_tb: Optional[Any],
|
|
209
|
+
) -> Coroutine[Any, Any, None]:
|
|
210
|
+
"""Async context manager exit - closes the file handle and flushes writes."""
|
|
211
|
+
...
|
|
212
|
+
|
|
213
|
+
class AsyncDictReader:
|
|
214
|
+
"""Async dictionary-based CSV reader.
|
|
215
|
+
|
|
216
|
+
Returns rows as dictionaries mapping field names to values.
|
|
217
|
+
Supports automatic header detection and header manipulation.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
path: Path to CSV file or async file-like object (WithAsyncRead).
|
|
221
|
+
fieldnames: Optional list of field names. If None, first row is used as header.
|
|
222
|
+
restkey: Key name for extra values when row has more fields than fieldnames
|
|
223
|
+
(default: None).
|
|
224
|
+
restval: Default value for missing fields when row has fewer fields
|
|
225
|
+
(default: None).
|
|
226
|
+
delimiter: Field delimiter character (default: ',').
|
|
227
|
+
quotechar: Quote character (default: '"').
|
|
228
|
+
escapechar: Escape character (default: None).
|
|
229
|
+
quoting: Quoting style (default: 1, QUOTE_MINIMAL).
|
|
230
|
+
lineterminator: Line terminator string (default: '\\r\\n').
|
|
231
|
+
skipinitialspace: Skip whitespace after delimiter (default: False).
|
|
232
|
+
strict: Strict mode for field count validation (default: False).
|
|
233
|
+
double_quote: Handle doubled quotes (default: True).
|
|
234
|
+
read_size: Buffer size for reading chunks in bytes (default: 8192).
|
|
235
|
+
|
|
236
|
+
Examples
|
|
237
|
+
--------
|
|
238
|
+
.. code-block:: python
|
|
239
|
+
|
|
240
|
+
from rapcsv import AsyncDictReader
|
|
241
|
+
|
|
242
|
+
# Automatic header detection
|
|
243
|
+
reader = AsyncDictReader("data.csv")
|
|
244
|
+
row = await reader.read_row() # {'name': 'Alice', 'age': '30'}
|
|
245
|
+
|
|
246
|
+
# Explicit fieldnames
|
|
247
|
+
reader = AsyncDictReader("data.csv", fieldnames=["name", "age", "city"])
|
|
248
|
+
row = await reader.read_row()
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
def __init__(
|
|
252
|
+
self,
|
|
253
|
+
path: str,
|
|
254
|
+
fieldnames: Optional[List[str]] = None,
|
|
255
|
+
restkey: Optional[str] = None,
|
|
256
|
+
restval: Optional[str] = None,
|
|
257
|
+
delimiter: Optional[str] = None,
|
|
258
|
+
quotechar: Optional[str] = None,
|
|
259
|
+
escapechar: Optional[str] = None,
|
|
260
|
+
quoting: Optional[int] = None,
|
|
261
|
+
lineterminator: Optional[str] = None,
|
|
262
|
+
skipinitialspace: Optional[bool] = None,
|
|
263
|
+
strict: Optional[bool] = None,
|
|
264
|
+
double_quote: Optional[bool] = None,
|
|
265
|
+
read_size: Optional[int] = None,
|
|
266
|
+
) -> None: ...
|
|
267
|
+
def read_row(self) -> Coroutine[Any, Any, Dict[str, str]]:
|
|
268
|
+
"""Read the next row as a dictionary.
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
Dictionary mapping field names to values, or empty dict if EOF.
|
|
272
|
+
"""
|
|
273
|
+
...
|
|
274
|
+
|
|
275
|
+
def get_fieldnames(self) -> Coroutine[Any, Any, Optional[List[str]]]:
|
|
276
|
+
"""Get fieldnames (lazy loaded).
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
List of field names, or None if fieldnames haven't been loaded yet.
|
|
280
|
+
"""
|
|
281
|
+
...
|
|
282
|
+
|
|
283
|
+
def add_field(self, field_name: str) -> Coroutine[Any, Any, None]:
|
|
284
|
+
"""Add a field to the fieldnames list.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
field_name: Name of the field to add.
|
|
288
|
+
"""
|
|
289
|
+
...
|
|
290
|
+
|
|
291
|
+
def remove_field(self, field_name: str) -> Coroutine[Any, Any, None]:
|
|
292
|
+
"""Remove a field from the fieldnames list.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
field_name: Name of the field to remove.
|
|
296
|
+
"""
|
|
297
|
+
...
|
|
298
|
+
|
|
299
|
+
def rename_field(self, old_name: str, new_name: str) -> Coroutine[Any, Any, None]:
|
|
300
|
+
"""Rename a field in the fieldnames list.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
old_name: Current field name.
|
|
304
|
+
new_name: New field name.
|
|
305
|
+
|
|
306
|
+
Raises:
|
|
307
|
+
ValueError: If old_name is not found in fieldnames.
|
|
308
|
+
RuntimeError: If fieldnames haven't been loaded yet.
|
|
309
|
+
"""
|
|
310
|
+
...
|
|
311
|
+
|
|
312
|
+
@property
|
|
313
|
+
def fieldnames(self) -> Optional[List[str]]:
|
|
314
|
+
"""Property for accessing fieldnames.
|
|
315
|
+
|
|
316
|
+
May be None until first row is read. For reliable access, use
|
|
317
|
+
``get_fieldnames()`` coroutine instead.
|
|
318
|
+
"""
|
|
319
|
+
...
|
|
320
|
+
|
|
321
|
+
def __aiter__(self) -> AsyncDictReader:
|
|
322
|
+
"""Async iterator protocol - returns self."""
|
|
323
|
+
...
|
|
324
|
+
|
|
325
|
+
def __anext__(self) -> Coroutine[Any, Any, Dict[str, str]]:
|
|
326
|
+
"""Async iterator next - returns next row as dict or raises StopAsyncIteration."""
|
|
327
|
+
...
|
|
328
|
+
|
|
329
|
+
class AsyncDictWriter:
|
|
330
|
+
"""Async dictionary-based CSV writer.
|
|
331
|
+
|
|
332
|
+
Writes rows from dictionaries mapping field names to values.
|
|
333
|
+
Requires explicit fieldnames to define CSV structure.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
path: Path to CSV file or async file-like object (WithAsyncWrite).
|
|
337
|
+
fieldnames: List of column names defining CSV structure (required).
|
|
338
|
+
restval: Default value for missing keys in dictionary (default: '').
|
|
339
|
+
extrasaction: Action for extra keys: 'raise' (default) or 'ignore'.
|
|
340
|
+
delimiter: Field delimiter character (default: ',').
|
|
341
|
+
quotechar: Quote character (default: '"').
|
|
342
|
+
escapechar: Escape character (default: None).
|
|
343
|
+
quoting: Quoting style (default: 1, QUOTE_MINIMAL).
|
|
344
|
+
lineterminator: Line terminator string (default: '\\r\\n').
|
|
345
|
+
double_quote: Handle doubled quotes (default: True).
|
|
346
|
+
write_size: Buffer size for writing chunks in bytes (default: 8192).
|
|
347
|
+
|
|
348
|
+
Examples
|
|
349
|
+
--------
|
|
350
|
+
.. code-block:: python
|
|
351
|
+
|
|
352
|
+
from rapcsv import AsyncDictWriter
|
|
353
|
+
|
|
354
|
+
writer = AsyncDictWriter("output.csv", fieldnames=["name", "age", "city"])
|
|
355
|
+
await writer.writeheader()
|
|
356
|
+
await writer.writerow({"name": "Alice", "age": "30", "city": "NYC"})
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
def __init__(
|
|
360
|
+
self,
|
|
361
|
+
path: str,
|
|
362
|
+
fieldnames: List[str],
|
|
363
|
+
restval: Optional[str] = None,
|
|
364
|
+
extrasaction: Optional[str] = None,
|
|
365
|
+
delimiter: Optional[str] = None,
|
|
366
|
+
quotechar: Optional[str] = None,
|
|
367
|
+
escapechar: Optional[str] = None,
|
|
368
|
+
quoting: Optional[int] = None,
|
|
369
|
+
lineterminator: Optional[str] = None,
|
|
370
|
+
double_quote: Optional[bool] = None,
|
|
371
|
+
write_size: Optional[int] = None,
|
|
372
|
+
) -> None: ...
|
|
373
|
+
def writeheader(self) -> Coroutine[Any, Any, None]:
|
|
374
|
+
"""Write header row with fieldnames."""
|
|
375
|
+
...
|
|
376
|
+
|
|
377
|
+
def writerow(self, row: Dict[str, str]) -> Coroutine[Any, Any, None]:
|
|
378
|
+
"""Write a single dictionary row.
|
|
379
|
+
|
|
380
|
+
Args:
|
|
381
|
+
row: Dictionary mapping field names to values.
|
|
382
|
+
|
|
383
|
+
Raises:
|
|
384
|
+
ValueError: If extrasaction='raise' and row contains extra keys
|
|
385
|
+
not in fieldnames.
|
|
386
|
+
"""
|
|
387
|
+
...
|
|
388
|
+
|
|
389
|
+
def writerows(self, rows: List[Dict[str, str]]) -> Coroutine[Any, Any, None]:
|
|
390
|
+
"""Write multiple dictionary rows efficiently.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
rows: List of dictionaries to write.
|
|
394
|
+
"""
|
|
395
|
+
...
|
|
396
|
+
|
|
397
|
+
def close(self) -> Coroutine[Any, Any, None]:
|
|
398
|
+
"""Explicitly close the file handle and flush any pending writes."""
|
|
399
|
+
...
|
|
400
|
+
|
|
401
|
+
def __aenter__(self) -> Coroutine[Any, Any, AsyncDictWriter]:
|
|
402
|
+
"""Async context manager entry."""
|
|
403
|
+
...
|
|
404
|
+
|
|
405
|
+
def __aexit__(
|
|
406
|
+
self,
|
|
407
|
+
exc_type: Optional[Any],
|
|
408
|
+
exc_val: Optional[Any],
|
|
409
|
+
exc_tb: Optional[Any],
|
|
410
|
+
) -> Coroutine[Any, Any, None]:
|
|
411
|
+
"""Async context manager exit - closes the file handle and flushes writes."""
|
|
412
|
+
...
|
|
413
|
+
|
|
414
|
+
class CSVError(Exception):
|
|
415
|
+
"""Raised when a CSV parsing error occurs.
|
|
416
|
+
|
|
417
|
+
This exception is raised when the CSV file is malformed or cannot be parsed.
|
|
418
|
+
|
|
419
|
+
Examples
|
|
420
|
+
--------
|
|
421
|
+
.. code-block:: python
|
|
422
|
+
|
|
423
|
+
from rapcsv import Reader, CSVError
|
|
424
|
+
|
|
425
|
+
try:
|
|
426
|
+
reader = Reader("malformed.csv")
|
|
427
|
+
row = await reader.read_row()
|
|
428
|
+
except CSVError as e:
|
|
429
|
+
print(f"CSV parsing error: {e}")
|
|
430
|
+
"""
|
|
431
|
+
|
|
432
|
+
...
|
|
433
|
+
|
|
434
|
+
class CSVFieldCountError(Exception):
|
|
435
|
+
"""Raised when there's a mismatch in the number of fields between rows.
|
|
436
|
+
|
|
437
|
+
This exception is raised when strict mode is enabled and rows have
|
|
438
|
+
inconsistent field counts.
|
|
439
|
+
|
|
440
|
+
Examples
|
|
441
|
+
--------
|
|
442
|
+
.. code-block:: python
|
|
443
|
+
|
|
444
|
+
from rapcsv import Reader, CSVFieldCountError
|
|
445
|
+
|
|
446
|
+
try:
|
|
447
|
+
reader = Reader("data.csv", strict=True)
|
|
448
|
+
row = await reader.read_row()
|
|
449
|
+
except CSVFieldCountError as e:
|
|
450
|
+
print(f"Field count mismatch: {e}")
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
...
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: rapcsv
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Intended Audience :: Developers
|
|
6
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Requires-Dist: ruff>=0.1.0 ; extra == 'dev'
|
|
16
|
+
Requires-Dist: sphinx>=7.0 ; extra == 'docs'
|
|
17
|
+
Requires-Dist: sphinx-rtd-theme>=1.3.0 ; extra == 'docs'
|
|
18
|
+
Requires-Dist: myst-parser>=2.0.0 ; extra == 'docs'
|
|
19
|
+
Requires-Dist: ruff>=0.1.0 ; extra == 'lint'
|
|
20
|
+
Requires-Dist: pytest>=7.0 ; extra == 'test'
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.23.8 ; extra == 'test'
|
|
22
|
+
Requires-Dist: pytest-timeout>=2.0 ; extra == 'test'
|
|
23
|
+
Requires-Dist: aiocsv>=0.3.0 ; extra == 'test'
|
|
24
|
+
Requires-Dist: aiofiles>=23.0 ; extra == 'test'
|
|
25
|
+
Requires-Dist: rapfiles>=0.2.1 ; extra == 'test'
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Provides-Extra: docs
|
|
28
|
+
Provides-Extra: lint
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Summary: Streaming async CSV — no fake async, no GIL stalls.
|
|
31
|
+
Keywords: async,csv,streaming,async-io
|
|
32
|
+
Author: RAP Project
|
|
33
|
+
License: MIT
|
|
34
|
+
Requires-Python: >=3.8
|
|
35
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
36
|
+
Project-URL: Documentation, https://rapcsv.readthedocs.io/
|
|
37
|
+
Project-URL: Homepage, https://github.com/eddiethedean/rapcsv
|
|
38
|
+
Project-URL: Issues, https://github.com/eddiethedean/rapcsv/issues
|
|
39
|
+
Project-URL: Repository, https://github.com/eddiethedean/rapcsv
|
|
40
|
+
|
|
41
|
+
# rapcsv
|
|
42
|
+
|
|
43
|
+
**Streaming async CSV — no fake async, no GIL stalls.**
|
|
44
|
+
|
|
45
|
+
[](https://pypi.org/project/rapcsv/)
|
|
46
|
+
[](https://pepy.tech/project/rapcsv)
|
|
47
|
+
[](https://www.python.org/downloads/)
|
|
48
|
+
[](https://opensource.org/licenses/MIT)
|
|
49
|
+
|
|
50
|
+
## Overview
|
|
51
|
+
|
|
52
|
+
`rapcsv` provides true async CSV reading and writing for Python, backed by Rust and Tokio. Unlike libraries that wrap blocking I/O in `async` syntax, `rapcsv` guarantees that all CSV operations execute **outside the Python GIL**, ensuring event loops never stall under load, even when processing large files.
|
|
53
|
+
|
|
54
|
+
**Roadmap Goal**: Achieve drop-in replacement compatibility with `aiocsv`, enabling seamless migration with true async performance. See [docs/ROADMAP.md](https://github.com/eddiethedean/rapcsv/blob/main/docs/ROADMAP.md) for details.
|
|
55
|
+
|
|
56
|
+
## Why `rap*`?
|
|
57
|
+
|
|
58
|
+
Packages prefixed with **`rap`** stand for **Real Async Python**. Unlike many libraries that merely wrap blocking I/O in `async` syntax, `rap*` packages guarantee that all I/O work is executed **outside the Python GIL** using native runtimes (primarily Rust). This means event loops are never stalled by hidden thread pools, blocking syscalls, or cooperative yielding tricks. If a `rap*` API is `async`, it is *structurally non-blocking by design*, not by convention. The `rap` prefix is a contract: measurable concurrency, real parallelism, and verifiable async behavior under load.
|
|
59
|
+
|
|
60
|
+
See the [rap-manifesto](https://github.com/eddiethedean/rap-manifesto) for philosophy and guarantees.
|
|
61
|
+
|
|
62
|
+
## Features
|
|
63
|
+
|
|
64
|
+
- ✅ **True async** CSV reading and writing
|
|
65
|
+
- ✅ **Streaming support** for large files
|
|
66
|
+
- ✅ **Native Rust-backed** execution (Tokio)
|
|
67
|
+
- ✅ **Zero Python thread pools**
|
|
68
|
+
- ✅ **Event-loop-safe** concurrency under load
|
|
69
|
+
- ✅ **GIL-independent** I/O operations
|
|
70
|
+
- ✅ **Verified** by Fake Async Detector
|
|
71
|
+
- ✅ **aiofiles compatibility** (drop-in replacement)
|
|
72
|
+
|
|
73
|
+
### Feature Categories
|
|
74
|
+
|
|
75
|
+
- **Core Operations** - Read and write CSV files with true async I/O
|
|
76
|
+
- **File Handles** - Support for file paths and async file-like objects (`aiofiles`, `rapfiles`)
|
|
77
|
+
- **Context Managers** - Async context manager support (`async with`)
|
|
78
|
+
- **Dict Readers/Writers** - Dictionary-based CSV operations (`AsyncDictReader`, `AsyncDictWriter`)
|
|
79
|
+
- **Streaming** - Incremental reading without loading entire files into memory
|
|
80
|
+
- **Error Handling** - CSV-specific exceptions (`CSVError`, `CSVFieldCountError`)
|
|
81
|
+
- **Compatibility** - aiocsv compatibility aliases for easy migration
|
|
82
|
+
|
|
83
|
+
## Requirements
|
|
84
|
+
|
|
85
|
+
- Python 3.8+ (including Python 3.13 and 3.14)
|
|
86
|
+
- Rust 1.70+ (for building from source)
|
|
87
|
+
|
|
88
|
+
## Installation
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install rapcsv
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
For detailed installation instructions, including building from source and development setup, see [Installation Guide](https://github.com/eddiethedean/rapcsv/blob/main/docs/INSTALLATION.md).
|
|
95
|
+
|
|
96
|
+
## Documentation
|
|
97
|
+
|
|
98
|
+
Comprehensive documentation is available in the `docs/` directory:
|
|
99
|
+
|
|
100
|
+
### User Guides
|
|
101
|
+
|
|
102
|
+
- **[Usage Guide](https://github.com/eddiethedean/rapcsv/blob/main/docs/USAGE_GUIDE.md)** - Comprehensive examples and usage patterns
|
|
103
|
+
- **[API Reference](https://github.com/eddiethedean/rapcsv/blob/main/docs/API_REFERENCE.md)** - Complete API documentation
|
|
104
|
+
- **[Installation Guide](https://github.com/eddiethedean/rapcsv/blob/main/docs/INSTALLATION.md)** - Installation and setup instructions
|
|
105
|
+
|
|
106
|
+
### Project Documentation
|
|
107
|
+
|
|
108
|
+
- **[Status](https://github.com/eddiethedean/rapcsv/blob/main/docs/STATUS.md)** - Current development status and feature completion
|
|
109
|
+
- **[Roadmap](https://github.com/eddiethedean/rapcsv/blob/main/docs/ROADMAP.md)** - Detailed development plans and feature roadmap
|
|
110
|
+
- **[Changelog](https://github.com/eddiethedean/rapcsv/blob/main/CHANGELOG.md)** - Version history and changes
|
|
111
|
+
- **[Bugs and Improvements](https://github.com/eddiethedean/rapcsv/blob/main/BUGS_AND_IMPROVEMENTS.md)** - Known issues and limitations tracker
|
|
112
|
+
- **[Testing Guide](https://github.com/eddiethedean/rapcsv/blob/main/docs/README_TESTING.md)** - Local development setup instructions
|
|
113
|
+
- **[Release Checklist](https://github.com/eddiethedean/rapcsv/blob/main/docs/RELEASE_CHECKLIST.md)** - Release process and validation
|
|
114
|
+
- **[Security](https://github.com/eddiethedean/rapcsv/blob/main/SECURITY.md)** - Security policy and vulnerability reporting
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Quick Start
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import asyncio
|
|
122
|
+
from rapcsv import Reader, Writer
|
|
123
|
+
|
|
124
|
+
async def main():
|
|
125
|
+
# Write CSV file
|
|
126
|
+
async with Writer("output.csv") as writer:
|
|
127
|
+
await writer.write_row(["name", "age", "city"])
|
|
128
|
+
await writer.write_row(["Alice", "30", "New York"])
|
|
129
|
+
|
|
130
|
+
# Read CSV file
|
|
131
|
+
async with Reader("output.csv") as reader:
|
|
132
|
+
row = await reader.read_row()
|
|
133
|
+
print(row) # Output: ['name', 'age', 'city']
|
|
134
|
+
|
|
135
|
+
asyncio.run(main())
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
For comprehensive usage examples and patterns, see [Usage Guide](https://github.com/eddiethedean/rapcsv/blob/main/docs/USAGE_GUIDE.md).
|
|
139
|
+
|
|
140
|
+
## API Reference
|
|
141
|
+
|
|
142
|
+
For complete API documentation, see [API Reference](https://github.com/eddiethedean/rapcsv/blob/main/docs/API_REFERENCE.md).
|
|
143
|
+
|
|
144
|
+
**Main Classes:**
|
|
145
|
+
- `Reader` - Async CSV reader
|
|
146
|
+
- `Writer` - Async CSV writer
|
|
147
|
+
- `AsyncDictReader` - Dictionary-based CSV reader
|
|
148
|
+
- `AsyncDictWriter` - Dictionary-based CSV writer
|
|
149
|
+
|
|
150
|
+
**Exception Types:**
|
|
151
|
+
- `CSVError` - CSV parsing errors
|
|
152
|
+
- `CSVFieldCountError` - Field count mismatches
|
|
153
|
+
|
|
154
|
+
## Testing
|
|
155
|
+
|
|
156
|
+
`rapcsv` includes comprehensive test coverage with tests adapted from the [aiocsv test suite](https://github.com/MKuranowski/aiocsv/tree/master/tests) to validate compatibility.
|
|
157
|
+
|
|
158
|
+
For detailed testing instructions, see [Testing Guide](https://github.com/eddiethedean/rapcsv/blob/main/docs/README_TESTING.md).
|
|
159
|
+
|
|
160
|
+
## Status
|
|
161
|
+
|
|
162
|
+
**Current Version**: v0.2.0 - Phase 2 Complete ✅
|
|
163
|
+
|
|
164
|
+
For detailed status information, feature completion, and known limitations, see [Status](https://github.com/eddiethedean/rapcsv/blob/main/docs/STATUS.md).
|
|
165
|
+
|
|
166
|
+
## Benchmarks
|
|
167
|
+
|
|
168
|
+
This package passes the [Fake Async Detector](https://github.com/eddiethedean/rap-bench). Benchmarks are available in the [rap-bench](https://github.com/eddiethedean/rap-bench) repository.
|
|
169
|
+
|
|
170
|
+
Run the detector yourself:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
pip install rap-bench
|
|
174
|
+
rap-bench detect rapcsv
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Related Projects
|
|
178
|
+
|
|
179
|
+
- [rap-manifesto](https://github.com/eddiethedean/rap-manifesto) - Philosophy and guarantees
|
|
180
|
+
- [rap-bench](https://github.com/eddiethedean/rap-bench) - Fake Async Detector CLI
|
|
181
|
+
- [rapfiles](https://github.com/eddiethedean/rapfiles) - True async filesystem I/O
|
|
182
|
+
- [rapsqlite](https://github.com/eddiethedean/rapsqlite) - True async SQLite
|
|
183
|
+
|
|
184
|
+
For detailed release notes, see [CHANGELOG.md](https://github.com/eddiethedean/rapcsv/blob/main/CHANGELOG.md).
|
|
185
|
+
|
|
186
|
+
## Contributing
|
|
187
|
+
|
|
188
|
+
Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/eddiethedean/rapcsv).
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|
|
193
|
+
|
|
194
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
rapcsv/__init__.py,sha256=ZaHtTLNRLYpbW5prkcdeRMjYDeMLTlCl5cEOIbG20tI,12705
|
|
2
|
+
rapcsv/_rapcsv.cpython-38-x86_64-linux-gnu.so,sha256=XofNnYKlRiTQBh2QX7YqwlYpDVHGIXu-GuI4GQS9OTM,2284648
|
|
3
|
+
rapcsv/_rapcsv.pyi,sha256=SfV4FqMQMOgWFcgU78Tg5QFsdhuvlMW98vFRbFvo130,14942
|
|
4
|
+
rapcsv/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
rapcsv-0.2.0.dist-info/METADATA,sha256=nW0xo5FjlVIE2NEGSo6O0vtFaXhPVaUBC8H20yrIVxc,8660
|
|
6
|
+
rapcsv-0.2.0.dist-info/WHEEL,sha256=3N5EpdPBJMYIW1be1E4XQax3kkFUj46lEw0Ks8eIJi4,107
|
|
7
|
+
rapcsv-0.2.0.dist-info/RECORD,,
|
rapcsv-0.0.2.dist-info/METADATA
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: rapcsv
|
|
3
|
-
Version: 0.0.2
|
|
4
|
-
Classifier: Development Status :: 3 - Alpha
|
|
5
|
-
Classifier: Intended Audience :: Developers
|
|
6
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
7
|
-
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
-
Summary: Streaming async CSV — no fake async, no GIL stalls.
|
|
14
|
-
Keywords: async,csv,streaming,async-io
|
|
15
|
-
Author: RAP Project
|
|
16
|
-
License: MIT
|
|
17
|
-
Requires-Python: >=3.8
|
|
18
|
-
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
19
|
-
|
|
20
|
-
# rapcsv
|
|
21
|
-
|
|
22
|
-
**Streaming async CSV — no fake async, no GIL stalls.**
|
|
23
|
-
|
|
24
|
-
[](https://pypi.org/project/rapcsv/)
|
|
25
|
-
[](https://pepy.tech/project/rapcsv)
|
|
26
|
-
[](https://www.python.org/downloads/)
|
|
27
|
-
[](https://opensource.org/licenses/MIT)
|
|
28
|
-
|
|
29
|
-
## Overview
|
|
30
|
-
|
|
31
|
-
`rapcsv` provides true async CSV reading and writing for Python, backed by Rust and Tokio. Unlike libraries that wrap blocking I/O in `async` syntax, `rapcsv` guarantees that all CSV operations execute **outside the Python GIL**, ensuring event loops never stall under load, even when processing large files.
|
|
32
|
-
|
|
33
|
-
**Roadmap Goal**: Achieve drop-in replacement compatibility with `aiocsv`, enabling seamless migration with true async performance. See [ROADMAP.md](https://github.com/eddiethedean/rapcsv/blob/main/ROADMAP.md) for details.
|
|
34
|
-
|
|
35
|
-
## Why `rap*`?
|
|
36
|
-
|
|
37
|
-
Packages prefixed with **`rap`** stand for **Real Async Python**. Unlike many libraries that merely wrap blocking I/O in `async` syntax, `rap*` packages guarantee that all I/O work is executed **outside the Python GIL** using native runtimes (primarily Rust). This means event loops are never stalled by hidden thread pools, blocking syscalls, or cooperative yielding tricks. If a `rap*` API is `async`, it is *structurally non-blocking by design*, not by convention. The `rap` prefix is a contract: measurable concurrency, real parallelism, and verifiable async behavior under load.
|
|
38
|
-
|
|
39
|
-
See the [rap-manifesto](https://github.com/eddiethedean/rap-manifesto) for philosophy and guarantees.
|
|
40
|
-
|
|
41
|
-
## Features
|
|
42
|
-
|
|
43
|
-
- ✅ **True async** CSV reading and writing
|
|
44
|
-
- ✅ **Streaming support** for large files
|
|
45
|
-
- ✅ **Native Rust-backed** execution (Tokio)
|
|
46
|
-
- ✅ **Zero Python thread pools**
|
|
47
|
-
- ✅ **Event-loop-safe** concurrency under load
|
|
48
|
-
- ✅ **GIL-independent** I/O operations
|
|
49
|
-
- ✅ **Verified** by Fake Async Detector
|
|
50
|
-
|
|
51
|
-
## Requirements
|
|
52
|
-
|
|
53
|
-
- Python 3.8+
|
|
54
|
-
- Rust 1.70+ (for building from source)
|
|
55
|
-
|
|
56
|
-
## Installation
|
|
57
|
-
|
|
58
|
-
```bash
|
|
59
|
-
pip install rapcsv
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Building from Source
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
git clone https://github.com/eddiethedean/rapcsv.git
|
|
66
|
-
cd rapcsv
|
|
67
|
-
pip install maturin
|
|
68
|
-
maturin develop
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
---
|
|
72
|
-
|
|
73
|
-
## Usage
|
|
74
|
-
|
|
75
|
-
```python
|
|
76
|
-
import asyncio
|
|
77
|
-
from rapcsv import Reader, Writer
|
|
78
|
-
|
|
79
|
-
async def main():
|
|
80
|
-
# Write CSV file (one row per Writer instance for MVP)
|
|
81
|
-
writer = Writer("output.csv")
|
|
82
|
-
await writer.write_row(["name", "age", "city"])
|
|
83
|
-
|
|
84
|
-
# Read CSV file (reads first row)
|
|
85
|
-
reader = Reader("output.csv")
|
|
86
|
-
row = await reader.read_row()
|
|
87
|
-
print(row) # Output: ['name', 'age', 'city']
|
|
88
|
-
|
|
89
|
-
asyncio.run(main())
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Writing Multiple Rows
|
|
93
|
-
|
|
94
|
-
```python
|
|
95
|
-
import asyncio
|
|
96
|
-
from rapcsv import Writer
|
|
97
|
-
|
|
98
|
-
async def main():
|
|
99
|
-
# Write multiple rows with a single Writer instance (file handle reused)
|
|
100
|
-
writer = Writer("output.csv")
|
|
101
|
-
rows = [
|
|
102
|
-
["name", "age", "city"],
|
|
103
|
-
["Alice", "30", "New York"],
|
|
104
|
-
["Bob", "25", "London"],
|
|
105
|
-
]
|
|
106
|
-
|
|
107
|
-
for row in rows:
|
|
108
|
-
await writer.write_row(row)
|
|
109
|
-
|
|
110
|
-
# Verify file contents
|
|
111
|
-
with open("output.csv") as f:
|
|
112
|
-
print(f.read())
|
|
113
|
-
|
|
114
|
-
asyncio.run(main())
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
**Note**: The Writer reuses the file handle across multiple `write_row()` calls for efficient writing. The Reader maintains position state across `read_row()` calls.
|
|
118
|
-
|
|
119
|
-
## API Reference
|
|
120
|
-
|
|
121
|
-
### `Reader(path: str)`
|
|
122
|
-
|
|
123
|
-
Create a new async CSV reader.
|
|
124
|
-
|
|
125
|
-
**Parameters:**
|
|
126
|
-
- `path` (str): Path to the CSV file to read
|
|
127
|
-
|
|
128
|
-
**Example:**
|
|
129
|
-
```python
|
|
130
|
-
reader = Reader("data.csv")
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
### `Reader.read_row() -> List[str]`
|
|
134
|
-
|
|
135
|
-
Read the next row from the CSV file.
|
|
136
|
-
|
|
137
|
-
**Returns:**
|
|
138
|
-
- `List[str]`: A list of string values for the row, or an empty list if EOF
|
|
139
|
-
|
|
140
|
-
**Raises:**
|
|
141
|
-
- `IOError`: If the file cannot be read or parsed
|
|
142
|
-
|
|
143
|
-
**Note**: The Reader maintains position state across `read_row()` calls, reading sequentially through the file.
|
|
144
|
-
|
|
145
|
-
### `Writer(path: str)`
|
|
146
|
-
|
|
147
|
-
Create a new async CSV writer.
|
|
148
|
-
|
|
149
|
-
**Parameters:**
|
|
150
|
-
- `path` (str): Path to the CSV file to write
|
|
151
|
-
|
|
152
|
-
**Example:**
|
|
153
|
-
```python
|
|
154
|
-
writer = Writer("output.csv")
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
### `Writer.write_row(row: List[str]) -> None`
|
|
158
|
-
|
|
159
|
-
Write a row to the CSV file.
|
|
160
|
-
|
|
161
|
-
**Parameters:**
|
|
162
|
-
- `row` (List[str]): A list of string values to write as a CSV row
|
|
163
|
-
|
|
164
|
-
**Raises:**
|
|
165
|
-
- `IOError`: If the file cannot be written
|
|
166
|
-
|
|
167
|
-
**Note**: The Writer reuses the file handle across multiple `write_row()` calls for efficient writing. Proper RFC 4180 compliant CSV escaping and quoting is applied automatically.
|
|
168
|
-
|
|
169
|
-
## Benchmarks
|
|
170
|
-
|
|
171
|
-
This package passes the [Fake Async Detector](https://github.com/eddiethedean/rap-bench). Benchmarks are available in the [rap-bench](https://github.com/eddiethedean/rap-bench) repository.
|
|
172
|
-
|
|
173
|
-
Run the detector yourself:
|
|
174
|
-
|
|
175
|
-
```bash
|
|
176
|
-
pip install rap-bench
|
|
177
|
-
rap-bench detect rapcsv
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## Roadmap
|
|
181
|
-
|
|
182
|
-
See [ROADMAP.md](https://github.com/eddiethedean/rapcsv/blob/main/ROADMAP.md) for detailed development plans. Key goals include:
|
|
183
|
-
- Drop-in replacement for `aiocsv` (Phase 1)
|
|
184
|
-
- Full streaming support for large files
|
|
185
|
-
- Comprehensive CSV dialect support
|
|
186
|
-
- Zero-copy optimizations
|
|
187
|
-
|
|
188
|
-
## Related Projects
|
|
189
|
-
|
|
190
|
-
- [rap-manifesto](https://github.com/eddiethedean/rap-manifesto) - Philosophy and guarantees
|
|
191
|
-
- [rap-bench](https://github.com/eddiethedean/rap-bench) - Fake Async Detector CLI
|
|
192
|
-
- [rapfiles](https://github.com/eddiethedean/rapfiles) - True async filesystem I/O
|
|
193
|
-
- [rapsqlite](https://github.com/eddiethedean/rapsqlite) - True async SQLite
|
|
194
|
-
|
|
195
|
-
## Limitations (v0.0.2)
|
|
196
|
-
|
|
197
|
-
**Current limitations:**
|
|
198
|
-
- Reader still reads entire file into memory on each call (streaming improvements planned)
|
|
199
|
-
- No advanced CSV dialect support (delimiters, quote characters, line terminators)
|
|
200
|
-
- No header detection or manipulation
|
|
201
|
-
- Not yet a drop-in replacement for `aiocsv` (goal for Phase 1)
|
|
202
|
-
- Not designed for synchronous use cases
|
|
203
|
-
|
|
204
|
-
**Recent improvements (v0.0.2):**
|
|
205
|
-
- ✅ Security fixes: Upgraded dependencies (pyo3 0.27, pyo3-async-runtimes 0.27), fixed CSV injection vulnerability
|
|
206
|
-
- ✅ Position tracking: Reader now maintains position state across `read_row()` calls
|
|
207
|
-
- ✅ File handle reuse: Writer reuses file handle across multiple `write_row()` calls
|
|
208
|
-
- ✅ CSV escaping: Implemented RFC 4180 compliant CSV escaping and quoting
|
|
209
|
-
- ✅ Input validation: Added path validation (non-empty, no null bytes)
|
|
210
|
-
- ✅ Improved error handling: Enhanced error messages with file path context
|
|
211
|
-
- ✅ Type stubs: Added `.pyi` type stubs for better IDE support and type checking
|
|
212
|
-
|
|
213
|
-
**Roadmap**: See [ROADMAP.md](https://github.com/eddiethedean/rapcsv/blob/main/ROADMAP.md) for planned improvements. Our goal is to achieve drop-in replacement compatibility with `aiocsv` while providing true async performance with GIL-independent I/O.
|
|
214
|
-
|
|
215
|
-
## Contributing
|
|
216
|
-
|
|
217
|
-
Contributions are welcome! Please see our [contributing guidelines](https://github.com/eddiethedean/rapcsv/blob/main/CONTRIBUTING.md) (coming soon).
|
|
218
|
-
|
|
219
|
-
## License
|
|
220
|
-
|
|
221
|
-
MIT
|
|
222
|
-
|
|
223
|
-
## Changelog
|
|
224
|
-
|
|
225
|
-
See [CHANGELOG.md](https://github.com/eddiethedean/rapcsv/blob/main/CHANGELOG.md) (coming soon) for version history.
|
|
226
|
-
|
rapcsv-0.0.2.dist-info/RECORD
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
rapcsv/__init__.py,sha256=sMhUUCdC2oVTKMic67GXxx2bs2nyKAvy5jp41aXlKEE,454
|
|
2
|
-
rapcsv/_rapcsv.cpython-38-x86_64-linux-gnu.so,sha256=OXkIqEexG4kGesbeMO8o61S-8as-NEGoOY5bglF10ac,1086888
|
|
3
|
-
rapcsv/_rapcsv.pyi,sha256=kHxUy6naJjUfEBGrM6p6FuOLIyTlJ0Fm2W8IlcYQsF0,353
|
|
4
|
-
rapcsv/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
rapcsv-0.0.2.dist-info/METADATA,sha256=gc2lMrQADF-WHhZJptugq70WcKhYb0s4U_iqmLhX6lg,7726
|
|
6
|
-
rapcsv-0.0.2.dist-info/WHEEL,sha256=3N5EpdPBJMYIW1be1E4XQax3kkFUj46lEw0Ks8eIJi4,107
|
|
7
|
-
rapcsv-0.0.2.dist-info/RECORD,,
|
|
File without changes
|