Repository: pymedusa/Medusa

Issue:
[APP SUBMITTED]: ReadTimeout: HTTPSConnectionPool(host='api.themoviedb.org', port=443): Read timed out. (read timeout=30)

### INFO
**Python Version**: `2.7.9 (default, Sep 17 2016, 20:26:04) [GCC 4.9.2]`
**Operating System**: `Linux-4.9.35-v7+-armv7l-with-debian-8.0`
**Locale**: `UTF-8`
**Branch**: [develop](../tree/develop)
**Database**: `44.9`
**Commit**: pymedusa/Medusa@52a946339926c31010f794da5f5858fecc5f5b1d
**Link to Log**: https://gist.github.com/248adcc24cf5bd056e28da8e84b6f012
### ERROR
<pre>
2018-01-19 02:01:39 ERROR    SHOWUPDATER :: [52a9463] Problem running show_updater, Indexer TMDB seems to be having issues while trying to get updates for show The Good Doctor. Cause: HTTPSConnectionPool(host='api.themoviedb.org', port=443): Read timed out. (read timeout=30)
Traceback (most recent call last):
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/medusa/show_updater.py#L145">medusa/show_updater.py</a>", line 145, in run
    updated_seasons = indexer_api.get_last_updated_seasons([show.indexerid], last_update, update_max_weeks)
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/medusa/indexers/tmdb/tmdb.py#L557">medusa/indexers/tmdb/tmdb.py</a>", line 557, in get_last_updated_seasons
    search_until.strftime('%Y-%m-%d'))
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/medusa/indexers/tmdb/tmdb.py#L479">medusa/indexers/tmdb/tmdb.py</a>", line 479, in _get_series_season_updates
    updates = self.tmdb.TV(sid).changes(start_date=start_date, end_date=end_date)
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/lib/tmdbsimple/tv.py#L280">lib/tmdbsimple/tv.py</a>", line 280, in changes
    response = self._GET(path, kwargs)
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/lib/tmdbsimple/base.py#L87">lib/tmdbsimple/base.py</a>", line 87, in _GET
    return self._request('GET', path, params=params)
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/lib/tmdbsimple/base.py#L80">lib/tmdbsimple/base.py</a>", line 80, in _request
    headers=self.headers)
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/medusa/session/core.py#L94">medusa/session/core.py</a>", line 94, in request
    **kwargs)
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/ext/requests/sessions.py#L508">ext/requests/sessions.py</a>", line 508, in request
    resp = self.send(prep, **send_kwargs)
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/ext/requests/sessions.py#L618">ext/requests/sessions.py</a>", line 618, in send
    r = adapter.send(request, **kwargs)
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/ext/cachecontrol/adapter.py#L50">ext/cachecontrol/adapter.py</a>", line 50, in send
    resp = super(CacheControlAdapter, self).send(request, **kw)
  File "/home/pi/Medusa/<a href="../blob/52a946339926c31010f794da5f5858fecc5f5b1d/ext/requests/adapters.py#L521">ext/requests/adapters.py</a>", line 521, in send
    raise ReadTimeout(e, request=request)
ReadTimeout: HTTPSConnectionPool(host='api.themoviedb.org', port=443): Read timed out. (read timeout=30)
</pre>
---
_STAFF NOTIFIED_: @pymedusa/support @pymedusa/moderators



Discussion:
Need to find where to `raise` a `medusa.indexers.indexer_exceptions.IndexerUnavailable` excpetion so to fix this getting logged as an exception instead of a warning.

Resolving patch:
diff --git a/medusa/indexers/tmdb/tmdb.py b/medusa/indexers/tmdb/tmdb.py
--- a/medusa/indexers/tmdb/tmdb.py
+++ b/medusa/indexers/tmdb/tmdb.py
@@ -163,8 +163,8 @@ def _show_search(self, show, request_language='en'):
                 last = search_result.get('total_pages', 0)
                 results += search_result.get('results')
                 page += 1
-        except Exception as e:
-            raise IndexerException('Show search failed in getting a result with error: {0!r}'.format(e))
+        except RequestException as error:
+            raise IndexerUnavailable('Show search failed using indexer TMDB. Cause: {cause}'.format(cause=error))
 
         if results:
             return results
