pyogrio 0.7.2__cp310-cp310-win_amd64.whl → 0.9.0__cp310-cp310-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 (83) hide show
  1. pyogrio/__init__.py +7 -3
  2. pyogrio/_compat.py +6 -1
  3. pyogrio/_err.c +855 -321
  4. pyogrio/_err.cp310-win_amd64.pyd +0 -0
  5. pyogrio/_err.pyx +7 -3
  6. pyogrio/_geometry.c +134 -75
  7. pyogrio/_geometry.cp310-win_amd64.pyd +0 -0
  8. pyogrio/_io.c +28462 -22659
  9. pyogrio/_io.cp310-win_amd64.pyd +0 -0
  10. pyogrio/_io.pyx +904 -242
  11. pyogrio/_ogr.c +1317 -1640
  12. pyogrio/_ogr.cp310-win_amd64.pyd +0 -0
  13. pyogrio/_ogr.pxd +69 -13
  14. pyogrio/_ogr.pyx +8 -24
  15. pyogrio/_version.py +3 -3
  16. pyogrio/_vsi.c +6815 -0
  17. pyogrio/_vsi.cp310-win_amd64.pyd +0 -0
  18. pyogrio/_vsi.pxd +4 -0
  19. pyogrio/_vsi.pyx +140 -0
  20. pyogrio/core.py +43 -44
  21. pyogrio/gdal_data/GDAL-targets-release.cmake +1 -1
  22. pyogrio/gdal_data/GDAL-targets.cmake +10 -6
  23. pyogrio/gdal_data/GDALConfigVersion.cmake +3 -3
  24. pyogrio/gdal_data/gdalinfo_output.schema.json +2 -0
  25. pyogrio/gdal_data/gdalvrt.xsd +163 -0
  26. pyogrio/gdal_data/ogrinfo_output.schema.json +12 -1
  27. pyogrio/gdal_data/vcpkg.spdx.json +23 -23
  28. pyogrio/gdal_data/vcpkg_abi_info.txt +29 -28
  29. pyogrio/geopandas.py +140 -34
  30. pyogrio/proj_data/ITRF2008 +2 -2
  31. pyogrio/proj_data/proj-config-version.cmake +2 -2
  32. pyogrio/proj_data/proj-config.cmake +2 -1
  33. pyogrio/proj_data/proj-targets-release.cmake +0 -1
  34. pyogrio/proj_data/proj-targets.cmake +10 -6
  35. pyogrio/proj_data/proj.db +0 -0
  36. pyogrio/proj_data/proj4-targets-release.cmake +0 -1
  37. pyogrio/proj_data/proj4-targets.cmake +10 -6
  38. pyogrio/proj_data/vcpkg.spdx.json +21 -43
  39. pyogrio/proj_data/vcpkg_abi_info.txt +16 -17
  40. pyogrio/raw.py +438 -116
  41. pyogrio/tests/conftest.py +75 -6
  42. pyogrio/tests/fixtures/poly_not_enough_points.shp.zip +0 -0
  43. pyogrio/tests/test_arrow.py +841 -7
  44. pyogrio/tests/test_core.py +99 -7
  45. pyogrio/tests/test_geopandas_io.py +827 -121
  46. pyogrio/tests/test_path.py +23 -3
  47. pyogrio/tests/test_raw_io.py +276 -50
  48. pyogrio/util.py +39 -19
  49. pyogrio-0.9.0.dist-info/DELVEWHEEL +2 -0
  50. {pyogrio-0.7.2.dist-info → pyogrio-0.9.0.dist-info}/METADATA +2 -2
  51. {pyogrio-0.7.2.dist-info → pyogrio-0.9.0.dist-info}/RECORD +72 -67
  52. {pyogrio-0.7.2.dist-info → pyogrio-0.9.0.dist-info}/WHEEL +1 -1
  53. pyogrio.libs/Lerc-5e4d8cbeeabca06f95e2270792304dc3.dll +0 -0
  54. pyogrio.libs/{gdal-c3b1d8f66682071d0cd26d86e4182013.dll → gdal-b434963605a006e01c486c0df6dea4e0.dll} +0 -0
  55. pyogrio.libs/geos-f0622d0794b81c937a851b2e6fa9b712.dll +0 -0
  56. pyogrio.libs/geos_c-0e16bf70612fc3301d077b9d863a3fdb.dll +0 -0
  57. pyogrio.libs/{geotiff-e43cdab688866b59f8800cfcde836d16.dll → geotiff-772e7c705fb15ddf91b432adb4eb1f6c.dll} +0 -0
  58. pyogrio.libs/iconv-2-8fcc23ddc6f096c45871011b6e008b44.dll +0 -0
  59. pyogrio.libs/{jpeg62-567ab743ac805dfb57fe3867ba5788a4.dll → jpeg62-2f9b7af22d78338e8f0be0058503dc35.dll} +0 -0
  60. pyogrio.libs/json-c-e52a077545e4057de42beb4948289b41.dll +0 -0
  61. pyogrio.libs/libcurl-bc81cd8afe15b10c0821b181b6af8bd0.dll +0 -0
  62. pyogrio.libs/libexpat-fbe03ca8917dfda776562d4338b289b8.dll +0 -0
  63. pyogrio.libs/{liblzma-de7f4770d4e3715acd031ca93883f10c.dll → liblzma-6b36f24d54d3dd45f274a2aebef81085.dll} +0 -0
  64. pyogrio.libs/libpng16-13928571ad910705eae8d7dd8eef8b11.dll +0 -0
  65. pyogrio.libs/{msvcp140-83b6a1a2fa8b1735a358b2fe13cabe4e.dll → msvcp140-46db46e967c8db2cb7a20fc75872a57e.dll} +0 -0
  66. pyogrio.libs/proj-8a30239ef2dfc3b9dd2bb48e8abb330f.dll +0 -0
  67. pyogrio.libs/{qhull_r-99ae8a526357acc44b162cb4df2c3bb6.dll → qhull_r-c45abde5d0c92faf723cc2942138af77.dll} +0 -0
  68. pyogrio.libs/sqlite3-df30c3cf230727e23c43c40126a530f7.dll +0 -0
  69. pyogrio.libs/{tiff-7c2d4b204ec2db46c81f6a597895c2f7.dll → tiff-43630f30487a9015213475ae86ed3fa3.dll} +0 -0
  70. pyogrio.libs/{zlib1-824de9299616f0908aeeb9441a084848.dll → zlib1-e1272810861a13dd8d6cff3beac47f17.dll} +0 -0
  71. pyogrio/tests/win32.py +0 -86
  72. pyogrio-0.7.2.dist-info/DELVEWHEEL +0 -2
  73. pyogrio.libs/Lerc-d5afc4101deffe7de21241ccd4d562f6.dll +0 -0
  74. pyogrio.libs/geos-1c764a1384537a0ad2995e83d23e8642.dll +0 -0
  75. pyogrio.libs/geos_c-0d7dfdcee49efa8df585e2fb993157aa.dll +0 -0
  76. pyogrio.libs/json-c-36c91e30c4410d41c22b2010c31183e3.dll +0 -0
  77. pyogrio.libs/libcurl-ebcc8c18195071a90e59f818902e10c6.dll +0 -0
  78. pyogrio.libs/libexpat-345379c9c11632130d8c383cbacde1a6.dll +0 -0
  79. pyogrio.libs/libpng16-2c30e6846653c47ef2ff9d7dec3338ba.dll +0 -0
  80. pyogrio.libs/proj-98758c96a6cb682b5cec7e8dc5e29a50.dll +0 -0
  81. pyogrio.libs/sqlite3-327ed7b38bfd91fb4a17544960e055e9.dll +0 -0
  82. {pyogrio-0.7.2.dist-info → pyogrio-0.9.0.dist-info}/LICENSE +0 -0
  83. {pyogrio-0.7.2.dist-info → pyogrio-0.9.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from pathlib import Path
2
3
  import contextlib
3
4
  from zipfile import ZipFile, ZIP_DEFLATED
4
5
 
@@ -6,7 +7,7 @@ import pytest
6
7
 
7
8
  import pyogrio
8
9
  import pyogrio.raw
9
- from pyogrio.util import vsi_path
10
+ from pyogrio.util import vsi_path, get_vsi_path_or_buffer
10
11
 
11
12
  try:
12
13
  import geopandas # NOQA
@@ -34,6 +35,7 @@ def change_cwd(path):
34
35
  ("/home/user/data.gpkg", "/home/user/data.gpkg"),
35
36
  (r"C:\User\Documents\data.gpkg", r"C:\User\Documents\data.gpkg"),
36
37
  ("file:///home/user/data.gpkg", "/home/user/data.gpkg"),
38
+ ("/home/folder # with hash/data.gpkg", "/home/folder # with hash/data.gpkg"),
37
39
  # cloud URIs
38
40
  ("https://testing/data.gpkg", "/vsicurl/https://testing/data.gpkg"),
39
41
  ("s3://testing/data.gpkg", "/vsis3/testing/data.gpkg"),
@@ -265,7 +267,7 @@ def test_detect_zip_path(tmp_path, naturalearth_lowres):
265
267
 
266
268
  @pytest.mark.network
267
269
  def test_url():
268
- url = "https://raw.githubusercontent.com/geopandas/pyogrio/main/pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.shp" # NOQA
270
+ url = "https://raw.githubusercontent.com/geopandas/pyogrio/main/pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.shp"
269
271
 
270
272
  result = pyogrio.raw.read(url)
271
273
  assert len(result[2]) == 177
@@ -279,7 +281,7 @@ def test_url():
279
281
 
280
282
  @pytest.mark.skipif(not has_geopandas, reason="GeoPandas not available")
281
283
  def test_url_dataframe():
282
- url = "https://raw.githubusercontent.com/geopandas/pyogrio/main/pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.shp" # NOQA
284
+ url = "https://raw.githubusercontent.com/geopandas/pyogrio/main/pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.shp"
283
285
 
284
286
  assert len(pyogrio.read_dataframe(url)) == 177
285
287
 
@@ -330,3 +332,21 @@ def test_uri_s3(aws_env_setup):
330
332
  def test_uri_s3_dataframe(aws_env_setup):
331
333
  df = pyogrio.read_dataframe("zip+s3://fiona-testing/coutwildrnp.zip")
332
334
  assert len(df) == 67
335
+
336
+
337
+ def test_get_vsi_path_or_buffer_obj_to_string():
338
+ path = Path("/tmp/test.gpkg")
339
+ assert get_vsi_path_or_buffer(path) == str(path)
340
+
341
+
342
+ def test_get_vsi_path_or_buffer_fixtures_to_string(tmp_path):
343
+ path = tmp_path / "test.gpkg"
344
+ assert get_vsi_path_or_buffer(path) == str(path)
345
+
346
+
347
+ @pytest.mark.parametrize(
348
+ "raw_path", ["/vsimem/test.shp.zip", "/vsizip//vsimem/test.shp.zip"]
349
+ )
350
+ def test_vsimem_path_exception(raw_path):
351
+ with pytest.raises(ValueError, match=""):
352
+ vsi_path(raw_path)
@@ -1,26 +1,30 @@
1
1
  import contextlib
2
+ import ctypes
3
+ from io import BytesIO
2
4
  import json
3
- import os
4
5
  import sys
5
6
 
6
7
  import numpy as np
7
8
  from numpy import array_equal
8
9
  import pytest
9
10
 
11
+ import pyogrio
10
12
  from pyogrio import (
11
13
  list_layers,
12
14
  list_drivers,
13
15
  read_info,
14
16
  set_gdal_config_options,
17
+ get_gdal_config_option,
15
18
  __gdal_version__,
16
19
  )
17
- from pyogrio._compat import HAS_SHAPELY
18
- from pyogrio.raw import read, write
20
+ from pyogrio._compat import HAS_SHAPELY, HAS_PYARROW
21
+ from pyogrio.raw import read, write, open_arrow
19
22
  from pyogrio.errors import DataSourceError, DataLayerError, FeatureError
20
23
  from pyogrio.tests.conftest import (
21
24
  DRIVERS,
22
25
  DRIVER_EXT,
23
26
  prepare_testfile,
27
+ requires_pyarrow_api,
24
28
  requires_arrow_api,
25
29
  )
26
30
 
@@ -79,6 +83,12 @@ def test_read_autodetect_driver(tmp_path, naturalearth_lowres, ext):
79
83
  assert len(geometry) == len(fields[0])
80
84
 
81
85
 
86
+ def test_read_arrow_unspecified_layer_warning(data_dir):
87
+ """Reading a multi-layer file without specifying a layer gives a warning."""
88
+ with pytest.warns(UserWarning, match="More than one layer found "):
89
+ read(data_dir / "sample.osm.pbf")
90
+
91
+
82
92
  def test_read_invalid_layer(naturalearth_lowres):
83
93
  with pytest.raises(DataLayerError, match="Layer 'invalid' could not be opened"):
84
94
  read(naturalearth_lowres, layer="invalid")
@@ -414,35 +424,43 @@ def test_read_return_only_fids(naturalearth_lowres):
414
424
  assert len(field_data) == 0
415
425
 
416
426
 
417
- def test_write(tmpdir, naturalearth_lowres):
427
+ @pytest.mark.parametrize("encoding", [None, "ISO-8859-1"])
428
+ def test_write_shp(tmp_path, naturalearth_lowres, encoding):
418
429
  meta, _, geometry, field_data = read(naturalearth_lowres)
419
430
 
420
- filename = os.path.join(str(tmpdir), "test.shp")
431
+ filename = tmp_path / "test.shp"
432
+ meta["encoding"] = encoding
421
433
  write(filename, geometry, field_data, **meta)
422
434
 
423
- assert os.path.exists(filename)
435
+ assert filename.exists()
424
436
  for ext in (".dbf", ".prj"):
425
- assert os.path.exists(filename.replace(".shp", ext))
437
+ assert filename.with_suffix(ext).exists()
426
438
 
439
+ # We write shapefiles in UTF-8 by default on all platforms
440
+ expected_encoding = encoding if encoding is not None else "UTF-8"
441
+ with open(filename.with_suffix(".cpg")) as cpg_file:
442
+ result_encoding = cpg_file.read()
443
+ assert result_encoding == expected_encoding
427
444
 
428
- def test_write_gpkg(tmpdir, naturalearth_lowres):
445
+
446
+ def test_write_gpkg(tmp_path, naturalearth_lowres):
429
447
  meta, _, geometry, field_data = read(naturalearth_lowres)
430
448
  meta.update({"geometry_type": "MultiPolygon"})
431
449
 
432
- filename = os.path.join(str(tmpdir), "test.gpkg")
450
+ filename = tmp_path / "test.gpkg"
433
451
  write(filename, geometry, field_data, driver="GPKG", **meta)
434
452
 
435
- assert os.path.exists(filename)
453
+ assert filename.exists()
436
454
 
437
455
 
438
- def test_write_gpkg_multiple_layers(tmpdir, naturalearth_lowres):
456
+ def test_write_gpkg_multiple_layers(tmp_path, naturalearth_lowres):
439
457
  meta, _, geometry, field_data = read(naturalearth_lowres)
440
458
  meta["geometry_type"] = "MultiPolygon"
441
459
 
442
- filename = os.path.join(str(tmpdir), "test.gpkg")
460
+ filename = tmp_path / "test.gpkg"
443
461
  write(filename, geometry, field_data, driver="GPKG", layer="first", **meta)
444
462
 
445
- assert os.path.exists(filename)
463
+ assert filename.exists()
446
464
 
447
465
  assert np.array_equal(list_layers(filename), [["first", "MultiPolygon"]])
448
466
 
@@ -453,13 +471,13 @@ def test_write_gpkg_multiple_layers(tmpdir, naturalearth_lowres):
453
471
  )
