Skip to content

TMDB indexer raises opaque AttributeError ('NoneType' has no attribute 'raise_for_status') instead of the real network error #12253

Description

@DavidHunterJS

Describe the bug
TMDB indexer calls (medusa/indexers/tmdb/api.py) can fail with a confusing AttributeError: 'NoneType' object has no attribute 'raise_for_status' instead of a proper, informative network error. Root cause is a type-contract mismatch between two of Medusa's own layers:

  1. Tmdb.__init__ injects Medusa's own session into the vendored third-party tmdbsimple library: self.tmdb.REQUESTS_SESSION = self.config['session'] — and that session (IndexerSession, from medusa/session/core.py) subclasses MedusaSafeSession.
  2. MedusaSafeSession.request() is explicitly designed to swallow exceptions and return None on failure (see the bare except Exception branch at the bottom of MedusaSafeSession.request() — logs at INFO ("Unknown exception...") with the real traceback only at DEBUG, then return None).
  3. tmdbsimple's base.py::_request() (ext/tmdbsimple/base.py line 105) has no such contract in mind — it assumes the session's .request() always either returns a real requests.Response or raises, so it unconditionally calls response.raise_for_status(). When MedusaSafeSession hands back None, this blows up as AttributeError: 'NoneType' object has no attribute 'raise_for_status'.

Net effect: whatever the actual underlying network problem was (timeout, connection reset, DNS blip — anything landing in that generic except Exception branch) is completely hidden. The log shows a Python AttributeError deep inside a third-party dependency instead of the real cause, which is only visible if debug logging happens to be enabled. Every call site in tmdb/api.py that goes through _GET/_request (e.g. _get_all_updates, used by get_last_updated_series, and the external-ID lookup in medusa/helpers/externals.py) is exposed to this, though in the cases I hit it was caught by a surrounding try/except a level up and converted into a merely-unhelpful IndexerException rather than crashing the app.

To Reproduce
Hard to force on demand (depends on hitting a transient network hiccup at the exact moment MedusaSafeSession.request() catches something), but the mechanism is fully reproducible by inspection:

  1. Tmdb.__init__ sets tmdbsimple.REQUESTS_SESSION to a MedusaSafeSession-derived IndexerSession.
  2. Any transient error that lands in MedusaSafeSession.request()'s final except Exception branch returns None instead of raising.
  3. tmdbsimple.base.TMDB._request() calls response.raise_for_status() unconditionally → AttributeError.

In practice, I saw this hit real, live traffic: on 2026-07-30 at 04:45 UTC, the daily ShowUpdater run's TMDB "get last updated series" call failed this way for every TMDB-indexed show in my library (6/6), each logging the same opaque AttributeError traceback instead of whatever the real network issue was.

Expected behavior
MedusaSafeSession's "return None on failure" contract should not be handed to third-party libraries (like vendored tmdbsimple) that don't expect it — either use a session type for the TMDB indexer that raises normally (matching what tmdbsimple assumes), or have tmdb/api.py check for a None/falsy response before letting tmdbsimple touch it, or patch the vendored tmdbsimple/base.py::_request() to guard if response is None: raise ... with a clear message. Any of these would replace the current opaque AttributeError with the real, actionable error.

Suggested fix
Simplest targeted fix: in medusa/indexers/tmdb/api.py::Tmdb.__init__, don't hand tmdbsimple a MedusaSafeSession-derived session — either construct a plain requests.Session/MedusaSession (the non-"safe" base class, which doesn't swallow exceptions) for this purpose, or wrap the assignment so a None return is converted back into a raised RequestException before tmdbsimple sees it.

Medusa (please complete the following information):

  • OS: Debian GNU/Linux 12 (bookworm)
  • Branch: master
  • Commit: 2bc1eb2
  • Python version: 3.11.2
  • Database version: 44.19

Debug logs (at least 50 lines):

Details
2026-07-30 04:45:21 WARNING SHOWUPDATER :: Problem running show_updater, Indexer TMDB seems to be having issues while trying to get updates for show The Westies. Cause: IndexerException("Could not get latest updates. Cause: 'NoneType' object has no attribute 'raise_for_status'")
Traceback (most recent call last):
  File "medusa/indexers/tmdb/api.py", line 594, in _get_all_updates
    updates = self.tmdb.Changes().tv(start_date=start_date, end_date=end_date, page=page)
  File "ext/tmdbsimple/changes.py", line 72, in tv
    response = self._GET(path, kwargs)
  File "ext/tmdbsimple/base.py", line 110, in _GET
    return self._request('GET', path, params=params)
  File "ext/tmdbsimple/base.py", line 105, in _request
    response.raise_for_status()
AttributeError: 'NoneType' object has no attribute 'raise_for_status'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
  File "medusa/schedulers/show_updater.py", line 83, in run
    indexer_updated_shows[show.indexer] = indexer_api.get_last_updated_series(
  File "medusa/indexers/tmdb/api.py", line 626, in get_last_updated_series
    total_updates += self._get_all_updates(search_from.strftime('%Y-%m-%d'), search_until.strftime('%Y-%m-%d'))
  File "medusa/indexers/tmdb/api.py", line 601, in _get_all_updates
    raise IndexerException('Could not get latest updates. Cause: {cause}'.format(
medusa.indexers.exceptions.IndexerException: Could not get latest updates. Cause: 'NoneType' object has no attribute 'raise_for_status'

Same shape repeated for The Paper, The Boys, IT: Welcome to Derry, High Potential, and Landman — every TMDB-indexed show in the library that run, all within the same minute.

Additional context
Found on the same instance/session as #12249, #12250, and #12252. The retry-storm behavior in show_updater.py (one failure causes every subsequent show for that indexer to re-attempt and re-fail the identical call) is already covered by #12252 and applies equally to TVDB and TMDB since it's the same code path — this issue is specifically about the opaque AttributeError masking the real cause, which is a separate defect in the TMDB indexer wrapper itself.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions