Make releasedata.Product usable in 'with' expression (#294)

This way the writing of the JSON file is handled automatically if the update does not fail.
It pave the way to further global improvements, such as a better error handling.
This commit is contained in:
Marc Wrobel
2024-02-04 14:48:05 +01:00
committed by GitHub
parent 025e06b371
commit 56cc29b49d
44 changed files with 597 additions and 667 deletions

View File

@@ -8,7 +8,7 @@ from common import dates, http, releasedata
RSS_URL = "https://docs.aws.amazon.com/neptune/latest/userguide/rssupdates.rss" RSS_URL = "https://docs.aws.amazon.com/neptune/latest/userguide/rssupdates.rss"
VERSION_PATTERN = re.compile(r"^Engine version (?P<version>[0-9R.]+)$") VERSION_PATTERN = re.compile(r"^Engine version (?P<version>[0-9R.]+)$")
product = releasedata.Product("amazon-neptune") with releasedata.ProductData("amazon-neptune") as product_data:
rss_response = http.fetch_url(RSS_URL) rss_response = http.fetch_url(RSS_URL)
rss = xml.dom.minidom.parseString(rss_response.text) rss = xml.dom.minidom.parseString(rss_response.text)
@@ -18,6 +18,4 @@ for entry in rss.getElementsByTagName("item"):
version_match = VERSION_PATTERN.match(version_str) version_match = VERSION_PATTERN.match(version_str)
if version_match: if version_match:
product.declare_version(version_match['version'], dates.parse_datetime(date_str)) product_data.declare_version(version_match['version'], dates.parse_datetime(date_str))
product.write()

View File

@@ -15,7 +15,7 @@ VERSION_AND_DATE_PATTERNS = [
re.compile(r"\s+(?P<version>\d+\.\d+\.\d+)\s*:.*Tagged and [rR]olled\s(?:on\s)?(?P<date>\w+\.?\s\d\d?,\s\d{4})"), re.compile(r"\s+(?P<version>\d+\.\d+\.\d+)\s*:.*Tagged and [rR]olled\s(?:on\s)?(?P<date>\w+\.?\s\d\d?,\s\d{4})"),
] ]
product = releasedata.Product("apache-http-server") with releasedata.ProductData("apache-http-server") as product_data:
git = Git("https://github.com/apache/httpd.git") git = Git("https://github.com/apache/httpd.git")
git.setup() git.setup()
@@ -31,6 +31,4 @@ for branch in git.list_branches("refs/heads/?.?.x"):
for pattern in VERSION_AND_DATE_PATTERNS: for pattern in VERSION_AND_DATE_PATTERNS:
for (version, date_str) in pattern.findall(release_notes): for (version, date_str) in pattern.findall(release_notes):
product.declare_version(version, dates.parse_date(date_str)) product_data.declare_version(version, dates.parse_date(date_str))
product.write()

View File

@@ -56,7 +56,7 @@ soups = [BeautifulSoup(response.text, features="html5lib") for response in http.
logging.info("::endgroup::") logging.info("::endgroup::")
for product_name in VERSION_PATTERNS: for product_name in VERSION_PATTERNS:
product = releasedata.Product(product_name) with releasedata.ProductData(product_name) as product_data:
for soup in soups: for soup in soups:
versions_table = soup.find(id="tableWraper") versions_table = soup.find(id="tableWraper")
versions_table = versions_table if versions_table else soup.find('table', class_="gb-table") versions_table = versions_table if versions_table else soup.find('table', class_="gb-table")
@@ -73,12 +73,10 @@ for product_name in VERSION_PATTERNS:
date_str = date_match.group(0).replace("Sept ", "Sep ") date_str = date_match.group(0).replace("Sept ", "Sep ")
date = dates.parse_date(date_str) date = dates.parse_date(date_str)
for version_pattern in VERSION_PATTERNS[product.name]: for version_pattern in VERSION_PATTERNS[product_data.name]:
for version_str in version_pattern.findall(version_text): for version_str in version_pattern.findall(version_text):
version = product.get_version(version_str) version = product_data.get_version(version_str)
if not version or version.date() > date: if not version or version.date() > date:
product.declare_version(version_str, date) product_data.declare_version(version_str, date)
else: else:
logging.info(f"ignoring version {version_str} ({date}) for {product.name}") logging.info(f"ignoring version {version_str} ({date}) for {product_data.name}")
product.write()

View File

@@ -12,9 +12,9 @@ If one day release dates are available in the AWS documentation, it would be bet
them though. Note that this would also be unnecessary if it was possible to disable release/latest them though. Note that this would also be unnecessary if it was possible to disable release/latest
release dates updates in the latest.py script.""" release dates updates in the latest.py script."""
product = releasedata.Product("aws-lambda") with releasedata.ProductData("aws-lambda") as product_data:
old_product = releasedata.Product.from_file(product.name) old_product_data = releasedata.ProductData.from_file(product_data.name)
product_frontmatter = endoflife.ProductFrontmatter(product.name) product_frontmatter = endoflife.ProductFrontmatter(product_data.name)
response = http.fetch_url("https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html") response = http.fetch_url("https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html")
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -30,10 +30,8 @@ for table in soup.find_all("table"):
date = product_frontmatter.get_release_date(identifier) # use the product releaseDate if available date = product_frontmatter.get_release_date(identifier) # use the product releaseDate if available
if date is None: if date is None:
date = old_product.get_version(identifier).date() # else use the previously found date date = old_product_data.get_version(identifier).date() # else use the previously found date
if date is None: if date is None:
date = dates.today() # else use today's date date = dates.today() # else use today's date
product.declare_version(identifier, date) product_data.declare_version(identifier, date)
product.write()

View File

@@ -10,7 +10,7 @@ METHOD = "cgit"
p_filter = sys.argv[1] if len(sys.argv) > 1 else None p_filter = sys.argv[1] if len(sys.argv) > 1 else None
for product in endoflife.list_products(METHOD, p_filter): for product in endoflife.list_products(METHOD, p_filter):
product_data = releasedata.Product(product.name) with releasedata.ProductData(product.name) as product_data:
for config in product.get_auto_configs(METHOD): for config in product.get_auto_configs(METHOD):
response = http.fetch_url(config.url + '/refs/tags') response = http.fetch_url(config.url + '/refs/tags')
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -34,5 +34,3 @@ for product in endoflife.list_products(METHOD, p_filter):
version = config.render(version_match) version = config.render(version_match)
date = dates.parse_datetime(datetime_str) date = dates.parse_datetime(datetime_str)
product_data.declare_version(version, date) product_data.declare_version(version, date)
product_data.write()

View File

@@ -31,7 +31,7 @@ FIXED_VERSIONS = {
"2023.0.0": dates.date(2022, 5, 16), # https://coldfusion.adobe.com/2023/05/coldfusion2023-release/ "2023.0.0": dates.date(2022, 5, 16), # https://coldfusion.adobe.com/2023/05/coldfusion2023-release/
} }
product = releasedata.Product("coldfusion") with releasedata.ProductData("coldfusion") as product_data:
for changelog in http.fetch_urls(URLS): for changelog in http.fetch_urls(URLS):
changelog_soup = BeautifulSoup(changelog.text, features="html5lib") changelog_soup = BeautifulSoup(changelog.text, features="html5lib")
@@ -40,7 +40,6 @@ for changelog in http.fetch_urls(URLS):
for (date_str, version_str) in VERSION_AND_DATE_PATTERN.findall(version_and_date_str): for (date_str, version_str) in VERSION_AND_DATE_PATTERN.findall(version_and_date_str):
date = dates.parse_date(date_str) date = dates.parse_date(date_str)
version = version_str.strip().replace(",", ".") # 11,0,0,289974 -> 11.0.0.289974 version = version_str.strip().replace(",", ".") # 11,0,0,289974 -> 11.0.0.289974
product.declare_version(version, date) product_data.declare_version(version, date)
product.declare_versions(FIXED_VERSIONS) product_data.declare_versions(FIXED_VERSIONS)
product.write()

