diff --git a/swh/model/identifiers.py b/swh/model/identifiers.py --- a/swh/model/identifiers.py +++ b/swh/model/identifiers.py @@ -417,7 +417,7 @@ - author_date - committer - committer_date - - metadata -> extra_headers + - extra_headers or metadata -> extra_headers - message A revision's identifier is the 'git'-checksum of a commit manifest @@ -477,12 +477,12 @@ ) # Handle extra headers - metadata = revision.get("metadata") - if not metadata: - metadata = {} - - for key, value in metadata.get("extra_headers", []): + metadata = revision.get("metadata") or {} + extra_headers = revision.get("extra_headers", ()) + if not extra_headers and "extra_headers" in metadata: + extra_headers = metadata["extra_headers"] + for key, value in extra_headers: # Integer values: decimal representation if isinstance(value, int): value = str(value).encode("utf-8") diff --git a/swh/model/model.py b/swh/model/model.py --- a/swh/model/model.py +++ b/swh/model/model.py @@ -6,6 +6,7 @@ import datetime from abc import ABCMeta, abstractmethod +from copy import deepcopy from enum import Enum from hashlib import sha256 from typing import Dict, Optional, Tuple, TypeVar, Union @@ -435,6 +436,21 @@ ) parents = attr.ib(type=Tuple[Sha1Git, ...], validator=type_validator(), default=()) id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"") + extra_headers = attr.ib( + type=Tuple[Tuple[str, Union[int, str, bytes]], ...], + validator=type_validator(), + default=(), + ) + + def __attrs_post_init__(self): + super().__attrs_post_init__() + # ensure metadata is a deep copy of whatever was given, and if needed + # extract extra_headers from there + if self.metadata: + metadata = deepcopy(self.metadata) + if not self.extra_headers and "extra_headers" in metadata: + object.__setattr__(self, "extra_headers", metadata.pop("extra_headers")) + object.__setattr__(self, "metadata", metadata) @staticmethod def compute_hash(object_dict): diff --git a/swh/model/tests/test_model.py b/swh/model/tests/test_model.py --- a/swh/model/tests/test_model.py +++ b/swh/model/tests/test_model.py @@ -415,6 +415,35 @@ SkippedContent.from_dict(skipped_content_d) +# Revision + + +def test_revision_extra_headers(): + rev_dict = revision_example.copy() + rev_dict.pop("id") + rev_model = Revision.from_dict(rev_dict) + assert rev_model.metadata is None + assert rev_model.extra_headers == () + + rev_dict["metadata"] = { + "something": "somewhere", + "some other thing": "stranger", + } + rev_model = Revision.from_dict(rev_dict) + assert rev_model.metadata == rev_dict["metadata"] + assert rev_model.extra_headers == () + + rev_dict["metadata"]["extra_headers"] = ( + ("header1", "value1"), + ("header2", 42), + ("header3", b"should I?\u0000"), + ("header1", "again"), + ) + rev_model = Revision.from_dict(rev_dict) + assert "extra_headers" not in rev_model.metadata + assert rev_model.extra_headers == rev_dict["metadata"]["extra_headers"] + + # ID computation