Coverage for src\baobab_web_api_caller\core\baobab_response.py: 86%

39 statements  

« prev     ^ index     » next       coverage.py v7.10.3, created at 2026-03-21 12:10 +0100

1"""Modèle de réponse HTTP.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass 

6from types import MappingProxyType 

7from typing import Mapping 

8 

9from baobab_web_api_caller.exceptions.configuration_exception import ConfigurationException 

10 

11 

12@dataclass(frozen=True, slots=True) 

13class BaobabResponse: 

14 """Représentation typée d'une réponse HTTP. 

15 

16 :param status_code: Code de statut HTTP. 

17 :type status_code: int 

18 :param headers: En-têtes HTTP. 

19 :type headers: Mapping[str, str] 

20 :param text: Contenu texte (si disponible). 

21 :type text: str | None 

22 :param content: Contenu binaire brut (si disponible). 

23 :type content: bytes | None 

24 :param json_data: Contenu JSON déjà décodé (si disponible). 

25 :type json_data: object | None 

26 :raises ConfigurationException: Si les paramètres de la réponse sont invalides. 

27 """ 

28 

29 status_code: int 

30 headers: Mapping[str, str] 

31 text: str | None = None 

32 content: bytes | bytearray | None = None 

33 json_data: object | None = None 

34 

35 def __post_init__(self) -> None: 

36 if not isinstance(self.status_code, int): 

37 raise ConfigurationException("status_code must be an int") 

38 if self.status_code < 100 or self.status_code > 599: 

39 raise ConfigurationException("status_code must be between 100 and 599") 

40 

41 object.__setattr__(self, "headers", self._freeze_headers(self.headers)) 

42 

43 if self.text is not None and not isinstance(self.text, str): 43 ↛ 44line 43 didn't jump to line 44 because the condition on line 43 was never true

44 raise ConfigurationException("text must be a string when provided") 

45 if isinstance(self.content, bytearray): 

46 object.__setattr__(self, "content", bytes(self.content)) 

47 if self.content is not None and not isinstance(self.content, bytes): 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true

48 raise ConfigurationException("content must be bytes when provided") 

49 

50 @staticmethod 

51 def _freeze_headers(headers: Mapping[str, str]) -> Mapping[str, str]: 

52 if not isinstance(headers, Mapping): 52 ↛ 53line 52 didn't jump to line 53 because the condition on line 52 was never true

53 raise ConfigurationException("headers must be a mapping") 

54 

55 frozen: dict[str, str] = {} 

56 for k, v in headers.items(): 

57 if not isinstance(k, str) or k.strip() == "": 57 ↛ 58line 57 didn't jump to line 58 because the condition on line 57 was never true

58 raise ConfigurationException("headers keys must be non-empty strings") 

59 if not isinstance(v, str): 

60 raise ConfigurationException("headers values must be strings") 

61 frozen[k] = v 

62 return MappingProxyType(frozen) 

63 

64 @property 

65 def is_success(self) -> bool: 

66 """Indique si le status code correspond à une réussite HTTP (2xx).""" 

67 

68 return 200 <= self.status_code <= 299