Source code for mafw.devtools.gitlab.docs_registry

#  Copyright 2025–2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2
"""
Documentation-specific GitLab Generic Package Registry operations.

This module wraps the generic GitLab registry helpers with
MAFw-docs-specific defaults (package name, filename conventions).
"""

from __future__ import annotations

from pathlib import Path
from typing import Any

from mafw.devtools.gitlab.api import GitlabAPIConfiguration
from mafw.devtools.gitlab.registry import (
    _get_generic_file,
    _head_generic_file,
    _put_generic_file,
    _write_generic_file_response,
    list_generic_packages,
    resolve_package_ids_by_version,
)


[docs] def list_mafw_docs_generic_packages( api_config: GitlabAPIConfiguration, package_name: str = 'mafw-docs', ) -> list[dict[str, Any]]: """List generic packages for mafw-docs in the GitLab Package Registry. Uses the Packages API: ``GET /projects/:id/packages`` and filters for ``package_type=generic`` and exact ``name == package_name``. :param api_config: GitLab API configuration :type api_config: gitlab.GitlabAPIConfiguration :param package_name: Package name, defaults to ``mafw-docs`` :type package_name: str :return: List of package dictionaries from the API :rtype: list[dict[str, Any]] """ return list_generic_packages(api_config, package_name)
[docs] def resolve_mafw_docs_package_ids_by_version( api_config: GitlabAPIConfiguration, package_name: str = 'mafw-docs' ) -> dict[str, int]: """Resolve package IDs for mafw-docs generic packages by version. :param api_config: GitLab API configuration :type api_config: gitlab.GitlabAPIConfiguration :param package_name: Package name, defaults to ``mafw-docs`` :type package_name: str :return: Mapping from version string to package id :rtype: dict[str, int] """ return resolve_package_ids_by_version(api_config, package_name)
[docs] def upload_docs_zip_to_gitlab_generic_registry( api_config: GitlabAPIConfiguration, package_version: str, zip_path: Path, package_name: str = 'mafw-docs', timeout_s: float = 60.0, ) -> bool: """Upload a documentation zip archive to the GitLab Generic Package Registry. The equivalent GitLab API endpoint is: ``PUT /projects/:id/packages/generic/:package_name/:package_version/:file_name`` Authentication headers: - ``JOB-TOKEN`` when running on CI (``api_config.on_ci`` is True) - ``PRIVATE-TOKEN`` for local execution :param api_config: GitLab API configuration :type api_config: gitlab.GitlabAPIConfiguration :param package_version: Package version (typically the git tag, e.g. ``v2.1.0``) :type package_version: str :param zip_path: Path to the zip file to upload :type zip_path: Path :param package_name: Generic package name, defaults to ``mafw-docs`` :type package_name: str :param timeout_s: Request timeout in seconds, defaults to 60.0 :type timeout_s: float :return: True if the file was uploaded, False if the upload was skipped because the target already exists :rtype: bool :raises FileNotFoundError: If ``zip_path`` does not exist :raises RuntimeError: If the upload fails (non-2xx response) """ zip_path = Path(zip_path).resolve() if not zip_path.exists(): raise FileNotFoundError(f'Zip file not found: {zip_path}') base_url = api_config.api_url.rstrip('/') file_name = zip_path.name url = f'{base_url}/projects/{api_config.project_id}/packages/generic/{package_name}/{package_version}/{file_name}' head_resp = _head_generic_file(api_config, url, timeout_s) if head_resp.status_code == 200: print(f'ℹ️ Zip already present on GitLab, skipping upload: {url}') return False if head_resp.status_code != 404: body_preview = (head_resp.text or '')[:500].replace('\n', ' ') raise RuntimeError(f'GitLab existence check failed ({head_resp.status_code}): {body_preview}') resp = _put_generic_file(api_config, url, zip_path, 'application/zip', timeout_s) if not (200 <= resp.status_code < 300): body_preview = (resp.text or '')[:500].replace('\n', ' ') raise RuntimeError(f'GitLab upload failed ({resp.status_code}): {body_preview}') return True
[docs] def download_docs_zip_from_gitlab_generic_registry( api_config: GitlabAPIConfiguration, package_version: str, download_dir: Path, package_name: str = 'mafw-docs', file_name: str | None = None, timeout_s: float = 60.0, ) -> Path | None: """Download a documentation zip archive from the GitLab Generic Package Registry. The equivalent GitLab API endpoint is: ``GET /projects/:id/packages/generic/:package_name/:package_version/:file_name`` The function first issues a HEAD request to determine if the file exists: - 404: the package file does not exist (cache miss) and ``None`` is returned. - 200: the file exists and is downloaded. - otherwise: a warning is printed and ``None`` is returned. Authentication headers: - ``JOB-TOKEN`` when running on CI (``api_config.on_ci`` is True) - ``PRIVATE-TOKEN`` for local execution :param api_config: GitLab API configuration :type api_config: gitlab.GitlabAPIConfiguration :param package_version: Package version (typically the git tag, e.g. ``v2.1.0``) :type package_version: str :param download_dir: Directory where the downloaded zip is stored :type download_dir: Path :param package_name: Generic package name, defaults to ``mafw-docs`` :type package_name: str :param file_name: File name to retrieve, defaults to ``mafw-docs-<version>.zip`` :type file_name: str | None :param timeout_s: Request timeout in seconds, defaults to 60.0 :type timeout_s: float :return: Path to the downloaded zip archive, or None if it does not exist or cannot be retrieved :rtype: Path | None """ download_dir = Path(download_dir).resolve() download_dir.mkdir(parents=True, exist_ok=True) base_url = api_config.api_url.rstrip('/') target_file_name = file_name or f'mafw-docs-{package_version}.zip' url = ( f'{base_url}/projects/{api_config.project_id}/packages/generic/' f'{package_name}/{package_version}/{target_file_name}' ) try: head_resp = _head_generic_file(api_config, url, timeout_s) except Exception as e: # pragma: no cover print(f'⚠️ Warning: cache HEAD request failed for {package_version}: {e}') return None if head_resp.status_code == 404: return None if head_resp.status_code != 200: body_preview = (head_resp.text or '')[:200].replace('\n', ' ') print( f'⚠️ Warning: cache existence check returned {head_resp.status_code} for {package_version}: {body_preview}' ) return None dest = download_dir / target_file_name try: resp = _get_generic_file(api_config, url, timeout_s) except Exception as e: # pragma: no cover print(f'⚠️ Warning: cache download request failed for {package_version}: {e}') return None if not (200 <= resp.status_code < 300): body_preview = (resp.text or '')[:200].replace('\n', ' ') print(f'⚠️ Warning: cache download failed for {package_version} ({resp.status_code}): {body_preview}') return None try: _write_generic_file_response(resp, dest) except OSError as e: print(f'⚠️ Warning: could not write downloaded zip for {package_version} to {dest}: {e}') return None return dest