Skip to content

Commit

Permalink
Merge pull request #38 from mam-dev/py313
Browse files Browse the repository at this point in the history
Support Python 3.13, drop 3.9, various updates
  • Loading branch information
bunny-therapist authored Jan 27, 2025
2 parents 400af69 + ff64009 commit 3e5a983
Show file tree
Hide file tree
Showing 10 changed files with 57 additions and 50 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ jobs:
build:
strategy:
matrix:
python_version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python_version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python_version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
- name: Install dependencies
Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ authors = [
]
license = {file = "LICENSE"}
urls = {repo = "https://github.com/mam-dev/security-constraints"}
requires-python = ">=3.8"
requires-python = ">=3.9"
dependencies = [
"requests",
"pyyaml",
]
dynamic = ["version"]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Security",
"License :: OSI Approved :: Apache Software License",
]
Expand Down Expand Up @@ -118,6 +118,7 @@ ignore = [
"PTH123",
"TRY003", "TRY301",
"UP032",
"ISC001",
]

[tool.ruff.lint.per-file-ignores]
Expand Down
1 change: 1 addition & 0 deletions src/security_constraints/common.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""This module contains common definitions for use in any other module."""

from __future__ import annotations

import abc
Expand Down
15 changes: 8 additions & 7 deletions src/security_constraints/github_security_advisory.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
"""Module for fetching vulnerabilities from the GitHub Security Advisory."""

import logging
import os
import string
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set
from typing import TYPE_CHECKING, Any, Optional

import requests

Expand All @@ -19,7 +20,7 @@
from typing import TypedDict

class _GraphQlResponseJson(TypedDict, total=False):
data: Dict[Any, Any]
data: dict[Any, Any]

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

def get_vulnerabilities(
self, severities: Set[SeverityLevel]
) -> List[SecurityVulnerability]:
self, severities: set[SeverityLevel]
) -> list[SecurityVulnerability]:
"""Fetch all CRITICAL vulnerabilities from GitHub Security Advisory.
The SeverityLevels map trivially to GitHub's SecurityAdvisorySeverity.
"""
after: Optional[str] = None
vulnerabilities: List[SecurityVulnerability] = []
vulnerabilities: list[SecurityVulnerability] = []
more_data_exists = True
while more_data_exists:
json_response: "_GraphQlResponseJson" = self._do_graphql_request(
severities=severities, after=after
)
try:
json_data: Dict[str, Any] = json_response["data"]
json_data: dict[str, Any] = json_response["data"]
vulnerabilities.extend(
[
SecurityVulnerability(
Expand Down Expand Up @@ -130,7 +131,7 @@ def get_vulnerabilities(
return vulnerabilities

def _do_graphql_request(
self, severities: Set[SeverityLevel], after: Optional[str] = None
self, severities: set[SeverityLevel], after: Optional[str] = None
) -> "_GraphQlResponseJson":
query = QUERY_TEMPLATE.substitute(
first=100,
Expand Down
34 changes: 18 additions & 16 deletions src/security_constraints/main.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Main module."""

import argparse
import logging
import sys
from collections.abc import Sequence
from datetime import datetime, timezone
from importlib.metadata import version
from typing import IO, List, Optional, Sequence, Set
from typing import IO, Optional

import yaml

Expand All @@ -23,26 +25,26 @@


def get_security_vulnerability_database_apis() -> (
List[SecurityVulnerabilityDatabaseAPI]
list[SecurityVulnerabilityDatabaseAPI]
):
"""Return the APIs to use for fetching vulnerabilities."""
return [GithubSecurityAdvisoryAPI()]


def fetch_vulnerabilities(
apis: Sequence[SecurityVulnerabilityDatabaseAPI], severities: Set[SeverityLevel]
) -> List[SecurityVulnerability]:
apis: Sequence[SecurityVulnerabilityDatabaseAPI], severities: set[SeverityLevel]
) -> list[SecurityVulnerability]:
"""Use apis to fetch and return vulnerabilities."""
vulnerabilities: List[SecurityVulnerability] = []
vulnerabilities: list[SecurityVulnerability] = []
for api in apis:
LOGGER.debug("Fetching vulnerabilities from %s...", api.get_database_name())
vulnerabilities.extend(api.get_vulnerabilities(severities=severities))
return vulnerabilities


