Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 33 additions & 7 deletions compuglobal/aio.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,22 @@ async def get_screencap(
caption = await self.client.handle_request(request)
return Screencap.model_validate(caption)

async def search(self, search_text: str) -> list[FrameResult]:
async def search(
self,
search_text: str,
season_minimum: int | None = None,
season_maximum: int | None = None,
) -> list[FrameResult]:
"""Perform a search of the given search text and returns a list of all the Frames.

Parameters
----------
search_text : str
The search text to query
season_minimum: int | None, optional
The minimum season allowed in the search results
season_maximum: int | None, optional
The maximum season allowed in the search results

Returns
-------
Expand All @@ -110,35 +119,50 @@ async def search(self, search_text: str) -> list[FrameResult]:
Raises an error if no search results are found

"""
params = {"q": search_text}
optional_params = {"smin": season_minimum, "smax": season_maximum}
query = {"q": search_text}
query |= {k: v for k, v in optional_params.items() if v is not None}

request = self.discovery.SEARCH.build_request(self.client.base_url, query=params)
request = self.discovery.SEARCH.build_request(self.client.base_url, query=query)
search_results = await self.client.handle_request(request)

if len(search_results) > 0:
return [FrameResult.model_validate(result) for result in search_results]

raise NoSearchResultsFoundError

async def search_for_screencap(self, search_text: str) -> Screencap:
async def search_for_screencap(
self,
search_text: str,
season_minimum: int | None = None,
season_maximum: int | None = None,
) -> Screencap:
"""Perform a search of the given search text and returns the top result.

Parameters
----------
search_text : str
The search text to query
season_minimum: int | None, optional
The minimum season allowed in the search
season_maximum: int | None, optional
The maximum season allowed in the search

Returns
-------
Screencap
The screencap of the top search result

"""
search_results = await self.search(search_text)
search_results = await self.search(search_text, season_minimum=season_minimum, season_maximum=season_maximum)
result = search_results[0]
return await self.get_screencap(result.key, result.timestamp)

async def get_random_screencap(self) -> Screencap:
async def get_random_screencap(
self,
season_minimum: int | None = None,
season_maximum: int | None = None,
) -> Screencap:
"""Get a random TV Show screencap.

Returns
Expand All @@ -147,7 +171,9 @@ async def get_random_screencap(self) -> Screencap:
A random screencap object.

"""
request = self.discovery.RANDOM.build_request(self.client.base_url)
optional_params = {"smin": season_minimum, "smax": season_maximum}
query = {k: v for k, v in optional_params.items() if v is not None}
request = self.discovery.RANDOM.build_request(self.client.base_url, query=query)
random = await self.client.handle_request(request)
return Screencap.model_validate(random)

Expand Down
6 changes: 4 additions & 2 deletions compuglobal/api/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DiscoveryAPI:
CAPTION = Endpoint(
path="/api/caption",
method=RequestMethod.GET,
query_params=frozenset({"e", "t", "nearby"}),
required_query_params=frozenset({"e", "t", "nearby"}),
)

DISCOVER = Endpoint(
Expand All @@ -37,6 +37,7 @@ class DiscoveryAPI:
RANDOM = Endpoint(
path="/api/random",
method=RequestMethod.GET,
optional_query_params=frozenset({"smin", "smax"}),
)

NAVIGATOR = Endpoint(
Expand All @@ -47,7 +48,8 @@ class DiscoveryAPI:
SEARCH = Endpoint(
path="/api/search",
method=RequestMethod.GET,
query_params=frozenset({"q"}),
required_query_params=frozenset({"q"}),
optional_query_params=frozenset({"smin", "smax"}),
)

FRAMES = Endpoint(
Expand Down
15 changes: 9 additions & 6 deletions compuglobal/api/endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,22 @@ class Endpoint:
Attributes
----------
path: str
The url path
The url path containing any path parameter names
method: RequestMethod, optional
The HTTP RequestMethod for the request (GET/POST/PUT)
query_params: dict[str, Any] | None, optional
The query parameters to use in the request
required_query_params: dict[str, Any] | None, optional
The required query parameters to use in the request
optional_query_params: dict[str, Any] | None, optional
Optional query params that can be used in the request
body_model: type[BaseModel], optional
The pydantic model for the json body

"""

path: str
method: RequestMethod = RequestMethod.GET
query_params: frozenset[str] = frozenset()
required_query_params: frozenset[str] = frozenset()
optional_query_params: frozenset[str] = frozenset()
body_model: type[BaseModel] | None = None

def build_url(
Expand Down Expand Up @@ -178,8 +181,8 @@ def validate_query(self, query: dict[str, Any]) -> None:
Raises error if contains missing or unexpected params from the definition of the endpoint

"""
missing = set(self.query_params - query.keys())
unexpected = query.keys() - self.query_params
missing = set(self.required_query_params - query.keys())
unexpected = query.keys() - (self.required_query_params | self.optional_query_params)

if missing:
raise ValueError(
Expand Down
6 changes: 3 additions & 3 deletions compuglobal/api/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ class MediaAPI:
COMIC_PANEL = Endpoint(
path="/comic/img",
method=RequestMethod.GET,
query_params=frozenset({"b64"}),
required_query_params=frozenset({"b64"}),
)

COMIC_STRIP = Endpoint(
path="/comic/img",
method=RequestMethod.GET,
query_params=frozenset({"b64", "layout"}),
required_query_params=frozenset({"b64", "layout"}),
)

RENDER_GIF = Endpoint(
Expand All @@ -56,7 +56,7 @@ class MediaAPI:
DETECT_LOOP = Endpoint(
path="/api/detect-loop",
method=RequestMethod.GET,
query_params=frozenset(
required_query_params=frozenset(
{"episode", "start", "end"},
),
)
2 changes: 1 addition & 1 deletion compuglobal/api/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ class MetadataAPI:
TRANSCRIPT = Endpoint(
path="/api/transcript",
method=RequestMethod.GET,
query_params=frozenset({"e", "t"}),
required_query_params=frozenset({"e", "t"}),
)
22 changes: 16 additions & 6 deletions tests/api/test_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def test_discovery_caption_expected_path() -> None:


def test_discovery_caption_expected_params() -> None:
params = DiscoveryAPI.CAPTION.query_params
params = DiscoveryAPI.CAPTION.required_query_params
assert params == snapshot(frozenset({"e", "nearby", "t"}))


Expand All @@ -48,7 +48,7 @@ def test_discovery_discover_expected_path() -> None:


def test_discovery_discover_expected_params() -> None:
params = DiscoveryAPI.DISCOVER.query_params
params = DiscoveryAPI.DISCOVER.required_query_params
assert params == frozenset()


Expand All @@ -58,17 +58,22 @@ def test_discovery_random_expected_path() -> None:


def test_discovery_random_expected_params() -> None:
params = DiscoveryAPI.RANDOM.query_params
params = DiscoveryAPI.RANDOM.required_query_params
assert params == frozenset()


def test_discovery_random_optional_params() -> None:
params = DiscoveryAPI.RANDOM.optional_query_params
assert params == snapshot(frozenset({"smax", "smin"}))


def test_discovery_navigator_expected_path() -> None:
path = DiscoveryAPI.NAVIGATOR.path
assert path == "/api/navigator"


def test_discovery_navigator_expected_params() -> None:
params = DiscoveryAPI.NAVIGATOR.query_params
params = DiscoveryAPI.NAVIGATOR.required_query_params
assert params == frozenset()


Expand All @@ -78,16 +83,21 @@ def test_discovery_search_expected_path() -> None:


def test_discovery_search_expected_params() -> None:
params = DiscoveryAPI.SEARCH.query_params
params = DiscoveryAPI.SEARCH.required_query_params
assert params == snapshot(frozenset({"q"}))


def test_discovery_search_optional_params() -> None:
params = DiscoveryAPI.SEARCH.optional_query_params
assert params == snapshot(frozenset({"smax", "smin"}))


def test_discovery_frames_expected_path() -> None:
path = DiscoveryAPI.FRAMES.path
assert path.startswith("/api/frames")
assert path.endswith("/{key}/{timestamp}/{before}/{after}")


def test_discovery_frames_expected_params() -> None:
params = DiscoveryAPI.FRAMES.query_params
params = DiscoveryAPI.FRAMES.required_query_params
assert params == frozenset()
38 changes: 25 additions & 13 deletions tests/api/test_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def test_prepared_request_params_is_none() -> None:

def test_endpoint_defaults() -> None:
endpoint = Endpoint(path="/example")
expected = Endpoint(path="/example", method=RequestMethod.GET, query_params=frozenset(), body_model=None)
expected = Endpoint(path="/example", method=RequestMethod.GET, required_query_params=frozenset(), body_model=None)
assert endpoint == expected


Expand All @@ -59,16 +59,16 @@ class ExampleModel(BaseModel):
endpoint = Endpoint(
path="/example",
method=RequestMethod.POST,
query_params=frozenset({"foo"}),
required_query_params=frozenset({"foo"}),
body_model=ExampleModel,
)
assert endpoint.path == "/example"
assert endpoint.query_params == frozenset({"foo"})
assert endpoint.required_query_params == frozenset({"foo"})
assert endpoint.body_model == ExampleModel


def test_endpoint_build_url() -> None:
endpoint = Endpoint(path="/example", query_params=frozenset({"a"}))
endpoint = Endpoint(path="/example", required_query_params=frozenset({"a"}))
url = endpoint.build_url("https://example.com", query={"a": 1}, path_params={})
assert url == "https://example.com/example"

Expand All @@ -80,7 +80,7 @@ def test_endpoint_build_url_path_params() -> None:


def test_endpoint_build_url_missing_or_unexpected_params() -> None:
endpoint = Endpoint(path="/example", query_params=frozenset({"a"}))
endpoint = Endpoint(path="/example", required_query_params=frozenset({"a"}))
with pytest.raises(ValueError, match="Missing query params"):
endpoint.build_url("https://example.com", query={"b": 1}, path_params={})

Expand All @@ -98,19 +98,31 @@ def test_endpoint_build_url_missing_or_unexpected_path_params() -> None:


def test_endpoint_build_encoded_url() -> None:
endpoint = Endpoint(path="/path/{key}", query_params=frozenset({"q"}))
endpoint = Endpoint(path="/path/{key}", required_query_params=frozenset({"q"}))
url = endpoint.build_encoded_url("https://example.com", query={"q": 1}, path_params={"key": "search"})
assert url == "https://example.com/path/search?q=1"


def test_endpoint_build_encoded_url_exists() -> None:
endpoint = Endpoint(path="/path/", query_params=frozenset())
endpoint = Endpoint(path="/path/", required_query_params=frozenset())
url = endpoint.build_encoded_url("https://example.com", query={}, path_params={})
assert url is not None


def test_endpoint_build_encoded_url_with_unused_optional_params() -> None:
endpoint = Endpoint(path="/example", required_query_params=frozenset({"a"}), optional_query_params=frozenset({"b"}))
url = endpoint.build_encoded_url("https://example.com", query={"a": 1}, path_params={})
assert url == "https://example.com/example?a=1"


def test_endpoint_build_encoded_url_with_used_optional_params() -> None:
endpoint = Endpoint(path="/example", required_query_params=frozenset({"a"}), optional_query_params=frozenset({"b"}))
url = endpoint.build_encoded_url("https://example.com", query={"a": 1, "b": 2}, path_params={})
assert url == "https://example.com/example?a=1&b=2"


def test_endpoint_build_encoded_url_missing_or_unexpected_params() -> None:
endpoint = Endpoint(path="/example", query_params=frozenset({"a"}))
endpoint = Endpoint(path="/example", required_query_params=frozenset({"a"}))
with pytest.raises(ValueError, match="Missing query params"):
endpoint.build_encoded_url("https://example.com", query={}, path_params={})

Expand All @@ -135,20 +147,20 @@ def test_endpoint_build_request() -> None:


def test_endpoint_build_request_overrides() -> None:
endpoint = Endpoint(path="/path/{key}", query_params=frozenset({"q"}))
endpoint = Endpoint(path="/path/{key}", required_query_params=frozenset({"q"}))
request = endpoint.build_request("https://example.com", query={"q": 1}, path_params={"key": "search"})
expected = PreparedRequest(url="https://example.com/path/search", params={"q": 1}, body=None)
assert request == expected


def test_endpoint_build_request_missing_params() -> None:
endpoint = Endpoint(path="/example", query_params=frozenset({"q"}))
endpoint = Endpoint(path="/example", required_query_params=frozenset({"q"}))
with pytest.raises(ValueError, match="Missing query params"):
endpoint.build_request("https://example.com", query={}, path_params={})


def test_endpoint_build_request_unexpected_params() -> None:
endpoint = Endpoint(path="/example", query_params=frozenset({"q"}))
endpoint = Endpoint(path="/example", required_query_params=frozenset({"q"}))
with pytest.raises(ValueError, match="Unexpected query params"):
endpoint.build_request("https://example.com", query={"q": 1, "_unexpected_": True}, path_params={})

Expand All @@ -171,13 +183,13 @@ def test_endpoint_validate_query() -> None:


def test_endpoint_validate_query_missing_query_params() -> None:
endpoint = Endpoint(path="/example", query_params=frozenset({"a"}))
endpoint = Endpoint(path="/example", required_query_params=frozenset({"a"}))
with pytest.raises(ValueError, match="Missing query params"):
endpoint.validate_query(query={})


def test_endpoint_validate_query_unexpected_query_params() -> None:
endpoint = Endpoint(path="/example", query_params=frozenset({"a"}))
endpoint = Endpoint(path="/example", required_query_params=frozenset({"a"}))
with pytest.raises(ValueError, match="Unexpected query params"):
endpoint.validate_query(query={"a": 1, "b": 1})

Expand Down
Loading