diff --git a/tuf/api/metadata.py b/tuf/api/metadata.py index 873ab85f..e7cc9a6d 100644 --- a/tuf/api/metadata.py +++ b/tuf/api/metadata.py @@ -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: