From 045ba5600d0578211fa2a2a96ffc65b100c4e8c9 Mon Sep 17 00:00:00 2001 From: Yaroslav Halchenko Date: Fri, 3 May 2024 10:22:12 -0400 Subject: [PATCH] Provide strtobool to avoid use of deprecated (removed in 3.12) distutils (#457) * Provide strtobool to avoid use of deprecated (removed in 3.12) distutils * Add Python 3.12 to supported * Update docs/developer_guide.rst --------- Co-authored-by: Cody Baker <51133164+CodyCBakerPhD@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/workflows/streaming-tests.yml | 2 +- .github/workflows/testing.yml | 2 +- CHANGELOG.md | 1 + docs/developer_guide.rst | 2 +- docs/user_guide/installation.rst | 2 +- setup.py | 1 + src/nwbinspector/nwbinspector.py | 2 +- src/nwbinspector/testing.py | 3 +-- src/nwbinspector/utils.py | 19 +++++++++++++++++++ tests/test_utils.py | 22 ++++++++++++++++++++++ 11 files changed, 50 insertions(+), 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 7d667e0cd..6ab2d5d09 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -40,6 +40,7 @@ body: - 3.9 - "3.10" - "3.11" + - "3.12" - type: dropdown id: streaming attributes: diff --git a/.github/workflows/streaming-tests.yml b/.github/workflows/streaming-tests.yml index 09e05b0e0..c72321633 100644 --- a/.github/workflows/streaming-tests.yml +++ b/.github/workflows/streaming-tests.yml @@ -9,7 +9,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "windows-latest"] # TODO: update mac and streaming methods - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: s-weigand/setup-conda@v1 with: diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 78b8821e9..7c494e1ac 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: os: ["ubuntu-latest", "macos-13", "windows-latest"] - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: conda-incubator/setup-miniconda@v3 with: diff --git a/CHANGELOG.md b/CHANGELOG.md index 7268393b9..5c7fb403e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ ### Improvements * Exposed progress bar control to `inspect_all` and `run_checks` to allow compatibility with more generic visualizations of inspection progress related to the NWB GUIDED. [#443](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/443) +* Added Python 3.12 support. [#457](https://github.com/NeurodataWithoutBorders/nwbinspector/pull/457) ### Testing diff --git a/docs/developer_guide.rst b/docs/developer_guide.rst index 719575981..a4d91d31e 100644 --- a/docs/developer_guide.rst +++ b/docs/developer_guide.rst @@ -48,7 +48,7 @@ Disable Tests That Require Network Connection Some of the tests in the suite require internet connectivity both to and from the DANDI archive S3 bucket. If this is failing for some reason, you can explicitly control all related tests by setting the environment variable -``NWBI_SKIP_NETWORK_TESTS`` to some value able to be parsed by ``distutils.util.str2tool``. For example, to disable them on +``NWBI_SKIP_NETWORK_TESTS`` to some value able to be parsed by ``nwbinspector.utils.strtobool``. For example, to disable them on a linux system, run .. code-block:: diff --git a/docs/user_guide/installation.rst b/docs/user_guide/installation.rst index 2ce5e9de0..2800e8711 100644 --- a/docs/user_guide/installation.rst +++ b/docs/user_guide/installation.rst @@ -6,7 +6,7 @@ If you haven't checked it out already, please read the :nwb-overview:`NWB Overvi The NWBInspector tool offers convenient command-line usage via any standard Conda or Python terminal. -To install the package in any generic Python v3.8-v3.11 environment, simply type +To install the package in any generic Python v3.8-v3.12 environment, simply type :: diff --git a/setup.py b/setup.py index 2d50976fa..a0788c424 100644 --- a/setup.py +++ b/setup.py @@ -42,5 +42,6 @@ "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", ], ) diff --git a/src/nwbinspector/nwbinspector.py b/src/nwbinspector/nwbinspector.py index 13a9ff014..2471eb3e7 100644 --- a/src/nwbinspector/nwbinspector.py +++ b/src/nwbinspector/nwbinspector.py @@ -12,7 +12,6 @@ from concurrent.futures import ProcessPoolExecutor, as_completed from types import FunctionType from warnings import filterwarnings, warn -from distutils.util import strtobool from collections import defaultdict from packaging.version import Version @@ -38,6 +37,7 @@ robust_s3_read, calculate_number_of_cpu, get_package_version, + strtobool, ) from nwbinspector import __version__ diff --git a/src/nwbinspector/testing.py b/src/nwbinspector/testing.py index f376a1314..6e7985d8f 100644 --- a/src/nwbinspector/testing.py +++ b/src/nwbinspector/testing.py @@ -3,7 +3,6 @@ import os import json from uuid import uuid4 -from distutils.util import strtobool from pathlib import Path from datetime import datetime from typing import Tuple, Optional @@ -16,7 +15,7 @@ from pynwb import NWBHDF5IO, NWBFile from pynwb.image import ImageSeries -from .utils import is_module_installed, get_package_version +from .utils import is_module_installed, get_package_version, strtobool # The tests must be invoked at the outer level of the repository TESTING_CONFIG_FILE_PATH = Path.cwd() / "tests" / "testing_config.json" diff --git a/src/nwbinspector/utils.py b/src/nwbinspector/utils.py index 75ba6c8c9..110316c3c 100644 --- a/src/nwbinspector/utils.py +++ b/src/nwbinspector/utils.py @@ -234,3 +234,22 @@ def __get_shape_helper(local_data): if hasattr(data, "__len__") and not isinstance(data, (str, bytes)): if not strict_no_data_load or isinstance(data, (list, tuple, set)): return __get_shape_helper(data) + + +def strtobool(val: str) -> bool: + """ + Convert a string representation of truth to True or False. + + True values are 'y', 'yes', 't', 'true', 'on', and '1'; + False values are 'n', 'no', 'f', 'false', 'off', and '0'. + Raises ValueError if 'val' is anything else. + """ + if not isinstance(val, str): + raise TypeError(f"Invalid type of {val!r} - must be str for `strtobool`") + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return True + elif val in ("n", "no", "f", "false", "off", "0"): + return False + else: + raise ValueError(f"Invalid truth value {val!r}") diff --git a/tests/test_utils.py b/tests/test_utils.py index 52ce0e1c4..57d58cf9e 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -11,9 +11,13 @@ get_package_version, calculate_number_of_cpu, is_ascending_series, + strtobool, ) +import pytest + + def test_format_byte_size(): assert format_byte_size(byte_size=12345) == "12.35KB" @@ -142,3 +146,21 @@ def test_is_ascending_series(): assert is_ascending_series(series=[1, 1, 1]) assert is_ascending_series(series=[1, 2, 3]) assert not is_ascending_series(series=[1, 2, 1]) + + +@pytest.mark.parametrize( + "values,target", + [ + (("y", "yes", "t", "true", "on", "1"), True), + (("n", "no", "f", "false", "off", "0"), False), + ], +) +def test_strtobool(values, target): + for v in values: + assert strtobool(v) is target + assert strtobool(v.upper()) is target + with pytest.raises(ValueError): + strtobool(v + "1") + # it is strtobool, so no bool is allowed + with pytest.raises(TypeError): + strtobool(target)