From 68e4e607acf5339e65854d263dd261b03f66b0ed Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Sat, 9 Aug 2025 23:46:28 +0200 Subject: [PATCH 1/9] ENH: add whether append is supported in list_drivers --- pyogrio/_ogr.pyx | 19 ++++++++++++++----- pyogrio/tests/test_core.py | 9 ++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/pyogrio/_ogr.pyx b/pyogrio/_ogr.pyx index 404029dd..3efac9a4 100644 --- a/pyogrio/_ogr.pyx +++ b/pyogrio/_ogr.pyx @@ -100,6 +100,14 @@ def get_gdal_config_option(str name): return str_value +def ogr_driver_supports_update(driver): + # check metadata for driver to see if it supports update + if _get_driver_metadata_item(driver, "DCAP_UPDATE") == "YES": + return True + + return False + + def ogr_driver_supports_write(driver): # check metadata for driver to see if it supports write if _get_driver_metadata_item(driver, "DCAP_CREATE") == "YES": @@ -125,14 +133,15 @@ def ogr_list_drivers(): for i in range(OGRGetDriverCount()): driver = OGRGetDriver(i) name_c = OGR_Dr_GetName(driver) - name = get_string(name_c) + capability = "r" + IF CTE_GDAL_VERSION >= (3, 11, 0): + if ogr_driver_supports_update(name): + capability += "a" if ogr_driver_supports_write(name): - drivers[name] = "rw" - - else: - drivers[name] = "r" + capability += "w" + drivers[name] = capability return drivers diff --git a/pyogrio/tests/test_core.py b/pyogrio/tests/test_core.py index e428beb9..52532f9f 100644 --- a/pyogrio/tests/test_core.py +++ b/pyogrio/tests/test_core.py @@ -18,7 +18,7 @@ vsi_rmtree, vsi_unlink, ) -from pyogrio._compat import GDAL_GE_38 +from pyogrio._compat import GDAL_GE_38, GDAL_GE_311 from pyogrio._env import GDALEnv from pyogrio.errors import DataLayerError, DataSourceError from pyogrio.raw import read, write @@ -136,11 +136,14 @@ def test_list_drivers(): for name in ("ESRI Shapefile", "GeoJSON", "GeoJSONSeq", "GPKG", "OpenFileGDB"): assert name in all_drivers - expected_capability = "rw" + if GDAL_GE_311 and name in ("ESRI Shapefile", "GeoJSON", "GPKG", "OpenFileGDB"): + expected_capability = "raw" + else: + expected_capability = "rw" if name == "OpenFileGDB" and __gdal_version__ < (3, 6, 0): expected_capability = "r" - assert all_drivers[name] == expected_capability + assert all_drivers[name] == expected_capability, f"Error for {name}" drivers = list_drivers(read=True) expected = {k: v for k, v in all_drivers.items() if v.startswith("r")} From 5a85bd38ab95bfe853f021c763762e5f688b72c0 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Sun, 10 Aug 2025 00:31:58 +0200 Subject: [PATCH 2/9] Update test_core.py --- pyogrio/tests/test_core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyogrio/tests/test_core.py b/pyogrio/tests/test_core.py index 52532f9f..8fcb79ba 100644 --- a/pyogrio/tests/test_core.py +++ b/pyogrio/tests/test_core.py @@ -136,7 +136,7 @@ def test_list_drivers(): for name in ("ESRI Shapefile", "GeoJSON", "GeoJSONSeq", "GPKG", "OpenFileGDB"): assert name in all_drivers - if GDAL_GE_311 and name in ("ESRI Shapefile", "GeoJSON", "GPKG", "OpenFileGDB"): + if GDAL_GE_311: expected_capability = "raw" else: expected_capability = "rw" From a296c0fd08d5f5958f814690c3ff4a1b0ad52022 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Thu, 13 Nov 2025 02:02:27 +0100 Subject: [PATCH 3/9] Add support + test --- pyogrio/_compat.py | 1 + pyogrio/_ogr.pyx | 20 ++++++++++-- pyogrio/core.py | 10 ++++-- pyogrio/tests/test_core.py | 64 +++++++++++++++++++++++++++++++++----- 4 files changed, 82 insertions(+), 13 deletions(-) diff --git a/pyogrio/_compat.py b/pyogrio/_compat.py index 0e4650de..c3449c4e 100644 --- a/pyogrio/_compat.py +++ b/pyogrio/_compat.py @@ -48,6 +48,7 @@ GDAL_GE_37 = __gdal_version__ >= (3, 7, 0) GDAL_GE_38 = __gdal_version__ >= (3, 8, 0) GDAL_GE_311 = __gdal_version__ >= (3, 11, 0) +GDAL_GE_312 = __gdal_version__ >= (3, 12, 0) HAS_GDAL_GEOS = __gdal_geos_version__ is not None diff --git a/pyogrio/_ogr.pyx b/pyogrio/_ogr.pyx index 3efac9a4..c70df4d6 100644 --- a/pyogrio/_ogr.pyx +++ b/pyogrio/_ogr.pyx @@ -108,6 +108,14 @@ def ogr_driver_supports_update(driver): return False +def ogr_driver_supports_append(driver): + # check metadata for driver to see if it supports append + if _get_driver_metadata_item(driver, "DCAP_APPEND") == "YES": + return True + + return False + + def ogr_driver_supports_write(driver): # check metadata for driver to see if it supports write if _get_driver_metadata_item(driver, "DCAP_CREATE") == "YES": @@ -136,11 +144,17 @@ def ogr_list_drivers(): name = get_string(name_c) capability = "r" - IF CTE_GDAL_VERSION >= (3, 11, 0): - if ogr_driver_supports_update(name): - capability += "a" + + if ogr_driver_supports_update(name): + capability += "a" + else: + IF CTE_GDAL_VERSION >= (3, 12, 0): + if ogr_driver_supports_append(name): + capability += "a" + if ogr_driver_supports_write(name): capability += "w" + drivers[name] = capability return drivers diff --git a/pyogrio/core.py b/pyogrio/core.py index 1fa18fa4..c3973adf 100644 --- a/pyogrio/core.py +++ b/pyogrio/core.py @@ -42,7 +42,7 @@ __gdal_geos_version__ = get_gdal_geos_version() -def list_drivers(read=False, write=False): +def list_drivers(read=False, write=False, append=False): """List drivers available in GDAL. Parameters @@ -51,11 +51,14 @@ def list_drivers(read=False, write=False): If True, will only return drivers that are known to support read capabilities. write: bool, optional (default: False) If True, will only return drivers that are known to support write capabilities. + append: bool, optional (default: False) + If True, will only return drivers that are known to support append capabilities. Returns ------- dict - Mapping of driver name to file mode capabilities: ``"r"``: read, ``"w"``: write. + Mapping of driver name to file mode capabilities: ``"r"``: read, + ``"a"``: append, ``"w"``: write. Drivers that are available but with unknown support are marked with ``"?"`` """ @@ -67,6 +70,9 @@ def list_drivers(read=False, write=False): if write: drivers = {k: v for k, v in drivers.items() if v.endswith("w")} + if append: + drivers = {k: v for k, v in drivers.items() if "a" in v} + return drivers diff --git a/pyogrio/tests/test_core.py b/pyogrio/tests/test_core.py index 8fcb79ba..ae2a6c2b 100644 --- a/pyogrio/tests/test_core.py +++ b/pyogrio/tests/test_core.py @@ -18,7 +18,7 @@ vsi_rmtree, vsi_unlink, ) -from pyogrio._compat import GDAL_GE_38, GDAL_GE_311 +from pyogrio._compat import GDAL_GE_38, GDAL_GE_312 from pyogrio._env import GDALEnv from pyogrio.errors import DataLayerError, DataSourceError from pyogrio.raw import read, write @@ -132,23 +132,63 @@ def test_ogr_driver_supports_write(driver, expected): def test_list_drivers(): all_drivers = list_drivers() + # Expected capabilities based on `fiona.supported_drivers`. + expected_drivers: dict[str, str] = { + "AeronavFAA": "r", + "ARCGEN": "r", + "BNA": "rw", + "DXF": "rw", + "CSV": "raw", + "FileGDB": "raw", + "OpenFileGDB": "raw", + "ESRIJSON": "r", + "ESRI Shapefile": "raw", + "FlatGeobuf": "rw", # Changed: "raw" to "rw": append only if no spatial index + "GeoJSON": "raw", + "GeoJSONSeq": "raw", + "GPKG": "raw", + "GML": "rw", + "GMT": "rw", + "OGR_GMT": "rw", + "GPX": "rw", + "Idrisi": "r", + "MapInfo File": "raw", + "DGN": "rw", # Changed: "raw" to "rw": unclear if append is possible + "Parquet": "rw", + "PCIDSK": "raw", + "PDS": "r", + "OGR_PDS": "r", + "S57": "rw", # Changed: "r" to "rw": create supported according to GDAL docs + "SEGY": "r", + "SQLite": "raw", + "SUA": "r", + "TileDB": "raw", + "TopoJSON": "r", + } + # verify that the core drivers are present - for name in ("ESRI Shapefile", "GeoJSON", "GeoJSONSeq", "GPKG", "OpenFileGDB"): - assert name in all_drivers + for name, expected_capability in expected_drivers.items(): + if name not in all_drivers: + print(f"{name} not in list_drivers(), ignore") + continue - if GDAL_GE_311: - expected_capability = "raw" - else: - expected_capability = "rw" + if not GDAL_GE_312: + expected_capability = expected_capability.replace("a", "") if name == "OpenFileGDB" and __gdal_version__ < (3, 6, 0): expected_capability = "r" - assert all_drivers[name] == expected_capability, f"Error for {name}" + assert all_drivers[name] == expected_capability, ( + f"Error for {name}: {expected_capability=}, {all_drivers[name]=}" + ) drivers = list_drivers(read=True) expected = {k: v for k, v in all_drivers.items() if v.startswith("r")} assert len(drivers) == len(expected) + drivers = list_drivers(append=True) + expected = {k: v for k, v in all_drivers.items() if "a" in v} + assert len(drivers) == len(expected) + drivers = list_drivers(write=True) expected = {k: v for k, v in all_drivers.items() if v.endswith("w")} assert len(drivers) == len(expected) @@ -159,6 +199,14 @@ def test_list_drivers(): } assert len(drivers) == len(expected) + drivers = list_drivers(read=True, write=True, append=True) + expected = { + k: v + for k, v in all_drivers.items() + if v.startswith("r") and v.endswith("w") and "a" in v + } + assert len(drivers) == len(expected) + def test_list_layers( naturalearth_lowres, From 11b25c0123a0a3b359e0774f4090555f021b502e Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Thu, 13 Nov 2025 02:12:51 +0100 Subject: [PATCH 4/9] Update _ogr.pyx --- pyogrio/_ogr.pyx | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyogrio/_ogr.pyx b/pyogrio/_ogr.pyx index c70df4d6..b308e051 100644 --- a/pyogrio/_ogr.pyx +++ b/pyogrio/_ogr.pyx @@ -145,12 +145,13 @@ def ogr_list_drivers(): capability = "r" - if ogr_driver_supports_update(name): - capability += "a" - else: - IF CTE_GDAL_VERSION >= (3, 12, 0): - if ogr_driver_supports_append(name): - capability += "a" + IF CTE_GDAL_VERSION >= (3, 11, 0): + if ogr_driver_supports_update(name): + capability += "a" + else: + IF CTE_GDAL_VERSION >= (3, 12, 0): + if ogr_driver_supports_append(name): + capability += "a" if ogr_driver_supports_write(name): capability += "w" From 8fb674ba203f0a7ff35b72c3a58e86f959f0d473 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Thu, 13 Nov 2025 02:19:24 +0100 Subject: [PATCH 5/9] Update core.py --- pyogrio/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyogrio/core.py b/pyogrio/core.py index c3973adf..d370216d 100644 --- a/pyogrio/core.py +++ b/pyogrio/core.py @@ -59,6 +59,7 @@ def list_drivers(read=False, write=False, append=False): dict Mapping of driver name to file mode capabilities: ``"r"``: read, ``"a"``: append, ``"w"``: write. + The flag to indicate append support (``"a"``) is available for GDAL >= 3.11. Drivers that are available but with unknown support are marked with ``"?"`` """ From cbfc6ec9eb6b01ab4b66465a13e50be953fff842 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Thu, 13 Nov 2025 02:24:03 +0100 Subject: [PATCH 6/9] Add versionadded to doc --- pyogrio/core.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyogrio/core.py b/pyogrio/core.py index d370216d..f24a90e6 100644 --- a/pyogrio/core.py +++ b/pyogrio/core.py @@ -53,15 +53,18 @@ def list_drivers(read=False, write=False, append=False): If True, will only return drivers that are known to support write capabilities. append: bool, optional (default: False) If True, will only return drivers that are known to support append capabilities. + .. versionadded:: 0.13.0 Returns ------- dict Mapping of driver name to file mode capabilities: ``"r"``: read, ``"a"``: append, ``"w"``: write. - The flag to indicate append support (``"a"``) is available for GDAL >= 3.11. Drivers that are available but with unknown support are marked with ``"?"`` + .. versionchanged:: 0.13.0 + Added the ``a`` flag, which is available for GDAL >= 3.11. + """ drivers = ogr_list_drivers() From 353ec632fff2fa23ac66e9d6b4d55baf2a366c48 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Thu, 13 Nov 2025 02:28:19 +0100 Subject: [PATCH 7/9] Update _ogr.pyx --- pyogrio/_ogr.pyx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/pyogrio/_ogr.pyx b/pyogrio/_ogr.pyx index b308e051..d1da2f55 100644 --- a/pyogrio/_ogr.pyx +++ b/pyogrio/_ogr.pyx @@ -102,16 +102,18 @@ def get_gdal_config_option(str name): def ogr_driver_supports_update(driver): # check metadata for driver to see if it supports update - if _get_driver_metadata_item(driver, "DCAP_UPDATE") == "YES": - return True + IF CTE_GDAL_VERSION >= (3, 11, 0): + if _get_driver_metadata_item(driver, "DCAP_UPDATE") == "YES": + return True return False def ogr_driver_supports_append(driver): # check metadata for driver to see if it supports append - if _get_driver_metadata_item(driver, "DCAP_APPEND") == "YES": - return True + IF CTE_GDAL_VERSION >= (3, 12, 0): + if _get_driver_metadata_item(driver, "DCAP_APPEND") == "YES": + return True return False @@ -145,13 +147,11 @@ def ogr_list_drivers(): capability = "r" - IF CTE_GDAL_VERSION >= (3, 11, 0): - if ogr_driver_supports_update(name): + if ogr_driver_supports_update(name): + capability += "a" + else: + if ogr_driver_supports_append(name): capability += "a" - else: - IF CTE_GDAL_VERSION >= (3, 12, 0): - if ogr_driver_supports_append(name): - capability += "a" if ogr_driver_supports_write(name): capability += "w" From 6f61b7b3b0ac529db649aaab57014b63401d95ab Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Thu, 13 Nov 2025 02:31:41 +0100 Subject: [PATCH 8/9] Update CHANGES.md --- CHANGES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 7cae5d8e..df2c08c9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,11 @@ # CHANGELOG +## 0.13.0 (xxxx-xx-xx) + +### Improvements + +- Add whether append is supported in `list_drivers` (#559) + ## 0.12.0 (xxxx-xx-xx) ### Potentially breaking changes From e59c633391f02f2e987f7aec071d478f1786ab82 Mon Sep 17 00:00:00 2001 From: Pieter Roggemans Date: Thu, 13 Nov 2025 02:33:11 +0100 Subject: [PATCH 9/9] Update _ogr.pyx --- pyogrio/_ogr.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyogrio/_ogr.pyx b/pyogrio/_ogr.pyx index 96d04085..e8b54162 100644 --- a/pyogrio/_ogr.pyx +++ b/pyogrio/_ogr.pyx @@ -148,7 +148,7 @@ def ogr_list_drivers(): else: if ogr_driver_supports_append(name): capability += "a" - + if ogr_driver_supports_write(name): capability += "w"