def get_next_step(self, logger: Logger, dt: datetime, channel: "Channel") -> Step: if self == channel.station_at: return Step.none() return Step(start=int(dt.timestamp()), end=int(dt.timestamp()), broadcast=Broadcast.empty(self))
def get_next_step(self, logger: Logger, dt: datetime, channel: "Channel") -> Step: if self == channel.station_at(dt): return Step.none() return Step(start=int(dt.timestamp()), end=int(dt.timestamp()), broadcast=Broadcast(title="La Playlist Pycolore", type=BroadcastType.PROGRAMME, station=self.station_info, thumbnail_src=self.station_thumbnail))
def test_detailed_radiofrance_diffusion_step_second_child(monkeypatch_apple_podcast): """Test radiofrance'step parsing: Detailed step with child precision (second). """ parsed_step = FranceInter()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][0], dt=datetime.fromtimestamp(1602738880), child_precision=True, detailed=True) expected_step = Step( start=1602738880, end=1602738960, broadcast=Broadcast(title="\"Bordel, cette américanisation !\"", summary="Le thème, et l’anathème, de \"l’américanisation\" de la France sont anciens. Autrefois on parlait de \"coca-colonisation\" des modes de vie.", type=BroadcastType.PROGRAMME, station=FranceInter().station_info, thumbnail_src="https://is3-ssl.mzstatic.com/image/thumb/Podcasts113/v4/9f/7c/81/9f7c81de-7d7d-6d54-27a2-c0e52509cb43/mza_3524174555840859685.jpg/626x0w.webp", link="https://www.franceinter.fr/emissions/les-80-de/les-80-de-15-octobre-2020", show_title="Les 80\" de...", show_link="https://www.franceinter.fr/emissions/les-80-de-nicolas-demorand", parent_show_title="Le 7/9", parent_show_link="https://www.franceinter.fr/emissions/le-7-9")) assert parsed_step == expected_step
def test_detailed_radiofrance_diffusion_step_first_child(monkeypatch_apple_podcast): """Test radiofrance'step parsing: Detailed step with child precision (first child programme). """ parsed_step = FranceInter()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][0], dt=datetime.fromtimestamp(1602738010), child_precision=True, detailed=True) expected_step = Step( start=1602738010, end=1602738780, broadcast=Broadcast(title="Retour au bercail à 21 heures pour 20 millions de français !", summary="Le chef de l'Etat a annoncé hier soir l'instauration d'un couvre feu en Ile de France et dans 8 métropoles à partir de samedi et pour 6 semaines. Emmanuel Macron en appelle à la responsabilité de chacun pour lutter contre l'épidémie qui a fait plus de 33 000 morts.", type=BroadcastType.PROGRAMME, station=FranceInter().station_info, thumbnail_src="https://is3-ssl.mzstatic.com/image/thumb/Podcasts113/v4/fc/74/e8/fc74e883-1a69-3b9d-70f4-43e185f57db6/mza_6895079223603784045.jpg/626x0w.webp", link="https://www.franceinter.fr/emissions/journal-de-7h/journal-de-7h-15-octobre-2020", show_title="Journal de 7h", show_link="https://www.franceinter.fr/emissions/le-journal-de-7h", parent_show_title="Le 7/9", parent_show_link="https://www.franceinter.fr/emissions/le-7-9")) assert parsed_step == expected_step
def get_schedule(self, logger: Logger, start: datetime, end: datetime) -> List[Step]: return [ Step(start=int(start.timestamp()), end=int(end.timestamp()), broadcast=Broadcast.empty(self)) ]
def get_step(self, logger: Logger, dt: datetime, channel) -> UpdateInfo: """Returns mapping containing info about current song. If music: {"type": BroadcastType.MUSIC, "artist": artist, "title": title} If ads: "type": BroadcastType.ADS Else: "type": BroadcastType.NONE Moreover, returns other metadata for postprocessing. end datetime object To sum up, here are the keys of returned mapping: - type: BroadcastType object - end: timestamp in sec - artist: str (optional) - title: str (optional) - thumbnail_src: url to thumbnail """ start = int(dt.timestamp()) try: fetched_data = self._fetch_song_metadata() except requests.exceptions.Timeout: return self._update_info(Step.empty_until(start, start + 90, self)) end = fetched_data["end"] if start > end: if not self._get_show_metadata(dt): return self._update_info(Step.empty(start, self)) end = 0 broadcast_data = { "thumbnail_src": self.station_thumbnail, "type": BroadcastType.PROGRAMME, "title": self.station_slogan, } else: title = fetched_data['title'] artist = fetched_data['singer'] thumbnail = fetched_data.get("cover") or self.station_thumbnail broadcast_data = { "title": f"{artist} • {title}", "type": BroadcastType.MUSIC, "thumbnail_src": thumbnail, "metadata": SongPayload(title=title, artist=artist) } broadcast_data.update(station=self.station_info, **self._get_show_metadata(dt)) return self._update_info( Step(start=start, end=end, broadcast=Broadcast(**broadcast_data)))
def _step_from_show_data(self, show_data: dict): return Step(start=show_data.get("show_start"), end=show_data.get("show_end"), broadcast=Broadcast(title=show_data.get( "show_title", self.station_slogan), type=BroadcastType.PROGRAMME, station=self.station_info, thumbnail_src=self.station_thumbnail, summary=show_data.get("summary", "")))
def get_schedule(self, logger: Logger, start: datetime, end: datetime) -> List[Step]: return [ Step(start=int(start.timestamp()), end=int(end.timestamp()), broadcast=Broadcast(title="La Playlist Pycolore", type=BroadcastType.PROGRAMME, station=self.station_info, thumbnail_src=self.station_thumbnail)) ]
def get_step(self, logger: Logger, dt: datetime, channel: Channel) -> UpdateInfo: dt_timestamp = int(dt.timestamp()) if self._current_song is None: next_station_name = channel.station_after(dt).name next_station_start = int(channel.station_end_at(dt).timestamp()) return UpdateInfo(should_notify_update=True, step=Step.waiting_for_next_station( dt_timestamp, next_station_start, self, next_station_name)) artists_list = tuple(self._artists) artists_str = ", ".join(artists_list[:-1]) + " et " + artists_list[-1] thumbnail_src, link = fetch_cover_and_link_on_deezer( self.station_thumbnail, self._current_song.artist, self._current_song.album, self._current_song.title) return UpdateInfo( should_notify_update=True, step=Step( start=dt_timestamp, end=int(self._current_song_end), broadcast=Broadcast( title= f"{self._current_song.artist} • {self._current_song.title}", link=link, thumbnail_src=thumbnail_src, station=self.station_info, type=BroadcastType.MUSIC, show_link= "https://radio.pycolore.fr/pages/playlist-pycolore", show_title="La playlist Pycolore", summary= (f"Une sélection aléatoire de chansons parmi les musiques stockées sur Pycolore. À suivre : " f"{artists_str}."), metadata=SongPayload( title=self._current_song.title, artist=self._current_song.artist, album="La Playlist Pycolore", base64_cover_art=url_to_base64(thumbnail_src)))))
def _handle_api_exception(self, api_data, logger, start) -> Optional[Step]: """Return a step if an error in API response was detected. Else return None.""" error_message: Optional[str] = None if "API Timeout" in api_data.values(): error_message = "API Timeout" elif "API rate limit exceeded" in api_data.values(): error_message = "Radio France API rate limit exceeded" elif api_data.get("data") is None: error_message = "No data provided by Radio France API" elif not api_data["data"]["grid"]: error_message = "Grid provided by Radio France API is empty" if error_message: logger.error(error_message) return Step.empty_until(start, start + 90, self) if error_message else None
def get_schedule(self, logger: Logger, start: datetime, end: datetime) -> List[Step]: temp_end, end = start, end steps = [] while temp_end <= end: temp_start = temp_end title, temp_end = self._get_franceinfo_slot(temp_end) steps.append( Step(start=int(temp_start.timestamp()), end=int(temp_end.timestamp()), broadcast=Broadcast( title=title, type=BroadcastType.PROGRAMME, station=self.station_info, thumbnail_src=self.station_thumbnail))) return steps
def _get_radiofrance_programme_step(self, api_data: dict, dt: datetime, child_precision: bool, detailed: bool): """Return radio france step starting at dt. Parameters: child_precision: bool -- if True, search current child if current broadcast contains any detailed: bool -- if True, return more info in step such as summary, external links, parent broadcast """ start = max(int(api_data["start"]), int(dt.timestamp())) metadata = { "station": self.station_info, "type": BroadcastType.PROGRAMME } children = (api_data.get("children") or []) if child_precision else [] broadcast, broadcast_end, is_child = (self._find_current_child_show( children, api_data, start) if any(children) else (api_data, int(api_data["end"]), False)) diffusion = broadcast.get("diffusion") if diffusion is None: title = broadcast["title"] show_title = "" thumbnail_src = self.station_thumbnail else: show = diffusion.get("show", {}) or {} title = diffusion.get("title") or show.get("title", "") show_title = show.get("title", "") if title != show.get("title", "") else "" podcast_link = (show.get("podcast") or {}).get("itunes") thumbnail_src = music.fetch_apple_podcast_cover( podcast_link, self.station_thumbnail) metadata.update({ "title": title, "show_title": show_title, "thumbnail_src": thumbnail_src, }) if detailed: metadata = self._get_detailed_metadata(metadata, api_data, broadcast, is_child) return Step(start=start, end=broadcast_end, broadcast=Broadcast(**metadata))
def test_radiofrance_blank_step(): """Test radiofrance step parsing: Blank step """ parsed_step = FranceInfo()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][3], dt=datetime.fromtimestamp(1610662290), child_precision=True, detailed=True) expected_step = Step( start=1610662290, end=1610662800, broadcast=Broadcast( title="Les nouvelles mesures sanitaires pour les écoles", type=BroadcastType.PROGRAMME, station=FranceInfo().station_info, thumbnail_src=FranceInfo.station_thumbnail)) assert parsed_step == expected_step
def test_basic_radiofrance_diffusion_step(monkeypatch_apple_podcast): """Test radiofrance'step parsing: Undetailed step without child precision. """ parsed_step = FranceInter()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][0], dt=datetime.fromtimestamp(1602738000), child_precision=False, detailed=False) expected_step = Step( start=1602738000, end=1602745200, broadcast=Broadcast(title="Gaël Perdriau - Aurélien Rousseau", type=BroadcastType.PROGRAMME, station=FranceInter().station_info, thumbnail_src="https://is3-ssl.mzstatic.com/image/thumb/Podcasts113/v4/88/31/cb/8831cb22-ff5f-03fa-7815-1c57552ea7d7/mza_5059723156060763498.jpg/626x0w.webp", show_title="Le 7/9",)) assert parsed_step == expected_step
def _get_radiofrance_track_step(self, api_data: dict, dt: datetime): start = max(int(api_data["start"]), int(dt.timestamp())) track_data = api_data["track"] artists = ", ".join( track_data.get("mainArtists") or track_data.get("performers")) cover_link, deezer_link = music.fetch_cover_and_link_on_deezer( self.station_thumbnail, artists, track_data.get("albumTitle")) return Step(start=start, end=api_data["end"], broadcast=Broadcast( title=f"{artists} • {track_data['title']}", type=BroadcastType.MUSIC, station=self.station_info, link=deezer_link, summary=self.station_slogan, thumbnail_src=cover_link, metadata=SongPayload( title=track_data["title"], artist=artists, album=track_data.get("album", ""), base64_cover_art=url_to_base64(cover_link))))
def process(self, step: Step, logger: Logger, dt: datetime) -> Step: """Play backup songs if advertising is detected on currently broadcasted station.""" if step.broadcast.type != BroadcastType.ADS: return step logger.debug( f"channel={self.channel.id} station={self.channel.station_at(dt).formatted_station_name} Ads detected." ) if not self.backup_songs: logger.debug( f"channel={self.channel.id} Backup songs list must be generated." ) self.backup_songs = self._parse_songs() backup_song = self.backup_songs.pop(0) # tell liquidsoap to play backup song with liquidsoap_telnet_session() as session: session.write( f"{self.channel.id}_custom_songs.push {backup_song.path}\n". encode()) broadcast = step.broadcast thumbnail, url = fetch_cover_and_link_on_deezer( self.channel.station_at(dt).station_thumbnail, backup_song.artist, backup_song.album, backup_song.title) # and update metadata return Step( start=step.start, end=step.start + int(backup_song.length), broadcast=Broadcast( title=f"{backup_song.artist} • {backup_song.title}", type=BroadcastType.MUSIC, station=broadcast.station, thumbnail_src=thumbnail, summary= (f"Publicité en cours sur {broadcast.station.name}. En attendant, voici une chanson de la " "playlist Pycolore."), show_title="La playlist Pycolore", show_link="/pycolore/playlist/", ))
def test_radiofrance_track_step(): """Test radiofrance step parsing: Track step """ parsed_step = FranceInterParis()._get_radiofrance_track_step( api_data=API_DATA["data"]["grid"][1], dt=datetime.fromtimestamp(1610662504)) expected_step = Step( start=1610662504, end=1610662753, broadcast=Broadcast( title="Ihsan Al-Munzer • Jamileh", type=BroadcastType.MUSIC, station=FranceInterParis().station_info, thumbnail_src="https://cdns-images.dzcdn.net/images/artist/a1a23844fed57b71fd1f18a9f633636e/500x500-000000-80-0-0.jpg", summary=FranceInterParis.station_slogan, metadata=SongPayload(title='Jamileh', artist='Ihsan Al-Munzer', album=''), link="https://www.deezer.com/artist/65281352")) assert parsed_step == expected_step
def test_radiofrance_track_step_without_artist(): """Test radiofrance step parsing: Track step without mainArtists field """ parsed_step = FranceInterParis()._get_radiofrance_track_step( api_data=API_DATA["data"]["grid"][2], dt=datetime.fromtimestamp(1610307944)) expected_step = Step( start=1610307944, end=1610308075, broadcast=Broadcast( title="HENRI TEXIER • ENFANT LIVRE", type=BroadcastType.MUSIC, station=FranceInterParis().station_info, thumbnail_src="https://cdns-images.dzcdn.net/images/artist/345f1b012b55d907be32e0b80864fed2/500x500-000000-80-0-0.jpg", summary=FranceInterParis.station_slogan, metadata=SongPayload(title='ENFANT LIVRE', artist='HENRI TEXIER', album=''), link="https://www.deezer.com/artist/153756")) assert parsed_step == expected_step
def test_detailed_radiofrance_diffusion_step_after_last_child(monkeypatch_apple_podcast): """Test radiofrance'step parsing: Detailed step with child precision (after last child). """ parsed_step = FranceInter()._get_radiofrance_programme_step( api_data=API_DATA["data"]["grid"][0], dt=datetime.fromtimestamp(1602745050), child_precision=True, detailed=True) expected_step = Step( start=1602745050, end=1602745200, broadcast=Broadcast(title="Gaël Perdriau - Aurélien Rousseau", summary="Gaël Perdriau, maire LR de Saint-Etienne, et Aurélien Rousseau, directeur général de l'Agence régional de santéd'Île-de-France, sont les invités du 7/9 de France Inter.", type=BroadcastType.PROGRAMME, station=FranceInter().station_info, thumbnail_src="https://is3-ssl.mzstatic.com/image/thumb/Podcasts113/v4/88/31/cb/8831cb22-ff5f-03fa-7815-1c57552ea7d7/mza_5059723156060763498.jpg/626x0w.webp", link='https://www.franceinter.fr/emissions/le-7-9/le-7-9-15-octobre-2020', show_title="Le 7/9", show_link="https://www.franceinter.fr/emissions/le-7-9")) assert parsed_step == expected_step
def schedule(self, data: List[Dict]): return [Step(**step_data) for step_data in data]
def test_update_info_unpacking(): should_notify, step = UpdateInfo(should_notify_update=True, step=Step.none()) assert (should_notify, step) == (True, Step.none())
def __init__(self): super().__init__() self._current_show_data = {"show_end": datetime.now().timestamp()} self.current_step = Step.none()
class RadioFranceStation(URLStation): API_RATE_LIMIT_EXCEEDED = 1 _station_api_name: str _grid_template = RADIO_FRANCE_GRID_TEMPLATE @property def token(self): if os.getenv("TOKEN") is None: # in case of development server load_dotenv() if os.getenv("TOKEN") is None: raise RuntimeError("No token for Radio France API found.") return os.getenv("TOKEN") @staticmethod def _notifying_update_info(step): return UpdateInfo(should_notify_update=True, step=step) def _fetch_metadata(self, start: datetime, end: datetime, retry=0, current=0, raise_exc=False) -> Dict[Any, Any]: """Fetch metadata from radiofrance open API.""" query = self._grid_template.format(start=int(start.timestamp()), end=int(end.timestamp()), station=self._station_api_name) try: rep = requests.post( url="https://openapi.radiofrance.fr/v1/graphql?x-token={}". format(self.token), json={"query": query}, timeout=4, ) except requests.exceptions.Timeout: if current < retry: return self._fetch_metadata(start, end, retry, current + 1, raise_exc) if raise_exc: raise TimeoutError("Radio France API Timeout") return {"message": "API Timeout"} data = json.loads(rep.text) return data @staticmethod def _find_current_child_show(children: List[Any], parent: Dict[str, Any], start: int) -> Tuple[Dict, int, bool]: """Return current show among children and its end timestamp. Sometimes, current timestamp is between 2 children. In this case, return parent show and next child start as end. Parameters: - children: list of dict representing radiofrance steps - parent: dict representing radiofrance step - start: datetime object representing asked timestamp Return a tuple containing: - dict representing a step - end timestamp - True if the current show is child or not """ # on enlève les enfants vides (les TrackStep que l'on ne prend pas en compte) children = filter(bool, children) # on trie dans l'ordre inverse children = sorted(children, key=lambda x: x.get("start"), reverse=True) # on initialise l'enfant suivant (par défaut le dernier) next_child = children[0] # et on parcourt la liste des enfants à l'envers for child in children: # dans certains cas, le type de step ne nous intéresse pas # et est donc vide, on passe directement au suivant # (c'est le cas des TrackSteps) if child.get("start") is None: continue # si le début du programme est à venir, on passe au précédent if child["start"] > start: next_child = child continue # au premier programme dont le début est avant la date courante # on sait qu'on est potentiellement dans le programme courant. # Il faut vérifier que l'on est encore dedans en vérifiant : if child["end"] > start: return child, int(child["end"]), True # sinon, on est dans un "trou" : on utilise donc le parent # et le début de l'enfant suivant. Cas particulier : si on est # entre la fin du dernier enfant et la fin du parent (càd l'enfant # suivant est égal à l'enfant courant), on prend la fin du parent. elif next_child == child: return parent, int(parent["end"]), False else: return parent, int(next_child["start"]), False else: # si on est ici, c'est que la boucle a parcouru tous les enfants # sans valider child["start"] < now. Autrement dit, le premier # enfant n'a pas encore commencé. On renvoie donc le parent et le # début du premier enfant (stocké dans next_child) comme end return parent, int(next_child.get("start")) or parent["end"], False def _handle_api_exception(self, api_data, logger, start) -> Optional[Step]: """Return a step if an error in API response was detected. Else return None.""" error_message: Optional[str] = None if "API Timeout" in api_data.values(): error_message = "API Timeout" elif "API rate limit exceeded" in api_data.values(): error_message = "Radio France API rate limit exceeded" elif api_data.get("data") is None: error_message = "No data provided by Radio France API" elif not api_data["data"]["grid"]: error_message = "Grid provided by Radio France API is empty" if error_message: logger.error(error_message) return Step.empty_until(start, start + 90, self) if error_message else None @staticmethod def _get_detailed_metadata(metadata: dict, parent: dict, child: dict, is_child: bool) -> dict: """Alter (add detailed information to) a copy of metadata and return it :param metadata: metadata to update (this method creates a copy and alter it) :param parent: parent broadcast :param child: child broadcast (may be identical to parent) :param is_child: if True, add parent_show_title and parent_show_link info :return: updated copy of metadata input """ detailed_metadata = metadata.copy() diffusion = child.get("diffusion") or {} show = diffusion.get("show") or {} if not is_child: diffusion_summary = diffusion.get("standFirst", "") or "" if len(diffusion_summary.strip()) == 1: diffusion_summary = "" detailed_metadata.update({ "show_link": show.get("url", ""), "link": diffusion.get("url", ""), "summary": diffusion_summary.strip(), }) return detailed_metadata parent_diffusion = parent.get("diffusion") or {} parent_show = parent_diffusion.get("show") or {} diffusion_summary = diffusion.get( "standFirst", "") or parent_diffusion.get("standFirst", "") or "" child_show_link = diffusion.get("url", "") or parent_diffusion.get( "url", "") parent_show_link = parent_show.get("url") or "" parent_show_title = parent_show.get("title") or parent["title"] # on vérifie que les infos parents ne sont pas redondantes avec les infos enfantes if (parent_show_link == child_show_link or parent_show_title.upper() in (metadata.get( "title", "").upper(), metadata.get("show_title", "").upper())): parent_show_link = "" parent_show_title = "" if len(diffusion_summary.strip()) == 1: diffusion_summary = "" detailed_metadata.update({ "show_link": show.get("url", ""), "link": diffusion.get("url", "") or parent_diffusion.get("url", ""), "summary": diffusion_summary.strip(), "parent_show_title": parent_show_title, "parent_show_link": parent_show_link, }) return detailed_metadata def _get_radiofrance_step(self, api_data: dict, dt: datetime, child_precision: bool, detailed: bool) -> Step: """Return a track step or a programme step from Radio France depending of contained metadata""" return (self._get_radiofrance_track_step(api_data, dt) if api_data.get("track") else self._get_radiofrance_programme_step( api_data, dt, child_precision, detailed)) def _get_radiofrance_programme_step(self, api_data: dict, dt: datetime, child_precision: bool, detailed: bool): """Return radio france step starting at dt. Parameters: child_precision: bool -- if True, search current child if current broadcast contains any detailed: bool -- if True, return more info in step such as summary, external links, parent broadcast """ start = max(int(api_data["start"]), int(dt.timestamp())) metadata = { "station": self.station_info, "type": BroadcastType.PROGRAMME } children = (api_data.get("children") or []) if child_precision else [] broadcast, broadcast_end, is_child = (self._find_current_child_show( children, api_data, start) if any(children) else (api_data, int(api_data["end"]), False)) diffusion = broadcast.get("diffusion") if diffusion is None: title = broadcast["title"] show_title = "" thumbnail_src = self.station_thumbnail else: show = diffusion.get("show", {}) or {} title = diffusion.get("title") or show.get("title", "") show_title = show.get("title", "") if title != show.get("title", "") else "" podcast_link = (show.get("podcast") or {}).get("itunes") thumbnail_src = music.fetch_apple_podcast_cover( podcast_link, self.station_thumbnail) metadata.update({ "title": title, "show_title": show_title, "thumbnail_src": thumbnail_src, }) if detailed: metadata = self._get_detailed_metadata(metadata, api_data, broadcast, is_child) return Step(start=start, end=broadcast_end, broadcast=Broadcast(**metadata)) def _get_radiofrance_track_step(self, api_data: dict, dt: datetime): start = max(int(api_data["start"]), int(dt.timestamp())) track_data = api_data["track"] artists = ", ".join( track_data.get("mainArtists") or track_data.get("performers")) cover_link, deezer_link = music.fetch_cover_and_link_on_deezer( self.station_thumbnail, artists, track_data.get("albumTitle")) return Step(start=start, end=api_data["end"], broadcast=Broadcast( title=f"{artists} • {track_data['title']}", type=BroadcastType.MUSIC, station=self.station_info, link=deezer_link, summary=self.station_slogan, thumbnail_src=cover_link, metadata=SongPayload( title=track_data["title"], artist=artists, album=track_data.get("album", ""), base64_cover_art=url_to_base64(cover_link)))) def get_step(self, logger: Logger, dt: datetime, channel) -> UpdateInfo: start = int(dt.timestamp()) fetched_data = self._fetch_metadata(dt, dt + timedelta(minutes=120)) if (error_step := self._handle_api_exception(fetched_data, logger, start)) is not None: return self._notifying_update_info(error_step) try: # on récupère la première émission trouvée first_show_in_grid = fetched_data["data"]["grid"][0] # si celle-ci est terminée et la suivante n'a pas encore démarrée # alors on RENVOIE une métadonnées neutre jusqu'au démarrage de l'émission # suivante if first_show_in_grid["end"] < start: next_show = fetched_data["data"]["grid"][1] return self._notifying_update_info( Step.empty_until(start, int(next_show["start"]), self)) # si l'émission n'est pas encore démarrée, on RENVOIE une métaonnée neutre # jusqu'au démarrage de celle-ci if first_show_in_grid["start"] > dt.timestamp(): return self._notifying_update_info( Step.empty_until(start, int(first_show_in_grid['start']), self)) # cas où on est dans un programme, on RENVOIE les métadonnées de ce programme return self._notifying_update_info( self._get_radiofrance_step(first_show_in_grid, dt, child_precision=True, detailed=True)) except Exception as err: logger.error(traceback.format_exc()) logger.error("Données récupérées avant l'exception : {}".format( fetched_data)) return self._notifying_update_info( Step.empty_until(start, start + 90, self))
if (error_step := self._handle_api_exception(api_data, logger, int(dt.timestamp()))) is not None: return error_step try: first_show_in_grid = api_data["data"]["grid"][0] return self._get_radiofrance_step(first_show_in_grid, dt, child_precision=True, detailed=False) except Exception as err: start = int(dt.timestamp()) logger.error(traceback.format_exc()) logger.error( "Données récupérées avant l'exception : {}".format(api_data)) return Step.empty_until(start, start + 90, self) def get_schedule(self, logger: Logger, start: datetime, end: datetime) -> List[Step]: api_data = self._fetch_metadata(start, end, retry=5, raise_exc=True) temp_end, end = start, end steps = [] grid = api_data["data"]["grid"] while grid: step_data = grid.pop(0) new_step = self._get_radiofrance_programme_step( step_data, temp_end, child_precision=False, detailed=False) steps.append(new_step) temp_end = datetime.fromtimestamp(new_step.end) return steps
def _post_get_hook_step(self, data: dict): try: return Step(**data) except (TypeError, ValidationError) as err: return Step.none()