pyogrio 0.8.0__cp312-cp312-win_amd64.whl → 0.10.0__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of pyogrio might be problematic. Click here for more details.

Files changed (101) hide show
  1. pyogrio/__init__.py +26 -18
  2. pyogrio/_compat.py +7 -1
  3. pyogrio/_env.py +4 -6
  4. pyogrio/_err.c +460 -445
  5. pyogrio/_err.cp312-win_amd64.pyd +0 -0
  6. pyogrio/_geometry.c +645 -612
  7. pyogrio/_geometry.cp312-win_amd64.pyd +0 -0
  8. pyogrio/_io.c +7764 -7602
  9. pyogrio/_io.cp312-win_amd64.pyd +0 -0
  10. pyogrio/_ogr.c +601 -609
  11. pyogrio/_ogr.cp312-win_amd64.pyd +0 -0
  12. pyogrio/_version.py +3 -3
  13. pyogrio/_vsi.c +7570 -2514
  14. pyogrio/_vsi.cp312-win_amd64.pyd +0 -0
  15. pyogrio/core.py +86 -20
  16. pyogrio/errors.py +9 -16
  17. pyogrio/gdal_data/GDAL-targets.cmake +1 -1
  18. pyogrio/gdal_data/GDALConfig.cmake +0 -1
  19. pyogrio/gdal_data/GDALConfigVersion.cmake +3 -3
  20. pyogrio/gdal_data/MM_m_idofic.csv +321 -0
  21. pyogrio/gdal_data/gdaltileindex.xsd +269 -0
  22. pyogrio/gdal_data/gdalvrt.xsd +130 -22
  23. pyogrio/gdal_data/ogrinfo_output.schema.json +23 -0
  24. pyogrio/gdal_data/ogrvrt.xsd +3 -0
  25. pyogrio/gdal_data/pci_datum.txt +222 -155
  26. pyogrio/gdal_data/pci_ellips.txt +90 -38
  27. pyogrio/gdal_data/vcpkg.spdx.json +21 -21
  28. pyogrio/gdal_data/vcpkg_abi_info.txt +27 -27
  29. pyogrio/geopandas.py +44 -27
  30. pyogrio/proj_data/proj-config-version.cmake +2 -2
  31. pyogrio/proj_data/proj-targets.cmake +1 -1
  32. pyogrio/proj_data/proj.db +0 -0
  33. pyogrio/proj_data/proj4-targets.cmake +1 -1
  34. pyogrio/proj_data/projjson.schema.json +1 -1
  35. pyogrio/proj_data/vcpkg.spdx.json +17 -17
  36. pyogrio/proj_data/vcpkg_abi_info.txt +15 -15
  37. pyogrio/raw.py +46 -30
  38. pyogrio/tests/conftest.py +206 -12
  39. pyogrio/tests/fixtures/README.md +32 -13
  40. pyogrio/tests/fixtures/curve.gpkg +0 -0
  41. pyogrio/tests/fixtures/{test_multisurface.gpkg → curvepolygon.gpkg} +0 -0
  42. pyogrio/tests/fixtures/line_zm.gpkg +0 -0
  43. pyogrio/tests/fixtures/multisurface.gpkg +0 -0
  44. pyogrio/tests/test_arrow.py +178 -24
  45. pyogrio/tests/test_core.py +162 -72
  46. pyogrio/tests/test_geopandas_io.py +341 -96
  47. pyogrio/tests/test_path.py +30 -17
  48. pyogrio/tests/test_raw_io.py +165 -54
  49. pyogrio/tests/test_util.py +56 -0
  50. pyogrio/util.py +55 -31
  51. pyogrio-0.10.0.dist-info/DELVEWHEEL +2 -0
  52. {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/LICENSE +1 -1
  53. {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/METADATA +37 -8
  54. {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/RECORD +74 -89
  55. {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/WHEEL +1 -1
  56. pyogrio.libs/{Lerc-62a2c1c74500e7815994b3e49b36750c.dll → Lerc-089e3fef3df84b17326dcddbf1dedaa4.dll} +0 -0
  57. pyogrio.libs/{gdal-2bfc6a9f962a8953b0640db9a272d797.dll → gdal-debee5933f0da7bb90b4bcd009023377.dll} +0 -0
  58. pyogrio.libs/{geos-289d7171bd083dfed1f8a90e4ae57442.dll → geos-ace4c5b5c1f569bb4213e7bbd0b0322e.dll} +0 -0
  59. pyogrio.libs/{geos_c-2a12859cd876719c648f1eb950b7d94c.dll → geos_c-7478ca0a86136b280d9b2d245c6f6627.dll} +0 -0
  60. pyogrio.libs/geotiff-c8fe8a095520a4ea4e465d27e06add3a.dll +0 -0
  61. pyogrio.libs/{iconv-2-f2d9304f8dc4cdd981024b520b73a099.dll → iconv-2-27352d156a5467ca5383d3951093ea5a.dll} +0 -0
  62. pyogrio.libs/{jpeg62-a67b2bf7fd32d34c565ae5bb6d47c224.dll → jpeg62-e56b6f95a95af498f4623b8da4cebd46.dll} +0 -0
  63. pyogrio.libs/{json-c-79a8df7e59952f5c5d594620e4b66c13.dll → json-c-c84940e2654a4f8468bfcf2ce992aa93.dll} +0 -0
  64. pyogrio.libs/libcurl-d69cfd4ad487d53d58743b6778ec85e7.dll +0 -0
  65. pyogrio.libs/{libexpat-fa55f107b678de136400c6d953c3cdde.dll → libexpat-6576a8d02641b6a3dbad35901ec200a7.dll} +0 -0
  66. pyogrio.libs/liblzma-9ee4accb476ec1ae24e924953140273d.dll +0 -0
  67. pyogrio.libs/{libpng16-6227e9a35c2a350ae6b0586079c10b9e.dll → libpng16-7c36142dda59f186f6bb683e8dae2bfe.dll} +0 -0
  68. pyogrio.libs/{msvcp140-46db46e967c8db2cb7a20fc75872a57e.dll → msvcp140-98b3e5b80de1e5e9d1703b786d795623.dll} +0 -0
  69. pyogrio.libs/proj-a408c5327f3fd2f5fabe8c56815beed7.dll +0 -0
  70. pyogrio.libs/{qhull_r-d8840f4ed1f7d452ff9a30237320bcfd.dll → qhull_r-516897f855568caab1ab1fe37912766c.dll} +0 -0
  71. pyogrio.libs/sqlite3-9bc109d8536d5ed9666332fec94485fc.dll +0 -0
  72. pyogrio.libs/{tiff-ffca1ff19d0e95dad39df0078fb037af.dll → tiff-9b3f605fffe0bccc0a964c374ee4f820.dll} +0 -0
  73. pyogrio.libs/{zlib1-aaba6ea052f6d3fa3d84a301e3eb3d30.dll → zlib1-e5af16a15c63f05bd82d90396807ae5b.dll} +0 -0
  74. pyogrio/_err.pxd +0 -4
  75. pyogrio/_err.pyx +0 -250
  76. pyogrio/_geometry.pxd +0 -4
  77. pyogrio/_geometry.pyx +0 -129
  78. pyogrio/_io.pxd +0 -0
  79. pyogrio/_io.pyx +0 -2738
  80. pyogrio/_ogr.pxd +0 -441
  81. pyogrio/_ogr.pyx +0 -346
  82. pyogrio/_vsi.pxd +0 -4
  83. pyogrio/_vsi.pyx +0 -140
  84. pyogrio/arrow_bridge.h +0 -115
  85. pyogrio/gdal_data/bag_template.xml +0 -201
  86. pyogrio/gdal_data/gmlasconf.xml +0 -169
  87. pyogrio/gdal_data/gmlasconf.xsd +0 -1066
  88. pyogrio/gdal_data/netcdf_config.xsd +0 -143
  89. pyogrio/gdal_data/template_tiles.mapml +0 -28
  90. pyogrio/tests/fixtures/test_datetime.geojson +0 -7
  91. pyogrio/tests/fixtures/test_datetime_tz.geojson +0 -8
  92. pyogrio/tests/fixtures/test_fgdb.gdb.zip +0 -0
  93. pyogrio/tests/fixtures/test_nested.geojson +0 -18
  94. pyogrio/tests/fixtures/test_ogr_types_list.geojson +0 -12
  95. pyogrio-0.8.0.dist-info/DELVEWHEEL +0 -2
  96. pyogrio.libs/geotiff-d1c0fcc3c454409ad8be61ff04a7422c.dll +0 -0
  97. pyogrio.libs/libcurl-7fef9869f6520a5fbdb2bc9ce4c496cc.dll +0 -0
  98. pyogrio.libs/liblzma-5a1f648afc3d4cf36e3aef2266d55143.dll +0 -0
  99. pyogrio.libs/proj-74051a73897c9fa6d7bfef4561688568.dll +0 -0
  100. pyogrio.libs/sqlite3-fe7a86058d1c5658d1f9106228a7fd83.dll +0 -0
  101. {pyogrio-0.8.0.dist-info → pyogrio-0.10.0.dist-info}/top_level.txt +0 -0
pyogrio/tests/conftest.py CHANGED
@@ -1,7 +1,8 @@
1
+ from io import BytesIO
1
2
  from pathlib import Path
2
3
  from zipfile import ZIP_DEFLATED, ZipFile
3
4
 
4
- import pytest
5
+ import numpy as np
5
6
 
6
7
  from pyogrio import (
7
8
  __gdal_version_string__,
@@ -13,10 +14,14 @@ from pyogrio._compat import (
13
14
  HAS_ARROW_WRITE_API,
14
15
  HAS_GDAL_GEOS,
15
16
  HAS_PYARROW,
17
+ HAS_PYPROJ,
16
18
  HAS_SHAPELY,
17
19
  )
20
+ from pyogrio.core import vsi_rmtree
18
21
  from pyogrio.raw import read, write
19
22
 
23
+ import pytest
24
+
20
25
  _data_dir = Path(__file__).parent.resolve() / "fixtures"
21
26
 
22
27
  # mapping of driver extension to driver name for well-supported drivers
@@ -34,6 +39,15 @@ DRIVER_EXT = {driver: ext for ext, driver in DRIVERS.items()}
34
39
 
35
40
  ALL_EXTS = [".fgb", ".geojson", ".geojsonl", ".gpkg", ".shp"]
36
41
 
42
+ START_FID = {
43
+ ".fgb": 0,
44
+ ".geojson": 0,
45
+ ".geojsonl": 0,
46
+ ".geojsons": 0,
47
+ ".gpkg": 1,
48
+ ".shp": 0,
49
+ }
50
+
37
51
 
38
52
  def pytest_report_header(config):
39
53
  drivers = ", ".join(
@@ -53,6 +67,8 @@ requires_pyarrow_api = pytest.mark.skipif(
53
67
  not HAS_ARROW_API or not HAS_PYARROW, reason="GDAL>=3.6 and pyarrow required"
54
68
  )
55
69
 
70
+ requires_pyproj = pytest.mark.skipif(not HAS_PYPROJ, reason="pyproj required")
71
+
56
72
  requires_arrow_write_api = pytest.mark.skipif(
57
73
  not HAS_ARROW_WRITE_API or not HAS_PYARROW,
58
74
  reason="GDAL>=3.8 required for Arrow write API",
@@ -110,7 +126,7 @@ def naturalearth_lowres_all_ext(tmp_path, naturalearth_lowres, request):
110
126
 
111
127
  @pytest.fixture(scope="function")
112
128
  def naturalearth_lowres_vsi(tmp_path, naturalearth_lowres):
113
- """Wrap naturalearth_lowres as a zip file for vsi tests"""
129
+ """Wrap naturalearth_lowres as a zip file for VSI tests"""
114
130
 
115
131
  path = tmp_path / f"{naturalearth_lowres.name}.zip"
116
132
  with ZipFile(path, mode="w", compression=ZIP_DEFLATED, compresslevel=5) as out:
@@ -121,29 +137,182 @@ def naturalearth_lowres_vsi(tmp_path, naturalearth_lowres):
121
137
  return path, f"/vsizip/{path}/{naturalearth_lowres.name}"
122
138
 
123
139
 
140
+ @pytest.fixture(scope="function")
141
+ def naturalearth_lowres_vsimem(naturalearth_lowres):
142
+ """Write naturalearth_lowres to a vsimem file for VSI tests"""
143
+
144
+ meta, _, geometry, field_data = read(naturalearth_lowres)
145
+ name = f"pyogrio_fixture_{naturalearth_lowres.stem}"
146
+ dst_path = Path(f"/vsimem/{name}/{name}.gpkg")
147
+ meta["spatial_index"] = False
148
+ meta["geometry_type"] = "MultiPolygon"
149
+
150
+ write(dst_path, geometry, field_data, layer="naturalearth_lowres", **meta)
151
+ yield dst_path
152
+
153
+ vsi_rmtree(dst_path.parent)
154
+
155
+
124
156
  @pytest.fixture(scope="session")
125
- def test_fgdb_vsi():
126
- return f"/vsizip/{_data_dir}/test_fgdb.gdb.zip"
157
+ def line_zm_file():
158
+ return _data_dir / "line_zm.gpkg"
127
159
 
128
160
 
129
161
  @pytest.fixture(scope="session")
130
- def test_gpkg_nulls():
131
- return _data_dir / "test_gpkg_nulls.gpkg"
162
+ def curve_file():
163
+ return _data_dir / "curve.gpkg"
132
164
 
133
165
 
134
166
  @pytest.fixture(scope="session")
135
- def test_ogr_types_list():
136
- return _data_dir / "test_ogr_types_list.geojson"
167
+ def curve_polygon_file():
168
+ return _data_dir / "curvepolygon.gpkg"
137
169
 
138
170
 
139
171
  @pytest.fixture(scope="session")
140
- def test_datetime():
141
- return _data_dir / "test_datetime.geojson"
172
+ def multisurface_file():
173
+ return _data_dir / "multisurface.gpkg"
142
174
 
143
175
 
144
176
  @pytest.fixture(scope="session")
145
- def test_datetime_tz():
146
- return _data_dir / "test_datetime_tz.geojson"
177
+ def test_gpkg_nulls():
178
+ return _data_dir / "test_gpkg_nulls.gpkg"
179
+
180
+
181
+ @pytest.fixture(scope="function")
182
+ def no_geometry_file(tmp_path):
183
+ # create a GPKG layer that does not include geometry
184
+ filename = tmp_path / "test_no_geometry.gpkg"
185
+ write(
186
+ filename,
187
+ layer="no_geometry",
188
+ geometry=None,
189
+ field_data=[np.array(["a", "b", "c"])],
190
+ fields=["col"],
191
+ )
192
+
193
+ return filename
194
+
195
+
196
+ @pytest.fixture(scope="function")
197
+ def list_field_values_file(tmp_path):
198
+ # Create a GeoJSON file with list values in a property
199
+ list_geojson = """{
200
+ "type": "FeatureCollection",
201
+ "features": [
202
+ {
203
+ "type": "Feature",
204
+ "properties": { "int64": 1, "list_int64": [0, 1] },
205
+ "geometry": { "type": "Point", "coordinates": [0, 2] }
206
+ },
207
+ {
208
+ "type": "Feature",
209
+ "properties": { "int64": 2, "list_int64": [2, 3] },
210
+ "geometry": { "type": "Point", "coordinates": [1, 2] }
211
+ },
212
+ {
213
+ "type": "Feature",
214
+ "properties": { "int64": 3, "list_int64": [4, 5] },
215
+ "geometry": { "type": "Point", "coordinates": [2, 2] }
216
+ },
217
+ {
218
+ "type": "Feature",
219
+ "properties": { "int64": 4, "list_int64": [6, 7] },
220
+ "geometry": { "type": "Point", "coordinates": [3, 2] }
221
+ },
222
+ {
223
+ "type": "Feature",
224
+ "properties": { "int64": 5, "list_int64": [8, 9] },
225
+ "geometry": { "type": "Point", "coordinates": [4, 2] }
226
+ }
227
+ ]
228
+ }"""
229
+
230
+ filename = tmp_path / "test_ogr_types_list.geojson"
231
+ with open(filename, "w") as f:
232
+ _ = f.write(list_geojson)
233
+
234
+ return filename
235
+
236
+
237
+ @pytest.fixture(scope="function")
238
+ def nested_geojson_file(tmp_path):
239
+ # create GeoJSON file with nested properties
240
+ nested_geojson = """{
241
+ "type": "FeatureCollection",
242
+ "features": [
243
+ {
244
+ "type": "Feature",
245
+ "geometry": {
246
+ "type": "Point",
247
+ "coordinates": [0, 0]
248
+ },
249
+ "properties": {
250
+ "top_level": "A",
251
+ "intermediate_level": {
252
+ "bottom_level": "B"
253
+ }
254
+ }
255
+ }
256
+ ]
257
+ }"""
258
+
259
+ filename = tmp_path / "test_nested.geojson"
260
+ with open(filename, "w") as f:
261
+ _ = f.write(nested_geojson)
262
+
263
+ return filename
264
+
265
+
266
+ @pytest.fixture(scope="function")
267
+ def datetime_file(tmp_path):
268
+ # create GeoJSON file with millisecond precision
269
+ datetime_geojson = """{
270
+ "type": "FeatureCollection",
271
+ "features": [
272
+ {
273
+ "type": "Feature",
274
+ "properties": { "col": "2020-01-01T09:00:00.123" },
275
+ "geometry": { "type": "Point", "coordinates": [1, 1] }
276
+ },
277
+ {
278
+ "type": "Feature",
279
+ "properties": { "col": "2020-01-01T10:00:00" },
280
+ "geometry": { "type": "Point", "coordinates": [2, 2] }
281
+ }
282
+ ]
283
+ }"""
284
+
285
+ filename = tmp_path / "test_datetime.geojson"
286
+ with open(filename, "w") as f:
287
+ _ = f.write(datetime_geojson)
288
+
289
+ return filename
290
+
291
+
292
+ @pytest.fixture(scope="function")
293
+ def datetime_tz_file(tmp_path):
294
+ # create GeoJSON file with datetimes with timezone
295
+ datetime_tz_geojson = """{
296
+ "type": "FeatureCollection",
297
+ "features": [
298
+ {
299
+ "type": "Feature",
300
+ "properties": { "datetime_col": "2020-01-01T09:00:00.123-05:00" },
301
+ "geometry": { "type": "Point", "coordinates": [1, 1] }
302
+ },
303
+ {
304
+ "type": "Feature",
305
+ "properties": { "datetime_col": "2020-01-01T10:00:00-05:00" },
306
+ "geometry": { "type": "Point", "coordinates": [2, 2] }
307
+ }
308
+ ]
309
+ }"""
310
+
311
+ filename = tmp_path / "test_datetime_tz.geojson"
312
+ with open(filename, "w") as f:
313
+ f.write(datetime_tz_geojson)
314
+
315
+ return filename
147
316
 
148
317
 
149
318
  @pytest.fixture(scope="function")
@@ -178,6 +347,31 @@ def geojson_filelike(tmp_path):
178
347
  yield f
179
348
 
180
349
 
350
+ @pytest.fixture(scope="function")
351
+ def nonseekable_bytes(tmp_path):
352
+ # mock a non-seekable byte stream, such as a zstandard handle
353
+ class NonSeekableBytesIO(BytesIO):
354
+ def seekable(self):
355
+ return False
356
+
357
+ def seek(self, *args, **kwargs):
358
+ raise OSError("cannot seek")
359
+
360
+ # wrap GeoJSON into a non-seekable BytesIO
361
+ geojson = """{
362
+ "type": "FeatureCollection",
363
+ "features": [
364
+ {
365
+ "type": "Feature",
366
+ "properties": { },
367
+ "geometry": { "type": "Point", "coordinates": [1, 1] }
368
+ }
369
+ ]
370
+ }"""
371
+
372
+ return NonSeekableBytesIO(geojson.encode("UTF-8"))
373
+
374
+
181
375
  @pytest.fixture(
182
376
  scope="session",
183
377
  params=[
@@ -1,13 +1,28 @@
1
1
  # Test datasets
2
2
 
3
- ## Natural Earth lowres
3
+ ## Obtaining / creating test datasets
4
4
 
5
- `naturalearth_lowres.shp` was copied from GeoPandas.
5
+ If a test dataset can be created in code, do that instead. If it is used in a
6
+ single test, create the test dataset as part of that test. If it is used in
7
+ more than a single test, add it to `pyogrio/tests/conftest.py` instead, as a
8
+ function-scoped test fixture.
9
+
10
+ If you need to obtain 3rd party test files:
11
+
12
+ - add a section below that describes the source location and processing steps
13
+ to derive that dataset
14
+ - make sure the license is compatible with including in Pyogrio (public domain or open-source)
15
+ and record that license below
16
+
17
+ Please keep the test files no larger than necessary to use in tests.
6
18
 
7
- ## FGDB test dataset
19
+ ## Included test datasets
20
+
21
+ ### Natural Earth lowres
22
+
23
+ `naturalearth_lowres.shp` was copied from GeoPandas.
8
24
 
9
- `test_fgdb.gdb.zip`
10
- Downloaded from http://trac.osgeo.org/gdal/raw-attachment/wiki/FileGDB/test_fgdb.gdb.zip
25
+ License: public domain
11
26
 
12
27
  ### GPKG test dataset with null values
13
28
 
@@ -75,15 +90,19 @@ NOTE: Reading boolean values into GeoPandas using Fiona backend treats those
75
90
  values as `None` and column dtype as `object`; Pyogrio treats those values as
76
91
  `np.nan` and column dtype as `float64`.
77
92
 
78
- ### GPKG test with MultiSurface
79
-
80
- This was extracted from https://prd-tnm.s3.amazonaws.com/StagedProducts/Hydrography/NHDPlusHR/Beta/GDB/NHDPLUS_H_0308_HU4_GDB.zip
81
- `NHDWaterbody` layer using ogr2ogr:
82
-
83
- ```bash
84
- ogr2ogr test_mixed_surface.gpkg NHDPLUS_H_0308_HU4_GDB.gdb NHDWaterbody -where '"NHDPlusID" = 15000300070477' -select "NHDPlusID"
85
- ```
93
+ License: same as Pyogrio
86
94
 
87
95
  ### OSM PBF test
88
96
 
89
97
  This was downloaded from https://github.com/openstreetmap/OSM-binary/blob/master/resources/sample.pbf
98
+
99
+ License: [Open Data Commons Open Database License (ODbL)](https://opendatacommons.org/licenses/odbl/)
100
+
101
+ ### Test files for geometry types that are downgraded on read
102
+
103
+ `line_zm.gpkg` was created using QGIS to digitize a LineString GPKG layer with Z and M enabled. Downgraded to LineString Z on read.
104
+ `curve.gpkg` was created using QGIS to digitize a Curve GPKG layer. Downgraded to LineString on read.
105
+ `curvepolygon.gpkg` was created using QGIS to digitize a CurvePolygon GPKG layer. Downgraded to Polygon on read.
106
+ `multisurface.gpkg` was created using QGIS to digitize a MultiSurface GPKG layer. Downgraded to MultiPolygon on read.
107
+
108
+ License: same as Pyogrio
Binary file
Binary file
Binary file
@@ -1,39 +1,42 @@
1
1
  import contextlib
2
- from io import BytesIO
3
2
  import json
4
3
  import math
5
4
  import os
6
- from packaging.version import Version
7
5
  import sys
6
+ from io import BytesIO
7
+ from packaging.version import Version
8
+ from zipfile import ZipFile
8
9
 
9
- import pytest
10
10
  import numpy as np
11
11
 
12
12
  import pyogrio
13
13
  from pyogrio import (
14
14
  __gdal_version__,
15
+ get_gdal_config_option,
16
+ list_layers,
15
17
  read_dataframe,
16
18
  read_info,
17
- list_layers,
18
- get_gdal_config_option,
19
19
  set_gdal_config_options,
20
+ vsi_listtree,
20
21
  )
22
+ from pyogrio.errors import DataLayerError, DataSourceError, FieldError
21
23
  from pyogrio.raw import open_arrow, read_arrow, write, write_arrow
22
- from pyogrio.errors import DataSourceError, FieldError, DataLayerError
23
24
  from pyogrio.tests.conftest import (
24
25
  ALL_EXTS,
25
- DRIVERS,
26
26
  DRIVER_EXT,
27
+ DRIVERS,
27
28
  requires_arrow_write_api,
28
29
  requires_pyarrow_api,
29
30
  )
30
31
 
32
+ import pytest
33
+
31
34
  try:
32
35
  import pandas as pd
33
- from pandas.testing import assert_frame_equal, assert_index_equal
34
- from geopandas.testing import assert_geodataframe_equal
35
-
36
36
  import pyarrow
37
+
38
+ from geopandas.testing import assert_geodataframe_equal
39
+ from pandas.testing import assert_frame_equal, assert_index_equal
37
40
  except ImportError:
38
41
  pass
39
42
 
@@ -130,24 +133,24 @@ def test_read_arrow_ignore_geometry(naturalearth_lowres):
130
133
  assert_frame_equal(result, expected)
131
134
 
132
135
 
133
- def test_read_arrow_nested_types(test_ogr_types_list):
136
+ def test_read_arrow_nested_types(list_field_values_file):
134
137
  # with arrow, list types are supported
135
- result = read_dataframe(test_ogr_types_list, use_arrow=True)
138
+ result = read_dataframe(list_field_values_file, use_arrow=True)
136
139
  assert "list_int64" in result.columns
137
140
  assert result["list_int64"][0].tolist() == [0, 1]
138
141
 
139
142
 
140
- def test_read_arrow_to_pandas_kwargs(test_fgdb_vsi):
143
+ def test_read_arrow_to_pandas_kwargs(no_geometry_file):
141
144
  # with arrow, list types are supported
142
145
  arrow_to_pandas_kwargs = {"strings_to_categorical": True}
143
- result = read_dataframe(
144
- test_fgdb_vsi,
145
- layer="basetable_2",
146
+ df = read_dataframe(
147
+ no_geometry_file,
148
+ read_geometry=False,
146
149
  use_arrow=True,
147
150
  arrow_to_pandas_kwargs=arrow_to_pandas_kwargs,
148
151
  )
149
- assert "SEGMENT_NAME" in result.columns
150
- assert result["SEGMENT_NAME"].dtype.name == "category"
152
+ assert df.col.dtype.name == "category"
153
+ assert np.array_equal(df.col.values.categories, ["a", "b", "c"])
151
154
 
152
155
 
153
156
  def test_read_arrow_raw(naturalearth_lowres):
@@ -160,6 +163,10 @@ def test_read_arrow_vsi(naturalearth_lowres_vsi):
160
163
  table = read_arrow(naturalearth_lowres_vsi[1])[1]
161
164
  assert len(table) == 177
162
165
 
166
+ # Check temp file was cleaned up. Filter to files created by pyogrio, as GDAL keeps
167
+ # cache files in /vsimem/.
168
+ assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
169
+
163
170
 
164
171
  def test_read_arrow_bytes(geojson_bytes):
165
172
  meta, table = read_arrow(geojson_bytes)
@@ -167,6 +174,18 @@ def test_read_arrow_bytes(geojson_bytes):
167
174
  assert meta["fields"].shape == (5,)
168
175
  assert len(table) == 3
169
176
 
177
+ # Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
178
+ assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
179
+
180
+
181
+ def test_read_arrow_nonseekable_bytes(nonseekable_bytes):
182
+ meta, table = read_arrow(nonseekable_bytes)
183
+ assert meta["fields"].shape == (0,)
184
+ assert len(table) == 1
185
+
186
+ # Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
187
+ assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
188
+
170
189
 
171
190
  def test_read_arrow_filelike(geojson_filelike):
172
191
  meta, table = read_arrow(geojson_filelike)
@@ -174,6 +193,9 @@ def test_read_arrow_filelike(geojson_filelike):
174
193
  assert meta["fields"].shape == (5,)
175
194
  assert len(table) == 3
176
195
 
196
+ # Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
197
+ assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
198
+
177
199
 
178
200
  def test_open_arrow_pyarrow(naturalearth_lowres):
179
201
  with open_arrow(naturalearth_lowres, use_pyarrow=True) as (meta, reader):
@@ -183,7 +205,7 @@ def test_open_arrow_pyarrow(naturalearth_lowres):
183
205
 
184
206
 
185
207
  def test_open_arrow_batch_size(naturalearth_lowres):
186
- meta, table = read_arrow(naturalearth_lowres)
208
+ _, table = read_arrow(naturalearth_lowres)
187
209
  batch_size = math.ceil(len(table) / 2)
188
210
 
189
211
  with open_arrow(naturalearth_lowres, batch_size=batch_size, use_pyarrow=True) as (
@@ -289,14 +311,15 @@ def use_arrow_context():
289
311
  del os.environ["PYOGRIO_USE_ARROW"]
290
312
 
291
313
 
292
- def test_enable_with_environment_variable(test_ogr_types_list):
314
+ def test_enable_with_environment_variable(list_field_values_file):
293
315
  # list types are only supported with arrow, so don't work by default and work
294
316
  # when arrow is enabled through env variable
295
- result = read_dataframe(test_ogr_types_list)
317
+ result = read_dataframe(list_field_values_file)
296
318
  assert "list_int64" not in result.columns
297
319
 
298
320
  with use_arrow_context():
299
- result = read_dataframe(test_ogr_types_list)
321
+ result = read_dataframe(list_field_values_file)
322
+
300
323
  assert "list_int64" in result.columns
301
324
 
302
325
 
@@ -483,6 +506,88 @@ def test_write_geojson(tmp_path, naturalearth_lowres):
483
506
  )
484
507
 
485
508
 
509
+ @requires_arrow_write_api
510
+ @pytest.mark.skipif(
511
+ __gdal_version__ < (3, 6, 0),
512
+ reason="OpenFileGDB write support only available for GDAL >= 3.6.0",
513
+ )
514
+ @pytest.mark.parametrize(
515
+ "write_int64",
516
+ [
517
+ False,
518
+ pytest.param(
519
+ True,
520
+ marks=pytest.mark.skipif(
521
+ __gdal_version__ < (3, 9, 0),
522
+ reason="OpenFileGDB write support for int64 values for GDAL >= 3.9.0",
523
+ ),
524
+ ),
525
+ ],
526
+ )
527
+ def test_write_openfilegdb(tmp_path, write_int64):
528
+ expected_field_data = [
529
+ np.array([True, False, True], dtype="bool"),
530
+ np.array([1, 2, 3], dtype="int16"),
531
+ np.array([1, 2, 3], dtype="int32"),
532
+ np.array([1, 2, 3], dtype="int64"),
533
+ np.array([1, 2, 3], dtype="float32"),
534
+ np.array([1, 2, 3], dtype="float64"),
535
+ ]
536
+
537
+ table = pa.table(
538
+ {
539
+ "geometry": points,
540
+ **{field.dtype.name: field for field in expected_field_data},
541
+ }
542
+ )
543
+
544
+ filename = tmp_path / "test.gdb"
545
+
546
+ expected_meta = {"crs": "EPSG:4326"}
547
+
548
+ # int64 is not supported without additional config: https://gdal.org/en/latest/drivers/vector/openfilegdb.html#bit-integer-field-support
549
+ # it is converted to float64 by default and raises a warning
550
+ # (for GDAL >= 3.9.0 only)
551
+ write_params = (
552
+ {"TARGET_ARCGIS_VERSION": "ARCGIS_PRO_3_2_OR_LATER"} if write_int64 else {}
553
+ )
554
+
555
+ if write_int64 or __gdal_version__ < (3, 9, 0):
556
+ ctx = contextlib.nullcontext()
557
+ else:
558
+ ctx = pytest.warns(
559
+ RuntimeWarning, match="Integer64 will be written as a Float64"
560
+ )
561
+
562
+ with ctx:
563
+ write_arrow(
564
+ table,
565
+ filename,
566
+ driver="OpenFileGDB",
567
+ geometry_type="Point",
568
+ geometry_name="geometry",
569
+ **expected_meta,
570
+ **write_params,
571
+ )
572
+
573
+ meta, table = read_arrow(filename)
574
+
575
+ if not write_int64:
576
+ expected_field_data[3] = expected_field_data[3].astype("float64")
577
+
578
+ # bool types are converted to int32
579
+ expected_field_data[0] = expected_field_data[0].astype("int32")
580
+
581
+ assert meta["crs"] == expected_meta["crs"]
582
+
583
+ # NOTE: geometry name is set to "SHAPE" by GDAL
584
+ assert np.array_equal(table[meta["geometry_name"]], points)
585
+ for i in range(len(expected_field_data)):
586
+ values = table[table.schema.names[i]].to_numpy()
587
+ assert values.dtype == expected_field_data[i].dtype
588
+ assert np.array_equal(values, expected_field_data[i])
589
+
590
+
486
591
  @pytest.mark.parametrize(
487
592
  "driver",
488
593
  {
@@ -877,6 +982,9 @@ def test_write_memory_driver_required(naturalearth_lowres):
877
982
  geometry_name=meta["geometry_name"] or "wkb_geometry",
878
983
  )
879
984
 
985
+ # Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
986
+ assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
987
+
880
988
 
881
989
  @requires_arrow_write_api
882
990
  @pytest.mark.parametrize("driver", ["ESRI Shapefile", "OpenFileGDB"])
@@ -945,6 +1053,48 @@ def test_write_memory_existing_unsupported(naturalearth_lowres):
945
1053
  )
946
1054
 
947
1055
 
1056
+ @requires_arrow_write_api
1057
+ def test_write_open_file_handle(tmp_path, naturalearth_lowres):
1058
+ """Verify that writing to an open file handle is not currently supported"""
1059
+
1060
+ meta, table = read_arrow(naturalearth_lowres, max_features=1)
1061
+ meta["geometry_type"] = "MultiPolygon"
1062
+
1063
+ # verify it fails for regular file handle
1064
+ with pytest.raises(
1065
+ NotImplementedError, match="writing to an open file handle is not yet supported"
1066
+ ):
1067
+ with open(tmp_path / "test.geojson", "wb") as f:
1068
+ write_arrow(
1069
+ table,
1070
+ f,
1071
+ driver="GeoJSON",
1072
+ layer="test",
1073
+ crs=meta["crs"],
1074
+ geometry_type=meta["geometry_type"],
1075
+ geometry_name=meta["geometry_name"] or "wkb_geometry",
1076
+ )
1077
+
1078
+ # verify it fails for ZipFile
1079
+ with pytest.raises(
1080
+ NotImplementedError, match="writing to an open file handle is not yet supported"
1081
+ ):
1082
+ with ZipFile(tmp_path / "test.geojson.zip", "w") as z:
1083
+ with z.open("test.geojson", "w") as f:
1084
+ write_arrow(
1085
+ table,
1086
+ f,
1087
+ driver="GeoJSON",
1088
+ layer="test",
1089
+ crs=meta["crs"],
1090
+ geometry_type=meta["geometry_type"],
1091
+ geometry_name=meta["geometry_name"] or "wkb_geometry",
1092
+ )
1093
+
1094
+ # Check temp file was cleaned up. Filter, as gdal keeps cache files in /vsimem/.
1095
+ assert vsi_listtree("/vsimem/", pattern="pyogrio_*") == []
1096
+
1097
+
948
1098
  @requires_arrow_write_api
949
1099
  def test_non_utf8_encoding_io_shapefile(tmp_path, encoded_text):
950
1100
  encoding, text = encoded_text
@@ -1004,13 +1154,17 @@ def test_non_utf8_encoding_io_shapefile(tmp_path, encoded_text):
1004
1154
 
1005
1155
  @requires_arrow_write_api
1006
1156
  def test_encoding_write_layer_option_collision_shapefile(tmp_path, naturalearth_lowres):
1007
- """Providing both encoding parameter and ENCODING layer creation option (even if blank) is not allowed"""
1157
+ """Providing both encoding parameter and ENCODING layer creation option
1158
+ (even if blank) is not allowed."""
1008
1159
 
1009
1160
  meta, table = read_arrow(naturalearth_lowres)
1010
1161
 
1011
1162
  with pytest.raises(
1012
1163
  ValueError,
1013
- match='cannot provide both encoding parameter and "ENCODING" layer creation option',
1164
+ match=(
1165
+ 'cannot provide both encoding parameter and "ENCODING" layer creation '
1166
+ "option"
1167
+ ),
1014
1168
  ):
1015
1169
  write_arrow(
1016
1170
  table,