Skip to content

Commit 3e5a983

Browse files
Merge pull request #38 from mam-dev/py313
Support Python 3.13, drop 3.9, various updates
2 parents 400af69 + ff64009 commit 3e5a983

File tree

10 files changed

+57
-50
lines changed

10 files changed

+57
-50
lines changed

.github/workflows/ci.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ jobs:
1010
build:
1111
strategy:
1212
matrix:
13-
python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
13+
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
1414
runs-on: "ubuntu-latest"
1515
steps:
16-
- uses: actions/checkout@v3
16+
- uses: actions/checkout@v4
1717
- name: Set up Python ${{ matrix.python_version }}
18-
uses: actions/setup-python@v4
18+
uses: actions/setup-python@v5
1919
with:
2020
python-version: ${{ matrix.python_version }}
2121
- name: Install dependencies

pyproject.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,19 @@ authors = [
77
]
88
license = {file = "LICENSE"}
99
urls = {repo = "https://github.com/mam-dev/security-constraints"}
10-
requires-python = ">=3.8"
10+
requires-python = ">=3.9"
1111
dependencies = [
1212
"requests",
1313
"pyyaml",
1414
]
1515
dynamic = ["version"]
1616
classifiers = [
1717
"Development Status :: 5 - Production/Stable",
18-
"Programming Language :: Python :: 3.8",
1918
"Programming Language :: Python :: 3.9",
2019
"Programming Language :: Python :: 3.10",
2120
"Programming Language :: Python :: 3.11",
2221
"Programming Language :: Python :: 3.12",
22+
"Programming Language :: Python :: 3.13",
2323
"Topic :: Security",
2424
"License :: OSI Approved :: Apache Software License",
2525
]
@@ -118,6 +118,7 @@ ignore = [
118118
"PTH123",
119119
"TRY003", "TRY301",
120120
"UP032",
121+
"ISC001",
121122
]
122123

123124
[tool.ruff.lint.per-file-ignores]

src/security_constraints/common.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""This module contains common definitions for use in any other module."""
2+
23
from __future__ import annotations
34

45
import abc

src/security_constraints/github_security_advisory.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
"""Module for fetching vulnerabilities from the GitHub Security Advisory."""
2+
23
import logging
34
import os
45
import string
5-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set
6+
from typing import TYPE_CHECKING, Any, Optional
67

78
import requests
89

@@ -19,7 +20,7 @@
1920
from typing import TypedDict
2021

2122
class _GraphQlResponseJson(TypedDict, total=False):
22-
data: Dict[Any, Any]
23+
data: dict[Any, Any]
2324

2425
if sys.version_info >= (3, 10):
2526
from typing import TypeGuard
@@ -87,22 +88,22 @@ def get_database_name(self) -> str:
8788
return "Github Security Advisory"
8889

8990
def get_vulnerabilities(
90-
self, severities: Set[SeverityLevel]
91-
) -> List[SecurityVulnerability]:
91+
self, severities: set[SeverityLevel]
92+
) -> list[SecurityVulnerability]:
9293
"""Fetch all CRITICAL vulnerabilities from GitHub Security Advisory.
9394
9495
The SeverityLevels map trivially to GitHub's SecurityAdvisorySeverity.
9596
9697
"""
9798
after: Optional[str] = None
98-
vulnerabilities: List[SecurityVulnerability] = []
99+
vulnerabilities: list[SecurityVulnerability] = []
99100
more_data_exists = True
100101
while more_data_exists:
101102
json_response: "_GraphQlResponseJson" = self._do_graphql_request(
102103
severities=severities, after=after
103104
)
104105
try:
105-
json_data: Dict[str, Any] = json_response["data"]
106+
json_data: dict[str, Any] = json_response["data"]
106107
vulnerabilities.extend(
107108
[
108109
SecurityVulnerability(
@@ -130,7 +131,7 @@ def get_vulnerabilities(
130131
return vulnerabilities
131132

132133
def _do_graphql_request(
133-
self, severities: Set[SeverityLevel], after: Optional[str] = None
134+
self, severities: set[SeverityLevel], after: Optional[str] = None
134135
) -> "_GraphQlResponseJson":
135136
query = QUERY_TEMPLATE.substitute(
136137
first=100,

src/security_constraints/main.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""Main module."""
2+
23
import argparse
34
import logging
45
import sys
6+
from collections.abc import Sequence
57
from datetime import datetime, timezone
68
from importlib.metadata import version
7-
from typing import IO, List, Optional, Sequence, Set
9+
from typing import IO, Optional
810

911
import yaml
1012

@@ -23,26 +25,26 @@
2325

2426

2527
def get_security_vulnerability_database_apis() -> (
26-
List[SecurityVulnerabilityDatabaseAPI]
28+
list[SecurityVulnerabilityDatabaseAPI]
2729
):
2830
"""Return the APIs to use for fetching vulnerabilities."""
2931
return [GithubSecurityAdvisoryAPI()]
3032

3133

3234
def fetch_vulnerabilities(
33-
apis: Sequence[SecurityVulnerabilityDatabaseAPI], severities: Set[SeverityLevel]
34-
) -> List[SecurityVulnerability]:
35+
apis: Sequence[SecurityVulnerabilityDatabaseAPI], severities: set[SeverityLevel]
36+
) -> list[SecurityVulnerability]:
3537
"""Use apis to fetch and return vulnerabilities."""
36-
vulnerabilities: List[SecurityVulnerability] = []
38+
vulnerabilities: list[SecurityVulnerability] = []
3739
for api in apis:
3840
LOGGER.debug("Fetching vulnerabilities from %s...", api.get_database_name())
3941
vulnerabilities.extend(api.get_vulnerabilities(severities=severities))
4042
return vulnerabilities
4143

4244

4345
def filter_vulnerabilities(
44-
config: Configuration, vulnerabilities: List[SecurityVulnerability]
45-
) -> List[SecurityVulnerability]:
46+
config: Configuration, vulnerabilities: list[SecurityVulnerability]
47+
) -> list[SecurityVulnerability]:
4648
"""Filter out vulnerabilities that should be ignored and return the rest."""
4749
if config.ignore_ids:
4850
LOGGER.debug("Applying ignore-ids...")
@@ -53,8 +55,8 @@ def filter_vulnerabilities(
5355

5456

5557
def sort_vulnerabilities(
56-
vulnerabilities: List[SecurityVulnerability],
57-
) -> List[SecurityVulnerability]:
58+
vulnerabilities: list[SecurityVulnerability],
59+
) -> list[SecurityVulnerability]:
5860
"""Sort vulnerabilities into the order they should appear in the constraints."""
5961
sorted_vulnerabilities = sorted(vulnerabilities, key=lambda v: v.identifier)
6062
sorted_vulnerabilities.sort(key=lambda v: v.package)
@@ -69,7 +71,7 @@ def get_safe_version_constraints(
6971
See SecurityVulnerability documentation for more information.
7072
7173
"""
72-
safe_specs: List[str] = []
74+
safe_specs: list[str] = []
7375
vulnerable_spec: str
7476
if "," in vulnerability.vulnerable_range:
7577
# If there is a known min and max affected version, make the constraints
@@ -124,9 +126,9 @@ def create_header(
124126
"""Create the comment header which goes at the top of the output."""
125127
time_format: str = "%Y-%m-%dT%H:%M:%S.%fZ" # ISO with Z for UTC
126128
timestamp: str = datetime.now(tz=timezone.utc).strftime(time_format)
127-
sources: List[str] = [api.get_database_name() for api in apis]
129+
sources: list[str] = [api.get_database_name() for api in apis]
128130
app_name: str = "security-constraints"
129-
lines: List[str] = [
131+
lines: list[str] = [
130132
f"Generated by {app_name} {version(app_name)} on {timestamp}",
131133
f"Data sources: {', '.join(sources)}",
132134
f"Configuration: {config.to_dict()}",
@@ -259,11 +261,11 @@ def main() -> int:
259261
yaml.safe_dump(config.to_dict(), stream=sys.stdout)
260262
return 0
261263

262-
apis: List[
263-
SecurityVulnerabilityDatabaseAPI
264-
] = get_security_vulnerability_database_apis()
264+
apis: list[SecurityVulnerabilityDatabaseAPI] = (
265+
get_security_vulnerability_database_apis()
266+
)
265267

266-
vulnerabilities: List[SecurityVulnerability] = fetch_vulnerabilities(
268+
vulnerabilities: list[SecurityVulnerability] = fetch_vulnerabilities(
267269
apis, severities=config.min_severity.get_higher_or_equal_severities()
268270
)
269271
vulnerabilities = filter_vulnerabilities(config, vulnerabilities)

test/conftest.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import datetime
2-
from typing import TYPE_CHECKING, Generator
2+
from collections.abc import Generator
3+
from typing import TYPE_CHECKING
34
from unittest.mock import Mock
45

56
import freezegun

test/test_common.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any, List, Set
1+
from typing import Any
22
from unittest.mock import Mock
33

44
import pytest
@@ -50,7 +50,7 @@ def test_severity_level_compare_with_other_type() -> None:
5050
],
5151
)
5252
def test_sort_severity_levels(
53-
severities: List[SeverityLevel], expected: List[SeverityLevel]
53+
severities: list[SeverityLevel], expected: list[SeverityLevel]
5454
) -> None:
5555
assert sorted(severities) == expected
5656

@@ -76,7 +76,7 @@ def test_sort_severity_levels(
7676
],
7777
)
7878
def test_get_higher_or_equal_severities(
79-
severity: SeverityLevel, expected: Set[SeverityLevel]
79+
severity: SeverityLevel, expected: set[SeverityLevel]
8080
) -> None:
8181
assert severity.get_higher_or_equal_severities() == expected
8282

@@ -225,7 +225,7 @@ def test_configuration_from_args() -> None:
225225
],
226226
)
227227
def test_configuration_merge(
228-
configs: List[Configuration], expected: Configuration
228+
configs: list[Configuration], expected: Configuration
229229
) -> None:
230230
assert Configuration.merge(*configs) == expected
231231

@@ -244,7 +244,7 @@ def test_configuration_supported_keys() -> None:
244244
],
245245
)
246246
def test_package_constraints_str(
247-
package: str, specifiers: List[str], expected: str
247+
package: str, specifiers: list[str], expected: str
248248
) -> None:
249249
assert str(PackageConstraints(package=package, specifiers=specifiers)) == expected
250250

test/test_github_security_advisory.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import TYPE_CHECKING, Any, Dict, List, Set
1+
from typing import TYPE_CHECKING, Any
22

33
import pytest
44

@@ -37,7 +37,7 @@ def test_get_database_name(github_token: str) -> None:
3737
def test_get_vulnerabilities(
3838
github_token: str,
3939
requests_mock: "RequestsMock",
40-
severities: Set[SeverityLevel],
40+
severities: set[SeverityLevel],
4141
expected_graphql_severities: str,
4242
) -> None:
4343
cursors = (
@@ -46,8 +46,8 @@ def test_get_vulnerabilities(
4646
"Y3Vyc29yOnYyOpK5MjAyMC0wOS0yNVQxOTo0MjowMCswMXowMM0DeQ==",
4747
"Y3Vyc29yOnYyOpK5MjAyMC0wOS0yNVQxOTo0MjowMCswMHowMM0LeQ==",
4848
)
49-
expected_vulnerabilities: List[SecurityVulnerability] = []
50-
vulnerability_nodes: List[Dict[str, Any]] = []
49+
expected_vulnerabilities: list[SecurityVulnerability] = []
50+
vulnerability_nodes: list[dict[str, Any]] = []
5151
for request_index in range(3):
5252
for i in range(100 if request_index < 2 else 41):
5353
ghsa = f"GHSA-{request_index}-{i}"
@@ -91,8 +91,9 @@ def test_get_vulnerabilities(
9191
"hasNextPage": request_index < 2,
9292
},
9393
"nodes": vulnerability_nodes[
94-
request_index
95-
* 100 : min(request_index * 100 + 100, 241)
94+
request_index * 100 : min(
95+
request_index * 100 + 100, 241
96+
)
9697
],
9798
}
9899
}

test/test_main.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import argparse
22
import logging
33
import sys
4-
from typing import TYPE_CHECKING, List, Set, Type
4+
from typing import TYPE_CHECKING
55
from unittest.mock import Mock, call, create_autospec
66

77
import pytest
@@ -192,7 +192,7 @@ def test_setup_logging(monkeypatch: "MonkeyPatch", debug: bool) -> None:
192192
"severities",
193193
[{SeverityLevel.CRITICAL}, {SeverityLevel.CRITICAL, SeverityLevel.HIGH}],
194194
)
195-
def test_fetch_vulnerabilities(severities: Set[SeverityLevel]) -> None:
195+
def test_fetch_vulnerabilities(severities: set[SeverityLevel]) -> None:
196196
mock_vulnerabilities = [create_autospec(SecurityVulnerability) for _ in range(3)]
197197
mock_apis = [
198198
Mock(
@@ -273,9 +273,9 @@ def test_fetch_vulnerabilities(severities: Set[SeverityLevel]) -> None:
273273
],
274274
)
275275
def test_filter_vulnerabilities(
276-
vulnerabilities: List[SecurityVulnerability],
276+
vulnerabilities: list[SecurityVulnerability],
277277
config: Configuration,
278-
expected: List[SecurityVulnerability],
278+
expected: list[SecurityVulnerability],
279279
) -> None:
280280
assert (
281281
filter_vulnerabilities(config=config, vulnerabilities=vulnerabilities)
@@ -375,7 +375,7 @@ def test_filter_vulnerabilities(
375375
ids=["sort by package", "empty", "sub-sort by identifier"],
376376
)
377377
def test_sort_vulnerabilities(
378-
vulnerabilities: List[SecurityVulnerability], expected: List[SecurityVulnerability]
378+
vulnerabilities: list[SecurityVulnerability], expected: list[SecurityVulnerability]
379379
) -> None:
380380
original_vulnerabilities = vulnerabilities.copy()
381381
assert sort_vulnerabilities(vulnerabilities=vulnerabilities) == expected
@@ -423,7 +423,7 @@ def test_sort_vulnerabilities(
423423
def test_create_header(
424424
monkeypatch: "MonkeyPatch",
425425
frozen_time: None,
426-
db_names: List[str],
426+
db_names: list[str],
427427
config: Configuration,
428428
expected: str,
429429
) -> None:
@@ -769,7 +769,7 @@ def test_main__exception(
769769
mock_get_apis: Mock,
770770
mock_filter_vulnerabilities: Mock,
771771
arg_namespace: ArgumentNamespace,
772-
exception_type: Type[Exception],
772+
exception_type: type[Exception],
773773
expected_exit_code: int,
774774
) -> None:
775775
mock_stream = Mock(isatty=Mock(return_value=True))

tox.ini

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ minversion = 4.0.0
55

66
[gh-actions]
77
python =
8-
3.8: py38
98
3.9: py39
109
3.10: py310
1110
3.11: py311
1211
3.12: py312
12+
3.13: py313
1313

1414
[testenv]
1515
deps =
1616
-rrequirements-test.txt
1717
-rrequirements-lint.txt
1818
commands =
19-
ruff check .
20-
black --check src test
19+
ruff check
20+
ruff format
2121
pytest
2222
mypy

0 commit comments

Comments
 (0)