Skip to content
Snippets Groups Projects
Commit af5e4614 authored by Nicolas Dandrimont's avatar Nicolas Dandrimont
Browse files

Truncate RawExtrinsicMetadata.discovery_date to a second

This truncation is already enshrined at the identifier level. Truncate
the object itself as well, to reduce the possibility multiple different
metadata objects with the same identifier.
parent 975e9892
No related branches found
Tags v2.3.0
No related merge requests found
......@@ -885,6 +885,17 @@ class MetadataFetcher(BaseModel):
return {"name": self.name, "version": self.version}
def normalize_discovery_date(value: Any) -> datetime.datetime:
if not isinstance(value, datetime.datetime):
raise TypeError("discovery_date must be a timezone-aware datetime.")
if value.tzinfo is None:
raise ValueError("discovery_date must be a timezone-aware datetime.")
# Normalize timezone to utc, and truncate microseconds to 0
return value.astimezone(datetime.timezone.utc).replace(microsecond=0)
@attr.s(frozen=True, slots=True)
class RawExtrinsicMetadata(HashableObject, BaseModel):
object_type: Final = "raw_extrinsic_metadata"
......@@ -893,7 +904,7 @@ class RawExtrinsicMetadata(HashableObject, BaseModel):
target = attr.ib(type=ExtendedSWHID, validator=type_validator())
# source
discovery_date = attr.ib(type=datetime.datetime, validator=type_validator())
discovery_date = attr.ib(type=datetime.datetime, converter=normalize_discovery_date)
authority = attr.ib(type=MetadataAuthority, validator=type_validator())
fetcher = attr.ib(type=MetadataFetcher, validator=type_validator())
......@@ -923,12 +934,6 @@ class RawExtrinsicMetadata(HashableObject, BaseModel):
def compute_hash(self) -> bytes:
return hash_to_bytes(raw_extrinsic_metadata_identifier(self.to_dict()))
@discovery_date.validator
def check_discovery_date(self, attribute, value):
"""Checks the discovery_date has a timezone."""
if value is not None and value.tzinfo is None:
raise ValueError("discovery_date must be a timezone-aware datetime.")
@origin.validator
def check_origin(self, attribute, value):
if value is None:
......
......@@ -1140,3 +1140,39 @@ def test_metadata_validate_context_directory():
),
**_common_metadata_fields,
)
def test_metadata_normalize_discovery_date():
fields_copy = {**_common_metadata_fields}
truncated_date = fields_copy.pop("discovery_date")
assert truncated_date.microsecond == 0
# Check for TypeError on disabled object type: we removed attrs_strict's
# type_validator
with pytest.raises(TypeError):
RawExtrinsicMetadata(
target=_content_swhid, discovery_date="not a datetime", **fields_copy
)
# Check for truncation to integral second
date_with_us = truncated_date.replace(microsecond=42)
md = RawExtrinsicMetadata(
target=_content_swhid, discovery_date=date_with_us, **fields_copy,
)
assert md.discovery_date == truncated_date
assert md.discovery_date.tzinfo == datetime.timezone.utc
# Check that the timezone gets normalized. Timezones can be offset by a
# non-integral number of seconds, so we need to handle that.
timezone = datetime.timezone(offset=datetime.timedelta(hours=2))
date_with_tz = truncated_date.astimezone(timezone)
assert date_with_tz.tzinfo != datetime.timezone.utc
md = RawExtrinsicMetadata(
target=_content_swhid, discovery_date=date_with_tz, **fields_copy,
)
assert md.discovery_date == truncated_date
assert md.discovery_date.tzinfo == datetime.timezone.utc
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment