refactor(tmdb): ACL pass — push VOs into DTOs, split search per media type

Anti-corruption boundary tightened on the TMDB adapter:

* TmdbMovieInfo / TmdbShowInfo now carry domain VOs (TmdbId, ImdbId,
  MovieTitle, ReleaseYear, ShowStatus) instead of raw scalars —
  validation happens at the boundary, not three layers later.
* ShowStatus enum added (domain/tv_shows/value_objects) with a
  from_tmdb() mapper that falls back to UNKNOWN + logs a warning on
  unrecognized values. TVShow.status is now ShowStatus, not str.
* MovieTitle cap raised from 100 to 150 chars.
* MediaResult / ExternalIds dropped. Replaced by per-media search
  DTOs: TmdbMovieSearchResult and TmdbShowSearchResult. Neither
  carries imdb_id — search no longer enriches with external_ids
  (callers needing imdb_id follow up with get_movie_info /
  get_tv_show_info on the chosen tmdb_id).
* TMDBClient: search_multi / search_media / _parse_result removed.
  search_movies (/search/movie) and search_shows (/search/tv) added,
  each parsing hits into VO-typed DTOs.
* SearchMovieUseCase returns a list of MovieHit (flattened to
  primitives for the agent). New symmetric SearchShowUseCase +
  ShowHit / SearchShowResponse DTOs.
* agent/tools/api.py: find_media_imdb_id → search_movies +
  search_shows wrappers.
