Skip to content

Commit 627f5e0

Browse files
committed
Drop python 3.9 support
1 parent 2d2fca4 commit 627f5e0

23 files changed

Lines changed: 80 additions & 92 deletions

.github/workflows/run.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ jobs:
6161
strategy:
6262
matrix:
6363
platform: [ubuntu-latest, macos-latest, windows-latest]
64-
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13', '3.14']
64+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
6565
runs-on: ${{ matrix.platform }}
6666
permissions:
6767
contents: read

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
- run: coverage run --source=dfetch --append -m behave features # Run features tests
5050
- run: coverage xml -o coverage.xml # Create XML report
5151
- run: pyroma --directory --min=10 . # Check pyproject
52-
- run: find dfetch -name "*.py" | xargs pyupgrade --py39-plus # Check syntax
52+
- run: find dfetch -name "*.py" | xargs pyupgrade --py310-plus # Check syntax
5353

5454
- name: Run codacy-coverage-reporter
5555
uses: codacy/codacy-coverage-reporter-action@a38818475bb21847788496e9f0fddaa4e84955ba # master

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ repos:
124124
entry: pyupgrade
125125
language: python
126126
files: ^dfetch/
127-
args: [--py39-plus]
127+
args: [--py310-plus]
128128
types: [file, python]
129129

130130
- repo: https://github.com/gitleaks/gitleaks

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Release 0.12.0 (unreleased)
1616
* Group logging under a project name header (#953)
1717
* Introduce new ``update-patch`` command (#614)
1818
* Introduce new ``format-patch`` command (#943)
19+
* Drop python 3.9 support (#0)
1920

2021
Release 0.11.0 (released 2026-01-03)
2122
====================================

dfetch/__main__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import argparse
77
import sys
88
from collections.abc import Sequence
9-
from typing import Optional
109

1110
from rich.console import Console
1211

@@ -65,7 +64,7 @@ def _help(_: argparse.Namespace) -> None:
6564
parser.print_help()
6665

6766

68-
def run(argv: Sequence[str], console: Optional[Console] = None) -> None:
67+
def run(argv: Sequence[str], console: Console | None = None) -> None:
6968
"""Start dfetch."""
7069
args = create_parser().parse_args(argv)
7170

dfetch/commands/update_patch.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232

3333
import argparse
3434
import pathlib
35-
from typing import Optional
3635

3736
import dfetch.commands.command
3837
import dfetch.manifest.project
@@ -154,7 +153,7 @@ def _update_patch(
154153
root: pathlib.Path,
155154
project_name: str,
156155
patch_text: str,
157-
) -> Optional[pathlib.Path]:
156+
) -> pathlib.Path | None:
158157
"""Update the specified patch file with new patch text."""
159158
patch_path = pathlib.Path(patch_to_update).resolve()
160159

dfetch/log.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import os
55
import sys
66
from contextlib import nullcontext
7-
from typing import Any, Optional, Union, cast
7+
from typing import Any, cast
88

99
from rich.console import Console
1010
from rich.highlighter import NullHighlighter
@@ -23,7 +23,7 @@ def make_console(no_color: bool = False) -> Console:
2323
)
2424

2525

26-
def configure_root_logger(console: Optional[Console] = None) -> None:
26+
def configure_root_logger(console: Console | None = None) -> None:
2727
"""Configure the root logger with RichHandler using the provided Console."""
2828
console = console or make_console()
2929

@@ -94,10 +94,10 @@ def error(self, msg: object, *args: Any, **kwargs: Any) -> None:
9494

9595
def status(
9696
self, name: str, message: str, spinner: str = "dots", enabled: bool = True
97-
) -> Union[Status, nullcontext[None]]:
97+
) -> Status | nullcontext[None]:
9898
"""Show status message with spinner if enabled."""
9999
rich_console = None
100-
logger: Optional[logging.Logger] = self
100+
logger: logging.Logger | None = self
101101
while logger:
102102
for handler in getattr(logger, "handlers", []):
103103
if isinstance(handler, RichHandler):
@@ -143,7 +143,7 @@ def filter(self, record: logging.LogRecord) -> bool:
143143
return True
144144

145145

146-
def setup_root(name: str, console: Optional[Console] = None) -> DLogger:
146+
def setup_root(name: str, console: Console | None = None) -> DLogger:
147147
"""Create and return the root logger."""
148148
logging.setLoggerClass(DLogger)
149149
configure_root_logger(console)
@@ -173,7 +173,7 @@ def increase_verbosity() -> None:
173173
logger_.setLevel(new_level)
174174

175175

176-
def get_logger(name: str, console: Optional[Console] = None) -> DLogger:
176+
def get_logger(name: str, console: Console | None = None) -> DLogger:
177177
"""Get logger for a module, optionally configuring console colors."""
178178
logging.setLoggerClass(DLogger)
179179
logger = logging.getLogger(name)

dfetch/manifest/manifest.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import re
2525
from collections.abc import Sequence
2626
from dataclasses import dataclass
27-
from typing import IO, Any, Optional, Union
27+
from typing import IO, Any
2828

2929
import yaml
3030
from typing_extensions import NotRequired, TypedDict
@@ -95,11 +95,9 @@ def _guess_project(self, names: Sequence[str]) -> Sequence[str]:
9595
class ManifestDict(TypedDict, total=True): # pylint: disable=too-many-ancestors
9696
"""Serialized dict types."""
9797

98-
version: Union[int, str]
99-
remotes: NotRequired[Sequence[Union[RemoteDict, Remote]]]
100-
projects: Sequence[
101-
Union[ProjectEntryDict, ProjectEntry, dict[str, Union[str, list[str]]]]
102-
]
98+
version: int | str
99+
remotes: NotRequired[Sequence[RemoteDict | Remote]]
100+
projects: Sequence[ProjectEntryDict | ProjectEntry | dict[str, str | list[str]]]
103101

104102

105103
class Manifest:
@@ -113,8 +111,8 @@ class Manifest:
113111
def __init__(
114112
self,
115113
manifest: ManifestDict,
116-
text: Optional[str] = None,
117-
path: Optional[Union[str, os.PathLike[str]]] = None,
114+
text: str | None = None,
115+
path: str | os.PathLike[str] | None = None,
118116
) -> None:
119117
"""Create the manifest."""
120118
self.__version: str = str(manifest.get("version", self.CURRENT_VERSION))
@@ -142,7 +140,7 @@ def __init__(
142140
def _init_projects(
143141
self,
144142
projects: Sequence[
145-
Union[ProjectEntryDict, ProjectEntry, dict[str, Union[str, list[str]]]]
143+
ProjectEntryDict | ProjectEntry | dict[str, str | list[str]]
146144
],
147145
) -> dict[str, ProjectEntry]:
148146
"""Iterate over projects from manifest and initialize ProjectEntries from it.
@@ -189,7 +187,7 @@ def _init_projects(
189187

190188
@staticmethod
191189
def _determine_remotes(
192-
remotes_from_manifest: Sequence[Union[RemoteDict, Remote]],
190+
remotes_from_manifest: Sequence[RemoteDict | Remote],
193191
) -> tuple[dict[str, Remote], list[Remote]]:
194192
default_remotes: list[Remote] = []
195193
remotes: dict[str, Remote] = {}
@@ -208,8 +206,8 @@ def _determine_remotes(
208206

209207
@staticmethod
210208
def from_yaml(
211-
text: Union[io.TextIOWrapper, str, IO[str]],
212-
path: Optional[Union[str, os.PathLike[str]]] = None,
209+
text: io.TextIOWrapper | str | IO[str],
210+
path: str | os.PathLike[str] | None = None,
213211
) -> "Manifest":
214212
"""Create a manifest from a file like object."""
215213
if isinstance(text, (io.TextIOWrapper, IO)):
@@ -228,7 +226,7 @@ def from_yaml(
228226
return Manifest(manifest, text=text, path=path)
229227

230228
@staticmethod
231-
def _load_yaml(text: Union[io.TextIOWrapper, str, IO[str]]) -> Any:
229+
def _load_yaml(text: io.TextIOWrapper | str | IO[str]) -> Any:
232230
try:
233231
return yaml.safe_load(text)
234232
except yaml.YAMLError as exc:
@@ -306,9 +304,9 @@ def _as_dict(self) -> dict[str, ManifestDict]:
306304
if len(remotes) == 1:
307305
remotes[0].pop("default", None)
308306

309-
projects: list[dict[str, Union[str, list[str]]]] = []
307+
projects: list[dict[str, str | list[str]]] = []
310308
for project in self.projects:
311-
project_yaml: dict[str, Union[str, list[str]]] = project.as_yaml()
309+
project_yaml: dict[str, str | list[str]] = project.as_yaml()
312310
if len(remotes) == 1:
313311
project_yaml.pop("remote", None)
314312
projects.append(project_yaml)

dfetch/manifest/parse.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import os
44
import pathlib
5-
from typing import Any, Optional, cast
5+
from typing import Any, cast
66

77
from strictyaml import StrictYAMLError, YAMLValidationError, load
88

@@ -82,7 +82,7 @@ def find_manifest() -> str:
8282
return os.path.realpath(paths[0])
8383

8484

85-
def get_childmanifests(skip: Optional[list[str]] = None) -> list[Manifest]:
85+
def get_childmanifests(skip: list[str] | None = None) -> list[Manifest]:
8686
"""Parse & validate any manifest file in cwd and return a list of all valid manifests."""
8787
skip = skip or []
8888
logger.debug("Looking for sub-manifests")

dfetch/manifest/project.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,6 @@
274274

275275
import copy
276276
from collections.abc import Sequence
277-
from typing import Optional, Union
278277

279278
from typing_extensions import Required, TypedDict
280279

@@ -291,7 +290,7 @@
291290
"src": str,
292291
"dst": str,
293292
"url": str,
294-
"patch": Union[str, list[str]],
293+
"patch": str | list[str],
295294
"repo": str,
296295
"branch": str,
297296
"tag": str,
@@ -315,7 +314,7 @@ def __init__(self, kwargs: ProjectEntryDict) -> None:
315314
self._revision: str = kwargs.get("revision", "")
316315

317316
self._remote: str = kwargs.get("remote", "")
318-
self._remote_obj: Optional[Remote] = None
317+
self._remote_obj: Remote | None = None
319318
self._src: str = kwargs.get("src", "") # noqa
320319
self._dst: str = kwargs.get("dst", self._name)
321320
self._url: str = kwargs.get("url", "")
@@ -332,7 +331,7 @@ def __init__(self, kwargs: ProjectEntryDict) -> None:
332331
@classmethod
333332
def from_yaml(
334333
cls,
335-
yamldata: Union[dict[str, Union[str, list[str]]], ProjectEntryDict],
334+
yamldata: dict[str, str | list[str]] | ProjectEntryDict,
336335
default_remote: str = "",
337336
) -> "ProjectEntry":
338337
"""Create a Project Entry from yaml data.
@@ -461,7 +460,7 @@ def as_recommendation(self) -> "ProjectEntry":
461460
recommendation._repo_path = "" # pylint: disable=protected-access
462461
return recommendation
463462

464-
def as_yaml(self) -> dict[str, Union[str, list[str]]]:
463+
def as_yaml(self) -> dict[str, str | list[str]]:
465464
"""Get this project as yaml dictionary."""
466465
yamldata = {
467466
"name": self._name,

0 commit comments

Comments
 (0)