Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/distro_support/_debian_like_downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
from urllib import request


def get_distro_info(url: str, *, name: str, esm_name: str) -> dict[str, dict[str, str]]:
def get_distro_info(
url: str, *, name: str, esm_name: str | None = None
) -> dict[str, dict[str, str | None]]:
response: http.client.HTTPResponse = request.urlopen(url)
if response.status != 200:
raise ConnectionError(response.status)
Expand All @@ -19,6 +21,8 @@ def get_distro_info(url: str, *, name: str, esm_name: str) -> dict[str, dict[str
"begin_support": row["release"],
"end_support": row["eol"],
"begin_dev": row["created"],
"end_extended_support": row[f"eol-{esm_name}"],
"end_extended_support": (row.get(f"eol-{esm_name}") or None)
if esm_name
else None,
}
return series
26 changes: 15 additions & 11 deletions src/distro_support/_distro.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,24 @@ def is_esm_on(self, date: datetime.date) -> bool:
)

@classmethod
def from_json(cls, data: dict[str, str]) -> Self:
def from_json(cls, data: dict[str, str | None]) -> Self:
begin_support = data.get("begin_support")
end_support = data.get("end_support")
begin_dev = data.get("begin_dev")
end_extended_support = data.get("end_extended_support")
return cls(
distribution=data["distribution"],
version=data["version"],
distribution=data["distribution"] or "",
version=data["version"] or "",
begin_support=None
if data.get("begin_support") is None
else datetime.date.fromisoformat(data["begin_support"]),
if begin_support is None
else datetime.date.fromisoformat(begin_support),
end_support=None
if data.get("end_support") is None
else datetime.date.fromisoformat(data["end_support"]),
if end_support is None
else datetime.date.fromisoformat(end_support),
begin_dev=None
if data.get("begin_dev") is None
else datetime.date.fromisoformat(data["begin_dev"]),
if begin_dev is None
else datetime.date.fromisoformat(begin_dev),
end_extended_support=None
if data.get("end_extended_support") is None
else datetime.date.fromisoformat(data["end_extended_support"]),
if end_extended_support is None
else datetime.date.fromisoformat(end_extended_support),
)
2 changes: 1 addition & 1 deletion src/distro_support/debian.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
SUPPORT_INFO_URL = "https://salsa.debian.org/debian/distro-info-data/-/raw/main/debian.csv?ref_type=heads&inline=false"


def get_distro_info() -> dict[str, dict[str, str]]:
def get_distro_info() -> dict[str, dict[str, str | None]]:
return _debian_like_downloader.get_distro_info(
SUPPORT_INFO_URL, name="debian", esm_name="elts"
)
74 changes: 74 additions & 0 deletions src/distro_support/devuan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"1": {
"distribution": "devuan",
"version": "1",
"begin_support": "2017-05-25",
"end_support": "2020-06-30",
"begin_dev": "2014-11-26",
"end_extended_support": null
},
"2": {
"distribution": "devuan",
"version": "2",
"begin_support": "2018-06-08",
"end_support": "2022-06-30",
"begin_dev": "2017-05-25",
"end_extended_support": null
},
"3": {
"distribution": "devuan",
"version": "3",
"begin_support": "2020-06-01",
"end_support": "2024-06-30",
"begin_dev": "2018-06-08",
"end_extended_support": null
},
"4": {
"distribution": "devuan",
"version": "4",
"begin_support": "2021-10-14",
"end_support": "2026-08-31",
"begin_dev": "2020-01-07",
"end_extended_support": null
},
"5": {
"distribution": "devuan",
"version": "5",
"begin_support": "2023-08-14",
"end_support": "2028-06-30",
"begin_dev": "2021-10-14",
"end_extended_support": null
},
"6": {
"distribution": "devuan",
"version": "6",
"begin_support": "2025-11-02",
"end_support": "2030-06-30",
"begin_dev": "2023-03-29",
"end_extended_support": null
},
"7": {
"distribution": "devuan",
"version": "7",
"begin_support": null,
"end_support": null,
"begin_dev": "2025-07-26",
"end_extended_support": null
},
"8": {
"distribution": "devuan",
"version": "8",
"begin_support": null,
"end_support": null,
"begin_dev": "2027-07-01",
"end_extended_support": null
},
"": {
"distribution": "devuan",
"version": "",
"begin_support": null,
"end_support": null,
"begin_dev": "2014-11-26",
"end_extended_support": null
}
}
11 changes: 11 additions & 0 deletions src/distro_support/devuan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Information about Devuan support."""

from . import _debian_like_downloader

SUPPORT_INFO_URL = (
"https://salsa.debian.org/debian/distro-info-data/-/raw/main/devuan.csv"
)


def get_distro_info() -> dict[str, dict[str, str | None]]:
return _debian_like_downloader.get_distro_info(SUPPORT_INFO_URL, name="devuan")
2 changes: 1 addition & 1 deletion src/distro_support/ubuntu.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
)


def get_distro_info() -> dict[str, dict[str, str]]:
def get_distro_info() -> dict[str, dict[str, str | None]]:
return _debian_like_downloader.get_distro_info(
SUPPORT_INFO_URL, name="ubuntu", esm_name="esm"
)
83 changes: 83 additions & 0 deletions tests/test_debian_like_downloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Tests for the debian-like downloader."""

