Changeset View
Changeset View
Standalone View
Standalone View
swh/loader/package/archive/loader.py
Show All 18 Lines | ||||||||||||||||||
from swh.storage.interface import StorageInterface | from swh.storage.interface import StorageInterface | |||||||||||||||||
logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | |||||||||||||||||
@attr.s | @attr.s | |||||||||||||||||
class ArchivePackageInfo(BasePackageInfo): | class ArchivePackageInfo(BasePackageInfo): | |||||||||||||||||
raw_info = attr.ib(type=Dict[str, Any]) | raw_info = attr.ib(type=Dict[str, Any]) | |||||||||||||||||
length = attr.ib(type=int) | ||||||||||||||||||
"""Size of the archive file""" | length = attr.ib(type=Optional[int], default=None) | |||||||||||||||||
time = attr.ib(type=Union[str, datetime.datetime]) | """Optional size of the archive file if integrity is provided.""" | |||||||||||||||||
"""Timestamp of the archive file on the server""" | time = attr.ib(type=Optional[Union[str, datetime.datetime]], default=None) | |||||||||||||||||
"""Optional timestamp of the archive file on the server if integrity is provided.""" | ||||||||||||||||||
version = attr.ib(type=Optional[str], default=None) # type: ignore | ||||||||||||||||||
"""(Override) Optional version if integrity is provided.""" | ||||||||||||||||||
integrity = attr.ib(type=Optional[str], default=None) | ||||||||||||||||||
vlorentz: add a link to the format's spec: https://www.w3.org/TR/SRI/#the-integrity-attribute and mention… | ||||||||||||||||||
"""Archive integrity field""" | ||||||||||||||||||
# default format for gnu | # default format for gnu | |||||||||||||||||
MANIFEST_FORMAT = string.Template("$time $length $version $url") | MANIFEST_FORMAT = string.Template("$time $length $version $url") | |||||||||||||||||
# default format for nixguix manifests (e.g. nixpkgs, guix) | ||||||||||||||||||
INTEGRITY_MANIFEST_FORMAT = string.Template("$integrity $url") | ||||||||||||||||||
INTEGRITY_EXTID_TYPE = "package-manifest-integrity" | ||||||||||||||||||
Not Done Inline Actions
Make it clear the one with a checksum is prefered vlorentz: Make it clear the one with a checksum is prefered | ||||||||||||||||||
def extid(self, manifest_format: Optional[string.Template] = None) -> PartialExtID: | def extid(self, manifest_format: Optional[string.Template] = None) -> PartialExtID: | |||||||||||||||||
"""Returns a unique intrinsic identifier of this package info | """Returns a unique intrinsic identifier of this package info | |||||||||||||||||
``manifest_format`` allows overriding the class' default MANIFEST_FORMAT""" | ``manifest_format`` allows overriding the class' default MANIFEST_FORMAT""" | |||||||||||||||||
if self.raw_info.get("integrity") is not None: | ||||||||||||||||||
manifest_format = manifest_format or self.INTEGRITY_MANIFEST_FORMAT | ||||||||||||||||||
extid_type = self.INTEGRITY_EXTID_TYPE | ||||||||||||||||||
else: | ||||||||||||||||||
manifest_format = manifest_format or self.MANIFEST_FORMAT | manifest_format = manifest_format or self.MANIFEST_FORMAT | |||||||||||||||||
extid_type = self.EXTID_TYPE | ||||||||||||||||||
# TODO: use parsed attributes instead of self.raw_info | # TODO: use parsed attributes instead of self.raw_info | |||||||||||||||||
manifest = manifest_format.substitute( | manifest = manifest_format.substitute( | |||||||||||||||||
{k: str(v) for (k, v) in self.raw_info.items()} | {k: str(v) for (k, v) in self.raw_info.items()} | |||||||||||||||||
) | ) | |||||||||||||||||
return ( | return ( | |||||||||||||||||
self.EXTID_TYPE, | extid_type, | |||||||||||||||||
self.EXTID_VERSION, | self.EXTID_VERSION, | |||||||||||||||||
hashlib.sha256(manifest.encode()).digest(), | hashlib.sha256(manifest.encode()).digest(), | |||||||||||||||||
) | ) | |||||||||||||||||
@classmethod | @classmethod | |||||||||||||||||
def from_metadata(cls, a_metadata: Dict[str, Any]) -> "ArchivePackageInfo": | def from_metadata(cls, a_metadata: Dict[str, Any]) -> "ArchivePackageInfo": | |||||||||||||||||
url = a_metadata["url"] | url = a_metadata["url"] | |||||||||||||||||
filename = a_metadata.get("filename") | integrity = a_metadata.get("integrity") | |||||||||||||||||
filename_ = a_metadata.get("filename") | ||||||||||||||||||
filename = filename_ if filename_ else path.split(url)[-1] | ||||||||||||||||||
if integrity is not None: | ||||||||||||||||||
return cls( | return cls( | |||||||||||||||||
url=url, | url=url, | |||||||||||||||||
filename=filename if filename else path.split(url)[-1], | filename=filename, | |||||||||||||||||
raw_info=a_metadata, | raw_info=a_metadata, | |||||||||||||||||
length=a_metadata["length"], | integrity=integrity, | |||||||||||||||||
time=a_metadata["time"], | ) | |||||||||||||||||
version=a_metadata["version"], | else: | |||||||||||||||||
length = a_metadata["length"] | ||||||||||||||||||
time = a_metadata["time"] | ||||||||||||||||||
version = a_metadata["version"] | ||||||||||||||||||
assert length is not None | ||||||||||||||||||
assert version is not None | ||||||||||||||||||
return cls( | ||||||||||||||||||
url=url, | ||||||||||||||||||
filename=filename, | ||||||||||||||||||
raw_info=a_metadata, | ||||||||||||||||||
length=length, | ||||||||||||||||||
time=time, | ||||||||||||||||||
version=version, | ||||||||||||||||||
) | ) | |||||||||||||||||
class ArchiveLoader(PackageLoader[ArchivePackageInfo]): | class ArchiveLoader(PackageLoader[ArchivePackageInfo]): | |||||||||||||||||
"""Load archive origin's artifact files into swh archive""" | """Load archive origin's artifact files into swh archive""" | |||||||||||||||||
visit_type = "tar" | visit_type = "tar" | |||||||||||||||||
def __init__( | def __init__( | |||||||||||||||||
▲ Show 20 Lines • Show All 66 Lines • ▼ Show 20 Lines | def new_packageinfo_to_extid( | |||||||||||||||||
self, p_info: ArchivePackageInfo | self, p_info: ArchivePackageInfo | |||||||||||||||||
) -> Optional[PartialExtID]: | ) -> Optional[PartialExtID]: | |||||||||||||||||
return p_info.extid(manifest_format=self.extid_manifest_format) | return p_info.extid(manifest_format=self.extid_manifest_format) | |||||||||||||||||
def build_release( | def build_release( | |||||||||||||||||
self, p_info: ArchivePackageInfo, uncompressed_path: str, directory: Sha1Git | self, p_info: ArchivePackageInfo, uncompressed_path: str, directory: Sha1Git | |||||||||||||||||
) -> Optional[Release]: | ) -> Optional[Release]: | |||||||||||||||||
time = p_info.time # assume it's a timestamp | time = p_info.time # assume it's a timestamp | |||||||||||||||||
parsed_time: Optional[datetime.datetime] | ||||||||||||||||||
if isinstance(time, str): # otherwise, assume it's a parsable date | if isinstance(time, str): # otherwise, assume it's a parsable date | |||||||||||||||||
parsed_time = iso8601.parse_date(time) | parsed_time = iso8601.parse_date(time) | |||||||||||||||||
else: | else: | |||||||||||||||||
parsed_time = time | parsed_time = time | |||||||||||||||||
normalized_time = ( | normalized_time = ( | |||||||||||||||||
TimestampWithTimezone.from_datetime(parsed_time) | TimestampWithTimezone.from_datetime(parsed_time) | |||||||||||||||||
if parsed_time is not None | if parsed_time is not None | |||||||||||||||||
else None | else None | |||||||||||||||||
) | ) | |||||||||||||||||
msg = f"Synthetic release for archive at {p_info.url}\n" | msg = f"Synthetic release for archive at {p_info.url}\n" | |||||||||||||||||
if p_info.version is not None: | ||||||||||||||||||
name = p_info.version.encode() | ||||||||||||||||||
elif p_info.integrity is not None: | ||||||||||||||||||
name = p_info.integrity.encode() | ||||||||||||||||||
else: | ||||||||||||||||||
raise ValueError("Either version or integrity must be provided.") | ||||||||||||||||||
return Release( | return Release( | |||||||||||||||||
name=p_info.version.encode(), | name=name, | |||||||||||||||||
message=msg.encode(), | message=msg.encode(), | |||||||||||||||||
date=normalized_time, | date=normalized_time, | |||||||||||||||||
author=EMPTY_AUTHOR, | author=EMPTY_AUTHOR, | |||||||||||||||||
target=directory, | target=directory, | |||||||||||||||||
target_type=ObjectType.DIRECTORY, | target_type=ObjectType.DIRECTORY, | |||||||||||||||||
synthetic=True, | synthetic=True, | |||||||||||||||||
) | ) | |||||||||||||||||
def extra_branches(self) -> Dict[bytes, Mapping[str, Any]]: | def extra_branches(self) -> Dict[bytes, Mapping[str, Any]]: | |||||||||||||||||
if not self.snapshot_append: | if not self.snapshot_append: | |||||||||||||||||
return {} | return {} | |||||||||||||||||
last_snapshot = self.last_snapshot() | last_snapshot = self.last_snapshot() | |||||||||||||||||
return last_snapshot.to_dict()["branches"] if last_snapshot else {} | return last_snapshot.to_dict()["branches"] if last_snapshot else {} |
add a link to the format's spec: https://www.w3.org/TR/SRI/#the-integrity-attribute and mention it should follow the hash-expression rule