View File

@@ -3,6 +3,8 @@ import logging
import os import os
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from types import TracebackType
from typing import Optional, Type
# Do not update the format: it's also used to declare groups in the GitHub Actions logs. # Do not update the format: it's also used to declare groups in the GitHub Actions logs.
logging.basicConfig(format="%(message)s", level=logging.INFO) logging.basicConfig(format="%(message)s", level=logging.INFO)
@@ -10,13 +12,16 @@ logging.basicConfig(format="%(message)s", level=logging.INFO)
VERSIONS_PATH = Path(os.environ.get("VERSIONS_PATH", "releases")) VERSIONS_PATH = Path(os.environ.get("VERSIONS_PATH", "releases"))
class ProductUpdateError(Exception):
"""Custom exceptions raised when unexpected errors occur during product updates."""
class ProductVersion: class ProductVersion:
def __init__(self, product: "Product", data: dict) -> None: def __init__(self, product: "ProductData", data: dict) -> None:
self.product = str(product) self.product = str(product)
self.data = data self.data = data
@staticmethod @staticmethod
def of(product: "Product", name: str, date: datetime) -> "ProductVersion": def of(product: "ProductData", name: str, date: datetime) -> "ProductVersion":
return ProductVersion(product, { return ProductVersion(product, {
"name": name, "name": name,
"date": date.strftime("%Y-%m-%d"), "date": date.strftime("%Y-%m-%d"),
@@ -35,16 +40,37 @@ class ProductVersion:
return f"{self.product}#{self.name()} ({self.date()})" return f"{self.product}#{self.name()} ({self.date()})"
class Product: class ProductData:
def __init__(self, name: str) -> None: def __init__(self, name: str) -> None:
self.name: str = name self.name: str = name
self.versions_path: Path = VERSIONS_PATH / f"{name}.json" self.versions_path: Path = VERSIONS_PATH / f"{name}.json"
self.versions: dict[str, ProductVersion] = {} self.versions: dict[str, ProductVersion] = {}
def __enter__(self) -> "ProductData":
logging.info(f"::group::{self}") logging.info(f"::group::{self}")
return self
def __exit__(self, exc_type: Optional[Type[BaseException]], exc_value: Optional[BaseException],
exc_traceback: Optional[TracebackType]) -> None:
try:
if exc_value:
message = f"an unexpected error occurred while updating {self} data"
logging.error(message, exc_info=exc_value)
raise ProductUpdateError(message) from exc_value
logging.info("updating %s data",self)
# sort by date then version (desc)
ordered_versions = sorted(self.versions.values(), key=lambda v: (v.date(), v.name()), reverse=True)
with self.versions_path.open("w") as f:
f.write(json.dumps({
"versions": {version.name(): version.data for version in ordered_versions},
}, indent=2))
finally:
logging.info("::endgroup::")
@staticmethod @staticmethod
def from_file(name: str) -> "Product": def from_file(name: str) -> "ProductData":
product = Product(name) product = ProductData(name)
if product.versions_path.is_file(): if product.versions_path.is_file():
with product.versions_path.open() as f: with product.versions_path.open() as f:
@@ -79,14 +105,5 @@ class Product:
logging.info(f"removing version {version} ({self.versions.pop(version)}) from {self}") logging.info(f"removing version {version} ({self.versions.pop(version)}) from {self}")
def write(self) -> None:
# sort by date then version (desc)
ordered_versions = sorted(self.versions.values(), key=lambda v: (v.date(), v.name()), reverse=True)
with self.versions_path.open("w") as f:
f.write(json.dumps({
"versions": {version.name(): version.data for version in ordered_versions},
}, indent=2))
logging.info("::endgroup::")
def __repr__(self) -> str: def __repr__(self) -> str:
return self.name return self.name

View File

@@ -5,13 +5,11 @@ from requests_html import HTMLSession
Note that requests_html is used because JavaScript is needed to render the page.""" Note that requests_html is used because JavaScript is needed to render the page."""
product = releasedata.Product("confluence") with releasedata.ProductData("confluence") as product_data:
r = HTMLSession().get("https://www.atlassian.com/software/confluence/download-archives") r = HTMLSession().get("https://www.atlassian.com/software/confluence/download-archives")
r.html.render(sleep=1, scrolldown=3) r.html.render(sleep=1, scrolldown=3)
for version_block in r.html.find('.versions-list'): for version_block in r.html.find('.versions-list'):
version = version_block.find('a.product-versions', first=True).attrs['data-version'] version = version_block.find('a.product-versions', first=True).attrs['data-version']
date = dates.parse_date(version_block.find('.release-date', first=True).text) date = dates.parse_date(version_block.find('.release-date', first=True).text)
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -14,7 +14,7 @@ def parse_date(date_text: str) -> datetime:
return dates.parse_date(date_text) return dates.parse_date(date_text)
product = releasedata.Product("cos") with releasedata.ProductData("cos") as product_data:
main = http.fetch_url("https://cloud.google.com/container-optimized-os/docs/release-notes/") main = http.fetch_url("https://cloud.google.com/container-optimized-os/docs/release-notes/")
main_soup = BeautifulSoup(main.text, features="html5lib") main_soup = BeautifulSoup(main.text, features="html5lib")
milestones = [cell.text.split(' ')[1] for cell in main_soup.find_all('td', string=MILESTONE_PATTERN)] milestones = [cell.text.split(' ')[1] for cell in main_soup.find_all('td', string=MILESTONE_PATTERN)]
@@ -40,6 +40,4 @@ for milestone in http.fetch_urls(milestones_urls):
date_str = heading.find_previous('h2').get('data-text') date_str = heading.find_previous('h2').get('data-text')
date = parse_date(date_str) date = parse_date(date_str)
product.declare_version(version_match.group(1), date) product_data.declare_version(version_match.group(1), date)
product.write()

View File

@@ -18,7 +18,7 @@ MANUAL_VERSIONS = {
"7.2.0": dates.date(2023, 6, 1), # https://www.couchbase.com/blog/couchbase-capella-spring-release-72/ "7.2.0": dates.date(2023, 6, 1), # https://www.couchbase.com/blog/couchbase-capella-spring-release-72/
} }
product = releasedata.Product("couchbase-server") with releasedata.ProductData("couchbase-server") as product_data:
main = http.fetch_url(f"{URLS}/current/install/install-intro.html") main = http.fetch_url(f"{URLS}/current/install/install-intro.html")
main_soup = BeautifulSoup(main.text, features="html5lib") main_soup = BeautifulSoup(main.text, features="html5lib")
@@ -34,7 +34,6 @@ for minor_version in http.fetch_urls(minor_version_urls):
version = match["version"] version = match["version"]
version = f"{version}.0" if len(version.split(".")) == 2 else version version = f"{version}.0" if len(version.split(".")) == 2 else version
date = dates.parse_month_year_date(match['date']) date = dates.parse_month_year_date(match['date'])
product.declare_version(version, date) product_data.declare_version(version, date)
product.declare_versions(MANUAL_VERSIONS) product_data.declare_versions(MANUAL_VERSIONS)
product.write()

View File

@@ -7,7 +7,7 @@ from common.git import Git
"""Fetch Debian versions by parsing news in www.debian.org source repository.""" """Fetch Debian versions by parsing news in www.debian.org source repository."""
def extract_major_versions(p: releasedata.Product, repo_dir: Path) -> None: def extract_major_versions(p: releasedata.ProductData, repo_dir: Path) -> None:
child = run( child = run(
f"grep -RhE -A 1 '<define-tag pagetitle>Debian [0-9]+.+</q> released' {repo_dir}/english/News " f"grep -RhE -A 1 '<define-tag pagetitle>Debian [0-9]+.+</q> released' {repo_dir}/english/News "
f"| cut -d '<' -f 2 " f"| cut -d '<' -f 2 "
@@ -26,7 +26,7 @@ def extract_major_versions(p: releasedata.Product, repo_dir: Path) -> None:
is_release_line = True is_release_line = True
def extract_point_versions(p: releasedata.Product, repo_dir: Path) -> None: def extract_point_versions(p: releasedata.ProductData, repo_dir: Path) -> None:
child = run( child = run(
f"grep -Rh -B 10 '<define-tag revision>' {repo_dir}/english/News " f"grep -Rh -B 10 '<define-tag revision>' {repo_dir}/english/News "
"| grep -Eo '(release_date>(.*)<|revision>(.*)<)' " "| grep -Eo '(release_date>(.*)<|revision>(.*)<)' "
@@ -41,12 +41,10 @@ def extract_point_versions(p: releasedata.Product, repo_dir: Path) -> None:
p.declare_version(version, dates.parse_date(date)) p.declare_version(version, dates.parse_date(date))
product = releasedata.Product("debian") with releasedata.ProductData("debian") as product_data:
git = Git("https://salsa.debian.org/webmaster-team/webwml.git") git = Git("https://salsa.debian.org/webmaster-team/webwml.git")
git.setup() git.setup()
git.checkout("master", file_list=["english/News"]) git.checkout("master", file_list=["english/News"])
extract_major_versions(product, git.repo_dir) extract_major_versions(product_data, git.repo_dir)
extract_point_versions(product, git.repo_dir) extract_point_versions(product_data, git.repo_dir)
product.write()

View File

@@ -7,7 +7,7 @@ METHOD = 'distrowatch'
p_filter = sys.argv[1] if len(sys.argv) > 1 else None p_filter = sys.argv[1] if len(sys.argv) > 1 else None
for product in endoflife.list_products(METHOD, p_filter): for product in endoflife.list_products(METHOD, p_filter):
product_data = releasedata.Product(product.name) with releasedata.ProductData(product.name) as product_data:
for config in product.get_auto_configs(METHOD): for config in product.get_auto_configs(METHOD):
response = http.fetch_url(f"https://distrowatch.com/index.php?distribution={config.url}") response = http.fetch_url(f"https://distrowatch.com/index.php?distribution={config.url}")
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -24,5 +24,3 @@ for product in endoflife.list_products(METHOD, p_filter):
for version in versions: for version in versions:
product_data.declare_version(version, date) product_data.declare_version(version, date)
product_data.write()

View File

@@ -9,7 +9,7 @@ Unfortunately images creation date cannot be retrieved, so we had to use the tag
METHOD = "docker_hub" METHOD = "docker_hub"
def fetch_releases(p: releasedata.Product, c: endoflife.AutoConfig, url: str) -> None: def fetch_releases(p: releasedata.ProductData, c: endoflife.AutoConfig, url: str) -> None:
data = http.fetch_url(url).json() data = http.fetch_url(url).json()
for result in data["results"]: for result in data["results"]:
@@ -24,7 +24,6 @@ def fetch_releases(p: releasedata.Product, c: endoflife.AutoConfig, url: str) ->
p_filter = sys.argv[1] if len(sys.argv) > 1 else None p_filter = sys.argv[1] if len(sys.argv) > 1 else None
for product in endoflife.list_products(METHOD, p_filter): for product in endoflife.list_products(METHOD, p_filter):
product_data = releasedata.Product(product.name) with releasedata.ProductData(product.name) as product_data:
for config in product.get_auto_configs(METHOD): for config in product.get_auto_configs(METHOD):
fetch_releases(product_data, config, f"https://hub.docker.com/v2/repositories/{config.url}/tags?page_size=100&page=1") fetch_releases(product_data, config, f"https://hub.docker.com/v2/repositories/{config.url}/tags?page_size=100&page=1")
product_data.write()

View File

@@ -13,7 +13,7 @@ URLS = [
"https://docs.aws.amazon.com/eks/latest/userguide/platform-versions.html", "https://docs.aws.amazon.com/eks/latest/userguide/platform-versions.html",
] ]
product = releasedata.Product("eks") with releasedata.ProductData("eks") as product_data:
for version_list in http.fetch_urls(URLS): for version_list in http.fetch_urls(URLS):
version_list_soup = BeautifulSoup(version_list.text, features="html5lib") version_list_soup = BeautifulSoup(version_list.text, features="html5lib")
for tr in version_list_soup.select("#main-col-body")[0].findAll("tr"): for tr in version_list_soup.select("#main-col-body")[0].findAll("tr"):
@@ -30,6 +30,4 @@ for version_list in http.fetch_urls(URLS):
date = dates.parse_date(date_str) date = dates.parse_date(date_str)
# K8S patch version is not kept to match versions on https://github.com/aws/eks-distro/tags. # K8S patch version is not kept to match versions on https://github.com/aws/eks-distro/tags.
version = f"{k8s_version_match.group('major')}.{k8s_version_match.group('minor')}-{eks_version.replace('.', '-')}" version = f"{k8s_version_match.group('major')}.{k8s_version_match.group('minor')}-{eks_version.replace('.', '-')}"
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -21,7 +21,7 @@ def batched(iterable: iter, n: int) -> iter:
yield batch yield batch
product = releasedata.Product("firefox") with releasedata.ProductData("firefox") as product_data:
releases_page = http.fetch_url("https://www.mozilla.org/en-US/firefox/releases/") releases_page = http.fetch_url("https://www.mozilla.org/en-US/firefox/releases/")
releases_soup = BeautifulSoup(releases_page.text, features="html5lib") releases_soup = BeautifulSoup(releases_page.text, features="html5lib")
releases_list = releases_soup.find_all("ol", class_="c-release-list") releases_list = releases_soup.find_all("ol", class_="c-release-list")
@@ -35,13 +35,11 @@ for batch_release_notes_urls in batched(release_notes_urls, 20):
date_elt = release_notes_soup.find(class_="c-release-date") date_elt = release_notes_soup.find(class_="c-release-date")
if date_elt: if date_elt:
date = dates.parse_date(date_elt.get_text()) date = dates.parse_date(date_elt.get_text())
product.declare_version(version, date) product_data.declare_version(version, date)
continue continue
date_elt = release_notes_soup.find("small", string=re.compile("^.?First offered")) date_elt = release_notes_soup.find("small", string=re.compile("^.?First offered"))
if date_elt: if date_elt:
date = dates.parse_date(' '.join(date_elt.get_text().split(" ")[-3:])) # get last 3 words date = dates.parse_date(' '.join(date_elt.get_text().split(" ")[-3:])) # get last 3 words
product.declare_version(version, date) product_data.declare_version(version, date)
# versions < 10.0 are ignored # versions < 10.0 are ignored
product.write()

View File

@@ -9,7 +9,7 @@ METHOD = 'git'
p_filter = sys.argv[1] if len(sys.argv) > 1 else None p_filter = sys.argv[1] if len(sys.argv) > 1 else None
for product in endoflife.list_products(METHOD, p_filter): for product in endoflife.list_products(METHOD, p_filter):
product_data = releasedata.Product(product.name) with releasedata.ProductData(product.name) as product_data:
for config in product.get_auto_configs(METHOD): for config in product.get_auto_configs(METHOD):
git = Git(config.url) git = Git(config.url)
git.setup(bare=True) git.setup(bare=True)
@@ -21,5 +21,3 @@ for product in endoflife.list_products(METHOD, p_filter):
version = config.render(version_match) version = config.render(version_match)
date = dates.parse_date(date_str) date = dates.parse_date(date_str)
product_data.declare_version(version, date) product_data.declare_version(version, date)
product_data.write()

View File

@@ -44,7 +44,7 @@ query($endCursor: String) {
p_filter = sys.argv[1] if len(sys.argv) > 1 else None p_filter = sys.argv[1] if len(sys.argv) > 1 else None
for product in endoflife.list_products(METHOD, p_filter): for product in endoflife.list_products(METHOD, p_filter):
product_data = releasedata.Product(product.name) with releasedata.ProductData(product.name) as product_data:
for config in product.get_auto_configs(METHOD): for config in product.get_auto_configs(METHOD):
for page in fetch_releases(config.url): for page in fetch_releases(config.url):
releases = [edge['node'] for edge in (page['data']['repository']['releases']['edges'])] releases = [edge['node'] for edge in (page['data']['repository']['releases']['edges'])]
@@ -57,5 +57,3 @@ for product in endoflife.list_products(METHOD, p_filter):
version = config.render(version_match) version = config.render(version_match)
date = dates.parse_datetime(release['publishedAt']) date = dates.parse_datetime(release['publishedAt'])
product_data.declare_version(version, date) product_data.declare_version(version, date)
product_data.write()

View File

@@ -13,7 +13,7 @@ URL_BY_PRODUCT = {
} }
for product_name, url in URL_BY_PRODUCT.items(): for product_name, url in URL_BY_PRODUCT.items():
product = releasedata.Product(product_name) with releasedata.ProductData(product_name) as product_data:
relnotes = http.fetch_url(url) relnotes = http.fetch_url(url)
relnotes_soup = BeautifulSoup(relnotes.text, features="html5lib") relnotes_soup = BeautifulSoup(relnotes.text, features="html5lib")
@@ -25,6 +25,4 @@ for product_name, url in URL_BY_PRODUCT.items():
for li in next_div.find_all('li'): for li in next_div.find_all('li'):
if "versions are now available" in li.text: if "versions are now available" in li.text:
for version in VERSION_PATTERN.findall(li.find('ul').text): for version in VERSION_PATTERN.findall(li.find('ul').text):
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -1,7 +1,7 @@
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from common import dates, http, releasedata from common import dates, http, releasedata
product = releasedata.Product("graalvm") with releasedata.ProductData("graalvm") as product_data:
release_calendar = http.fetch_url("https://www.graalvm.org/release-calendar/") release_calendar = http.fetch_url("https://www.graalvm.org/release-calendar/")
release_calendar_soup = BeautifulSoup(release_calendar.text, features="html5lib") release_calendar_soup = BeautifulSoup(release_calendar.text, features="html5lib")
@@ -13,6 +13,4 @@ for tr in release_calendar_soup.find("h2", id="previous-releases").find_next("ta
# GraalVM release cycles. Example: GraalVM for JDK 20 and 20.0. # GraalVM release cycles. Example: GraalVM for JDK 20 and 20.0.
versions_str = cells[2].get_text().replace("GraalVM for JDK ", "jdk-") versions_str = cells[2].get_text().replace("GraalVM for JDK ", "jdk-")
for version in versions_str.split(", "): for version in versions_str.split(", "):
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -6,7 +6,7 @@ from common import dates, http, releasedata
CYCLE_PATTERN = re.compile(r"^(\d+\.\d+)/$") CYCLE_PATTERN = re.compile(r"^(\d+\.\d+)/$")
DATE_AND_VERSION_PATTERN = re.compile(r"^(\d{4})/(\d{2})/(\d{2})\s+:\s+(\d+\.\d+\.\d.?)$") # https://regex101.com/r/1JCnFC/1 DATE_AND_VERSION_PATTERN = re.compile(r"^(\d{4})/(\d{2})/(\d{2})\s+:\s+(\d+\.\d+\.\d.?)$") # https://regex101.com/r/1JCnFC/1
product = releasedata.Product("haproxy") with releasedata.ProductData("haproxy") as product_data:
# First, get all minor releases from the download page # First, get all minor releases from the download page
download = http.fetch_url('https://www.haproxy.org/download/') download = http.fetch_url('https://www.haproxy.org/download/')
download_soup = BeautifulSoup(download.text, features="html5lib") download_soup = BeautifulSoup(download.text, features="html5lib")
@@ -27,6 +27,4 @@ for changelog in http.fetch_urls(changelog_urls):
date_and_version_match = DATE_AND_VERSION_PATTERN.match(line) date_and_version_match = DATE_AND_VERSION_PATTERN.match(line)
if date_and_version_match: if date_and_version_match:
year, month, day, version = date_and_version_match.groups() year, month, day, version = date_and_version_match.groups()
product.declare_version(version, dates.date(int(year), int(month), int(day))) product_data.declare_version(version, dates.date(int(year), int(month), int(day)))
product.write()

View File

@@ -7,7 +7,7 @@ URLS = [
"https://www.ibm.com/support/pages/aix-support-lifecycle-information", "https://www.ibm.com/support/pages/aix-support-lifecycle-information",
] ]
product = releasedata.Product("ibm-aix") with releasedata.ProductData("ibm-aix") as product_data:
for page in http.fetch_urls(URLS): for page in http.fetch_urls(URLS):
page_soup = BeautifulSoup(page.text, features="html5lib") page_soup = BeautifulSoup(page.text, features="html5lib")
@@ -16,6 +16,4 @@ for page in http.fetch_urls(URLS):
cells = row.find_all("td") cells = row.find_all("td")
version = cells[0].text.strip("AIX ").replace(' TL', '.') version = cells[0].text.strip("AIX ").replace(' TL', '.')
date = dates.parse_month_year_date(cells[1].text) date = dates.parse_month_year_date(cells[1].text)
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -5,13 +5,11 @@ from requests_html import HTMLSession
Note that requests_html is used because JavaScript is needed to render the page.""" Note that requests_html is used because JavaScript is needed to render the page."""
product = releasedata.Product("jira") with releasedata.ProductData("jira") as product_data:
r = HTMLSession().get("https://www.atlassian.com/software/jira/update") r = HTMLSession().get("https://www.atlassian.com/software/jira/update")
r.html.render(sleep=1, scrolldown=3) r.html.render(sleep=1, scrolldown=3)
for version_block in r.html.find('.versions-list'): for version_block in r.html.find('.versions-list'):
version = version_block.find('a.product-versions', first=True).attrs['data-version'] version = version_block.find('a.product-versions', first=True).attrs['data-version']
date = dates.parse_date(version_block.find('.release-date', first=True).text) date = dates.parse_date(version_block.find('.release-date', first=True).text)
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -10,7 +10,7 @@ from common import dates, http, releasedata
ANNOUNCEMENT_PATTERN = re.compile(r"includes\s+the\s+following\s+changes", re.IGNORECASE) ANNOUNCEMENT_PATTERN = re.compile(r"includes\s+the\s+following\s+changes", re.IGNORECASE)
VERSION_PATTERN = re.compile(r"Looker\s+(?P<version>\d+\.\d+)", re.IGNORECASE) VERSION_PATTERN = re.compile(r"Looker\s+(?P<version>\d+\.\d+)", re.IGNORECASE)
product = releasedata.Product("looker") with releasedata.ProductData("looker") as product_data:
response = http.fetch_url("https://cloud.google.com/feeds/looker-release-notes.xml") response = http.fetch_url("https://cloud.google.com/feeds/looker-release-notes.xml")
rss = xml.dom.minidom.parseString(response.text) rss = xml.dom.minidom.parseString(response.text)
@@ -29,6 +29,4 @@ for item in rss.getElementsByTagName("entry"):
version = version_match.group("version") version = version_match.group("version")
date_str = item.getElementsByTagName("updated")[0].firstChild.nodeValue date_str = item.getElementsByTagName("updated")[0].firstChild.nodeValue
date = dates.parse_datetime(date_str) date = dates.parse_datetime(date_str)
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -7,7 +7,7 @@ METHOD = "maven"
p_filter = sys.argv[1] if len(sys.argv) > 1 else None p_filter = sys.argv[1] if len(sys.argv) > 1 else None
for product in endoflife.list_products(METHOD, p_filter): for product in endoflife.list_products(METHOD, p_filter):
product_data = releasedata.Product(product.name) with releasedata.ProductData(product.name) as product_data:
for config in product.get_auto_configs(METHOD): for config in product.get_auto_configs(METHOD):
start = 0 start = 0
group_id, artifact_id = config.url.split("/") group_id, artifact_id = config.url.split("/")
@@ -26,5 +26,3 @@ for product in endoflife.list_products(METHOD, p_filter):
start += 100 start += 100
if data["response"]["numFound"] <= start: if data["response"]["numFound"] <= start:
break break
product_data.write()

View File

@@ -6,7 +6,7 @@ METHOD = "npm"
p_filter = sys.argv[1] if len(sys.argv) > 1 else None p_filter = sys.argv[1] if len(sys.argv) > 1 else None
for product in endoflife.list_products(METHOD, p_filter): for product in endoflife.list_products(METHOD, p_filter):
product_data = releasedata.Product(product.name) with releasedata.ProductData(product.name) as product_data:
for config in product.get_auto_configs(METHOD): for config in product.get_auto_configs(METHOD):
data = http.fetch_url(f"https://registry.npmjs.org/{config.url}").json() data = http.fetch_url(f"https://registry.npmjs.org/{config.url}").json()
for version_str in data["versions"]: for version_str in data["versions"]:
@@ -15,5 +15,3 @@ for product in endoflife.list_products(METHOD, p_filter):
version = config.render(version_match) version = config.render(version_match)
date = dates.parse_datetime(data["time"][version_str]) date = dates.parse_datetime(data["time"][version_str])
product_data.declare_version(version, date) product_data.declare_version(version, date)
product_data.write()

View File

@@ -9,13 +9,10 @@ PRODUCTS = {
} }
for product_name, url in PRODUCTS.items(): for product_name, url in PRODUCTS.items():
product = releasedata.Product(product_name) with releasedata.ProductData(product_name) as product_data:
data = http.fetch_url(url).json() data = http.fetch_url(url).json()
for version_data in data["contents"]: for version_data in data["contents"]:
if 'GENERAL_AVAILABILITY' in version_data: if 'GENERAL_AVAILABILITY' in version_data:
version = version_data["version"] version = version_data["version"]
date = dates.parse_datetime(version_data["GENERAL_AVAILABILITY"]) date = dates.parse_datetime(version_data["GENERAL_AVAILABILITY"])
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -5,7 +5,7 @@ from requests_html import HTMLSession
This script is using requests-html because the page needs JavaScript to render correctly.""" This script is using requests-html because the page needs JavaScript to render correctly."""
product = releasedata.Product("oracle-jdk") with releasedata.ProductData("oracle-jdk") as product_data:
r = HTMLSession().get('https://www.java.com/releases/') r = HTMLSession().get('https://www.java.com/releases/')
r.html.render(sleep=1, scrolldown=3) r.html.render(sleep=1, scrolldown=3)
@@ -16,8 +16,7 @@ for row in r.html.find('#released tr'):
version = version_cell.attrs['id'] version = version_cell.attrs['id']
date_str = row.find('td')[1].text date_str = row.find('td')[1].text
date = dates.parse_date(date_str) if date_str else previous_date date = dates.parse_date(date_str) if date_str else previous_date
product.declare_version(version, date) product_data.declare_version(version, date)
previous_date = date previous_date = date
product.remove_version('1.0_alpha') # the only version we don't want, a regex is not needed product_data.remove_version('1.0_alpha') # the only version we don't want, a regex is not needed
product.write()

View File

@@ -17,7 +17,7 @@ soup = BeautifulSoup(response.text, features="html5lib")
logging.info("::endgroup::") logging.info("::endgroup::")
for product_name, identifier in IDENTIFIERS_BY_PRODUCT.items(): for product_name, identifier in IDENTIFIERS_BY_PRODUCT.items():
product = releasedata.Product(product_name) with releasedata.ProductData(product_name) as product_data:
table = soup.find(id=identifier) table = soup.find(id=identifier)
for tr in table.findAll("tr")[3:]: for tr in table.findAll("tr")[3:]:
td_list = tr.findAll("td") td_list = tr.findAll("td")
@@ -33,6 +33,4 @@ for product_name, identifier in IDENTIFIERS_BY_PRODUCT.items():
date_str = re.sub(r'(\w+) (\d{1,2})\w{2}, (\d{4})', r'\1 \2, \3', td_list[1].get_text()) date_str = re.sub(r'(\w+) (\d{1,2})\w{2}, (\d{4})', r'\1 \2, \3', td_list[1].get_text())
date = dates.parse_date(date_str) date = dates.parse_date(date_str)
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -2,8 +2,7 @@ from common import dates, endoflife, http, releasedata
MAIN_URL = "https://www.php.net/releases/index.php?json&max=-1" MAIN_URL = "https://www.php.net/releases/index.php?json&max=-1"
product = releasedata.Product("php") with releasedata.ProductData("php") as product_data:
# Fetch major versions # Fetch major versions
latest_by_major = http.fetch_url(MAIN_URL).json() latest_by_major = http.fetch_url(MAIN_URL).json()
major_version_urls = [f"{MAIN_URL}&version={major_version}" for major_version in latest_by_major] major_version_urls = [f"{MAIN_URL}&version={major_version}" for major_version in latest_by_major]
@@ -14,6 +13,4 @@ for major_versions_response in http.fetch_urls(major_version_urls):
for version in major_versions_data: for version in major_versions_data:
if endoflife.DEFAULT_VERSION_PATTERN.match(version): # exclude versions such as "3.0.x (latest)" if endoflife.DEFAULT_VERSION_PATTERN.match(version): # exclude versions such as "3.0.x (latest)"
date = dates.parse_date(major_versions_data[version]["date"]) date = dates.parse_date(major_versions_data[version]["date"])
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -6,7 +6,7 @@ from common import dates, http, releasedata
Only 18.0.20.3 and later will be picked up, as the format of the change log for 18.0.20 and 18.0.19 are different and Only 18.0.20.3 and later will be picked up, as the format of the change log for 18.0.20 and 18.0.19 are different and
there is no entry for GA of version 18.0.18 and older.""" there is no entry for GA of version 18.0.18 and older."""
product = releasedata.Product("plesk") with releasedata.ProductData("plesk") as product_data:
response = http.fetch_url("https://docs.plesk.com/release-notes/obsidian/change-log") response = http.fetch_url("https://docs.plesk.com/release-notes/obsidian/change-log")
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -20,6 +20,4 @@ for release in soup.find_all("div", class_="changelog-entry--obsidian"):
continue continue
date = dates.parse_date(release.p.text) date = dates.parse_date(release.p.text)
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -6,7 +6,7 @@ METHOD = "pypi"
p_filter = sys.argv[1] if len(sys.argv) > 1 else None p_filter = sys.argv[1] if len(sys.argv) > 1 else None
for product in endoflife.list_products(METHOD, p_filter): for product in endoflife.list_products(METHOD, p_filter):
product_data = releasedata.Product(product.name) with releasedata.ProductData(product.name) as product_data:
for config in product.get_auto_configs(METHOD): for config in product.get_auto_configs(METHOD):
data = http.fetch_url(f"https://pypi.org/pypi/{config.url}/json").json() data = http.fetch_url(f"https://pypi.org/pypi/{config.url}/json").json()
@@ -18,5 +18,3 @@ for product in endoflife.list_products(METHOD, p_filter):
version = config.render(version_match) version = config.render(version_match)
date = dates.parse_datetime(version_data[0]["upload_time_iso_8601"]) date = dates.parse_datetime(version_data[0]["upload_time_iso_8601"])
product_data.declare_version(version, date) product_data.declare_version(version, date)
product_data.write()

View File

@@ -16,7 +16,7 @@ PRODUCTS = {
VERSION_REGEX = re.compile(r"(?P<version>\d+(?:\.\d+)*)", flags=re.IGNORECASE) # https://regex101.com/r/BY1vwV/1 VERSION_REGEX = re.compile(r"(?P<version>\d+(?:\.\d+)*)", flags=re.IGNORECASE) # https://regex101.com/r/BY1vwV/1
for product_name, url in PRODUCTS.items(): for product_name, url in PRODUCTS.items():
product = releasedata.Product(product_name) with releasedata.ProductData(product_name) as product_data:
response = http.fetch_url(url) response = http.fetch_url(url)
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -30,6 +30,4 @@ for product_name, url in PRODUCTS.items():
if version_match: if version_match:
version = version_match.group("version") version = version_match.group("version")
date = dates.parse_date(columns[2].text) date = dates.parse_date(columns[2].text)
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -7,7 +7,7 @@ from common.git import Git
VERSION_AND_DATE_PATTERN = re.compile(r"{product-title}\s(?P<version>\d+\.\d+\.\d+).*\n+Issued:\s(?P<date>\d{4}-\d\d-\d\d)$", re.MULTILINE) VERSION_AND_DATE_PATTERN = re.compile(r"{product-title}\s(?P<version>\d+\.\d+\.\d+).*\n+Issued:\s(?P<date>\d{4}-\d\d-\d\d)$", re.MULTILINE)
product = releasedata.Product("red-hat-openshift") with releasedata.ProductData("red-hat-openshift") as product_data:
git = Git("https://github.com/openshift/openshift-docs.git") git = Git("https://github.com/openshift/openshift-docs.git")
git.setup() git.setup()
@@ -24,6 +24,4 @@ for branch in git.list_branches("refs/heads/enterprise-[4-9]*"):
with release_notes_file.open("rb") as f: with release_notes_file.open("rb") as f:
content = f.read().decode("utf-8") content = f.read().decode("utf-8")
for (version, date_str) in VERSION_AND_DATE_PATTERN.findall(content): for (version, date_str) in VERSION_AND_DATE_PATTERN.findall(content):
product.declare_version(version, dates.parse_date(date_str)) product_data.declare_version(version, dates.parse_date(date_str))
product.write()

View File

@@ -10,7 +10,7 @@ A few of the older versions, such as 'Satellite 6.1 GA Release (Build 6.1.1)', w
# https://regex101.com/r/m8aWXG/1 # https://regex101.com/r/m8aWXG/1
VERSION_PATTERN = re.compile(r"^Satellite (?P<version>\d+\.\d+\.\d+([.-]\d+)?) ([Uu]pdate|[Rr]elease)$") VERSION_PATTERN = re.compile(r"^Satellite (?P<version>\d+\.\d+\.\d+([.-]\d+)?) ([Uu]pdate|[Rr]elease)$")
product = releasedata.Product("redhat-satellite") with releasedata.ProductData("redhat-satellite") as product_data:
response = http.fetch_url("https://access.redhat.com/articles/1365633") response = http.fetch_url("https://access.redhat.com/articles/1365633")
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -23,6 +23,4 @@ for table in soup.findAll("tbody"):
if version_match: if version_match:
version = version_match["version"].replace('-', '.') # a.b.c-d => a.b.c.d version = version_match["version"].replace('-', '.') # a.b.c-d => a.b.c.d
date = dates.parse_date(td_list[1].get_text().strip()) date = dates.parse_date(td_list[1].get_text().strip())
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -6,7 +6,7 @@ from common import dates, http, releasedata
# https://regex101.com/r/877ibq/1 # https://regex101.com/r/877ibq/1
VERSION_PATTERN = re.compile(r"RHEL (?P<major>\d)(\. ?(?P<minor>\d+))?(( Update (?P<minor2>\d))| GA)?") VERSION_PATTERN = re.compile(r"RHEL (?P<major>\d)(\. ?(?P<minor>\d+))?(( Update (?P<minor2>\d))| GA)?")
product = releasedata.Product("redhat") with releasedata.ProductData("redhat") as product_data:
response = http.fetch_url("https://access.redhat.com/articles/3078") response = http.fetch_url("https://access.redhat.com/articles/3078")
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -21,7 +21,4 @@ for tr in soup.findAll("tr"):
version += ("." + version_match["minor"]) if version_match["minor"] else "" version += ("." + version_match["minor"]) if version_match["minor"] else ""
version += ("." + version_match["minor2"]) if version_match["minor2"] else "" version += ("." + version_match["minor2"]) if version_match["minor2"] else ""
date = dates.parse_date(td_list[1].get_text()) date = dates.parse_date(td_list[1].get_text())
product_data.declare_version(version, date)
product.declare_version(version, date)
product.write()

View File

@@ -1,13 +1,10 @@
from common import dates, endoflife, http, releasedata from common import dates, endoflife, http, releasedata
product = releasedata.Product("rockylinux") with releasedata.ProductData("rockylinux") as product_data:
response = http.fetch_url("https://raw.githubusercontent.com/rocky-linux/wiki.rockylinux.org/development/docs/include/releng/version_table.md") response = http.fetch_url("https://raw.githubusercontent.com/rocky-linux/wiki.rockylinux.org/development/docs/include/releng/version_table.md")
for line in response.text.strip().split('\n'): for line in response.text.strip().split('\n'):
items = line.split('|') items = line.split('|')
if len(items) >= 5 and endoflife.DEFAULT_VERSION_PATTERN.match(items[1].strip()): if len(items) >= 5 and endoflife.DEFAULT_VERSION_PATTERN.match(items[1].strip()):
version = items[1].strip() version = items[1].strip()
date = dates.parse_date(items[3]) date = dates.parse_date(items[3])
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -6,7 +6,7 @@ from common import dates, http, releasedata
# https://regex101.com/r/c1ribd/1 # https://regex101.com/r/c1ribd/1
VERSION_PATTERN = re.compile(r"^ROS (?P<name>(\w| )+)") VERSION_PATTERN = re.compile(r"^ROS (?P<name>(\w| )+)")
product = releasedata.Product("ros") with releasedata.ProductData("ros") as product_data:
response = http.fetch_url("https://wiki.ros.org/Distributions") response = http.fetch_url("https://wiki.ros.org/Distributions")
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -25,6 +25,4 @@ for tr in soup.findAll("tr"):
x = td_list[1].get_text().split(",") x = td_list[1].get_text().split(",")
date = dates.parse_date(x[0][:-2] + x[1]) date = dates.parse_date(x[0][:-2] + x[1])
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -3,7 +3,7 @@ import logging
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from common import dates, http, releasedata from common import dates, http, releasedata
product = releasedata.Product("sles") with releasedata.ProductData("sles") as product_data:
response = http.fetch_url("https://www.suse.com/lifecycle") response = http.fetch_url("https://www.suse.com/lifecycle")
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -25,8 +25,6 @@ for detail_id in [f"detail{row['id']}" for row in sles_header_rows]:
try: try:
date = dates.parse_date(date_str) date = dates.parse_date(date_str)
product.declare_version(version, date) product_data.declare_version(version, date)
except ValueError: except ValueError:
logging.info(f"Ignoring {version}: date '{date_str}' could not be parsed") logging.info(f"Ignoring {version}: date '{date_str}' could not be parsed")
product.write()

View File

@@ -30,7 +30,7 @@ def get_latest_minor_versions(versions: list[str]) -> list[str]:
return latest_versions return latest_versions
product = releasedata.Product("splunk") with releasedata.ProductData("splunk") as product_data:
main = http.fetch_url("https://docs.splunk.com/Documentation/Splunk") main = http.fetch_url("https://docs.splunk.com/Documentation/Splunk")
soup = BeautifulSoup(main.text, features="html5lib") soup = BeautifulSoup(main.text, features="html5lib")
@@ -44,6 +44,4 @@ for response in http.fetch_urls(latest_minor_versions_urls):
for (version_str, date_str) in VERSION_DATE_PATTERN.findall(response.text): for (version_str, date_str) in VERSION_DATE_PATTERN.findall(response.text):
version_str = f"{version_str}.0" if len(version_str.split(".")) == 2 else version_str # convert x.y to x.y.0 version_str = f"{version_str}.0" if len(version_str.split(".")) == 2 else version_str # convert x.y to x.y.0
date = dates.parse_date(date_str) date = dates.parse_date(date_str)
product.declare_version(version_str, date) product_data.declare_version(version_str, date)
product.write()

View File

@@ -1,6 +1,6 @@
from common import dates, http, releasedata from common import dates, http, releasedata
product = releasedata.Product("typo3") with releasedata.ProductData("typo3") as product_data:
data = http.fetch_url("https://get.typo3.org/api/v1/release/").json() data = http.fetch_url("https://get.typo3.org/api/v1/release/").json()
for v in data: for v in data:
if v['type'] == 'development': if v['type'] == 'development':
@@ -8,6 +8,4 @@ for v in data:
version = v["version"] version = v["version"]
date = dates.parse_datetime(v["date"], to_utc=False) # utc kept for now for backwards compatibility date = dates.parse_datetime(v["date"], to_utc=False) # utc kept for now for backwards compatibility
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -9,9 +9,8 @@ from common import dates, http, releasedata
BASE_URL = "https://unity.com/releases/editor/qa/lts-releases" BASE_URL = "https://unity.com/releases/editor/qa/lts-releases"
product = releasedata.Product("unity")
next_page_url = BASE_URL next_page_url = BASE_URL
with releasedata.ProductData("unity") as product_data:
# Do not try to fetch multiple pages in parallel: it is raising a lot of errors and make the overall process slower. # Do not try to fetch multiple pages in parallel: it is raising a lot of errors and make the overall process slower.
while next_page_url: while next_page_url:
response = http.fetch_url(next_page_url) response = http.fetch_url(next_page_url)
@@ -20,9 +19,7 @@ while next_page_url:
for release in soup.find_all('div', class_='component-releases-item__show__inner-header'): for release in soup.find_all('div', class_='component-releases-item__show__inner-header'):
version = release.find('h4').find('span').text version = release.find('h4').find('span').text
date = dates.parse_datetime(release.find('time').attrs['datetime']) date = dates.parse_datetime(release.find('time').attrs['datetime'])
product.declare_version(version, date) product_data.declare_version(version, date)
next_link = soup.find('a', {"rel": "next"}) next_link = soup.find('a', {"rel": "next"})
next_page_url = BASE_URL + next_link.attrs['href'] if next_link else None next_page_url = BASE_URL + next_link.attrs['href'] if next_link else None
product.write()

View File

@@ -5,7 +5,7 @@ from common import dates, endoflife, http, releasedata
DATE_PATTERN = re.compile(r"\d{4}-\d{2}-\d{2}") DATE_PATTERN = re.compile(r"\d{4}-\d{2}-\d{2}")
product = releasedata.Product("unrealircd") with releasedata.ProductData("unrealircd") as product_data:
response = http.fetch_url("https://www.unrealircd.org/docwiki/index.php?title=History_of_UnrealIRCd_releases&action=raw") response = http.fetch_url("https://www.unrealircd.org/docwiki/index.php?title=History_of_UnrealIRCd_releases&action=raw")
wikicode = mwparserfromhell.parse(response.text) wikicode = mwparserfromhell.parse(response.text)
@@ -18,6 +18,4 @@ for tr in wikicode.ifilter_tags(matches=lambda node: node.tag == "tr"):
date_str = items[1].__strip__() date_str = items[1].__strip__()
if endoflife.DEFAULT_VERSION_PATTERN.match(version) and DATE_PATTERN.match(date_str): if endoflife.DEFAULT_VERSION_PATTERN.match(version) and DATE_PATTERN.match(date_str):
date = dates.parse_date(date_str) date = dates.parse_date(date_str)
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -5,7 +5,7 @@ from common import dates, http, releasedata
"""Fetches Veeam versions from https://www.veeam.com.""" """Fetches Veeam versions from https://www.veeam.com."""
product = releasedata.Product("veeam-backup-and-replication") with releasedata.ProductData("veeam-backup-and-replication") as product_data:
response = http.fetch_url("https://www.veeam.com/kb2680") response = http.fetch_url("https://www.veeam.com/kb2680")
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -26,6 +26,4 @@ for table in soup.find_all("table"):
# whitespaces in version numbers are replaced with dashes # whitespaces in version numbers are replaced with dashes
version = re.sub(r'\s+', "-", cells[version_index].get_text().strip()) version = re.sub(r'\s+', "-", cells[version_index].get_text().strip())
date = dates.parse_date(date_str) date = dates.parse_date(date_str)
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()

View File

@@ -9,7 +9,7 @@ URLS = [
"https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-history", "https://learn.microsoft.com/en-us/visualstudio/releases/2022/release-history",
] ]
product = releasedata.Product("visualstudio") with releasedata.ProductData("visualstudio") as product_data:
for response in http.fetch_urls(URLS): for response in http.fetch_urls(URLS):
soup = BeautifulSoup(response.text, features="html5lib") soup = BeautifulSoup(response.text, features="html5lib")
@@ -30,6 +30,4 @@ for response in http.fetch_urls(URLS):
date = dates.parse_date(date) date = dates.parse_date(date)
if date and version and endoflife.DEFAULT_VERSION_PATTERN.match(version): if date and version and endoflife.DEFAULT_VERSION_PATTERN.match(version):
product.declare_version(version, date) product_data.declare_version(version, date)
product.write()