Source code for radioviz.services.export_services

#  Copyright 2026 European Union
#  Author: Bulgheroni Antonio (antonio.bulgheroni@ec.europa.eu)
#  SPDX-License-Identifier: EUPL-1.2
"""
Export services for RadioViz application.

This module provides export functionality for saving data in various formats.
It defines the ExportSpec dataclass, Exportable protocol, and ExportCoordinator
class to manage the complete export workflow.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Protocol, runtime_checkable

from PySide6.QtCore import QObject
from PySide6.QtWidgets import QFileDialog, QInputDialog, QWidget


[docs] @dataclass(frozen=True) class ExportSpec: """ Data class representing export specifications. This class holds all the information needed to define how an object can be exported, including the export key, display label, file filters, default suffixes, and the actual writer function. """ key: str """str: Unique identifier for the export specification.""" label: str """str: Human-readable label for the export option.""" filter: str """str: File filter string for Qt file dialogs.""" default_suffix: str """str: Default file suffix for this export format.""" suffix_map: dict[str, str] """dict[str, str]: Mapping from filter strings to their default suffixes.""" writer: Callable[[Path], None] """Callable[[Path], None]: Function that performs the actual writing operation."""
[docs] @runtime_checkable class Exportable(Protocol): """ Protocol defining the interface for exportable objects. Any class implementing this protocol can be exported using the :class:`ExportCoordinator`. """
[docs] def get_export_specs(self) -> list[ExportSpec]: """ Get available export specifications for this object. :return: List of export specifications :rtype: list[:class:`ExportSpec`] """ ...
[docs] def can_export_as(self) -> bool: """ Check if this object can be exported. :return: True if the object can be exported, False otherwise :rtype: bool """ ...
[docs] class ExportCoordinator(QObject): """ Coordinator class for managing export operations. This class handles the complete export workflow, including: - Determining available export formats - Presenting format selection to the user when multiple options exist - Executing the actual export using the specified writer function """ def __init__(self, parent: QObject | None = None) -> None: """ Initialize the export coordinator. :param parent: Parent QObject for Qt ownership management :type parent: QObject or None """ super().__init__(parent)
[docs] def export(self, exportable: Exportable, parent_widget: QWidget | None) -> None: """ Initiate the export process for an exportable object. If the exportable has only one export specification, it will be used directly. Otherwise, the user will be prompted to select a format. :param exportable: Object to be exported :type exportable: :class:`Exportable` :param parent_widget: Parent widget for dialog windows :type parent_widget: :class:`QWidget` """ specs = exportable.get_export_specs() if not specs: return if len(specs) == 1: self._export_with_spec(exportable, specs[0], parent_widget) else: self._choose_and_export(exportable, specs, parent_widget)
[docs] def _export_with_spec(self, exportable: Exportable, spec: ExportSpec, parent_widget: QWidget | None) -> None: """ Export using a specific export specification. Opens a file dialog to get the destination path and calls the writer function. :param exportable: Object to be exported :type exportable: :class:`Exportable` :param spec: Export specification to use :type spec: :class:`ExportSpec` :param parent_widget: Parent widget for dialog windows :type parent_widget: :class:`QWidget` """ filename, selected_filter = QFileDialog.getSaveFileName( parent_widget, f'Export {spec.label}', filter=spec.filter ) if not filename: return path = Path(filename) spec.writer(path)
[docs] def _choose_and_export( self, exportable: Exportable, specs: list[ExportSpec], parent_widget: QWidget | None ) -> None: """ Present format selection to user and export using chosen format. :param exportable: Object to be exported :type exportable: :class:`Exportable` :param specs: List of available export specifications :type specs: list[:class:`ExportSpec`] :param parent_widget: Parent widget for dialog windows :type parent_widget: :class:`QWidget` """ spec = self._ask_user_for_format(specs, parent_widget) if spec is not None: self._export_with_spec(exportable, spec, parent_widget)
[docs] def _ask_user_for_format(self, specs: list[ExportSpec], parent: QWidget | None) -> ExportSpec | None: """ Prompt user to select an export format. :param specs: List of available export specifications :type specs: list[:class:`ExportSpec`] :param parent: Parent widget for dialog window :type parent: :class:`QWidget` :return: Selected export specification or None if cancelled :rtype: :class:`ExportSpec` or None """ keys = [spec.key for spec in specs] key, ok = QInputDialog.getItem( parent, 'Export as', 'Select what to export:', keys, current=0, editable=False, ) if not ok: return None return next(spec for spec in specs if spec.key == key)