Skip to content

Commit 2bacb8f

Browse files
committed
refactor(python): drop Python 3.9 support, require Python 3.10+
BREAKING CHANGE: Python 3.9 is EOL and no longer supported. - Update requires-python to >=3.10 in pyproject.toml - Remove Python 3.9 classifier and 3.9-only matplotlib dependency - Set ruff target-version to py310 - Update CI matrix minimum version from 3.9 to 3.10 (pytest, pylint, pyright workflows) - Modernize 512 typing annotations: Optional[X]→X|None, Union[X,Y]→X|Y - Remove redundant Optional/Union imports from typing module - Update test asserting UnionType origin to use types.UnionType instead of typing.Unionfix tests
1 parent 8db61e6 commit 2bacb8f

77 files changed

Lines changed: 494 additions & 512 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/pylint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
runs-on: ubuntu-latest
2424
strategy:
2525
matrix:
26-
python-version: ["3.9", "3.14"]
26+
python-version: ["3.10", "3.14"]
2727

2828
steps:
2929
- name: Harden the runner (Audit all outbound calls)

.github/workflows/pyright.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
runs-on: ubuntu-latest
2424
strategy:
2525
matrix:
26-
python-version: ["3.9", "3.14"]
26+
python-version: ["3.10", "3.14"]
2727

2828
steps:
2929
- name: Harden the runner (Audit all outbound calls)

.github/workflows/pytest.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
python-version: ["3.14"] # to make sure the latest AMC supported python version does not break
3232
include:
3333
- os: ubuntu-latest
34-
python-version: "3.9" # to make sure the minimum AMC supported python version does not break
34+
python-version: "3.10" # to make sure the minimum AMC supported python version does not break
3535
# this is also the version used to report test coverage, other versions do not report coverage
3636

3737
steps:
@@ -269,7 +269,7 @@ jobs:
269269
with:
270270
github-token: ${{ secrets.GITHUB_TOKEN }}
271271
parallel-finished: true
272-
carryforward: "run-ubuntu-latest-py3.9,run-ubuntu-latest-py3.14,run-windows-latest-py3.14,run-macos-latest-py3.14"
272+
carryforward: "run-ubuntu-latest-py3.10,run-ubuntu-latest-py3.14,run-windows-latest-py3.14,run-macos-latest-py3.14"
273273

274274
# TODO: create a badge that presents the result of the Upload coverage xml report step
275275

@@ -345,14 +345,14 @@ jobs:
345345
if: needs.pytest.result != 'skipped'
346346
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
347347
with:
348-
name: coverage-ubuntu-latest-3.9
348+
name: coverage-ubuntu-latest-3.10
349349

350350
# https://docs.astral.sh/uv/guides/integration/github/
351351
- name: Install uv and set the Python version
352352
if: needs.pytest.result != 'skipped'
353353
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
354354
with:
355-
python-version: '3.9' # Match with the coverage report Python version
355+
python-version: '3.10' # Match with the coverage report Python version
356356
activate-environment: true
357357
enable-cache: true
358358
cache-dependency-glob: "pyproject.toml"
@@ -389,7 +389,7 @@ jobs:
389389
- name: Download coverage xml report
390390
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
391391
with:
392-
name: coverage-ubuntu-latest-3.9-xml
392+
name: coverage-ubuntu-latest-3.10-xml
393393

394394
- name: Get Cover
395395
uses: orgoro/coverage@71cf993a407154ad9d8dd027c88a374b0ed002a9 # v3.3

ardupilot_methodic_configurator/__main__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
from logging import warning as logging_warning
3030
from pathlib import Path
3131
from sys import exit as sys_exit
32-
from typing import Union
3332

3433
import argcomplete
3534

@@ -117,7 +116,7 @@ def __init__(self, args: argparse.Namespace) -> None:
117116
self.vehicle_type: str = ""
118117
self.param_default_values: ParDict = ParDict()
119118
self.local_filesystem: LocalFilesystem = None # type: ignore[assignment]
120-
self.vehicle_project_manager: Union[VehicleProjectManager, None] = None
119+
self.vehicle_project_manager: VehicleProjectManager | None = None
121120
self.param_default_values_dirty: bool = False
122121

123122

@@ -391,7 +390,7 @@ def initialize_flight_controller_and_filesystem(state: ApplicationState) -> None
391390
initialize_filesystem(state)
392391

393392

394-
def vehicle_directory_selection(state: ApplicationState) -> Union[VehicleProjectOpenerWindow, None]:
393+
def vehicle_directory_selection(state: ApplicationState) -> VehicleProjectOpenerWindow | None:
395394
"""
396395
Handle vehicle directory selection if no parameter files are found in the current working directory.
397396
@@ -434,7 +433,7 @@ def create_and_configure_component_editor(
434433
local_filesystem: LocalFilesystem,
435434
flight_controller: FlightController,
436435
vehicle_type: str,
437-
vehicle_project_manager: Union[None, VehicleProjectManager],
436+
vehicle_project_manager: None | VehicleProjectManager,
438437
) -> ComponentEditorWindow:
439438
"""
440439
Create and configure the component editor window.

