fsspec-data 0.2.0__tar.gz → 0.2.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.
Files changed (31) hide show
  1. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/Cargo.lock +61 -50
  2. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/Cargo.toml +2 -2
  3. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/PKG-INFO +2 -2
  4. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/__init__.py +1 -1
  5. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/filesystem.py +30 -27
  6. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/fsspec_data.abi3.so +0 -0
  7. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/interchange.py +52 -15
  8. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/tests/test_all.py +62 -0
  9. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/tests/test_filesystem.py +40 -1
  10. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/pyproject.toml +4 -4
  11. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/rust/Cargo.toml +1 -1
  12. fsspec_data-0.2.3/rust/coverage +2229 -0
  13. fsspec_data-0.2.3/rust/python/lib.rs +327 -0
  14. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/rust/src/codec.rs +386 -51
  15. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/rust/src/error.rs +2 -0
  16. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/rust/src/lib.rs +2 -2
  17. fsspec_data-0.2.0/rust/coverage +0 -1690
  18. fsspec_data-0.2.0/rust/python/lib.rs +0 -168
  19. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/.gitignore +0 -0
  20. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/LICENSE +0 -0
  21. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/README.md +0 -0
  22. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/_version.py +0 -0
  23. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/backends/__init__.py +0 -0
  24. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/backends/base.py +0 -0
  25. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/backends/http.py +0 -0
  26. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/backends/sqlalchemy.py +0 -0
  27. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/tests/__init__.py +0 -0
  28. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/fsspec_data/tests/conftest.py +0 -0
  29. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/rust/.config/nextest.toml +0 -0
  30. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/rust/src/plan.rs +0 -0
  31. {fsspec_data-0.2.0 → fsspec_data-0.2.3}/rust/src/stream.rs +0 -0
@@ -233,7 +233,7 @@ version = "54.3.1"
233
233
  source = "registry+https://github.com/rust-lang/crates.io-index"
234
234
  checksum = "39cfaf5e440be44db5413b75b72c2a87c1f8f0627117d110264048f2969b99e9"
235
235
  dependencies = [
236
- "bitflags 2.13.0",
236
+ "bitflags 2.13.1",
237
237
  ]
238
238
 
239
239
  [[package]]
@@ -296,9 +296,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
296
296
 
297
297
  [[package]]
298
298
  name = "bitflags"
299
- version = "2.13.0"
299
+ version = "2.13.1"
300
300
  source = "registry+https://github.com/rust-lang/crates.io-index"
301
- checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
301
+ checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
302
302
 
303
303
  [[package]]
304
304
  name = "brotli"
@@ -341,9 +341,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
341
341
 
342
342
  [[package]]
343
343
  name = "cc"
344
- version = "1.2.67"
344
+ version = "1.3.0"
345
345
  source = "registry+https://github.com/rust-lang/crates.io-index"
