Skip to content

Commit b5d6b68

Browse files
committed
Python bindings: rework gdal.run() and add gdal.algorithm()
1 parent b21f1d7 commit b5d6b68

File tree

5 files changed

+441
-178
lines changed

5 files changed

+441
-178
lines changed

autotest/gcore/driver_algorithms.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
###############################################################################
1313

1414

15+
import pytest
16+
1517
from osgeo import gdal
1618

1719

@@ -28,6 +30,8 @@ def check(alg, expected_name):
2830

2931
def test_driver_algorithms():
3032

31-
alg = gdal.GetGlobalAlgorithmRegistry()["driver"]
32-
if alg:
33-
check(alg, "driver")
33+
try:
34+
alg = gdal.GetGlobalAlgorithmRegistry()["driver"]
35+
except Exception:
36+
pytest.skip("no drivers")
37+
check(alg, "driver")

autotest/utilities/test_gdal.py

+75-23
Original file line numberDiff line numberDiff line change
@@ -457,52 +457,104 @@ def test_gdal_algorithm_getter_setter():
457457
alg["no-mask"] = "bar"
458458

459459

460-
def test_gdal_run():
460+
def test_gdal_algorithm():
461+
462+
with pytest.raises(RuntimeError, match="'i_do_not_exist' is not a valid algorithm"):
463+
gdal.Algorithm("i_do_not_exist")
464+
465+
with pytest.raises(RuntimeError, match="'i_do_not_exist' is not a valid algorithm"):
466+
gdal.Algorithm(["i_do_not_exist"])
461467

462468
with pytest.raises(
463-
Exception, match="i_do_not_exist is not a valid sub-algorithm of gdal"
469+
RuntimeError, match="'i_do_not_exist' is not a valid sub-algorithm"
464470
):
465-
with gdal.run("i_do_not_exist"):
466-
pass
471+
gdal.Algorithm(["raster", "i_do_not_exist"])
472+
473+
with pytest.raises(
474+
RuntimeError,
475+
match="Wrong type for algorithm path. Expected string or list of strings",
476+
):
477+
gdal.Algorithm(None)
467478

468479
with pytest.raises(
469-
Exception, match="i_do_not_exist is not a valid sub-algorithm of gdal"
480+
RuntimeError,
481+
match="Wrong type for algorithm path. Expected string or list of strings",
470482
):
471-
with gdal.run(["i_do_not_exist"]):
483+
gdal.Algorithm()
484+
485+
alg = gdal.Algorithm(["raster", "info"])
486+
assert alg.GetName() == "info"
487+
488+
alg = gdal.Algorithm("raster info")
489+
assert alg.GetName() == "info"
490+
491+
alg = gdal.Algorithm("raster", "info")
492+
assert alg.GetName() == "info"
493+
494+
with pytest.raises(RuntimeError, match=r"Algorithm.Run\(\) must be called before"):
495+
alg.Output()
496+
497+
with pytest.raises(RuntimeError, match=r"Algorithm.Run\(\) must be called before"):
498+
alg.Output()
499+
500+
with gdal.Algorithm("raster info") as alg:
501+
assert alg.GetName() == "info"
502+
503+
504+
def test_gdal_run():
505+
506+
with pytest.raises(RuntimeError, match="'i_do_not_exist' is not a valid algorithm"):
507+
with gdal.Run("i_do_not_exist"):
508+
pass
509+
510+
with pytest.raises(RuntimeError, match="'i_do_not_exist' is not a valid algorithm"):
511+
with gdal.Run(["i_do_not_exist"]):
472512
pass
473513

474514
with pytest.raises(
475-
Exception, match="Wrong type for algorithm. Expected string or list of string"
515+
RuntimeError, match="'i_do_not_exist' is not a valid sub-algorithm"
476516
):
477-
with gdal.run(None):
517+
with gdal.Run(["raster", "i_do_not_exist"]):
478518
pass
479519

480-
with gdal.run("raster info", {"input": "../gcore/data/byte.tif"}) as res:
481-
assert len(res["bands"]) == 1
520+
with pytest.raises(
521+
RuntimeError,
522+
match="Wrong type for alg. Expected string, list of strings or Algorithm",
523+
):
524+
gdal.Run(None)
525+
526+
with gdal.Run("raster", "info", {"input": "../gcore/data/byte.tif"}) as alg:
527+
assert len(alg.Output()["bands"]) == 1
528+
529+
with gdal.Run("raster info", {"input": "../gcore/data/byte.tif"}) as alg:
530+
assert len(alg.Output()["bands"]) == 1
531+
532+
with gdal.Run("gdal raster info", input="../gcore/data/byte.tif") as alg:
533+
assert len(alg.Output()["bands"]) == 1
482534

483-
with gdal.run("gdal raster info", input="../gcore/data/byte.tif") as res:
484-
assert len(res["bands"]) == 1
535+
with gdal.Run(
536+
gdal.Algorithm("raster info"), {"input": "../gcore/data/byte.tif"}
537+
) as alg:
538+
assert len(alg.Output()["bands"]) == 1
485539

