diff --git a/PKG-INFO b/PKG-INFO index 5de3f064505bd964308ad99af07b5387d6c0dca1..c27c44f2a7804cc3922dfd614a561d9d92595adf 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: swh.model -Version: 6.3.1 +Version: 6.4.0 Summary: Software Heritage data model Home-page: https://forge.softwareheritage.org/diffusion/DMOD/ Author: Software Heritage developers diff --git a/swh.model.egg-info/PKG-INFO b/swh.model.egg-info/PKG-INFO index 5de3f064505bd964308ad99af07b5387d6c0dca1..c27c44f2a7804cc3922dfd614a561d9d92595adf 100644 --- a/swh.model.egg-info/PKG-INFO +++ b/swh.model.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: swh.model -Version: 6.3.1 +Version: 6.4.0 Summary: Software Heritage data model Home-page: https://forge.softwareheritage.org/diffusion/DMOD/ Author: Software Heritage developers diff --git a/swh/model/git_objects.py b/swh/model/git_objects.py index 41be6f2ea77ed6304e9435ed72b47a7281f8d9df..cf258370efdaa7011dc77a10bb247885628509d8 100644 --- a/swh/model/git_objects.py +++ b/swh/model/git_objects.py @@ -412,7 +412,9 @@ def release_git_object(release: Union[Dict, model.Release]) -> bytes: return format_git_object_from_headers("tag", headers, release.message) -def snapshot_git_object(snapshot: Union[Dict, model.Snapshot]) -> bytes: +def snapshot_git_object( + snapshot: Union[Dict, model.Snapshot], *, ignore_unresolved: bool = False +) -> bytes: """Formats a snapshot as a git-like object. Snapshots are a set of named branches, which are pointers to objects at any @@ -456,6 +458,10 @@ def snapshot_git_object(snapshot: Union[Dict, model.Snapshot]) -> bytes: Note that, akin to directory manifests, there is no separator between entries. Because of symbolic branches, identifiers are of arbitrary length but are length-encoded to avoid ambiguity. + + Args: + ignore_unresolved: if False (the default), raises an exception when + alias branches point to non-existing branches """ if isinstance(snapshot, dict): # For backward compatibility @@ -495,7 +501,7 @@ def snapshot_git_object(snapshot: Union[Dict, model.Snapshot]) -> bytes: ] ) - if unresolved: + if unresolved and not ignore_unresolved: raise ValueError( "Branch aliases unresolved: %s" % ", ".join("%r -> %r" % x for x in unresolved), diff --git a/swh/model/hypothesis_strategies.py b/swh/model/hypothesis_strategies.py index 89fe6dec358d6a5641fca453f289581f43d746b1..19a8d89118f1e6b522b2f8af61ff6a1337515e38 100644 --- a/swh/model/hypothesis_strategies.py +++ b/swh/model/hypothesis_strategies.py @@ -131,6 +131,18 @@ def timestamps_d(**kwargs): min_seconds = datetime.datetime.min.replace( tzinfo=datetime.timezone.utc ).timestamp() + + # in Python 3.9, datetime.datetime.max is 9999-12-31T23:59:59.999999, which + # means its .timestamp() is 253402300799.999999 in UTC. Unfortunately, because of + # flotting-point loss of precision, this is rounded up to 253402300800.0, which + # is the timestamp of 10000-01-01T00:00:00 in UTC, which cannot be passed to + # datetime.datetime.fromtimestamp because it overflows. + # To work around this issue, we move from max_seconds and min_seconds one second + # closer to Epoch, which is more than enough (actually, subtracting 20ms from + # max_seconds is enough). + max_seconds -= 1 + min_seconds += 1 + defaults = dict( seconds=integers(min_seconds, max_seconds), microseconds=integers(0, 1000000 - 1), diff --git a/swh/model/model.py b/swh/model/model.py index 1073cc61cf4623ba65428b164466b82d4ad35b6c..8910db8cf98e0b6738e8f7c1f40326f676094583 100644 --- a/swh/model/model.py +++ b/swh/model/model.py @@ -197,7 +197,9 @@ class BaseModel: def check(self) -> None: """Performs internal consistency checks, and raises an error if one fails.""" - attr.validate(self) + # without the type-ignore comment below, attr >= 22.1.0 causes mypy to report: + # Argument 1 has incompatible type "BaseModel"; expected "AttrsInstance" + attr.validate(self) # type: ignore[arg-type] def _compute_hash_from_manifest(manifest: bytes) -> Sha1Git: @@ -741,7 +743,9 @@ class Snapshot(HashableObject, BaseModel): id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"", repr=hash_repr) def _compute_hash_from_attributes(self) -> bytes: - return _compute_hash_from_manifest(git_objects.snapshot_git_object(self)) + return _compute_hash_from_manifest( + git_objects.snapshot_git_object(self, ignore_unresolved=True) + ) @classmethod def from_dict(cls, d): diff --git a/swh/model/tests/test_identifiers.py b/swh/model/tests/test_identifiers.py index 793e6d5b3aa6f931a1afc8ca17f7b3be61c4e90d..d5a0eb30cc67f51725037b732a744348b5dbb726 100644 --- a/swh/model/tests/test_identifiers.py +++ b/swh/model/tests/test_identifiers.py @@ -755,8 +755,14 @@ class SnapshotIdentifier(unittest.TestCase): ) def test_unresolved(self): + self.assertEqual( + Snapshot.from_dict(remove_id(self.unresolved)).id, self.unresolved["id"] + ) + + def test_git_object_unresolved(self): with self.assertRaisesRegex(ValueError, "b'foo' -> b'bar'"): - Snapshot.from_dict(remove_id(self.unresolved)) + git_objects.snapshot_git_object(self.unresolved) + git_objects.snapshot_git_object(self.unresolved, ignore_unresolved=True) def test_all_types(self): self.assertEqual(