454
472
 
455
473
 
456
- def test_write_geojson(tmpdir, naturalearth_lowres):
474
+ def test_write_geojson(tmp_path, naturalearth_lowres):
457
475
  meta, _, geometry, field_data = read(naturalearth_lowres)
458
476
 
459
- filename = os.path.join(str(tmpdir), "test.json")
477
+ filename = tmp_path / "test.json"
460
478
  write(filename, geometry, field_data, driver="GeoJSON", **meta)
461
479
 
462
- assert os.path.exists(filename)
480
+ assert filename.exists()
463
481
 
464
482
  data = json.loads(open(filename).read())
465
483
 
@@ -478,17 +496,21 @@ def test_write_no_fields(tmp_path, naturalearth_lowres):
478
496
  meta, _, geometry, field_data = read(naturalearth_lowres)
479
497
  field_data = None
480
498
  meta["fields"] = None
499
+ # naturalearth_lowres actually contains MultiPolygons. A shapefile doesn't make the
500
+ # distinction, so the metadata just reports Polygon. GPKG does, so override here to
501
+ # avoid GDAL warnings.
502
+ meta["geometry_type"] = "MultiPolygon"
481
503
 
482
504
  # Test
483
505
  filename = tmp_path / "test.gpkg"
484
506
  write(filename, geometry, field_data, driver="GPKG", **meta)
485
507
 
486
508
  # Check result
487
- assert os.path.exists(filename)
509
+ assert filename.exists()
488
510
  meta, _, geometry, fields = read(filename)
489
511
 
490
512
  assert meta["crs"] == "EPSG:4326"
491
- assert meta["geometry_type"] == "Polygon"
513
+ assert meta["geometry_type"] == "MultiPolygon"
492
514
  assert meta["encoding"] == "UTF-8"
493
515
  assert meta["fields"].shape == (0,)
494
516
  assert len(fields) == 0
@@ -510,7 +532,7 @@ def test_write_no_geom(tmp_path, naturalearth_lowres):
510
532
  write(filename, geometry, field_data, driver="GPKG", **meta)
511
533
 
512
534
  # Check result
513
- assert os.path.exists(filename)
535
+ assert filename.exists()
514
536
  meta, _, geometry, fields = read(filename)
515
537
 
516
538
  assert meta["crs"] is None
@@ -547,7 +569,7 @@ def test_write_no_geom_data(tmp_path, naturalearth_lowres):
547
569
  write(filename, geometry, field_data, driver="GPKG", **meta)
548
570
 
549
571
  # Check result
550
- assert os.path.exists(filename)
572
+ assert filename.exists()
551
573
  result_meta, _, result_geometry, result_field_data = read(filename)
552
574
 
553
575
  assert result_meta["crs"] is None
@@ -581,17 +603,17 @@ def test_write_no_geom_no_fields():
581
603
  __gdal_version__ < (3, 6, 0),
582
604
  reason="OpenFileGDB write support only available for GDAL >= 3.6.0",
583
605
  )
584
- def test_write_openfilegdb(tmpdir, naturalearth_lowres):
606
+ def test_write_openfilegdb(tmp_path, naturalearth_lowres):
585
607
  meta, _, geometry, field_data = read(naturalearth_lowres)
586
608
 
587
- filename = os.path.join(str(tmpdir), "test.gdb")
609
+ filename = tmp_path / "test.gdb"
588
610
  write(filename, geometry, field_data, driver="OpenFileGDB", **meta)
589
611
 
590
- assert os.path.exists(filename)
612
+ assert filename.exists()
591
613
 
592
614
 
593
615
  @pytest.mark.parametrize("ext", DRIVERS)
594
- def test_write_append(tmpdir, naturalearth_lowres, ext):
616
+ def test_write_append(tmp_path, naturalearth_lowres, ext):
595
617
  if ext == ".fgb" and __gdal_version__ <= (3, 5, 0):
596
618
  pytest.skip("Append to FlatGeobuf fails for GDAL <= 3.5.0")
597
619
 
@@ -603,10 +625,10 @@ def test_write_append(tmpdir, naturalearth_lowres, ext):
603
625
  # coerce output layer to MultiPolygon to avoid mixed type errors
604
626
  meta["geometry_type"] = "MultiPolygon"
605
627
 
606
- filename = os.path.join(str(tmpdir), f"test{ext}")
628
+ filename = tmp_path / f"test{ext}"
607
629
  write(filename, geometry, field_data, **meta)
608
630
 
609
- assert os.path.exists(filename)
631
+ assert filename.exists()
610
632
 
611
633
  assert read_info(filename)["features"] == 177
612
634
 
@@ -617,17 +639,17 @@ def test_write_append(tmpdir, naturalearth_lowres, ext):
617
639
 
618
640
 
619
641
  @pytest.mark.parametrize("driver,ext", [("GML", ".gml"), ("GeoJSONSeq", ".geojsons")])
620
- def test_write_append_unsupported(tmpdir, naturalearth_lowres, driver, ext):
642
+ def test_write_append_unsupported(tmp_path, naturalearth_lowres, driver, ext):
621
643
  if ext == ".geojsons" and __gdal_version__ >= (3, 6, 0):
622
644
  pytest.skip("Append to GeoJSONSeq supported for GDAL >= 3.6.0")
623
645
 
624
646
  meta, _, geometry, field_data = read(naturalearth_lowres)
625
647
 
626
648
  # GML does not support append functionality
627
- filename = os.path.join(str(tmpdir), f"test{ext}")
649
+ filename = tmp_path / f"test{ext}"
628
650
  write(filename, geometry, field_data, driver=driver, **meta)
629
651
 
630
- assert os.path.exists(filename)
652
+ assert filename.exists()
631
653
 
632
654
  assert read_info(filename, force_feature_count=True)["features"] == 177
633
655
 
@@ -639,16 +661,16 @@ def test_write_append_unsupported(tmpdir, naturalearth_lowres, driver, ext):
639
661
  __gdal_version__ > (3, 5, 0),
640
662
  reason="segfaults on FlatGeobuf limited to GDAL <= 3.5.0",
641
663
  )
642
- def test_write_append_prevent_gdal_segfault(tmpdir, naturalearth_lowres):
664
+ def test_write_append_prevent_gdal_segfault(tmp_path, naturalearth_lowres):
643
665
  """GDAL <= 3.5.0 segfaults when appending to FlatGeobuf; this test
644
666
  verifies that we catch that before segfault"""
645
667
  meta, _, geometry, field_data = read(naturalearth_lowres)
646
668
  meta["geometry_type"] = "MultiPolygon"
647
669
 
648
- filename = os.path.join(str(tmpdir), "test.fgb")
670
+ filename = tmp_path / "test.fgb"
649
671
  write(filename, geometry, field_data, **meta)
650
672
 
651
- assert os.path.exists(filename)
673
+ assert filename.exists()
652
674
 
653
675
  with pytest.raises(
654
676
  RuntimeError, # match="append to FlatGeobuf is not supported for GDAL <= 3.5.0"
@@ -664,7 +686,7 @@ def test_write_append_prevent_gdal_segfault(tmpdir, naturalearth_lowres):
664
686
  if driver not in ("ESRI Shapefile", "GPKG", "GeoJSON")
665
687
  },
666
688
  )
667
- def test_write_supported(tmpdir, naturalearth_lowres, driver):
689
+ def test_write_supported(tmp_path, naturalearth_lowres, driver):
668
690
  """Test drivers known to work that are not specifically tested above"""
669
691
  meta, _, geometry, field_data = read(naturalearth_lowres, columns=["iso_a3"])
670
692
 
@@ -673,7 +695,7 @@ def test_write_supported(tmpdir, naturalearth_lowres, driver):
673
695
  # we take the first record only.
674
696
  meta["geometry_type"] = "MultiPolygon"
675
697
 
