Skip to content

Commit

Permalink
Merge pull request #470 from lsst-sqre/tickets/DM-48682-dates
Browse files Browse the repository at this point in the history
Add date and age fields to RSPTag
  • Loading branch information
athornton authored Feb 6, 2025
2 parents 4fed99b + 0d5f8ee commit a91b08a
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 0 deletions.
3 changes: 3 additions & 0 deletions controller/src/controller/models/domain/rspimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ def resolve_alias(self, target: RSPImage) -> None:
self.aliases.add(alias)
target.aliases.add(self.tag)
self.cycle = target.cycle
# If we don't have a date, adopt the target's (which might be None too)
if self.date is None:
self.date = target.date

# If the tag display name has cycle information, we don't want to keep
# that part when adding the description of the target tag since it
Expand Down
59 changes: 59 additions & 0 deletions controller/src/controller/models/domain/rsptag.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from collections import defaultdict
from collections.abc import Iterable, Iterator
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from enum import Enum
from functools import total_ordering
from typing import Self
Expand Down Expand Up @@ -130,6 +131,17 @@ class RSPImageTag:
display_name: str
"""Human-readable display name."""

date: datetime | None
"""When the image was created, or as close as we can get to that.
We try to derive this from the tag string: For RSP daily or weekly
tags (or experimentals in one of those formats), we can calculate
this to within a day or a week, which is good enough for display
purposes. Otherwise, we may be able to extract this info from
the registry, but even if we can, it may be image upload time
rather than creation time.
"""

@classmethod
def alias(cls, tag: str) -> Self:
"""Create an alias tag.
Expand Down Expand Up @@ -157,6 +169,7 @@ def alias(cls, tag: str) -> Self:
version=None,
cycle=cycle,
display_name=display_name,
date=None,
)

@classmethod
Expand Down Expand Up @@ -192,6 +205,7 @@ def from_str(cls, tag: str) -> Self:
tag=tag,
cycle=None,
display_name=tag,
date=None,
)

def __eq__(self, other: object) -> bool:
Expand Down Expand Up @@ -241,6 +255,7 @@ def _from_match(
tag=tag,
cycle=int(cycle) if cycle else None,
display_name=display_name,
date=None,
)

# Experimental tags are often exp_<legal-tag>, meaning that they are
Expand All @@ -264,6 +279,7 @@ def _from_match(
tag=tag,
cycle=subtag.cycle,
display_name=display_name,
date=subtag.date,
)

# Determine the build number, the last component of the semantic
Expand Down Expand Up @@ -312,6 +328,7 @@ def _from_match(
tag=tag,
cycle=int(cycle) if cycle else None,
display_name=display_name,
date=cls._calculate_date(data),
)

@classmethod
Expand Down Expand Up @@ -349,6 +366,48 @@ def _determine_build(
else:
return rest if rest else None

@staticmethod
def _calculate_date(tagdata: dict[str, str]) -> datetime | None:
"""Calculate the date when the image should have been created.
Parameters
----------
tagdata
The match groups from the regular expression tag match.
Returns
-------
datetime.datetime | None
The image creation date if it can be gleaned from the tag.
"""
year = tagdata.get("year")
if not year:
return None
week = tagdata.get("week")
if year and week:
thursday = 4 # We build on Thursday, which is ISO day 4
stamp = datetime.fromisocalendar(int(year), int(week), thursday)
return stamp.replace(tzinfo=UTC)
month = tagdata.get("month")
day = tagdata.get("day")
if not (month and day):
return None
return datetime(int(year), int(month), int(day), tzinfo=UTC)

def age(self, since: datetime | None = None) -> timedelta | None:
"""Calculate image age, if possible.
Parameters
----------
since
Datestamp to measure age from. If unspecified, the present.
"""
if self.date is None:
return None
if since is None:
since = datetime.now(tz=UTC)
return since - self.date

def _compare(self, other: object) -> int:
"""Compare to image tags for sorting purposes.
Expand Down
7 changes: 7 additions & 0 deletions controller/tests/models/domain/rspimage_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import os
from dataclasses import asdict
from datetime import UTC, datetime
from random import SystemRandom
from typing import cast

Expand Down Expand Up @@ -38,6 +39,7 @@ def test_image() -> None:
"display_name": "Daily 2077_10_23",
"version": VersionInfo(2077, 10, 23),
"cycle": None,
"date": datetime(2077, 10, 23, tzinfo=UTC),
"registry": "lighthouse.ceres",
"repository": "library/sketchbook",
"digest": "sha256:1234",
Expand All @@ -64,6 +66,7 @@ def test_resolve_alias() -> None:
digest="sha256:1234",
)
assert image.cycle == 45
assert image.date == datetime(2077, 10, 23, tzinfo=UTC)
recommended = RSPImage.from_tag(
registry="lighthouse.ceres",
repository="library/sketchbook",
Expand All @@ -80,6 +83,7 @@ def test_resolve_alias() -> None:
assert recommended.display_name == "recommended"
assert recommended.alias_target is None
assert recommended.cycle is None
assert recommended.date is None
assert recommended.is_possible_alias

recommended.resolve_alias(image)
Expand All @@ -90,6 +94,7 @@ def test_resolve_alias() -> None:
"Recommended (Daily 2077_10_23, SAL Cycle 0045, Build 003)"
)
assert recommended.cycle == 45
assert recommended.date == datetime(2077, 10, 23, tzinfo=UTC)
assert recommended.is_possible_alias

# Can do the same thing with a tag that's already an alias.
Expand All @@ -101,6 +106,7 @@ def test_resolve_alias() -> None:
)
assert latest_daily.image_type == RSPImageType.ALIAS
assert latest_daily.display_name == "Latest Daily (SAL Cycle 0045)"
assert latest_daily.date is None

latest_daily.resolve_alias(image)
assert latest_daily.image_type == RSPImageType.ALIAS
Expand All @@ -110,6 +116,7 @@ def test_resolve_alias() -> None:
assert latest_daily.display_name == (
"Latest Daily (Daily 2077_10_23, SAL Cycle 0045, Build 003)"
)
assert latest_daily.date == datetime(2077, 10, 23, tzinfo=UTC)

# Can't resolve some other image type.
with pytest.raises(ValueError, match=r"Can only resolve.*"):
Expand Down
Loading

0 comments on commit a91b08a

Please sign in to comment.