pybcsv 1.4.1__tar.gz → 1.4.3__tar.gz
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.
- {pybcsv-1.4.1 → pybcsv-1.4.3}/CMakeLists.txt +3 -4
- {pybcsv-1.4.1 → pybcsv-1.4.3}/PKG-INFO +1 -3
- {pybcsv-1.4.1 → pybcsv-1.4.3}/README.md +0 -2
- {pybcsv-1.4.1 → pybcsv-1.4.3}/benchmarks/run_pybcsv_benchmarks.py +2 -2
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/bcsv_c_api.h +41 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/std_charconv_compat.h +18 -4
- {pybcsv-1.4.1 → pybcsv-1.4.3}/pybcsv/__init__.py +0 -3
- {pybcsv-1.4.1 → pybcsv-1.4.3}/pybcsv/_version.py +3 -3
- {pybcsv-1.4.1 → pybcsv-1.4.3}/pybcsv/bindings.cpp +135 -139
- {pybcsv-1.4.1 → pybcsv-1.4.3}/pybcsv/pandas_utils.py +0 -6
- pybcsv-1.4.1/tests/run_all_tests.py +0 -233
- pybcsv-1.4.1/tests/run_tests.py +0 -92
- {pybcsv-1.4.1 → pybcsv-1.4.3}/BUILD_NOTES.md +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/LICENSE +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/MANIFEST.in +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/benchmarks/README.md +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/examples/README.md +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/examples/advanced_usage.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/examples/basic_usage.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/examples/pandas_integration.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/examples/performance_benchmark.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/bcsv.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/bcsv.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/bitset.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/bitset.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/byte_buffer.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/checksum.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_file/file_codec_concept.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_file/file_codec_dispatch.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_file/file_codec_packet001.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_file/file_codec_packet_lz4_001.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_file/file_codec_packet_lz4_batch001.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_file/file_codec_stream001.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_file/file_codec_stream_lz4_001.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_row/row_codec_delta002.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_row/row_codec_delta002.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_row/row_codec_dispatch.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_row/row_codec_flat001.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_row/row_codec_flat001.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_row/row_codec_zoh001.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/codec_row/row_codec_zoh001.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/column_name_index.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/column_name_index.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/csv_reader.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/csv_reader.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/csv_writer.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/csv_writer.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/definitions.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/file_footer.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/file_header.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/file_header.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/layout.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/layout.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/layout_guard.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/lz4_block.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/lz4_stream.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/packet_header.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/reader.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/reader.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/reader_concept.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/row.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/row.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/row_visitors.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler_ast.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler_compiler.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler_parser.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler_tokenizer.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler_types.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler_vm.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler_vm.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/sampler/sampler_window.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/vle.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/writer.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/writer.hpp +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/bcsv/writer_concept.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/lz4.c +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/lz4.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/lz4file.c +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/lz4file.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/lz4frame.c +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/lz4frame.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/lz4frame_static.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/lz4hc.c +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/lz4hc.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/xxhash.c +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/lz4-1.10.0/xxhash.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/xxHash-0.8.3/xxhash.c +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/include/xxHash-0.8.3/xxhash.h +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/pybcsv/__version__.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/pybcsv/polars_utils.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/pybcsv/py.typed +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/pyproject.toml +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/sync_headers.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/README.md +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/conftest.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_basic_functionality.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_count_rows.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_csv_writer_reader.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_data_types.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_interoperability.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_minimal_string.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_performance_edge_cases.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_arrow.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_codecs.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_columnar.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_core.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_direct_access.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_errors.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_pandas.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_performance.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_polars.py +0 -0
- {pybcsv-1.4.1 → pybcsv-1.4.3}/tests/test_pybcsv_sampler.py +0 -0
|
@@ -119,7 +119,6 @@ if(MSVC)
|
|
|
119
119
|
/O2
|
|
120
120
|
/std:c++20
|
|
121
121
|
)
|
|
122
|
-
target_compile_definitions(_bcsv PRIVATE BCSV_HAS_BATCH_CODEC=1)
|
|
123
122
|
elseif(APPLE)
|
|
124
123
|
# Apple Clang ships LLVM 18-era libc++ where jthread/stop_token are
|
|
125
124
|
# still experimental (guarded by _LIBCPP_HAS_NO_EXPERIMENTAL_STOP_TOKEN).
|
|
@@ -129,15 +128,15 @@ elseif(APPLE)
|
|
|
129
128
|
-fexperimental-library
|
|
130
129
|
-mmacosx-version-min=13.3
|
|
131
130
|
)
|
|
132
|
-
target_compile_definitions(_bcsv PRIVATE BCSV_HAS_BATCH_CODEC=1)
|
|
133
131
|
else()
|
|
134
|
-
# Linux: full C++20 with
|
|
132
|
+
# Linux: full C++20 with pthreads
|
|
135
133
|
target_compile_options(_bcsv PRIVATE
|
|
136
134
|
-O3 -Wall -Wextra
|
|
137
135
|
)
|
|
138
|
-
target_compile_definitions(_bcsv PRIVATE BCSV_HAS_BATCH_CODEC=1)
|
|
139
136
|
target_link_libraries(_bcsv PRIVATE pthread)
|
|
140
137
|
endif()
|
|
141
138
|
|
|
139
|
+
target_compile_definitions(_bcsv PRIVATE BCSV_HAS_BATCH_CODEC=1)
|
|
140
|
+
|
|
142
141
|
# ── Install ─────────────────────────────────────────────────────────────
|
|
143
142
|
install(TARGETS _bcsv LIBRARY DESTINATION pybcsv)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pybcsv
|
|
3
|
-
Version: 1.4.
|
|
3
|
+
Version: 1.4.3
|
|
4
4
|
Summary: High-performance Python bindings for the BCSV (Binary CSV) library with pandas integration
|
|
5
5
|
Keywords: csv,binary,data-science,compression,performance,pandas,numpy,big-data,data-processing
|
|
6
6
|
Author-Email: Tobias Weber <weber.tobias.md@gmail.com>
|
|
@@ -318,8 +318,6 @@ pybcsv.to_csv(bcsv_file, csv_file)
|
|
|
318
318
|
pybcsv.read_columns(filename) -> dict[str, np.ndarray | list[str]]
|
|
319
319
|
pybcsv.write_columns(filename, columns, col_order, col_types,
|
|
320
320
|
row_codec="delta", compression_level=1)
|
|
321
|
-
pybcsv.read_to_dataframe(filename, columns=None) -> pd.DataFrame
|
|
322
|
-
|
|
323
321
|
# Type utilities
|
|
324
322
|
pybcsv.type_to_string(column_type) -> str
|
|
325
323
|
```
|
|
@@ -272,8 +272,6 @@ pybcsv.to_csv(bcsv_file, csv_file)
|
|
|
272
272
|
pybcsv.read_columns(filename) -> dict[str, np.ndarray | list[str]]
|
|
273
273
|
pybcsv.write_columns(filename, columns, col_order, col_types,
|
|
274
274
|
row_codec="delta", compression_level=1)
|
|
275
|
-
pybcsv.read_to_dataframe(filename, columns=None) -> pd.DataFrame
|
|
276
|
-
|
|
277
275
|
# Type utilities
|
|
278
276
|
pybcsv.type_to_string(column_type) -> str
|
|
279
277
|
```
|
|
@@ -291,9 +291,9 @@ def write_dataframe_mode(file_path: Path, columns_spec, rows: list[list], row_co
|
|
|
291
291
|
|
|
292
292
|
|
|
293
293
|
def read_dataframe_mode(file_path: Path) -> tuple[float, int]:
|
|
294
|
-
"""Read using
|
|
294
|
+
"""Read using pybcsv.read_dataframe (Arrow fast-path)."""
|
|
295
295
|
start = time.perf_counter()
|
|
296
|
-
df = pybcsv.
|
|
296
|
+
df = pybcsv.read_dataframe(str(file_path))
|
|
297
297
|
elapsed_ms = (time.perf_counter() - start) * 1000.0
|
|
298
298
|
return elapsed_ms, len(df)
|
|
299
299
|
|
|
@@ -339,6 +339,47 @@ size_t bcsv_sampler_source_row_pos (const_bcsv_sampler_t sampler);
|
|
|
339
339
|
// Last error/compile-error message (empty if no error)
|
|
340
340
|
const char* bcsv_sampler_error_msg (const_bcsv_sampler_t sampler);
|
|
341
341
|
|
|
342
|
+
// ============================================================================
|
|
343
|
+
// Columnar Bulk I/O API
|
|
344
|
+
// ============================================================================
|
|
345
|
+
// Read up to max_rows rows from an opened reader into column-oriented buffers.
|
|
346
|
+
// bufs is an array of num_cols pointers. For numeric columns, each pointer must
|
|
347
|
+
// point to a pre-allocated buffer of element type matching the column type.
|
|
348
|
+
// For string columns, the pointer is ignored (strings are stored internally
|
|
349
|
+
// and retrieved via bcsv_reader_column_string).
|
|
350
|
+
// Returns the number of rows actually read (may be less than max_rows at EOF).
|
|
351
|
+
size_t bcsv_reader_read_columns (bcsv_reader_t reader, void** bufs,
|
|
352
|
+
size_t num_cols, size_t max_rows);
|
|
353
|
+
|
|
354
|
+
// Retrieve a string from the internal buffer after bcsv_reader_read_columns.
|
|
355
|
+
// col is the column index, row is the row index (0-based within the batch).
|
|
356
|
+
// Returns empty string if indices are out of range.
|
|
357
|
+
const char* bcsv_reader_column_string (bcsv_reader_t reader, size_t col, size_t row);
|
|
358
|
+
|
|
359
|
+
// Number of strings stored for a given column after bcsv_reader_read_columns.
|
|
360
|
+
size_t bcsv_reader_column_string_count(bcsv_reader_t reader, size_t col);
|
|
361
|
+
|
|
362
|
+
// Pack all strings for a column into a contiguous null-terminated buffer.
|
|
363
|
+
// Returns total bytes needed (including null terminators).
|
|
364
|
+
// If out_buf is NULL or buf_size is 0, returns required size without writing.
|
|
365
|
+
size_t bcsv_reader_column_strings_packed(bcsv_reader_t reader, size_t col,
|
|
366
|
+
char* out_buf, size_t buf_size);
|
|
367
|
+
|
|
368
|
+
// Write num_rows rows from column-oriented buffers through an opened writer.
|
|
369
|
+
// bufs is an array of num_cols pointers. For numeric columns, each pointer
|
|
370
|
+
// must point to a buffer of element type matching the column type.
|
|
371
|
+
// For string columns, bufs[c] must be a const char** (array of C string
|
|
372
|
+
// pointers, one per row). Returns true on success.
|
|
373
|
+
bool bcsv_writer_write_columns (bcsv_writer_t writer, const void** bufs,
|
|
374
|
+
size_t num_cols, size_t num_rows);
|
|
375
|
+
|
|
376
|
+
// Return the compact data size in bytes for a single row (sum of fixed-size
|
|
377
|
+
// column widths — excludes strings). Useful for buffer pre-allocation.
|
|
378
|
+
size_t bcsv_layout_row_data_size (const_bcsv_layout_t layout);
|
|
379
|
+
|
|
380
|
+
// Retrieve file flags as reported by the reader (only valid after open).
|
|
381
|
+
int bcsv_reader_file_flags (const_bcsv_reader_t reader);
|
|
382
|
+
|
|
342
383
|
#ifdef __cplusplus
|
|
343
384
|
}
|
|
344
385
|
#endif
|
|
@@ -113,19 +113,33 @@ namespace bcsv::compat {
|
|
|
113
113
|
return std::to_chars(first, last, value);
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
/// float fallback via snprintf.
|
|
116
|
+
/// float fallback via snprintf — shortest round-trip representation.
|
|
117
117
|
inline std::to_chars_result to_chars(char* first, char* last, float value) {
|
|
118
118
|
auto bufSize = static_cast<size_t>(last - first);
|
|
119
|
-
|
|
119
|
+
// Try digits10 first (6 digits); if round-trip fails, use max_digits10 (9).
|
|
120
|
+
int n = std::snprintf(first, bufSize, "%.6g", value);
|
|
121
|
+
if (n >= 0 && static_cast<size_t>(n) < bufSize) {
|
|
122
|
+
float check = std::strtof(first, nullptr);
|
|
123
|
+
if (check != value) {
|
|
124
|
+
n = std::snprintf(first, bufSize, "%.9g", value);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
120
127
|
if (n < 0 || static_cast<size_t>(n) >= bufSize)
|
|
121
128
|
return {last, std::errc::value_too_large};
|
|
122
129
|
return {first + n, std::errc{}};
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
/// double fallback via snprintf.
|
|
132
|
+
/// double fallback via snprintf — shortest round-trip representation.
|
|
126
133
|
inline std::to_chars_result to_chars(char* first, char* last, double value) {
|
|
127
134
|
auto bufSize = static_cast<size_t>(last - first);
|
|
128
|
-
|
|
135
|
+
// Try digits10 first (15 digits); if round-trip fails, use max_digits10 (17).
|
|
136
|
+
int n = std::snprintf(first, bufSize, "%.15g", value);
|
|
137
|
+
if (n >= 0 && static_cast<size_t>(n) < bufSize) {
|
|
138
|
+
double check = std::strtod(first, nullptr);
|
|
139
|
+
if (check != value) {
|
|
140
|
+
n = std::snprintf(first, bufSize, "%.17g", value);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
129
143
|
if (n < 0 || static_cast<size_t>(n) >= bufSize)
|
|
130
144
|
return {last, std::errc::value_too_large};
|
|
131
145
|
return {first + n, std::errc{}};
|
|
@@ -39,8 +39,6 @@ except ImportError:
|
|
|
39
39
|
raise ImportError(_STUB_MSG)
|
|
40
40
|
def write_columns(*args, **kwargs):
|
|
41
41
|
raise ImportError(_STUB_MSG)
|
|
42
|
-
def read_to_dataframe(*args, **kwargs):
|
|
43
|
-
raise ImportError(_STUB_MSG)
|
|
44
42
|
def type_to_string(*args, **kwargs):
|
|
45
43
|
raise ImportError(_STUB_MSG)
|
|
46
44
|
def read_to_arrow(*args, **kwargs):
|
|
@@ -101,7 +99,6 @@ __all__ = [
|
|
|
101
99
|
# Columnar I/O
|
|
102
100
|
"read_columns",
|
|
103
101
|
"write_columns",
|
|
104
|
-
"read_to_dataframe",
|
|
105
102
|
# Arrow interop
|
|
106
103
|
"read_to_arrow",
|
|
107
104
|
"write_from_arrow",
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.4.
|
|
32
|
-
__version_tuple__ = version_tuple = (1, 4,
|
|
31
|
+
__version__ = version = '1.4.3'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 4, 3)
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'gb295a0343'
|
|
@@ -261,6 +261,40 @@ namespace {
|
|
|
261
261
|
}
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// ── Shared columnar write loop ────────────────────────────────────
|
|
265
|
+
// Used by both write_columns and write_from_arrow.
|
|
266
|
+
|
|
267
|
+
inline void write_columnar_core(
|
|
268
|
+
const bcsv::Layout& layout,
|
|
269
|
+
const std::string& row_codec,
|
|
270
|
+
const std::string& filename,
|
|
271
|
+
size_t num_rows, size_t num_cols,
|
|
272
|
+
const std::vector<const void*>& bufs,
|
|
273
|
+
const std::vector<std::vector<std::string>>& string_cols,
|
|
274
|
+
const std::vector<bool>& is_string,
|
|
275
|
+
const std::vector<bcsv::ColumnType>& col_types,
|
|
276
|
+
size_t compression_level,
|
|
277
|
+
bcsv::FileFlags flags) {
|
|
278
|
+
PyWriter pw(layout, row_codec);
|
|
279
|
+
pw.visit([&](auto& w) {
|
|
280
|
+
nb::gil_scoped_release release;
|
|
281
|
+
if (!w.open(filename, true, compression_level, 64, flags))
|
|
282
|
+
throw std::runtime_error("Failed to open file for writing: " + filename);
|
|
283
|
+
for (size_t r = 0; r < num_rows; ++r) {
|
|
284
|
+
auto& row = w.row();
|
|
285
|
+
for (size_t c = 0; c < num_cols; ++c) {
|
|
286
|
+
if (is_string[c]) {
|
|
287
|
+
row.set(c, string_cols[c][r]);
|
|
288
|
+
} else {
|
|
289
|
+
set_from_numpy(row, c, col_types[c], bufs[c], r);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
w.writeRow();
|
|
293
|
+
}
|
|
294
|
+
w.close();
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
264
298
|
// ── Shared binding helpers for reader-like / writer-like classes ───
|
|
265
299
|
// Attaches read_row, read_all, __enter__, __exit__, __iter__, __next__
|
|
266
300
|
// to any class that has .readNext(), .row(), .layout(), .close().
|
|
@@ -275,8 +309,18 @@ namespace {
|
|
|
275
309
|
.def("read_all", [](ReaderT& r) -> nb::list {
|
|
276
310
|
nb::list result;
|
|
277
311
|
const auto& layout = r.layout();
|
|
278
|
-
|
|
279
|
-
|
|
312
|
+
const size_t n = layout.columnCount();
|
|
313
|
+
// Cache column types to avoid per-row layout lookups
|
|
314
|
+
std::vector<bcsv::ColumnType> col_types(n);
|
|
315
|
+
for (size_t i = 0; i < n; ++i)
|
|
316
|
+
col_types[i] = layout.columnType(i);
|
|
317
|
+
while (r.readNext()) {
|
|
318
|
+
const auto& row = r.row();
|
|
319
|
+
nb::list row_list;
|
|
320
|
+
for (size_t i = 0; i < n; ++i)
|
|
321
|
+
row_list.append(extract_column_value(row, i, col_types[i]));
|
|
322
|
+
result.append(std::move(row_list));
|
|
323
|
+
}
|
|
280
324
|
return result;
|
|
281
325
|
})
|
|
282
326
|
.def("__enter__", [](ReaderT& r) -> ReaderT& { return r; }, nb::rv_policy::reference)
|
|
@@ -602,7 +646,10 @@ NB_MODULE(_bcsv, m) {
|
|
|
602
646
|
return success;
|
|
603
647
|
}, nb::arg("filename"))
|
|
604
648
|
.def("layout", &ReaderT::layout, nb::rv_policy::reference_internal)
|
|
605
|
-
.def("read_next", &
|
|
649
|
+
.def("read_next", [](ReaderT& r) {
|
|
650
|
+
nb::gil_scoped_release release;
|
|
651
|
+
return r.readNext();
|
|
652
|
+
})
|
|
606
653
|
.def("close", [](ReaderT& reader) { nb::gil_scoped_release release; reader.close(); })
|
|
607
654
|
.def("is_open", &ReaderT::isOpen)
|
|
608
655
|
.def("file_flags", [](ReaderT& r) -> int { return static_cast<int>(r.fileFlags()); })
|
|
@@ -638,7 +685,81 @@ NB_MODULE(_bcsv, m) {
|
|
|
638
685
|
result[nb::cast(layout.columnName(i))] =
|
|
639
686
|
extract_column_value(row, i, layout.columnType(i));
|
|
640
687
|
return result;
|
|
641
|
-
}, "Get the current row as a dict {column_name: value}")
|
|
688
|
+
}, "Get the current row as a dict {column_name: value}")
|
|
689
|
+
.def("read_batch", [](ReaderT& r, size_t batch_size) -> nb::object {
|
|
690
|
+
const auto& layout = r.layout();
|
|
691
|
+
const size_t num_cols = layout.columnCount();
|
|
692
|
+
if (num_cols == 0)
|
|
693
|
+
throw std::runtime_error("Reader has no columns");
|
|
694
|
+
|
|
695
|
+
// Cache column metadata
|
|
696
|
+
std::vector<bcsv::ColumnType> col_types(num_cols);
|
|
697
|
+
std::vector<std::string> col_names(num_cols);
|
|
698
|
+
std::vector<bool> is_str(num_cols, false);
|
|
699
|
+
for (size_t c = 0; c < num_cols; ++c) {
|
|
700
|
+
col_types[c] = layout.columnType(c);
|
|
701
|
+
col_names[c] = layout.columnName(c);
|
|
702
|
+
is_str[c] = (col_types[c] == bcsv::ColumnType::STRING);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// Allocate numpy arrays (numeric) and C++ string vectors (strings)
|
|
706
|
+
auto np = nb::module_::import_("numpy");
|
|
707
|
+
std::vector<nb::object> arrays(num_cols);
|
|
708
|
+
std::vector<void*> bufs(num_cols, nullptr);
|
|
709
|
+
std::vector<std::vector<std::string>> string_cols(num_cols);
|
|
710
|
+
|
|
711
|
+
for (size_t c = 0; c < num_cols; ++c) {
|
|
712
|
+
if (is_str[c]) {
|
|
713
|
+
string_cols[c].reserve(batch_size);
|
|
714
|
+
} else {
|
|
715
|
+
nb::object shape = nb::make_tuple(static_cast<int64_t>(batch_size));
|
|
716
|
+
arrays[c] = np.attr("empty")(shape,
|
|
717
|
+
"dtype"_a = bcsv_type_to_numpy_dtype(col_types[c]));
|
|
718
|
+
bufs[c] = reinterpret_cast<void*>(
|
|
719
|
+
nb::cast<intptr_t>(arrays[c].attr("ctypes").attr("data")));
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Read loop under GIL release
|
|
724
|
+
size_t rows_read = 0;
|
|
725
|
+
{
|
|
726
|
+
nb::gil_scoped_release release;
|
|
727
|
+
while (rows_read < batch_size && r.readNext()) {
|
|
728
|
+
const auto& row = r.row();
|
|
729
|
+
for (size_t c = 0; c < num_cols; ++c) {
|
|
730
|
+
if (is_str[c]) {
|
|
731
|
+
string_cols[c].emplace_back(row.template get<std::string>(c));
|
|
732
|
+
} else {
|
|
733
|
+
fill_numpy_cell(row, c, col_types[c], bufs[c], rows_read);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
++rows_read;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
if (rows_read == 0) return nb::none();
|
|
741
|
+
|
|
742
|
+
// Build result dict
|
|
743
|
+
nb::dict result;
|
|
744
|
+
for (size_t c = 0; c < num_cols; ++c) {
|
|
745
|
+
if (is_str[c]) {
|
|
746
|
+
nb::list str_list;
|
|
747
|
+
for (auto& s : string_cols[c])
|
|
748
|
+
str_list.append(nb::cast(std::move(s)));
|
|
749
|
+
result[nb::cast(col_names[c])] = std::move(str_list);
|
|
750
|
+
} else {
|
|
751
|
+
if (rows_read < batch_size) {
|
|
752
|
+
// Slice to actual row count
|
|
753
|
+
auto stop = nb::int_(static_cast<int64_t>(rows_read));
|
|
754
|
+
auto sl = nb::steal(PySlice_New(Py_None, stop.ptr(), Py_None));
|
|
755
|
+
arrays[c] = arrays[c][sl];
|
|
756
|
+
}
|
|
757
|
+
result[nb::cast(col_names[c])] = std::move(arrays[c]);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return result;
|
|
761
|
+
}, nb::arg("batch_size") = 10000,
|
|
762
|
+
"Read up to batch_size rows into a dict of numpy arrays/lists. Returns None at EOF.");
|
|
642
763
|
bind_reader_iteration<ReaderT>(reader_cls);
|
|
643
764
|
|
|
644
765
|
// Utility functions
|
|
@@ -908,102 +1029,6 @@ NB_MODULE(_bcsv, m) {
|
|
|
908
1029
|
}, nb::arg("filename"),
|
|
909
1030
|
"Read a BCSV file into a dict of numpy arrays (numeric) and lists (strings)");
|
|
910
1031
|
|
|
911
|
-
// ── Columnar I/O: read_to_dataframe ────────────────────────────────
|
|
912
|
-
|
|
913
|
-
m.def("read_to_dataframe", [](const std::string& filename,
|
|
914
|
-
const std::optional<nb::list>& columns) -> nb::object {
|
|
915
|
-
// Phase 1: Open via ReaderDirectAccess
|
|
916
|
-
bcsv::ReaderDirectAccess<bcsv::Layout> reader;
|
|
917
|
-
{
|
|
918
|
-
nb::gil_scoped_release release;
|
|
919
|
-
if (!reader.open(filename, true)) {
|
|
920
|
-
throw std::runtime_error("Failed to open file: " + filename);
|
|
921
|
-
}
|
|
922
|
-
}
|
|
923
|
-
const size_t num_rows = reader.rowCount();
|
|
924
|
-
const auto& layout = reader.layout();
|
|
925
|
-
const size_t num_cols = layout.columnCount();
|
|
926
|
-
if (num_cols == 0)
|
|
927
|
-
throw std::runtime_error("File has no columns: " + filename);
|
|
928
|
-
|
|
929
|
-
// Cache column metadata
|
|
930
|
-
std::vector<std::string> col_names(num_cols);
|
|
931
|
-
std::vector<bcsv::ColumnType> col_types(num_cols);
|
|
932
|
-
std::vector<bool> is_string(num_cols, false);
|
|
933
|
-
for (size_t i = 0; i < num_cols; ++i) {
|
|
934
|
-
col_names[i] = layout.columnName(i);
|
|
935
|
-
col_types[i] = layout.columnType(i);
|
|
936
|
-
is_string[i] = (col_types[i] == bcsv::ColumnType::STRING);
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// Phase 2: Allocate numpy arrays / string vectors
|
|
940
|
-
auto np = nb::module_::import_("numpy");
|
|
941
|
-
std::vector<nb::object> arrays(num_cols);
|
|
942
|
-
std::vector<void*> bufs(num_cols, nullptr);
|
|
943
|
-
std::vector<std::vector<std::string>> string_cols(num_cols);
|
|
944
|
-
|
|
945
|
-
for (size_t c = 0; c < num_cols; ++c) {
|
|
946
|
-
if (is_string[c]) {
|
|
947
|
-
string_cols[c].reserve(num_rows);
|
|
948
|
-
} else {
|
|
949
|
-
nb::object shape = nb::make_tuple(static_cast<int64_t>(num_rows));
|
|
950
|
-
arrays[c] = np.attr("empty")(shape, "dtype"_a = bcsv_type_to_numpy_dtype(col_types[c]));
|
|
951
|
-
bufs[c] = reinterpret_cast<void*>(nb::cast<intptr_t>(arrays[c].attr("ctypes").attr("data")));
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
// Phase 3: Entire read loop under GIL release
|
|
956
|
-
{
|
|
957
|
-
nb::gil_scoped_release release;
|
|
958
|
-
size_t row_idx = 0;
|
|
959
|
-
while (reader.readNext()) {
|
|
960
|
-
const auto& row = reader.row();
|
|
961
|
-
for (size_t c = 0; c < num_cols; ++c) {
|
|
962
|
-
if (is_string[c]) {
|
|
963
|
-
string_cols[c].emplace_back(row.template get<std::string>(c));
|
|
964
|
-
} else {
|
|
965
|
-
fill_numpy_cell(row, c, col_types[c], bufs[c], row_idx);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
++row_idx;
|
|
969
|
-
}
|
|
970
|
-
reader.close();
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
// Phase 4: Build column dict
|
|
974
|
-
nb::dict col_dict;
|
|
975
|
-
for (size_t c = 0; c < num_cols; ++c) {
|
|
976
|
-
if (is_string[c]) {
|
|
977
|
-
nb::list str_list;
|
|
978
|
-
for (size_t i = 0; i < string_cols[c].size(); ++i) {
|
|
979
|
-
str_list.append(nb::cast(std::move(string_cols[c][i])));
|
|
980
|
-
}
|
|
981
|
-
col_dict[nb::cast(col_names[c])] = std::move(str_list);
|
|
982
|
-
} else {
|
|
983
|
-
col_dict[nb::cast(col_names[c])] = std::move(arrays[c]);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
// Phase 5: Filter columns if requested
|
|
988
|
-
if (columns.has_value()) {
|
|
989
|
-
nb::dict filtered;
|
|
990
|
-
const nb::list& cols_list = columns.value();
|
|
991
|
-
for (size_t i = 0; i < cols_list.size(); ++i) {
|
|
992
|
-
nb::handle col_name = cols_list[i];
|
|
993
|
-
auto key = nb::cast<std::string>(col_name);
|
|
994
|
-
if (col_dict.contains(nb::cast(key))) {
|
|
995
|
-
filtered[col_name] = col_dict[col_name];
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
col_dict = std::move(filtered);
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
// Phase 6: Construct pd.DataFrame in C++
|
|
1002
|
-
auto pd = nb::module_::import_("pandas");
|
|
1003
|
-
return pd.attr("DataFrame")(col_dict);
|
|
1004
|
-
}, nb::arg("filename"), nb::arg("columns") = nb::none(),
|
|
1005
|
-
"Read a BCSV file directly into a pandas DataFrame");
|
|
1006
|
-
|
|
1007
1032
|
// ── Columnar I/O: write_columns ────────────────────────────────────
|
|
1008
1033
|
|
|
1009
1034
|
m.def("write_columns", [](const std::string& filename, const nb::dict& columns,
|
|
@@ -1063,26 +1088,10 @@ NB_MODULE(_bcsv, m) {
|
|
|
1063
1088
|
}
|
|
1064
1089
|
}
|
|
1065
1090
|
|
|
1066
|
-
//
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
if (!w.open(filename, true, compression_level, 64, flags)) {
|
|
1071
|
-
throw std::runtime_error("Failed to open file for writing: " + filename);
|
|
1072
|
-
}
|
|
1073
|
-
for (size_t r = 0; r < num_rows; ++r) {
|
|
1074
|
-
auto& row = w.row();
|
|
1075
|
-
for (size_t c = 0; c < num_cols; ++c) {
|
|
1076
|
-
if (is_string[c]) {
|
|
1077
|
-
row.set(c, string_cols[c][r]);
|
|
1078
|
-
} else {
|
|
1079
|
-
set_from_numpy(row, c, col_types[c], bufs[c], r);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
w.writeRow();
|
|
1083
|
-
}
|
|
1084
|
-
w.close();
|
|
1085
|
-
});
|
|
1091
|
+
// Write via shared helper
|
|
1092
|
+
write_columnar_core(layout, row_codec, filename, num_rows, num_cols,
|
|
1093
|
+
bufs, string_cols, is_string, col_types,
|
|
1094
|
+
compression_level, flags);
|
|
1086
1095
|
}, nb::arg("filename"), nb::arg("columns"), nb::arg("col_order"),
|
|
1087
1096
|
nb::arg("col_types"), nb::arg("row_codec") = "delta",
|
|
1088
1097
|
nb::arg("compression_level") = 1,
|
|
@@ -1493,25 +1502,12 @@ NB_MODULE(_bcsv, m) {
|
|
|
1493
1502
|
}
|
|
1494
1503
|
}
|
|
1495
1504
|
|
|
1496
|
-
// Write
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
for (int64_t r = 0; r < num_rows; ++r) {
|
|
1503
|
-
auto& row = w.row();
|
|
1504
|
-
for (int64_t c = 0; c < num_cols; ++c) {
|
|
1505
|
-
if (is_string[c]) {
|
|
1506
|
-
row.set(c, string_cols[c][r]);
|
|
1507
|
-
} else {
|
|
1508
|
-
set_from_numpy(row, c, col_types[c], bufs[c], static_cast<size_t>(r));
|
|
1509
|
-
}
|
|
1510
|
-
}
|
|
1511
|
-
w.writeRow();
|
|
1512
|
-
}
|
|
1513
|
-
w.close();
|
|
1514
|
-
});
|
|
1505
|
+
// Write via shared helper
|
|
1506
|
+
write_columnar_core(layout, row_codec, filename,
|
|
1507
|
+
static_cast<size_t>(num_rows),
|
|
1508
|
+
static_cast<size_t>(num_cols),
|
|
1509
|
+
bufs, string_cols, is_string, col_types,
|
|
1510
|
+
compression_level, flags);
|
|
1515
1511
|
}, nb::arg("filename"), nb::arg("table"),
|
|
1516
1512
|
nb::arg("row_codec") = "delta",
|
|
1517
1513
|
nb::arg("compression_level") = 1,
|
|
@@ -23,12 +23,10 @@ from . import Layout, ColumnType, Writer, Reader, FileFlags
|
|
|
23
23
|
# Import columnar I/O if available (requires numpy headers in bindings)
|
|
24
24
|
try:
|
|
25
25
|
from ._bcsv import read_columns as _read_columns, write_columns as _write_columns
|
|
26
|
-
from ._bcsv import read_to_dataframe as _read_to_dataframe
|
|
27
26
|
_COLUMNAR_AVAILABLE = True
|
|
28
27
|
except ImportError:
|
|
29
28
|
try:
|
|
30
29
|
from _bcsv import read_columns as _read_columns, write_columns as _write_columns
|
|
31
|
-
from _bcsv import read_to_dataframe as _read_to_dataframe
|
|
32
30
|
_COLUMNAR_AVAILABLE = True
|
|
33
31
|
except ImportError:
|
|
34
32
|
_COLUMNAR_AVAILABLE = False
|
|
@@ -185,10 +183,6 @@ def read_dataframe(filename: str,
|
|
|
185
183
|
table = _read_to_arrow(filename, columns=columns)
|
|
186
184
|
return table.to_pandas()
|
|
187
185
|
|
|
188
|
-
# Fast path: C++ read_to_dataframe (single call, constructs DataFrame in C++)
|
|
189
|
-
if _COLUMNAR_AVAILABLE:
|
|
190
|
-
return _read_to_dataframe(filename, columns)
|
|
191
|
-
|
|
192
186
|
reader = Reader()
|
|
193
187
|
try:
|
|
194
188
|
if not reader.open(filename):
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
# Copyright (c) 2025-2026 Tobias Weber <weber.tobias.md@gmail.com>
|
|
4
|
-
#
|
|
5
|
-
# This file is part of the BCSV library.
|
|
6
|
-
#
|
|
7
|
-
# Licensed under the MIT License. See LICENSE file in the project root
|
|
8
|
-
# for full license information.
|
|
9
|
-
|
|
10
|
-
"""
|
|
11
|
-
Comprehensive test runner for PyBCSV unit tests.
|
|
12
|
-
Runs all test suites and provides a summary report.
|
|
13
|
-
"""
|
|
14
|
-
|
|
15
|
-
import unittest
|
|
16
|
-
import sys
|
|
17
|
-
import os
|
|
18
|
-
import time
|
|
19
|
-
from io import StringIO
|
|
20
|
-
|
|
21
|
-
# Add the parent directory to Python path to import tests
|
|
22
|
-
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
23
|
-
|
|
24
|
-
# Import all test modules
|
|
25
|
-
from test_basic_functionality import TestBasicFunctionality
|
|
26
|
-
from test_interoperability import TestInteroperability
|
|
27
|
-
from test_error_handling_edge_cases import TestErrorHandling
|
|
28
|
-
from test_pandas_integration import TestPandasIntegration
|
|
29
|
-
from test_performance_edge_cases import TestPerformanceEdgeCases
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class ColoredTextTestResult(unittest.TextTestResult):
|
|
33
|
-
"""Test result with colored output for better readability."""
|
|
34
|
-
|
|
35
|
-
def startTest(self, test):
|
|
36
|
-
super().startTest(test)
|
|
37
|
-
self.stream.write(f"Running {test._testMethodName}... ")
|
|
38
|
-
self.stream.flush()
|
|
39
|
-
|
|
40
|
-
def addSuccess(self, test):
|
|
41
|
-
super().addSuccess(test)
|
|
42
|
-
self.stream.write("✅ PASS\n")
|
|
43
|
-
self.stream.flush()
|
|
44
|
-
|
|
45
|
-
def addError(self, test, err):
|
|
46
|
-
super().addError(test, err)
|
|
47
|
-
self.stream.write("❌ ERROR\n")
|
|
48
|
-
self.stream.flush()
|
|
49
|
-
|
|
50
|
-
def addFailure(self, test, err):
|
|
51
|
-
super().addFailure(test, err)
|
|
52
|
-
self.stream.write("❌ FAIL\n")
|
|
53
|
-
self.stream.flush()
|
|
54
|
-
|
|
55
|
-
def addSkip(self, test, reason):
|
|
56
|
-
super().addSkip(test, reason)
|
|
57
|
-
self.stream.write(f"⏭️ SKIP ({reason})\n")
|
|
58
|
-
self.stream.flush()
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def run_test_suite(test_class, suite_name):
|
|
62
|
-
"""Run a specific test suite and return results."""
|
|
63
|
-
print(f"\n{'='*60}")
|
|
64
|
-
print(f"🧪 {suite_name}")
|
|
65
|
-
print(f"{'='*60}")
|
|
66
|
-
|
|
67
|
-
# Create test suite
|
|
68
|
-
loader = unittest.TestLoader()
|
|
69
|
-
suite = loader.loadTestsFromTestCase(test_class)
|
|
70
|
-
|
|
71
|
-
# Run tests with custom result class
|
|
72
|
-
stream = sys.stdout
|
|
73
|
-
runner = unittest.TextTestRunner(
|
|
74
|
-
stream=stream,
|
|
75
|
-
verbosity=0,
|
|
76
|
-
resultclass=ColoredTextTestResult
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
start_time = time.time()
|
|
80
|
-
result = runner.run(suite)
|
|
81
|
-
end_time = time.time()
|
|
82
|
-
|
|
83
|
-
# Print summary for this suite
|
|
84
|
-
total_tests = result.testsRun
|
|
85
|
-
failures = len(result.failures)
|
|
86
|
-
errors = len(result.errors)
|
|
87
|
-
skipped = len(result.skipped)
|
|
88
|
-
passed = total_tests - failures - errors - skipped
|
|
89
|
-
|
|
90
|
-
print(f"\n📊 Suite Summary:")
|
|
91
|
-
print(f" ✅ Passed: {passed}/{total_tests}")
|
|
92
|
-
if failures > 0:
|
|
93
|
-
print(f" ❌ Failed: {failures}")
|
|
94
|
-
if errors > 0:
|
|
95
|
-
print(f" 💥 Errors: {errors}")
|
|
96
|
-
if skipped > 0:
|
|
97
|
-
print(f" ⏭️ Skipped: {skipped}")
|
|
98
|
-
print(f" ⏱️ Time: {end_time - start_time:.2f}s")
|
|
99
|
-
|
|
100
|
-
return result, end_time - start_time
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def print_detailed_failures(all_results):
|
|
104
|
-
"""Print detailed information about failures and errors."""
|
|
105
|
-
has_failures = False
|
|
106
|
-
|
|
107
|
-
for suite_name, result, _ in all_results:
|
|
108
|
-
if result.failures or result.errors:
|
|
109
|
-
if not has_failures:
|
|
110
|
-
print(f"\n{'='*60}")
|
|
111
|
-
print("🔍 DETAILED FAILURE/ERROR REPORT")
|
|
112
|
-
print(f"{'='*60}")
|
|
113
|
-
has_failures = True
|
|
114
|
-
|
|
115
|
-
print(f"\n📋 {suite_name}:")
|
|
116
|
-
|
|
117
|
-
for test, traceback in result.failures:
|
|
118
|
-
print(f"\n❌ FAILURE: {test}")
|
|
119
|
-
print("-" * 40)
|
|
120
|
-
print(traceback)
|
|
121
|
-
|
|
122
|
-
for test, traceback in result.errors:
|
|
123
|
-
print(f"\n💥 ERROR: {test}")
|
|
124
|
-
print("-" * 40)
|
|
125
|
-
print(traceback)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def main():
|
|
129
|
-
"""Run all PyBCSV tests and provide comprehensive report."""
|
|
130
|
-
print("🚀 PyBCSV Comprehensive Test Suite")
|
|
131
|
-
print("Testing Python BCSV implementation for:")
|
|
132
|
-
print(" • Core functionality and data integrity")
|
|
133
|
-
print(" • C++/Python interoperability")
|
|
134
|
-
print(" • Error handling and edge cases")
|
|
135
|
-
print(" • Pandas DataFrame integration")
|
|
136
|
-
print(" • Performance and boundary conditions")
|
|
137
|
-
|
|
138
|
-
# Test suites to run
|
|
139
|
-
test_suites = [
|
|
140
|
-
(TestBasicFunctionality, "Basic Functionality Tests"),
|
|
141
|
-
(TestInteroperability, "C++/Python Interoperability Tests"),
|
|
142
|
-
(TestErrorHandling, "Error Handling & Edge Cases"),
|
|
143
|
-
(TestPandasIntegration, "Pandas Integration Tests"),
|
|
144
|
-
(TestPerformanceEdgeCases, "Performance & Edge Cases"),
|
|
145
|
-
]
|
|
146
|
-
|
|
147
|
-
# Run all test suites
|
|
148
|
-
all_results = []
|
|
149
|
-
total_start_time = time.time()
|
|
150
|
-
|
|
151
|
-
for test_class, suite_name in test_suites:
|
|
152
|
-
try:
|
|
153
|
-
result, duration = run_test_suite(test_class, suite_name)
|
|
154
|
-
all_results.append((suite_name, result, duration))
|
|
155
|
-
except Exception as e:
|
|
156
|
-
print(f"❌ Failed to run {suite_name}: {e}")
|
|
157
|
-
continue
|
|
158
|
-
|
|
159
|
-
total_end_time = time.time()
|
|
160
|
-
|
|
161
|
-
# Calculate overall statistics
|
|
162
|
-
total_tests = sum(result.testsRun for _, result, _ in all_results)
|
|
163
|
-
total_failures = sum(len(result.failures) for _, result, _ in all_results)
|
|
164
|
-
total_errors = sum(len(result.errors) for _, result, _ in all_results)
|
|
165
|
-
total_skipped = sum(len(result.skipped) for _, result, _ in all_results)
|
|
166
|
-
total_passed = total_tests - total_failures - total_errors - total_skipped
|
|
167
|
-
total_time = total_end_time - total_start_time
|
|
168
|
-
|
|
169
|
-
# Print overall summary
|
|
170
|
-
print(f"\n{'='*60}")
|
|
171
|
-
print("🎯 OVERALL TEST SUMMARY")
|
|
172
|
-
print(f"{'='*60}")
|
|
173
|
-
print(f"📊 Total Tests: {total_tests}")
|
|
174
|
-
print(f"✅ Passed: {total_passed} ({total_passed/total_tests*100:.1f}%)" if total_tests > 0 else "✅ Passed: 0")
|
|
175
|
-
print(f"❌ Failed: {total_failures}")
|
|
176
|
-
print(f"💥 Errors: {total_errors}")
|
|
177
|
-
print(f"⏭️ Skipped: {total_skipped}")
|
|
178
|
-
print(f"⏱️ Total Time: {total_time:.2f}s")
|
|
179
|
-
|
|
180
|
-
# Success rate
|
|
181
|
-
if total_tests > 0:
|
|
182
|
-
success_rate = (total_passed / total_tests) * 100
|
|
183
|
-
if success_rate >= 95:
|
|
184
|
-
print(f"🎉 Excellent! {success_rate:.1f}% success rate")
|
|
185
|
-
elif success_rate >= 80:
|
|
186
|
-
print(f"✅ Good! {success_rate:.1f}% success rate")
|
|
187
|
-
elif success_rate >= 60:
|
|
188
|
-
print(f"⚠️ Acceptable: {success_rate:.1f}% success rate")
|
|
189
|
-
else:
|
|
190
|
-
print(f"❌ Needs improvement: {success_rate:.1f}% success rate")
|
|
191
|
-
|
|
192
|
-
# Per-suite breakdown
|
|
193
|
-
print(f"\n📋 Per-Suite Results:")
|
|
194
|
-
for suite_name, result, duration in all_results:
|
|
195
|
-
total = result.testsRun
|
|
196
|
-
passed = total - len(result.failures) - len(result.errors) - len(result.skipped)
|
|
197
|
-
status = "✅" if len(result.failures) == 0 and len(result.errors) == 0 else "❌"
|
|
198
|
-
print(f" {status} {suite_name}: {passed}/{total} passed ({duration:.2f}s)")
|
|
199
|
-
|
|
200
|
-
# Print detailed failures if any
|
|
201
|
-
print_detailed_failures(all_results)
|
|
202
|
-
|
|
203
|
-
# Final recommendations
|
|
204
|
-
print(f"\n{'='*60}")
|
|
205
|
-
print("📝 RECOMMENDATIONS")
|
|
206
|
-
print(f"{'='*60}")
|
|
207
|
-
|
|
208
|
-
if total_failures == 0 and total_errors == 0:
|
|
209
|
-
print("🎉 All tests passed! PyBCSV implementation is working correctly.")
|
|
210
|
-
print("✅ The Python wrapper demonstrates:")
|
|
211
|
-
print(" • Correct basic functionality")
|
|
212
|
-
print(" • Proper error handling")
|
|
213
|
-
print(" • C++/Python interoperability")
|
|
214
|
-
print(" • Pandas integration")
|
|
215
|
-
print(" • Good performance characteristics")
|
|
216
|
-
else:
|
|
217
|
-
print("⚠️ Some tests failed. Please review the detailed failure report above.")
|
|
218
|
-
print("🔧 Recommended actions:")
|
|
219
|
-
if total_errors > 0:
|
|
220
|
-
print(" • Fix critical errors that prevent test execution")
|
|
221
|
-
if total_failures > 0:
|
|
222
|
-
print(" • Address test failures to ensure correct behavior")
|
|
223
|
-
if total_skipped > 0:
|
|
224
|
-
print(" • Review skipped tests and their skip reasons")
|
|
225
|
-
|
|
226
|
-
# Return exit code
|
|
227
|
-
exit_code = 0 if (total_failures == 0 and total_errors == 0) else 1
|
|
228
|
-
return exit_code
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if __name__ == '__main__':
|
|
232
|
-
exit_code = main()
|
|
233
|
-
sys.exit(exit_code)
|
pybcsv-1.4.1/tests/run_tests.py
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Test runner for the pybcsv test suite.
|
|
4
|
-
|
|
5
|
-
This script runs all tests and provides a summary of results.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import unittest
|
|
9
|
-
import sys
|
|
10
|
-
import os
|
|
11
|
-
|
|
12
|
-
# Add the pybcsv package to the path
|
|
13
|
-
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
|
14
|
-
|
|
15
|
-
def run_tests():
|
|
16
|
-
"""Run all tests and return the result."""
|
|
17
|
-
# Discover and run all tests
|
|
18
|
-
loader = unittest.TestLoader()
|
|
19
|
-
start_dir = os.path.dirname(__file__)
|
|
20
|
-
suite = loader.discover(start_dir, pattern='test_*.py')
|
|
21
|
-
|
|
22
|
-
# Run tests with verbose output
|
|
23
|
-
runner = unittest.TextTestRunner(verbosity=2, buffer=True)
|
|
24
|
-
result = runner.run(suite)
|
|
25
|
-
|
|
26
|
-
return result
|
|
27
|
-
|
|
28
|
-
def main():
|
|
29
|
-
"""Main test runner function."""
|
|
30
|
-
print("=" * 60)
|
|
31
|
-
print("Running pybcsv test suite")
|
|
32
|
-
print("=" * 60)
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
import pybcsv
|
|
36
|
-
print(f"✓ pybcsv version {pybcsv.__version__} loaded successfully")
|
|
37
|
-
except ImportError as e:
|
|
38
|
-
print(f"❌ Failed to import pybcsv: {e}")
|
|
39
|
-
print("Make sure pybcsv is installed: pip install -e .")
|
|
40
|
-
return 1
|
|
41
|
-
|
|
42
|
-
# Check optional dependencies
|
|
43
|
-
try:
|
|
44
|
-
import pandas
|
|
45
|
-
print(f"✓ pandas {pandas.__version__} available")
|
|
46
|
-
except ImportError:
|
|
47
|
-
print("⚠ pandas not available - some tests will be skipped")
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
import numpy
|
|
51
|
-
print(f"✓ numpy {numpy.__version__} available")
|
|
52
|
-
except ImportError:
|
|
53
|
-
print("⚠ numpy not available - some tests may fail")
|
|
54
|
-
|
|
55
|
-
print("\n" + "=" * 60)
|
|
56
|
-
print("Starting tests...")
|
|
57
|
-
print("=" * 60)
|
|
58
|
-
|
|
59
|
-
result = run_tests()
|
|
60
|
-
|
|
61
|
-
print("\n" + "=" * 60)
|
|
62
|
-
print("Test Summary")
|
|
63
|
-
print("=" * 60)
|
|
64
|
-
|
|
65
|
-
if result.wasSuccessful():
|
|
66
|
-
print(f"✅ All tests passed!")
|
|
67
|
-
print(f" Tests run: {result.testsRun}")
|
|
68
|
-
if result.skipped:
|
|
69
|
-
print(f" Tests skipped: {len(result.skipped)}")
|
|
70
|
-
return 0
|
|
71
|
-
else:
|
|
72
|
-
print(f"❌ Some tests failed")
|
|
73
|
-
print(f" Tests run: {result.testsRun}")
|
|
74
|
-
print(f" Failures: {len(result.failures)}")
|
|
75
|
-
print(f" Errors: {len(result.errors)}")
|
|
76
|
-
if result.skipped:
|
|
77
|
-
print(f" Tests skipped: {len(result.skipped)}")
|
|
78
|
-
|
|
79
|
-
if result.failures:
|
|
80
|
-
print("\nFailures:")
|
|
81
|
-
for test, traceback in result.failures:
|
|
82
|
-
print(f" - {test}")
|
|
83
|
-
|
|
84
|
-
if result.errors:
|
|
85
|
-
print("\nErrors:")
|
|
86
|
-
for test, traceback in result.errors:
|
|
87
|
-
print(f" - {test}")
|
|
88
|
-
|
|
89
|
-
return 1
|
|
90
|
-
|
|
91
|
-
if __name__ == '__main__':
|
|
92
|
-
sys.exit(main())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|