676
- filename = tmpdir / f"test{DRIVER_EXT[driver]}"
698
+ filename = tmp_path / f"test{DRIVER_EXT[driver]}"
677
699
  write(
678
700
  filename,
679
701
  geometry[:1],
@@ -688,10 +710,10 @@ def test_write_supported(tmpdir, naturalearth_lowres, driver):
688
710
  @pytest.mark.skipif(
689
711
  __gdal_version__ >= (3, 6, 0), reason="OpenFileGDB supports write for GDAL >= 3.6.0"
690
712
  )
691
- def test_write_unsupported(tmpdir, naturalearth_lowres):
713
+ def test_write_unsupported(tmp_path, naturalearth_lowres):
692
714
  meta, _, geometry, field_data = read(naturalearth_lowres)
693
715
 
694
- filename = os.path.join(str(tmpdir), "test.gdb")
716
+ filename = tmp_path / "test.gdb"
695
717
 
696
718
  with pytest.raises(DataSourceError, match="does not support write functionality"):
697
719
  write(filename, geometry, field_data, driver="OpenFileGDB", **meta)
@@ -734,10 +756,10 @@ def assert_equal_result(result1, result2):
734
756
 
735
757
  @pytest.mark.filterwarnings("ignore:File /vsimem:RuntimeWarning") # TODO
736
758
  @pytest.mark.parametrize("driver,ext", [("GeoJSON", "geojson"), ("GPKG", "gpkg")])
737
- def test_read_from_bytes(tmpdir, naturalearth_lowres, driver, ext):
759
+ def test_read_from_bytes(tmp_path, naturalearth_lowres, driver, ext):
738
760
  meta, index, geometry, field_data = read(naturalearth_lowres)
739
761
  meta.update({"geometry_type": "Unknown"})
740
- filename = os.path.join(str(tmpdir), f"test.{ext}")
762
+ filename = tmp_path / f"test.{ext}"
741
763
  write(filename, geometry, field_data, driver=driver, **meta)
742
764
 
743
765
  with open(filename, "rb") as f:
@@ -747,7 +769,7 @@ def test_read_from_bytes(tmpdir, naturalearth_lowres, driver, ext):
747
769
  assert_equal_result((meta, index, geometry, field_data), result2)
748
770
 
749
771
 
750
- def test_read_from_bytes_zipped(tmpdir, naturalearth_lowres_vsi):
772
+ def test_read_from_bytes_zipped(naturalearth_lowres_vsi):
751
773
  path, vsi_path = naturalearth_lowres_vsi
752
774
  meta, index, geometry, field_data = read(vsi_path)
753
775
 
@@ -760,10 +782,10 @@ def test_read_from_bytes_zipped(tmpdir, naturalearth_lowres_vsi):
760
782
 
761
783
  @pytest.mark.filterwarnings("ignore:File /vsimem:RuntimeWarning") # TODO
762
784
  @pytest.mark.parametrize("driver,ext", [("GeoJSON", "geojson"), ("GPKG", "gpkg")])
763
- def test_read_from_file_like(tmpdir, naturalearth_lowres, driver, ext):
785
+ def test_read_from_file_like(tmp_path, naturalearth_lowres, driver, ext):
764
786
  meta, index, geometry, field_data = read(naturalearth_lowres)
765
787
  meta.update({"geometry_type": "Unknown"})
766
- filename = os.path.join(str(tmpdir), f"test.{ext}")
788
+ filename = tmp_path / f"test.{ext}"
767
789
  write(filename, geometry, field_data, driver=driver, **meta)
768
790
 
769
791
  with open(filename, "rb") as f:
@@ -972,11 +994,11 @@ def test_write_float_nan_null(tmp_path, dtype):
972
994
  field_data = [np.array([1.5, np.nan], dtype=dtype)]
973
995
  fields = ["col"]
974
996
  meta = dict(geometry_type="Point", crs="EPSG:4326")
975
- fname = tmp_path / "test.geojson"
997
+ filename = tmp_path / "test.geojson"
976
998
 
977
999
  # default nan_as_null=True
978
- write(fname, geometry, field_data, fields, **meta)
979
- with open(str(fname), "r") as f:
1000
+ write(filename, geometry, field_data, fields, **meta)
1001
+ with open(filename, "r") as f:
980
1002
  content = f.read()
981
1003
  assert '{ "col": null }' in content
982
1004
 
@@ -987,14 +1009,14 @@ def test_write_float_nan_null(tmp_path, dtype):
987
1009
  else:
988
1010
  ctx = contextlib.nullcontext()
989
1011
  with ctx:
990
- write(fname, geometry, field_data, fields, **meta, nan_as_null=False)
991
- with open(str(fname), "r") as f:
1012
+ write(filename, geometry, field_data, fields, **meta, nan_as_null=False)
1013
+ with open(filename, "r") as f:
992
1014
  content = f.read()
993
1015
  assert '"properties": { }' in content
994
1016
 
995
1017
  # but can instruct GDAL to write NaN to json
996
1018
  write(
997
- fname,
1019
+ filename,
998
1020
  geometry,
999
1021
  field_data,
1000
1022
  fields,
@@ -1002,12 +1024,12 @@ def test_write_float_nan_null(tmp_path, dtype):
1002
1024
  nan_as_null=False,
1003
1025
  WRITE_NON_FINITE_VALUES="YES",
1004
1026
  )
1005
- with open(str(fname), "r") as f:
1027
+ with open(filename, "r") as f:
1006
1028
  content = f.read()
1007
1029
  assert '{ "col": NaN }' in content
1008
1030
 
1009
1031
 
1010
- @requires_arrow_api
1032
+ @requires_pyarrow_api
1011
1033
  @pytest.mark.skipif(
1012
1034
  "Arrow" not in list_drivers(), reason="Arrow driver is not available"
1013
1035
  )
@@ -1039,6 +1061,91 @@ def test_write_float_nan_null_arrow(tmp_path):
1039
1061
  assert pc.is_nan(table["col"]).to_pylist() == [False, True]
1040
1062
 
1041
1063
 
1064
+ @pytest.mark.filterwarnings("ignore:File /vsimem:RuntimeWarning")
1065
+ @pytest.mark.parametrize("driver", ["GeoJSON", "GPKG"])
1066
+ def test_write_memory(naturalearth_lowres, driver):
1067
+ meta, _, geometry, field_data = read(naturalearth_lowres)
1068
+ meta.update({"geometry_type": "MultiPolygon"})
1069
+
1070
+ buffer = BytesIO()
1071
+ write(buffer, geometry, field_data, driver=driver, layer="test", **meta)
1072
+
1073
+ assert len(buffer.getbuffer()) > 0
1074
+ assert list_layers(buffer)[0][0] == "test"
1075
+
1076
+ actual_meta, _, actual_geometry, actual_field_data = read(buffer)
1077
+
1078
+ assert np.array_equal(actual_meta["fields"], meta["fields"])
1079
+ assert np.array_equal(actual_field_data, field_data)
1080
+ assert len(actual_geometry) == len(geometry)
1081
+
1082
+
1083
+ def test_write_memory_driver_required(naturalearth_lowres):
1084
+ meta, _, geometry, field_data = read(naturalearth_lowres)
1085
+
1086
+ buffer = BytesIO()
1087
+ with pytest.raises(
1088
+ ValueError,
1089
+ match="driver must be provided to write to in-memory file",
1090
+ ):
1091
+ write(buffer, geometry, field_data, driver=None, layer="test", **meta)
1092
+
1093
+
1094
+ @pytest.mark.parametrize("driver", ["ESRI Shapefile", "OpenFileGDB"])
1095
+ def test_write_memory_unsupported_driver(naturalearth_lowres, driver):
1096
+ if driver == "OpenFileGDB" and __gdal_version__ < (3, 6, 0):
1097
+ pytest.skip("OpenFileGDB write support only available for GDAL >= 3.6.0")
1098
+
1099
+ meta, _, geometry, field_data = read(naturalearth_lowres)
1100
+
1101
+ buffer = BytesIO()
1102
+
1103
+ with pytest.raises(
1104
+ ValueError, match=f"writing to in-memory file is not supported for {driver}"
1105
+ ):
1106
+ write(
1107
+ buffer,
1108
+ geometry,
1109
+ field_data,
1110
+ driver=driver,
1111
+ layer="test",
1112
+ append=True,
1113
+ **meta,
1114
+ )
1115
+
1116
+
1117
+ @pytest.mark.parametrize("driver", ["GeoJSON", "GPKG"])
1118
+ def test_write_memory_append_unsupported(naturalearth_lowres, driver):
1119
+ meta, _, geometry, field_data = read(naturalearth_lowres)
1120
+ meta.update({"geometry_type": "MultiPolygon"})
1121
+
1122
+ buffer = BytesIO()
1123
+
1124
+ with pytest.raises(
1125
+ NotImplementedError, match="append is not supported for in-memory files"
1126
+ ):
1127
+ write(
1128
+ buffer,
1129
+ geometry,
1130
+ field_data,
1131
+ driver=driver,
1132
+ layer="test",
1133
+ append=True,
1134
+ **meta,
1135
+ )
1136
+
1137
+
1138
+ def test_write_memory_existing_unsupported(naturalearth_lowres):
1139
+ meta, _, geometry, field_data = read(naturalearth_lowres)
1140
+
1141
+ buffer = BytesIO(b"0000")
1142
+ with pytest.raises(
1143
+ NotImplementedError,
1144
+ match="writing to existing in-memory object is not supported",
1145
+ ):
1146
+ write(buffer, geometry, field_data, driver="GeoJSON", layer="test", **meta)
1147
+
1148
+
1042
1149
  @pytest.mark.parametrize("ext", ["fgb", "gpkg", "geojson"])
1043
1150
  @pytest.mark.parametrize(
1044
1151
  "read_encoding,write_encoding",
@@ -1141,7 +1248,7 @@ def test_encoding_io_shapefile(tmp_path, read_encoding, write_encoding):
1141
1248
  # verify that if cpg file is not present, that user-provided encoding is used,
1142
1249
  # otherwise it defaults to ISO-8859-1
1143
1250
  if read_encoding is not None:
1144
- os.unlink(str(filename).replace(".shp", ".cpg"))
1251
+ filename.with_suffix(".cpg").unlink()
1145
1252
  actual_meta, _, _, actual_field_data = read(filename, encoding=read_encoding)
1146
1253
  assert np.array_equal(fields, actual_meta["fields"])
1147
1254
  assert np.array_equal(field_data, actual_field_data)
@@ -1150,6 +1257,97 @@ def test_encoding_io_shapefile(tmp_path, read_encoding, write_encoding):
1150
1257
  )
1151
1258
 
1152
1259
 
1260
+ @pytest.mark.parametrize("ext", ["gpkg", "geojson"])
1261
+ def test_non_utf8_encoding_io(tmp_path, ext, encoded_text):
1262
+ """Verify that we write non-UTF data to the data source
1263
+
1264
+ IMPORTANT: this may not be valid for the data source and will likely render
1265
+ them unusable in other tools, but should successfully roundtrip unless we
1266
+ disable writing using other encodings.
1267
+
1268
+ NOTE: FlatGeobuff driver cannot handle non-UTF data in GDAL >= 3.9
1269
+ """
1270
+ encoding, text = encoded_text
1271
+
1272
+ # Point(0, 0)
1273
+ geometry = np.array(
1274
+ [bytes.fromhex("010100000000000000000000000000000000000000")], dtype=object
1275
+ )
1276
+
1277
+ field_data = [np.array([text], dtype=object)]
1278
+
1279
+ fields = [text]
1280
+ meta = dict(geometry_type="Point", crs="EPSG:4326", encoding=encoding)
1281
+
1282
+ filename = tmp_path / f"test.{ext}"
1283
+ write(filename, geometry, field_data, fields, **meta)
1284
+
1285
+ # cannot open these files without specifying encoding
1286
+ with pytest.raises(UnicodeDecodeError):
1287
+ read(filename)
1288
+
1289
+ with pytest.raises(UnicodeDecodeError):
1290
+ read_info(filename)
1291
+
1292
+ # must provide encoding to read these properly
1293
+ actual_meta, _, _, actual_field_data = read(filename, encoding=encoding)
1294
+ assert actual_meta["fields"][0] == text
1295
+ assert actual_field_data[0] == text
1296
+ assert read_info(filename, encoding=encoding)["fields"][0] == text
1297
+
1298
+
1299
+ def test_non_utf8_encoding_io_shapefile(tmp_path, encoded_text):
1300
+ encoding, text = encoded_text
1301
+
1302
+ # Point(0, 0)
1303
+ geometry = np.array(
1304
+ [bytes.fromhex("010100000000000000000000000000000000000000")], dtype=object
1305
+ )
1306
+
1307
+ field_data = [np.array([text], dtype=object)]
1308
+
1309
+ fields = [text]
1310
+ meta = dict(geometry_type="Point", crs="EPSG:4326", encoding=encoding)
1311
+
1312
+ filename = tmp_path / "test.shp"
1313
+ write(filename, geometry, field_data, fields, **meta)
1314
+
1315
+ # NOTE: GDAL automatically creates a cpg file with the encoding name, which
1316
+ # means that if we read this without specifying the encoding it uses the
1317
+ # correct one
1318
+ actual_meta, _, _, actual_field_data = read(filename)
1319
+ assert actual_meta["fields"][0] == text
1320
+ assert actual_field_data[0] == text
1321
+ assert read_info(filename)["fields"][0] == text
1322
+
1323
+ # verify that if cpg file is not present, that user-provided encoding must be used
1324
+ filename.with_suffix(".cpg").unlink()
1325
+
1326
+ # We will assume ISO-8859-1, which is wrong
1327
+ miscoded = text.encode(encoding).decode("ISO-8859-1")
1328
+ bad_meta, _, _, bad_field_data = read(filename)
1329
+ assert bad_meta["fields"][0] == miscoded
1330
+ assert bad_field_data[0] == miscoded
1331
+ assert read_info(filename)["fields"][0] == miscoded
1332
+
1333
+ # If encoding is provided, that should yield correct text
1334
+ actual_meta, _, _, actual_field_data = read(filename, encoding=encoding)
1335
+ assert actual_meta["fields"][0] == text
1336
+ assert actual_field_data[0] == text
1337
+ assert read_info(filename, encoding=encoding)["fields"][0] == text
1338
+
1339
+ # verify that setting encoding does not corrupt SHAPE_ENCODING option if set
1340
+ # globally (it is ignored during read when encoding is specified by user)
1341
+ try:
1342
+ set_gdal_config_options({"SHAPE_ENCODING": "CP1254"})
1343
+ _ = read(filename, encoding=encoding)
1344
+ assert get_gdal_config_option("SHAPE_ENCODING") == "CP1254"
1345
+
1346
+ finally:
1347
+ # reset to clear between tests
1348
+ set_gdal_config_options({"SHAPE_ENCODING": None})
1349
+
1350
+
1153
1351
  def test_write_with_mask(tmp_path):
1154
1352
  # Point(0, 0), null
1155
1353
  geometry = np.array(
@@ -1176,3 +1374,31 @@ def test_write_with_mask(tmp_path):
1176
1374
  field_mask = [np.array([False, True, False])] * 2
1177
1375
  with pytest.raises(ValueError):
1178
1376
  write(filename, geometry, field_data, fields, field_mask, **meta)
1377
+
1378
+
1379
+ @requires_arrow_api
1380
+ def test_open_arrow_capsule_protocol_without_pyarrow(naturalearth_lowres):
1381
+ # this test is included here instead of test_arrow.py to ensure we also run
1382
+ # it when pyarrow is not installed
1383
+
1384
+ with open_arrow(naturalearth_lowres) as (meta, reader):
1385
+ assert isinstance(meta, dict)
1386
+ assert isinstance(reader, pyogrio._io._ArrowStream)
1387
+ capsule = reader.__arrow_c_stream__()
1388
+ assert (
1389
+ ctypes.pythonapi.PyCapsule_IsValid(
1390
+ ctypes.py_object(capsule), b"arrow_array_stream"
1391
+ )
1392
+ == 1
1393
+ )
1394
+
1395
+
1396
+ @pytest.mark.skipif(HAS_PYARROW, reason="pyarrow is installed")
1397
+ @requires_arrow_api
1398
+ def test_open_arrow_error_no_pyarrow(naturalearth_lowres):
1399
+ # this test is included here instead of test_arrow.py to ensure we run
1400
+ # it when pyarrow is not installed
1401
+
1402
+ with pytest.raises(ImportError):
1403
+ with open_arrow(naturalearth_lowres, use_pyarrow=True) as _:
1404
+ pass