Files
script.service.ultimate/lib/streaming_providers/base/vod.py
T
2026-10-03 14:35:57 +02:00

110 lines
3.3 KiB
Python

# streaming_providers/base/vod.py
"""
Shared VOD return type.
Prior to this module, providers returned VOD category children in three
different shapes (dict-with-entries, bare list, ...). This module defines
one canonical shape.
Pagination rule: `next_cursor is None` is the authoritative end-of-list
signal (exposed as `has_more`). `total` may be missing; do not use it to
decide whether to keep paging.
Truthiness: bool(page) is driven by __len__, so an empty page is falsy and
a page with entries is truthy -- matching list semantics. An empty page
with a next_cursor is falsy; check `page.has_more` when the caller means
"are there more pages?".
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Iterator, List, Optional, Union
from .models.vod import VodCategory, VodItem
VodEntry = Union[VodCategory, VodItem]
@dataclass
class VodPage:
"""
One page of VOD results.
Attributes:
entries: Mixed list of VodCategory and VodItem.
next_cursor: Opaque continuation token; None = no next page.
total: Optional total count; None = unknown.
Truthiness follows list semantics via __len__: an empty page is falsy.
For pagination, use `has_more` -- NOT bool(page) -- because a page can
legitimately have zero entries and a non-None cursor.
"""
entries: List[VodEntry] = field(default_factory=list)
next_cursor: Optional[str] = None
total: Optional[int] = None
@property
def has_more(self) -> bool:
"""True when the provider indicated a next page exists.
This is the correct pagination check. `bool(page)` answers
"are there entries to display?" -- a different question.
"""
return self.next_cursor is not None
def __len__(self) -> int:
# Drives both len(page) and bool(page) via Python's default
# truthiness rule for objects with __len__. Matches list semantics.
return len(self.entries)
def __iter__(self) -> Iterator[VodEntry]:
return iter(self.entries)
def to_dict(self) -> dict:
return {
"entries": [
e.to_dict() if hasattr(e, "to_dict") else e
for e in self.entries
],
"next_cursor": self.next_cursor,
"total": self.total,
}
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def empty_page() -> VodPage:
"""Return a fresh empty VodPage with no pagination."""
return VodPage()
def normalize_vod_result(result) -> VodPage:
"""
Coerce a legacy return value into a VodPage.
Migration bridge. Once every provider returns VodPage directly, this
becomes redundant.
"""
if isinstance(result, VodPage):
return result
if result is None:
return VodPage()
if isinstance(result, dict):
return VodPage(
entries=result.get("entries") or [],
next_cursor=result.get("next_cursor"),
total=result.get("total"),
)
if isinstance(result, list):
return VodPage(entries=result)
import logging
logging.getLogger(__name__).warning(
f"normalize_vod_result: unexpected type {type(result).__name__}; "
f"returning empty page"
)
return VodPage()