Coverage for src\baobab_web_api_caller\config\service_config.py: 86%
43 statements
« prev ^ index » next coverage.py v7.10.3, created at 2026-03-21 12:10 +0100
« prev ^ index » next coverage.py v7.10.3, created at 2026-03-21 12:10 +0100
1"""Configuration d'un service distant."""
3from __future__ import annotations
5from dataclasses import dataclass
6from types import MappingProxyType
7from typing import Mapping
8from urllib.parse import urlparse
10from baobab_web_api_caller.auth.authentication_strategy import AuthenticationStrategy
11from baobab_web_api_caller.auth.no_authentication_strategy import NoAuthenticationStrategy
12from baobab_web_api_caller.config.rate_limit_policy import RateLimitPolicy
13from baobab_web_api_caller.config.retry_policy import RetryPolicy
14from baobab_web_api_caller.exceptions.configuration_exception import ConfigurationException
15from baobab_web_api_caller.utils.mapping_utils import freeze_str_mapping
18@dataclass(frozen=True, slots=True)
19class ServiceConfig:
20 """Configuration transverse d'un service distant.
22 :param base_url: Base URL du service (ex: ``https://api.example.com``).
23 :type base_url: str
24 :param default_headers: Headers appliqués par défaut à chaque requête.
25 :type default_headers: Mapping[str, str]
26 :param authentication_strategy: Stratégie d'authentification à appliquer.
27 :type authentication_strategy: AuthenticationStrategy
28 :param default_timeout_seconds: Timeout par défaut en secondes.
29 :type default_timeout_seconds: float | None
30 :param retry_policy: Politique de retry.
31 :type retry_policy: RetryPolicy
32 :param rate_limit_policy: Politique de throttling.
33 :type rate_limit_policy: RateLimitPolicy
34 :raises ConfigurationException: Si la configuration est invalide.
35 """
37 base_url: str
38 default_headers: Mapping[str, str] = MappingProxyType({})
39 authentication_strategy: AuthenticationStrategy = NoAuthenticationStrategy()
40 default_timeout_seconds: float | None = None
41 retry_policy: RetryPolicy = RetryPolicy()
42 rate_limit_policy: RateLimitPolicy = RateLimitPolicy()
44 def __post_init__(self) -> None:
45 normalized = self._normalize_and_validate_base_url(self.base_url)
46 object.__setattr__(self, "base_url", normalized)
47 object.__setattr__(
48 self, "default_headers", freeze_str_mapping(self.default_headers, "default_headers")
49 )
51 if not isinstance(self.authentication_strategy, AuthenticationStrategy): 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true
52 raise ConfigurationException(
53 "authentication_strategy must be an AuthenticationStrategy"
54 )
56 if self.default_timeout_seconds is not None and self.default_timeout_seconds <= 0:
57 raise ConfigurationException("default_timeout_seconds must be positive when provided")
59 if not isinstance(self.retry_policy, RetryPolicy): 59 ↛ 60line 59 didn't jump to line 60 because the condition on line 59 was never true
60 raise ConfigurationException("retry_policy must be a RetryPolicy")
61 if not isinstance(self.rate_limit_policy, RateLimitPolicy): 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true
62 raise ConfigurationException("rate_limit_policy must be a RateLimitPolicy")
64 @staticmethod
65 def _normalize_and_validate_base_url(base_url: str) -> str:
66 if not isinstance(base_url, str) or base_url.strip() == "": 66 ↛ 67line 66 didn't jump to line 67 because the condition on line 66 was never true
67 raise ConfigurationException("base_url must be a non-empty string")
69 trimmed = base_url.strip()
70 parsed = urlparse(trimmed)
71 if parsed.scheme not in {"http", "https"}:
72 raise ConfigurationException("base_url must start with http:// or https://")
73 if parsed.netloc == "":
74 raise ConfigurationException("base_url must include a host")
76 normalized = trimmed.rstrip("/")
77 return normalized