diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9fd3ab8..01c73ce 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,7 +8,7 @@ on: - published env: - MINIMUM_PYTHON_VERSION: "3.7" + MINIMUM_PYTHON_VERSION: "3.8" concurrency: group: ${{ github.head_ref || github.run_id }} @@ -20,7 +20,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Python ${{ env.MINIMUM_PYTHON_VERSION }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ env.MINIMUM_PYTHON_VERSION }} - uses: actions/checkout@v3 @@ -36,29 +36,25 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.7", "3.8", "3.9"] + python-version: ["3.8", "3.9", "3.10", "3.11"] steps: - uses: actions/checkout@v3 - name: Setup Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install Tox and any other packages run: | python -m pip install --upgrade pip python -m pip install tox-gh-actions poetry - - name: Run quick tests (Not Python ${{ env.MINIMUM_PYTHON_VERSION }}) - if: ${{ matrix.python-version != env.MINIMUM_PYTHON_VERSION }} - run: tox -- -m "not slow" - - name: Run all tests (Python ${{ env.MINIMUM_PYTHON_VERSION }} only) - if: ${{ matrix.python-version == env.MINIMUM_PYTHON_VERSION }} + - name: Run all tests run: tox docs: runs-on: ubuntu-latest steps: - name: Install Python ${{ env.MINIMUM_PYTHON_VERSION }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ env.MINIMUM_PYTHON_VERSION }} - uses: actions/checkout@v3 @@ -81,7 +77,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Install Python ${{ env.MINIMUM_PYTHON_VERSION }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: ${{ env.MINIMUM_PYTHON_VERSION }} - uses: actions/checkout@v3 diff --git a/.gitignore b/.gitignore index 4ab6e72..9eb2032 100644 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,6 @@ ENV/ # Poetry poetry.lock + +# vscode +.vscode/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 45db7b6..9fb0648 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.1.0 + rev: v4.4.0 hooks: - id: check-docstring-first - id: debug-statements @@ -8,22 +8,22 @@ repos: - id: mixed-line-ending - id: trailing-whitespace - repo: https://github.com/asottile/pyupgrade - rev: v2.31.0 + rev: v3.3.1 hooks: - id: pyupgrade language: python - args: [--py37-plus] + args: [--py38-plus] - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort - repo: https://github.com/ambv/black - rev: 22.3.0 + rev: 23.1.0 hooks: - id: black language: python - - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 hooks: - id: flake8 language: python @@ -35,8 +35,6 @@ repos: - mccabe - yesqa - repo: https://github.com/pre-commit/mirrors-mypy - rev: 'v0.931' + rev: 'v1.0.1' hooks: - id: mypy - additional_dependencies: - - types-setuptools # Required for versioning on py37 diff --git a/HISTORY.md b/HISTORY.md index 6ba1e9a..4e027a3 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # History +## 0.3.2 (UNRELEASED) + + - Removed support for Python 3.7 + - Added support for Python 3.10 and Python 3.11 + ## 0.3.1 (2022-01-24) - Added parameters to docker run commands to better reflect how diff --git a/README.md b/README.md index 569c6b7..b79064b 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ containers for grand-challenge.org. ## Getting Started -[evalutils](https://github.com/comic/evalutils) requires Python 3.7 or +[evalutils](https://github.com/comic/evalutils) requires Python 3.8 or above, and can be installed from `pip`. Please see the [Getting Started](https://comic.github.io/evalutils/usage.html) documentation for more details. diff --git a/docs/conf.py b/docs/conf.py index 2cb539c..3750d09 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -19,17 +19,10 @@ # import os import sys +from importlib.metadata import version as _get_version from typing import Dict, List -try: - from importlib.metadata import version - - evalutils_version = version("evalutils") -except ImportError: - # py <= py37 - from pkg_resources import get_distribution - - evalutils_version = get_distribution("evalutils").version +evalutils_version = _get_version("evalutils") sys.path.insert(0, os.path.abspath("..")) diff --git a/docs/contributing.rst b/docs/contributing.rst index 8758468..adc6497 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -102,7 +102,7 @@ Before you submit a pull request, check that it meets these guidelines: 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.md. -3. The pull request should work for Python 3.7, 3.8 and 3.9. Check +3. The pull request should work for Python 3.8, 3.9, 3.10 and 3.11. Check https://travis-ci.org/comic/evalutils/pull_requests and make sure that the tests pass for all supported Python versions. diff --git a/docs/environment.yml b/docs/environment.yml deleted file mode 100644 index 9f7cf67..0000000 --- a/docs/environment.yml +++ /dev/null @@ -1,10 +0,0 @@ -name: evalutils -channels: - - anaconda -dependencies: - - pip: - - SimpleITK - - sphinx-autodoc-typehints - - python=3.9 - - pandas - - imageio diff --git a/docs/usage.rst b/docs/usage.rst index a7ff4f3..d5234dd 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -67,7 +67,7 @@ The different challenge types that you can select are: For this sort of challenge, you may have many candidate points and many ground truth points per case. By default, the results per case are also reported. -If you do not have a local python 3.7+ environment you can also +If you do not have a local python 3.8+ environment you can also generate your project with docker by running a container and sharing your current user id: .. code-block:: console @@ -396,7 +396,7 @@ The different algorithm types that you can select are: For instance, this evaluation could be used for detection of tumours in images. By default, the algorithm outputs a single `/output/results.json` which lists the results per case. -If you do not have a local python 3.7+ environment you can also +If you do not have a local python 3.8+ environment you can also generate your project with docker by running a container and sharing your current user id: .. code-block:: console diff --git a/evalutils/cli.py b/evalutils/cli.py index 76a97db..f843941 100644 --- a/evalutils/cli.py +++ b/evalutils/cli.py @@ -1,4 +1,6 @@ import re +import sys +from importlib.metadata import version from pathlib import Path from typing import List @@ -6,15 +8,7 @@ from cookiecutter.exceptions import FailedHookException from cookiecutter.main import cookiecutter -try: - from importlib.metadata import version - - evalutils_version = version("evalutils") -except ImportError: - # py <= py37 - from pkg_resources import get_distribution - - evalutils_version = get_distribution("evalutils").version +evalutils_version = version("evalutils") EVALUATION_CHOICES = ["Classification", "Segmentation", "Detection"] ALGORITHM_CHOICES = EVALUATION_CHOICES @@ -40,7 +34,7 @@ def validate_python_module_name_string(ctx, param, arg): exit(1) if not re.match(MODULE_REGEX, arg) or arg in FORBIDDEN_NAMES: - click.echo(f"ERROR: '{arg}' is not a valid Python module name!") + click.echo(f"ERROR: {arg!r} is not a valid Python module name!") exit(1) return arg @@ -96,6 +90,8 @@ def init_evaluation(challenge_name, kind, dev): "evalutils_version": evalutils_version, "challenge_kind": kind, "dev_build": 1 if dev else 0, + "python_major_version": sys.version_info.major, + "python_minor_version": sys.version_info.minor, }, ) click.echo(f"Created project {challenge_name}") @@ -186,6 +182,8 @@ def init_algorithm(algorithm_name, kind, dev): "evalutils_name": __name__.split(".")[0], "evalutils_version": evalutils_version, "dev_build": 1 if dev else 0, + "python_major_version": sys.version_info.major, + "python_minor_version": sys.version_info.minor, }, ) click.echo(f"Created project {algorithm_name}") diff --git a/evalutils/evalutils.py b/evalutils/evalutils.py index 71fd91a..645d273 100644 --- a/evalutils/evalutils.py +++ b/evalutils/evalutils.py @@ -131,7 +131,7 @@ def _load_cases( *, folder: Path, file_loader: ImageLoader, - file_filter: Pattern[str] = None, + file_filter: Optional[Pattern[str]] = None, ) -> DataFrame: cases = None @@ -183,7 +183,7 @@ def process(self): self.process_cases() self.save() - def process_cases(self, file_loader_key: str = None): + def process_cases(self, file_loader_key: Optional[str] = None): if file_loader_key is None: file_loader_key = self._index_key self._case_results = [] @@ -329,8 +329,8 @@ def __init__( file_sorter_key: Callable = first_int_in_filename_key, file_loader: FileLoader, validators: Tuple[DataFrameValidator, ...], - join_key: str = None, - aggregates: Set[str] = None, + join_key: Optional[str] = None, + aggregates: Optional[Set[str]] = None, output_file: PathLike = DEFAULT_EVALUATION_OUTPUT_FILE_PATH, ): """ diff --git a/evalutils/io.py b/evalutils/io.py index df56c28..182944a 100644 --- a/evalutils/io.py +++ b/evalutils/io.py @@ -46,7 +46,7 @@ def first_int_in_filename_key(fname: Path) -> str: try: return f"{get_first_int_in(fname.stem):>64}" except AttributeError: - logger.warning(f"Could not find an int in the string '{fname.stem}'.") + logger.warning(f"Could not find an int in the string {fname.stem!r}.") return fname.stem diff --git a/evalutils/templates/algorithm/cookiecutter.json b/evalutils/templates/algorithm/cookiecutter.json index 987ec5e..f312b33 100644 --- a/evalutils/templates/algorithm/cookiecutter.json +++ b/evalutils/templates/algorithm/cookiecutter.json @@ -8,9 +8,7 @@ "package_name": "{{ cookiecutter.algorithm_name|replace(' ', '') }}", "evalutils_name": "", "evalutils_version": "", - "docker_base_container": "python:3.9-slim", "dev_build": 0, - "_copy_without_render": [ - "*.github" - ] + "python_major_version": "", + "python_minor_version": "" } diff --git a/evalutils/templates/algorithm/hooks/pre_gen_project.py b/evalutils/templates/algorithm/hooks/pre_gen_project.py index 281b1ae..264caf4 100644 --- a/evalutils/templates/algorithm/hooks/pre_gen_project.py +++ b/evalutils/templates/algorithm/hooks/pre_gen_project.py @@ -7,5 +7,5 @@ package_name = "{{ cookiecutter.package_name }}" if not re.match(MODULE_REGEX, package_name) or package_name in FORBIDDEN_NAMES: - print(f"ERROR: '{package_name}' is not a valid Python module name!") + print(f"ERROR: {package_name!r} is not a valid Python module name!") exit(1) diff --git a/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/.github/workflows/ci.yml b/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/.github/workflows/ci.yml index 5ea8b5e..dd0570c 100644 --- a/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/.github/workflows/ci.yml +++ b/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/.github/workflows/ci.yml @@ -3,17 +3,17 @@ name: CI on: [push, pull_request] env: - PYTHON_VERSION: '3.9' + PYTHON_VERSION: '{{ cookiecutter.python_major_version }}.{{ cookiecutter.python_minor_version }}' jobs: algorithm-tests: runs-on: ubuntu-latest steps: - - name: Install Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v3 + - name: Install Python ${{ "{{" }} env.PYTHON_VERSION {{ "}}" }} + uses: actions/setup-python@v4 with: - python-version: ${{ env.PYTHON_VERSION }} + python-version: ${{ "{{" }} env.PYTHON_VERSION {{ "}}" }} - uses: actions/checkout@v3 - name: Build the containers run: | diff --git a/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/Dockerfile b/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/Dockerfile index 20a4f57..731c669 100644 --- a/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/Dockerfile +++ b/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/Dockerfile @@ -1,4 +1,4 @@ -FROM {{ cookiecutter.docker_base_container }} +FROM python:{{ cookiecutter.python_major_version }}.{{ cookiecutter.python_minor_version }}-slim {% if cookiecutter.dev_build|int -%} RUN apt-get update && apt-get install -y git @@ -28,4 +28,4 @@ RUN python -m pip install --user -rrequirements.txt COPY --chown=algorithm:algorithm process.py /opt/algorithm/ -ENTRYPOINT python -m process $0 $@ +ENTRYPOINT [ "python", "-m", "process" ] diff --git a/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/requirements.txt b/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/requirements.txt index 826d796..f81d119 100644 --- a/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/requirements.txt +++ b/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/requirements.txt @@ -1,5 +1,3 @@ {% if not cookiecutter.dev_build|int %} {{ cookiecutter.evalutils_name }}=={{ cookiecutter.evalutils_version }} {% endif %} -scikit-learn==0.24.2 -scipy==1.6.3 diff --git a/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/test.sh b/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/test.sh index 3d47407..8d00def 100755 --- a/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/test.sh +++ b/evalutils/templates/algorithm/{{ cookiecutter.package_name }}/test.sh @@ -24,12 +24,12 @@ docker run --rm \ docker run --rm \ -v {{ cookiecutter.package_name|lower }}-output-$VOLUME_SUFFIX:/output/ \ - {{ cookiecutter.docker_base_container }} cat /output/results.json | python -m json.tool + python:{{ cookiecutter.python_major_version }}.{{ cookiecutter.python_minor_version }}-slim cat /output/results.json | python -m json.tool docker run --rm \ -v {{ cookiecutter.package_name|lower }}-output-$VOLUME_SUFFIX:/output/ \ -v $SCRIPTPATH/test/:/input/ \ - {{ cookiecutter.docker_base_container }} python -c "import json, sys; f1 = json.load(open('/output/results.json')); f2 = json.load(open('/input/expected_output.json')); sys.exit(f1 != f2);" + python:{{ cookiecutter.python_major_version }}.{{ cookiecutter.python_minor_version }}-slim python -c "import json, sys; f1 = json.load(open('/output/results.json')); f2 = json.load(open('/input/expected_output.json')); sys.exit(f1 != f2);" if [ $? -eq 0 ]; then echo "Tests successfully passed..." diff --git a/evalutils/templates/evaluation/cookiecutter.json b/evalutils/templates/evaluation/cookiecutter.json index fcc0383..9718602 100644 --- a/evalutils/templates/evaluation/cookiecutter.json +++ b/evalutils/templates/evaluation/cookiecutter.json @@ -8,9 +8,7 @@ ], "evalutils_name": "", "evalutils_version": "", - "docker_base_container": "python:3.9-slim", "dev_build": 0, - "_copy_without_render": [ - "*.github" - ] + "python_major_version": "", + "python_minor_version": "" } diff --git a/evalutils/templates/evaluation/hooks/pre_gen_project.py b/evalutils/templates/evaluation/hooks/pre_gen_project.py index 281b1ae..264caf4 100644 --- a/evalutils/templates/evaluation/hooks/pre_gen_project.py +++ b/evalutils/templates/evaluation/hooks/pre_gen_project.py @@ -7,5 +7,5 @@ package_name = "{{ cookiecutter.package_name }}" if not re.match(MODULE_REGEX, package_name) or package_name in FORBIDDEN_NAMES: - print(f"ERROR: '{package_name}' is not a valid Python module name!") + print(f"ERROR: {package_name!r} is not a valid Python module name!") exit(1) diff --git a/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/.github/workflows/ci.yml b/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/.github/workflows/ci.yml index 3e95a9d..c18166c 100644 --- a/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/.github/workflows/ci.yml +++ b/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/.github/workflows/ci.yml @@ -3,17 +3,17 @@ name: CI on: [push, pull_request] env: - PYTHON_VERSION: '3.9' + PYTHON_VERSION: '{{ cookiecutter.python_major_version }}.{{ cookiecutter.python_minor_version }}' jobs: evaluation-tests: runs-on: ubuntu-latest steps: - - name: Install Python ${{ env.PYTHON_VERSION }} - uses: actions/setup-python@v3 + - name: Install Python ${{ "{{" }} env.PYTHON_VERSION {{ "}}" }} + uses: actions/setup-python@v4 with: - python-version: ${{ env.PYTHON_VERSION }} + python-version: ${{ "{{" }} env.PYTHON_VERSION {{ "}}" }} - uses: actions/checkout@v3 - name: Build the containers run: | diff --git a/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/Dockerfile b/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/Dockerfile index 2b9935a..0b989ed 100644 --- a/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/Dockerfile +++ b/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/Dockerfile @@ -1,4 +1,4 @@ -FROM {{ cookiecutter.docker_base_container }} +FROM python:{{ cookiecutter.python_major_version }}.{{ cookiecutter.python_minor_version }}-slim {% if cookiecutter.dev_build|int -%} RUN apt-get update && apt-get install -y git @@ -29,4 +29,4 @@ RUN python -m pip install --user -rrequirements.txt COPY --chown=evaluator:evaluator evaluation.py /opt/evaluation/ -ENTRYPOINT "python" "-m" "evaluation" +ENTRYPOINT [ "python", "-m", "evaluation" ] diff --git a/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/requirements.txt b/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/requirements.txt index 826d796..f81d119 100644 --- a/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/requirements.txt +++ b/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/requirements.txt @@ -1,5 +1,3 @@ {% if not cookiecutter.dev_build|int %} {{ cookiecutter.evalutils_name }}=={{ cookiecutter.evalutils_version }} {% endif %} -scikit-learn==0.24.2 -scipy==1.6.3 diff --git a/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/test.sh b/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/test.sh index e8ba1b2..72c111a 100755 --- a/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/test.sh +++ b/evalutils/templates/evaluation/{{ cookiecutter.package_name }}/test.sh @@ -23,6 +23,6 @@ docker run --rm \ docker run --rm \ -v {{ cookiecutter.package_name|lower }}-output-$VOLUME_SUFFIX:/output/ \ - {{ cookiecutter.docker_base_container }} cat /output/metrics.json | python -m json.tool + python:{{ cookiecutter.python_major_version }}.{{ cookiecutter.python_minor_version }}-slim cat /output/metrics.json | python -m json.tool docker volume rm {{ cookiecutter.package_name|lower }}-output-$VOLUME_SUFFIX diff --git a/evalutils/validators.py b/evalutils/validators.py index 39653b0..a50d13f 100644 --- a/evalutils/validators.py +++ b/evalutils/validators.py @@ -102,7 +102,6 @@ def __init__( super().__init__() def validate(self, *, df: DataFrame): - undefined_cols = [c for c in self._expected if c not in df.columns] if undefined_cols: diff --git a/pyproject.toml b/pyproject.toml index 237cb06..ef08466 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,7 +17,7 @@ classifiers = [ evalutils = "evalutils.__main__:main" [tool.poetry.dependencies] -python = ">=3.7.1,<3.10" # Pandas requires py>=3.7.1 +python = "^3.8" imageio = { version = "*", extras=["tifffile"] } # Exclude 2.1.1.1 due to # https://github.com/SimpleITK/SimpleITK/issues/1627 @@ -27,12 +27,8 @@ cookiecutter = "*" click = "*" scipy = "*" scikit-learn = "*" -numpy = [ - { version = "^1.21", python = "<3.8" }, - { version = "^1.22", python = ">=3.8,<3.10" } -] -pandas = "^1.3" -setuptools = { version = "*", python = "~3.7" } +numpy = ">=1.22" +pandas = ">=1.3" [tool.poetry.dev-dependencies] pytest = "*" @@ -53,7 +49,7 @@ line_length = 79 [tool.black] line-length = 79 -target-version = ['py37'] +target-version = ['py38'] [tool.pytest.ini_options] minversion = "6.0" @@ -75,13 +71,14 @@ filterwarnings = [ legacy_tox_ini = """ [tox] isolated_build = True -envlist = py{37, 38, 39} +envlist = py38, py39, py310, p311 [gh-actions] python = - 3.7: py37 3.8: py38 3.9: py39 + 3.10: py310 + 3.11: py311 [testenv] allowlist_externals = diff --git a/setup.cfg b/setup.cfg index b674feb..bc1957c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -2,7 +2,7 @@ collect_ignore = ['setup.py'] [mypy] -python_version = 3.9 +python_version = 3.11 # disallow_untyped_defs = True [flake8] @@ -30,3 +30,6 @@ ignore = W503 # E501 (line lengths) will be checked with B950 instead E501 + # B905 `zip()` without an explicit `strict=` parameter + # Introduced in py310, still need to support py39 + B905