486-
with gdal.run(
487-
"raster info", {"input": "../gcore/data/byte.tif"}, optimize_single_output=False
488-
) as res:
489-
assert len(res) == 1
490-
assert res["output-string"].startswith("{")
540+
alg = gdal.Run("raster info", {"input": "../gcore/data/byte.tif"})
541+
assert len(alg.Outputs()) == 1
542+
assert alg.Outputs(parse_json=False)["output-string"].startswith("{")
491543

492-
with gdal.run(
544+
with gdal.Run(
493545
["gdal", "raster", "reproject"],
494546
input="../gcore/data/byte.tif",
495547
output_format="MEM",
496548
dst_crs="EPSG:4326",
497-
) as ds:
498-
assert ds.GetSpatialRef().GetAuthorityCode(None) == "4326"
549+
) as alg:
550+
assert alg.Output().GetSpatialRef().GetAuthorityCode(None) == "4326"
499551

500-
with gdal.run(
552+
with gdal.Run(
501553
["raster", "reproject"],
502554
{
503555
"input": "../gcore/data/byte.tif",
504556
"output-format": "MEM",
505557
"dst-crs": "EPSG:4326",
506558
},
507-
) as ds:
508-
assert ds.GetSpatialRef().GetAuthorityCode(None) == "4326"
559+
) as alg:
560+
assert alg.Output().GetSpatialRef().GetAuthorityCode(None) == "4326"

doc/source/programs/gdal_cli_from_python.rst

+113-44
Original file line numberDiff line numberDiff line change
@@ -4,83 +4,152 @@
44
How to use "gdal" CLI algorithms from Python
55
================================================================================
66

7-
Raster commands
8-
---------------
7+
Principles
8+
----------
99

10-
* Getting information on a raster dataset as JSON
10+
"gdal" CLI algorithms are available as :py:class:`osgeo.gdal.Algorithm` instances.
1111

12-
.. code-block:: python
12+
A convenient way to access an algorithm and run it is to use the :py:func:`osgeo.gdal.Run`
13+
function.
14+
15+
.. py:function:: Run(alg, arguments={}, progress=None, **kwargs)
16+
17+
Run a GDAL algorithm and return it.
18+
19+
.. versionadded: 3.11
1320
14-
from osgeo import gdal
21+
:param alg: Path to the algorithm or algorithm instance itself. For example "raster info", or ["raster", "info"] or "raster", "info".
22+
:type alg: str, list[str], tuple[str], Algorithm
23+
:param arguments: Input arguments of the algorithm. For example {"format": "json", "input": "byte.tif"}
24+
:type arguments: dict
25+
:param progress: Progress function whose arguments are a progress ratio, a string and a user data
26+
:type progress: callable
27+
:param kwargs: Instead of using the ``arguments`` parameter, it is possible to pass
28+
algorithm arguments directly as named parameters of gdal.Run().
29+
If the named argument has dash characters in it, the corresponding
30+
parameter must replace them with an underscore character.
31+
For example ``dst_crs`` as a a parameter of gdal.Run(), instead of
32+
``dst-crs`` which is the name to use on the command line.
33+
:rtype: a :py:class:`osgeo.gdal.Algorithm` instance
1534

16-
gdal.UseExceptions()
17-
with gdal.run(["raster", "info"], {"input": "byte.tif"}) as info:
18-
print(info)
1935

36+
If you do not need to access output value(s) of the algorithm, you can call
37+
``Run`` directly:
38+
``gdal.Run(["raster", "convert"], input="in.tif", output="out.tif")``
2039

21-
* Converting a georeferenced netCDF file to cloud-optimized GeoTIFF
40+
The name of algorithm arguments are the long names as documented in the
41+
documentation page of each algorithm. They can also be obtained with
42+
:py:meth:`osgeo.gdal.Algorithm.GetArgNames`.
2243

2344
.. code-block:: python
2445
25-
from osgeo import gdal
46+
>>> gdal.Algorithm("raster", "convert").GetArgNames()
47+
['help', 'help-doc', 'version', 'json-usage', 'drivers', 'config', 'progress', 'output-format', 'open-option', 'input-format', 'input', 'output', 'creation-option', 'overwrite', 'append']
48+
49+
For a command such as :ref:`gdal_raster_info_subcommand` that returns a JSON
50+
output, you can get the return value of :py:func:`osgeo.gdal.Run` and call the
51+
:py:meth:`osgeo.gdal.Algorithm.Output` method.
52+
53+
.. code-block:: python
2654
27-
gdal.UseExceptions()
28-
with gdal.run(["raster", "convert"], {"input": "in.tif", "output": "out.tif", "output-format": "COG", "overwrite": True}):
29-
pass
55+
alg = gdal.Run("raster", "info", input="byte.tif")
56+
info_as_dict = alg.Output()
3057
31-
or
58+
If the return value is a dataset, :py:func:`osgeo.gdal.Run` can be used
59+
within a context manager, in which case :py:meth:`osgeo.gdal.Algorithm.Finalize`
60+
will be called at the exit of the context manager.
3261

3362
.. code-block:: python
3463
35-
from osgeo import gdal
64+
with gdal.Run("raster reproject", input=src_ds, output_format="MEM",
65+
dst_crs="EPSG:4326"}) as alg:
66+
values = alg.Output().ReadAsArray()
3667
37-
gdal.UseExceptions()
38-
with gdal.run(["raster", "convert"], input="in.tif", output="out.tif", output_format="COG", overwrite=True):
39-
pass
4068
69+
Raster commands examples
70+
------------------------
4171

42-
* Reprojecting a GeoTIFF file to a Deflate compressed tiled GeoTIFF file
72+
.. example::
73+
:title: Getting information on a raster dataset as JSON
4374

44-
.. code-block:: python
75+
.. code-block:: python
4576
46-
from osgeo import gdal
77+
from osgeo import gdal
4778
48-
gdal.UseExceptions()
49-
with gdal.run(["raster", "reproject"], {"input": "in.tif", "output": "out.tif", "dst-crs": "EPSG:4326", "creation-options": { "TILED": "YES", "COMPRESS": "DEFLATE"} }):
50-
pass
79+
gdal.UseExceptions()
80+
alg = gdal.Run("raster", "info", input="byte.tif")
81+
info_as_dict = alg.Output()
5182
5283
53-
* Reprojecting a (possibly in-memory) dataset to a in-memory dataset
84+
.. example::
85+
:title: Converting a georeferenced netCDF file to cloud-optimized GeoTIFF
5486

55-
.. code-block:: python
87+
.. code-block:: python
5688
57-
from osgeo import gdal
89+
from osgeo import gdal
5890
59-
gdal.UseExceptions()
60-
with gdal.run(["raster", "reproject"], {"input": "in.tif", "output-format": "MEM", "dst-crs": "EPSG:4326"}) as ds:
61-
print(ds.ReadAsArray())
91+
gdal.UseExceptions()
92+
gdal.Run("raster", "convert", input="in.tif", output="out.tif",
93+
output_format="COG", overwrite=True)
6294
95+
or
6396

64-
Vector commands
65-
---------------
97+
.. code-block:: python
6698
67-
* Getting information on a vector dataset as JSON
99+
from osgeo import gdal
68100
69-
.. code-block:: python
101+
gdal.UseExceptions()
102+
gdal.Run(["raster", "convert"], {"input": "in.tif", "output": "out.tif", "output-format": "COG", "overwrite": True})
70103
71-
from osgeo import gdal
72104
73-
gdal.UseExceptions()
74-
with gdal.run(["raster", "info"], {"input": "poly.gpkg"}) as info:
75-
print(info)
105+
.. example::
106+
:title: Reprojecting a GeoTIFF file to a Deflate-compressed tiled GeoTIFF file
76107

108+
.. code-block:: python
77109
78-
* Converting a shapefile to a GeoPackage
110+
from osgeo import gdal
79111
80-
.. code-block:: python
112+
gdal.UseExceptions()
113+
gdal.Run("raster", "reproject", input="in.tif", output="out.tif",
114+
dst_crs="EPSG:4326",
115+
creation_options={ "TILED": "YES", "COMPRESS": "DEFLATE"})
116+
117+
118+
.. example::
119+
:title: Reprojecting a (possibly in-memory) dataset to a in-memory dataset
120+
121+
.. code-block:: python
122+
123+
from osgeo import gdal
124+
125+
gdal.UseExceptions()
126+
with gdal.Run("raster", "reproject", input=src_ds, output_format="MEM",
127+
dst_crs="EPSG:4326"}) as alg:
128+
values = alg.Output().ReadAsArray()
129+
130+
131+
Vector commands examples
132+
------------------------
133+
134+
135+
.. example::
136+
:title: Getting information on a vector dataset as JSON
137+
138+
.. code-block:: python
139+
140+
from osgeo import gdal
141+
142+
gdal.UseExceptions()
143+
alg = gdal.Run("raster", "info", input="poly.gpkg"})
144+
info_as_dict = alg.Output()
145+
146+
147+
.. example::
148+
:title: Converting a shapefile to a GeoPackage
149+
150+
.. code-block:: python
81151
82-
from osgeo import gdal
152+
from osgeo import gdal
83153
84-
gdal.UseExceptions()
85-
with gdal.run(["raster", "convert"], {"input": "in.shp", "output": "out.gpkg", "overwrite": True}):
86-
pass
154+
gdal.UseExceptions()
155+
gdal.Run("raster", "convert", input="in.shp", output="out.gpkg", overwrite=True)

doc/source/spelling_wordlist.txt

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Alamos
4646
Albers
4747
Alessandro
4848
alg
49+
AlgorithmArg
4950
Alibaba
5051
Alicante
5152
allCountries

0 commit comments

Comments
 (0)