Skip to content

Commit 7d9fe0d

Browse files
committed
build: Use Python 3.14 in Docker image and drop 3.9 support
1 parent e39bae6 commit 7d9fe0d

18 files changed

Lines changed: 456 additions & 689 deletions

File tree

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
fail-fast: false
1717
matrix:
18-
python-version: ["3.9", "3.13"]
18+
python-version: ["3.10", "3.14"]
1919
runs-on: [ubuntu-latest]
2020
steps:
2121
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
ARG PYTHON_IMAGE=3.13-slim@sha256:1020ca463dc51c26bbad49de85dbb2986d93b71050102f3fa2a7f0fc4c2ea81e
1+
ARG PYTHON_IMAGE=3.14-slim@sha256:4ed33101ee7ec299041cc41dd268dae17031184be94384b1ce7936dc4e5dead3
22

33
# --------------- `base` stage ---------------
44
FROM python:${PYTHON_IMAGE} AS base

dependencies/scripts/download_packages.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import json
22
import logging
3+
from collections.abc import Callable
34
from dataclasses import dataclass
45
from datetime import datetime
56
from pathlib import Path
6-
from typing import Any, Callable, Optional
7+
from typing import Any
78
from zoneinfo import ZoneInfo
89

910
import click
@@ -33,8 +34,8 @@ class ServerError(Exception):
3334
@dataclass(frozen=True)
3435
class Ecosystem:
3536
url: str
36-
params: Optional[dict[str, Any]]
37-
pages: Optional[int]
37+
params: dict[str, Any] | None
38+
pages: int | None
3839
parser: Callable[[dict[str, Any]], list[str]]
3940

4041

@@ -88,7 +89,7 @@ def download(
8889

8990

9091
def get_packages(
91-
base_url: str, parser: Callable[[dict[str, Any]], list[str]], params: Optional[dict[str, Any]] = None
92+
base_url: str, parser: Callable[[dict[str, Any]], list[str]], params: dict[str, Any] | None = None
9293
) -> list[str]:
9394
for attempt in stamina.retry_context(
9495
on=(httpx.TransportError, httpx.TimeoutException, ServerError),

justfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# VARIABLE DEFINITIONS
22
venv := ".venv"
3-
python_version :="3.13"
3+
python_version :="3.14"
44
run := "uv run"
55

66
venv-exists := path_exists(venv)

pyproject.toml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@ maintainers = [
99
{ name = "Daniel Sanz", email = "imsdn4z@gmail.com" },
1010
{ name = "Sergio Castillo", email = "s.cast.lara@gmail.com" },
1111
]
12-
requires-python = "<4,>=3.9"
12+
requires-python = "<4,>=3.10"
1313
dependencies = [
1414
"requests<3.0.0,>=2.32.4",
1515
"rapidfuzz<4.0.0,>=2.13.7",
1616
"pyparsing<4.0.0,>=3.2.3",
1717
"tomlkit<0.14.0,>=0.11.6",
18-
"tomli<3.0.0,>=2.2.1; python_version < \"3.13\"",
1918
"pydantic>=2.11.7,<3.0.0",
2019
"pyyaml>=6.0.2",
2120
]
@@ -70,7 +69,7 @@ requires = ["hatchling"]
7069
build-backend = "hatchling.build"
7170

7271
[tool.ruff]
73-
target-version = "py39"
72+
target-version = "py310"
7473
line-length = 120
7574

7675
src = ["twyn", "tests"]
@@ -115,7 +114,7 @@ ignore = [
115114
convention = "pep257"
116115

117116
[tool.mypy]
118-
python_version = "3.9"
117+
python_version = "3.10"
119118
ignore_missing_imports = true
120119
namespace_packages = true
121120
explicit_package_bases = true

src/twyn/base/constants.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING, Literal
4-
5-
from typing_extensions import TypeAlias
3+
from typing import TYPE_CHECKING, Literal, TypeAlias
64

75
from twyn import dependency_parser
86
from twyn.trusted_packages import selectors

src/twyn/base/exceptions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from typing import IO, Any, Optional
2+
from typing import IO, Any
33

44
logger = logging.getLogger("twyn")
55

@@ -28,7 +28,7 @@ class CliError(click.ClickException):
2828
def __init__(self, message: str = "") -> None:
2929
super().__init__(message)
3030

31-
def show(self, file: Optional[IO[Any]] = None) -> None:
31+
def show(self, file: IO[Any] | None = None) -> None:
3232
"""Display the error message."""
3333
logger.debug(self.format_message(), exc_info=True)
3434
logger.error(self.format_message(), exc_info=False)

src/twyn/cli.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22
import sys
3-
from typing import Optional
43

54
from twyn.__version__ import __version__
65
from twyn.base.constants import (
@@ -129,13 +128,13 @@ def run( # noqa: C901
129128
selector_method: str,
130129
v: bool,
131130
vv: bool,
132-
no_cache: Optional[bool],
131+
no_cache: bool | None,
133132
no_track: bool,
134133
json: bool,
135-
package_ecosystem: Optional[str],
134+
package_ecosystem: str | None,
136135
recursive: bool,
137-
pypi_source: Optional[str],
138-
npm_source: Optional[str],
136+
pypi_source: str | None,
137+
npm_source: str | None,
139138
) -> int:
140139
if vv:
141140
logger.setLevel(logging.DEBUG)

src/twyn/config/config_handler.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from dataclasses import asdict, dataclass, field
33
from enum import Enum
44
from pathlib import Path
5-
from typing import Any, Optional, Union
5+
from typing import Any
66

77
from tomlkit import TOMLDocument, dumps, load, table
88

@@ -38,55 +38,55 @@ class TwynConfiguration:
3838
"""Method for selecting similar packages."""
3939
allowlist: set[str]
4040
"""Set of package names to allow without checking."""
41-
pypi_source: Optional[str]
41+
pypi_source: str | None
4242
"""Alternative PyPI source URL."""
43-
npm_source: Optional[str]
43+
npm_source: str | None
4444
"""Alternative npm source URL."""
4545
use_cache: bool
4646
"""Whether to use cached trusted packages."""
47-
package_ecosystem: Optional[PackageEcosystems]
47+
package_ecosystem: PackageEcosystems | None
4848
"""Target package ecosystem for analysis."""
49-
recursive: Optional[bool]
49+
recursive: bool | None
5050
"""Whether to recursively search for dependency files."""
5151

5252

5353
@dataclass
5454
class ReadTwynConfiguration:
5555
"""Configuration for twyn as set by the user. It may have None values."""
5656

57-
dependency_files: Optional[set[str]] = field(default_factory=set)
57+
dependency_files: set[str] | None = field(default_factory=set)
5858
"""Optional set of dependency file paths to analyze."""
59-
selector_method: Optional[str] = None
59+
selector_method: str | None = None
6060
"""Optional method for selecting similar packages."""
6161
allowlist: set[str] = field(default_factory=set)
6262
"""Set of package names to allow without checking."""
63-
pypi_source: Optional[str] = None
63+
pypi_source: str | None = None
6464
"""Optional alternative PyPI source URL."""
65-
npm_source: Optional[str] = None
65+
npm_source: str | None = None
6666
"""Optional alternative npm source URL."""
67-
use_cache: Optional[bool] = None
67+
use_cache: bool | None = None
6868
"""Optional setting for using cached trusted packages."""
69-
package_ecosystem: Optional[PackageEcosystems] = None
69+
package_ecosystem: PackageEcosystems | None = None
7070
"""Optional target package ecosystem for analysis."""
71-
recursive: Optional[bool] = None
71+
recursive: bool | None = None
7272
"""Optional setting for recursive dependency file search."""
7373

7474

7575
class ConfigHandler:
7676
"""Manage reading and writing configurations for Twyn."""
7777

78-
def __init__(self, file_handler: Optional[FileHandler] = None) -> None:
78+
def __init__(self, file_handler: FileHandler | None = None) -> None:
7979
self.file_handler = file_handler
8080

8181
def resolve_config( # noqa: C901, PLR0912
8282
self,
83-
selector_method: Optional[str] = None,
84-
dependency_files: Optional[set[str]] = None,
85-
use_cache: Optional[bool] = None,
86-
package_ecosystem: Optional[PackageEcosystems] = None,
87-
recursive: Optional[bool] = None,
88-
pypi_source: Optional[str] = None,
89-
npm_source: Optional[str] = None,
83+
selector_method: str | None = None,
84+
dependency_files: set[str] | None = None,
85+
use_cache: bool | None = None,
86+
package_ecosystem: PackageEcosystems | None = None,
87+
recursive: bool | None = None,
88+
pypi_source: str | None = None,
89+
npm_source: str | None = None,
9090
) -> TwynConfiguration:
9191
"""Resolve the configuration for Twyn.
9292
@@ -244,10 +244,10 @@ def get_default_config_file_path() -> str:
244244
return DEFAULT_PROJECT_TOML_FILE
245245

246246

247-
def _serialize_config(x: Any) -> Union[Any, str, list[Any]]:
247+
def _serialize_config(x: Any) -> Any | str | list[Any]:
248248
"""Serialize configuration values for TOML format."""
249249

250-
def _value_to_for_config(v: Any) -> Union[str, list[Any], Any]:
250+
def _value_to_for_config(v: Any) -> str | list[Any] | Any:
251251
if isinstance(v, Enum):
252252
return v.name
253253
elif isinstance(v, set):

src/twyn/dependency_managers/managers/base.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from dataclasses import dataclass
22
from pathlib import Path
3-
from typing import Optional
43

54
from twyn.trusted_packages.references.base import AbstractPackageReference
65

@@ -30,7 +29,7 @@ def matches_ecosystem_name(cls, name: str) -> bool:
3029
return cls.name == Path(name).name.lower()
3130

3231
@classmethod
33-
def get_alternative_source(cls, sources: dict[str, str]) -> Optional[str]:
32+
def get_alternative_source(cls, sources: dict[str, str]) -> str | None:
3433
"""Get alternative source URL for this ecosystem from sources dict."""
3534
match = [x for x in sources if x == cls.name]
3635

0 commit comments

Comments
 (0)