@@ -207,9 +207,14 @@ def _get_show_by_id(self, tmdb_id, request_language='en'):  # pylint: disable=un
         :return: An ordered dict with the show searched for.
         """
         log.debug('Getting all show data for {0}', tmdb_id)
-        results = self.tmdb.TV(tmdb_id).info(language='{0},null'.format(request_language))
-        if not results:
-            return
+        try:
+            results = self.tmdb.TV(tmdb_id).info(language='{0},null'.format(request_language))
+            if not results:
+                return
+        except RequestException as error:
+            raise IndexerUnavailable('Show info retrieval failed using indexer TMDB. Cause: {cause!r}'.format(
+                cause=error
+            ))
 
         mapped_results = self._map_results(results, self.series_map, '|')
 
@@ -238,13 +243,21 @@ def _get_episodes(self, tmdb_id, specials=False, aired_season=None):  # pylint:
 
         # get episodes for each season
         for season in aired_season:
-            season_info = self.tmdb.TV_Seasons(tmdb_id, season).info(language=self.config['language'])
-            results += season_info['episodes']
+            try:
+                season_info = self.tmdb.TV_Seasons(tmdb_id, season).info(language=self.config['language'])
+                results += season_info['episodes']
+            except RequestException as error:
+                raise IndexerException(
+                    'Could not get episodes for series {series} using indexer TMDB. Cause: {cause}'.format(
+                        series=tmdb_id, cause=error
+                    )
+                )
 
         if not results:
             log.debug('Series results incomplete')
-            raise IndexerShowIncomplete('Show search returned incomplete results '
-                                        '(cannot find complete show on TheMovieDb)')
+            raise IndexerShowIncomplete(
+                'Show search returned incomplete results (cannot find complete show on TheMovieDb)'
+            )
 
         mapped_episodes = self._map_results(results, self.episodes_map, '|')
         episode_data = OrderedDict({'episode': mapped_episodes})
@@ -295,7 +308,7 @@ def _get_episodes(self, tmdb_id, specials=False, aired_season=None):  # pylint:
                                                                  file_path=v)
                 self._set_item(tmdb_id, seas_no, ep_no, k, v)
 
-    def _parse_images(self, sid):
+    def _parse_images(self, tmdb_id):
         """Parse images.
 
         This interface will be improved in future versions.
@@ -304,13 +317,17 @@ def _parse_images(self, sid):
         image_sizes = {'fanart': 'backdrop_sizes', 'poster': 'poster_sizes'}
         typecasts = {'rating': float, 'ratingcount': int}
 
-        log.debug('Getting show banners for {0}', sid)
+        log.debug('Getting show banners for {series}', series=tmdb_id)
         _images = {}
 
         # Let's get the different type of images available for this series
         params = {'include_image_language': '{search_language},null'.format(search_language=self.config['language'])}
 
-        images = self.tmdb.TV(sid).images(params=params)
+        try:
+            images = self.tmdb.TV(tmdb_id).images(params=params)
+        except RequestException as error:
+            raise IndexerUnavailable('Error trying to get images. Cause: {cause}'.format(cause=error))
+
         bid = images['id']
         for image_type, images in viewitems({'poster': images['posters'], 'fanart': images['backdrops']}):
             try:
@@ -360,20 +377,20 @@ def _parse_images(self, sid):
                             _images[image_type][resolution][bid]['rating'] = 0
 
             except Exception as error:
-                log.warning('Could not parse Poster for show id: {0}, with exception: {1!r}', sid, error)
+                log.warning('Could not parse Poster for show id: {0}, with exception: {1!r}', tmdb_id, error)
                 return False
 
-        season_images = self._parse_season_images(sid)
+        season_images = self._parse_season_images(tmdb_id)
         if season_images:
             _images.update(season_images)
 
-        self._save_images(sid, _images)
-        self._set_show_data(sid, '_banners', _images)
+        self._save_images(tmdb_id, _images)
+        self._set_show_data(tmdb_id, '_banners', _images)
 
-    def _parse_season_images(self, sid):
+    def _parse_season_images(self, tmdb_id):
         """Get all season posters for a TMDB show."""
         # Let's fget the different type of images available for this series
-        season_posters = getattr(self[sid], 'seasons', None)
+        season_posters = getattr(self[tmdb_id], 'seasons', None)
         if not season_posters:
             return
 
@@ -390,12 +407,15 @@ def _parse_season_images(self, sid):
 
         return _images
 
