pybcsv 1.5.8__tar.gz → 1.5.10.dev0__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.5.8 → pybcsv-1.5.10.dev0}/PKG-INFO +39 -1
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/README.md +38 -0
- pybcsv-1.5.10.dev0/VERSION.txt +1 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/_version.py +3 -3
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/parquet_utils.py +88 -42
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_parquet_conversion.py +38 -2
- pybcsv-1.5.8/VERSION.txt +0 -1
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/BUILD_NOTES.md +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/CMakeLists.txt +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/LICENSE +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/MANIFEST.in +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/benchmarks/README.md +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/benchmarks/run_pybcsv_benchmarks.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/examples/README.md +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/examples/advanced_usage.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/examples/basic_usage.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/examples/pandas_integration.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/examples/performance_benchmark.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/bcsv.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/bcsv.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/bcsv_c_api.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/bitset.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/bitset.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/byte_buffer.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/checksum.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_file/file_codec_concept.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_file/file_codec_dispatch.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_file/file_codec_packet001.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_file/file_codec_packet_lz4_001.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_file/file_codec_packet_lz4_batch001.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_file/file_codec_stream001.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_file/file_codec_stream_lz4_001.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_row/row_codec_delta002.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_row/row_codec_delta002.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_row/row_codec_dispatch.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_row/row_codec_flat001.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_row/row_codec_flat001.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_row/row_codec_zoh001.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_row/row_codec_zoh001.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/column_name_index.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/column_name_index.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/csv_reader.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/csv_reader.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/csv_writer.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/csv_writer.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/definitions.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/file_footer.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/file_header.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/file_header.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/layout.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/layout.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/layout_guard.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/lz4_block.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/lz4_stream.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/packet_header.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/reader.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/reader.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/reader_concept.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/row.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/row.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/row_visitors.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler_ast.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler_compiler.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler_parser.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler_tokenizer.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler_types.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler_vm.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler_vm.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/sampler/sampler_window.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/std_charconv_compat.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/vle.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/writer.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/writer.hpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/writer_concept.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/lz4.c +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/lz4.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/lz4file.c +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/lz4file.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/lz4frame.c +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/lz4frame.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/lz4frame_static.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/lz4hc.c +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/lz4hc.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/xxhash.c +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/lz4-1.10.0/xxhash.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/xxHash-0.8.3/xxhash.c +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/xxHash-0.8.3/xxhash.h +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/__init__.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/__init__.pyi +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/__version__.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/_bcsv.pyi +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/bindings.cpp +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/pandas_utils.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/polars_utils.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pybcsv/py.typed +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/pyproject.toml +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/sync_headers.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/README.md +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/conftest.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_basic_functionality.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_count_rows.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_csv_writer_reader.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_cycle4_python_hardening.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_data_types.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_interoperability.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_minimal_string.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_performance_edge_cases.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_arrow.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_codecs.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_columnar.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_core.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_direct_access.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_errors.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_pandas.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_performance.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_polars.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_pybcsv_sampler.py +0 -0
- {pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/tests/test_streaming_bindings.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pybcsv
|
|
3
|
-
Version: 1.5.
|
|
3
|
+
Version: 1.5.10.dev0
|
|
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>
|
|
@@ -156,6 +156,44 @@ with pybcsv.ReaderDirectAccess() as da:
|
|
|
156
156
|
print(da.read(100)) # alternative syntax
|
|
157
157
|
```
|
|
158
158
|
|
|
159
|
+
## Parquet Conversion Tools (CLI)
|
|
160
|
+
|
|
161
|
+
Installing `pybcsv` provides two streaming command-line converters (require the
|
|
162
|
+
`arrow` extra: `pip install pybcsv[arrow]`). They stream in bounded batches, so
|
|
163
|
+
they handle files larger than memory.
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# Parquet → BCSV
|
|
167
|
+
parquet2bcsv input.parquet -o output.bcsv
|
|
168
|
+
# --row-codec {delta,zoh,flat} row codec (default: delta)
|
|
169
|
+
# --file-codec {packet_lz4_batch,...} file codec (default: packet_lz4_batch)
|
|
170
|
+
# --chunk-size N rows per streamed batch (default: 512000)
|
|
171
|
+
# -f/--force overwrite an existing output
|
|
172
|
+
|
|
173
|
+
# BCSV → Parquet
|
|
174
|
+
bcsv2parquet input.bcsv -o output.parquet
|
|
175
|
+
# --columns "a,b,c" select/reorder columns (order is honored)
|
|
176
|
+
# --slice 10:100 Python-style row slice
|
|
177
|
+
# --unflatten (default) reconstruct nested structs from dotted/bracketed names
|
|
178
|
+
# --no-unflatten keep flat columns (names like 'a.b', 'vals[0]')
|
|
179
|
+
# --parquet-compression {none,snappy,gzip,zstd,lz4}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Schema mapping.** Parquet structs and fixed-size lists are *flattened* to BCSV
|
|
183
|
+
columns using dotted (`location.lat`) and bracketed (`vals[0]`) names;
|
|
184
|
+
`bcsv2parquet --unflatten` reverses this. Notes and limitations:
|
|
185
|
+
|
|
186
|
+
- **No nulls:** BCSV has no null representation — a null in any converted column
|
|
187
|
+
is rejected with the offending row number. Filter nulls before converting.
|
|
188
|
+
- **Type widening:** `float16`/`bfloat16` widen to `float32`; `large_string`
|
|
189
|
+
maps to `string`.
|
|
190
|
+
- **Unsupported types** (variable-length lists, maps, timestamps, decimals,
|
|
191
|
+
dictionaries) are rejected with a clear error.
|
|
192
|
+
- **Column names ending in `_`** are rejected (the unflatten escape protocol
|
|
193
|
+
reserves trailing underscores).
|
|
194
|
+
- **Colliding names:** if a literal dotted column (`a.b`) and a struct path both
|
|
195
|
+
map to the same nested path, `--unflatten` fails loudly; use `--no-unflatten`.
|
|
196
|
+
|
|
159
197
|
## Available Types
|
|
160
198
|
|
|
161
199
|
| Constant | Description |
|
|
@@ -110,6 +110,44 @@ with pybcsv.ReaderDirectAccess() as da:
|
|
|
110
110
|
print(da.read(100)) # alternative syntax
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
+
## Parquet Conversion Tools (CLI)
|
|
114
|
+
|
|
115
|
+
Installing `pybcsv` provides two streaming command-line converters (require the
|
|
116
|
+
`arrow` extra: `pip install pybcsv[arrow]`). They stream in bounded batches, so
|
|
117
|
+
they handle files larger than memory.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Parquet → BCSV
|
|
121
|
+
parquet2bcsv input.parquet -o output.bcsv
|
|
122
|
+
# --row-codec {delta,zoh,flat} row codec (default: delta)
|
|
123
|
+
# --file-codec {packet_lz4_batch,...} file codec (default: packet_lz4_batch)
|
|
124
|
+
# --chunk-size N rows per streamed batch (default: 512000)
|
|
125
|
+
# -f/--force overwrite an existing output
|
|
126
|
+
|
|
127
|
+
# BCSV → Parquet
|
|
128
|
+
bcsv2parquet input.bcsv -o output.parquet
|
|
129
|
+
# --columns "a,b,c" select/reorder columns (order is honored)
|
|
130
|
+
# --slice 10:100 Python-style row slice
|
|
131
|
+
# --unflatten (default) reconstruct nested structs from dotted/bracketed names
|
|
132
|
+
# --no-unflatten keep flat columns (names like 'a.b', 'vals[0]')
|
|
133
|
+
# --parquet-compression {none,snappy,gzip,zstd,lz4}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Schema mapping.** Parquet structs and fixed-size lists are *flattened* to BCSV
|
|
137
|
+
columns using dotted (`location.lat`) and bracketed (`vals[0]`) names;
|
|
138
|
+
`bcsv2parquet --unflatten` reverses this. Notes and limitations:
|
|
139
|
+
|
|
140
|
+
- **No nulls:** BCSV has no null representation — a null in any converted column
|
|
141
|
+
is rejected with the offending row number. Filter nulls before converting.
|
|
142
|
+
- **Type widening:** `float16`/`bfloat16` widen to `float32`; `large_string`
|
|
143
|
+
maps to `string`.
|
|
144
|
+
- **Unsupported types** (variable-length lists, maps, timestamps, decimals,
|
|
145
|
+
dictionaries) are rejected with a clear error.
|
|
146
|
+
- **Column names ending in `_`** are rejected (the unflatten escape protocol
|
|
147
|
+
reserves trailing underscores).
|
|
148
|
+
- **Colliding names:** if a literal dotted column (`a.b`) and a struct path both
|
|
149
|
+
map to the same nested path, `--unflatten` fails loudly; use `--no-unflatten`.
|
|
150
|
+
|
|
113
151
|
## Available Types
|
|
114
152
|
|
|
115
153
|
| Constant | Description |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
1.5.9
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.5.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 5,
|
|
21
|
+
__version__ = version = '1.5.10.dev0'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 5, 10, 'dev0')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g6ec074145'
|
|
@@ -11,6 +11,7 @@ import time
|
|
|
11
11
|
from typing import List, Optional, Set, Tuple, Union
|
|
12
12
|
|
|
13
13
|
import pyarrow as pa
|
|
14
|
+
import pyarrow.compute as pc
|
|
14
15
|
import pyarrow.parquet as pq
|
|
15
16
|
|
|
16
17
|
import pybcsv
|
|
@@ -252,8 +253,24 @@ def flatten_batch(
|
|
|
252
253
|
|
|
253
254
|
|
|
254
255
|
def _extract_flat_array(batch: pa.RecordBatch, flat_name: str) -> pa.Array:
|
|
255
|
-
"""Navigate into nested columns to extract a flat array.
|
|
256
|
-
|
|
256
|
+
"""Navigate into nested columns to extract a flat array.
|
|
257
|
+
|
|
258
|
+
For already-flattened Parquet files whose column names contain literal
|
|
259
|
+
dots, tries an exact match first. If found, returns the column
|
|
260
|
+
directly without decomposition.
|
|
261
|
+
"""
|
|
262
|
+
# Fast path: flat_name matches a literal column name (common for
|
|
263
|
+
# pre-flattened Parquet files with dot-separated identifiers).
|
|
264
|
+
for i in range(len(batch.schema)):
|
|
265
|
+
if batch.schema.field(i).name == flat_name:
|
|
266
|
+
return batch.column(i)
|
|
267
|
+
|
|
268
|
+
# Undo collision-escape underscores before navigating: a struct path whose
|
|
269
|
+
# flattened name collided with a literal dotted column was escaped (e.g.
|
|
270
|
+
# 'a.b' -> 'a_.b') during flattening. The batch's actual struct is named
|
|
271
|
+
# 'a', so strip the escape suffixes to navigate it. Real identifiers can
|
|
272
|
+
# never end with '_' (rejected at flatten time), so this is unambiguous.
|
|
273
|
+
parts = _strip_escape_suffixes(_decompose_name(flat_name))
|
|
257
274
|
root_name: str = parts[0] # type: ignore[assignment]
|
|
258
275
|
|
|
259
276
|
col_idx = -1
|
|
@@ -275,9 +292,17 @@ def _extract_flat_array(batch: pa.RecordBatch, flat_name: str) -> pa.Array:
|
|
|
275
292
|
f"List index {part} out of range [0, {list_size}) "
|
|
276
293
|
f"for column '{flat_name}'."
|
|
277
294
|
)
|
|
295
|
+
# FixedSizeList values are row-major: element `i` of row `r` lives
|
|
296
|
+
# at child[r*list_size + i]. Extracting element `part` across all
|
|
297
|
+
# rows is therefore a *strided* gather, not a contiguous slice.
|
|
298
|
+
# flatten() yields the logical, offset-adjusted row-major child.
|
|
278
299
|
num_rows = len(arr)
|
|
279
|
-
|
|
280
|
-
|
|
300
|
+
flat_values = arr.flatten()
|
|
301
|
+
gather = pa.array(
|
|
302
|
+
[r * list_size + part for r in range(num_rows)],
|
|
303
|
+
type=pa.int64(),
|
|
304
|
+
)
|
|
305
|
+
arr = flat_values.take(gather)
|
|
281
306
|
else:
|
|
282
307
|
arr = arr[part]
|
|
283
308
|
else:
|
|
@@ -451,6 +476,21 @@ def unflatten_schema_to_arrow(
|
|
|
451
476
|
)
|
|
452
477
|
for n in names:
|
|
453
478
|
_check_underscore_name(n)
|
|
479
|
+
# Two distinct flat names that collapse to the same nested path (e.g. a
|
|
480
|
+
# literal 'a.b' column and a struct path escaped to 'a_.b') cannot be
|
|
481
|
+
# reconstructed unambiguously — the trie would silently merge them and drop
|
|
482
|
+
# data. Detect this and fail loudly; --no-unflatten preserves the columns.
|
|
483
|
+
_seen_paths: dict = {}
|
|
484
|
+
for n in names:
|
|
485
|
+
key = tuple(_strip_escape_suffixes(_decompose_name(n)))
|
|
486
|
+
if key in _seen_paths:
|
|
487
|
+
path_str = ".".join(str(p) for p in key)
|
|
488
|
+
raise ValueError(
|
|
489
|
+
f"Cannot unflatten: columns '{_seen_paths[key]}' and '{n}' both "
|
|
490
|
+
f"map to nested path '{path_str}'. Re-run with --no-unflatten to "
|
|
491
|
+
"keep the flat columns."
|
|
492
|
+
)
|
|
493
|
+
_seen_paths[key] = n
|
|
454
494
|
trie = _build_trie(names, arrow_types)
|
|
455
495
|
fields = _trie_to_arrow_field(trie)
|
|
456
496
|
return pa.schema(fields)
|
|
@@ -524,8 +564,20 @@ def _build_nested_array(
|
|
|
524
564
|
chunks.append(arr)
|
|
525
565
|
else:
|
|
526
566
|
raise ValueError(f"Cannot find array for '{bracket}'")
|
|
567
|
+
# `chunks[i]` holds element `i` of every row (element-major). The Arrow
|
|
568
|
+
# FixedSizeList child must be row-major (child[r*list_size + i]), so
|
|
569
|
+
# re-interleave: child[r*list_size + i] = chunks[i][r] = merged[i*num_rows + r].
|
|
527
570
|
merged = pa.concat_arrays(chunks)
|
|
528
|
-
|
|
571
|
+
num_rows = len(chunks[0])
|
|
572
|
+
gather = pa.array(
|
|
573
|
+
[
|
|
574
|
+
(p % list_size) * num_rows + (p // list_size)
|
|
575
|
+
for p in range(num_rows * list_size)
|
|
576
|
+
],
|
|
577
|
+
type=pa.int64(),
|
|
578
|
+
)
|
|
579
|
+
child = merged.take(gather)
|
|
580
|
+
return pa.FixedSizeListArray.from_arrays(child, list_size)
|
|
529
581
|
|
|
530
582
|
struct_fields = field.type
|
|
531
583
|
child_arrays: List[pa.Array] = []
|
|
@@ -616,41 +668,28 @@ def validate_parquet_schema(pf: pq.ParquetFile) -> None:
|
|
|
616
668
|
|
|
617
669
|
|
|
618
670
|
def _check_nulls(batch: pa.RecordBatch, row_offset: int) -> None:
|
|
619
|
-
"""Raise ValueError on first null
|
|
671
|
+
"""Raise ValueError on the first null in any column of the batch.
|
|
672
|
+
|
|
673
|
+
Uses pyarrow-native null location, which is offset-safe for sliced arrays
|
|
674
|
+
(a hand-rolled bitmap scan mis-attributes the row number when the array has
|
|
675
|
+
a non-zero offset, e.g. an element extracted from a FixedSizeList).
|
|
676
|
+
"""
|
|
620
677
|
for i in range(len(batch.schema)):
|
|
621
678
|
col = batch.column(i)
|
|
622
679
|
if col.null_count == 0:
|
|
623
680
|
continue
|
|
624
681
|
|
|
625
682
|
field_name = batch.schema.field(i).name
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
if byte_val != 0xFF:
|
|
637
|
-
for bit_pos in range(8):
|
|
638
|
-
absolute_bit = byte_idx * 8 + bit_pos
|
|
639
|
-
if absolute_bit >= max_bits:
|
|
640
|
-
break
|
|
641
|
-
logical_row = absolute_bit - offset
|
|
642
|
-
if not (byte_val & (1 << bit_pos)):
|
|
643
|
-
raise ValueError(
|
|
644
|
-
f"Null value detected in column '{field_name}' "
|
|
645
|
-
f"at row {row_offset + logical_row}. "
|
|
646
|
-
"BCSV does not support nulls. Filter before conversion."
|
|
647
|
-
)
|
|
648
|
-
else:
|
|
649
|
-
raise ValueError(
|
|
650
|
-
f"Null value detected in column '{field_name}' "
|
|
651
|
-
f"(null_count={col.null_count}). "
|
|
652
|
-
"BCSV does not support nulls."
|
|
653
|
-
)
|
|
683
|
+
first_null = pc.index(pc.is_null(col), True).as_py()
|
|
684
|
+
location = (
|
|
685
|
+
f" at row {row_offset + first_null}"
|
|
686
|
+
if first_null is not None and first_null >= 0
|
|
687
|
+
else ""
|
|
688
|
+
)
|
|
689
|
+
raise ValueError(
|
|
690
|
+
f"Null value detected in column '{field_name}'{location}. "
|
|
691
|
+
"BCSV does not support nulls. Filter before conversion."
|
|
692
|
+
)
|
|
654
693
|
|
|
655
694
|
|
|
656
695
|
# ---- Selection & helpers ----
|
|
@@ -859,14 +898,20 @@ def bcsv_to_parquet(
|
|
|
859
898
|
subset_layout: Optional[pybcsv.Layout] = None
|
|
860
899
|
unflatten_schema: Optional[pa.Schema] = None
|
|
861
900
|
if unflatten:
|
|
862
|
-
if col_names and
|
|
901
|
+
if col_names and col_names != list(bcsv_layout.get_column_names()):
|
|
902
|
+
# Build the subset layout in the user-requested column order (col_names),
|
|
903
|
+
# NOT file order — otherwise the streamed output and the empty-file
|
|
904
|
+
# fallback (which uses arrow_schema, built from col_names) disagree, and
|
|
905
|
+
# a requested reordering is silently ignored.
|
|
906
|
+
type_by_name = dict(
|
|
907
|
+
zip(
|
|
908
|
+
bcsv_layout.get_column_names(),
|
|
909
|
+
bcsv_layout.get_column_types(),
|
|
910
|
+
)
|
|
911
|
+
)
|
|
863
912
|
subset_layout = pybcsv.Layout()
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
bcsv_layout.get_column_names(), bcsv_layout.get_column_types()
|
|
867
|
-
):
|
|
868
|
-
if n in col_set:
|
|
869
|
-
subset_layout.add_column(n, t)
|
|
913
|
+
for n in col_names:
|
|
914
|
+
subset_layout.add_column(n, type_by_name[n])
|
|
870
915
|
else:
|
|
871
916
|
subset_layout = bcsv_layout
|
|
872
917
|
layout_names = list(subset_layout.get_column_names())
|
|
@@ -1135,7 +1180,8 @@ def bcsv2parquet_cli() -> None:
|
|
|
1135
1180
|
"--row-group-size",
|
|
1136
1181
|
type=int,
|
|
1137
1182
|
default=None,
|
|
1138
|
-
help="
|
|
1183
|
+
help="Max rows per Parquet row group (applied per streamed batch; "
|
|
1184
|
+
"values larger than --chunk-size are bounded by the batch size)",
|
|
1139
1185
|
)
|
|
1140
1186
|
mut_unflatten = parser.add_mutually_exclusive_group()
|
|
1141
1187
|
mut_unflatten.add_argument(
|
|
@@ -278,8 +278,9 @@ class TestFlatSchemaToBcsvLayout(unittest.TestCase):
|
|
|
278
278
|
continue # widened to float32 before reaching this function
|
|
279
279
|
flat = [("x", arrow_type)]
|
|
280
280
|
layout = _flat_schema_to_bcsv_layout(flat)
|
|
281
|
-
self.assertEqual(
|
|
282
|
-
|
|
281
|
+
self.assertEqual(
|
|
282
|
+
len(layout.get_column_names()), 1, f"Missing mapping for {arrow_type}"
|
|
283
|
+
)
|
|
283
284
|
|
|
284
285
|
def test_large_string_maps_to_string(self):
|
|
285
286
|
"""pa.large_string() must map to BCSV STRING (not raise)."""
|
|
@@ -477,6 +478,41 @@ class TestFlattenBatch(unittest.TestCase):
|
|
|
477
478
|
self.assertEqual(result.schema.field(0).name, "b")
|
|
478
479
|
self.assertEqual(result.schema.field(1).name, "a")
|
|
479
480
|
|
|
481
|
+
def test_flatten_already_flat_with_dots(self):
|
|
482
|
+
"""Parquet file where columns are already flat with dot-separated names.
|
|
483
|
+
|
|
484
|
+
This is a regression test for column names like 'sim.ve.counter' where
|
|
485
|
+
the Parquet schema has no nesting but the name contains dots. The
|
|
486
|
+
flatten_batch function must recognize the name as a literal column name
|
|
487
|
+
and not decompose it into a non-existent struct path.
|
|
488
|
+
"""
|
|
489
|
+
batch = pa.RecordBatch.from_arrays(
|
|
490
|
+
[
|
|
491
|
+
pa.array([1, 2, 3]),
|
|
492
|
+
pa.array([10.0, 20.0, 30.0]),
|
|
493
|
+
pa.array([100, 200, 300]),
|
|
494
|
+
],
|
|
495
|
+
schema=pa.schema(
|
|
496
|
+
[
|
|
497
|
+
("sim.ve.counter", pa.int64()),
|
|
498
|
+
("sim.ve.value", pa.float64()),
|
|
499
|
+
("time", pa.int64()),
|
|
500
|
+
]
|
|
501
|
+
),
|
|
502
|
+
)
|
|
503
|
+
flat_schema = [
|
|
504
|
+
("sim.ve.counter", pa.int64()),
|
|
505
|
+
("sim.ve.value", pa.float64()),
|
|
506
|
+
("time", pa.int64()),
|
|
507
|
+
]
|
|
508
|
+
result = flatten_batch(batch, flat_schema)
|
|
509
|
+
self.assertEqual(result.num_rows, 3)
|
|
510
|
+
self.assertEqual(result.num_columns, 3)
|
|
511
|
+
# Values should be identical since no transformation needed
|
|
512
|
+
self.assertEqual(result.column("sim.ve.counter").to_pylist(), [1, 2, 3])
|
|
513
|
+
self.assertEqual(result.column("sim.ve.value").to_pylist(), [10.0, 20.0, 30.0])
|
|
514
|
+
self.assertEqual(result.column("time").to_pylist(), [100, 200, 300])
|
|
515
|
+
|
|
480
516
|
|
|
481
517
|
if __name__ == "__main__":
|
|
482
518
|
unittest.main()
|
pybcsv-1.5.8/VERSION.txt
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
1.5.8
|
|
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
|
{pybcsv-1.5.8 → pybcsv-1.5.10.dev0}/include/bcsv/codec_file/file_codec_packet_lz4_batch001.h
RENAMED
|
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
|