Current File : /home/inlingua/miniconda3/lib/python3.1/site-packages/conda_anaconda_tos/local.py |
# Copyright (C) 2024 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
"""Low-level local (acceptance & rejection) Terms of Service metadata management."""
from __future__ import annotations
from datetime import datetime, timezone
from typing import TYPE_CHECKING
from conda.models.channel import Channel
from pydantic import ValidationError
from .exceptions import CondaToSMissingError, CondaToSPermissionError
from .models import LocalPair, LocalToSMetadata, RemoteToSMetadata
from .path import get_all_channel_paths, get_channel_paths, get_metadata_path, get_path
if TYPE_CHECKING:
import os
from collections.abc import Iterable, Iterator
from pathlib import Path
from typing import Any
def write_metadata(
tos_root: str | os.PathLike[str] | Path,
channel: str | Channel,
metadata: LocalToSMetadata | RemoteToSMetadata,
# kwargs extends/overrides metadata fields
**kwargs: Any, # noqa: ANN401
) -> LocalPair:
"""Write the metadata to file."""
# argument validation/coercion
channel = Channel(channel)
if not channel.base_url:
raise ValueError("`channel` must have a base URL.")
if not isinstance(metadata, (LocalToSMetadata, RemoteToSMetadata)):
raise TypeError("`metadata` must be a LocalToSMetadata or RemoteToSMetadata.")
# create/update ToSMetadata object
metadata = LocalToSMetadata(
**{
**metadata.model_dump(),
**kwargs,
# override the following fields with the current time and channel base URL
"acceptance_timestamp": datetime.now(tz=timezone.utc),
"base_url": channel.base_url,
}
)
# write metadata to file
path = get_metadata_path(tos_root, channel, metadata.version)
try:
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(metadata.model_dump_json())
except PermissionError as exc:
# PermissionError: can't write metadata path
raise CondaToSPermissionError(path, channel) from exc
return LocalPair(metadata=metadata, path=path)
def read_metadata(path: str | os.PathLike[str] | Path) -> LocalPair | None:
"""Load the metadata from file."""
try:
return LocalPair(
metadata=LocalToSMetadata.model_validate_json(get_path(path).read_text()),
path=path,
)
except (FileNotFoundError, ValidationError):
# FileNotFoundError: metadata path doesn't exist
# ValidationError: invalid JSON schema, treat it as missing
return None
except PermissionError as exc:
# PermissionError: can't read metadata path
raise CondaToSPermissionError(path) from exc
def get_local_metadata(
channel: str | Channel,
*,
extend_search_path: Iterable[str | os.PathLike[str] | Path] | None = None,
) -> LocalPair:
"""Get the latest metadata for the given channel."""
# find all metadata files for the given channel
metadata_pairs = [
metadata_pair
for path in get_channel_paths(channel, extend_search_path=extend_search_path)
if (metadata_pair := read_metadata(path))
]
# return if no metadata found
if not metadata_pairs:
raise CondaToSMissingError(f"No Terms of Service metadata found for {channel}")
# reverse to order from lowest to highest priority
metadata_pairs.reverse()
# return newest (and highest priority) metadata for channel
return sorted(metadata_pairs)[-1]
def get_local_metadatas(
*,
extend_search_path: Iterable[str | os.PathLike[str] | Path] | None = None,
) -> Iterator[tuple[Channel, LocalPair]]:
"""Yield all metadata."""
# group metadata by channel
grouped_metadatas: dict[Channel, list[LocalPair]] = {}
for path in get_all_channel_paths(extend_search_path=extend_search_path):
if metadata_pair := read_metadata(path):
channel = Channel(metadata_pair.metadata.base_url)
grouped_metadatas.setdefault(channel, []).append(metadata_pair)
# return the newest (and highest priority) metadata for each channel
for channel, metadata_pairs in grouped_metadatas.items():
# reverse to order from lowest to highest priority
metadata_pairs.reverse()
# yield newest (and highest priority) metadata for channel
yield channel, sorted(metadata_pairs)[-1]