Update release-data JSON file format (#274)
This makes the format open for extension, such as adding release cycle level data (such as EOL dates). Version data is still accessible by the version's name. While this repeats the version name, it's also much more convenient for users of those data. A few other things have also been updated in the process: - verbosity of the diff has been increased in update.py to make workflow summaries more readable, - dates without timezone are now set to UTC by default (this was already supposed, so no impact expected here).
This commit is contained in:
@@ -44,8 +44,14 @@ def parse_datetime(text: str, formats: list[str] = frozenset([
|
||||
text = text.strip().replace(", ", " ").replace(". ", " ").replace("(", "").replace(")", "")
|
||||
for fmt in formats:
|
||||
try:
|
||||
date = datetime.strptime(text, fmt) # NOQA: DTZ007, timezone is handled below
|
||||
return date.astimezone(timezone.utc) if to_utc else date
|
||||
dt = datetime.strptime(text, fmt) # NOQA: DTZ007, timezone is handled below
|
||||
|
||||
if to_utc:
|
||||
dt = dt.astimezone(timezone.utc)
|
||||
elif dt.tzinfo is None:
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
|
||||
return dt
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
@@ -74,11 +74,33 @@ class ProductFrontmatter:
|
||||
return None
|
||||
|
||||
|
||||
class ProductVersion:
|
||||
def __init__(self, product: "Product", name: str, date: datetime) -> None:
|
||||
self.product = str(product)
|
||||
self.name = name
|
||||
self.date = date
|
||||
|
||||
@staticmethod
|
||||
def from_json(product: "Product", data: dict) -> "ProductVersion":
|
||||
name = data["name"]
|
||||
date = datetime.strptime(data["date"], "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
||||
return ProductVersion(product, name, date)
|
||||
|
||||
def __dict__(self) -> dict:
|
||||
return {
|
||||
"name": self.name,
|
||||
"date": self.date.strftime("%Y-%m-%d"),
|
||||
}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.product}#{self.name} ({self.date})"
|
||||
|
||||
|
||||
class Product:
|
||||
def __init__(self, name: str) -> None:
|
||||
self.name: str = name
|
||||
self.versions_path: Path = VERSIONS_PATH / f"{name}.json"
|
||||
self.versions = {}
|
||||
self.versions: dict[str, ProductVersion] = {}
|
||||
logging.info(f"::group::{self}")
|
||||
|
||||
@staticmethod
|
||||
@@ -87,9 +109,9 @@ class Product:
|
||||
|
||||
if product.versions_path.is_file():
|
||||
with product.versions_path.open() as f:
|
||||
for version, date in json.load(f).items():
|
||||
date_obj = datetime.strptime(date, "%Y-%m-%d").replace(tzinfo=timezone.utc)
|
||||
product.versions[version] = date_obj
|
||||
for json_version in json.load(f)["versions"].values():
|
||||
version = ProductVersion.from_json(product, json_version)
|
||||
product.versions[version.name] = version
|
||||
logging.info(f"loaded versions data for {product} from {product.versions_path}")
|
||||
else:
|
||||
logging.warning(f"no versions data found for {product} at {product.versions_path}")
|
||||
@@ -100,17 +122,17 @@ class Product:
|
||||
return version in self.versions
|
||||
|
||||
def get_version_date(self, version: str) -> datetime:
|
||||
return self.versions[version] if version in self.versions else None
|
||||
return self.versions[version].date if version in self.versions else None
|
||||
|
||||
def declare_version(self, version: str, date: datetime) -> None:
|
||||
if version in self.versions:
|
||||
if self.versions[version] != date:
|
||||
logging.warning(f"overwriting version {version} ({self.versions[version]} -> {date}) for {self}")
|
||||
if self.versions[version].date != date:
|
||||
logging.warning(f"overwriting {version} ({self.get_version_date(version)} -> {date}) for {self}")
|
||||
else:
|
||||
return # already declared
|
||||
|
||||
logging.info(f"adding version {version} ({date}) to {self}")
|
||||
self.versions[version] = date
|
||||
self.versions[version] = ProductVersion(self, version, date)
|
||||
|
||||
def declare_versions(self, dates_by_version: dict[str, datetime]) -> None:
|
||||
for (version, date) in dates_by_version.items():
|
||||
@@ -121,8 +143,8 @@ class Product:
|
||||
msg = f"version {version} cannot be replaced as it does not exist for {self}"
|
||||
raise ValueError(msg)
|
||||
|
||||
logging.info(f"replacing version {version} ({self.versions[version]} -> {date}) in {self}")
|
||||
self.versions[version] = date
|
||||
logging.info(f"replacing version {version} ({self.get_version_date(version)} -> {date}) in {self}")
|
||||
self.versions[version].date = date
|
||||
|
||||
def remove_version(self, version: str) -> None:
|
||||
if not self.has_version(version):
|
||||
@@ -132,12 +154,12 @@ class Product:
|
||||
logging.info(f"removing version {version} ({self.versions.pop(version)}) from {self}")
|
||||
|
||||
def write(self) -> None:
|
||||
versions = {version: date.strftime("%Y-%m-%d") for version, date in self.versions.items()}
|
||||
# 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(dict(
|
||||
# sort by date then version (desc)
|
||||
sorted(versions.items(), key=lambda x: (x[1], x[0]), reverse=True),
|
||||
), indent=2))
|
||||
f.write(json.dumps({
|
||||
"versions": {version.name: version.__dict__() for version in ordered_versions},
|
||||
}, indent=2))
|
||||
logging.info("::endgroup::")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
|
||||
Reference in New Issue
Block a user