Repository: rasterio/rasterio

Issue:
PROPOSAL: show_versions
Thoughts on adding a `show_versions` method for simplifying the debug process? This will also help with bug reports.

Additionally, could add `--show-versions` to the `rio` CLI.


- [Pandas example](https://github.com/pandas-dev/pandas/blob/7b78b730aee122bb7d6e90723c4208da234de19e/pandas/util/_print_versions.py#L93)
- [pyproj example](https://github.com/pyproj4/pyproj/blob/75b5d0445e63112c1c1b67e940bc6ccd38e68dbc/pyproj/_show_versions.py#L114)


GDAL specific items to add: PROJ version, GEOS version, & data directories.


Discussion:
@snowman2 I feel like our bigger problem is that GDAL can link dozens of other native libraries and doesn't have an API to show those versions. In my experience that's what users struggle to grasp and struggle to communicate in bug reports. We've got a `rio --gdal-version` now https://github.com/rasterio/rasterio/blob/master/rasterio/rio/main.py#L76 but it doesn't say anything about the curl or openssh (for example) versions.
That makes sense. I think exposing available library versions used by `GDAL` would be a good thing. Here are some I found that `GDAL` exposes:
- [OSRGetPROJVersion](https://gdal.org/api/ogr_srs_api.html?#_CPPv417OSRGetPROJVersionPiPiPi)
- [OGRGetGEOSVersion](https://gdal.org/api/vector_c_api.html#_CPPv417OGRGetGEOSVersionPiPiPi)

Resolving patch:
diff --git a/rasterio/__init__.py b/rasterio/__init__.py
--- a/rasterio/__init__.py
+++ b/rasterio/__init__.py
@@ -8,6 +8,7 @@
 
 import rasterio._loading
 with rasterio._loading.add_gdal_dll_directories():
+    from rasterio._show_versions import show_versions
     from rasterio._version import gdal_version, get_geos_version, get_proj_version
     from rasterio.crs import CRS
     from rasterio.drivers import driver_from_extension, is_blacklisted
diff --git a/rasterio/_show_versions.py b/rasterio/_show_versions.py
new file mode 100644
--- /dev/null
+++ b/rasterio/_show_versions.py
@@ -0,0 +1,115 @@
+"""
+Utility methods to print system info for debugging
+
+adapted from :func:`sklearn.utils._show_versions`
+which was adapted from :func:`pandas.show_versions`
+"""
+import importlib
+import os
+import platform
+import sys
+
+
+def _get_sys_info():
+    """System information
+
+    Return
+    ------
+    dict:
+        system and Python version information
+    """
+    blob = [
+        ("python", sys.version.replace("\n", " ")),
+        ("executable", sys.executable),
+        ("machine", platform.platform()),
+    ]
+
+    return dict(blob)
+
+
+def _get_gdal_info():
+    """Information on system GDAL
+
+    Returns
+    -------
+    dict:
+        system GDAL information
+    """
+    # pylint: disable=import-outside-toplevel
+    import rasterio
+
+    blob = [
+        ("rasterio", rasterio.__version__),
+        ("GDAL", rasterio.__gdal_version__),
+        ("PROJ", rasterio.__proj_version__),
+        ("GEOS", rasterio.__geos_version__),
+        ("PROJ DATA", os.pathsep.join(rasterio._env.get_proj_data_search_paths())),
+        ("GDAL DATA", rasterio._env.get_gdal_data()),
+    ]
+
+    return dict(blob)
+
+
+def _get_deps_info():
+    """Overview of the installed version of main dependencies
+
+    Returns
+    -------
+    dict:
+        version information on relevant Python libraries
+    """
+    deps = [
+        "affine",
+        "attrs",
+        "certifi",
+        "click",
+        "cligj",
+        "cython",
+        "numpy",
+        "snuggs",
+        "click-plugins",
+        "setuptools",
+    ]
+
+    def get_version(module):
+        try:
+            return module.__version__
+        except AttributeError:
+            return module.version
+
+    deps_info = {}
+
+    for modname in deps:
+        try:
+            if modname in sys.modules:
+                mod = sys.modules[modname]
+            else:
+                mod = importlib.import_module(modname)
+            deps_info[modname] = get_version(mod)
+        except ImportError:
+            deps_info[modname] = None
+
+    return deps_info
+
+
+def _print_info_dict(info_dict):
+    """Print the information dictionary"""
+    for key, stat in info_dict.items():
+        print(f"{key:>10}: {stat}")
+
+
+def show_versions():
+    """
+    Print useful debugging information
+
+    Example
+    -------
+    > python -c "import rasterio; rasterio.show_versions()"
+
+    """
+    print("rasterio info:")
+    _print_info_dict(_get_gdal_info())
+    print("\nSystem:")
+    _print_info_dict(_get_sys_info())
+    print("\nPython deps:")
+    _print_info_dict(_get_deps_info())
diff --git a/rasterio/rio/main.py b/rasterio/rio/main.py
--- a/rasterio/rio/main.py
+++ b/rasterio/rio/main.py
@@ -56,6 +56,14 @@ def gdal_version_cb(ctx, param, value):
     click.echo("{0}".format(rasterio.__gdal_version__), color=ctx.color)
     ctx.exit()
 
+def show_versions_cb(ctx, param, value):
+    if not value or ctx.resilient_parsing:
+        return
+
+    rasterio.show_versions()
+    ctx.exit()
+
+
 
 @with_plugins(
     ep
@@ -74,6 +82,7 @@ def gdal_version_cb(ctx, param, value):
 )
 @click.version_option(version=rasterio.__version__, message="%(version)s")
 @click.option("--gdal-version", is_eager=True, is_flag=True, callback=gdal_version_cb)
+@click.option("--show-versions", help="Show dependency versions", is_eager=True, is_flag=True, callback=show_versions_cb)
 @click.pass_context
 def main_group(
     ctx,
@@ -83,6 +92,7 @@ def main_group(
     aws_no_sign_requests,
     aws_requester_pays,
     gdal_version,
+    show_versions,
 ):
     """Rasterio command line interface.
     """


Test patch:
diff --git a/tests/test__env.py b/tests/test__env.py
--- a/tests/test__env.py
+++ b/tests/test__env.py
@@ -1,8 +1,14 @@
 """Tests of _env util module"""
 
+from numpy import isin
 import pytest
 
-from rasterio._env import GDALDataFinder, PROJDataFinder
+from rasterio._env import (
+    GDALDataFinder,
+    PROJDataFinder,
+    get_proj_data_search_paths,
+    get_gdal_data,
+)
 
 from .conftest import gdal_version, requires_gdal_lt_3
 
@@ -125,3 +131,13 @@ def test_search_proj_data_wheel(mock_wheel):
 def test_search_proj_data_fhs(mock_fhs):
     finder = PROJDataFinder()
     assert finder.search(str(mock_fhs)) == str(mock_fhs.join("share").join("proj"))
+
+
+def test_get_proj_data_search_paths():
+    assert isinstance(get_proj_data_search_paths(), list)
+
+
+def test_get_gdal_data():
+    gdal_data = get_gdal_data()
+    if gdal_data is not None:
+        assert isinstance(gdal_data, str) and gdal_data
diff --git a/tests/test_rio_main.py b/tests/test_rio_main.py
--- a/tests/test_rio_main.py
+++ b/tests/test_rio_main.py
@@ -19,3 +19,12 @@ def test_gdal_version(runner):
     result = runner.invoke(main_group, ['--gdal-version'])
     assert result.exit_code == 0
     assert parse(result.output.strip())
+
+
+def test_show_versions(runner):
+    result = runner.invoke(main_group, ['--show-versions'])
+    assert result.exit_code == 0
+    assert "System" in result.output
+    assert "python" in result.output
+    assert "GDAL" in result.output
+    assert "Python deps" in result.output
diff --git a/tests/test_show_versions.py b/tests/test_show_versions.py
new file mode 100644
--- /dev/null
+++ b/tests/test_show_versions.py
@@ -0,0 +1,48 @@
+from rasterio._show_versions import (
+    _get_deps_info,
+    _get_gdal_info,
+    _get_sys_info,
+    show_versions,
+)
+
+
+def test_get_gdal_info():
+    yagdal_info = _get_gdal_info()
+    assert "rasterio" in yagdal_info
+    assert "GDAL" in yagdal_info
+    assert "PROJ" in yagdal_info
+    assert "GEOS" in yagdal_info
+    assert "PROJ DATA" in yagdal_info
+    assert "GDAL DATA" in yagdal_info
+
+
+def test_get_sys_info():
+    sys_info = _get_sys_info()
+
+    assert "python" in sys_info
+    assert "executable" in sys_info
+    assert "machine" in sys_info
+
+
+def test_get_deps_info():
+    deps_info = _get_deps_info()
+
+    assert "affine" in deps_info
+    assert "attrs" in deps_info
+    assert "certifi" in deps_info
+    assert "click" in deps_info
+    assert "click-plugins" in deps_info
+    assert "cligj" in deps_info
+    assert "cython" in deps_info
+    assert "numpy" in deps_info
+    assert "setuptools" in deps_info
+    assert "snuggs" in deps_info
+
+
+def test_show_versions_with_gdal(capsys):
+    show_versions()
+    out, err = capsys.readouterr()
+    assert "System" in out
+    assert "python" in out
+    assert "GDAL" in out
+    assert "Python deps" in out