* FileEntry moved from domain/shared/ports/filesystem_scanner.py to
  domain/shared/file_entry.py (it's a DTO, not a Protocol); size_kb
  (float) → size (int bytes). Scanner and SubtitleIdentifier
  updated.

Tests: 79/79 pass on tests/infrastructure/api/ +
tests/application/test_search_movie.py +
tests/application/test_search_show.py.
This commit is contained in:
2026-05-26 05:45:30 +02:00
parent cffafa2e60
commit c62ae81275
29 changed files with 735 additions and 490 deletions
+47 -57
View File
@@ -1,15 +1,16 @@
"""Tests for ``alfred.application.movies.search_movie.SearchMovieUseCase``.
The use case wraps ``TMDBClient.search_media`` and converts results / errors
into a ``SearchMovieResponse`` envelope (status="ok"|"error").
The use case wraps :meth:`TMDBClient.search_movies` and flattens each
hit's domain VOs into agent-friendly primitives wrapped in a
:class:`SearchMovieResponse` envelope (status="ok"|"error").
Coverage:
- ``TestSuccess`` — full MediaResult with imdb_id → ok+imdb_id; missing
imdb_id → ok+no_imdb_id; TV media_type preserved.
- ``TestErrorTranslation`` — ``TMDBNotFoundError`` → not_found,
``TMDBConfigurationError`` → configuration_error,
``TMDBAPIError`` → api_error, ``ValueError`` → validation_failed.
- ``TestSuccess`` — list of hits flattened, year present/absent,
empty list still ``status="ok"``.
- ``TestErrorTranslation`` — ``TMDBConfigurationError`` →
configuration_error, ``TMDBAPIError`` → api_error, ``ValueError``
→ validation_failed.
- ``TestPassThrough`` — query is forwarded to the client unchanged.
TMDBClient is fully mocked — no real HTTP.
@@ -22,11 +23,12 @@ from unittest.mock import MagicMock
import pytest
from alfred.application.movies.search_movie import SearchMovieUseCase
from alfred.infrastructure.api.tmdb.dto import MediaResult
from alfred.domain.movies.value_objects import MovieTitle, ReleaseYear
from alfred.domain.shared.value_objects import TmdbId
from alfred.infrastructure.api.tmdb.dto import TmdbMovieSearchResult
from alfred.infrastructure.api.tmdb.exceptions import (
TMDBAPIError,
TMDBConfigurationError,
TMDBNotFoundError,
)
@@ -40,19 +42,14 @@ def use_case(client):
return SearchMovieUseCase(client)
def _result(**kw) -> MediaResult:
def _hit(**kw) -> TmdbMovieSearchResult:
defaults = dict(
tmdb_id=1,
title="Inception",
media_type="movie",
imdb_id="tt1375666",
overview="o",
release_date="2010-07-15",
poster_path="/x.jpg",
vote_average=8.4,
tmdb_id=TmdbId(27205),
title=MovieTitle("Inception"),
release_year=ReleaseYear(2010),
)
defaults.update(kw)
return MediaResult(**defaults)
return TmdbMovieSearchResult(**defaults)
# --------------------------------------------------------------------------- #
@@ -61,36 +58,36 @@ def _result(**kw) -> MediaResult:
class TestSuccess:
def test_full_result_returns_ok_with_imdb_id(self, client, use_case):
client.search_media.return_value = _result()
def test_single_hit_is_flattened(self, client, use_case):
client.search_movies.return_value = [_hit()]
r = use_case.execute("Inception")
assert r.status == "ok"
assert r.imdb_id == "tt1375666"
assert r.title == "Inception"
assert r.media_type == "movie"
assert r.tmdb_id == 1
assert r.vote_average == 8.4
assert len(r.hits) == 1
h = r.hits[0]
assert h.tmdb_id == 27205
assert h.title == "Inception"
assert h.release_year == 2010
def test_multiple_hits_preserve_order(self, client, use_case):
client.search_movies.return_value = [
_hit(),
_hit(tmdb_id=TmdbId(42), title=MovieTitle("Inception 2")),
]
r = use_case.execute("Inception")
assert [h.tmdb_id for h in r.hits] == [27205, 42]
def test_hit_without_release_year(self, client, use_case):
client.search_movies.return_value = [_hit(release_year=None)]
r = use_case.execute("Inception")
assert r.hits[0].release_year is None
def test_empty_results_returns_ok_with_no_hits(self, client, use_case):
client.search_movies.return_value = []
r = use_case.execute("nothing")
assert r.status == "ok"
assert r.hits == []
assert r.error is None
def test_tv_result(self, client, use_case):
client.search_media.return_value = _result(
media_type="tv", title="Breaking Bad", imdb_id="tt0903747"
)
r = use_case.execute("Breaking Bad")
assert r.status == "ok"
assert r.media_type == "tv"
assert r.imdb_id == "tt0903747"
def test_missing_imdb_id_returns_ok_with_no_imdb_id_error(self, client, use_case):
client.search_media.return_value = _result(imdb_id=None)
r = use_case.execute("Inception")
assert r.status == "ok"
assert r.error == "no_imdb_id"
assert r.message is not None
assert "Inception" in r.message
assert r.imdb_id is None
assert r.title == "Inception"
# --------------------------------------------------------------------------- #
# Error translation #
@@ -98,28 +95,21 @@ class TestSuccess:
class TestErrorTranslation:
def test_not_found(self, client, use_case):
client.search_media.side_effect = TMDBNotFoundError("no match")
r = use_case.execute("ghost")
assert r.status == "error"
assert r.error == "not_found"
assert "no match" in r.message
def test_configuration_error(self, client, use_case):
client.search_media.side_effect = TMDBConfigurationError("missing key")
client.search_movies.side_effect = TMDBConfigurationError("missing key")
r = use_case.execute("x")
assert r.status == "error"
assert r.error == "configuration_error"
def test_api_error(self, client, use_case):
client.search_media.side_effect = TMDBAPIError("500 oops")
client.search_movies.side_effect = TMDBAPIError("500 oops")
r = use_case.execute("x")
assert r.status == "error"
assert r.error == "api_error"
assert "500" in r.message
def test_validation_error(self, client, use_case):
client.search_media.side_effect = ValueError("query too long")
client.search_movies.side_effect = ValueError("query too long")
r = use_case.execute("x")
assert r.status == "error"
assert r.error == "validation_failed"
@@ -133,6 +123,6 @@ class TestErrorTranslation:
class TestPassThrough:
def test_query_forwarded_verbatim(self, client, use_case):
client.search_media.return_value = _result()
client.search_movies.return_value = []
use_case.execute("Inception")
client.search_media.assert_called_once_with("Inception")
client.search_movies.assert_called_once_with("Inception")
+101
View File
@@ -0,0 +1,101 @@
"""Tests for ``alfred.application.tv_shows.search_show.SearchShowUseCase``.
Symmetric to ``test_search_movie.py``. The use case wraps
:meth:`TMDBClient.search_shows` and flattens each hit into a
:class:`ShowHit` wrapped in a :class:`SearchShowResponse` envelope.
"""
from __future__ import annotations
from unittest.mock import MagicMock
import pytest
from alfred.application.tv_shows.search_show import SearchShowUseCase
from alfred.domain.shared.value_objects import TmdbId
from alfred.infrastructure.api.tmdb.dto import TmdbShowSearchResult
from alfred.infrastructure.api.tmdb.exceptions import (
TMDBAPIError,
TMDBConfigurationError,
)
@pytest.fixture
def client():
return MagicMock()
@pytest.fixture
def use_case(client):
return SearchShowUseCase(client)
def _hit(**kw) -> TmdbShowSearchResult:
defaults = dict(
tmdb_id=TmdbId(84958),
name="Foundation",
first_air_year=2021,
)
defaults.update(kw)
return TmdbShowSearchResult(**defaults)
class TestSuccess:
def test_single_hit_is_flattened(self, client, use_case):
client.search_shows.return_value = [_hit()]
r = use_case.execute("Foundation")
assert r.status == "ok"
assert len(r.hits) == 1
h = r.hits[0]
assert h.tmdb_id == 84958
assert h.name == "Foundation"
assert h.first_air_year == 2021
def test_multiple_hits_preserve_order(self, client, use_case):
client.search_shows.return_value = [
_hit(),
_hit(tmdb_id=TmdbId(42), name="Fallout"),
]
r = use_case.execute("Foundation")
assert [h.tmdb_id for h in r.hits] == [84958, 42]
def test_hit_without_first_air_year(self, client, use_case):
client.search_shows.return_value = [_hit(first_air_year=None)]
r = use_case.execute("Foundation")
assert r.hits[0].first_air_year is None
def test_empty_results_returns_ok_with_no_hits(self, client, use_case):
client.search_shows.return_value = []
r = use_case.execute("nothing")
assert r.status == "ok"
assert r.hits == []
assert r.error is None
class TestErrorTranslation:
def test_configuration_error(self, client, use_case):
client.search_shows.side_effect = TMDBConfigurationError("missing key")
r = use_case.execute("x")
assert r.status == "error"
assert r.error == "configuration_error"
def test_api_error(self, client, use_case):
client.search_shows.side_effect = TMDBAPIError("500 oops")
r = use_case.execute("x")
assert r.status == "error"
assert r.error == "api_error"
assert "500" in r.message
def test_validation_error(self, client, use_case):
client.search_shows.side_effect = ValueError("query too long")
r = use_case.execute("x")
assert r.status == "error"
assert r.error == "validation_failed"
assert "too long" in r.message
class TestPassThrough:
def test_query_forwarded_verbatim(self, client, use_case):
client.search_shows.return_value = []
use_case.execute("Foundation")
client.search_shows.assert_called_once_with("Foundation")
+2 -2
View File
@@ -22,7 +22,7 @@ from unittest.mock import patch
import pytest
from alfred.domain.shared.ports import FileEntry
from alfred.domain.shared import FileEntry
from alfred.domain.subtitles.entities import SubtitleScanResult
from alfred.domain.subtitles.services.identifier import (
SubtitleIdentifier,
@@ -48,7 +48,7 @@ def _file_entry(path) -> FileEntry:
path=path,
is_file=path.is_file(),
is_dir=path.is_dir(),
size_kb=(path.stat().st_size / 1024) if path.is_file() else None,
size=path.stat().st_size if path.is_file() else None,
)
+103 -118
View File
@@ -6,13 +6,12 @@ Exercises the public surface without any real HTTP traffic:
enforcement of the ``api_key``/``base_url`` invariants.
- ``TestMakeRequest`` — error translation for timeouts, HTTP 401/404/5xx,
and generic ``RequestException``.
- ``TestSearchMulti`` — query validation, success path, empty-results →
``TMDBNotFoundError``.
- ``TestSearchMovies`` / ``TestSearchShows`` — query validation, success
path (VO-wrapped hits), empty results yield empty list (no exception).
- ``TestGetExternalIds`` — ``media_type`` whitelist enforcement.
- ``TestSearchMedia`` — happy path (movie/tv), media_type fallthrough to
the next result, structural-validation error, and the case where
external-ID resolution fails but the search still succeeds.
- ``TestDetailsEndpoints`` — ``get_movie_details`` / ``get_tv_details``.
- ``TestGetTvShowInfo`` / ``TestGetMovieInfo`` — composite info getters
aggregating details + external_ids into VO-typed DTOs.
- ``TestIsConfigured`` — reports ``True`` only when both api_key & url set.
All HTTP is mocked at ``alfred.infrastructure.api.tmdb.client.requests``.
@@ -25,8 +24,10 @@ from unittest.mock import MagicMock, patch
import pytest
from requests.exceptions import HTTPError, RequestException, Timeout
from alfred.domain.movies.value_objects import MovieTitle, ReleaseYear
from alfred.domain.shared.value_objects import ImdbId, TmdbId
from alfred.domain.tv_shows.value_objects import ShowStatus
from alfred.infrastructure.api.tmdb.client import TMDBClient
from alfred.infrastructure.api.tmdb.dto import MediaResult
from alfred.infrastructure.api.tmdb.exceptions import (
TMDBAPIError,
TMDBConfigurationError,
@@ -85,7 +86,6 @@ class TestInit:
TMDBClient(api_key="", config=cfg)
def test_missing_base_url_raises(self):
# Pass api_key but force empty base_url. Need a config with empty URL too.
from alfred.settings import Settings
cfg = Settings(tmdb_api_key="fake", tmdb_base_url="")
@@ -140,34 +140,109 @@ class TestMakeRequest:
# --------------------------------------------------------------------------- #
# search_multi #
# search_movies #
# --------------------------------------------------------------------------- #
class TestSearchMulti:
class TestSearchMovies:
@pytest.mark.parametrize("bad", ["", None, 123])
def test_invalid_query_raises_value_error(self, client, bad):
with pytest.raises(ValueError):
client.search_multi(bad)
client.search_movies(bad)
def test_query_too_long(self, client):
with pytest.raises(ValueError, match="too long"):
client.search_multi("a" * 501)
client.search_movies("a" * 501)
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_success(self, mock_get, client):
def test_success_wraps_vos(self, mock_get, client):
mock_get.return_value = _ok_response(
{"results": [{"id": 1, "media_type": "movie"}]}
{
"results": [
{
"id": 27205,
"title": "Inception",
"release_date": "2010-07-15",
}
]
}
)
results = client.search_multi("Inception")
results = client.search_movies("Inception")
assert len(results) == 1
assert results[0]["id"] == 1
hit = results[0]
assert hit.tmdb_id == TmdbId(27205)
assert hit.title == MovieTitle("Inception")
assert hit.release_year == ReleaseYear(2010)
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_empty_results_raise_not_found(self, mock_get, client):
def test_empty_results_returns_empty_list(self, mock_get, client):
mock_get.return_value = _ok_response({"results": []})
with pytest.raises(TMDBNotFoundError):
client.search_multi("nothing")
assert client.search_movies("nothing") == []
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_missing_release_date_yields_none_year(self, mock_get, client):
mock_get.return_value = _ok_response(
{"results": [{"id": 1, "title": "X"}]}
)
results = client.search_movies("X")
assert results[0].release_year is None
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_malformed_year_yields_none(self, mock_get, client):
mock_get.return_value = _ok_response(
{"results": [{"id": 1, "title": "X", "release_date": "soon"}]}
)
results = client.search_movies("X")
assert results[0].release_year is None
# --------------------------------------------------------------------------- #
# search_shows #
# --------------------------------------------------------------------------- #
class TestSearchShows:
@pytest.mark.parametrize("bad", ["", None, 123])
def test_invalid_query_raises_value_error(self, client, bad):
with pytest.raises(ValueError):
client.search_shows(bad)
def test_query_too_long(self, client):
with pytest.raises(ValueError, match="too long"):
client.search_shows("a" * 501)
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_success_wraps_vos(self, mock_get, client):
mock_get.return_value = _ok_response(
{
"results": [
{
"id": 84958,
"name": "Foundation",
"first_air_date": "2021-09-24",
}
]
}
)
results = client.search_shows("Foundation")
assert len(results) == 1
hit = results[0]
assert hit.tmdb_id == TmdbId(84958)
assert hit.name == "Foundation"
assert hit.first_air_year == 2021
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_empty_results_returns_empty_list(self, mock_get, client):
mock_get.return_value = _ok_response({"results": []})
assert client.search_shows("nothing") == []
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_missing_first_air_date_yields_none_year(self, mock_get, client):
mock_get.return_value = _ok_response(
{"results": [{"id": 1, "name": "X"}]}
)
results = client.search_shows("X")
assert results[0].first_air_year is None
# --------------------------------------------------------------------------- #
@@ -193,97 +268,6 @@ class TestGetExternalIds:
assert result["imdb_id"] == "tt0903747"
# --------------------------------------------------------------------------- #
# search_media (composite) #
# --------------------------------------------------------------------------- #
class TestSearchMedia:
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_happy_path_movie(self, mock_get, client):
# First call → /search/multi ; second → /movie/X/external_ids
mock_get.side_effect = [
_ok_response(
{
"results": [
{
"id": 27205,
"media_type": "movie",
"title": "Inception",
"overview": "...",
"release_date": "2010-07-15",
"poster_path": "/x.jpg",
"vote_average": 8.4,
}
]
}
),
_ok_response({"imdb_id": "tt1375666"}),
]
result = client.search_media("Inception")
assert isinstance(result, MediaResult)
assert result.title == "Inception"
assert result.imdb_id == "tt1375666"
assert result.media_type == "movie"
assert result.vote_average == 8.4
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_tv_uses_name_field(self, mock_get, client):
mock_get.side_effect = [
_ok_response(
{"results": [{"id": 1396, "media_type": "tv", "name": "Breaking Bad"}]}
),
_ok_response({"imdb_id": "tt0903747"}),
]
result = client.search_media("Breaking Bad")
assert result.title == "Breaking Bad"
assert result.media_type == "tv"
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_person_result_skipped_uses_next(self, mock_get, client):
# First result is a person → falls through to second result.
mock_get.side_effect = [
_ok_response(
{
"results": [
{"id": 1, "media_type": "person", "name": "X"},
{"id": 2, "media_type": "movie", "title": "Y"},
]
}
),
_ok_response({"imdb_id": "tt7654321"}),
]
result = client.search_media("Y")
assert result.title == "Y"
assert result.media_type == "movie"
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_only_person_result_raises_not_found(self, mock_get, client):
mock_get.return_value = _ok_response(
{"results": [{"id": 1, "media_type": "person", "name": "X"}]}
)
with pytest.raises(TMDBNotFoundError):
client.search_media("X")
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_malformed_top_result_raises(self, mock_get, client):
mock_get.return_value = _ok_response(
{"results": [{"title": "no id or media_type"}]}
)
with pytest.raises(TMDBAPIError, match="Invalid"):
client.search_media("X")
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_external_ids_failure_returns_result_without_imdb(self, mock_get, client):
# Second call (external IDs) fails — the search should still succeed.
mock_get.side_effect = [
_ok_response({"results": [{"id": 1, "media_type": "movie", "title": "X"}]}),
Timeout("slow"),
]
result = client.search_media("X")
assert result.imdb_id is None
# --------------------------------------------------------------------------- #
# Details endpoints #
# --------------------------------------------------------------------------- #
@@ -333,10 +317,10 @@ class TestGetTvShowInfo:
info = client.get_tv_show_info(84958)
assert info.tmdb_id == 84958
assert info.imdb_id == "tt0804484"
assert info.tmdb_id == TmdbId(84958)
assert info.imdb_id == ImdbId("tt0804484")
assert info.name == "Foundation"
assert info.status == "Returning Series"
assert info.status == ShowStatus.RETURNING_SERIES
assert len(info.seasons) == 2
assert info.seasons[0].number == 1
assert info.seasons[0].episode_count == 10
@@ -353,11 +337,12 @@ class TestGetTvShowInfo:
"seasons": [],
}
),
_ok_response({}), # external_ids without imdb_id
_ok_response({}),
]
info = client.get_tv_show_info(1)
assert info.imdb_id is None
assert info.seasons == ()
assert info.status == ShowStatus.ENDED
class TestGetMovieInfo:
@@ -378,10 +363,10 @@ class TestGetMovieInfo:
info = client.get_movie_info(27205)
assert info.tmdb_id == 27205
assert info.imdb_id == "tt1375666"
assert info.title == "Inception"
assert info.release_year == 2010
assert info.tmdb_id == TmdbId(27205)
assert info.imdb_id == ImdbId("tt1375666")
assert info.title == MovieTitle("Inception")
assert info.release_year == ReleaseYear(2010)
@patch("alfred.infrastructure.api.tmdb.client.requests.get")
def test_missing_imdb_id_becomes_none(self, mock_get, client):
@@ -397,7 +382,7 @@ class TestGetMovieInfo:
]
info = client.get_movie_info(1)
assert info.imdb_id is None
assert info.release_year == 2024
assert info.release_year == ReleaseYear(2024)
class TestIsConfigured:
+11 -8
View File
@@ -12,6 +12,9 @@ from datetime import date
import pytest
from alfred.domain.movies.value_objects import MovieTitle, ReleaseYear
from alfred.domain.shared.value_objects import ImdbId, TmdbId
from alfred.domain.tv_shows.value_objects import ShowStatus
from alfred.infrastructure.api.tmdb.dto import (
TmdbMovieInfo,
TmdbSeasonInfo,
@@ -42,10 +45,10 @@ class TestParseTvShowInfoHappyPath:
today=REF_DATE,
)
assert info == TmdbShowInfo(
tmdb_id=84958,
imdb_id="tt0804484",
tmdb_id=TmdbId(84958),
imdb_id=ImdbId("tt0804484"),
name="Foundation",
status="Returning Series",
status=ShowStatus.RETURNING_SERIES,
seasons=(),
)
@@ -192,10 +195,10 @@ class TestParseMovieInfoHappyPath:
{"imdb_id": "tt1375666"},
)
assert info == TmdbMovieInfo(
tmdb_id=27205,
imdb_id="tt1375666",
title="Inception",
release_year=2010,
tmdb_id=TmdbId(27205),
imdb_id=ImdbId("tt1375666"),
title=MovieTitle("Inception"),
release_year=ReleaseYear(2010),
)
def test_release_year_extracted_from_release_date(self):
@@ -203,7 +206,7 @@ class TestParseMovieInfoHappyPath:
_movie_details(release_date="1999-03-31"),
{},
)
assert info.release_year == 1999
assert info.release_year == ReleaseYear(1999)
class TestParseMovieInfoImdb:
@@ -24,7 +24,7 @@ from alfred.domain.releases.entities import (
from alfred.domain.releases.value_objects import EpisodeRange, ReleaseMode
from alfred.domain.shared.media import AudioTrack, SubtitleTrack
from alfred.domain.shared.value_objects import FilePath, ImdbId, TmdbId
from alfred.domain.tv_shows.value_objects import EpisodeNumber, SeasonNumber
from alfred.domain.tv_shows.value_objects import EpisodeNumber, SeasonNumber, ShowStatus
from alfred.infrastructure.api.tmdb.dto import TmdbSeasonInfo, TmdbShowInfo
@@ -164,10 +164,10 @@ def inception_release() -> MovieRelease:
def foundation_tmdb_info() -> TmdbShowInfo:
"""Foundation TMDB cache snapshot — 3 seasons, S03 not yet aired."""
return TmdbShowInfo(
tmdb_id=84958,
imdb_id="tt0804484",
tmdb_id=TmdbId(84958),
imdb_id=ImdbId("tt0804484"),
name="Foundation",
status="Returning Series",
status=ShowStatus.RETURNING_SERIES,
seasons=(
TmdbSeasonInfo(number=1, episode_count=10, aired=True),
TmdbSeasonInfo(number=2, episode_count=10, aired=True),