import unittest.mock

Check warning on line 4 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L4

Import "pytest" could not be resolved (reportMissingImports)
import pytest

from distro_support import _debian_like_downloader
from distro_support._distro import SupportRange


def _make_response(csv_text: str, status: int = 200):
mock_response = unittest.mock.MagicMock()
mock_response.status = status
mock_response.read.return_value = csv_text.encode()
mock_response.__enter__ = lambda self: self
mock_response.__exit__ = unittest.mock.MagicMock(return_value=False)
return mock_response


CSV_WITH_EMPTY_ESM = """\
version,release,eol,created,eol-esm
22.04 LTS,2022-04-21,2027-04-01,2021-10-14,2032-04-09
24.04 LTS,2024-04-25,2029-04-25,2023-10-12,
"""

CSV_WITHOUT_ESM = """\
version,release,eol,created
5,2021-01-01,2026-06-15,2020-01-01
"""


@unittest.mock.patch("distro_support._debian_like_downloader.request.urlopen")
def test_empty_esm_column_returns_none(mock_urlopen):
"""An empty eol-esm column must produce None, not an empty string.

Check notice on line 34 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L34

Multi-line docstring summary should start at the second line (D213)

Without the `or None` fix, row.get() returns "" for an empty CSV cell.
SupportRange.from_json() checks `is None`, so "" bypasses the guard and
datetime.date.fromisoformat("") raises a ValueError when the data is used.
"""
mock_urlopen.return_value = _make_response(CSV_WITH_EMPTY_ESM)

result = _debian_like_downloader.get_distro_info(
"https://example.com/data.csv", name="ubuntu", esm_name="esm"
)

assert result["24.04"]["end_extended_support"] is None

Check warning on line 46 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L46

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check warning on line 46 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L46

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. (B101)
# Verify the data round-trips through SupportRange.from_json without error
support_range = SupportRange.from_json(result["24.04"])
assert support_range.end_extended_support is None

Check warning on line 49 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L49

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check warning on line 49 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L49

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. (B101)


@unittest.mock.patch("distro_support._debian_like_downloader.request.urlopen")
def test_populated_esm_column_returns_date_string(mock_urlopen):
"""A populated eol-esm column must be returned as-is."""
mock_urlopen.return_value = _make_response(CSV_WITH_EMPTY_ESM)

result = _debian_like_downloader.get_distro_info(
"https://example.com/data.csv", name="ubuntu", esm_name="esm"
)

assert result["22.04"]["end_extended_support"] == "2032-04-09"

Check warning on line 61 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L61

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check warning on line 61 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L61

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. (B101)


@unittest.mock.patch("distro_support._debian_like_downloader.request.urlopen")
def test_no_esm_name_returns_none(mock_urlopen):
"""When esm_name is not provided, end_extended_support must be None."""
mock_urlopen.return_value = _make_response(CSV_WITHOUT_ESM)

result = _debian_like_downloader.get_distro_info(
"https://example.com/data.csv", name="devuan"
)

assert result["5"]["end_extended_support"] is None

Check warning on line 73 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L73

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.

Check warning on line 73 in tests/test_debian_like_downloader.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

tests/test_debian_like_downloader.py#L73

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. (B101)


@unittest.mock.patch("distro_support._debian_like_downloader.request.urlopen")
def test_http_error_raises(mock_urlopen):
mock_urlopen.return_value = _make_response("", status=404)

with pytest.raises(ConnectionError):
_debian_like_downloader.get_distro_info(
"https://example.com/data.csv", name="ubuntu"
)
3 changes: 3 additions & 0 deletions tests/test_get_support_range.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
("ubuntu", "25.10", date(2025, 8, 12), False, True, None),
("debian", "1.1", date(2000, 1, 1), False, False, None),
("debian", "", date(3000, 1, 1), False, True, None),
("devuan", "4", date(2022, 1, 1), True, False, None),
("devuan", "4", date(2030, 1, 1), False, False, None),
("devuan", "7", date(2026, 1, 1), False, True, None),
],
)
def test_get_support_range(
Expand Down
6 changes: 4 additions & 2 deletions tools/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
import json
import pathlib

from distro_support import debian, ubuntu
from distro_support import debian, devuan, ubuntu


def update(module):
ubuntu_data = pathlib.Path(module.__file__).with_suffix(".json")
ubuntu_data.write_text(json.dumps(module.get_distro_info(), indent=" "))
ubuntu_data.write_text(json.dumps(module.get_distro_info(), indent=" ") + "\n")


if __name__ == "__main__":
print("Updating Ubuntu data")
update(ubuntu)
print("Updating Debian data")
update(debian)
print("Updating Devuan data")
update(devuan)
Loading