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>
This commit is contained in:
Jussi Kukkonen 2022-03-23 16:43:50 +02:00
parent b7b035aea1
commit d2f8c99c19

View file

@ -32,7 +32,7 @@
import io
import logging
import tempfile
from datetime import datetime
from datetime import datetime, timedelta
from typing import (
IO,
Any,
@ -120,11 +120,11 @@ class Metadata(Generic[T]):
def __init__(
self,
signed: T,
signatures: Dict[str, Signature],
signatures: Optional[Dict[str, Signature]] = None,
unrecognized_fields: Optional[Mapping[str, Any]] = None,
):
self.signed: T = signed
self.signatures = signatures
self.signatures = signatures or {}
self.unrecognized_fields: Mapping[str, Any] = unrecognized_fields or {}
def __eq__(self, other: Any) -> bool:
@ -480,11 +480,13 @@ def expires(self, value: datetime) -> None:
# or "inner metadata")
def __init__(
self,
version: int,
spec_version: str,
expires: datetime,
unrecognized_fields: Optional[Mapping[str, Any]] = None,
version: Optional[int],
spec_version: Optional[str],
expires: Optional[datetime],
unrecognized_fields: Optional[Mapping[str, Any]],
):
if spec_version is None:
spec_version = ".".join(SPECIFICATION_VERSION)
# Accept semver (X.Y.Z) but also X.Y for legacy compatibility
spec_list = spec_version.split(".")
if len(spec_list) not in [2, 3] or not all(
@ -497,11 +499,15 @@ def __init__(
raise ValueError(f"Unsupported spec_version {spec_version}")
self.spec_version = spec_version
self.expires = expires
if version <= 0:
self.expires = expires or datetime.utcnow() + timedelta(days=1)
if version is None:
version = 1
elif version <= 0:
raise ValueError(f"version must be > 0, got {version}")
self.version = version
self.unrecognized_fields: Mapping[str, Any] = unrecognized_fields or {}
def __eq__(self, other: Any) -> bool:
@ -838,20 +844,22 @@ class Root(Signed):
# pylint: disable=too-many-arguments
def __init__(
self,
version: int,
spec_version: str,
expires: datetime,
keys: Dict[str, Key],
roles: Mapping[str, Role],
consistent_snapshot: Optional[bool] = None,
version: Optional[int] = None,
spec_version: Optional[str] = None,
expires: Optional[datetime] = None,
keys: Optional[Dict[str, Key]] = None,
roles: Optional[Mapping[str, Role]] = None,
consistent_snapshot: Optional[bool] = True,
unrecognized_fields: Optional[Mapping[str, Any]] = None,
):
super().__init__(version, spec_version, expires, unrecognized_fields)
self.consistent_snapshot = consistent_snapshot
self.keys = keys
if set(roles) != TOP_LEVEL_ROLE_NAMES:
raise ValueError("Role names must be the top-level metadata roles")
self.keys = keys or {}
if roles is None:
roles = {r: Role([], 1) for r in TOP_LEVEL_ROLE_NAMES}
elif set(roles) != TOP_LEVEL_ROLE_NAMES:
raise ValueError("Role names must be the top-level metadata roles")
self.roles = roles
def __eq__(self, other: Any) -> bool:
@ -1129,14 +1137,14 @@ class Timestamp(Signed):
def __init__(
self,
version: int,
spec_version: str,
expires: datetime,
snapshot_meta: MetaFile,
version: Optional[int] = None,
spec_version: Optional[str] = None,
expires: Optional[datetime] = None,
snapshot_meta: Optional[MetaFile] = None,
unrecognized_fields: Optional[Mapping[str, Any]] = None,
):
super().__init__(version, spec_version, expires, unrecognized_fields)
self.snapshot_meta = snapshot_meta
self.snapshot_meta = snapshot_meta or MetaFile(1)
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Timestamp):
@ -1190,14 +1198,14 @@ class Snapshot(Signed):
def __init__(
self,
version: int,
spec_version: str,
expires: datetime,
meta: Dict[str, MetaFile],
version: Optional[int] = None,
spec_version: Optional[str] = None,
expires: Optional[datetime] = None,
meta: Optional[Dict[str, MetaFile]] = None,
unrecognized_fields: Optional[Mapping[str, Any]] = None,
):
super().__init__(version, spec_version, expires, unrecognized_fields)
self.meta = meta
self.meta = meta if meta is not None else {"targets.json": MetaFile(1)}
def __eq__(self, other: Any) -> bool:
if not isinstance(other, Snapshot):
@ -1660,15 +1668,15 @@ class Targets(Signed):
# pylint: disable=too-many-arguments
def __init__(
self,
version: int,
spec_version: str,
expires: datetime,
targets: Dict[str, TargetFile],
version: Optional[int] = None,
spec_version: Optional[str] = None,
expires: Optional[datetime] = None,
targets: Optional[Dict[str, TargetFile]] = None,
delegations: Optional[Delegations] = None,
unrecognized_fields: Optional[Mapping[str, Any]] = None,
) -> None:
super().__init__(version, spec_version, expires, unrecognized_fields)
self.targets = targets
self.targets = targets or {}
self.delegations = delegations
def __eq__(self, other: Any) -> bool: