ffbb-data-client 2.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 (207) hide show
  1. ffbb_api_client_v3/__init__.py +25 -0
  2. ffbb_data_client/__init__.py +175 -0
  3. ffbb_data_client/clients/__init__.py +13 -0
  4. ffbb_data_client/clients/api_ffbb_app_client.py +2475 -0
  5. ffbb_data_client/clients/ffbb_data_client.py +2789 -0
  6. ffbb_data_client/clients/meilisearch_client.py +218 -0
  7. ffbb_data_client/clients/meilisearch_ffbb_client.py +647 -0
  8. ffbb_data_client/config.py +153 -0
  9. ffbb_data_client/data/__init__.py +25 -0
  10. ffbb_data_client/data/collections.json +1364 -0
  11. ffbb_data_client/data/endpoint_discovery.json +1875 -0
  12. ffbb_data_client/data/indexes.json +501 -0
  13. ffbb_data_client/data/openapi.json +35713 -0
  14. ffbb_data_client/data/openapi_full.json +37622 -0
  15. ffbb_data_client/helpers/__init__.py +27 -0
  16. ffbb_data_client/helpers/http_requests_helper.py +73 -0
  17. ffbb_data_client/helpers/http_requests_utils.py +502 -0
  18. ffbb_data_client/helpers/meilisearch_client_extension.py +153 -0
  19. ffbb_data_client/helpers/multi_search_query_helper.py +35 -0
  20. ffbb_data_client/models/__init__.py +241 -0
  21. ffbb_data_client/models/affiche.py +45 -0
  22. ffbb_data_client/models/cartographie.py +82 -0
  23. ffbb_data_client/models/categorie.py +55 -0
  24. ffbb_data_client/models/categorie_type.py +42 -0
  25. ffbb_data_client/models/clock.py +38 -0
  26. ffbb_data_client/models/club_contacts.py +77 -0
  27. ffbb_data_client/models/code.py +7 -0
  28. ffbb_data_client/models/commune.py +66 -0
  29. ffbb_data_client/models/competition_fields.py +309 -0
  30. ffbb_data_client/models/competition_id.py +116 -0
  31. ffbb_data_client/models/competition_id_categorie.py +31 -0
  32. ffbb_data_client/models/competition_id_sexe.py +31 -0
  33. ffbb_data_client/models/competition_id_type_competition.py +27 -0
  34. ffbb_data_client/models/competition_id_type_competition_generique.py +24 -0
  35. ffbb_data_client/models/competition_origine.py +69 -0
  36. ffbb_data_client/models/competition_origine_categorie.py +23 -0
  37. ffbb_data_client/models/competition_origine_type_competition.py +14 -0
  38. ffbb_data_client/models/competition_origine_type_competition_generique.py +24 -0
  39. ffbb_data_client/models/competition_type.py +6 -0
  40. ffbb_data_client/models/competitions_facet_distribution.py +65 -0
  41. ffbb_data_client/models/competitions_facet_stats.py +14 -0
  42. ffbb_data_client/models/competitions_hit.py +232 -0
  43. ffbb_data_client/models/competitions_multi_search_query.py +40 -0
  44. ffbb_data_client/models/competitions_query.py +11 -0
  45. ffbb_data_client/models/configuration_models.py +5 -0
  46. ffbb_data_client/models/contact_info.py +18 -0
  47. ffbb_data_client/models/content_multi_search_query.py +93 -0
  48. ffbb_data_client/models/coordonnees.py +27 -0
  49. ffbb_data_client/models/coordonnees_type.py +5 -0
  50. ffbb_data_client/models/document_flyer.py +205 -0
  51. ffbb_data_client/models/document_flyer_type.py +6 -0
  52. ffbb_data_client/models/engagement_contacts.py +97 -0
  53. ffbb_data_client/models/engagements_facet_distribution.py +59 -0
  54. ffbb_data_client/models/engagements_facet_stats.py +14 -0
  55. ffbb_data_client/models/engagements_hit.py +192 -0
  56. ffbb_data_client/models/engagements_multi_search_query.py +41 -0
  57. ffbb_data_client/models/etat.py +6 -0
  58. ffbb_data_client/models/external_competition_id.py +42 -0
  59. ffbb_data_client/models/external_id.py +72 -0
  60. ffbb_data_client/models/facet_distribution.py +13 -0
  61. ffbb_data_client/models/facet_stats.py +13 -0
  62. ffbb_data_client/models/field_set.py +10 -0
  63. ffbb_data_client/models/folder.py +35 -0
  64. ffbb_data_client/models/formation_session.py +60 -0
  65. ffbb_data_client/models/formations_facet_distribution.py +61 -0
  66. ffbb_data_client/models/formations_facet_stats.py +14 -0
  67. ffbb_data_client/models/formations_hit.py +277 -0
  68. ffbb_data_client/models/formations_multi_search_query.py +41 -0
  69. ffbb_data_client/models/game_stats_model.py +57 -0
  70. ffbb_data_client/models/game_stats_models.py +5 -0
  71. ffbb_data_client/models/generic_search.py +92 -0
  72. ffbb_data_client/models/geo.py +27 -0
  73. ffbb_data_client/models/geo_sort_order.py +6 -0
  74. ffbb_data_client/models/get_commune_response.py +18 -0
  75. ffbb_data_client/models/get_competition_response.py +523 -0
  76. ffbb_data_client/models/get_configuration_response.py +45 -0
  77. ffbb_data_client/models/get_engagement_response.py +23 -0
  78. ffbb_data_client/models/get_entraineur_response.py +18 -0
  79. ffbb_data_client/models/get_formation_response.py +28 -0
  80. ffbb_data_client/models/get_officiel_response.py +18 -0
  81. ffbb_data_client/models/get_organisme_response.py +476 -0
  82. ffbb_data_client/models/get_poule_response.py +68 -0
  83. ffbb_data_client/models/get_pratique_response.py +18 -0
  84. ffbb_data_client/models/get_rencontre_response.py +93 -0
  85. ffbb_data_client/models/get_saisons_response.py +56 -0
  86. ffbb_data_client/models/get_salle_response.py +20 -0
  87. ffbb_data_client/models/get_terrain_response.py +16 -0
  88. ffbb_data_client/models/get_tournoi_response.py +16 -0
  89. ffbb_data_client/models/gradient_color.py +27 -0
  90. ffbb_data_client/models/hit.py +16 -0
  91. ffbb_data_client/models/id_engagement_equipe.py +32 -0
  92. ffbb_data_client/models/id_organisme_equipe.py +51 -0
  93. ffbb_data_client/models/id_organisme_equipe1_logo.py +28 -0
  94. ffbb_data_client/models/id_poule.py +27 -0
  95. ffbb_data_client/models/jour.py +11 -0
  96. ffbb_data_client/models/label.py +15 -0
  97. ffbb_data_client/models/labellisation.py +30 -0
  98. ffbb_data_client/models/live.py +192 -0
  99. ffbb_data_client/models/lives.py +6 -0
  100. ffbb_data_client/models/logo.py +28 -0
  101. ffbb_data_client/models/multi_search_queries.py +24 -0
  102. ffbb_data_client/models/multi_search_query.py +96 -0
  103. ffbb_data_client/models/multi_search_result_competitions.py +14 -0
  104. ffbb_data_client/models/multi_search_result_engagements.py +14 -0
  105. ffbb_data_client/models/multi_search_result_formations.py +12 -0
  106. ffbb_data_client/models/multi_search_result_organismes.py +12 -0
  107. ffbb_data_client/models/multi_search_result_pratiques.py +12 -0
  108. ffbb_data_client/models/multi_search_result_rencontres.py +12 -0
  109. ffbb_data_client/models/multi_search_result_salles.py +12 -0
  110. ffbb_data_client/models/multi_search_result_terrains.py +12 -0
  111. ffbb_data_client/models/multi_search_result_tournois.py +12 -0
  112. ffbb_data_client/models/multi_search_results.py +103 -0
  113. ffbb_data_client/models/multi_search_results_class.py +96 -0
  114. ffbb_data_client/models/nature_sol.py +57 -0
  115. ffbb_data_client/models/niveau.py +10 -0
  116. ffbb_data_client/models/niveau_class.py +27 -0
  117. ffbb_data_client/models/niveau_extractor.py +214 -0
  118. ffbb_data_client/models/niveau_info.py +64 -0
  119. ffbb_data_client/models/niveau_models.py +14 -0
  120. ffbb_data_client/models/niveau_type.py +10 -0
  121. ffbb_data_client/models/objectif.py +7 -0
  122. ffbb_data_client/models/organisateur.py +197 -0
  123. ffbb_data_client/models/organisateur_type.py +6 -0
  124. ffbb_data_client/models/organisme_fields.py +327 -0
  125. ffbb_data_client/models/organisme_id_pere.py +177 -0
  126. ffbb_data_client/models/organismes_facet_distribution.py +46 -0
  127. ffbb_data_client/models/organismes_facet_stats.py +14 -0
  128. ffbb_data_client/models/organismes_hit.py +196 -0
  129. ffbb_data_client/models/organismes_multi_search_query.py +41 -0
  130. ffbb_data_client/models/organismes_query.py +8 -0
  131. ffbb_data_client/models/phase_code.py +23 -0
  132. ffbb_data_client/models/poule.py +35 -0
  133. ffbb_data_client/models/poule_fields.py +261 -0
  134. ffbb_data_client/models/poule_rencontre_item_model.py +69 -0
  135. ffbb_data_client/models/poules_models.py +6 -0
  136. ffbb_data_client/models/poules_query.py +9 -0
  137. ffbb_data_client/models/pratique.py +7 -0
  138. ffbb_data_client/models/pratiques_facet_distribution.py +29 -0
  139. ffbb_data_client/models/pratiques_facet_stats.py +14 -0
  140. ffbb_data_client/models/pratiques_hit.py +310 -0
  141. ffbb_data_client/models/pratiques_hit_type.py +9 -0
  142. ffbb_data_client/models/pratiques_multi_search_query.py +41 -0
  143. ffbb_data_client/models/pratiques_type_class.py +45 -0
  144. ffbb_data_client/models/publication_internet.py +6 -0
  145. ffbb_data_client/models/purple_logo.py +24 -0
  146. ffbb_data_client/models/query_fields_manager.py +75 -0
  147. ffbb_data_client/models/ranking_engagement.py +41 -0
  148. ffbb_data_client/models/rankings_models.py +6 -0
  149. ffbb_data_client/models/rencontres_engagement.py +23 -0
  150. ffbb_data_client/models/rencontres_facet_distribution.py +65 -0
  151. ffbb_data_client/models/rencontres_facet_stats.py +14 -0
  152. ffbb_data_client/models/rencontres_hit.py +271 -0
  153. ffbb_data_client/models/rencontres_multi_search_query.py +41 -0
  154. ffbb_data_client/models/saison.py +23 -0
  155. ffbb_data_client/models/saison_fields.py +36 -0
  156. ffbb_data_client/models/saisons_models.py +6 -0
  157. ffbb_data_client/models/saisons_query.py +9 -0
  158. ffbb_data_client/models/salle.py +56 -0
  159. ffbb_data_client/models/salles_facet_distribution.py +14 -0
  160. ffbb_data_client/models/salles_facet_stats.py +14 -0
  161. ffbb_data_client/models/salles_hit.py +153 -0
  162. ffbb_data_client/models/salles_multi_search_query.py +40 -0
  163. ffbb_data_client/models/sexe.py +9 -0
  164. ffbb_data_client/models/sexe_class.py +31 -0
  165. ffbb_data_client/models/source.py +5 -0
  166. ffbb_data_client/models/status.py +5 -0
  167. ffbb_data_client/models/team_engagement.py +56 -0
  168. ffbb_data_client/models/team_ranking.py +108 -0
  169. ffbb_data_client/models/terrains_categorie_championnat_3x3_libelle.py +5 -0
  170. ffbb_data_client/models/terrains_facet_distribution.py +41 -0
  171. ffbb_data_client/models/terrains_facet_stats.py +14 -0
  172. ffbb_data_client/models/terrains_hit.py +223 -0
  173. ffbb_data_client/models/terrains_multi_search_query.py +42 -0
  174. ffbb_data_client/models/terrains_name.py +5 -0
  175. ffbb_data_client/models/terrains_sexe_enum.py +7 -0
  176. ffbb_data_client/models/terrains_storage.py +5 -0
  177. ffbb_data_client/models/tournoi_type_class.py +35 -0
  178. ffbb_data_client/models/tournoi_type_enum.py +7 -0
  179. ffbb_data_client/models/tournoi_types_3x3.py +43 -0
  180. ffbb_data_client/models/tournoi_types_3x3_libelle.py +60 -0
  181. ffbb_data_client/models/tournoi_types_3x3_libelle_enum.py +10 -0
  182. ffbb_data_client/models/tournois_facet_distribution.py +41 -0
  183. ffbb_data_client/models/tournois_facet_stats.py +14 -0
  184. ffbb_data_client/models/tournois_hit.py +132 -0
  185. ffbb_data_client/models/tournois_hit_type.py +5 -0
  186. ffbb_data_client/models/tournois_libelle.py +7 -0
  187. ffbb_data_client/models/tournois_multi_search_query.py +40 -0
  188. ffbb_data_client/models/type_association.py +23 -0
  189. ffbb_data_client/models/type_association_libelle.py +30 -0
  190. ffbb_data_client/models/type_class.py +23 -0
  191. ffbb_data_client/models/type_competition.py +8 -0
  192. ffbb_data_client/models/type_competition_generique.py +31 -0
  193. ffbb_data_client/models/type_enum.py +7 -0
  194. ffbb_data_client/models/type_league.py +6 -0
  195. ffbb_data_client/py.typed +0 -0
  196. ffbb_data_client/utils/__init__.py +27 -0
  197. ffbb_data_client/utils/cache_manager.py +393 -0
  198. ffbb_data_client/utils/converter_utils.py +329 -0
  199. ffbb_data_client/utils/input_validation.py +360 -0
  200. ffbb_data_client/utils/retry_utils.py +478 -0
  201. ffbb_data_client/utils/secure_logging.py +153 -0
  202. ffbb_data_client/utils/token_manager.py +115 -0
  203. ffbb_data_client-2.0.0.dist-info/METADATA +339 -0
  204. ffbb_data_client-2.0.0.dist-info/RECORD +207 -0
  205. ffbb_data_client-2.0.0.dist-info/WHEEL +5 -0
  206. ffbb_data_client-2.0.0.dist-info/licenses/LICENSE.txt +201 -0
  207. ffbb_data_client-2.0.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,27 @@
1
+ """Helper modules and class extensions."""
2
+
3
+ from .http_requests_helper import catch_result
4
+ from .http_requests_utils import (
5
+ encode_params,
6
+ http_get,
7
+ http_get_json,
8
+ http_post,
9
+ http_post_json,
10
+ to_json_from_response,
11
+ url_with_params,
12
+ )
13
+ from .meilisearch_client_extension import MeilisearchClientExtension
14
+ from .multi_search_query_helper import generate_queries
15
+
16
+ __all__ = [
17
+ "MeilisearchClientExtension",
18
+ "catch_result",
19
+ "encode_params",
20
+ "generate_queries",
21
+ "http_get",
22
+ "http_get_json",
23
+ "http_post",
24
+ "http_post_json",
25
+ "to_json_from_response",
26
+ "url_with_params",
27
+ ]
@@ -0,0 +1,73 @@
1
+ """HTTP request helper utilities for FFBB API Client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Callable, Coroutine
7
+ from typing import Any, TypeVar
8
+
9
+ from httpx import ReadTimeout
10
+
11
+ __all__ = ["async_catch_result", "catch_result"]
12
+
13
+ T = TypeVar("T")
14
+
15
+
16
+ def catch_result(callback: Callable[[], T], is_retrieving: bool = False) -> T | None:
17
+ """
18
+ Catch the result of a callback function.
19
+
20
+ Args:
21
+ callback: The callback function.
22
+ is_retrieving: Whether this is a retry attempt.
23
+
24
+ Returns:
25
+ The result of the callback function or None if an exception occurs.
26
+ """
27
+
28
+ try:
29
+ return callback()
30
+ except json.decoder.JSONDecodeError as e:
31
+ if e.msg == "Expecting value":
32
+ return None
33
+ raise e
34
+ except ReadTimeout as e:
35
+ if not is_retrieving:
36
+ return catch_result(callback, True)
37
+ raise e
38
+ except ConnectionError as e:
39
+ if not is_retrieving:
40
+ return catch_result(callback, True)
41
+ raise e
42
+
43
+
44
+ async def async_catch_result(
45
+ coro: Coroutine[Any, Any, T], is_retrieving: bool = False
46
+ ) -> T | None:
47
+ """
48
+ Catch the result of an awaitable coroutine.
49
+
50
+ Mirrors the behaviour of :func:`catch_result` for async call sites:
51
+ retries once on transient network errors, returns ``None`` on empty JSON.
52
+
53
+ Args:
54
+ coro: The coroutine to await.
55
+ is_retrieving: Whether this is a retry attempt.
56
+
57
+ Returns:
58
+ The result of the coroutine or None if a recoverable exception occurs.
59
+ """
60
+ try:
61
+ return await coro
62
+ except json.decoder.JSONDecodeError as e:
63
+ if e.msg == "Expecting value":
64
+ return None
65
+ raise
66
+ except ReadTimeout as e:
67
+ if not is_retrieving:
68
+ return None # caller already has retry logic via retry_utils
69
+ raise e
70
+ except ConnectionError as e:
71
+ if not is_retrieving:
72
+ return None
73
+ raise e
@@ -0,0 +1,502 @@
1
+ from __future__ import annotations
2
+
3
+ import atexit
4
+ import json
5
+ import time
6
+ from typing import Any
7
+ from urllib.parse import urlencode
8
+
9
+ import httpx
10
+ from httpx import Client, Response
11
+
12
+ from ..utils.retry_utils import (
13
+ RetryConfig,
14
+ TimeoutConfig,
15
+ make_http_request_with_retry,
16
+ make_http_request_with_retry_async,
17
+ )
18
+ from ..utils.secure_logging import get_secure_logger
19
+
20
+ logger = get_secure_logger(__name__)
21
+
22
+ _DEFAULT_SYNC_CLIENT: httpx.Client | None = None
23
+ _DEFAULT_ASYNC_CLIENT: httpx.AsyncClient | None = None
24
+
25
+
26
+ def _build_timeout(timeout: int | float | TimeoutConfig | None) -> httpx.Timeout:
27
+ if isinstance(timeout, TimeoutConfig):
28
+ return httpx.Timeout(
29
+ timeout.total_timeout,
30
+ connect=timeout.connect_timeout,
31
+ read=timeout.read_timeout,
32
+ write=timeout.read_timeout,
33
+ pool=timeout.connect_timeout,
34
+ )
35
+ value = 20.0 if timeout is None else float(timeout)
36
+ return httpx.Timeout(value)
37
+
38
+
39
+ def _get_default_sync_client(timeout: int | float = 20) -> httpx.Client:
40
+ global _DEFAULT_SYNC_CLIENT
41
+ if _DEFAULT_SYNC_CLIENT is None or _DEFAULT_SYNC_CLIENT.is_closed:
42
+ _DEFAULT_SYNC_CLIENT = httpx.Client(timeout=_build_timeout(timeout))
43
+ return _DEFAULT_SYNC_CLIENT
44
+
45
+
46
+ async def _get_default_async_client(timeout: int | float = 20) -> httpx.AsyncClient:
47
+ global _DEFAULT_ASYNC_CLIENT
48
+ if _DEFAULT_ASYNC_CLIENT is None or _DEFAULT_ASYNC_CLIENT.is_closed:
49
+ _DEFAULT_ASYNC_CLIENT = httpx.AsyncClient(timeout=_build_timeout(timeout))
50
+ return _DEFAULT_ASYNC_CLIENT
51
+
52
+
53
+ def close_default_clients() -> None:
54
+ """Close module-level fallback clients used when no session is provided."""
55
+ global _DEFAULT_SYNC_CLIENT, _DEFAULT_ASYNC_CLIENT
56
+ if _DEFAULT_SYNC_CLIENT is not None and not _DEFAULT_SYNC_CLIENT.is_closed:
57
+ _DEFAULT_SYNC_CLIENT.close()
58
+ _DEFAULT_SYNC_CLIENT = None
59
+ _DEFAULT_ASYNC_CLIENT = None
60
+
61
+
62
+ atexit.register(close_default_clients)
63
+
64
+
65
+ def to_json_from_response(response: Response) -> Any:
66
+ """
67
+ Converts the HTTP response to a JSON dictionary.
68
+
69
+ Args:
70
+ response (Response): The HTTP response.
71
+
72
+ Returns:
73
+ Any: Parsed JSON payload (dict, list, etc.).
74
+ """
75
+ if isinstance(response, Response):
76
+ try:
77
+ return response.json()
78
+ except (json.JSONDecodeError, ValueError) as e:
79
+ logger.warning(f"Error in to_json_from_response: {e}")
80
+
81
+ data_str = response.text.strip()
82
+
83
+ try:
84
+ return json.loads(data_str)
85
+ except json.JSONDecodeError as e:
86
+ logger.warning(f"Error in to_json_from_response: {e}")
87
+
88
+ if data_str.endswith(","):
89
+ data_str = data_str[:-1]
90
+
91
+ data_str = data_str.replace("][", ",")
92
+ data_str = data_str.replace("KO", "")
93
+
94
+ if data_str.startswith('""'):
95
+ data_str = data_str[2:]
96
+
97
+ return json.loads(data_str)
98
+
99
+
100
+ def http_get(
101
+ url: str,
102
+ headers: dict[str, str],
103
+ debug: bool = False,
104
+ cached_session: Client | None = None,
105
+ timeout: int = 20,
106
+ retry_config: RetryConfig | None = None,
107
+ timeout_config: TimeoutConfig | None = None,
108
+ ) -> Response:
109
+ """
110
+ Performs an HTTP GET request with retry logic.
111
+
112
+ Args:
113
+ url (str): The URL of the request.
114
+ headers (Dict[str, str]): The headers of the request.
115
+ debug (bool): Whether to enable debug mode or not. Default is False.
116
+ cached_session (CachedSession): Cached session to use. Default is None.
117
+ timeout (int): The timeout value in seconds. Default is 20.
118
+ retry_config (RetryConfig): Retry configuration. Default is None.
119
+ timeout_config (TimeoutConfig): Timeout configuration. Default is None.
120
+
121
+ Returns:
122
+ Response: The HTTP response.
123
+ """
124
+ start_time: float = 0.0
125
+ if debug:
126
+ logger.debug(f"Making GET request to {url}")
127
+ start_time = time.time()
128
+
129
+ # Use retry logic if configured
130
+ if retry_config and timeout_config:
131
+ response = make_http_request_with_retry(
132
+ "GET",
133
+ url,
134
+ headers,
135
+ cached_session=cached_session,
136
+ retry_config=retry_config,
137
+ timeout_config=timeout_config,
138
+ debug=debug,
139
+ )
140
+ else:
141
+ # Fallback to reusable client to keep connections warm across calls.
142
+ if cached_session:
143
+ response = cached_session.get(
144
+ url, headers=headers, timeout=_build_timeout(timeout_config or timeout)
145
+ )
146
+ else:
147
+ response = _get_default_sync_client(timeout).get(
148
+ url, headers=headers, timeout=_build_timeout(timeout_config or timeout)
149
+ )
150
+
151
+ if debug:
152
+ end_time = time.time()
153
+ logger.debug(f"GET request to {url} took {end_time - start_time} seconds.")
154
+ logger.debug(f"GET response: {response.text}")
155
+
156
+ return response
157
+
158
+
159
+ def http_post(
160
+ url: str,
161
+ headers: dict[str, str],
162
+ data: dict[str, Any] | None = None,
163
+ debug: bool = False,
164
+ cached_session: Client | None = None,
165
+ timeout: int = 20,
166
+ retry_config: RetryConfig | None = None,
167
+ timeout_config: TimeoutConfig | None = None,
168
+ ) -> Response:
169
+ """
170
+ Performs an HTTP POST request with retry logic.
171
+
172
+ Args:
173
+ url (str): The URL of the request.
174
+ headers (Dict[str, str]): The headers of the request.
175
+ data (Dict[str, Any]): The data of the request.
176
+ debug (bool): Whether to enable debug mode or not. Default is False.
177
+ cached_session (CachedSession): Cached session to use. Default is None.
178
+ timeout (int): The timeout value in seconds. Default is 20.
179
+ retry_config (RetryConfig): Retry configuration. Default is None.
180
+ timeout_config (TimeoutConfig): Timeout configuration. Default is None.
181
+
182
+ Returns:
183
+ Response: The HTTP response.
184
+ """
185
+ start_time: float = 0.0
186
+ data_str: str = ""
187
+ if debug:
188
+ data_str = ", ".join([f"{k}:{v}" for k, v in data.items()]) if data else ""
189
+ logger.debug(f"Making POST request to {url} {data_str}")
190
+ start_time = time.time()
191
+
192
+ # Use retry logic if configured
193
+ if retry_config and timeout_config:
194
+ response = make_http_request_with_retry(
195
+ "POST",
196
+ url,
197
+ headers,
198
+ data=data,
199
+ cached_session=cached_session,
200
+ retry_config=retry_config,
201
+ timeout_config=timeout_config,
202
+ debug=debug,
203
+ )
204
+ else:
205
+ # Fallback to reusable client to keep connections warm across calls.
206
+ if cached_session:
207
+ response = cached_session.post(
208
+ url,
209
+ headers=headers,
210
+ json=data,
211
+ timeout=_build_timeout(timeout_config or timeout),
212
+ )
213
+ else:
214
+ response = _get_default_sync_client(timeout).post(
215
+ url,
216
+ headers=headers,
217
+ json=data,
218
+ timeout=_build_timeout(timeout_config or timeout),
219
+ )
220
+
221
+ if debug:
222
+ end_time = time.time()
223
+ logger.debug(
224
+ f"POST request to {url} {data_str} took {end_time - start_time} seconds."
225
+ )
226
+ logger.debug(f"POST response: {response.text}")
227
+
228
+ return response
229
+
230
+
231
+ def http_get_json(
232
+ url: str,
233
+ headers: dict[str, str],
234
+ debug: bool = False,
235
+ cached_session: Client | None = None,
236
+ timeout: int = 20,
237
+ retry_config: RetryConfig | None = None,
238
+ timeout_config: TimeoutConfig | None = None,
239
+ ) -> Any:
240
+ """
241
+ Performs an HTTP GET request and returns the result in JSON format.
242
+
243
+ Args:
244
+ url (str): The URL of the request.
245
+ headers (Dict[str, str]): The headers of the request.
246
+ debug (bool): Whether to enable debug mode or not. Default is False.
247
+ cached_session (CachedSession): Cached session to use. Default is None.
248
+ timeout (int): The timeout value in seconds. Default is 20.
249
+ retry_config (RetryConfig): Retry configuration. Default is None.
250
+ timeout_config (TimeoutConfig): Timeout configuration. Default is None.
251
+
252
+ Returns:
253
+ Dict[str, Any]: The result of the request in JSON format.
254
+ """
255
+ response = http_get(
256
+ url,
257
+ headers,
258
+ debug=debug,
259
+ cached_session=cached_session,
260
+ timeout=timeout,
261
+ retry_config=retry_config,
262
+ timeout_config=timeout_config,
263
+ )
264
+ response.raise_for_status()
265
+ return to_json_from_response(response)
266
+
267
+
268
+ def http_post_json(
269
+ url: str,
270
+ headers: dict[str, str],
271
+ data: dict[str, Any] | None = None,
272
+ debug: bool = False,
273
+ cached_session: Client | None = None,
274
+ timeout: int = 20,
275
+ retry_config: RetryConfig | None = None,
276
+ timeout_config: TimeoutConfig | None = None,
277
+ ) -> Any:
278
+ """
279
+ Performs an HTTP POST request and returns the result in JSON format.
280
+
281
+ Args:
282
+ url (str): The URL of the request.
283
+ headers (Dict[str, str]): The headers of the request.
284
+ data (Dict[str, Any]): The data of the request.
285
+ debug (bool): Whether to enable debug mode or not. Default is False.
286
+ cached_session (CachedSession): Cached session to use. Default is None.
287
+ timeout (int): The timeout value in seconds. Default is 20.
288
+ retry_config (RetryConfig): Retry configuration. Default is None.
289
+ timeout_config (TimeoutConfig): Timeout configuration. Default is None.
290
+
291
+ Returns:
292
+ Dict[str, Any]: The result of the request in JSON format.
293
+ """
294
+ filtered_data = {k: v for k, v in data.items() if v is not None} if data else None
295
+
296
+ response = http_post(
297
+ url,
298
+ headers,
299
+ filtered_data,
300
+ debug=debug,
301
+ cached_session=cached_session,
302
+ timeout=timeout,
303
+ retry_config=retry_config,
304
+ timeout_config=timeout_config,
305
+ )
306
+ response.raise_for_status()
307
+ return to_json_from_response(response)
308
+
309
+
310
+ async def http_get_async(
311
+ url: str,
312
+ headers: dict[str, str],
313
+ debug: bool = False,
314
+ cached_session: httpx.AsyncClient | None = None,
315
+ timeout: int = 20,
316
+ retry_config: RetryConfig | None = None,
317
+ timeout_config: TimeoutConfig | None = None,
318
+ ) -> Response:
319
+ """
320
+ Performs an HTTP GET request asynchroniously.
321
+ """
322
+ start_time: float = 0.0
323
+ if debug:
324
+ logger.debug(f"Making async GET request to {url}")
325
+ start_time = time.time()
326
+
327
+ # Use retry logic if configured
328
+ if retry_config and timeout_config:
329
+ response = await make_http_request_with_retry_async(
330
+ "GET",
331
+ url,
332
+ headers,
333
+ cached_session=cached_session,
334
+ retry_config=retry_config,
335
+ timeout_config=timeout_config,
336
+ debug=debug,
337
+ )
338
+ else:
339
+ # Fallback to reusable async client to keep connections warm across calls.
340
+ if cached_session:
341
+ response = await cached_session.get(
342
+ url, headers=headers, timeout=_build_timeout(timeout_config or timeout)
343
+ )
344
+ else:
345
+ response = await (await _get_default_async_client(timeout)).get(
346
+ url, headers=headers, timeout=_build_timeout(timeout_config or timeout)
347
+ )
348
+
349
+ if debug:
350
+ end_time = time.time()
351
+ logger.debug(
352
+ f"Async GET request to {url} took {end_time - start_time} seconds."
353
+ )
354
+ logger.debug(f"Async GET response: {response.text}")
355
+
356
+ return response
357
+
358
+
359
+ async def http_get_json_async(
360
+ url: str,
361
+ headers: dict[str, str],
362
+ debug: bool = False,
363
+ cached_session: httpx.AsyncClient | None = None,
364
+ timeout: int = 20,
365
+ retry_config: RetryConfig | None = None,
366
+ timeout_config: TimeoutConfig | None = None,
367
+ ) -> Any:
368
+ """
369
+ Performs an HTTP GET request and returns the result in JSON format asynchroniously.
370
+ """
371
+ response = await http_get_async(
372
+ url,
373
+ headers,
374
+ debug=debug,
375
+ cached_session=cached_session,
376
+ timeout=timeout,
377
+ retry_config=retry_config,
378
+ timeout_config=timeout_config,
379
+ )
380
+ response.raise_for_status()
381
+ return to_json_from_response(response)
382
+
383
+
384
+ async def http_post_async(
385
+ url: str,
386
+ headers: dict[str, str],
387
+ data: dict[str, Any] | None = None,
388
+ debug: bool = False,
389
+ cached_session: httpx.AsyncClient | None = None,
390
+ timeout: int = 20,
391
+ retry_config: RetryConfig | None = None,
392
+ timeout_config: TimeoutConfig | None = None,
393
+ ) -> Response:
394
+ """
395
+ Performs an HTTP POST request asynchroniously.
396
+ """
397
+ start_time: float = 0.0
398
+ data_str: str = ""
399
+ if debug:
400
+ data_str = ", ".join([f"{k}:{v}" for k, v in data.items()]) if data else ""
401
+ logger.debug(f"Making async POST request to {url} {data_str}")
402
+ start_time = time.time()
403
+
404
+ filtered_data = {k: v for k, v in data.items() if v is not None} if data else None
405
+
406
+ # Use retry logic if configured
407
+ if retry_config and timeout_config:
408
+ response = await make_http_request_with_retry_async(
409
+ "POST",
410
+ url,
411
+ headers,
412
+ data=filtered_data,
413
+ cached_session=cached_session,
414
+ retry_config=retry_config,
415
+ timeout_config=timeout_config,
416
+ debug=debug,
417
+ )
418
+ else:
419
+ # Fallback to reusable async client to keep connections warm across calls.
420
+ if cached_session:
421
+ response = await cached_session.post(
422
+ url,
423
+ headers=headers,
424
+ json=filtered_data,
425
+ timeout=_build_timeout(timeout_config or timeout),
426
+ )
427
+ else:
428
+ response = await (await _get_default_async_client(timeout)).post(
429
+ url,
430
+ headers=headers,
431
+ json=filtered_data,
432
+ timeout=_build_timeout(timeout_config or timeout),
433
+ )
434
+
435
+ if debug:
436
+ end_time = time.time()
437
+ logger.debug(
438
+ f"Async POST request to {url} {data_str} took {end_time - start_time} seconds."
439
+ )
440
+ logger.debug(f"Async POST response: {response.text}")
441
+
442
+ return response
443
+
444
+
445
+ async def http_post_json_async(
446
+ url: str,
447
+ headers: dict[str, str],
448
+ data: dict[str, Any] | None = None,
449
+ debug: bool = False,
450
+ cached_session: httpx.AsyncClient | None = None,
451
+ timeout: int = 20,
452
+ retry_config: RetryConfig | None = None,
453
+ timeout_config: TimeoutConfig | None = None,
454
+ ) -> Any:
455
+ """
456
+ Performs an HTTP POST request and returns the result in JSON format asynchroniously.
457
+ """
458
+ response = await http_post_async(
459
+ url,
460
+ headers,
461
+ data,
462
+ debug=debug,
463
+ cached_session=cached_session,
464
+ timeout=timeout,
465
+ retry_config=retry_config,
466
+ timeout_config=timeout_config,
467
+ )
468
+ response.raise_for_status()
469
+ return to_json_from_response(response)
470
+
471
+
472
+ def encode_params(params: dict[str, Any]) -> str:
473
+ """
474
+ Encodes the request parameters into a query string.
475
+ Handles array parameters correctly (fields[], etc.)
476
+
477
+ Args:
478
+ params (Dict[str, Any]): The request parameters.
479
+
480
+ Returns:
481
+ str: The encoded query string.
482
+ """
483
+ # ⚡ Bolt optimization: using urlencode with doseq=True is ~3x faster than manual loop concatenation
484
+ # It correctly handles array parameters (e.g., fields[]) natively in C/optimized Python.
485
+ return urlencode({k: v for k, v in params.items() if v is not None}, doseq=True)
486
+
487
+
488
+ def url_with_params(url: str, params: dict[str, Any]) -> str:
489
+ """
490
+ Adds the request parameters to the URL.
491
+
492
+ Args:
493
+ url (str): The URL of the request.
494
+ params (Dict[str, Any]): The request parameters.
495
+
496
+ Returns:
497
+ str: The URL with the request parameters.
498
+ """
499
+ if encoded_params := encode_params(params):
500
+ return f"{url}?{encoded_params}"
501
+ else:
502
+ return url