Release archives on GitHub: the manifest contract and the download client.
A MAMUT-routing release ships one zip per (problem type, family) plus a
snapshot-manifest.json (ReleaseArchiveManifest) listing every asset
with its download URL, sha256 and size. GitHubReleaseClient reads the
manifest of a tag (or of the latest release), downloads assets with retries
and verifies their checksum; the mamut-routing remote commands are thin
wrappers. Configuration: MAMUT_ROUTING_RELEASE_REPO (default
ANR-MAMUT/MAMUT-routing) and MAMUT_ROUTING_GITHUB_TOKEN (optional,
for rate limits or private releases).
DEFAULT_RELEASE_REPO_ENV
module-attribute
DEFAULT_RELEASE_REPO_ENV = 'MAMUT_ROUTING_RELEASE_REPO'
DEFAULT_GITHUB_TOKEN_ENV
module-attribute
DEFAULT_GITHUB_TOKEN_ENV = 'MAMUT_ROUTING_GITHUB_TOKEN'
DEFAULT_MANIFEST_FILENAME
module-attribute
DEFAULT_MANIFEST_FILENAME = 'snapshot-manifest.json'
MANIFEST_SCHEMA_VERSION
module-attribute
MANIFEST_SCHEMA_VERSION = '1.0.0'
DOWNLOAD_CHUNK_SIZE
module-attribute
DOWNLOAD_CHUNK_SIZE = 1024 * 1024
DEFAULT_REQUEST_TIMEOUT_SECONDS
module-attribute
DEFAULT_REQUEST_TIMEOUT_SECONDS = 30
DEFAULT_RETRY_ATTEMPTS
module-attribute
DEFAULT_RETRY_ATTEMPTS = 3
DEFAULT_RETRY_DELAY_SECONDS
module-attribute
DEFAULT_RETRY_DELAY_SECONDS = 0.5
ProgressCallback
module-attribute
ProgressCallback: TypeAlias = Callable[
[int, int | None], None
]
ReleaseArchiveScope
Bases: str, Enum
What an archive covers: today always problem_family (one problem type of one family).
PROBLEM_FAMILY
class-attribute
instance-attribute
PROBLEM_FAMILY = 'problem_family'
FAMILY_COLLECTION
class-attribute
instance-attribute
FAMILY_COLLECTION = 'family_collection'
ReleaseArchiveAsset
Bases: BaseModel
One archive of a release: scope, filename, download_url, family identity, checksum_sha256, size_bytes, archive_root.
scope
instance-attribute
scope: ReleaseArchiveScope
filename
instance-attribute
download_url
instance-attribute
problem_type
class-attribute
instance-attribute
problem_type: ProblemType | None = None
benchmark_name
class-attribute
instance-attribute
benchmark_name: BenchmarkName | None = None
checksum_sha256
class-attribute
instance-attribute
checksum_sha256: str | None = None
size_bytes
class-attribute
instance-attribute
size_bytes: int | None = None
archive_root
class-attribute
instance-attribute
archive_root: str | None = None
ReleaseArchiveManifest
Bases: BaseModel
The snapshot-manifest.json of a release: snapshot identity (id, date, commit, tag) and its assets.
schema_version
class-attribute
instance-attribute
schema_version: str = MANIFEST_SCHEMA_VERSION
snapshot_id
instance-attribute
published_at
instance-attribute
source_commit
instance-attribute
source_branch
class-attribute
instance-attribute
source_branch: str | None = None
release_tag
class-attribute
instance-attribute
release_tag: str | None = None
assets
class-attribute
instance-attribute
assets: list[ReleaseArchiveAsset] = Field(
default_factory=list
)
select_assets
select_assets(
*,
scope: ReleaseArchiveScope | None = None,
problem_type: ProblemType | None = None,
benchmark_name: BenchmarkName | None = None,
) -> list[ReleaseArchiveAsset]
Assets matching every given filter (scope, problem_type, benchmark_name); no filter returns all.
Source code in MAMUT-routing-tools/MAMUT-routing-lib/src/mamut_routing_lib/remote.py
| def select_assets(
self,
*,
scope: ReleaseArchiveScope | None = None,
problem_type: ProblemType | None = None,
benchmark_name: BenchmarkName | None = None,
) -> list[ReleaseArchiveAsset]:
"""Assets matching every given filter (``scope``, ``problem_type``, ``benchmark_name``); no filter returns all."""
selected = self.assets
if scope is not None:
selected = [asset for asset in selected if asset.scope == scope]
if problem_type is not None:
selected = [asset for asset in selected if asset.problem_type == problem_type]
if benchmark_name is not None:
selected = [asset for asset in selected if asset.benchmark_name == benchmark_name]
return selected
|
GitHubReleaseSource
dataclass
GitHubReleaseSource(
repo_full_name: str,
manifest_filename: str = DEFAULT_MANIFEST_FILENAME,
token: str | None = None,
)
Where releases are read from: owner/name repository, manifest filename, optional API token.
repo_full_name
instance-attribute
manifest_filename
class-attribute
instance-attribute
manifest_filename: str = DEFAULT_MANIFEST_FILENAME
token
class-attribute
instance-attribute
from_env
classmethod
from_env() -> 'GitHubReleaseSource'
Source from MAMUT_ROUTING_RELEASE_REPO and MAMUT_ROUTING_GITHUB_TOKEN.
Source code in MAMUT-routing-tools/MAMUT-routing-lib/src/mamut_routing_lib/remote.py
| @classmethod
def from_env(cls) -> "GitHubReleaseSource":
"""Source from ``MAMUT_ROUTING_RELEASE_REPO`` and ``MAMUT_ROUTING_GITHUB_TOKEN``."""
repo_full_name = os.getenv(DEFAULT_RELEASE_REPO_ENV, "ANR-MAMUT/MAMUT-routing")
token = os.getenv(DEFAULT_GITHUB_TOKEN_ENV)
return cls(repo_full_name=repo_full_name, token=token)
|
GitHubReleaseClient
GitHubReleaseClient(
source: GitHubReleaseSource | None = None,
*,
retry_attempts: int = DEFAULT_RETRY_ATTEMPTS,
retry_delay_seconds: float = DEFAULT_RETRY_DELAY_SECONDS,
request_timeout_seconds: float = DEFAULT_REQUEST_TIMEOUT_SECONDS,
)
Reads manifests and downloads assets of GitHub releases with retries and a request timeout.
Source code in MAMUT-routing-tools/MAMUT-routing-lib/src/mamut_routing_lib/remote.py
| def __init__(
self,
source: GitHubReleaseSource | None = None,
*,
retry_attempts: int = DEFAULT_RETRY_ATTEMPTS,
retry_delay_seconds: float = DEFAULT_RETRY_DELAY_SECONDS,
request_timeout_seconds: float = DEFAULT_REQUEST_TIMEOUT_SECONDS,
) -> None:
self.source = source or GitHubReleaseSource.from_env()
self.retry_attempts = max(1, retry_attempts)
self.retry_delay_seconds = max(0.0, retry_delay_seconds)
self.request_timeout_seconds = request_timeout_seconds
|
source
instance-attribute
source = source or GitHubReleaseSource.from_env()
retry_attempts
instance-attribute
retry_attempts = max(1, retry_attempts)
retry_delay_seconds
instance-attribute
retry_delay_seconds = max(0.0, retry_delay_seconds)
request_timeout_seconds
instance-attribute
request_timeout_seconds = request_timeout_seconds
fetch_manifest
fetch_manifest(
tag: str | None = None,
) -> ReleaseArchiveManifest
The manifest of tag, or of the latest release when tag is None.
Source code in MAMUT-routing-tools/MAMUT-routing-lib/src/mamut_routing_lib/remote.py
| def fetch_manifest(self, tag: str | None = None) -> ReleaseArchiveManifest:
"""The manifest of ``tag``, or of the latest release when ``tag`` is ``None``."""
resolved_tag = tag if tag is not None else self._resolve_latest_tag()
manifest_url = self._build_release_asset_url(resolved_tag, self.source.manifest_filename)
payload = self._download_json(manifest_url)
return ReleaseArchiveManifest(**payload)
|
download_asset
download_asset(
asset: ReleaseArchiveAsset,
destination_dir: str | Path,
*,
extract: bool = False,
progress_callback: ProgressCallback | None = None,
) -> Path
Download asset into destination_dir, verify its checksum, optionally extract.
Returns the archive path, or the extraction directory (<destination>/<stem>,
replaced if it exists) when extract is true. progress_callback
receives (bytes_so_far, total_or_None).
Source code in MAMUT-routing-tools/MAMUT-routing-lib/src/mamut_routing_lib/remote.py
| def download_asset(
self,
asset: ReleaseArchiveAsset,
destination_dir: str | Path,
*,
extract: bool = False,
progress_callback: ProgressCallback | None = None,
) -> Path:
"""Download ``asset`` into ``destination_dir``, verify its checksum, optionally extract.
Returns the archive path, or the extraction directory (``<destination>/<stem>``,
replaced if it exists) when ``extract`` is true. ``progress_callback``
receives ``(bytes_so_far, total_or_None)``.
"""
destination_root = Path(destination_dir)
destination_root.mkdir(parents=True, exist_ok=True)
destination_path = destination_root / asset.filename
self._download_file(
asset.download_url,
destination_path,
expected_total_bytes=asset.size_bytes,
progress_callback=progress_callback,
)
if asset.checksum_sha256 is not None:
verify_sha256(destination_path, asset.checksum_sha256)
if not extract:
return destination_path
extract_dir = destination_root / destination_path.stem
if extract_dir.exists():
shutil.rmtree(extract_dir)
extract_dir.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(destination_path, "r") as archive:
archive.extractall(extract_dir)
return extract_dir
|
load_release_manifest
load_release_manifest(
path: str | Path,
) -> ReleaseArchiveManifest
Load and validate a manifest file.
Source code in MAMUT-routing-tools/MAMUT-routing-lib/src/mamut_routing_lib/remote.py
| def load_release_manifest(path: str | Path) -> ReleaseArchiveManifest:
"""Load and validate a manifest file."""
return ReleaseArchiveManifest(**load_json_from_file(path))
|
compute_sha256
compute_sha256(filepath: str | Path) -> str
Hex sha256 of a file, streamed.
Source code in MAMUT-routing-tools/MAMUT-routing-lib/src/mamut_routing_lib/remote.py
| def compute_sha256(filepath: str | Path) -> str:
"""Hex sha256 of a file, streamed."""
digest = hashlib.sha256()
with Path(filepath).open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
|
verify_sha256
verify_sha256(
filepath: str | Path, expected_sha256: str
) -> None
Raise ValueError unless the file's sha256 equals expected_sha256.
Source code in MAMUT-routing-tools/MAMUT-routing-lib/src/mamut_routing_lib/remote.py
| def verify_sha256(filepath: str | Path, expected_sha256: str) -> None:
"""Raise ``ValueError`` unless the file's sha256 equals ``expected_sha256``."""
actual_sha256 = compute_sha256(filepath)
if actual_sha256 != expected_sha256:
raise ValueError(f"SHA256 mismatch for {filepath}: expected {expected_sha256}, got {actual_sha256}")
|