Skip to content

Commit d2f8c99

Browse files
author
Jussi Kukkonen
committed
Metadata API: Add default constructor arguments
This allows creating new metadata with less boilerplate: root = Metadata(Root()) targets = Metadata(Targets()) Set reasonable default values for all the arguments -- version to 1, spec_version to current supported version, etc. Expires does not have a good default value and my original plan was to require expires argument to be set. That would mean an incompatible API change though as arguments before expires would be now optional... So expires now defaults to an arbitrary value of 1 day from moment of creation. One noteworthy special case is consistent_snapshot where the default value is True (since that's what we want people to use for new metadata) but None is also used to imply that metadata does not contain the field at all. Signed-off-by: Jussi Kukkonen <jkukkonen@vmware.com>
1 parent b7b035a commit d2f8c99

1 file changed

Lines changed: 41 additions & 33 deletions

File tree

tuf/api/metadata.py

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import io
3333
import logging
3434
import tempfile
35-
from datetime import datetime
35+
from datetime import datetime, timedelta
3636
from typing import (
3737
IO,
3838
Any,
@@ -120,11 +120,11 @@ class Metadata(Generic[T]):
120120
def __init__(
121121
self,
122122
signed: T,
123-
signatures: Dict[str, Signature],
123+
signatures: Optional[Dict[str, Signature]] = None,
124124
unrecognized_fields: Optional[Mapping[str, Any]] = None,
125125
):
126126
self.signed: T = signed
127-
self.signatures = signatures
127+
self.signatures = signatures or {}
128128
self.unrecognized_fields: Mapping[str, Any] = unrecognized_fields or {}
129129

130130
def __eq__(self, other: Any) -> bool:
@@ -480,11 +480,13 @@ def expires(self, value: datetime) -> None:
480480
# or "inner metadata")
481481
def __init__(
482482
self,
483-
version: int,
484-
spec_version: str,
485-
expires: datetime,
486-
unrecognized_fields: Optional[Mapping[str, Any]] = None,
483+
version: Optional[int],
484+
spec_version: Optional[str],
485+
expires: Optional[datetime],
486+
unrecognized_fields: Optional[Mapping[str, Any]],
487487
):
488+
if spec_version is None:
489+
spec_version = ".".join(SPECIFICATION_VERSION)
488490
# Accept semver (X.Y.Z) but also X.Y for legacy compatibility
489491
spec_list = spec_version.split(".")
490492
if len(spec_list) not in [2, 3] or not all(
@@ -497,11 +499,15 @@ def __init__(
497499
raise ValueError(f"Unsupported spec_version {spec_version}")
498500

499501
self.spec_version = spec_version
500-
self.expires = expires
501502

502-
if version <= 0:
503+
self.expires = expires or datetime.utcnow() + timedelta(days=1)
504+
505+
if version is None:
506+
version = 1
507+
elif version <= 0:
503508
raise ValueError(f"version must be > 0, got {version}")
504509
self.version = version
510+
505511
self.unrecognized_fields: Mapping[str, Any] = unrecognized_fields or {}
506512

507513
def __eq__(self, other: Any) -> bool:
@@ -838,20 +844,22 @@ class Root(Signed):
838844
# pylint: disable=too-many-arguments
839845
def __init__(
840846
self,
841-
version: int,
842-
spec_version: str,
843-
expires: datetime,
844-
keys: Dict[str, Key],
845-
roles: Mapping[str, Role],
846-
consistent_snapshot: Optional[bool] = None,
847+
version: Optional[int] = None,
848+
spec_version: Optional[str] = None,
849+
expires: Optional[datetime] = None,
850+
keys: Optional[Dict[str, Key]] = None,
851+
roles: Optional[Mapping[str, Role]] = None,
852+
consistent_snapshot: Optional[bool] = True,
847853
unrecognized_fields: Optional[Mapping[str, Any]] = None,
848854
):
849855
super().__init__(version, spec_version, expires, unrecognized_fields)
850856
self.consistent_snapshot = consistent_snapshot
851-
self.keys = keys
852-
if set(roles) != TOP_LEVEL_ROLE_NAMES:
853-
raise ValueError("Role names must be the top-level metadata roles")
857+
self.keys = keys or {}
854858

859+
if roles is None:
860+
roles = {r: Role([], 1) for r in TOP_LEVEL_ROLE_NAMES}
861+
elif set(roles) != TOP_LEVEL_ROLE_NAMES:
862+
raise ValueError("Role names must be the top-level metadata roles")
855863
self.roles = roles
856864

857865
def __eq__(self, other: Any) -> bool:
@@ -1129,14 +1137,14 @@ class Timestamp(Signed):
11291137

11301138
def __init__(
11311139
self,
1132-
version: int,
1133-
spec_version: str,
1134-
expires: datetime,
1135-
snapshot_meta: MetaFile,
1140+
version: Optional[int] = None,
1141+
spec_version: Optional[str] = None,
1142+
expires: Optional[datetime] = None,
1143+
snapshot_meta: Optional[MetaFile] = None,
11361144
unrecognized_fields: Optional[Mapping[str, Any]] = None,
11371145
):
11381146
super().__init__(version, spec_version, expires, unrecognized_fields)
1139-
self.snapshot_meta = snapshot_meta
1147+
self.snapshot_meta = snapshot_meta or MetaFile(1)
11401148

11411149
def __eq__(self, other: Any) -> bool:
11421150
if not isinstance(other, Timestamp):
@@ -1190,14 +1198,14 @@ class Snapshot(Signed):
11901198

11911199
def __init__(
11921200
self,
1193-
version: int,
1194-
spec_version: str,
1195-
expires: datetime,
1196-
meta: Dict[str, MetaFile],
1201+
version: Optional[int] = None,
1202+
spec_version: Optional[str] = None,
1203+
expires: Optional[datetime] = None,
1204+
meta: Optional[Dict[str, MetaFile]] = None,
11971205
unrecognized_fields: Optional[Mapping[str, Any]] = None,
11981206
):
11991207
super().__init__(version, spec_version, expires, unrecognized_fields)
1200-
self.meta = meta
1208+
self.meta = meta if meta is not None else {"targets.json": MetaFile(1)}
12011209

12021210
def __eq__(self, other: Any) -> bool:
12031211
if not isinstance(other, Snapshot):
@@ -1660,15 +1668,15 @@ class Targets(Signed):
16601668
# pylint: disable=too-many-arguments
16611669
def __init__(
16621670
self,
1663-
version: int,
1664-
spec_version: str,
1665-
expires: datetime,
1666-
targets: Dict[str, TargetFile],
1671+
version: Optional[int] = None,
1672+
spec_version: Optional[str] = None,
1673+
expires: Optional[datetime] = None,
1674+
targets: Optional[Dict[str, TargetFile]] = None,
16671675
delegations: Optional[Delegations] = None,
16681676
unrecognized_fields: Optional[Mapping[str, Any]] = None,
16691677
) -> None:
16701678
super().__init__(version, spec_version, expires, unrecognized_fields)
1671-
self.targets = targets
1679+
self.targets = targets or {}
16721680
self.delegations = delegations
16731681

16741682
def __eq__(self, other: Any) -> bool:

0 commit comments

Comments
 (0)