Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Drop python 3.8 #1206

Merged
merged 13 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python_version: ["3.9", "3.10", "3.11", "3.12"]
os: ["ubuntu-20.04"]
qt_backend: ["PyQt5"]
tox_args: [ "" ]
Expand Down Expand Up @@ -90,7 +90,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python_version: ["3.9", "3.10", "3.11", "3.12"]
os: ["ubuntu-20.04", "macos-13", "windows-2019"]
qt_backend: ["PySide2", "PyQt5"]
include:
Expand Down Expand Up @@ -134,8 +134,8 @@ jobs:
uses: ./.github/workflows/base_test_workflow.yml
with:
test_data: True
python_version: "3.8"
tox_args: "-e py38-PyQt5-minimal"
python_version: "3.9"
tox_args: "-e py39-PyQt5-minimal"
coverage: true

coverage_prepare:
Expand Down
2 changes: 1 addition & 1 deletion azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ stages:
displayName: "download data"
- task: UsePythonVersion@0
inputs:
versionSpec: '3.9'
versionSpec: '3.11'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Potential Compatibility Issues with Python 3.11

The update to Python 3.11 may introduce compatibility issues due to existing version-specific checks in the codebase:

  • Test Skipping for Python >= 3.10:

    • pytest.mark.skipif(sys.version_info >= (3, 10), reason="class register is not compatible with python 3.10+")
  • Version Requirements:

    • @pytest.mark.skipif(sys.version_info < (3, 9), reason="requires python 3.9 or higher")
    • if sys.version_info < (3, 10):
    • if sys.version_info[:3] != (3, 9, 7):
    • if sys.version_info[:3] == (3, 9, 7):

These checks indicate that the code is not currently designed to support Python versions above 3.10, which includes the proposed 3.11 update.

Action Items:

  • Update Version Constraints: Modify the existing sys.version_info checks to include support for Python 3.11.
  • Dependency Verification: Ensure all project dependencies are compatible with Python 3.11.
  • Comprehensive Testing: Run the full test suite in a Python 3.11 environment to identify and address any compatibility issues.
🔗 Analysis chain

Approved: Python version update in GetTestData stage

The update from Python 3.9 to 3.11 in the GetTestData stage is consistent with the PR objective of dropping support for older Python versions. This change could potentially improve performance and allow the use of newer Python features.

To ensure this change doesn't introduce any compatibility issues, please verify:

  1. All dependencies used in the GetTestData stage are compatible with Python 3.11.
  2. The artifacts produced by this stage (TestData and ReleaseInfo) are still compatible with the downstream stages that depend on them.

You can use the following script to check for any Python version-specific code or dependencies in the repository:

This script will help identify any potential compatibility issues that might arise from updating the Python version.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for Python version-specific code or dependencies

# Search for version-specific imports or syntax
echo "Checking for version-specific Python code:"
rg --type python 'import.*python3\.(8|9|10)' || echo "No version-specific imports found."

# Check requirements files for version-specific dependencies
echo "Checking requirements files for version-specific dependencies:"
rg --type-add 'requirements:*.txt' --type requirements '^python_version' || echo "No version-specific dependencies found in requirements files."

# Check for sys.version_info checks
echo "Checking for sys.version_info checks:"
rg --type python 'sys\.version_info' || echo "No sys.version_info checks found."

Length of output: 826


Script:

#!/bin/bash
# Description: Check for Python version-specific code or dependencies

# Search for version-specific imports or syntax in Python files
echo "Checking for version-specific Python code:"
rg --glob '*.py' 'import.*python3\.(8|9|10)' || echo "No version-specific imports found."

# Check requirements files for version-specific dependencies
echo "Checking requirements files for version-specific dependencies:"
rg --glob 'requirements*.txt' '^python_version' || echo "No version-specific dependencies found in requirements files."

# Check for sys.version_info checks in Python files
echo "Checking for sys.version_info checks:"
rg --glob '*.py' 'sys\.version_info' || echo "No sys.version_info checks found."

Length of output: 1278

