baobab-altered-api 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. baobab_altered_api/__init__.py +33 -0
  2. baobab_altered_api/auth/__init__.py +5 -0
  3. baobab_altered_api/auth/altered_auth_factory.py +36 -0
  4. baobab_altered_api/clients/__init__.py +17 -0
  5. baobab_altered_api/clients/altered_client.py +137 -0
  6. baobab_altered_api/clients/api_client_config_alignment.py +35 -0
  7. baobab_altered_api/clients/async_altered_client.py +141 -0
  8. baobab_altered_api/clients/transport_port_validator.py +39 -0
  9. baobab_altered_api/config/__init__.py +17 -0
  10. baobab_altered_api/config/altered_api_client_config_builder.py +81 -0
  11. baobab_altered_api/config/altered_api_config.py +193 -0
  12. baobab_altered_api/config/altered_locale.py +27 -0
  13. baobab_altered_api/config/altered_network_parameter_validator.py +124 -0
  14. baobab_altered_api/config/env_loader.py +135 -0
  15. baobab_altered_api/exceptions/__init__.py +41 -0
  16. baobab_altered_api/exceptions/altered_api_error.py +44 -0
  17. baobab_altered_api/exceptions/altered_auth_error.py +29 -0
  18. baobab_altered_api/exceptions/altered_config_error.py +8 -0
  19. baobab_altered_api/exceptions/altered_forbidden_error.py +26 -0
  20. baobab_altered_api/exceptions/altered_http_error.py +13 -0
  21. baobab_altered_api/exceptions/altered_json_decode_error.py +31 -0
  22. baobab_altered_api/exceptions/altered_mapping_error.py +29 -0
  23. baobab_altered_api/exceptions/altered_network_error.py +26 -0
  24. baobab_altered_api/exceptions/altered_not_found_error.py +26 -0
  25. baobab_altered_api/exceptions/altered_open_data_download_error.py +26 -0
  26. baobab_altered_api/exceptions/altered_rate_limit_error.py +38 -0
  27. baobab_altered_api/exceptions/altered_service_unavailable_error.py +30 -0
  28. baobab_altered_api/exceptions/altered_timeout_error.py +26 -0
  29. baobab_altered_api/exceptions/altered_transport_error.py +29 -0
  30. baobab_altered_api/exceptions/error_mapper.py +136 -0
  31. baobab_altered_api/internal/__init__.py +1 -0
  32. baobab_altered_api/internal/altered_api_logger.py +65 -0
  33. baobab_altered_api/internal/altered_log_redactor.py +144 -0
  34. baobab_altered_api/internal/open_data_file_io.py +61 -0
  35. baobab_altered_api/internal/query_params.py +144 -0
  36. baobab_altered_api/mappers/__init__.py +13 -0
  37. baobab_altered_api/mappers/altered_cards_json_mapper.py +94 -0
  38. baobab_altered_api/mappers/altered_common_models_json_mapper.py +321 -0
  39. baobab_altered_api/mappers/altered_deck_json_mapper.py +62 -0
  40. baobab_altered_api/mappers/altered_marketplace_offer_json_mapper.py +60 -0
  41. baobab_altered_api/mappers/altered_open_data_json_mapper.py +132 -0
  42. baobab_altered_api/mappers/altered_pagination_json_mapper.py +92 -0
  43. baobab_altered_api/mappers/altered_referentials_json_mapper.py +89 -0
  44. baobab_altered_api/models/__init__.py +25 -0
  45. baobab_altered_api/models/altered_card_summary.py +44 -0
  46. baobab_altered_api/models/altered_card_type_reference.py +16 -0
  47. baobab_altered_api/models/altered_deck.py +38 -0
  48. baobab_altered_api/models/altered_faction.py +33 -0
  49. baobab_altered_api/models/altered_faction_reference.py +18 -0
  50. baobab_altered_api/models/altered_image.py +29 -0
  51. baobab_altered_api/models/altered_json_payload.py +19 -0
  52. baobab_altered_api/models/altered_localized_label.py +29 -0
  53. baobab_altered_api/models/altered_marketplace_offer.py +29 -0
  54. baobab_altered_api/models/altered_open_data_card_entry.py +25 -0
  55. baobab_altered_api/models/altered_open_data_faction_node.py +22 -0
  56. baobab_altered_api/models/altered_open_data_set_node.py +22 -0
  57. baobab_altered_api/models/altered_open_data_snapshot.py +37 -0
  58. baobab_altered_api/models/altered_page.py +29 -0
  59. baobab_altered_api/models/altered_pagination_params.py +58 -0
  60. baobab_altered_api/models/altered_rarity.py +29 -0
  61. baobab_altered_api/models/altered_rarity_reference.py +16 -0
  62. baobab_altered_api/models/altered_ref.py +27 -0
  63. baobab_altered_api/models/altered_set.py +33 -0
  64. baobab_altered_api/models/altered_set_reference.py +25 -0
  65. baobab_altered_api/py.typed +0 -0
  66. baobab_altered_api/resources/__init__.py +37 -0
  67. baobab_altered_api/resources/altered_api_paths.py +33 -0
  68. baobab_altered_api/resources/async_cards_resource.py +113 -0
  69. baobab_altered_api/resources/async_decks_resource.py +34 -0
  70. baobab_altered_api/resources/async_marketplace_resource.py +21 -0
  71. baobab_altered_api/resources/async_open_data_downloader.py +78 -0
  72. baobab_altered_api/resources/async_open_data_resource.py +86 -0
  73. baobab_altered_api/resources/async_referentials_resource.py +62 -0
  74. baobab_altered_api/resources/card_search_filters.py +65 -0
  75. baobab_altered_api/resources/cards_resource.py +146 -0
  76. baobab_altered_api/resources/decks_resource.py +34 -0
  77. baobab_altered_api/resources/marketplace_resource.py +63 -0
  78. baobab_altered_api/resources/open_data_access.py +9 -0
  79. baobab_altered_api/resources/open_data_catalog.py +92 -0
  80. baobab_altered_api/resources/open_data_downloader.py +99 -0
  81. baobab_altered_api/resources/open_data_reader.py +37 -0
  82. baobab_altered_api/resources/open_data_resource.py +89 -0
  83. baobab_altered_api/resources/referential_list_response_parser.py +43 -0
  84. baobab_altered_api/resources/referentials_resource.py +62 -0
  85. baobab_altered_api/transports/__init__.py +13 -0
  86. baobab_altered_api/transports/altered_async_transport.py +144 -0
  87. baobab_altered_api/transports/altered_rate_limit_middleware.py +91 -0
  88. baobab_altered_api/transports/altered_rate_limit_middleware_async.py +88 -0
  89. baobab_altered_api/transports/altered_sync_transport.py +137 -0
  90. baobab_altered_api/transports/response_handler.py +92 -0
  91. baobab_altered_api-1.0.0.dist-info/METADATA +102 -0
  92. baobab_altered_api-1.0.0.dist-info/RECORD +94 -0
  93. baobab_altered_api-1.0.0.dist-info/WHEEL +4 -0
  94. baobab_altered_api-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,33 @@
1
+ """Point d’entrée public du package ``baobab_altered_api``.
2
+
3
+ L’import de ce module ne doit pas créer de client HTTP, lire la configuration
4
+ depuis l’environnement ni configurer le logging global. Les classes client
5
+ exposées ici ne réalisent aucune requête tant qu’aucune méthode HTTP n’est
6
+ appelée sur ``AlteredClient.http`` ou ``AsyncAlteredClient.http``.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from baobab_altered_api.auth import AlteredAuthFactory
12
+ from baobab_altered_api.clients import AlteredClient, AsyncAlteredClient
13
+ from baobab_altered_api.config import (
14
+ AlteredApiConfig,
15
+ AlteredLocale,
16
+ load_config_from_env,
17
+ to_api_client_config,
18
+ )
19
+ from baobab_altered_api.exceptions import AlteredConfigError
20
+
21
+ __version__ = "1.0.0"
22
+
23
+ __all__: tuple[str, ...] = (
24
+ "__version__",
25
+ "AlteredApiConfig",
26
+ "AlteredAuthFactory",
27
+ "AlteredClient",
28
+ "AlteredConfigError",
29
+ "AlteredLocale",
30
+ "AsyncAlteredClient",
31
+ "load_config_from_env",
32
+ "to_api_client_config",
33
+ )
@@ -0,0 +1,5 @@
1
+ """Authentification Altered compatible avec le socle ``baobab-api-call``."""
2
+
3
+ from baobab_altered_api.auth.altered_auth_factory import AlteredAuthFactory
4
+
5
+ __all__ = ("AlteredAuthFactory",)
@@ -0,0 +1,36 @@
1
+ """Fabrique d'authentification pour le socle ``baobab-api-call``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from baobab_api_call import ApiKeyAuth, AuthStrategy, BearerAuth
6
+
7
+ from baobab_altered_api.config.altered_api_config import AlteredApiConfig
8
+
9
+
10
+ class AlteredAuthFactory:
11
+ """Produit une stratégie ``AuthStrategy`` du socle."""
12
+
13
+ def create(self, config: AlteredApiConfig) -> AuthStrategy | None:
14
+ """Retourne la stratégie d'authentification ou ``None`` si aucune n'est requise.
15
+
16
+ Priorité : ``bearer_token`` puis ``api_key`` (cahier des charges).
17
+
18
+ :param config: Configuration Altered.
19
+ :return: ``BearerAuth``, ``ApiKeyAuth`` ou ``None``.
20
+ """
21
+ if config.bearer_token is not None:
22
+ return BearerAuth(token=config.bearer_token)
23
+ if config.api_key is not None:
24
+ return ApiKeyAuth(
25
+ api_key=config.api_key,
26
+ header_name=config.api_key_header_name,
27
+ scheme=config.api_key_query_param,
28
+ )
29
+ return None
30
+
31
+ def __repr__(self) -> str:
32
+ """Représentation de débogage sans données sensibles.
33
+
34
+ :return: Identifiant fixe de la fabrique (aucun secret ni état).
35
+ """
36
+ return "AlteredAuthFactory()"
@@ -0,0 +1,17 @@
1
+ """Clients publics synchrones et asynchrones.
2
+
3
+ Les classes ci-dessous constituent la surface publique de ce sous-package ;
4
+ les modules internes (validateurs, alignement de config) ne sont pas
5
+ ré-exportés ici.
6
+
7
+ Imports supportés :
8
+
9
+ - ``from baobab_altered_api.clients import AlteredClient, AsyncAlteredClient``
10
+ - ``from baobab_altered_api import AlteredClient, AsyncAlteredClient`` (re-export
11
+ racine, voir ``baobab_altered_api.__all__``).
12
+ """
13
+
14
+ from baobab_altered_api.clients.altered_client import AlteredClient
15
+ from baobab_altered_api.clients.async_altered_client import AsyncAlteredClient
16
+
17
+ __all__: tuple[str, ...] = ("AlteredClient", "AsyncAlteredClient")
@@ -0,0 +1,137 @@
1
+ """Façade synchrone publique pour les appels HTTP Altered."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from types import TracebackType
6
+
7
+ from baobab_api_call import ApiClientConfig, SyncApiClient
8
+ from baobab_api_call.transports.sync_transport import SyncTransport
9
+
10
+ from baobab_altered_api.clients.api_client_config_alignment import (
11
+ ApiClientConfigAlignment,
12
+ )
13
+ from baobab_altered_api.clients.transport_port_validator import TransportPortValidator
14
+ from baobab_altered_api.config.altered_api_client_config_builder import (
15
+ AlteredApiClientConfigBuilder,
16
+ )
17
+ from baobab_altered_api.config.altered_api_config import AlteredApiConfig
18
+ from baobab_altered_api.resources.cards_resource import CardsResource
19
+ from baobab_altered_api.resources.decks_resource import DecksResource
20
+ from baobab_altered_api.resources.marketplace_resource import MarketplaceResource
21
+ from baobab_altered_api.resources.open_data_resource import OpenDataResource
22
+ from baobab_altered_api.resources.referentials_resource import ReferentialsResource
23
+ from baobab_altered_api.transports.altered_sync_transport import AlteredSyncTransport
24
+
25
+
26
+ class AlteredClient:
27
+ """Façade synchrone : assemble ``AlteredApiConfig`` et ``SyncApiClient``.
28
+
29
+ Aucune requête réseau n'est effectuée à l'instanciation.
30
+
31
+ :param config: Configuration applicative Altered (immuable) ; sert de
32
+ référence métier et, par défaut, de source pour ``ApiClientConfig``.
33
+ :param transport: Port ``SyncTransport`` avec ``send`` synchrone ; si
34
+ ``None``, un
35
+ :class:`~baobab_altered_api.transports.altered_sync_transport.AlteredSyncTransport`
36
+ est créé et détenu par cette façade.
37
+ :param api_client_config: Configuration du socle ``baobab-api-call`` ; si
38
+ fournie, elle remplace celle produite par ``AlteredApiClientConfigBuilder``
39
+ pour le client HTTP. Son ``base_url`` doit coïncider avec
40
+ ``config.base_url`` (normalisation par ``rstrip('/')``).
41
+
42
+ **Cycle de vie** : :meth:`close` délègue à :class:`~baobab_api_call.SyncApiClient`,
43
+ qui appelle ``close()`` sur le transport s'il est exposé. Un transport
44
+ **injecté** subit donc aussi ``close()`` : ne pas partager un transport fermé
45
+ entre plusieurs clients sans le recréer. Un transport **créé par défaut**
46
+ est fermé avec la façade.
47
+ """
48
+
49
+ def __init__(
50
+ self,
51
+ config: AlteredApiConfig,
52
+ *,
53
+ transport: SyncTransport | None = None,
54
+ api_client_config: ApiClientConfig | None = None,
55
+ ) -> None:
56
+ self._altered_config: AlteredApiConfig = config
57
+ if api_client_config is not None:
58
+ ApiClientConfigAlignment.assert_matching_base_url(config, api_client_config)
59
+ api_config = api_client_config
60
+ else:
61
+ api_config = AlteredApiClientConfigBuilder.build(config)
62
+ self._transport: SyncTransport = (
63
+ AlteredSyncTransport(
64
+ default_timeout_seconds=float(config.timeout_seconds),
65
+ )
66
+ if transport is None
67
+ else transport
68
+ )
69
+ TransportPortValidator.verify_sync_send(self._transport)
70
+ self._client: SyncApiClient = SyncApiClient(api_config, self._transport)
71
+ self._cards: CardsResource | None = None
72
+ self._decks: DecksResource | None = None
73
+ self._referentials: ReferentialsResource | None = None
74
+ self._marketplace: MarketplaceResource | None = None
75
+ self._open_data: OpenDataResource | None = None
76
+
77
+ @property
78
+ def config(self) -> AlteredApiConfig:
79
+ """Configuration Altered (lecture seule)."""
80
+ return self._altered_config
81
+
82
+ @property
83
+ def http(self) -> SyncApiClient:
84
+ """Client HTTP du socle (accès direct, en attendant les ressources métier)."""
85
+ return self._client
86
+
87
+ @property
88
+ def cards(self) -> CardsResource:
89
+ """Ressource cartes (lecture)."""
90
+ if self._cards is None:
91
+ self._cards = CardsResource(self._client)
92
+ return self._cards
93
+
94
+ @property
95
+ def decks(self) -> DecksResource:
96
+ """Ressource decks (lecture)."""
97
+ if self._decks is None:
98
+ self._decks = DecksResource(self._client)
99
+ return self._decks
100
+
101
+ @property
102
+ def referentials(self) -> ReferentialsResource:
103
+ """Référentiels (sets, factions, etc.)."""
104
+ if self._referentials is None:
105
+ self._referentials = ReferentialsResource(self._client)
106
+ return self._referentials
107
+
108
+ @property
109
+ def marketplace(self) -> MarketplaceResource:
110
+ """Marketplace lecture seule."""
111
+ if self._marketplace is None:
112
+ self._marketplace = MarketplaceResource(self._client)
113
+ return self._marketplace
114
+
115
+ @property
116
+ def open_data(self) -> OpenDataResource:
117
+ """Open Data : téléchargement explicite et lecture locale."""
118
+ if self._open_data is None:
119
+ self._open_data = OpenDataResource(self._altered_config, self._client)
120
+ return self._open_data
121
+
122
+ def close(self) -> None:
123
+ """Ferme le client du socle et le transport sous-jacent si applicable."""
124
+ self._client.close()
125
+
126
+ def __enter__(self) -> AlteredClient:
127
+ """Context manager synchrone : retourne ``self``."""
128
+ return self
129
+
130
+ def __exit__(
131
+ self,
132
+ exc_type: type[BaseException] | None,
133
+ exc_val: BaseException | None,
134
+ exc_tb: TracebackType | None,
135
+ ) -> None:
136
+ """Ferme la façade à la sortie du bloc ``with``."""
137
+ self.close()
@@ -0,0 +1,35 @@
1
+ """Alignement entre ``AlteredApiConfig`` et ``ApiClientConfig`` du socle."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from baobab_api_call import ApiClientConfig
6
+
7
+ from baobab_altered_api.config.altered_api_config import AlteredApiConfig
8
+ from baobab_altered_api.exceptions.altered_config_error import AlteredConfigError
9
+
10
+
11
+ class ApiClientConfigAlignment:
12
+ """Vérifie la cohérence minimale entre ``AlteredApiConfig`` et ``ApiClientConfig``.
13
+
14
+ Utilisé lorsque l'appelant injecte une configuration du socle via
15
+ ``api_client_config`` sur les façades publiques.
16
+ """
17
+
18
+ @staticmethod
19
+ def assert_matching_base_url(
20
+ altered: AlteredApiConfig,
21
+ api_cfg: ApiClientConfig,
22
+ ) -> None:
23
+ """Vérifie que les URL de base HTTP concordent après normalisation.
24
+
25
+ :param altered: Configuration applicative Altered (référence métier).
26
+ :param api_cfg: Configuration ``baobab-api-call`` injectée.
27
+ :raises AlteredConfigError: Si ``base_url`` diffère après ``rstrip('/')``.
28
+ """
29
+ left = altered.base_url.rstrip("/")
30
+ right = api_cfg.base_url.rstrip("/")
31
+ if left != right:
32
+ raise AlteredConfigError(
33
+ "api_client_config.base_url must match config.base_url "
34
+ f"(got {right!r} vs config {left!r})",
35
+ )
@@ -0,0 +1,141 @@
1
+ """Façade asynchrone publique pour les appels HTTP Altered."""
2
+
3
+ # pylint: disable=duplicate-code
4
+ # Parallèle volontaire avec ``altered_client`` (FEAT-004 / BL-004-002).
5
+
6
+ from __future__ import annotations
7
+
8
+ from types import TracebackType
9
+
10
+ from baobab_api_call import ApiClientConfig, AsyncApiClient
11
+ from baobab_api_call.transports.async_transport import AsyncTransport
12
+
13
+ from baobab_altered_api.clients.api_client_config_alignment import (
14
+ ApiClientConfigAlignment,
15
+ )
16
+ from baobab_altered_api.clients.transport_port_validator import TransportPortValidator
17
+ from baobab_altered_api.config.altered_api_client_config_builder import (
18
+ AlteredApiClientConfigBuilder,
19
+ )
20
+ from baobab_altered_api.config.altered_api_config import AlteredApiConfig
21
+ from baobab_altered_api.resources.async_cards_resource import AsyncCardsResource
22
+ from baobab_altered_api.resources.async_decks_resource import AsyncDecksResource
23
+ from baobab_altered_api.resources.async_marketplace_resource import (
24
+ AsyncMarketplaceResource,
25
+ )
26
+ from baobab_altered_api.resources.async_open_data_resource import AsyncOpenDataResource
27
+ from baobab_altered_api.resources.async_referentials_resource import (
28
+ AsyncReferentialsResource,
29
+ )
30
+ from baobab_altered_api.transports.altered_async_transport import AlteredAsyncTransport
31
+
32
+
33
+ class AsyncAlteredClient:
34
+ """Façade asynchrone : assemble ``AlteredApiConfig`` et ``AsyncApiClient``.
35
+
36
+ Aucune requête réseau n'est effectuée à l'instanciation.
37
+
38
+ :param config: Configuration applicative Altered (immuable) ; sert de
39
+ référence métier et, par défaut, de source pour ``ApiClientConfig``.
40
+ :param transport: Port ``AsyncTransport`` avec ``send`` asynchrone ; si
41
+ ``None``, un
42
+ :class:`~baobab_altered_api.transports.altered_async_transport.AlteredAsyncTransport`
43
+ est créé et détenu par cette façade.
44
+ :param api_client_config: Configuration du socle ; si fournie, elle remplace
45
+ celle du builder. Son ``base_url`` doit coïncider avec ``config.base_url``.
46
+
47
+ **Cycle de vie** : :meth:`aclose` délègue à :class:`~baobab_api_call.AsyncApiClient`
48
+ qui appelle ``aclose()`` sur le transport s'il est exposé. Un transport
49
+ **injecté** est fermé par cette chaîne : ne pas réutiliser un transport fermé
50
+ sans le recréer.
51
+ """
52
+
53
+ def __init__(
54
+ self,
55
+ config: AlteredApiConfig,
56
+ *,
57
+ transport: AsyncTransport | None = None,
58
+ api_client_config: ApiClientConfig | None = None,
59
+ ) -> None:
60
+ self._altered_config: AlteredApiConfig = config
61
+ if api_client_config is not None:
62
+ ApiClientConfigAlignment.assert_matching_base_url(config, api_client_config)
63
+ api_config = api_client_config
64
+ else:
65
+ api_config = AlteredApiClientConfigBuilder.build(config)
66
+ self._transport: AsyncTransport = (
67
+ AlteredAsyncTransport(
68
+ default_timeout_seconds=float(config.timeout_seconds),
69
+ )
70
+ if transport is None
71
+ else transport
72
+ )
73
+ TransportPortValidator.verify_async_send(self._transport)
74
+ self._client: AsyncApiClient = AsyncApiClient(api_config, self._transport)
75
+ self._cards: AsyncCardsResource | None = None
76
+ self._decks: AsyncDecksResource | None = None
77
+ self._referentials: AsyncReferentialsResource | None = None
78
+ self._marketplace: AsyncMarketplaceResource | None = None
79
+ self._open_data: AsyncOpenDataResource | None = None
80
+
81
+ @property
82
+ def config(self) -> AlteredApiConfig:
83
+ """Configuration Altered (lecture seule)."""
84
+ return self._altered_config
85
+
86
+ @property
87
+ def http(self) -> AsyncApiClient:
88
+ """Client HTTP async du socle (accès direct, en attendant les ressources)."""
89
+ return self._client
90
+
91
+ @property
92
+ def cards(self) -> AsyncCardsResource:
93
+ """Ressource cartes async."""
94
+ if self._cards is None:
95
+ self._cards = AsyncCardsResource(self._client)
96
+ return self._cards
97
+
98
+ @property
99
+ def decks(self) -> AsyncDecksResource:
100
+ """Ressource decks async."""
101
+ if self._decks is None:
102
+ self._decks = AsyncDecksResource(self._client)
103
+ return self._decks
104
+
105
+ @property
106
+ def referentials(self) -> AsyncReferentialsResource:
107
+ """Référentiels async."""
108
+ if self._referentials is None:
109
+ self._referentials = AsyncReferentialsResource(self._client)
110
+ return self._referentials
111
+
112
+ @property
113
+ def marketplace(self) -> AsyncMarketplaceResource:
114
+ """Marketplace async (lecture seule)."""
115
+ if self._marketplace is None:
116
+ self._marketplace = AsyncMarketplaceResource(self._client)
117
+ return self._marketplace
118
+
119
+ @property
120
+ def open_data(self) -> AsyncOpenDataResource:
121
+ """Open Data async : téléchargement explicite et lecture locale."""
122
+ if self._open_data is None:
123
+ self._open_data = AsyncOpenDataResource(self._altered_config, self._client)
124
+ return self._open_data
125
+
126
+ async def aclose(self) -> None:
127
+ """Ferme le client du socle et le transport sous-jacent si applicable."""
128
+ await self._client.aclose()
129
+
130
+ async def __aenter__(self) -> AsyncAlteredClient:
131
+ """Context manager async : retourne ``self``."""
132
+ return self
133
+
134
+ async def __aexit__(
135
+ self,
136
+ exc_type: type[BaseException] | None,
137
+ exc_val: BaseException | None,
138
+ exc_tb: TracebackType | None,
139
+ ) -> None:
140
+ """Ferme la façade à la sortie du bloc ``async with``."""
141
+ await self.aclose()
@@ -0,0 +1,39 @@
1
+ """Vérifications runtime des ports transport pour les façades clients."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ from typing import Any
7
+
8
+
9
+ class TransportPortValidator:
10
+ """Rejette les transports dont ``send`` ne correspond pas au mode sync/async.
11
+
12
+ Les protocoles ``SyncTransport`` / ``AsyncTransport`` du socle ne sont pas
13
+ vérifiables statiquement à l'exécution : ces garde-fous évitent les erreurs
14
+ tardives lorsqu'un fake ou un adaptateur incorrect est injecté.
15
+ """
16
+
17
+ @staticmethod
18
+ def verify_sync_send(transport: Any) -> None:
19
+ """Lève si ``send`` est une coroutine (transport async sur client sync)."""
20
+ send = getattr(transport, "send", None)
21
+ if send is None or not callable(send):
22
+ raise TypeError("transport must expose a callable send() method")
23
+ if inspect.iscoroutinefunction(send):
24
+ raise TypeError(
25
+ "This transport uses async send(); use AsyncAlteredClient instead "
26
+ "of AlteredClient."
27
+ )
28
+
29
+ @staticmethod
30
+ def verify_async_send(transport: Any) -> None:
31
+ """Lève si ``send`` n'est pas une coroutine (transport sync sur async)."""
32
+ send = getattr(transport, "send", None)
33
+ if send is None or not callable(send):
34
+ raise TypeError("transport must expose a callable send() method")
35
+ if not inspect.iscoroutinefunction(send):
36
+ raise TypeError(
37
+ "This transport uses synchronous send(); use AlteredClient instead "
38
+ "of AsyncAlteredClient."
39
+ )
@@ -0,0 +1,17 @@
1
+ """Configuration Altered et pont vers ``baobab-api-call``."""
2
+
3
+ from baobab_altered_api.config.altered_api_config import AlteredApiConfig
4
+ from baobab_altered_api.config.altered_api_client_config_builder import (
5
+ AlteredApiClientConfigBuilder,
6
+ to_api_client_config,
7
+ )
8
+ from baobab_altered_api.config.altered_locale import AlteredLocale
9
+ from baobab_altered_api.config.env_loader import load_config_from_env
10
+
11
+ __all__ = (
12
+ "AlteredApiClientConfigBuilder",
13
+ "AlteredApiConfig",
14
+ "AlteredLocale",
15
+ "load_config_from_env",
16
+ "to_api_client_config",
17
+ )
@@ -0,0 +1,81 @@
1
+ """Conversion ``AlteredApiConfig`` vers ``ApiClientConfig`` (baobab-api-call)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from baobab_api_call import ApiClientConfig, RetryPolicy
6
+ from baobab_api_call.middleware import Middleware, MiddlewareAsync
7
+
8
+ from baobab_altered_api.config.altered_api_config import AlteredApiConfig
9
+
10
+
11
+ class AlteredApiClientConfigBuilder:
12
+ """Construit un ``ApiClientConfig`` à partir d'un ``AlteredApiConfig``."""
13
+
14
+ @staticmethod
15
+ def build(config: AlteredApiConfig) -> ApiClientConfig:
16
+ """Produit la configuration du socle ``baobab-api-call``.
17
+
18
+ Les en-têtes par défaut incluent ``User-Agent``, ``Accept`` et
19
+ ``Accept-Language``. ``timeout_seconds`` et la :class:`RetryPolicy`
20
+ (tentatives, backoff initial, jitter) sont alignés sur le socle.
21
+ Si ``rate_limit_per_second`` est défini, des middlewares sync et async
22
+ espacent les envois côté client (pacing local ; voir
23
+ ``altered_rate_limit_middleware``). Les réponses ``429`` serveur restent
24
+ traitées par le socle et le mapping d'exceptions Altered.
25
+
26
+ :param config: Source Altered validée.
27
+ :return: Instance immuable du socle.
28
+ """
29
+ default_headers: dict[str, str] = {
30
+ "User-Agent": config.user_agent,
31
+ "Accept": "application/json",
32
+ "Accept-Language": config.locale.accept_language_value(),
33
+ }
34
+ retry_policy = RetryPolicy(
35
+ max_attempts=int(config.retry_max_attempts),
36
+ backoff_initial=float(config.retry_backoff_seconds),
37
+ jitter=bool(config.retry_jitter),
38
+ )
39
+ # Import local pour éviter un cycle config → auth → config.
40
+ # pylint: disable-next=import-outside-toplevel
41
+ from baobab_altered_api.auth.altered_auth_factory import (
42
+ AlteredAuthFactory,
43
+ )
44
+
45
+ # Import local : middlewares dépendent du transport, pas de l'auth.
46
+ # pylint: disable-next=import-outside-toplevel
47
+ from baobab_altered_api.transports.altered_rate_limit_middleware import (
48
+ AlteredRateLimitMiddleware,
49
+ )
50
+
51
+ # pylint: disable-next=import-outside-toplevel
52
+ from baobab_altered_api.transports.altered_rate_limit_middleware_async import (
53
+ AlteredRateLimitMiddlewareAsync,
54
+ )
55
+
56
+ auth_strategy = AlteredAuthFactory().create(config)
57
+ sync_middlewares: tuple[Middleware, ...] = ()
58
+ async_middlewares: tuple[MiddlewareAsync, ...] = ()
59
+ if config.rate_limit_per_second is not None:
60
+ rps = float(config.rate_limit_per_second)
61
+ sync_middlewares = (AlteredRateLimitMiddleware(max_per_second=rps),)
62
+ async_middlewares = (AlteredRateLimitMiddlewareAsync(max_per_second=rps),)
63
+
64
+ return ApiClientConfig(
65
+ base_url=config.base_url,
66
+ default_headers=default_headers,
67
+ timeout_seconds=float(config.timeout_seconds),
68
+ retry_policy=retry_policy,
69
+ auth_strategy=auth_strategy,
70
+ middlewares=sync_middlewares,
71
+ async_middlewares=async_middlewares,
72
+ )
73
+
74
+
75
+ def to_api_client_config(config: AlteredApiConfig) -> ApiClientConfig:
76
+ """Convertit une configuration Altered en :class:`ApiClientConfig`.
77
+
78
+ :param config: Configuration applicative Altered.
79
+ :return: Configuration du client HTTP générique.
80
+ """
81
+ return AlteredApiClientConfigBuilder.build(config)