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,72 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from ..utils.converter_utils import (
7
+ from_int,
8
+ from_obj,
9
+ from_str,
10
+ )
11
+ from .external_competition_id import ExternalCompetitionID
12
+ from .id_organisme_equipe import IDOrganismeEquipe
13
+ from .id_poule import IDPoule
14
+ from .salle import Salle
15
+
16
+
17
+ @dataclass
18
+ class ExternalID:
19
+ nom_equipe1: str | None = None
20
+ nom_equipe2: str | None = None
21
+ numero_journee: int | None = None
22
+ competition_id: ExternalCompetitionID | None = None
23
+ id_organisme_equipe1: IDOrganismeEquipe | None = None
24
+ id_organisme_equipe2: IDOrganismeEquipe | None = None
25
+ salle: Salle | None = None
26
+ id_poule: IDPoule | None = None
27
+
28
+ @staticmethod
29
+ def from_dict(obj: Any) -> ExternalID:
30
+ assert isinstance(obj, dict)
31
+ nom_equipe1 = from_str(obj, "nomEquipe1")
32
+ nom_equipe2 = from_str(obj, "nomEquipe2")
33
+ numero_journee = from_int(obj, "numeroJournee")
34
+ competition_id = from_obj(ExternalCompetitionID.from_dict, obj, "competitionId")
35
+ id_organisme_equipe1 = from_obj(
36
+ IDOrganismeEquipe.from_dict, obj, "idOrganismeEquipe1"
37
+ )
38
+ id_organisme_equipe2 = from_obj(
39
+ IDOrganismeEquipe.from_dict, obj, "idOrganismeEquipe2"
40
+ )
41
+ salle = from_obj(Salle.from_dict, obj, "salle")
42
+ id_poule = from_obj(IDPoule.from_dict, obj, "idPoule")
43
+ return ExternalID(
44
+ nom_equipe1=nom_equipe1,
45
+ nom_equipe2=nom_equipe2,
46
+ numero_journee=numero_journee,
47
+ competition_id=competition_id,
48
+ id_organisme_equipe1=id_organisme_equipe1,
49
+ id_organisme_equipe2=id_organisme_equipe2,
50
+ salle=salle,
51
+ id_poule=id_poule,
52
+ )
53
+
54
+ def to_dict(self) -> dict:
55
+ result: dict = {}
56
+ if self.nom_equipe1 is not None:
57
+ result["nomEquipe1"] = self.nom_equipe1
58
+ if self.nom_equipe2 is not None:
59
+ result["nomEquipe2"] = self.nom_equipe2
60
+ if self.numero_journee is not None:
61
+ result["numeroJournee"] = str(self.numero_journee)
62
+ if self.competition_id is not None:
63
+ result["competitionId"] = self.competition_id.to_dict()
64
+ if self.id_organisme_equipe1 is not None:
65
+ result["idOrganismeEquipe1"] = self.id_organisme_equipe1.to_dict()
66
+ if self.id_organisme_equipe2 is not None:
67
+ result["idOrganismeEquipe2"] = self.id_organisme_equipe2.to_dict()
68
+ if self.salle is not None:
69
+ result["salle"] = self.salle.to_dict()
70
+ if self.id_poule is not None:
71
+ result["idPoule"] = self.id_poule.to_dict()
72
+ return result
@@ -0,0 +1,13 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+
5
+ class FacetDistribution(ABC):
6
+ @staticmethod
7
+ def from_dict(obj: Any) -> "FacetDistribution":
8
+ # Cannot instantiate abstract class - should be implemented by subclasses
9
+ assert False
10
+
11
+ @abstractmethod
12
+ def to_dict(self) -> dict[str, Any]:
13
+ return {}
@@ -0,0 +1,13 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+
5
+ class FacetStats(ABC):
6
+ @staticmethod
7
+ def from_dict(obj: Any) -> "FacetStats":
8
+ # This should be implemented by concrete subclasses
9
+ assert False, "FacetStats is abstract and cannot be instantiated"
10
+
11
+ @abstractmethod
12
+ def to_dict(self) -> dict[str, Any]:
13
+ return {}
@@ -0,0 +1,10 @@
1
+ from enum import Enum
2
+
3
+
4
+ class FieldSet(Enum):
5
+ """Enum for different field sets."""
6
+
7
+ BASIC = "basic"
8
+ DEFAULT = "default"
9
+ DETAILED = "detailed"
10
+ MINIMAL = "minimal"
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+ from uuid import UUID
6
+
7
+ from ..utils.converter_utils import (
8
+ from_str,
9
+ from_uuid,
10
+ )
11
+
12
+
13
+ @dataclass
14
+ class Folder:
15
+ id: UUID | None = None
16
+ name: str | None = None
17
+ parent: str | None = None
18
+
19
+ @staticmethod
20
+ def from_dict(obj: Any) -> Folder:
21
+ assert isinstance(obj, dict)
22
+ id = from_uuid(obj, "id")
23
+ name = from_str(obj, "name")
24
+ parent = from_str(obj, "parent")
25
+ return Folder(id=id, name=name, parent=parent)
26
+
27
+ def to_dict(self) -> dict:
28
+ result: dict = {}
29
+ if self.id is not None:
30
+ result["id"] = str(self.id)
31
+ if self.name is not None:
32
+ result["name"] = self.name
33
+ if self.parent is not None:
34
+ result["parent"] = self.parent
35
+ return result
@@ -0,0 +1,60 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+ from typing import Any
6
+
7
+ from ..utils.converter_utils import from_datetime, from_str
8
+
9
+
10
+ @dataclass
11
+ class FormationSession:
12
+ """A session within a formation (MeiliSearch denormalized data)."""
13
+
14
+ id: str | None = None
15
+ title: str | None = None
16
+ date_start: datetime | None = None
17
+ date_end: datetime | None = None
18
+ place: str | None = None
19
+ postal_code: str | None = None
20
+ reference: str | None = None
21
+ entity: str | None = None
22
+ subscribe_btn: str | None = None
23
+
24
+ @staticmethod
25
+ def from_dict(obj: Any) -> FormationSession:
26
+ if not isinstance(obj, dict):
27
+ raise TypeError(f"Expected dict, got {obj.__class__.__name__}")
28
+ return FormationSession(
29
+ id=from_str(obj, "id"),
30
+ title=from_str(obj, "title"),
31
+ date_start=from_datetime(obj, "date_start"),
32
+ date_end=from_datetime(obj, "date_end"),
33
+ place=from_str(obj, "place"),
34
+ postal_code=from_str(obj, "postal_code"),
35
+ reference=from_str(obj, "reference"),
36
+ entity=from_str(obj, "entity"),
37
+ subscribe_btn=from_str(obj, "subscribeBtn"),
38
+ )
39
+
40
+ def to_dict(self) -> dict:
41
+ result: dict = {}
42
+ if self.id is not None:
43
+ result["id"] = self.id
44
+ if self.title is not None:
45
+ result["title"] = self.title
46
+ if self.date_start is not None:
47
+ result["date_start"] = self.date_start.isoformat()
48
+ if self.date_end is not None:
49
+ result["date_end"] = self.date_end.isoformat()
50
+ if self.place is not None:
51
+ result["place"] = self.place
52
+ if self.postal_code is not None:
53
+ result["postal_code"] = self.postal_code
54
+ if self.reference is not None:
55
+ result["reference"] = self.reference
56
+ if self.entity is not None:
57
+ result["entity"] = self.entity
58
+ if self.subscribe_btn is not None:
59
+ result["subscribeBtn"] = self.subscribe_btn
60
+ return result
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+ from .facet_distribution import FacetDistribution
7
+
8
+
9
+ @dataclass
10
+ class FormationsFacetDistribution(FacetDistribution):
11
+ domain: dict[str, int] | None = None
12
+ mode: dict[str, int] | None = None
13
+ theme: dict[str, int] | None = None
14
+ type: dict[str, int] | None = None
15
+ place: dict[str, int] | None = None
16
+ places: dict[str, int] | None = None
17
+ postal_code: dict[str, int] | None = None
18
+ postal_codes: dict[str, int] | None = None
19
+ date_start_formatted: dict[str, int] | None = None
20
+ date_end_formatted: dict[str, int] | None = None
21
+
22
+ @staticmethod
23
+ def from_dict(obj: Any) -> FormationsFacetDistribution:
24
+ if not isinstance(obj, dict):
25
+ raise TypeError(f"Expected dict, got {obj.__class__.__name__}")
26
+ return FormationsFacetDistribution(
27
+ domain=obj.get("domain"),
28
+ mode=obj.get("mode"),
29
+ theme=obj.get("theme"),
30
+ type=obj.get("type"),
31
+ place=obj.get("place"),
32
+ places=obj.get("places"),
33
+ postal_code=obj.get("postal_code"),
34
+ postal_codes=obj.get("postal_codes"),
35
+ date_start_formatted=obj.get("date_start_formatted"),
36
+ date_end_formatted=obj.get("date_end_formatted"),
37
+ )
38
+
39
+ def to_dict(self) -> dict:
40
+ result: dict = {}
41
+ if self.domain is not None:
42
+ result["domain"] = self.domain
43
+ if self.mode is not None:
44
+ result["mode"] = self.mode
45
+ if self.theme is not None:
46
+ result["theme"] = self.theme
47
+ if self.type is not None:
48
+ result["type"] = self.type
49
+ if self.place is not None:
50
+ result["place"] = self.place
51
+ if self.places is not None:
52
+ result["places"] = self.places
53
+ if self.postal_code is not None:
54
+ result["postal_code"] = self.postal_code
55
+ if self.postal_codes is not None:
56
+ result["postal_codes"] = self.postal_codes
57
+ if self.date_start_formatted is not None:
58
+ result["date_start_formatted"] = self.date_start_formatted
59
+ if self.date_end_formatted is not None:
60
+ result["date_end_formatted"] = self.date_end_formatted
61
+ return result
@@ -0,0 +1,14 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from .facet_stats import FacetStats
6
+
7
+
8
+ class FormationsFacetStats(FacetStats):
9
+ @staticmethod
10
+ def from_dict(obj: Any) -> FormationsFacetStats:
11
+ return FormationsFacetStats()
12
+
13
+ def to_dict(self) -> dict:
14
+ return super().to_dict()
@@ -0,0 +1,277 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from datetime import datetime, timedelta
5
+ from typing import Any
6
+ from uuid import UUID
7
+
8
+ from ..utils.converter_utils import (
9
+ from_datetime,
10
+ from_duration,
11
+ from_int,
12
+ from_list,
13
+ from_str,
14
+ from_uuid,
15
+ )
16
+ from .formation_session import FormationSession
17
+ from .hit import Hit
18
+
19
+
20
+ @dataclass
21
+ class FormationsHit(Hit):
22
+ id: str | None = None
23
+ title: str | None = None
24
+ type: str | None = None
25
+ mode: str | None = None
26
+ domain: str | None = None
27
+ theme: str | None = None
28
+ description: str | None = None
29
+ goals: str | None = None
30
+ public: str | None = None
31
+ prerequisites: str | None = None
32
+ content: str | None = None
33
+ pedagogy: str | None = None
34
+ certification: str | None = None
35
+ results: str | None = None
36
+ modalities: str | None = None
37
+ level: str | None = None
38
+ reference: str | None = None
39
+ program_id_fbi: str | None = None
40
+ duration_hours: timedelta | None = None
41
+ sessions: list[FormationSession] | None = None
42
+ files: list[Any] | None = None
43
+ image: str | None = None
44
+ thumbnail: str | None = None
45
+ mode_hidden: str | None = None
46
+ postal_codes: list[str] | None = None
47
+ places: list[str] | None = None
48
+ id_origin_hash: str | None = None
49
+ date_end: datetime | None = None
50
+ date_end_formatted: int | None = None
51
+ date_start: datetime | None = None
52
+ date_start_formatted: int | None = None
53
+ entity: str | None = None
54
+ formation_domain: str | None = None
55
+ formation_duration_hours: str | None = None
56
+ formation_id: UUID | None = None
57
+ formation_image: str | None = None
58
+ formation_mode: str | None = None
59
+ formation_theme: str | None = None
60
+ formation_thumbnail: str | None = None
61
+ formation_title: str | None = None
62
+ place: str | None = None
63
+ postal_code: str | None = None
64
+ reference_hidden: str | None = None
65
+ subscribe_btn: str | None = None
66
+ subscribe_button: str | None = None
67
+
68
+ lower_title: str | None = field(init=False, default=None, repr=False)
69
+ lower_domain: str | None = field(init=False, default=None, repr=False)
70
+ lower_theme: str | None = field(init=False, default=None, repr=False)
71
+
72
+ def __post_init__(self) -> None:
73
+ self.lower_title = self.title.lower() if self.title else None
74
+ self.lower_domain = self.domain.lower() if self.domain else None
75
+ self.lower_theme = self.theme.lower() if self.theme else None
76
+
77
+ @staticmethod
78
+ def from_dict(obj: Any) -> FormationsHit:
79
+ if not isinstance(obj, dict):
80
+ raise TypeError(f"Expected dict, got {obj.__class__.__name__}")
81
+ id = from_str(obj, "id")
82
+ title = from_str(obj, "title")
83
+ type = from_str(obj, "type")
84
+ mode = from_str(obj, "mode")
85
+ domain = from_str(obj, "domain")
86
+ theme = from_str(obj, "theme")
87
+ description = from_str(obj, "description")
88
+ goals = from_str(obj, "goals")
89
+ public = from_str(obj, "public")
90
+ prerequisites = from_str(obj, "prerequisites")
91
+ content = from_str(obj, "content")
92
+ pedagogy = from_str(obj, "pedagogy")
93
+ certification = from_str(obj, "certification")
94
+ results = from_str(obj, "results")
95
+ modalities = from_str(obj, "modalities")
96
+ level = from_str(obj, "level")
97
+ reference = from_str(obj, "reference")
98
+ program_id_fbi = from_str(obj, "programIdFbi")
99
+ duration_hours = from_duration(obj, "duration_hours")
100
+ sessions = from_list(FormationSession.from_dict, obj, "sessions")
101
+ files = from_list(lambda x: x, obj, "files")
102
+ image = from_str(obj, "image")
103
+ thumbnail = from_str(obj, "thumbnail")
104
+ mode_hidden = from_str(obj, "mode_hidden")
105
+ postal_codes = from_list(str, obj, "postal_codes")
106
+ places = from_list(str, obj, "places")
107
+ id_origin_hash = from_str(obj, "id_origin_hash")
108
+ date_end = from_datetime(obj, "date_end")
109
+ date_end_formatted = from_int(obj, "date_end_formatted")
110
+ date_start = from_datetime(obj, "date_start")
111
+ date_start_formatted = from_int(obj, "date_start_formatted")
112
+ entity = from_str(obj, "entity")
113
+ formation_domain = from_str(obj, "formation_domain")
114
+ formation_duration_hours = from_str(obj, "formation_duration_hours")
115
+ formation_id = from_uuid(obj, "formation_id")
116
+ formation_image = from_str(obj, "formation_image")
117
+ formation_mode = from_str(obj, "formation_mode")
118
+ formation_theme = from_str(obj, "formation_theme")
119
+ formation_thumbnail = from_str(obj, "formation_thumbnail")
120
+ formation_title = from_str(obj, "formation_title")
121
+ place = from_str(obj, "place")
122
+ postal_code = from_str(obj, "postal_code")
123
+ reference_hidden = from_str(obj, "reference_hidden")
124
+ subscribe_btn = from_str(obj, "subscribeBtn")
125
+ subscribe_button = from_str(obj, "subscribe_button")
126
+ return FormationsHit(
127
+ id=id,
128
+ title=title,
129
+ type=type,
130
+ mode=mode,
131
+ domain=domain,
132
+ theme=theme,
133
+ description=description,
134
+ goals=goals,
135
+ public=public,
136
+ prerequisites=prerequisites,
137
+ content=content,
138
+ pedagogy=pedagogy,
139
+ certification=certification,
140
+ results=results,
141
+ modalities=modalities,
142
+ level=level,
143
+ reference=reference,
144
+ program_id_fbi=program_id_fbi,
145
+ duration_hours=duration_hours,
146
+ sessions=sessions,
147
+ files=files,
148
+ image=image,
149
+ thumbnail=thumbnail,
150
+ mode_hidden=mode_hidden,
151
+ postal_codes=postal_codes,
152
+ places=places,
153
+ id_origin_hash=id_origin_hash,
154
+ date_end=date_end,
155
+ date_end_formatted=date_end_formatted,
156
+ date_start=date_start,
157
+ date_start_formatted=date_start_formatted,
158
+ entity=entity,
159
+ formation_domain=formation_domain,
160
+ formation_duration_hours=formation_duration_hours,
161
+ formation_id=formation_id,
162
+ formation_image=formation_image,
163
+ formation_mode=formation_mode,
164
+ formation_theme=formation_theme,
165
+ formation_thumbnail=formation_thumbnail,
166
+ formation_title=formation_title,
167
+ place=place,
168
+ postal_code=postal_code,
169
+ reference_hidden=reference_hidden,
170
+ subscribe_btn=subscribe_btn,
171
+ subscribe_button=subscribe_button,
172
+ )
173
+
174
+ def to_dict(self) -> dict:
175
+ result: dict = {}
176
+ if self.id is not None:
177
+ result["id"] = self.id
178
+ if self.title is not None:
179
+ result["title"] = self.title
180
+ if self.type is not None:
181
+ result["type"] = self.type
182
+ if self.mode is not None:
183
+ result["mode"] = self.mode
184
+ if self.domain is not None:
185
+ result["domain"] = self.domain
186
+ if self.theme is not None:
187
+ result["theme"] = self.theme
188
+ if self.description is not None:
189
+ result["description"] = self.description
190
+ if self.goals is not None:
191
+ result["goals"] = self.goals
192
+ if self.public is not None:
193
+ result["public"] = self.public
194
+ if self.prerequisites is not None:
195
+ result["prerequisites"] = self.prerequisites
196
+ if self.content is not None:
197
+ result["content"] = self.content
198
+ if self.pedagogy is not None:
199
+ result["pedagogy"] = self.pedagogy
200
+ if self.certification is not None:
201
+ result["certification"] = self.certification
202
+ if self.results is not None:
203
+ result["results"] = self.results
204
+ if self.modalities is not None:
205
+ result["modalities"] = self.modalities
206
+ if self.level is not None:
207
+ result["level"] = self.level
208
+ if self.reference is not None:
209
+ result["reference"] = self.reference
210
+ if self.program_id_fbi is not None:
211
+ result["programIdFbi"] = self.program_id_fbi
212
+ if self.duration_hours is not None:
213
+ total_seconds = int(self.duration_hours.total_seconds())
214
+ hours, remainder = divmod(total_seconds, 3600)
215
+ minutes = remainder // 60
216
+ result["duration_hours"] = f"{hours}h{minutes:02d}"
217
+ if self.sessions is not None:
218
+ result["sessions"] = [s.to_dict() for s in self.sessions]
219
+ if self.files is not None:
220
+ result["files"] = self.files
221
+ if self.image is not None:
222
+ result["image"] = self.image
223
+ if self.thumbnail is not None:
224
+ result["thumbnail"] = self.thumbnail
225
+ if self.mode_hidden is not None:
226
+ result["mode_hidden"] = self.mode_hidden
227
+ if self.postal_codes is not None:
228
+ result["postal_codes"] = self.postal_codes
229
+ if self.places is not None:
230
+ result["places"] = self.places
231
+ if self.id_origin_hash is not None:
232
+ result["id_origin_hash"] = self.id_origin_hash
233
+ if self.date_end is not None:
234
+ result["date_end"] = self.date_end.isoformat()
235
+ if self.date_end_formatted is not None:
236
+ result["date_end_formatted"] = self.date_end_formatted
237
+ if self.date_start is not None:
238
+ result["date_start"] = self.date_start.isoformat()
239
+ if self.date_start_formatted is not None:
240
+ result["date_start_formatted"] = self.date_start_formatted
241
+ if self.entity is not None:
242
+ result["entity"] = self.entity
243
+ if self.formation_domain is not None:
244
+ result["formation_domain"] = self.formation_domain
245
+ if self.formation_duration_hours is not None:
246
+ result["formation_duration_hours"] = self.formation_duration_hours
247
+ if self.formation_id is not None:
248
+ result["formation_id"] = str(self.formation_id)
249
+ if self.formation_image is not None:
250
+ result["formation_image"] = self.formation_image
251
+ if self.formation_mode is not None:
252
+ result["formation_mode"] = self.formation_mode
253
+ if self.formation_theme is not None:
254
+ result["formation_theme"] = self.formation_theme
255
+ if self.formation_thumbnail is not None:
256
+ result["formation_thumbnail"] = self.formation_thumbnail
257
+ if self.formation_title is not None:
258
+ result["formation_title"] = self.formation_title
259
+ if self.place is not None:
260
+ result["place"] = self.place
261
+ if self.postal_code is not None:
262
+ result["postal_code"] = self.postal_code
263
+ if self.reference_hidden is not None:
264
+ result["reference_hidden"] = self.reference_hidden
265
+ if self.subscribe_btn is not None:
266
+ result["subscribeBtn"] = self.subscribe_btn
267
+ if self.subscribe_button is not None:
268
+ result["subscribe_button"] = self.subscribe_button
269
+ return result
270
+
271
+ def is_valid_for_query(self, query: str) -> bool:
272
+ return bool(
273
+ not query
274
+ or (self.lower_title and query in self.lower_title)
275
+ or (self.lower_domain and query in self.lower_domain)
276
+ or (self.lower_theme and query in self.lower_theme)
277
+ )
@@ -0,0 +1,41 @@
1
+ from __future__ import annotations
2
+
3
+ from ..config import MEILISEARCH_FACETS_FORMATIONS, MEILISEARCH_INDEX_FORMATIONS
4
+ from .formations_facet_distribution import FormationsFacetDistribution
5
+ from .formations_facet_stats import FormationsFacetStats
6
+ from .multi_search_query import MultiSearchQuery
7
+ from .multi_search_result_formations import FormationsMultiSearchResult
8
+ from .multi_search_results import MultiSearchResult
9
+
10
+
11
+ class FormationsMultiSearchQuery(MultiSearchQuery):
12
+ def __init__(
13
+ self,
14
+ q: str | None,
15
+ limit: int | None = 10,
16
+ offset: int | None = 0,
17
+ filter: list[str] | None = None,
18
+ sort: list[str] | None = None,
19
+ ):
20
+ super().__init__(
21
+ index_uid=MEILISEARCH_INDEX_FORMATIONS,
22
+ q=q,
23
+ facets=MEILISEARCH_FACETS_FORMATIONS,
24
+ limit=limit,
25
+ offset=offset,
26
+ filter=filter,
27
+ sort=sort,
28
+ )
29
+
30
+ def is_valid_result(self, result: MultiSearchResult):
31
+ return result and (
32
+ isinstance(result, FormationsMultiSearchResult)
33
+ and (
34
+ result.facet_distribution is None
35
+ or isinstance(result.facet_distribution, FormationsFacetDistribution)
36
+ )
37
+ and (
38
+ result.facet_stats is None
39
+ or isinstance(result.facet_stats, FormationsFacetStats)
40
+ )
41
+ )
@@ -0,0 +1,57 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any
5
+
6
+
7
+ @dataclass
8
+ class GameStatsModel:
9
+ """Model for live game statistics from gsId field."""
10
+
11
+ match_id: str | None = None
12
+ current_status: str | None = None
13
+ current_period: str | None = None
14
+
15
+ # Score domicile (home team)
16
+ score_q1_home: int | None = None
17
+ score_q2_home: int | None = None
18
+ score_q3_home: int | None = None
19
+ score_q4_home: int | None = None
20
+ score_ot1_home: int | None = None
21
+ score_ot2_home: int | None = None
22
+
23
+ # Score extérieur (away team)
24
+ score_q1_out: int | None = None
25
+ score_q2_out: int | None = None
26
+ score_q3_out: int | None = None
27
+ score_q4_out: int | None = None
28
+ score_ot1_out: int | None = None
29
+ score_ot2_out: int | None = None
30
+
31
+ @classmethod
32
+ def from_dict(cls, data: dict[str, Any]) -> GameStatsModel | None:
33
+ """Convert dictionary to GameStatsModel instance."""
34
+ if not data:
35
+ return None
36
+
37
+ # Handle case where data is not a dictionary
38
+ if not isinstance(data, dict):
39
+ return None
40
+
41
+ return cls(
42
+ match_id=data.get("matchId"),
43
+ current_status=data.get("currentStatus"),
44
+ current_period=data.get("currentPeriod"),
45
+ score_q1_home=data.get("score_q1_home"),
46
+ score_q2_home=data.get("score_q2_home"),
47
+ score_q3_home=data.get("score_q3_home"),
48
+ score_q4_home=data.get("score_q4_home"),
49
+ score_ot1_home=data.get("score_ot1_home"),
50
+ score_ot2_home=data.get("score_ot2_home"),
51
+ score_q1_out=data.get("score_q1_out"),
52
+ score_q2_out=data.get("score_q2_out"),
53
+ score_q3_out=data.get("score_q3_out"),
54
+ score_q4_out=data.get("score_q4_out"),
55
+ score_ot1_out=data.get("score_ot1_out"),
56
+ score_ot2_out=data.get("score_ot2_out"),
57
+ )
@@ -0,0 +1,5 @@
1
+ """Backward-compatibility re-export shim for GameStatsModel."""
2
+
3
+ from .game_stats_model import GameStatsModel
4
+
5
+ __all__ = ["GameStatsModel"]