-    def _parse_actors(self, sid):
+    def _parse_actors(self, tmdb_id):
         """Parse actors XML."""
-        log.debug('Getting actors for {0}', sid)
+        log.debug('Getting actors for {0}', tmdb_id)
 
         # TMDB also support passing language here as a param.
-        credits = self.tmdb.TV(sid).credits(language=self.config['language'])  # pylint: disable=W0622
+        try:
+            credits = self.tmdb.TV(tmdb_id).credits(language=self.config['language'])  # pylint: disable=W0622
+        except RequestException as error:
+            raise IndexerException('Could not get actors. Cause: {cause}'.format(cause=error))
 
         if not credits or not credits.get('cast'):
             log.debug('Actors result returned zero')
@@ -413,9 +433,9 @@ def _parse_actors(self, sid):
             new_actor['role'] = cur_actor['character']
             new_actor['sortorder'] = cur_actor['order']
             cur_actors.append(new_actor)
-        self._set_show_data(sid, '_actors', cur_actors)
+        self._set_show_data(tmdb_id, '_actors', cur_actors)
 
-    def _get_show_data(self, sid, language='en'):
+    def _get_show_data(self, tmdb_id, language='en'):
         """Take a series ID, gets the epInfo URL and parses the TMDB json response.
 
         into the shows dict in layout:
@@ -432,22 +452,27 @@ def _get_show_data(self, sid, language='en'):
             get_show_in_language = self.config['language']
 
         # Parse show information
-        log.debug('Getting all series data for {0}', sid)
+        log.debug('Getting all series data for {0}', tmdb_id)
 
         # Parse show information
-        series_info = self._get_show_by_id(sid, request_language=get_show_in_language)
+        series_info = self._get_show_by_id(tmdb_id, request_language=get_show_in_language)
 
         if not series_info:
             log.debug('Series result returned zero')
             raise IndexerError('Series result returned zero')
 
         # Get MPAA rating if available
-        content_ratings = self.tmdb.TV(sid).content_ratings()
-        if content_ratings and content_ratings.get('results'):
-            mpaa_rating = next((r['rating'] for r in content_ratings['results']
-                                if r['iso_3166_1'].upper() == 'US'), None)
-            if mpaa_rating:
-                self._set_show_data(sid, 'contentrating', mpaa_rating)
+        try:
+            content_ratings = self.tmdb.TV(tmdb_id).content_ratings()
+            if content_ratings and content_ratings.get('results'):
+                mpaa_rating = next((r['rating'] for r in content_ratings['results']
+                                    if r['iso_3166_1'].upper() == 'US'), None)
+                if mpaa_rating:
+                    self._set_show_data(tmdb_id, 'contentrating', mpaa_rating)
+        except RequestException as error:
+            raise IndexerException('Could not get series data for series {series}. Cause: {cause}'.format(
+                series=tmdb_id, cause=error
+            ))
 
         # get series data / add the base_url to the image urls
         # Create a key/value dict, to map the image type to a default image width.
@@ -462,27 +487,33 @@ def _get_show_data(self, sid, language='en'):
                                                              image_size=image_width[k],
                                                              file_path=v)
 
-            self._set_show_data(sid, k, v)
+            self._set_show_data(tmdb_id, k, v)
 
         # Get external ids.
         # As the external id's are not part of the shows default response, we need to make an additional call for it.
-        self._set_show_data(sid, 'externals', self.tmdb.TV(sid).external_ids())
+        try:
+            external_ids = self.tmdb.TV(tmdb_id).external_ids()
+            self._set_show_data(tmdb_id, 'externals', external_ids)
+        except RequestException as error:
+            raise IndexerException("Could not get external id's for series {series} Cause: {cause}".format(
+                series=tmdb_id, cause=error
+            ))
 
         # get episode data
         if self.config['episodes_enabled']:
-            self._get_episodes(sid, specials=False, aired_season=None)
+            self._get_episodes(tmdb_id, specials=False, aired_season=None)
 
         # Parse banners
         if self.config['banners_enabled']:
-            self._parse_images(sid)
+            self._parse_images(tmdb_id)
 
         # Parse actors
         if self.config['actors_enabled']:
-            self._parse_actors(sid)
+            self._parse_actors(tmdb_id)
 
         return True
 
-    def _get_series_season_updates(self, sid, start_date=None, end_date=None):
+    def _get_series_season_updates(self, tmdb_id, start_date=None, end_date=None):
         """
         Retrieve all updates (show,season,episode) from TMDB.
 