def filter_vulnerabilities(
config: Configuration, vulnerabilities: List[SecurityVulnerability]
) -> List[SecurityVulnerability]:
config: Configuration, vulnerabilities: list[SecurityVulnerability]
) -> list[SecurityVulnerability]:
"""Filter out vulnerabilities that should be ignored and return the rest."""
if config.ignore_ids:
LOGGER.debug("Applying ignore-ids...")
Expand All @@ -53,8 +55,8 @@ def filter_vulnerabilities(


def sort_vulnerabilities(
vulnerabilities: List[SecurityVulnerability],
) -> List[SecurityVulnerability]:
vulnerabilities: list[SecurityVulnerability],
) -> list[SecurityVulnerability]:
"""Sort vulnerabilities into the order they should appear in the constraints."""
sorted_vulnerabilities = sorted(vulnerabilities, key=lambda v: v.identifier)
sorted_vulnerabilities.sort(key=lambda v: v.package)
Expand All @@ -69,7 +71,7 @@ def get_safe_version_constraints(
See SecurityVulnerability documentation for more information.
"""
safe_specs: List[str] = []
safe_specs: list[str] = []
vulnerable_spec: str
if "," in vulnerability.vulnerable_range:
# If there is a known min and max affected version, make the constraints
Expand Down Expand Up @@ -124,9 +126,9 @@ def create_header(
"""Create the comment header which goes at the top of the output."""
time_format: str = "%Y-%m-%dT%H:%M:%S.%fZ" # ISO with Z for UTC
timestamp: str = datetime.now(tz=timezone.utc).strftime(time_format)
sources: List[str] = [api.get_database_name() for api in apis]
sources: list[str] = [api.get_database_name() for api in apis]
app_name: str = "security-constraints"
lines: List[str] = [
lines: list[str] = [
f"Generated by {app_name} {version(app_name)} on {timestamp}",
f"Data sources: {', '.join(sources)}",
f"Configuration: {config.to_dict()}",
Expand Down Expand Up @@ -259,11 +261,11 @@ def main() -> int:
yaml.safe_dump(config.to_dict(), stream=sys.stdout)
return 0

apis: List[
SecurityVulnerabilityDatabaseAPI
] = get_security_vulnerability_database_apis()
apis: list[SecurityVulnerabilityDatabaseAPI] = (
get_security_vulnerability_database_apis()
)

vulnerabilities: List[SecurityVulnerability] = fetch_vulnerabilities(
vulnerabilities: list[SecurityVulnerability] = fetch_vulnerabilities(
apis, severities=config.min_severity.get_higher_or_equal_severities()
)
vulnerabilities = filter_vulnerabilities(config, vulnerabilities)
Expand Down
3 changes: 2 additions & 1 deletion test/conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
from typing import TYPE_CHECKING, Generator
from collections.abc import Generator
from typing import TYPE_CHECKING
from unittest.mock import Mock

import freezegun
Expand Down
10 changes: 5 additions & 5 deletions test/test_common.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Any, List, Set
from typing import Any
from unittest.mock import Mock

import pytest
Expand Down Expand Up @@ -50,7 +50,7 @@ def test_severity_level_compare_with_other_type() -> None:
],
)
def test_sort_severity_levels(
severities: List[SeverityLevel], expected: List[SeverityLevel]
severities: list[SeverityLevel], expected: list[SeverityLevel]
) -> None:
assert sorted(severities) == expected

Expand All @@ -76,7 +76,7 @@ def test_sort_severity_levels(
],
)
def test_get_higher_or_equal_severities(
severity: SeverityLevel, expected: Set[SeverityLevel]
severity: SeverityLevel, expected: set[SeverityLevel]
) -> None:
assert severity.get_higher_or_equal_severities() == expected

Expand Down Expand Up @@ -225,7 +225,7 @@ def test_configuration_from_args() -> None:
],
)
def test_configuration_merge(
configs: List[Configuration], expected: Configuration
configs: list[Configuration], expected: Configuration
) -> None:
assert Configuration.merge(*configs) == expected

Expand All @@ -244,7 +244,7 @@ def test_configuration_supported_keys() -> None:
],
)
def test_package_constraints_str(
package: str, specifiers: List[str], expected: str
package: str, specifiers: list[str], expected: str
) -> None:
assert str(PackageConstraints(package=package, specifiers=specifiers)) == expected

Expand Down
13 changes: 7 additions & 6 deletions test/test_github_security_advisory.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import TYPE_CHECKING, Any, Dict, List, Set
from typing import TYPE_CHECKING, Any

import pytest

Expand Down Expand Up @@ -37,7 +37,7 @@ def test_get_database_name(github_token: str) -> None:
def test_get_vulnerabilities(
github_token: str,
requests_mock: "RequestsMock",
severities: Set[SeverityLevel],
severities: set[SeverityLevel],
expected_graphql_severities: str,
) -> None:
cursors = (
Expand All @@ -46,8 +46,8 @@ def test_get_vulnerabilities(
"Y3Vyc29yOnYyOpK5MjAyMC0wOS0yNVQxOTo0MjowMCswMXowMM0DeQ==",
"Y3Vyc29yOnYyOpK5MjAyMC0wOS0yNVQxOTo0MjowMCswMHowMM0LeQ==",
)
expected_vulnerabilities: List[SecurityVulnerability] = []
vulnerability_nodes: List[Dict[str, Any]] = []
expected_vulnerabilities: list[SecurityVulnerability] = []
vulnerability_nodes: list[dict[str, Any]] = []
for request_index in range(3):
for i in range(100 if request_index < 2 else 41):
ghsa = f"GHSA-{request_index}-{i}"
Expand Down Expand Up @@ -91,8 +91,9 @@ def test_get_vulnerabilities(
"hasNextPage": request_index < 2,
},
"nodes": vulnerability_nodes[
request_index
* 100 : min(request_index * 100 + 100, 241)
request_index * 100 : min(
request_index * 100 + 100, 241
)
],
}
}
Expand Down
14 changes: 7 additions & 7 deletions test/test_main.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import argparse
import logging
import sys
from typing import TYPE_CHECKING, List, Set, Type
from typing import TYPE_CHECKING
from unittest.mock import Mock, call, create_autospec

import pytest
Expand Down Expand Up @@ -192,7 +192,7 @@ def test_setup_logging(monkeypatch: "MonkeyPatch", debug: bool) -> None:
"severities",
[{SeverityLevel.CRITICAL}, {SeverityLevel.CRITICAL, SeverityLevel.HIGH}],
)
def test_fetch_vulnerabilities(severities: Set[SeverityLevel]) -> None:
def test_fetch_vulnerabilities(severities: set[SeverityLevel]) -> None:
mock_vulnerabilities = [create_autospec(SecurityVulnerability) for _ in range(3)]
mock_apis = [
Mock(
Expand Down Expand Up @@ -273,9 +273,9 @@ def test_fetch_vulnerabilities(severities: Set[SeverityLevel]) -> None:
],
)
def test_filter_vulnerabilities(
vulnerabilities: List[SecurityVulnerability],
vulnerabilities: list[SecurityVulnerability],
config: Configuration,
expected: List[SecurityVulnerability],
expected: list[SecurityVulnerability],
) -> None:
assert (
filter_vulnerabilities(config=config, vulnerabilities=vulnerabilities)
Expand Down Expand Up @@ -375,7 +375,7 @@ def test_filter_vulnerabilities(
ids=["sort by package", "empty", "sub-sort by identifier"],
)
def test_sort_vulnerabilities(
vulnerabilities: List[SecurityVulnerability], expected: List[SecurityVulnerability]
vulnerabilities: list[SecurityVulnerability], expected: list[SecurityVulnerability]
) -> None:
original_vulnerabilities = vulnerabilities.copy()
assert sort_vulnerabilities(vulnerabilities=vulnerabilities) == expected
Expand Down Expand Up @@ -423,7 +423,7 @@ def test_sort_vulnerabilities(
def test_create_header(
monkeypatch: "MonkeyPatch",
frozen_time: None,
db_names: List[str],
db_names: list[str],
config: Configuration,
expected: str,
) -> None:
Expand Down Expand Up @@ -769,7 +769,7 @@ def test_main__exception(
mock_get_apis: Mock,
mock_filter_vulnerabilities: Mock,
arg_namespace: ArgumentNamespace,
exception_type: Type[Exception],
exception_type: type[Exception],
expected_exit_code: int,
) -> None:
mock_stream = Mock(isatty=Mock(return_value=True))
Expand Down
6 changes: 3 additions & 3 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ minversion = 4.0.0

[gh-actions]
python =
3.8: py38
3.9: py39
3.10: py310
3.11: py311
3.12: py312
3.13: py313

[testenv]
deps =
-rrequirements-test.txt
-rrequirements-lint.txt
commands =
ruff check .
black --check src test
ruff check
ruff format
pytest
mypy

0 comments on commit 3e5a983

Please sign in to comment.