Coverage for src/ezcompiler/adapters/base_uploader.py: 80.95%
17 statements
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-04 11:22 +0000
« prev ^ index » next coverage.py v7.14.3, created at 2026-07-04 11:22 +0000
1# ///////////////////////////////////////////////////////////////
2# BASE_UPLOADER - Abstract base uploader interface
3# Project: ezcompiler
4# ///////////////////////////////////////////////////////////////
6"""
7Base uploader - Abstract base class for uploader implementations.
9This module defines the interface and common functionality for all uploaders,
10providing validation and contract enforcement for upload operations.
12Note: Protocols layer should not perform logging directly. Logging is handled
13by the service layer that orchestrates upload operations.
14"""
16from __future__ import annotations
18# ///////////////////////////////////////////////////////////////
19# IMPORTS
20# ///////////////////////////////////////////////////////////////
21# Standard library imports
22from abc import ABC, abstractmethod
23from pathlib import Path
24from typing import Any
26# Local imports
27from ..shared.exceptions import UploadError
29# ///////////////////////////////////////////////////////////////
30# CLASSES
31# ///////////////////////////////////////////////////////////////
34# Le contrat structurel (Port) est défini par ``types.UploaderPort`` (Protocol).
35# Cette classe reste une base abstraite *concrète* : elle conforme au Port et
36# factorise le comportement partagé (validation de config et de source path).
37# Les frontières (factory, service) sont typées via le Port, pas via cette base.
38class BaseUploader(ABC):
39 """
40 Abstract base class for uploaders.
42 Defines the interface that all uploaders must implement and provides
43 common functionality for upload operations and validation.
45 Attributes:
46 _config: Configuration dictionary for the uploader
48 Example:
49 >>> class MyUploader(BaseUploader):
50 ... def upload(self, source_path: Path, destination: str) -> None:
51 ... # Implementation
52 ... pass
53 ... def get_uploader_name(self) -> str:
54 ... return "My Uploader"
55 """
57 # ////////////////////////////////////////////////
58 # INITIALIZATION
59 # ////////////////////////////////////////////////
61 def __init__(self, config: dict[str, Any] | None = None) -> None:
62 """
63 Initialize the uploader with configuration.
65 Args:
66 config: Configuration dictionary (default: None)
68 Note:
69 Subclasses should call super().__init__(config) to initialize
70 configuration and validation.
71 """
72 self._config = config or {}
73 self._validate_config()
75 # ////////////////////////////////////////////////
76 # ABSTRACT METHODS
77 # ////////////////////////////////////////////////
79 @abstractmethod
80 def upload(self, source_path: Path, destination: str) -> None:
81 """
82 Upload a file or directory to the destination.
84 Args:
85 source_path: Path to the source file or directory
86 destination: Destination path or URL
88 Raises:
89 UploadError: If upload fails
91 Note:
92 Subclasses must implement this method to define upload behavior.
93 """
95 @abstractmethod
96 def get_uploader_name(self) -> str:
97 """
98 Get the name of this uploader.
100 Returns:
101 str: Human-readable name of the uploader
103 Note:
104 Used for identification purposes.
105 """
107 # ////////////////////////////////////////////////
108 # PUBLIC METHODS
109 # ////////////////////////////////////////////////
111 def download(self, remote_source: str, local_dir: Path) -> None: # noqa: ARG002
112 """
113 Download a remote tree into ``local_dir``.
115 Default implementation: backends that host an update channel override
116 this. Others reject it.
118 Args:
119 remote_source: Remote source (path, URL or prefix) to fetch.
120 local_dir: Local directory to populate with the downloaded tree.
122 Raises:
123 UploadError: Always, unless overridden by a subclass.
124 """
125 raise UploadError(f"{self.get_uploader_name()} does not support download")
127 # ////////////////////////////////////////////////
128 # VALIDATION METHODS
129 # ////////////////////////////////////////////////
131 def _validate_config(self) -> None: # noqa: B027
132 """
133 Validate uploader configuration.
135 Base implementation does nothing. Subclasses should override this
136 method to perform specific validation for their configuration.
138 Raises:
139 UploadError: If configuration is invalid (in subclasses)
141 Note:
142 This method is intentionally empty in the base class.
143 """
144 # Base implementation intentionally empty - subclasses should override
146 def _validate_source_path(self, source_path: Path) -> None:
147 """
148 Validate that the source path exists and is accessible.
150 Args:
151 source_path: Path to validate
153 Raises:
154 UploadError: If source path is invalid
156 Note:
157 Validation is handled at protocol level to keep this port independent
158 from uploader utility helpers.
159 """
160 if not source_path.exists(): 160 ↛ 161line 160 didn't jump to line 161 because the condition on line 160 was never true
161 raise UploadError(f"Source path does not exist: {source_path}")
162 if not source_path.is_file() and not source_path.is_dir(): 162 ↛ 163line 162 didn't jump to line 163 because the condition on line 162 was never true
163 raise UploadError(f"Source path is not a file or directory: {source_path}")