@@ -491,15 +522,21 @@ def _get_series_season_updates(self, sid, start_date=None, end_date=None):
         results = []
         page = 1
         total_pages = 1
-        while page <= total_pages:
-            # Requesting for the changes on a specific showid, will result in json with changes per season.
-            updates = self.tmdb.TV(sid).changes(start_date=start_date, end_date=end_date)
-            if updates and updates.get('changes'):
-                for items in [update['items'] for update in updates['changes'] if update['key'] == 'season']:
-                    for season in items:
-                        results += [season['value']['season_number']]
-                total_pages = updates.get('total_pages', 0)
-            page += 1
+
+        try:
+            while page <= total_pages:
+                # Requesting for the changes on a specific showid, will result in json with changes per season.
+                updates = self.tmdb.TV(tmdb_id).changes(start_date=start_date, end_date=end_date)
+                if updates and updates.get('changes'):
+                    for items in [update['items'] for update in updates['changes'] if update['key'] == 'season']:
+                        for season in items:
+                            results += [season['value']['season_number']]
+                    total_pages = updates.get('total_pages', 0)
+                page += 1
+        except RequestException as error:
+            raise IndexerException('Could not get latest series season updates for series {series}. Cause: {cause}'.format(
+                series=tmdb_id, cause=error
+            ))
 
         return set(results)
 
@@ -508,13 +545,19 @@ def _get_all_updates(self, start_date=None, end_date=None):
         results = []
         page = 1
         total_pages = 1
-        while page <= total_pages:
-            updates = self.tmdb.Changes().tv(start_date=start_date, end_date=end_date, page=page)
-            if not updates or not updates.get('results'):
-                break
-            results += [_.get('id') for _ in updates.get('results')]
-            total_pages = updates.get('total_pages')
-            page += 1
+
+        try:
+            while page <= total_pages:
+                updates = self.tmdb.Changes().tv(start_date=start_date, end_date=end_date, page=page)
+                if not updates or not updates.get('results'):
+                    break
+                results += [_.get('id') for _ in updates.get('results')]
+                total_pages = updates.get('total_pages')
+                page += 1
+        except RequestException as error:
+            raise IndexerException('Could not get latest updates. Cause: {cause}'.format(
+                cause=error
+            ))
 
         return set(results)
 
@@ -585,17 +628,20 @@ def get_id_by_external(self, **kwargs):
         :param imdb_id: An imdb id (inc. tt).
         :returns: A dict with externals, including the tvmaze id.
         """
-        wanted_externals = ['tvdb_id', 'imdb_id', 'tvrage_id']
-        for external_id in wanted_externals:
-            if kwargs.get(external_id):
-                result = self.tmdb.Find(kwargs.get(external_id)).info(**{'external_source': external_id})
-                if result.get('tv_results') and result['tv_results'][0]:
-                    # Get the external id's for the passed shows id.
-                    externals = self.tmdb.TV(result['tv_results'][0]['id']).external_ids()
-                    externals = {tmdb_external_id: external_value
-                                 for tmdb_external_id, external_value
-                                 in viewitems(externals)
-                                 if external_value and tmdb_external_id in wanted_externals}
-                    externals['tmdb_id'] = result['tv_results'][0]['id']
-                    return externals
-        return {}
+        try:
+            wanted_externals = ['tvdb_id', 'imdb_id', 'tvrage_id']
+            for external_id in wanted_externals:
+                if kwargs.get(external_id):
+                    result = self.tmdb.Find(kwargs.get(external_id)).info(**{'external_source': external_id})
+                    if result.get('tv_results') and result['tv_results'][0]:
+                        # Get the external id's for the passed shows id.
+                        externals = self.tmdb.TV(result['tv_results'][0]['id']).external_ids()
+                        externals = {tmdb_external_id: external_value
+                                     for tmdb_external_id, external_value
+                                     in viewitems(externals)
+                                     if external_value and tmdb_external_id in wanted_externals}
+                        externals['tmdb_id'] = result['tv_results'][0]['id']
+                        return externals
+            return {}
+        except RequestException as error:
+            raise IndexerException("Could not get external id's. Cause: {cause}".format(cause=error))


Test patch: