Repository: dask/dask
Pull request: Create show_versions.py
- [x] Closes #9099
- [x] Tests passed (nothing added)
- [x] Passes `pre-commit run --all-files`

For now the import is not integrated into `__init__.py`, not sure if that's feasible/reasonable.

```python
In [2]: from dask.utils import show_versions

In [3]: show_versions()
{
  "Python": "3.10.4",
  "Platform": "Linux",
  "dask": "2022.5.2+8.geb5f4e200",
  "distributed": "2022.5.2",
  "numpy": "1.22.3",
  "pandas": "1.4.2",
  "cloudpickle": "2.1.0",
  "fsspec": "2022.5.0",
  "bokeh": "2.4.3",
  "fastparquet": null,
  "pyarrow": null,
  "zarr": null
}
```


Problem statement:
Improving bug report template
This is a suggestion to simplify reporting the dask version in the issue template by providing a suggested code snippet to run (in the **Environment** section of the issue template):

```python
import dask; print("- Dask version:", dask.__version__)
import sys; print("- Python version:", sys.version)
```

Hints from issue discussion:
Thanks for the suggestion @SultanOrazbayev. I'd go a step further and follow the lead from other projects like `pandas` / `xarray` and add a `dask.show_versions()` method which outputs all the version/system-related information we want included when users open an issue. That would let us replace that section in the template with something like "Include the output of `dask.show_versions()`".

Is this something you're interested in working on? (no obligation though)
Thanks, James, yes, I am interested in this!

On Thursday, May 19, 2022, James Bourbeau ***@***.***> wrote:

> Thanks for the suggestion @SultanOrazbayev
> <https://github.com/SultanOrazbayev>. I'd go a step further and follow
> the lead from other projects like pandas / xarray and add a
> dask.show_versions() method which outputs all the version/system-related
> information we want included when users open an issue. That would let us
> replace that section in the template with something like "Include the
> output of dask.show_versions()".
>
> Is this something you're interested in working on? (no obligation though)
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/dask/dask/issues/9099#issuecomment-1130390141>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AE2FWER5Z77TABRSB7MVYJDVKU4GJANCNFSM5WIN5EVA>
> .
> You are receiving this because you were mentioned.Message ID:
> ***@***.***>
>

I'll propose an alternative which is that we just don't ask people for this
information.  I almost never provide it, and I find it useful only a small
fraction of the time.  Should we reconsider asking folks to provide this?

On Wed, May 18, 2022 at 2:04 PM Sultan Orazbayev ***@***.***>
wrote:

> Thanks, James, yes, I am interested in this!
>
> On Thursday, May 19, 2022, James Bourbeau ***@***.***> wrote:
>
> > Thanks for the suggestion @SultanOrazbayev
> > <https://github.com/SultanOrazbayev>. I'd go a step further and follow
> > the lead from other projects like pandas / xarray and add a
> > dask.show_versions() method which outputs all the version/system-related
> > information we want included when users open an issue. That would let us
> > replace that section in the template with something like "Include the
> > output of dask.show_versions()".
> >
> > Is this something you're interested in working on? (no obligation though)
> >
> > —
> > Reply to this email directly, view it on GitHub
> > <https://github.com/dask/dask/issues/9099#issuecomment-1130390141>, or
> > unsubscribe
> > <
> https://github.com/notifications/unsubscribe-auth/AE2FWER5Z77TABRSB7MVYJDVKU4GJANCNFSM5WIN5EVA
> >
> > .
> > You are receiving this because you were mentioned.Message ID:
> > ***@***.***>
> >
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/dask/dask/issues/9099#issuecomment-1130398225>, or
> unsubscribe
> <https://github.com/notifications/unsubscribe-auth/AACKZTCEHEB5WGTB3TOHTQTVKU5K3ANCNFSM5WIN5EVA>
> .
> You are receiving this because you are subscribed to this thread.Message
> ID: ***@***.***>
>

> I am interested in this!

Great, getting a bit more specific it would be nice to have output like the following:

```python
>>> import dask
>>> dask.show_versions()
Python: 3.9.6
Platform: darwin
Versions:
- dask: X.Y.Z
- distributed: X.Y.Z
- numpy: X.Y.Z
- pandas: X.Y.Z
- cloudpickle: X.Y.Z
- fsspec: X.Y.Z
- bokeh: X.Y.Z
- fastparquet: X.Y.Z
- pyarrow: X.Y.Z
- zarr: X.Y.Z
```

> Should we reconsider asking folks to provide this?

I find myself asking questions like "What version of `dask` are you using? Have you tried the latest release yet?" / "What version of `pandas` / `numpy` / `pyarrow` / ... are you using?" often. I think something like `dask.show_versions()` would allow us to answer several of those questions proactively without users needing to do much. That said, we don't require any of this information when opening an issue, so folks can still feel free to not include it, but it streamlines the process for those that do

Commit message: Create show_versions.py

Patch to review:
diff --git a/dask/show_versions.py b/dask/show_versions.py
new file mode 100644
index 00000000000..21eaa5f56b4
--- /dev/null
+++ b/dask/show_versions.py
@@ -0,0 +1,57 @@
+from json import dumps
+from platform import uname
+from sys import stdout, version_info
+
+from pandas._typing import JSONSerializable
+from pandas.compat._optional import get_version, import_optional_dependency
+
+
+def show_versions(as_json: bool = False) -> None:
+    """Provide version information for bug reports.
+
+    Parameters
+    ----------
+    as_json : bool, default False
+        * If False, outputs info in a YAML format to the console (can be copy-
+          pasted into an environment.yml file for conda).
+        * If True, outputs info in JSON format to the console.
+    """
+
+    def _format_as_conda_environment_yaml(result: dict[str, JSONSerializable]) -> str:
+        """A small utility to return dependencies in a conda-friendly format."""
+        formatted_result = ""
+        for dep, dep_ver in result.items():
+            if dep == "Platform" or dep_ver is None:
+                continue
+            formatted_result += f"- {dep}={dep_ver}"
+            formatted_result += "\n"
+
+        return formatted_result
+
+    deps = [
+        "dask",
+        "distributed",
+        "numpy",
+        "pandas",
+        "cloudpickle",
+        "fsspec",
+        "bokeh",
+        "fastparquet",
+        "pyarrow",
+        "zarr",
+    ]
+
+    result = {
+        # note: only major, minor, micro are extracted
+        "Python": ".".join([str(i) for i in version_info[:3]]),
+        "Platform": uname().system,
+    }
+
+    for modname in deps:
+        mod = import_optional_dependency(modname, errors="ignore")
+        result[modname] = get_version(mod) if mod else None
+
+    if as_json is True:
+        stdout.writelines(dumps(result, indent=2))
+    elif as_json is False:
+        stdout.writelines(_format_as_conda_environment_yaml(result))


Code review comments:
diff_hunk: @@ -0,0 +1,57 @@
+from json import dumps
+from platform import uname
+from sys import stdout, version_info
+
+from pandas._typing import JSONSerializable
+from pandas.compat._optional import get_version, import_optional_dependency
+
+
+def show_versions(as_json: bool = False) -> None:
+    """Provide version information for bug reports.
+
+    Parameters
+    ----------
+    as_json : bool, default False
+        * If False, outputs info in a YAML format to the console (can be copy-
+          pasted into an environment.yml file for conda).
+        * If True, outputs info in JSON format to the console.
path: dask/show_versions.py
text: @user1:
Maybe let's hold off on adding this flag for now until someone asks for it (if they do) with a reasonably generic use case. I think just a plain `dask.show_versions()` call that prints to the screen is a good first step.

diff_hunk: @@ -0,0 +1,57 @@
+from json import dumps
+from platform import uname
+from sys import stdout, version_info
+
+from pandas._typing import JSONSerializable
+from pandas.compat._optional import get_version, import_optional_dependency
path: dask/show_versions.py
text: @user1:
I'd prefer to avoid using private methods where possible as they may go away / have breaking changes without warning

diff_hunk: @@ -0,0 +1,57 @@
+from json import dumps
+from platform import uname
+from sys import stdout, version_info
+
+from pandas._typing import JSONSerializable
+from pandas.compat._optional import get_version, import_optional_dependency
+
+
+def show_versions(as_json: bool = False) -> None:
+    """Provide version information for bug reports.
+
+    Parameters
+    ----------
+    as_json : bool, default False
+        * If False, outputs info in a YAML format to the console (can be copy-
+          pasted into an environment.yml file for conda).
+        * If True, outputs info in JSON format to the console.
+    """
+
+    def _format_as_conda_environment_yaml(result: dict[str, JSONSerializable]) -> str:
+        """A small utility to return dependencies in a conda-friendly format."""
+        formatted_result = ""
+        for dep, dep_ver in result.items():
+            if dep == "Platform" or dep_ver is None:
+                continue
+            formatted_result += f"- {dep}={dep_ver}"
+            formatted_result += "\n"
+
+        return formatted_result
path: dask/show_versions.py
text: @user1:
Do we need a this as a separate nested function, or can this be easily inlined below where this function is currently being called?

diff_hunk: @@ -0,0 +1,57 @@
+from json import dumps
+from platform import uname
+from sys import stdout, version_info
+
+from pandas._typing import JSONSerializable
+from pandas.compat._optional import get_version, import_optional_dependency
+
+
+def show_versions(as_json: bool = False) -> None:
+    """Provide version information for bug reports.
+
+    Parameters
+    ----------
+    as_json : bool, default False
+        * If False, outputs info in a YAML format to the console (can be copy-
+          pasted into an environment.yml file for conda).
+        * If True, outputs info in JSON format to the console.
+    """
+
+    def _format_as_conda_environment_yaml(result: dict[str, JSONSerializable]) -> str:
+        """A small utility to return dependencies in a conda-friendly format."""
+        formatted_result = ""
+        for dep, dep_ver in result.items():
+            if dep == "Platform" or dep_ver is None:
+                continue
+            formatted_result += f"- {dep}={dep_ver}"
+            formatted_result += "\n"
+
+        return formatted_result
+
+    deps = [
+        "dask",
+        "distributed",
+        "numpy",
+        "pandas",
+        "cloudpickle",
+        "fsspec",
+        "bokeh",
+        "fastparquet",
+        "pyarrow",
+        "zarr",
+    ]
+
+    result = {
+        # note: only major, minor, micro are extracted
+        "Python": ".".join([str(i) for i in version_info[:3]]),
+        "Platform": uname().system,
+    }
+
+    for modname in deps:
+        mod = import_optional_dependency(modname, errors="ignore")
path: dask/show_versions.py
text: @user1:
I believe we can use Python's `importlib.metadata` module here to determine if a package is installed and extract the version number https://docs.python.org/3/library/importlib.metadata.html. This should let us avoid the need for `import_optional_dependency`, which is a private pandas method