ardupilot_methodic_configurator/annotate_params.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import re
3131
from os import path as os_path
3232
from sys import exit as sys_exit
33-
from typing import Any, Optional
33+
from typing import Any
3434
from xml.etree import ElementTree as ET # no parsing, just data-structure manipulation
3535

3636
import argcomplete
@@ -139,7 +139,7 @@ def check_max_line_length(value: int) -> int:
139139

140140

141141
def get_xml_data(
142-
base_url: str, directory: str, filename: str, vehicle_type: str, fallback_xml_url: Optional[str] = None
142+
base_url: str, directory: str, filename: str, vehicle_type: str, fallback_xml_url: str | None = None
143143
) -> ET.Element:
144144
"""
145145
Fetch XML data from a local file or a URL.
@@ -418,7 +418,7 @@ def update_parameter_documentation(
418418
doc: dict[str, Any],
419419
target: str = ".",
420420
sort_type: str = "none",
421-
param_default_dict: Optional[ParDict] = None,
421+
param_default_dict: ParDict | None = None,
422422
delete_documentation_annotations: bool = False,
423423
) -> None:
424424
"""
@@ -595,7 +595,7 @@ def get_fallback_xml_url(vehicle_type: str, firmware_version: str) -> str:
595595

596596

597597
def parse_parameter_metadata( # pylint: disable=too-many-arguments, too-many-positional-arguments
598-
xml_url: str, xml_dir: str, xml_file: str, vehicle_type: str, max_line_length: int, fallback_xml_url: Optional[str] = None
598+
xml_url: str, xml_dir: str, xml_file: str, vehicle_type: str, max_line_length: int, fallback_xml_url: str | None = None
599599
) -> dict[str, Any]:
600600
xml_root = get_xml_data(xml_url, xml_dir, xml_file, vehicle_type, fallback_xml_url)
601601
return create_doc_dict(xml_root, vehicle_type, max_line_length)

ardupilot_methodic_configurator/argparse_check_range.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from argparse import Action, ArgumentError, ArgumentParser, Namespace
1414
from collections.abc import Sequence
1515
from operator import ge, gt, le, lt
16-
from typing import Any, Union
16+
from typing import Any
1717

1818
from ardupilot_methodic_configurator import _
1919

@@ -56,8 +56,8 @@ def __call__(
5656
self,
5757
parser: ArgumentParser, # noqa: ARG002
5858
namespace: Namespace,
59-
values: Union[str, Sequence[Any], None],
60-
option_string: Union[None, str] = None, # noqa: ARG002
59+
values: str | Sequence[Any] | None,
60+
option_string: None | str = None, # noqa: ARG002
6161
) -> None:
6262
if not isinstance(values, (int, float)):
6363
raise ArgumentError(self, _("Value must be a number."))

ardupilot_methodic_configurator/backend_filesystem.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from shutil import copytree as shutil_copytree
3131
from shutil import rmtree as shutil_rmtree
3232
from subprocess import SubprocessError, run
33-
from typing import Any, Optional, Union
33+
from typing import Any
3434
from zipfile import ZipFile
3535

3636
from argcomplete.completers import DirectoriesCompleter
@@ -201,7 +201,7 @@ def _format_columns_sorted_numerically( # pylint: disable=too-many-locals
201201
return []
202202

203203
# Sort values numerically by key
204-
def sort_key(item: tuple[str, Any]) -> tuple[int, Union[int, float, str]]:
204+
def sort_key(item: tuple[str, Any]) -> tuple[int, int | float | str]:
205205
key = item[0]
206206
try:
207207
return (0, int(key)) # sort integers and floats together
@@ -358,7 +358,7 @@ def read_params_from_files(self) -> dict[str, ParDict]:
358358
logging_error(_("Error: %s is not a directory."), self.vehicle_dir)
359359
return parameters
360360

361-
def compound_params(self, last_filename: Optional[str] = None, skip_default: bool = True) -> tuple[ParDict, Optional[str]]:
361+
def compound_params(self, last_filename: str | None = None, skip_default: bool = True) -> tuple[ParDict, str | None]:
362362
"""
363363
Compound parameters from multiple .param files into a single ParDict.
364364
@@ -399,7 +399,7 @@ def compound_params(self, last_filename: Optional[str] = None, skip_default: boo
399399
return compound, first_config_step_filename
400400

401401
@staticmethod
402-
def str_to_bool(s: str) -> Optional[bool]:
402+
def str_to_bool(s: str) -> bool | None:
403403
"""
404404
Converts a string representation of a boolean value to a boolean.
405405
@@ -606,7 +606,7 @@ def copy_template_files_to_new_vehicle_dir( # pylint: disable=too-many-argument
606606
blank_change_reason: bool,
607607
copy_vehicle_image: bool,
608608
use_fc_params: bool = False,
609-
fc_parameters: Optional[dict[str, float]] = None,
609+
fc_parameters: dict[str, float] | None = None,
610610
) -> str:
611611
# Copy the template files to the new vehicle directory
612612
try:
@@ -662,7 +662,7 @@ def _transform_param_dict(
662662
params: ParDict,
663663
blank_change_reason: bool,
664664
use_fc_params: bool,
665-
fc_parameters: Optional[dict[str, float]],
665+
fc_parameters: dict[str, float] | None,
666666
) -> None:
667667
"""
668668
Apply in-place transformations to a parameter dict during template copy.
@@ -845,7 +845,7 @@ def get_eval_variables(self) -> dict[str, dict[str, Any]]:
845845
def calculate_derived_and_forced_param_changes(
846846
self,
847847
fc_param_names: list[str],
848-
fc_parameters: Optional[dict[str, float]] = None,
848+
fc_parameters: dict[str, float] | None = None,
849849
) -> dict[str, ParDict]:
850850
"""
851851
Compute updated parameter values for all configuration files.
@@ -966,8 +966,8 @@ def merge_forced_or_derived_parameters(
966966
self,
967967
filename: str,
968968
new_parameters: dict[str, ParDict],
969-
fc_param_names: Optional[list[str]],
970-
target: Optional[ParDict] = None,
969+
fc_param_names: list[str] | None,
970+
target: ParDict | None = None,
971971
) -> bool:
972972
"""
973973
Merge forced or derived parameter values into a target parameter dict.

ardupilot_methodic_configurator/backend_filesystem_configuration_steps.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from math import isfinite
1717
from os import path as os_path
1818
from re import search as re_search
19-
from typing import Any, Optional, TypedDict, Union
19+
from typing import Any, TypedDict
2020

2121
# from sys import exit as sys_exit
2222
from jsonschema import validate as json_validate
@@ -290,7 +290,7 @@ def _ensure_file_entry(destination: dict[str, ParDict], filename: str) -> None:
290290

291291
def _eval_new_value( # pylint: disable=too-many-arguments, too-many-positional-arguments
292292
self,
293-
new_value_expr: Union[str, float, bool],
293+
new_value_expr: str | float | bool,
294294
filename: str,
295295
parameter: str,
296296
parameter_type: str,
@@ -618,7 +618,7 @@ def get_sorted_phases_with_end_and_weight(self, total_files: int) -> dict[str, P
618618

619619
return sorted_phases
620620

621-
def get_plugin(self, selected_file: str) -> Optional[dict]:
621+
def get_plugin(self, selected_file: str) -> dict | None:
622622
"""
623623
Get the plugin configuration for the selected file.
624624
@@ -633,7 +633,7 @@ def get_plugin(self, selected_file: str) -> Optional[dict]:
633633
return self.configuration_steps[selected_file].get("plugin")
634634
return None
635635

636-
def get_instructions_popup(self, selected_file: str) -> Optional[dict]:
636+
def get_instructions_popup(self, selected_file: str) -> dict | None:
637637
"""Get the instructions popup configuration for the selected file."""
638638
if selected_file in self.configuration_steps:
639639
return self.configuration_steps[selected_file].get("instructions_popup")

ardupilot_methodic_configurator/backend_filesystem_freedesktop.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
from os import path as os_path
2525
from shutil import which as shutil_which
2626
from sys import platform as sys_platform
27-
from typing import Optional, Union
2827

2928
from ardupilot_methodic_configurator.backend_filesystem_program_settings import ProgramSettings
3029

@@ -57,7 +56,7 @@ def _desktop_icon_exists(desktop_file_path: str) -> bool:
5756
return os_path.exists(desktop_file_path)
5857

5958
@staticmethod
60-
def _get_virtual_env_path() -> Optional[str]:
59+
def _get_virtual_env_path() -> str | None:
6160
"""Get the virtual environment path from environment variables."""
6261
return os_environ.get("VIRTUAL_ENV")
6362

@@ -157,7 +156,7 @@ def create_desktop_icon_if_needed() -> None:
157156
logging_error("Failed to create application launch desktop icon")
158157

159158
@staticmethod
160-
def _get_desktop_startup_id() -> Union[str, None]:
159+
def _get_desktop_startup_id() -> str | None:
161160
"""
162161
Get the DESKTOP_STARTUP_ID environment variable.
163162

ardupilot_methodic_configurator/backend_filesystem_json_with_schema.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from logging import debug as logging_debug
1818
from logging import error as logging_error
1919
from os import path as os_path
20-
from typing import Any, Union
20+
from typing import Any
2121

2222
from jsonschema import ValidationError, validate, validators
2323

@@ -31,8 +31,8 @@ class FilesystemJSONWithSchema:
3131
def __init__(self, json_filename: str, schema_filename: str) -> None:
3232
self.json_filename = json_filename
3333
self.schema_filename = schema_filename
34-
self.data: Union[None, dict[str, Any]] = None
35-
self.schema: Union[None, dict[Any, Any]] = None
34+
self.data: None | dict[str, Any] = None
35+
self.schema: None | dict[Any, Any] = None
3636

3737
def load_schema(self) -> dict:
3838
"""

0 commit comments

Comments
 (0)