rapcsv 0.2.0__cp39-cp39-manylinux_2_28_aarch64.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 ADDED
@@ -0,0 +1,423 @@
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)
183
+
184
+
185
+ try:
186
+ from _rapcsv import (
187
+ AsyncDictReader,
188
+ AsyncDictWriter,
189
+ CSVError,
190
+ CSVFieldCountError,
191
+ Reader,
192
+ Writer,
193
+ ) # type: ignore[import-not-found]
194
+ except ImportError:
195
+ try:
196
+ from rapcsv._rapcsv import (
197
+ AsyncDictReader,
198
+ AsyncDictWriter,
199
+ CSVError,
200
+ CSVFieldCountError,
201
+ Reader,
202
+ Writer,
203
+ )
204
+ except ImportError as err:
205
+ raise ImportError(
206
+ "Could not import _rapcsv. Make sure rapcsv is built with maturin."
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
+
407
+
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
+ ]
rapcsv/_rapcsv.pyi ADDED
@@ -0,0 +1,453 @@
1
+ """Type stubs for _rapcsv Rust extension module.
2
+
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
12
+
13
+ class Reader:
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
+ ...
127
+
128
+ class Writer:
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
+ ...
rapcsv/py.typed ADDED
File without changes
@@ -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
+ [![PyPI version](https://img.shields.io/pypi/v/rapcsv.svg)](https://pypi.org/project/rapcsv/)
46
+ [![Downloads](https://pepy.tech/badge/rapcsv)](https://pepy.tech/project/rapcsv)
47
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
48
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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-39-aarch64-linux-gnu.so,sha256=6eippQvGf_fhWqL7lG8Yb04i4TriGNZiFzRvgPuTu0w,2367576
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=ZcJjA6hLAB2DEfZUkRevdkCSldHBsF3aJn53-Yy3CcU,108
7
+ rapcsv-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.11.5)
3
+ Root-Is-Purelib: false
4
+ Tag: cp39-cp39-manylinux_2_28_aarch64