346
- checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38"
346
+ checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8"
347
347
  dependencies = [
348
348
  "find-msvc-tools",
349
349
  "jobserver",
@@ -464,7 +464,7 @@ dependencies = [
464
464
 
465
465
  [[package]]
466
466
  name = "fsspec_data"
467
- version = "0.2.0"
467
+ version = "0.2.3"
468
468
  dependencies = [
469
469
  "arrow",
470
470
  "bytes",
@@ -474,7 +474,7 @@ dependencies = [
474
474
 
475
475
  [[package]]
476
476
  name = "fsspec_data_py"
477
- version = "0.2.0"
477
+ version = "0.2.3"
478
478
  dependencies = [
479
479
  "arrow",
480
480
  "fsspec_data",
@@ -483,21 +483,21 @@ dependencies = [
483
483
 
484
484
  [[package]]
485
485
  name = "futures-core"
486
- version = "0.3.32"
486
+ version = "0.3.33"
487
487
  source = "registry+https://github.com/rust-lang/crates.io-index"
488
- checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
488
+ checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
489
489
 
490
490
  [[package]]
491
491
  name = "futures-task"
492
- version = "0.3.32"
492
+ version = "0.3.33"
493
493
  source = "registry+https://github.com/rust-lang/crates.io-index"
494
- checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
494
+ checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
495
495
 
496
496
  [[package]]
497
497
  name = "futures-util"
498
- version = "0.3.32"
498
+ version = "0.3.33"
499
499
  source = "registry+https://github.com/rust-lang/crates.io-index"
500
- checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
500
+ checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
501
501
  dependencies = [
502
502
  "futures-core",
503
503
  "futures-task",
@@ -732,7 +732,7 @@ version = "0.11.6"
732
732
  source = "registry+https://github.com/rust-lang/crates.io-index"
733
733
  checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a"
734
734
  dependencies = [
735
- "twox-hash 2.1.2",
735
+ "twox-hash 2.1.3",
736
736
  ]
737
737
 
738
738
  [[package]]
@@ -901,15 +901,15 @@ checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
901
901
 
902
902
  [[package]]
903
903
  name = "portable-atomic"
904
- version = "1.13.1"
904
+ version = "1.14.0"
905
905
  source = "registry+https://github.com/rust-lang/crates.io-index"
906
- checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49"
906
+ checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
907
907
 
908
908
  [[package]]
909
909
  name = "proc-macro2"
910
- version = "1.0.106"
910
+ version = "1.0.107"
911
911
  source = "registry+https://github.com/rust-lang/crates.io-index"
912
- checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
912
+ checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
913
913
  dependencies = [
914
914
  "unicode-ident",
915
915
  ]
@@ -961,7 +961,7 @@ dependencies = [
961
961
  "proc-macro2",
962
962
  "pyo3-macros-backend",
963
963
  "quote",
964
- "syn",
964
+ "syn 2.0.119",
965
965
  ]
966
966
 
967
967
  [[package]]
@@ -974,14 +974,14 @@ dependencies = [
974
974
  "proc-macro2",
975
975
  "pyo3-build-config",
976
976
  "quote",
977
- "syn",
977
+ "syn 2.0.119",
978
978
  ]
979
979
 
980
980
  [[package]]
981
981
  name = "quote"
982
- version = "1.0.46"
982
+ version = "1.0.47"
983
983
  source = "registry+https://github.com/rust-lang/crates.io-index"
984
- checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
984
+ checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
985
985
  dependencies = [
986
986
  "proc-macro2",
987
987
  ]
@@ -1000,9 +1000,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
1000
1000
 
1001
1001
  [[package]]
1002
1002
  name = "regex"
1003
- version = "1.13.0"
1003
+ version = "1.13.1"
1004
1004
  source = "registry+https://github.com/rust-lang/crates.io-index"
1005
- checksum = "2a0e75113e14dc5acb068cd0786884f214f1312650a3d36d269f5c4f3cdee8a2"
1005
+ checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
1006
1006
  dependencies = [
1007
1007
  "aho-corasick",
1008
1008
  "memchr",
@@ -1012,9 +1012,9 @@ dependencies = [
1012
1012
 
1013
1013
  [[package]]
1014
1014
  name = "regex-automata"
1015
- version = "0.4.15"
1015
+ version = "0.4.16"
1016
1016
  source = "registry+https://github.com/rust-lang/crates.io-index"
1017
- checksum = "1f388202e4b80542a0921078cc23b6333bcf1409c1e3f86404cae4766a6131db"
1017
+ checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad"
1018
1018
  dependencies = [
1019
1019
  "aho-corasick",
1020
1020
  "memchr",
@@ -1062,38 +1062,38 @@ checksum = "1bc711410fbe7399f390ca1c3b60ad0f53f80e95c5eb935e52268a0e2cd49acc"
1062
1062
 
1063
1063
  [[package]]
1064
1064
  name = "serde"
1065
- version = "1.0.228"
1065
+ version = "1.0.229"
1066
1066
  source = "registry+https://github.com/rust-lang/crates.io-index"
1067
- checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
1067
+ checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
1068
1068
  dependencies = [
1069
1069
  "serde_core",
1070
1070
  ]
1071
1071
 
1072
1072
  [[package]]
1073
1073
  name = "serde_core"
1074
- version = "1.0.228"
1074
+ version = "1.0.229"
1075
1075
  source = "registry+https://github.com/rust-lang/crates.io-index"
1076
- checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
1076
+ checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
1077
1077
  dependencies = [
1078
1078
  "serde_derive",
1079
1079
  ]
1080
1080
 
1081
1081
  [[package]]
1082
1082
  name = "serde_derive"
1083
- version = "1.0.228"
1083
+ version = "1.0.229"
1084
1084
  source = "registry+https://github.com/rust-lang/crates.io-index"
1085
- checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
1085
+ checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
1086
1086
  dependencies = [
1087
1087
  "proc-macro2",
1088
1088
  "quote",
1089
- "syn",
1089
+ "syn 3.0.2",
1090
1090
  ]
1091
1091
 
1092
1092
  [[package]]
1093
1093
  name = "serde_json"
1094
- version = "1.0.150"
1094
+ version = "1.0.151"
1095
1095
  source = "registry+https://github.com/rust-lang/crates.io-index"
1096
- checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
1096
+ checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
1097
1097
  dependencies = [
1098
1098
  "itoa",
1099
1099
  "memchr",
@@ -1128,9 +1128,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
1128
1128
 
1129
1129
  [[package]]
1130
1130
  name = "snap"
1131
- version = "1.1.1"
1131
+ version = "1.1.2"
1132
1132
  source = "registry+https://github.com/rust-lang/crates.io-index"
1133
- checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
1133
+ checksum = "199905e6153d6405f9728fe44daace35f8f837bbf830bb6e85fbd5828709a886"
1134
1134
 
1135
1135
  [[package]]
1136
1136
  name = "static_assertions"
@@ -1140,9 +1140,20 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
1140
1140
 
1141
1141
  [[package]]
1142
1142
  name = "syn"
1143
- version = "2.0.118"
1143
+ version = "2.0.119"
1144
+ source = "registry+https://github.com/rust-lang/crates.io-index"
1145
+ checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
1146
+ dependencies = [
1147
+ "proc-macro2",
1148
+ "quote",
1149
+ "unicode-ident",
1150
+ ]
1151
+
1152
+ [[package]]
1153
+ name = "syn"
1154
+ version = "3.0.2"
1144
1155
  source = "registry+https://github.com/rust-lang/crates.io-index"
1145
- checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
1156
+ checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3"
1146
1157
  dependencies = [
1147
1158
  "proc-macro2",
1148
1159
  "quote",
@@ -1157,22 +1168,22 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
1157
1168
 
1158
1169
  [[package]]
1159
1170
  name = "thiserror"
1160
- version = "2.0.18"
1171
+ version = "2.0.19"
1161
1172
  source = "registry+https://github.com/rust-lang/crates.io-index"
1162
- checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
1173
+ checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9"
1163
1174
  dependencies = [
1164
1175
  "thiserror-impl",
1165
1176
  ]
1166
1177
 
1167
1178
  [[package]]
1168
1179
  name = "thiserror-impl"
1169
- version = "2.0.18"
1180
+ version = "2.0.19"
1170
1181
  source = "registry+https://github.com/rust-lang/crates.io-index"
1171
- checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
1182
+ checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd"
1172
1183
  dependencies = [
1173
1184
  "proc-macro2",
1174
1185
  "quote",
1175
- "syn",
1186
+ "syn 3.0.2",
1176
1187
  ]
1177
1188
 
1178
1189
  [[package]]
@@ -1207,9 +1218,9 @@ dependencies = [
1207
1218
 
1208
1219
  [[package]]
1209
1220
  name = "twox-hash"
1210
- version = "2.1.2"
1221
+ version = "2.1.3"
1211
1222
  source = "registry+https://github.com/rust-lang/crates.io-index"
1212
- checksum = "9ea3136b675547379c4bd395ca6b938e5ad3c3d20fad76e7fe85f9e0d011419c"
1223
+ checksum = "8464ec13c3691491391d9fce00f6416c9a48e46972f72d7865688be2080192c9"
1213
1224
 
1214
1225
  [[package]]
1215
1226
  name = "unicode-ident"
@@ -1276,7 +1287,7 @@ dependencies = [
1276
1287
  "bumpalo",
1277
1288
  "proc-macro2",
1278
1289
  "quote",
1279
- "syn",
1290
+ "syn 2.0.119",
1280
1291
  "wasm-bindgen-shared",
1281
1292
  ]
1282
1293
 
@@ -1310,7 +1321,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
1310
1321
  dependencies = [
1311
1322
  "proc-macro2",
1312
1323
  "quote",
1313
- "syn",
1324
+ "syn 2.0.119",
1314
1325
  ]
1315
1326
 
1316
1327
  [[package]]
@@ -1321,7 +1332,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
1321
1332
  dependencies = [
1322
1333
  "proc-macro2",
1323
1334
  "quote",
1324
- "syn",
1335
+ "syn 2.0.119",
1325
1336
  ]
1326
1337
 
1327
1338
  [[package]]
@@ -1371,7 +1382,7 @@ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5"
1371
1382
  dependencies = [
1372
1383
  "proc-macro2",
1373
1384
  "quote",
1374
- "syn",
1385
+ "syn 2.0.119",
1375
1386
  ]
1376
1387
 
1377
1388
  [[package]]
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "fsspec_data_py"
3
- version = "0.2.0"
3
+ version = "0.2.3"
4
4
  edition = "2021"
5
5
  publish = false
6
6
 
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
12
12
  [dependencies]
13
13
  fsspec_data = { path = "./rust", version = "*" }
14
14
  arrow = { version = "54.3.1", features = ["pyarrow"] }
15
- pyo3 = { version = "0.23", features = ["abi3-py311", "extension-module"] }
15
+ pyo3 = { version = "0.23", features = ["abi3-py310", "extension-module"] }
16
16
 
17
17
  [profile.release]
18
18
  panic = "abort"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fsspec-data
3
- Version: 0.2.0
3
+ Version: 0.2.3
4
4
  Summary: Arrow interchange planning for the fsspec data ecosystem
5
5
  Project-URL: Repository, https://github.com/1kbgz/fsspec-data
6
6
  Project-URL: Homepage, https://github.com/1kbgz/fsspec-data
@@ -18,7 +18,7 @@ Classifier: Programming Language :: Python :: 3.14
18
18
  Classifier: Programming Language :: Python :: Implementation :: CPython
19
19
  Classifier: Programming Language :: Python :: Implementation :: PyPy
20
20
  Classifier: Programming Language :: Rust
21
- Requires-Python: >=3.11
21
+ Requires-Python: >=3.10
22
22
  Requires-Dist: fsspec
23
23
  Requires-Dist: pyarrow
24
24
  Provides-Extra: develop
@@ -15,7 +15,7 @@ from .interchange import (
15
15
  plan_schema,
16
16
  )
17
17
 
18
- __version__ = "0.2.0"
18
+ __version__ = "0.2.3"
19
19
 
20
20
  __all__ = [
21
21
  "Codec",
@@ -70,11 +70,14 @@ class DataFileSystem(ChainedFileSystem):
70
70
  raise ValueError("fsspec-data is a read-only filesystem")
71
71
 
72
72
  path = self._strip_protocol(path)
73
- output = self._convert(path)
74
73
  file = tempfile.SpooledTemporaryFile(max_size=self.spool_max_size, mode="w+b")
75
- file.write(output)
76
- file.seek(0)
77
- self._sizes[path] = len(output)
74
+ try:
75
+ self._convert(path, file)
76
+ self._sizes[path] = file.tell()
77
+ file.seek(0)
78
+ except Exception:
79
+ file.close()
80
+ raise
78
81
  return file
79
82
 
80
83
  def info(self, path: str, **kwargs: Any) -> dict[str, Any]:
@@ -94,31 +97,31 @@ class DataFileSystem(ChainedFileSystem):
94
97
  entry = self.info(path, **kwargs)
95
98
  return [entry] if detail else [entry["name"]]
96
99
 
97
- def _convert(self, path: str) -> bytes:
100
+ def _convert(self, path: str, output) -> None:
98
101
  provided_format = self.provided_format or _format_from_path(self.fo)
99
102
  requested_format = self.requested_format or _format_from_path(path)
100
- source = self.fs.cat_file(self.fo)
101
- decoded = DEFAULT_REGISTRY.get(provided_format).decode_batches(
102
- source,
103
- schema=self.provided_schema,
104
- batch_size=self.batch_size,
105
- row_limit=self.row_limit,
106
- byte_limit=self.byte_limit,
107
- )
108
- if self.provided_schema is not None and not decoded.schema.equals(self.provided_schema, check_metadata=False):
109
- raise ValueError("provided schema does not match the source schema")
110
-
111
- provided_schema = self.provided_schema or decoded.schema
112
- requested_schema = self.requested_schema or provided_schema
113
- plan = InterchangeRequest(
114
- provided_format,
115
- requested_format,
116
- provided_schema,
117
- requested_schema,
118
- self.schema_policy,
119
- ).plan()
120
- batches = tuple(plan.apply_batch(batch) for batch in decoded.batches)
121
- return DEFAULT_REGISTRY.get(requested_format).encode_batches(batches, schema=requested_schema)
103
+ with self.fs.open(self.fo, "rb") as source:
104
+ decoded = DEFAULT_REGISTRY.get(provided_format).iter_batches(
105
+ source,
106
+ schema=self.provided_schema,
107
+ batch_size=self.batch_size,
108
+ row_limit=self.row_limit,
109
+ byte_limit=self.byte_limit,
110
+ )
111
+ if self.provided_schema is not None and not decoded.schema.equals(self.provided_schema, check_metadata=False):
112
+ raise ValueError("provided schema does not match the source schema")
113
+
114
+ provided_schema = self.provided_schema or decoded.schema
115
+ requested_schema = self.requested_schema or provided_schema
116
+ plan = InterchangeRequest(
117
+ provided_format,
118
+ requested_format,
119
+ provided_schema,
120
+ requested_schema,
121
+ self.schema_policy,
122
+ ).plan()
123
+ batches = (plan.apply_batch(batch) for batch in decoded)
124
+ DEFAULT_REGISTRY.get(requested_format).encode_batches_to(batches, output, schema=requested_schema)
122
125
 
123
126
 
124
127
  _SUFFIX_FORMATS = {
@@ -1,9 +1,11 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from collections.abc import Iterable
3
4
  from dataclasses import dataclass, replace
4
5
  from enum import Enum
5
6
  from importlib import import_module
6
- from typing import Any
7
+ from itertools import chain
8
+ from typing import Any, BinaryIO
7
9
 
8
10
  import pyarrow as pa
9
11
  import pyarrow.compute as pc
@@ -94,9 +96,34 @@ class Codec:
94
96
  raise TypeError("batches must contain pyarrow.RecordBatch objects")
95
97
  return _rust.encode_batches(self.format.value, schema, batches)
96
98
 
99
+ def encode_batches_to(
100
+ self,
101
+ batches: Iterable[pa.RecordBatch],
102
+ output: BinaryIO,
103
+ *,
104
+ schema: pa.Schema | None = None,
105
+ ) -> None:
106
+ batches = iter(batches)
107
+ if schema is None:
108
+ try:
109
+ first = next(batches)
110
+ except StopIteration as error:
111
+ raise ValueError("schema is required when encoding no record batches") from error
112
+ if not isinstance(first, pa.RecordBatch):
113
+ raise TypeError("batches must contain pyarrow.RecordBatch objects")
114
+ schema = first.schema
115
+ batches = chain((first,), batches)
116
+ schema = _ensure_pyarrow(schema)
117
+ writer = _rust.start_codec_writer(self.format.value, schema, output)
118
+ for batch in batches:
119
+ if not isinstance(batch, pa.RecordBatch):
120
+ raise TypeError("batches must contain pyarrow.RecordBatch objects")
121
+ writer.write_batch(batch)
122
+ writer.finish()
123
+
97
124
  def decode_batches(
98
125
  self,
99
- data: bytes | bytearray | memoryview,
126
+ data: bytes | bytearray | memoryview | BinaryIO,
100
127
  *,
101
128
  schema: pa.Schema | None = None,
102
129
  batch_size: int = 1024,
@@ -113,25 +140,35 @@ class Codec:
113
140
 
114
141
  def iter_batches(
115
142
  self,
116
- data: bytes | bytearray | memoryview,
143
+ data: bytes | bytearray | memoryview | BinaryIO,
117
144
  *,
118
145
  schema: pa.Schema | None = None,
119
146
  batch_size: int = 1024,
120
147
  row_limit: int | None = None,
121
148
  byte_limit: int | None = None,
122
149
  ) -> DecodedBatchStream:
123
- if not isinstance(data, (bytes, bytearray, memoryview)):
124
- raise TypeError("data must be bytes-like")
125
150
  if schema is not None:
126
151
  schema = _ensure_pyarrow(schema)
127
- decoded_schema, native = _rust.decode_stream(
128
- self.format.value,
129
- bytes(data),
130
- schema,
131
- batch_size,
132
- row_limit,
133
- byte_limit,
134
- )
152
+ if isinstance(data, (bytes, bytearray, memoryview)):
153
+ decoded_schema, native = _rust.decode_stream(
154
+ self.format.value,
155
+ bytes(data),
156
+ schema,
157
+ batch_size,
158
+ row_limit,
159
+ byte_limit,
160
+ )
161
+ elif hasattr(data, "read"):
162
+ decoded_schema, native = _rust.decode_reader(
163
+ self.format.value,
164
+ data,
165
+ schema,
166
+ batch_size,
167
+ row_limit,
168
+ byte_limit,
169
+ )
170
+ else:
171
+ raise TypeError("data must be bytes-like or a binary file-like object")
135
172
  return DecodedBatchStream(decoded_schema, native)
136
173
 
137
174
 
@@ -204,7 +241,7 @@ class InterchangePlan:
204
241
 
205
242
  def iter_batches(
206
243
  self,
207
- data: bytes | bytearray | memoryview,
244
+ data: bytes | bytearray | memoryview | BinaryIO,
208
245
  *,
209
246
  batch_size: int = 1024,
210
247
  row_limit: int | None = None,
@@ -222,7 +259,7 @@ class InterchangePlan:
222
259
 
223
260
  def convert(
224
261
  self,
225
- data: bytes | bytearray | memoryview,
262
+ data: bytes | bytearray | memoryview | BinaryIO,
226
263
  *,
227
264
  batch_size: int = 1024,
228
265
  row_limit: int | None = None,
@@ -1,3 +1,6 @@
1
+ import io
2
+ from typing import Any
3
+
1
4
  import pyarrow as pa
2
5
  import pyarrow.parquet as pq
3
6
  import pytest
@@ -280,6 +283,43 @@ def test_text_codec_round_trip_preserves_schema_and_rows(format, prefix):
280
283
  assert pa.Table.from_batches(decoded.batches).to_pylist() == pa.Table.from_batches(batches).to_pylist()
281
284
 
282
285
 
286
+ @pytest.mark.parametrize("format", list(DataFormat))
287
+ def test_codec_encodes_batches_to_binary_file(format):
288
+ provided, _ = schemas()
289
+ batches = (
290
+ pa.RecordBatch.from_arrays([pa.array([1, 2]), pa.array(["ada", None])], schema=provided),
291
+ pa.RecordBatch.from_arrays([pa.array([3]), pa.array(["margaret"])], schema=provided),
292
+ )
293
+ codec = DEFAULT_REGISTRY.get(format)
294
+ output = io.BytesIO()
295
+
296
+ codec.encode_batches_to(iter(batches), output)
297
+
298
+ decode_schema = provided if format in {DataFormat.CSV, DataFormat.JSONL} else None
299
+ decoded = codec.decode_batches(output.getvalue(), schema=decode_schema)
300
+ assert pa.Table.from_batches(decoded.batches).to_pylist() == pa.Table.from_batches(batches).to_pylist()
301
+
302
+
303
+ def test_codec_file_writer_requires_schema_for_empty_stream():
304
+ codec = DEFAULT_REGISTRY.get(DataFormat.ARROW)
305
+
306
+ with pytest.raises(ValueError, match="schema is required"):
307
+ codec.encode_batches_to((), io.BytesIO())
308
+
309
+
310
+ def test_codec_file_writer_rejects_non_batches():
311
+ provided, _ = schemas()
312
+ codec = DEFAULT_REGISTRY.get(DataFormat.ARROW)
313
+ invalid: Any = object()
314
+
315
+ with pytest.raises(TypeError, match="pyarrow.RecordBatch"):
316
+ codec.encode_batches_to(iter([invalid]), io.BytesIO())
317
+
318
+ batch = pa.record_batch([[1], ["ada"]], schema=provided)
319
+ with pytest.raises(TypeError, match="pyarrow.RecordBatch"):
320
+ codec.encode_batches_to(iter([batch, invalid]), io.BytesIO(), schema=provided)
321
+
322
+
283
323
  @pytest.mark.parametrize("format", [DataFormat.CSV, DataFormat.JSONL])
284
324
  def test_text_codec_decode_requires_schema(format):
285
325
  codec = DEFAULT_REGISTRY.get(format)
@@ -302,6 +342,28 @@ def test_lazy_decode_applies_batch_and_row_limits():
302
342
  assert pa.Table.from_batches(batches).column("id").to_pylist() == [1, 2, 3, 4]
303
343
 
304
344
 
345
+ def test_lazy_decode_reads_binary_file_without_eagerly_consuming_it():
346
+ class CountingReader(io.BytesIO):
347
+ def __init__(self, data):
348
+ super().__init__(data)
349
+ self.bytes_read = 0
350
+
351
+ def read(self, size=-1):
352
+ data = super().read(size)
353
+ self.bytes_read += len(data)
354
+ return data
355
+
356
+ schema = pa.schema([("id", pa.int64())])
357
+ batch = pa.record_batch([pa.array(range(1024))], schema=schema)
358
+ codec = DEFAULT_REGISTRY.get(DataFormat.ARROW)
359
+ encoded = codec.encode_batches((batch,) * 128)
360
+ source = CountingReader(encoded)
361
+ stream = codec.iter_batches(source)
362
+
363
+ assert next(stream).num_rows == 1024
364
+ assert source.bytes_read < len(encoded)
365
+
366
+
305
367
  def test_lazy_decode_observes_cancellation():
306
368
  schema = pa.schema([("id", pa.int64())])
307
369
  codec = DEFAULT_REGISTRY.get(DataFormat.ARROW)