diff --git a/betfairlightweight/endpoints/inplayservice.py b/betfairlightweight/endpoints/inplayservice.py index a4b75a1e..3d68bee4 100644 --- a/betfairlightweight/endpoints/inplayservice.py +++ b/betfairlightweight/endpoints/inplayservice.py @@ -100,6 +100,36 @@ def get_scores( response_json, resources.Scores, elapsed_time, lightweight ) + def get_scores_and_broadcast( + self, + event_ids: list, + session: requests.Session = None, + lightweight: bool = None, + ) -> Union[list, List[resources.ScoresAndBroadcast]]: + """ + Returns a list of scores with broadcast data (tv/radio/ + live video) and match info based on event id's supplied. + + :param list event_ids: List of event id's to return + :param requests.session session: Requests session object + :param bool lightweight: If True will return dict not a resource + + :rtype: list[resources.ScoresAndBroadcast] + """ + url = "%s%s" % (self.url, "scoresAndBroadcast") + params = { + "eventIds": ",".join(str(x) for x in event_ids), + "alt": "json", + "regionCode": "UK", + "locale": "en_GB", + } + (response, response_json, elapsed_time) = self.request( + params=params, session=session, url=url + ) + return self.process_response( + response_json, resources.ScoresAndBroadcast, elapsed_time, lightweight + ) + def request( self, method: str = None, diff --git a/betfairlightweight/resources/__init__.py b/betfairlightweight/resources/__init__.py index 56e4c611..e72c3b78 100644 --- a/betfairlightweight/resources/__init__.py +++ b/betfairlightweight/resources/__init__.py @@ -36,7 +36,7 @@ from .racecardresources import RaceCard -from .inplayserviceresources import EventTimeline, Scores +from .inplayserviceresources import EventTimeline, Scores, ScoresAndBroadcast from .streamingresources import ( CricketMatch, diff --git a/betfairlightweight/resources/inplayserviceresources.py b/betfairlightweight/resources/inplayserviceresources.py index 577f9c87..2fa8491b 100644 --- a/betfairlightweight/resources/inplayserviceresources.py +++ b/betfairlightweight/resources/inplayserviceresources.py @@ -196,3 +196,54 @@ def __init__(self, **kwargs): if kwargs.get("stateOfBall") else None ) + + +class BroadcastChannel: + def __init__(self, startTime=None, endTime=None, channel=None): + self.start_time = BaseResource.strip_datetime(startTime) + self.end_time = BaseResource.strip_datetime(endTime) + self.channel = channel + + +class RadioBroadcast: + def __init__(self, url=None): + self.url = url + + +class Broadcasts: + def __init__( + self, + tv=None, + radio=None, + bfLiveVideo=None, + isLiveVideoAvailable=None, + isDataVisualizationAvailable=None, + isPaddockViewAvailable=None, + channel=None, + ): + self.tv = [BroadcastChannel(**i) for i in tv] if tv else [] + self.radio = RadioBroadcast(**radio) if radio else None + self.bf_live_video = BroadcastChannel(**bfLiveVideo) if bfLiveVideo else None + self.is_live_video_available = isLiveVideoAvailable + self.is_data_visualization_available = isDataVisualizationAvailable + self.is_paddock_view_available = isPaddockViewAvailable + self.channel = channel + + +class MatchInfo: + # fields returned vary by event type, raw response stored in _data + def __init__(self, **kwargs): + self._data = kwargs + self.surface = kwargs.get("surface") + self.number_of_sets = kwargs.get("numberOfSets") + + +class ScoresAndBroadcast(Scores): + def __init__(self, **kwargs): + super(ScoresAndBroadcast, self).__init__(**kwargs) + self.broadcasts = ( + Broadcasts(**kwargs.get("broadcasts")) if kwargs.get("broadcasts") else None + ) + self.match_info = ( + MatchInfo(**kwargs.get("matchInfo")) if kwargs.get("matchInfo") else None + ) diff --git a/docs/endpoints.md b/docs/endpoints.md index dd5ca0e7..c065ee44 100644 --- a/docs/endpoints.md +++ b/docs/endpoints.md @@ -158,6 +158,9 @@ trading.in_play_service.get_event_timelines() ```python trading.in_play_service.get_scores() ``` +```python +trading.in_play_service.get_scores_and_broadcast() +``` ### Race Card diff --git a/tests/resources/scoresandbroadcast.json b/tests/resources/scoresandbroadcast.json new file mode 100644 index 00000000..abe9c4b8 --- /dev/null +++ b/tests/resources/scoresandbroadcast.json @@ -0,0 +1,75 @@ +{ + "eventTypeId": 2, + "eventId": 33163030, + "score": { + "home": { + "name": "J Smith", + "score": "40", + "halfTimeScore": "", + "fullTimeScore": "", + "penaltiesScore": "", + "penaltiesSequence": [], + "games": "3", + "sets": "1", + "gameSequence": [6, 3], + "isServing": true, + "playerSeed": "1", + "aces": 4, + "doubleFaults": 1, + "serviceBreaks": 2, + "highlight": false + }, + "away": { + "name": "R Jones", + "score": "30", + "halfTimeScore": "", + "fullTimeScore": "", + "penaltiesScore": "", + "penaltiesSequence": [], + "games": "2", + "sets": "0", + "gameSequence": [4, 2], + "isServing": false, + "playerSeed": "4", + "aces": 2, + "doubleFaults": 3, + "serviceBreaks": 1, + "highlight": false + } + }, + "timeElapsed": 58, + "timeElapsedSeconds": 12, + "currentSet": 2, + "fullTimeElapsed": { + "hour": 0, + "min": 58, + "sec": 12 + }, + "status": "IN_PLAY", + "matchStatus": "InProgress", + "broadcasts": { + "tv": [ + { + "startTime": "2024-03-26T16:00:00.000Z", + "endTime": "2024-03-26T21:00:00.000Z", + "channel": "Sky Sports Main Event" + } + ], + "radio": { + "url": "http://radio.betfair.com" + }, + "bfLiveVideo": { + "startTime": "2024-03-26T16:00:00.000Z", + "endTime": "2024-03-26T21:00:00.000Z", + "channel": "http://livevideo.betfair.com/Default.do?mi=226657935" + }, + "isLiveVideoAvailable": true, + "isDataVisualizationAvailable": false, + "isPaddockViewAvailable": false, + "channel": "WEB" + }, + "matchInfo": { + "surface": "", + "numberOfSets": "3" + } +} diff --git a/tests/test_inplayservice.py b/tests/test_inplayservice.py index 89112862..cb18cc70 100644 --- a/tests/test_inplayservice.py +++ b/tests/test_inplayservice.py @@ -93,6 +93,31 @@ def test_get_scores(self, mock_request, mock_process_response): assert mock_request.call_count == 1 assert mock_process_response.call_count == 1 + @mock.patch( + "betfairlightweight.endpoints.inplayservice.InPlayService.process_response" + ) + @mock.patch( + "betfairlightweight.endpoints.inplayservice.InPlayService.request", + return_value=(mock.Mock(), mock.Mock(), 1.3), + ) + def test_get_scores_and_broadcast(self, mock_request, mock_process_response): + event_ids = [12345, 54321] + params = { + "eventIds": "12345,54321", + "alt": "json", + "regionCode": "UK", + "locale": "en_GB", + } + self.in_play_service.get_scores_and_broadcast(event_ids) + + mock_request.assert_called_with( + url="https://ips.betfair.com/inplayservice/v1.1/scoresAndBroadcast", + session=None, + params=params, + ) + assert mock_request.call_count == 1 + assert mock_process_response.call_count == 1 + @mock.patch("betfairlightweight.endpoints.inplayservice.check_status_code") @mock.patch("betfairlightweight.endpoints.inplayservice.InPlayService.headers") @mock.patch("betfairlightweight.baseclient.requests.get") diff --git a/tests/test_inplayserviceresources.py b/tests/test_inplayserviceresources.py index b26b92d1..dc40ea50 100644 --- a/tests/test_inplayserviceresources.py +++ b/tests/test_inplayserviceresources.py @@ -18,3 +18,38 @@ def test_event_timeline(self): assert isinstance(resource, resources.EventTimeline) assert resource.event_type_id == 1 + + def test_scores_and_broadcast(self): + mock_response = create_mock_json("tests/resources/scoresandbroadcast.json") + resource = resources.ScoresAndBroadcast(**mock_response.json()) + + assert isinstance(resource, resources.ScoresAndBroadcast) + assert isinstance(resource, resources.Scores) + assert resource.event_type_id == 2 + assert resource.event_id == 33163030 + assert resource.score.home.name == "J Smith" + assert resource.score.away.name == "R Jones" + assert len(resource.broadcasts.tv) == 1 + assert resource.broadcasts.tv[0].channel == "Sky Sports Main Event" + assert resource.broadcasts.tv[0].start_time is not None + assert resource.broadcasts.tv[0].end_time is not None + assert resource.broadcasts.radio.url == "http://radio.betfair.com" + assert ( + resource.broadcasts.bf_live_video.channel + == "http://livevideo.betfair.com/Default.do?mi=226657935" + ) + assert resource.broadcasts.is_live_video_available is True + assert resource.broadcasts.is_data_visualization_available is False + assert resource.broadcasts.is_paddock_view_available is False + assert resource.broadcasts.channel == "WEB" + assert resource.match_info.surface == "" + assert resource.match_info.number_of_sets == "3" + + def test_scores_and_broadcast_empty(self): + mock_response = create_mock_json("tests/resources/scores.json") + resource = resources.ScoresAndBroadcast(**mock_response.json()) + + assert isinstance(resource, resources.ScoresAndBroadcast) + assert resource.event_type_id == 1 + assert resource.broadcasts is None + assert resource.match_info is None