pyogrio 0.10.0__tar.gz → 0.11.1__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.

Potentially problematic release.


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

Files changed (55) hide show
  1. {pyogrio-0.10.0/pyogrio.egg-info → pyogrio-0.11.1}/PKG-INFO +25 -30
  2. pyogrio-0.11.1/README.md +70 -0
  3. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/__init__.py +12 -10
  4. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_compat.py +8 -0
  5. pyogrio-0.11.1/pyogrio/_err.pxd +9 -0
  6. pyogrio-0.11.1/pyogrio/_err.pyx +441 -0
  7. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_geometry.pxd +1 -1
  8. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_geometry.pyx +48 -48
  9. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_io.pyx +463 -251
  10. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_ogr.pxd +130 -68
  11. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_ogr.pyx +58 -37
  12. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_version.py +3 -3
  13. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_vsi.pxd +1 -1
  14. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_vsi.pyx +8 -9
  15. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/geopandas.py +91 -43
  16. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/conftest.py +8 -0
  17. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/test_arrow.py +3 -0
  18. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/test_core.py +8 -4
  19. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/test_geopandas_io.py +270 -45
  20. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/test_path.py +10 -0
  21. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/test_raw_io.py +6 -2
  22. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/util.py +15 -2
  23. {pyogrio-0.10.0 → pyogrio-0.11.1/pyogrio.egg-info}/PKG-INFO +25 -30
  24. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyproject.toml +12 -6
  25. {pyogrio-0.10.0 → pyogrio-0.11.1}/setup.py +1 -1
  26. pyogrio-0.10.0/README.md +0 -76
  27. pyogrio-0.10.0/pyogrio/_err.pxd +0 -4
  28. pyogrio-0.10.0/pyogrio/_err.pyx +0 -250
  29. {pyogrio-0.10.0 → pyogrio-0.11.1}/LICENSE +0 -0
  30. {pyogrio-0.10.0 → pyogrio-0.11.1}/MANIFEST.in +0 -0
  31. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_env.py +0 -0
  32. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/_io.pxd +0 -0
  33. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/arrow_bridge.h +0 -0
  34. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/core.py +0 -0
  35. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/errors.py +0 -0
  36. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/raw.py +0 -0
  37. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/__init__.py +0 -0
  38. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/README.md +0 -0
  39. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/curve.gpkg +0 -0
  40. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/curvepolygon.gpkg +0 -0
  41. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/line_zm.gpkg +0 -0
  42. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/multisurface.gpkg +0 -0
  43. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.cpg +0 -0
  44. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.dbf +0 -0
  45. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.prj +0 -0
  46. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.shp +0 -0
  47. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/naturalearth_lowres/naturalearth_lowres.shx +0 -0
  48. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/sample.osm.pbf +0 -0
  49. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/fixtures/test_gpkg_nulls.gpkg +0 -0
  50. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio/tests/test_util.py +0 -0
  51. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio.egg-info/SOURCES.txt +0 -0
  52. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio.egg-info/dependency_links.txt +0 -0
  53. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio.egg-info/requires.txt +0 -0
  54. {pyogrio-0.10.0 → pyogrio-0.11.1}/pyogrio.egg-info/top_level.txt +0 -0
  55. {pyogrio-0.10.0 → pyogrio-0.11.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyogrio
3
- Version: 0.10.0
3
+ Version: 0.11.1
4
4
  Summary: Vectorized spatial vector file format I/O using GDAL/OGR
5
5
  Author: pyogrio contributors
6
6
  Author-email: "Brendan C. Ward" <bcward@astutespruce.com>
@@ -50,39 +50,34 @@ Provides-Extra: benchmark
50
50
  Requires-Dist: pytest-benchmark; extra == "benchmark"
51
51
  Provides-Extra: geopandas
52
52
  Requires-Dist: geopandas; extra == "geopandas"
53
+ Dynamic: license-file
53
54
 
54
- # pyogrio - Vectorized spatial vector file format I/O using GDAL/OGR
55
+ # pyogrio - bulk-oriented spatial vector file I/O using GDAL/OGR
55
56
 
56
- Pyogrio provides a
57
- [GeoPandas](https://github.com/geopandas/geopandas)-oriented API to OGR vector
58
- data sources, such as ESRI Shapefile, GeoPackage, and GeoJSON. Vector data sources
59
- have geometries, such as points, lines, or polygons, and associated records
60
- with potentially many columns worth of data.
57
+ Pyogrio provides fast, bulk-oriented read and write access to
58
+ [GDAL/OGR](https://gdal.org/en/latest/drivers/vector/index.html) vector data
59
+ sources, such as ESRI Shapefile, GeoPackage, GeoJSON, and several others.
60
+ Vector data sources typically have geometries, such as points, lines, or
61
+ polygons, and associated records with potentially many columns worth of data.
61
62
 
62
- Pyogrio uses a vectorized approach for reading and writing GeoDataFrames to and
63
- from OGR vector data sources in order to give you faster interoperability. It
64
- uses pre-compiled bindings for GDAL/OGR so that the performance is primarily
65
- limited by the underlying I/O speed of data source drivers in GDAL/OGR rather
66
- than multiple steps of converting to and from Python data types within Python.
63
+ The typical use is to read or write these data sources to/from
64
+ [GeoPandas](https://github.com/geopandas/geopandas) `GeoDataFrames`. Because
65
+ the geometry column is optional, reading or writing only non-spatial data is
66
+ also possible. Hence, GeoPackage attribute tables, DBF files, or CSV files are
67
+ also supported.
67
68
 
68
- We have seen \>5-10x speedups reading files and \>5-20x speedups writing files
69
- compared to using non-vectorized approaches (Fiona and current I/O support in
70
- GeoPandas).
71
-
72
- You can read these data sources into
73
- `GeoDataFrames`, read just the non-geometry columns into Pandas `DataFrames`,
74
- or even read non-spatial data sources that exist alongside vector data sources,
75
- such as tables in a ESRI File Geodatabase, or antiquated DBF files.
69
+ Pyogrio is fast because it uses pre-compiled bindings for GDAL/OGR to read and
70
+ write the data records in bulk. This approach avoids multiple steps of
71
+ converting to and from Python data types within Python, so performance becomes
72
+ primarily limited by the underlying I/O speed of data source drivers in
73
+ GDAL/OGR.
76
74
 
77
- Pyogrio also enables you to write `GeoDataFrames` to at least a few different
78
- OGR vector data source formats.
75
+ We have seen \>5-10x speedups reading files and \>5-20x speedups writing files
76
+ compared to using row-per-row approaches (e.g. Fiona).
79
77
 
80
78
  Read the documentation for more information:
81
79
  [https://pyogrio.readthedocs.io](https://pyogrio.readthedocs.io/en/latest/).
82
80
 
83
- WARNING: Pyogrio is still at an early version and the API is subject to
84
- substantial change. Please see [CHANGES](CHANGES.md).
85
-
86
81
  ## Requirements
87
82
 
88
83
  Supports Python 3.9 - 3.13 and GDAL 3.4.x - 3.9.x.
@@ -105,9 +100,9 @@ for more information.
105
100
 
106
101
  ## Supported vector formats
107
102
 
108
- Pyogrio supports some of the most common vector data source formats (provided
109
- they are also supported by GDAL/OGR), including ESRI Shapefile, GeoPackage,
110
- GeoJSON, and FlatGeobuf.
103
+ Pyogrio supports most common vector data source formats (provided they are also
104
+ supported by GDAL/OGR), including ESRI Shapefile, GeoPackage, GeoJSON, and
105
+ FlatGeobuf.
111
106
 
112
107
  Please see the [list of supported formats](https://pyogrio.readthedocs.io/en/latest/supported_formats.html)
113
108
  for more information.
@@ -117,7 +112,7 @@ for more information.
117
112
  Please read the [introduction](https://pyogrio.readthedocs.io/en/latest/supported_formats.html)
118
113
  for more information and examples to get started using Pyogrio.
119
114
 
120
- You can also check out the the [API documentation](https://pyogrio.readthedocs.io/en/latest/api.html)
115
+ You can also check out the [API documentation](https://pyogrio.readthedocs.io/en/latest/api.html)
121
116
  for full details on using the API.
122
117
 
123
118
  ## Credits
@@ -0,0 +1,70 @@
1
+ # pyogrio - bulk-oriented spatial vector file I/O using GDAL/OGR
2
+
3
+ Pyogrio provides fast, bulk-oriented read and write access to
4
+ [GDAL/OGR](https://gdal.org/en/latest/drivers/vector/index.html) vector data
5
+ sources, such as ESRI Shapefile, GeoPackage, GeoJSON, and several others.
6
+ Vector data sources typically have geometries, such as points, lines, or
7
+ polygons, and associated records with potentially many columns worth of data.
8
+
9
+ The typical use is to read or write these data sources to/from
10
+ [GeoPandas](https://github.com/geopandas/geopandas) `GeoDataFrames`. Because
11
+ the geometry column is optional, reading or writing only non-spatial data is
12
+ also possible. Hence, GeoPackage attribute tables, DBF files, or CSV files are
13
+ also supported.
14
+
15
+ Pyogrio is fast because it uses pre-compiled bindings for GDAL/OGR to read and
16
+ write the data records in bulk. This approach avoids multiple steps of
17
+ converting to and from Python data types within Python, so performance becomes
18
+ primarily limited by the underlying I/O speed of data source drivers in
19
+ GDAL/OGR.
20
+
21
+ We have seen \>5-10x speedups reading files and \>5-20x speedups writing files
22
+ compared to using row-per-row approaches (e.g. Fiona).
23
+
24
+ Read the documentation for more information:
25
+ [https://pyogrio.readthedocs.io](https://pyogrio.readthedocs.io/en/latest/).
26
+
27
+ ## Requirements
28
+
29
+ Supports Python 3.9 - 3.13 and GDAL 3.4.x - 3.9.x.
30
+
31
+ Reading to GeoDataFrames requires `geopandas>=0.12` with `shapely>=2`.
32
+
33
+ Additionally, installing `pyarrow` in combination with GDAL 3.6+ enables
34
+ a further speed-up when specifying `use_arrow=True`.
35
+
36
+ ## Installation
37
+
38
+ Pyogrio is currently available on
39
+ [conda-forge](https://anaconda.org/conda-forge/pyogrio)
40
+ and [PyPI](https://pypi.org/project/pyogrio/)
41
+ for Linux, MacOS, and Windows.
42
+
43
+ Please read the
44
+ [installation documentation](https://pyogrio.readthedocs.io/en/latest/install.html)
45
+ for more information.
46
+
47
+ ## Supported vector formats
48
+
49
+ Pyogrio supports most common vector data source formats (provided they are also
50
+ supported by GDAL/OGR), including ESRI Shapefile, GeoPackage, GeoJSON, and
51
+ FlatGeobuf.
52
+
53
+ Please see the [list of supported formats](https://pyogrio.readthedocs.io/en/latest/supported_formats.html)
54
+ for more information.
55
+
56
+ ## Getting started
57
+
58
+ Please read the [introduction](https://pyogrio.readthedocs.io/en/latest/supported_formats.html)
59
+ for more information and examples to get started using Pyogrio.
60
+
61
+ You can also check out the [API documentation](https://pyogrio.readthedocs.io/en/latest/api.html)
62
+ for full details on using the API.
63
+
64
+ ## Credits
65
+
66
+ This project is made possible by the tremendous efforts of the GDAL, Fiona, and
67
+ Geopandas communities.
68
+
69
+ - Core I/O methods and supporting functions adapted from [Fiona](https://github.com/Toblerity/Fiona)
70
+ - Inspired by [Fiona PR](https://github.com/Toblerity/Fiona/pull/540/files)
@@ -4,7 +4,9 @@ try:
4
4
  # we try importing shapely, to ensure it is imported (and it can load its
5
5
  # own GEOS copy) before we load GDAL and its linked GEOS
6
6
  import shapely
7
- import shapely.geos # noqa: F401
7
+
8
+ if shapely.__version__ < "2.0.0":
9
+ import shapely.geos
8
10
  except Exception:
9
11
  pass
10
12
 
@@ -32,24 +34,24 @@ __version__ = get_versions()["version"]
32
34
  del get_versions
33
35
 
34
36
  __all__ = [
35
- "list_drivers",
37
+ "__gdal_geos_version__",
38
+ "__gdal_version__",
39
+ "__gdal_version_string__",
40
+ "__version__",
36
41
  "detect_write_driver",
37
- "list_layers",
38
- "read_bounds",
39
- "read_info",
40
- "set_gdal_config_options",
41
42
  "get_gdal_config_option",
42
43
  "get_gdal_data_path",
44
+ "list_drivers",
45
+ "list_layers",
43
46
  "open_arrow",
44
47
  "read_arrow",
48
+ "read_bounds",
45
49
  "read_dataframe",
50
+ "read_info",
51
+ "set_gdal_config_options",
46
52
  "vsi_listtree",
47
53
  "vsi_rmtree",
48
54
  "vsi_unlink",
49
55
  "write_arrow",
50
56
  "write_dataframe",
51
- "__gdal_version__",
52
- "__gdal_version_string__",
53
- "__gdal_geos_version__",
54
- "__version__",
55
57
  ]
@@ -33,15 +33,23 @@ HAS_ARROW_API = __gdal_version__ >= (3, 6, 0)
33
33
  HAS_ARROW_WRITE_API = __gdal_version__ >= (3, 8, 0)
34
34
  HAS_PYARROW = pyarrow is not None
35
35
  HAS_PYPROJ = pyproj is not None
36
+ PYARROW_GE_19 = pyarrow is not None and Version(pyarrow.__version__) >= Version(
37
+ "19.0.0"
38
+ )
36
39
 
37
40
  HAS_GEOPANDAS = geopandas is not None
38
41
 
39
42
  PANDAS_GE_15 = pandas is not None and Version(pandas.__version__) >= Version("1.5.0")
40
43
  PANDAS_GE_20 = pandas is not None and Version(pandas.__version__) >= Version("2.0.0")
41
44
  PANDAS_GE_22 = pandas is not None and Version(pandas.__version__) >= Version("2.2.0")
45
+ PANDAS_GE_30 = pandas is not None and Version(pandas.__version__) >= Version("3.0.0dev")
42
46
 
47
+ GDAL_GE_352 = __gdal_version__ >= (3, 5, 2)
48
+ GDAL_GE_37 = __gdal_version__ >= (3, 7, 0)
43
49
  GDAL_GE_38 = __gdal_version__ >= (3, 8, 0)
50
+ GDAL_GE_311 = __gdal_version__ >= (3, 11, 0)
44
51
 
45
52
  HAS_GDAL_GEOS = __gdal_geos_version__ is not None
46
53
 
47
54
  HAS_SHAPELY = shapely is not None and Version(shapely.__version__) >= Version("2.0.0")
55
+ SHAPELY_GE_21 = shapely is not None and Version(shapely.__version__) >= Version("2.1.0")
@@ -0,0 +1,9 @@
1
+ cdef object check_last_error()
2
+ cdef int check_int(int retval) except -1
3
+ cdef void *check_pointer(void *ptr) except NULL
4
+
5
+ cdef class ErrorHandler:
6
+ cdef object error_stack
7
+ cdef int check_int(self, int retval, bint squash_errors) except -1
8
+ cdef void *check_pointer(self, void *ptr, bint squash_errors) except NULL
9
+ cdef void _handle_error_stack(self, bint squash_errors)
@@ -0,0 +1,441 @@
1
+ """Error handling code for GDAL/OGR.
2
+
3
+ Ported from fiona::_err.pyx
4
+ """
5
+
6
+ import contextlib
7
+ import warnings
8
+ from contextvars import ContextVar
9
+ from itertools import zip_longest
10
+
11
+ from pyogrio._ogr cimport (
12
+ CE_Warning, CE_Failure, CE_Fatal, CPLErrorReset,
13
+ CPLGetLastErrorType, CPLGetLastErrorNo, CPLGetLastErrorMsg,
14
+ OGRERR_NONE, CPLErr, CPLErrorHandler, CPLDefaultErrorHandler,
15
+ CPLPopErrorHandler, CPLPushErrorHandler)
16
+
17
+ _ERROR_STACK = ContextVar("error_stack")
18
+ _ERROR_STACK.set([])
19
+
20
+
21
+ class CPLE_BaseError(Exception):
22
+ """Base CPL error class.
23
+
24
+ For internal use within Cython only.
25
+ """
26
+
27
+ def __init__(self, error, errno, errmsg):
28
+ self.error = error
29
+ self.errno = errno
30
+ self.errmsg = errmsg
31
+
32
+ def __str__(self):
33
+ return self.__unicode__()
34
+
35
+ def __unicode__(self):
36
+ return u"{}".format(self.errmsg)
37
+
38
+ @property
39
+ def args(self):
40
+ return self.error, self.errno, self.errmsg
41
+
42
+
43
+ class CPLE_AppDefinedError(CPLE_BaseError):
44
+ pass
45
+
46
+
47
+ class CPLE_OutOfMemoryError(CPLE_BaseError):
48
+ pass
49
+
50
+
51
+ class CPLE_FileIOError(CPLE_BaseError):
52
+ pass
53
+
54
+
55
+ class CPLE_OpenFailedError(CPLE_BaseError):
56
+ pass
57
+
58
+
59
+ class CPLE_IllegalArgError(CPLE_BaseError):
60
+ pass
61
+
62
+
63
+ class CPLE_NotSupportedError(CPLE_BaseError):
64
+ pass
65
+
66
+
67
+ class CPLE_AssertionFailedError(CPLE_BaseError):
68
+ pass
69
+
70
+
71
+ class CPLE_NoWriteAccessError(CPLE_BaseError):
72
+ pass
73
+
74
+
75
+ class CPLE_UserInterruptError(CPLE_BaseError):
76
+ pass
77
+
78
+
79
+ class ObjectNullError(CPLE_BaseError):
80
+ pass
81
+
82
+
83
+ class CPLE_HttpResponseError(CPLE_BaseError):
84
+ pass
85
+
86
+
87
+ class CPLE_AWSBucketNotFoundError(CPLE_BaseError):
88
+ pass
89
+
90
+
91
+ class CPLE_AWSObjectNotFoundError(CPLE_BaseError):
92
+ pass
93
+
94
+
95
+ class CPLE_AWSAccessDeniedError(CPLE_BaseError):
96
+ pass
97
+
98
+
99
+ class CPLE_AWSInvalidCredentialsError(CPLE_BaseError):
100
+ pass
101
+
102
+
103
+ class CPLE_AWSSignatureDoesNotMatchError(CPLE_BaseError):
104
+ pass
105
+
106
+
107
+ class CPLE_AWSError(CPLE_BaseError):
108
+ pass
109
+
110
+
111
+ class NullPointerError(CPLE_BaseError):
112
+ """
113
+ Returned from check_pointer when a NULL pointer is passed, but no GDAL
114
+ error was raised.
115
+ """
116
+ pass
117
+
118
+
119
+ class CPLError(CPLE_BaseError):
120
+ """
121
+ Returned from check_int when a error code is returned, but no GDAL
122
+ error was set.
123
+ """
124
+ pass
125
+
126
+
127
+ # Map of GDAL error numbers to the Python exceptions.
128
+ exception_map = {
129
+ 1: CPLE_AppDefinedError,
130
+ 2: CPLE_OutOfMemoryError,
131
+ 3: CPLE_FileIOError,
132
+ 4: CPLE_OpenFailedError,
133
+ 5: CPLE_IllegalArgError,
134
+ 6: CPLE_NotSupportedError,
135
+ 7: CPLE_AssertionFailedError,
136
+ 8: CPLE_NoWriteAccessError,
137
+ 9: CPLE_UserInterruptError,
138
+ 10: ObjectNullError,
139
+
140
+ # error numbers 11-16 are introduced in GDAL 2.1. See
141
+ # https://github.com/OSGeo/gdal/pull/98.
142
+ 11: CPLE_HttpResponseError,
143
+ 12: CPLE_AWSBucketNotFoundError,
144
+ 13: CPLE_AWSObjectNotFoundError,
145
+ 14: CPLE_AWSAccessDeniedError,
146
+ 15: CPLE_AWSInvalidCredentialsError,
147
+ 16: CPLE_AWSSignatureDoesNotMatchError,
148
+ 17: CPLE_AWSError
149
+ }
150
+
151
+
152
+ cdef inline object check_last_error():
153
+ """Checks if the last GDAL error was a fatal or non-fatal error.
154
+
155
+ When a non-fatal error is found, an appropriate exception is raised.
156
+
157
+ When a fatal error is found, SystemExit is called.
158
+
159
+ Returns
160
+ -------
161
+ An Exception, SystemExit, or None
162
+ """
163
+ err_type = CPLGetLastErrorType()
164
+ err_no = CPLGetLastErrorNo()
165
+ err_msg = clean_error_message(CPLGetLastErrorMsg())
166
+ if err_msg == "":
167
+ err_msg = "No error message."
168
+
169
+ if err_type == CE_Failure:
170
+ CPLErrorReset()
171
+ return exception_map.get(
172
+ err_no, CPLE_BaseError)(err_type, err_no, err_msg)
173
+
174
+ if err_type == CE_Fatal:
175
+ return SystemExit("Fatal error: {0}".format((err_type, err_no, err_msg)))
176
+
177
+
178
+ cdef clean_error_message(const char* err_msg):
179
+ """Cleans up error messages from GDAL.
180
+
181
+ Parameters
182
+ ----------
183
+ err_msg : const char*
184
+ The error message to clean up.
185
+
186
+ Returns
187
+ -------
188
+ str
189
+ The cleaned up error message or empty string
190
+ """
191
+ if err_msg != NULL:
192
+ # Reformat message.
193
+ msg_b = err_msg
194
+ try:
195
+ msg = msg_b.decode("utf-8")
196
+ msg = msg.replace("`", "'")
197
+ msg = msg.replace("\n", " ")
198
+ except UnicodeDecodeError as exc:
199
+ msg = f"Could not decode error message to UTF-8. Raw error: {msg_b}"
200
+
201
+ else:
202
+ msg = ""
203
+
204
+ return msg
205
+
206
+
207
+ cdef void *check_pointer(void *ptr) except NULL:
208
+ """Check the pointer returned by a GDAL/OGR function.
209
+
210
+ If `ptr` is `NULL`, an exception inheriting from CPLE_BaseError is raised.
211
+ When the last error registered by GDAL/OGR was a non-fatal error, the
212
+ exception raised will be customized appropriately. Otherwise a
213
+ NullPointerError is raised.
214
+ """
215
+ if ptr == NULL:
216
+ exc = check_last_error()
217
+ if exc:
218
+ raise exc
219
+ else:
220
+ # null pointer was passed, but no error message from GDAL
221
+ raise NullPointerError(-1, -1, "NULL pointer error")
222
+
223
+ return ptr
224
+
225
+
226
+ cdef int check_int(int err) except -1:
227
+ """Check the CPLErr (int) value returned by a GDAL/OGR function.
228
+
229
+ If `err` is not OGRERR_NONE, an exception inheriting from CPLE_BaseError is raised.
230
+ When the last error registered by GDAL/OGR was a non-fatal error, the
231
+ exception raised will be customized appropriately. Otherwise a CPLError is
232
+ raised.
233
+ """
234
+ if err != OGRERR_NONE:
235
+ exc = check_last_error()
236
+ if exc:
237
+ raise exc
238
+ else:
239
+ # no error message from GDAL
240
+ raise CPLError(-1, -1, "Unspecified OGR / GDAL error")
241
+
242
+ return err
243
+
244
+
245
+ cdef void error_handler(
246
+ CPLErr err_class, int err_no, const char* err_msg
247
+ ) noexcept nogil:
248
+ """Custom CPL error handler to match the Python behaviour.
249
+
250
+ For non-fatal errors (CE_Failure), error printing to stderr (behaviour of
251
+ the default GDAL error handler) is suppressed, because we already raise a
252
+ Python exception that includes the error message.
253
+
254
+ Warnings are converted to Python warnings.
255
+ """
256
+ if err_class == CE_Fatal:
257
+ # If the error class is CE_Fatal, we want to have a message issued
258
+ # because the CPL support code does an abort() before any exception
259
+ # can be generated
260
+ CPLDefaultErrorHandler(err_class, err_no, err_msg)
261
+ return
262
+
263
+ if err_class == CE_Failure:
264
+ # For Failures, do nothing as those are explicitly caught
265
+ # with error return codes and translated into Python exceptions
266
+ return
267
+
268
+ if err_class == CE_Warning:
269
+ with gil:
270
+ warnings.warn(clean_error_message(err_msg), RuntimeWarning)
271
+ return
272
+
273
+ # Fall back to the default handler for non-failure messages since
274
+ # they won't be translated into exceptions.
275
+ CPLDefaultErrorHandler(err_class, err_no, err_msg)
276
+
277
+
278
+ def _register_error_handler():
279
+ CPLPushErrorHandler(<CPLErrorHandler>error_handler)
280
+
281
+
282
+ cdef class ErrorHandler:
283
+
284
+ def __init__(self, error_stack=None):
285
+ self.error_stack = error_stack or {}
286
+
287
+ cdef int check_int(self, int err, bint squash_errors) except -1:
288
+ """Check the CPLErr (int) value returned by a GDAL/OGR function.
289
+
290
+ If `err` is not OGRERR_NONE, an exception inheriting from CPLE_BaseError is
291
+ raised.
292
+ When a non-fatal GDAL/OGR error was captured in the error stack, the
293
+ exception raised will be customized appropriately. Otherwise, a
294
+ CPLError is raised.
295
+
296
+ Parameters
297
+ ----------
298
+ err : int
299
+ The CPLErr returned by a GDAL/OGR function.
300
+ squash_errors : bool
301
+ True to squash all errors captured to one error with the exception type of
302
+ the last error and all error messages concatenated.
303
+
304
+ Returns
305
+ -------
306
+ int
307
+ The `err` input parameter if it is OGRERR_NONE. Otherwise an exception is
308
+ raised.
309
+
310
+ """
311
+ if err != OGRERR_NONE:
312
+ if self.error_stack.get():
313
+ self._handle_error_stack(squash_errors)
314
+ else:
315
+ raise CPLError(CE_Failure, err, "Unspecified OGR / GDAL error")
316
+
317
+ return err
318
+
319
+ cdef void *check_pointer(self, void *ptr, bint squash_errors) except NULL:
320
+ """Check the pointer returned by a GDAL/OGR function.
321
+
322
+ If `ptr` is `NULL`, an exception inheriting from CPLE_BaseError is
323
+ raised.
324
+ When a non-fatal GDAL/OGR error was captured in the error stack, the
325
+ exception raised will be customized appropriately. Otherwise, a
326
+ NullPointerError is raised.
327
+
328
+ Parameters
329
+ ----------
330
+ ptr : pointer
331
+ The pointer returned by a GDAL/OGR function.
332
+ squash_errors : bool
333
+ True to squash all errors captured to one error with the exception type of
334
+ the last error and all error messages concatenated.
335
+
336
+ Returns
337
+ -------
338
+ pointer
339
+ The `ptr` input parameter if it is not `NULL`. Otherwise an exception is
340
+ raised.
341
+
342
+ """
343
+ if ptr == NULL:
344
+ if self.error_stack.get():
345
+ self._handle_error_stack(squash_errors)
346
+ else:
347
+ raise NullPointerError(-1, -1, "NULL pointer error")
348
+
349
+ return ptr
350
+
351
+ cdef void _handle_error_stack(self, bint squash_errors):
352
+ """Handle the errors in `error_stack`."""
353
+ stack = self.error_stack.get()
354
+ for error, cause in zip_longest(stack[::-1], stack[::-1][1:]):
355
+ if error is not None and cause is not None:
356
+ error.__cause__ = cause
357
+
358
+ last = stack.pop()
359
+ if last is not None:
360
+ if squash_errors:
361
+ # Concatenate all error messages, and raise a single exception
362
+ errmsg = str(last)
363
+ inner = last.__cause__
364
+ while inner is not None:
365
+ errmsg = f"{errmsg}; {inner}"
366
+ inner = inner.__cause__
367
+
368
+ if errmsg == "":
369
+ errmsg = "No error message."
370
+
371
+ raise type(last)(-1, -1, errmsg)
372
+
373
+ raise last
374
+
375
+
376
+ cdef void stacking_error_handler(
377
+ CPLErr err_class,
378
+ int err_no,
379
+ const char* err_msg
380
+ ) noexcept nogil:
381
+ """Custom CPL error handler that adds non-fatal errors to a stack.
382
+
383
+ All non-fatal errors (CE_Failure) are not printed to stderr (behaviour
384
+ of the default GDAL error handler), but they are converted to python
385
+ exceptions and added to a stack, so they can be dealt with afterwards.
386
+
387
+ Warnings are converted to Python warnings.
388
+ """
389
+ if err_class == CE_Fatal:
390
+ # If the error class is CE_Fatal, we want to have a message issued
391
+ # because the CPL support code does an abort() before any exception
392
+ # can be generated
393
+ CPLDefaultErrorHandler(err_class, err_no, err_msg)
394
+ return
395
+
396
+ if err_class == CE_Failure:
397
+ # For Failures, add them to the error exception stack
398
+ with gil:
399
+ stack = _ERROR_STACK.get()
400
+ stack.append(
401
+ exception_map.get(err_no, CPLE_BaseError)(
402
+ err_class, err_no, clean_error_message(err_msg)
403
+ ),
404
+ )
405
+ _ERROR_STACK.set(stack)
406
+
407
+ return
408
+
409
+ if err_class == CE_Warning:
410
+ with gil:
411
+ warnings.warn(clean_error_message(err_msg), RuntimeWarning)
412
+ return
413
+
414
+ # Fall back to the default handler for non-failure messages since
415
+ # they won't be translated into exceptions.
416
+ CPLDefaultErrorHandler(err_class, err_no, err_msg)
417
+
418
+
419
+ @contextlib.contextmanager
420
+ def capture_errors():
421
+ """A context manager that captures all GDAL non-fatal errors occuring.
422
+
423
+ It adds all errors to a single stack, so it assumes that no more than one
424
+ GDAL function is called.
425
+
426
+ Yields an ErrorHandler object that can be used to handle the errors
427
+ if any were captured.
428
+ """
429
+ CPLErrorReset()
430
+ _ERROR_STACK.set([])
431
+
432
+ # stacking_error_handler records GDAL errors in the order they occur and
433
+ # converts them to exceptions.
434
+ CPLPushErrorHandler(<CPLErrorHandler>stacking_error_handler)
435
+
436
+ # Run code in the `with` block.
437
+ yield ErrorHandler(_ERROR_STACK)
438
+
439
+ CPLPopErrorHandler()
440
+ _ERROR_STACK.set([])
441
+ CPLErrorReset()
@@ -1,4 +1,4 @@
1
1
  from pyogrio._ogr cimport *
2
2
 
3
3
  cdef str get_geometry_type(void *ogr_layer)
4
- cdef OGRwkbGeometryType get_geometry_type_code(str geometry_type) except *
4
+ cdef OGRwkbGeometryType get_geometry_type_code(str geometry_type) except *