Skip to content

Commit 61c9fa3

Browse files
ENH: raise Python warnings instead of printing to stderr for GDAL Warning messages (#242)
1 parent 691fe85 commit 61c9fa3

File tree

9 files changed

+58
-18
lines changed

9 files changed

+58
-18
lines changed

CHANGES.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,9 @@
1212
specifying a mask manually for missing values in `write` (#219)
1313
- Standardized 3-dimensional geometry type labels from "2.5D <type>" to
1414
"<type> Z" for consistency with well-known text (WKT) formats (#234)
15-
- Failure error messages from GDAL are no longer printed to stderr (they were
16-
already translated into Python exceptions as well) (#236).
15+
- Failure and warning error messages from GDAL are no longer printed to
16+
stderr: failures were already translated into Python exceptions
17+
and warning messages are now translated into Python warnings (#236, #242).
1718

1819
### Packaging
1920

pyogrio/_err.pyx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# ported from fiona::_err.pyx
22
from enum import IntEnum
3+
import warnings
34

45
from pyogrio._ogr cimport (
56
CE_None, CE_Debug, CE_Warning, CE_Failure, CE_Fatal, CPLErrorReset,
@@ -229,6 +230,13 @@ cdef void error_handler(CPLErr err_class, int err_no, const char* err_msg) nogil
229230
# with error return codes and translated into Python exceptions
230231
return
231232

233+
elif err_class == CE_Warning:
234+
with gil:
235+
msg_b = err_msg
236+
msg = msg_b.decode('utf-8')
237+
warnings.warn(msg, RuntimeWarning)
238+
return
239+
232240
# Fall back to the default handler for non-failure messages since
233241
# they won't be translated into exceptions.
234242
CPLDefaultErrorHandler(err_class, err_no, err_msg)

pyogrio/core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def read_info(path_or_buffer, /, layer=None, encoding=None, **kwargs):
165165
source.
166166
**kwargs
167167
Additional driver-specific dataset open options passed to OGR. Invalid
168-
options are logged by OGR to stderr and are not captured.
168+
options will trigger a warning.
169169
170170
Returns
171171
-------

pyogrio/geopandas.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def read_dataframe(
124124
installed). When enabled, this provides a further speed-up.
125125
**kwargs
126126
Additional driver-specific dataset open options passed to OGR. Invalid
127-
options are logged by OGR to stderr and are not captured.
127+
options will trigger a warning.
128128
129129
Returns
130130
-------

pyogrio/raw.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def read(
110110
If True, will return the FIDs of the feature that were read.
111111
**kwargs
112112
Additional driver-specific dataset open options passed to OGR. Invalid
113-
options are logged by OGR to stderr and are not captured.
113+
options will trigger a warning.
114114
115115
Returns
116116
-------

pyogrio/tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ def prepare_testfile(testfile_path, dst_dir, ext):
4242
elif ext == ".gpkg":
4343
# For .gpkg, spatial_index=False to avoid the rows being reordered
4444
meta["spatial_index"] = False
45+
meta["geometry_type"] = "MultiPolygon"
4546

4647
write(dst_path, geometry, field_data, **meta)
4748
return dst_path

pyogrio/tests/test_core.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,9 @@ def test_read_info_dataset_kwargs(data_dir, dataset_kwargs, fields):
263263
assert meta["fields"].tolist() == fields
264264

265265

266-
def test_read_info_invalid_dataset_kwargs(capfd, naturalearth_lowres):
267-
read_info(naturalearth_lowres, INVALID="YES")
268-
assert "does not support open option INVALID" in capfd.readouterr().err
266+
def test_read_info_invalid_dataset_kwargs(naturalearth_lowres):
267+
with pytest.warns(RuntimeWarning, match="does not support open option INVALID"):
268+
read_info(naturalearth_lowres, INVALID="YES")
269269

270270

271271
@pytest.mark.parametrize(
@@ -298,3 +298,12 @@ def test_error_handling(capfd):
298298
read_info("non-existent.shp")
299299

300300
assert capfd.readouterr().err == ""
301+
302+
303+
def test_error_handling_warning(capfd, naturalearth_lowres):
304+
# an operation that triggers a GDAL Warning
305+
# -> translated into a Python warning + not printed to stderr
306+
with pytest.warns(RuntimeWarning, match="does not support open option INVALID"):
307+
read_info(naturalearth_lowres, INVALID="YES")
308+
309+
assert capfd.readouterr().err == ""

pyogrio/tests/test_geopandas_io.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
from datetime import datetime
23
import os
34
from packaging.version import Version
@@ -649,12 +650,21 @@ def test_write_dataframe_promote_to_multi_layer_geom_type(
649650
input_gdf = read_dataframe(naturalearth_lowres)
650651

651652
output_path = tmp_path / f"test_promote_layer_geom_type{ext}"
652-
write_dataframe(
653-
input_gdf,
654-
output_path,
655-
promote_to_multi=promote_to_multi,
656-
geometry_type=geometry_type,
657-
)
653+
654+
if ext == ".gpkg" and geometry_type in ("Polygon", "Point"):
655+
ctx = pytest.warns(
656+
RuntimeWarning, match="A geometry of type MULTIPOLYGON is inserted"
657+
)
658+
else:
659+
ctx = contextlib.nullcontext()
660+
661+
with ctx:
662+
write_dataframe(
663+
input_gdf,
664+
output_path,
665+
promote_to_multi=promote_to_multi,
666+
geometry_type=geometry_type,
667+
)
658668

659669
assert output_path.exists()
660670
output_gdf = read_dataframe(output_path)
@@ -1024,9 +1034,9 @@ def test_read_dataset_kwargs(data_dir, use_arrow):
10241034
),
10251035
],
10261036
)
1027-
def test_read_invalid_dataset_kwargs(capfd, naturalearth_lowres, use_arrow):
1028-
read_dataframe(naturalearth_lowres, use_arrow=use_arrow, INVALID="YES")
1029-
assert "does not support open option INVALID" in capfd.readouterr().err
1037+
def test_read_invalid_dataset_kwargs(naturalearth_lowres, use_arrow):
1038+
with pytest.warns(RuntimeWarning, match="does not support open option INVALID"):
1039+
read_dataframe(naturalearth_lowres, use_arrow=use_arrow, INVALID="YES")
10301040

10311041

10321042
def test_write_nullable_dtypes(tmp_path):

pyogrio/tests/test_raw_io.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import contextlib
12
import json
23
import os
34
import sys
@@ -270,6 +271,7 @@ def test_write(tmpdir, naturalearth_lowres):
270271

271272
def test_write_gpkg(tmpdir, naturalearth_lowres):
272273
meta, _, geometry, field_data = read(naturalearth_lowres)
274+
meta.update({"geometry_type": "MultiPolygon"})
273275

274276
filename = os.path.join(str(tmpdir), "test.gpkg")
275277
write(filename, geometry, field_data, driver="GPKG", **meta)
@@ -456,9 +458,11 @@ def assert_equal_result(result1, result2):
456458
assert all([np.array_equal(f1, f2) for f1, f2 in zip(field_data1, field_data2)])
457459

458460

461+
@pytest.mark.filterwarnings("ignore:File /vsimem:RuntimeWarning") # TODO
459462
@pytest.mark.parametrize("driver,ext", [("GeoJSON", "geojson"), ("GPKG", "gpkg")])
460463
def test_read_from_bytes(tmpdir, naturalearth_lowres, driver, ext):
461464
meta, index, geometry, field_data = read(naturalearth_lowres)
465+
meta.update({"geometry_type": "Unknown"})
462466
filename = os.path.join(str(tmpdir), f"test.{ext}")
463467
write(filename, geometry, field_data, driver=driver, **meta)
464468

@@ -480,9 +484,11 @@ def test_read_from_bytes_zipped(tmpdir, naturalearth_lowres_vsi):
480484
assert_equal_result((meta, index, geometry, field_data), result2)
481485

482486

487+
@pytest.mark.filterwarnings("ignore:File /vsimem:RuntimeWarning") # TODO
483488
@pytest.mark.parametrize("driver,ext", [("GeoJSON", "geojson"), ("GPKG", "gpkg")])
484489
def test_read_from_file_like(tmpdir, naturalearth_lowres, driver, ext):
485490
meta, index, geometry, field_data = read(naturalearth_lowres)
491+
meta.update({"geometry_type": "Unknown"})
486492
filename = os.path.join(str(tmpdir), f"test.{ext}")
487493
write(filename, geometry, field_data, driver=driver, **meta)
488494

@@ -647,7 +653,12 @@ def test_write_float_nan_null(tmp_path, dtype):
647653

648654
# set to False
649655
# by default, GDAL will skip the property for GeoJSON if the value is NaN
650-
write(fname, geometry, field_data, fields, **meta, nan_as_null=False)
656+
if dtype == "float32":
657+
ctx = pytest.warns(RuntimeWarning, match="NaN of Infinity value found. Skipped")
658+
else:
659+
ctx = contextlib.nullcontext()
660+
with ctx:
661+
write(fname, geometry, field_data, fields, **meta, nan_as_null=False)
651662
with open(str(fname), "r") as f:
652663
content = f.read()
653664
assert '"properties": { }' in content

0 commit comments

Comments
 (0)