displayName: 'Use Python $(python.version)'
- script: python build_utils/cut_changelog.py changelog_cut.md
displayName: "Cut changelog"
Expand Down
5 changes: 3 additions & 2 deletions package/PartSeg/_launcher/check_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ def run(self):
return
try:
if os.path.exists(os.path.join(state_store.save_folder, IGNORE_FILE)):
with open(os.path.join(state_store.save_folder, IGNORE_FILE), encoding="utf-8") as f_p, suppress(
ValueError
with (
open(os.path.join(state_store.save_folder, IGNORE_FILE), encoding="utf-8") as f_p,
suppress(ValueError),
):
old_date = date.fromisoformat(f_p.read())
if (date.today() - old_date).days < IGNORE_DAYS:
Expand Down
4 changes: 2 additions & 2 deletions package/PartSeg/_launcher/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
import warnings
from functools import partial
from typing import TYPE_CHECKING, Type
from typing import TYPE_CHECKING

from qtpy.QtCore import QSize, Qt, QThread, Signal
from qtpy.QtGui import QIcon
Expand Down Expand Up @@ -34,7 +34,7 @@ def run(self):

plugins.register()
main_window_module = importlib.import_module(self.module)
main_window: Type[BaseMainWindow] = main_window_module.MainWindow
main_window: type[BaseMainWindow] = main_window_module.MainWindow
settings: BaseSettings = main_window.get_setting_class()(main_window_module.CONFIG_FOLDER)
self.errors = settings.load()
reader = TiffImageReader()
Expand Down
8 changes: 4 additions & 4 deletions package/PartSeg/_roi_analysis/advanced_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import os
from contextlib import suppress
from copy import deepcopy
from typing import List, Optional, Tuple, Type, Union, cast
from typing import Optional, Union, cast

from qtpy.QtCore import QEvent, Qt, Slot
from qtpy.QtGui import QIcon
Expand Down Expand Up @@ -49,7 +49,7 @@
from PartSegCore.universal_const import UNIT_SCALE, Units
from PartSegData import icons_dir

_DialogType = Union[Type[str], Type[int], Type[float]]
_DialogType = Union[type[str], type[int], type[float]]


def h_line():
Expand Down Expand Up @@ -366,7 +366,7 @@ class MeasurementSettings(QWidget):
def __init__(self, settings: PartSettings, parent=None): # noqa: PLR0915
super().__init__(parent)
self.chosen_element: Optional[MeasurementListWidgetItem] = None
self.chosen_element_area: Optional[Tuple[AreaType, float]] = None
self.chosen_element_area: Optional[tuple[AreaType, float]] = None
self.settings = settings
self.profile_list = QListWidget(self)
self.profile_description = QTextEdit(self)
Expand Down Expand Up @@ -841,7 +841,7 @@ def __init__(
self,
text: str,
help_text: str = "",
objects_list: Optional[List[Union[Tuple[str, _DialogType], Tuple[str, _DialogType, str]]]] = None,
objects_list: Optional[list[Union[tuple[str, _DialogType], tuple[str, _DialogType, str]]]] = None,
parent: Optional[QWidget] = None,
):
if objects_list is None: # pragma: no cover
Expand Down
4 changes: 2 additions & 2 deletions package/PartSeg/_roi_analysis/batch_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def get_name(cls) -> str:
return "Excel (*.xlsx)"

@classmethod
def get_fields(cls) -> typing.List[typing.Union[AlgorithmProperty, str]]:
def get_fields(cls) -> list[typing.Union[AlgorithmProperty, str]]:
return []


Expand Down Expand Up @@ -376,7 +376,7 @@ class CalculationPrepare(QDialog):

def __init__(
self,
file_list: typing.List[os.PathLike],
file_list: list[os.PathLike],
calculation_plan: CalculationPlan,
measurement_file_path: os.PathLike,
settings: PartSettings,
Expand Down
2 changes: 1 addition & 1 deletion package/PartSeg/_roi_analysis/export_batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ def _excel_path_changed(self):

def _extract_information_from_excel_to_export(
excel_path: typing.Union[str, Path], base_folder: typing.Union[str, Path]
) -> typing.List[typing.Tuple[str, bool]]:
) -> list[tuple[str, bool]]:
"""Extract information from Excel file to export"""
file_list = []
file_set = set()
Expand Down
3 changes: 1 addition & 2 deletions package/PartSeg/_roi_analysis/main_window.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import os
from contextlib import suppress
from typing import Type

from qtpy.QtCore import QByteArray, Qt
from qtpy.QtGui import QIcon, QKeyEvent, QKeySequence, QResizeEvent
Expand Down Expand Up @@ -534,7 +533,7 @@ class MainWindow(BaseMainWindow):
settings: PartSettings

@classmethod
def get_setting_class(cls) -> Type[PartSettings]:
def get_setting_class(cls) -> type[PartSettings]:
return PartSettings

initial_image_path = PartSegData.segmentation_analysis_default_image
Expand Down
9 changes: 4 additions & 5 deletions package/PartSeg/_roi_analysis/measurement_widget.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import locale
import os
from enum import Enum
from typing import List, Tuple

from qtpy.QtCore import Qt
from qtpy.QtGui import QKeyEvent, QResizeEvent
Expand Down Expand Up @@ -56,7 +55,7 @@ def clear(self):
self.header = []
self.max_rows = 0
self.content = []
self.measurements: List[MeasurementResult] = []
self.measurements: list[MeasurementResult] = []

def get_size(self, save_orientation: bool):
if save_orientation:
Expand Down Expand Up @@ -116,13 +115,13 @@ def get_val_as_str(self, x: int, y: int, save_orientation: bool) -> str:
val = sublist[y]
return locale.str(val) if isinstance(val, float) else str(val)

def get_header(self, save_orientation: bool) -> List[str]:
def get_header(self, save_orientation: bool) -> list[str]:
if save_orientation:
return [str(i) for i in range(self.max_rows)]

return self.header

def get_rows(self, save_orientation: bool) -> List[str]:
def get_rows(self, save_orientation: bool) -> list[str]:
return self.get_header(not save_orientation)


Expand Down Expand Up @@ -315,7 +314,7 @@ def update_measurement_list(self):
self.measurement_type.blockSignals(False)

@staticmethod
def _move_widgets(widgets_list: List[Tuple[QWidget, int]], layout1: QBoxLayout, layout2: QBoxLayout):
def _move_widgets(widgets_list: list[tuple[QWidget, int]], layout1: QBoxLayout, layout2: QBoxLayout):
for el in widgets_list:
layout1.removeWidget(el[0])
layout2.addWidget(el[0], el[1])
Expand Down
16 changes: 8 additions & 8 deletions package/PartSeg/_roi_analysis/partseg_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class PartSettings(BaseSettings):
json_encoder_class = PartSegEncoder
load_metadata = staticmethod(load_metadata)
last_executed_algorithm: str
save_locations_keys: typing.ClassVar[typing.List[str]] = [
save_locations_keys: typing.ClassVar[list[str]] = [
"open_directory",
"save_directory",
"export_directory",
Expand Down Expand Up @@ -132,7 +132,7 @@ def set_project_info(self, data: typing.Union[ProjectTuple, MaskInfo, PointsInfo
)
self.algorithm_changed.emit()

def get_save_list(self) -> typing.List[SaveSettingsDescription]:
def get_save_list(self) -> list[SaveSettingsDescription]:
return [
*super().get_save_list(),
SaveSettingsDescription("segmentation_pipeline_save.json", self._segmentation_pipelines_dict),
Expand All @@ -142,27 +142,27 @@ def get_save_list(self) -> typing.List[SaveSettingsDescription]:
]

@property
def segmentation_pipelines(self) -> typing.Dict[str, SegmentationPipeline]:
def segmentation_pipelines(self) -> dict[str, SegmentationPipeline]:
warnings.warn("segmentation_pipelines is deprecated, use roi_pipelines", DeprecationWarning, stacklevel=2)
return self.roi_pipelines

@property
def roi_pipelines(self) -> typing.Dict[str, SegmentationPipeline]:
def roi_pipelines(self) -> dict[str, SegmentationPipeline]:
return self._segmentation_pipelines_dict.get(self._current_roi_dict, EventedDict())

@property
def segmentation_profiles(self) -> typing.Dict[str, ROIExtractionProfile]:
def segmentation_profiles(self) -> dict[str, ROIExtractionProfile]:
warnings.warn("segmentation_profiles is deprecated, use roi_profiles", DeprecationWarning, stacklevel=2)
return self.roi_profiles

@property
def roi_profiles(self) -> typing.Dict[str, ROIExtractionProfile]:
def roi_profiles(self) -> dict[str, ROIExtractionProfile]:
return self._segmentation_profiles_dict.get(self._current_roi_dict, EventedDict())

@property
def batch_plans(self) -> typing.Dict[str, CalculationPlan]:
def batch_plans(self) -> dict[str, CalculationPlan]:
return self._batch_plans_dict.get(self._current_roi_dict, EventedDict())

@property
def measurement_profiles(self) -> typing.Dict[str, MeasurementProfile]:
def measurement_profiles(self) -> dict[str, MeasurementProfile]:
return self._measurement_profiles_dict.get(self._current_roi_dict, EventedDict())
12 changes: 5 additions & 7 deletions package/PartSeg/_roi_analysis/prepare_plan_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,17 +294,15 @@ def enable_protect(self):
self.protect = previous

@classmethod
def refresh_profiles(
cls, list_widget: typing.Union[QListWidget, SearchableListWidget], new_values: typing.List[str]
):
def refresh_profiles(cls, list_widget: typing.Union[QListWidget, SearchableListWidget], new_values: list[str]):
index = cls.get_index(list_widget.currentItem(), new_values)
list_widget.clear()
list_widget.addItems(new_values)
if index != -1:
list_widget.setCurrentRow(index)

@staticmethod
def get_index(item: QListWidgetItem, new_values: typing.List[str]) -> int:
def get_index(item: QListWidgetItem, new_values: list[str]) -> int:
if item is None:
return -1
text = item.text()
Expand All @@ -319,7 +317,7 @@ class OtherOperations(ProtectedGroupBox):

def __init__(self, parent=None):
super().__init__("Other operations:", parent)
self.save_translate_dict: typing.Dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()}
self.save_translate_dict: dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()}
self.save_constructor = None

self.change_root = QEnumComboBox(self, enum_class=RootType)
Expand Down Expand Up @@ -642,7 +640,7 @@ def __init__(self, settings: PartSettings, parent: typing.Optional[QWidget] = No

self.add_mask_btn.setDisabled(True)

def update_mask_set(self, mask_set: typing.Set[str]):
def update_mask_set(self, mask_set: set[str]):
self.mask_set = mask_set

def set_replace(self, replace: bool):
Expand Down Expand Up @@ -692,7 +690,7 @@ class CreatePlan(QWidget):
def __init__(self, settings: PartSettings):
super().__init__()
self.settings = settings
self.save_translate_dict: typing.Dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()}
self.save_translate_dict: dict[str, SaveBase] = {x.get_short_name(): x for x in save_dict.values()}
self._mask_set = set()
self.plan = PlanPreview(self)
self.save_plan_btn = QPushButton("Save")
Expand Down
8 changes: 4 additions & 4 deletions package/PartSeg/_roi_analysis/profile_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,10 @@ def get_checked(self):
class ImportDialog(QDialog):
def __init__(
self,
import_dict: typing.Dict[str, typing.Any],
local_dict: typing.Dict[str, typing.Any],
viewer: typing.Type[ObjectPreviewProtocol],
expected_type: typing.Optional[typing.Type] = None,
import_dict: dict[str, typing.Any],
local_dict: dict[str, typing.Any],
viewer: type[ObjectPreviewProtocol],
expected_type: typing.Optional[type] = None,
parent: typing.Optional[QWidget] = None,
):
"""
Expand Down
6 changes: 3 additions & 3 deletions package/PartSeg/_roi_mask/batch_proceed.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from functools import partial
from pathlib import Path
from queue import Queue
from typing import List, NamedTuple, Optional, Tuple, Union, cast
from typing import NamedTuple, Optional, Union, cast

from pydantic import BaseModel
from qtpy.QtCore import QThread, Signal
Expand All @@ -19,7 +19,7 @@
class BatchTask(NamedTuple):
data: Union[str, MaskProjectTuple]
parameters: ROIExtractionProfile
save_prefix: Optional[Tuple[Union[str, Path], Union[dict, BaseModel]]]
save_prefix: Optional[tuple[Union[str, Path], Union[dict, BaseModel]]]


class BatchProceed(QThread):
Expand All @@ -40,7 +40,7 @@ def __init__(self):
self.result_dir = ""
self.save_parameters = {}

def add_task(self, task: Union[BatchTask, List[BatchTask]]):
def add_task(self, task: Union[BatchTask, list[BatchTask]]):
if isinstance(task, list):
for el in task:
self.queue.put(el)
Expand Down
3 changes: 1 addition & 2 deletions package/PartSeg/_roi_mask/main_window.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import os
from contextlib import suppress
from functools import partial
from typing import Type

import numpy as np
from qtpy.QtCore import QByteArray, Qt, Signal, Slot
Expand Down Expand Up @@ -888,7 +887,7 @@ class MainWindow(BaseMainWindow):
settings: StackSettings

@classmethod
def get_setting_class(cls) -> Type[StackSettings]:
def get_setting_class(cls) -> type[StackSettings]:
return StackSettings

initial_image_path = PartSegData.segmentation_mask_default_image
Expand Down
4 changes: 2 additions & 2 deletions package/PartSeg/_roi_mask/segmentation_info_dialog.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Callable, Dict, Optional
from typing import Callable, Optional

from qtpy.QtCore import QEvent
from qtpy.QtWidgets import QGridLayout, QLabel, QListWidget, QPlainTextEdit, QPushButton, QWidget
Expand Down Expand Up @@ -45,7 +45,7 @@ def __init__(self, settings: StackSettings, set_parameters: Callable[[str, dict]
self.setLayout(layout)
self.setWindowTitle("Parameters preview")

def set_parameters_dict(self, val: Optional[Dict[int, ROIExtractionProfile]]):
def set_parameters_dict(self, val: Optional[dict[int, ROIExtractionProfile]]):
self.parameters_dict = val

def set_additional_text(self, text):
Expand Down
Loading
Loading