<div align="center">
<h1>🎬 tmdbfusion 🎬</h1>
<h3><i>The Blockbuster Python Wrapper for The Movie Database API</i></h3>
<p align="center">
  <b>🚧 NOW SHOWING IN A TERMINAL NEAR YOU 🚧</b>
  <br />
  <a href="#-box-office---installation">🎟️ Get Tickets</a> •
  <a href="#-showtimes---quick-start">🍿 Showtimes</a> •
  <a href="#-cast--crew---api-reference">🎬 Cast & Crew</a> •
  <a href="#-directors-cut---advanced-usage">📽️ Director's Cut</a>
</p>

</div>

---

# 📜 THE PLOT   Project Overview

### 🎞️ Logline

**`tmdbfusion`** isn't just another API wrapper options it's a **cinematic experience** for your codebase. Built for the modern Python auteur, it fuses high-performance async capabilities with strict type safety, delivering data faster than a summer blockbuster opening weekend.

### 🎭 The Premise

The Movie Database (TMDB) is the world's premier source for movie and TV metadata. But interacting with raw HTTP endpoints is like watching a movie without sound you get the picture, but you miss the *soul*.

**`tmdbfusion`** changes the script. It encapsulates the complexity of API pagination, rate limiting, and session management into a sleek, elegant interface. Whether you're building a simple CLI tool or a massive streaming platform, this library provides the **special effects** you need to dazzle your users.

### 🎬 Directed By Design

Every frame of this library was crafted with intent:

- **Dual Audio Tracks**: Full support for both **Synchronous** and **Asynchronous** (asyncio) runtimes.
- **High Frame Rate**: Powered by `httpx` and `msgspec` for lightning-fast serialization.
- **IMAX Certified**: Fully type-hinted data models (no more cryptic dictionaries).
- **Director's Control**: Precise handling of rate limits and retries.

> "If `requests` is the silent era, `tmdbfusion` is Dolby Atmos."   *A Developer, probably.*

---

# 🎟️ BOX OFFICE   Installation

### 📋 The Rider (Requirements)

Before you step onto the red carpet, ensure your trailer is equipped with the following:

| Component | Rating | Notes |
| :--- | :--- | :--- |
| **Python** | **3.13+** | Only the latest tech for this production. |
| **httpx** | **0.24+** | The engine behind the scenes. |
| **msgspec** | **0.18+** | For high-speed parsing stunts. |

### 🎫 Purchase Tickets (Install via pip)

Grab your front-row seat using the standard package manager.

```bash
# Standard Admission
pip install tmdbfusion

# VIP Access (Dev Dependencies)
pip install "tmdbfusion[dev]"
```

### 🔐 Security Clearance (API Key)

You cannot enter the theater without a ticket. Obtain your API Key from [TMDB Settings](https://www.themoviedb.org/settings/api).

**Method A: Environment Variable (Recommended)**
Export your key to keep it off-camera.

```bash
export TMDB_API_KEY="your_api_key_here"
```

**Method B: Direct Pass (Not Recommended for Production)**
Pass it during client initialization (see *Showtimes* below).

### 🏗️ Studio Setup (Development)

If you want to contribute to the sequel, set up the studio lot:

```bash
# Clone the reel
git clone https://github.com/xsyncio/tmdbfusion.git
cd tmdbfusion

# Install hatch (The Producer)
pip install hatch


# Run the test screening
hatch run test
```

### 🎫 BACKSTAGE PASS   Development Tools

The crew uses a specialized toolset to ensure the production quality remains high.

| Role | Tool | Command | Description |
| :--- | :--- | :--- | :--- |
| **Stunt Coordinator** | **Nox** | `nox` | Runs the full test suite across multiple python versions. |
| **Script Supervisor** | **Ruff** | `ruff check .` | Enforces `flake8`, `isort`, and other linting rules. |
| **Cinematographer** | **Ruff Format** | `ruff format .` | Auto-formats code to the project style. |
| **Safety Inspector** | **Mypy** | `mypy .` | Strict static type checking to prevent runtime crashes. |
| **Script Doctor** | **Numpydoc** | `nox -s lint` | Validates strict adherence to NumPy docstring standards. |
| **Set Designer** | **Deptry** | `deptry .` | Checks for unused or missing dependencies. |
| **Security Guard** | **Pip-Audit** | `pip-audit` | Scans dependencies for known vulnerabilities. |
| **Distributor** | **Bump My Version** | `bump-my-version show` | Manages versioning and release tagging. |
| **Editor** | **Towncrier** | `towncrier` | Compiles the changelog from fragment files. |
| **Quality Control** | **Pre-commit** | `pre-commit run --all-files` | Runs all checks before you can commit. |

> **Pro Tip:** Install the git hooks to auto-run safety checks:
>
> ```bash
> pre-commit install
> ```

---

# 🍿 SHOWTIMES   Quick Start

Grab your popcorn. Here are three feature presentations to get you started.

### 🎥 Feature Presentation 1: The Classic (Synchronous)

*For when you want to enjoy the film one frame at a time.*

This example demonstrates the standard synchronous workflow: initializing the client, fetching a movie by ID, and accessing its data model attributes. Note the use of dot-notation for attributes (`movie.title`) thanks to our type-safe models.

```python
from tmdbfusion import TMDBClient
from tmdbfusion.exceptions import TMDBError

def main():
    # 🎬 ACTION! Initialize the client
    # Pro Tip: Pass api_key=None to read from TMDB_API_KEY env var
    with TMDBClient("your_api_key_here") as client:
        try:
            # Fetch "Fight Club" (ID: 550)
            # The director calls for a close-up on the details
            movie = client.movies.details(550)

            print(f"Title: {movie.title}")
            print(f"Tagline: {movie.tagline}")
            print(f"Budget: ${movie.budget:,.2f}")

        except TMDBError as e:
            # 🎬 CUT! Something went wrong on set.
            print(f"Scene failed: {e}")

if __name__ == "__main__":
    main()
```

### 🏎️ Feature Presentation 2: Fast & Furious (Asynchronous)

*For when you need speed. Lots of speed.*

This example showcases the power of `asyncio`. We use the `AsyncTMDBClient` to fetch the first pages of "Popular", "Top Rated", and "Now Playing" movies simultaneously. This is the **IMAX** way to use the library.

```python
import asyncio
from tmdbfusion import AsyncTMDBClient

async def main():
    async with AsyncTMDBClient("your_api_key_here") as client:
        # Schedule concurrent filming units
        # We fetch three different lists at the exact same time
        tasks = [
            client.movies.popular(),
            client.movies.top_rated(),
            client.movies.now_playing()
        ]
        
        # 🎬 ACTION! Execute all tasks
        results = await asyncio.gather(*tasks)
        popular, top_rated, now_playing = results

        print(f"Popular: {popular.results[0].title}")
        print(f"Top Rated: {top_rated.results[0].title}")
        print(f"Now Playing: {now_playing.results[0].title}")

if __name__ == "__main__":
    asyncio.run(main())
```

### 🔄 Feature Presentation 3: The Extended Cut (Auto-Pagination)

*Binge-watch the entire series without lifting a finger.*

Manually handling page numbers is so 1999. Use the built-in `paginate` helper to stream results lazily. This iterator handles the page requests for you behind the scenes.

```python
from tmdbfusion import TMDBClient

def main():
    with TMDBClient("your_api_key") as client:
        # Create an iterator for popular movies
        # We start rolling and don't stop strictly at page 1
        iterator = client.paginate(client.movies.popular)

        print("--- Top 20 Movies ---")
        # take(20) ensures we only consume what we need
        for movie in iterator.take(20):
            print(f"⭐ {movie.vote_average} | {movie.title}")

if __name__ == "__main__":
    main()
```

---

# 🎬 CAST & CREW   API Reference

## 👤 The Director: `TMDBClient` / `AsyncTMDBClient`

**Import:** `from tmdbfusion import TMDBClient, AsyncTMDBClient`

The `Client` is the auteur of your application. It manages the connection, handles authentication, and directs the scene.

### 📝 Constructor

```python
def __init__(
    self,
    api_key: str,
    *,
    access_token: str | None = None,
    language: str = "en-US",
    timeout: float = 30.0,
    retries: int = 3,
    backoff_factor: float = 0.5,
    max_connections: int = 100,
    max_keepalive_connections: int = 20,
    cache_ttl: float | None = None,
    log_hook: Callable[[str, str, int], None] | None = None,
)
```

<details>
<summary><b>📋 Production Notes (Parameters)</b></summary>

| Parameter | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `api_key` | `str` | **Required** | Your TMDB API Key (v3). |
| `access_token` | `str` | `None` | Your API Read Access Token (v4). Required for some v4 endpoints. |
| `language` | `str` | `"en-US"` | Default language for all requests (ISO 639-1). |
| `timeout` | `float` | `30.0` | Connection timeout in seconds. Don't be late to the set. |
| `retries` | `int` | `3` | Application-level retries for 5xx errors and 429s. |
| `backoff_factor` | `float` | `0.5` | Exponential backoff multiplier. |
| `cache_ttl` | `float` | `None` | Time-to-live for local caching (seconds). If set, GET requests are cached. |

</details>

### 📡 Core Methods

#### `get()`

**Signature:** `get(id_or_external: int | str, *, append: list[str] | None = None) -> object`  
**Description:** The "Magic Lens". Attempts to auto-detect the media type based on the ID format.

- If `int`: Assumes `Movie`.
- If `str` (e.g., `tt12345`): Performs a `find` for IMDb ID.
- If `str` (e.g., `tv:123`): Explicitly fetches TV show.

#### `paginate()`

**Signature:** `paginate(method, *, map_response=None, **kwargs) -> PaginatedIterator`  
**Description:** Creates a lazy iterator for any API method that accepts a `page` parameter.
**Returns:** `PaginatedIterator` (Sync) or `AsyncPaginatedIterator` (Async) with `.take(n)` and `.skip(n)` methods.

#### `batch()`

**Signature:** `batch(concurrency: int = 10) -> BatchContext`  
**Description:** Creates a context manager to queue operations and execute them with controlled concurrency.
**Usage:**

```python
async with client.batch(concurrency=5) as batch:
    for mid in [550, 551, 552]:
        batch.add(client.movies.details(mid))
# Results available after exit
results = batch.results
```

#### `sync_config()`

**Signature:** `sync_config() -> None`  
**Description:** Fetches the latest API configuration from TMDB and configures the `images` utility with base URLs.

#### `close()`

**Signature:** `close() -> None`  
**Description:** Cuts the wrap. Closes the underlying HTTP transport.

---

## 🌟 The Stars (Core Namespaces)

### 🎬 `client.movies`

**Class:** `MoviesAPI` / `AsyncMoviesAPI`  
**Purpose:** The headliner. Interfaces with the standard Movie endpoints.

| Method | Description |
| :--- | :--- |
| `details` | Get the primary details of a movie. |
| `popular` | Get a list of the current popular movies. |
| `top_rated` | Get the top rated movies on TMDB. |
| `now_playing` | Get a list of movies in theatres. |
| `upcoming` | Get a list of movies coming soon. |
| `credits` | Get the cast and crew for a movie. |
| `recommendations` | Get a list of recommended movies for a movie. |
| `similar` | Get a list of similar movies. |
| `images` | Get the images that belong to a movie. |
| `videos` | Get the videos that have been added to a movie. |
| `watch_providers` | Get the list of streaming providers. |

<details open>
<summary><b>📜 Method Details: Movies</b></summary>

#### `details()`

- **Signature:** `details(movie_id: int, *, append_to_response: str | None = None) -> MovieDetails`
- **Sync/Async:** Both.
- **Parameters:**
  - `movie_id` (int): The ID of the movie.
  - `append_to_response` (str): Comma separated list of extra requests (e.g., "credits,images").
- **Returns:** `MovieDetails` model.
- **Usage:**

  ```python
  movie = client.movies.details(550, append_to_response="credits")
  ```

#### `popular()`

- **Signature:** `popular(*, page: int = 1) -> MoviePaginatedResponse`
- **Sync/Async:** Both.
- **Parameters:**
  - `page` (int): Page number (default: 1).
- **Returns:** `MoviePaginatedResponse` containing list of `Movie`.
- **Usage:**

  ```python
  popular = client.movies.popular(page=2)
  ```

#### `top_rated()`

- **Signature:** `top_rated(*, page: int = 1) -> MoviePaginatedResponse`
- **Sync/Async:** Both.
- **Parameters:** `page` (int).
- **Returns:** `MoviePaginatedResponse`.

#### `credits()`

- **Signature:** `credits(movie_id: int) -> Credits`
- **Sync/Async:** Both.
- **Parameters:** `movie_id` (int).
- **Returns:** `Credits` model (contains `cast` and `crew` lists).
- **Usage:**

  ```python
  credits = client.movies.credits(550)
  print(credits.cast[0].name)
  ```

#### `images()`

- **Signature:** `images(movie_id: int, *, include_image_language: str | None = None) -> ImagesResponse`
- **Sync/Async:** Both.
- **Parameters:**
  - `movie_id` (int).
  - `include_image_language` (str): Filter by language (e.g., "en,null").
- **Returns:** `ImagesResponse` (backdrops, posters, logos).

*(All other methods follow similar standard signatures)*

</details>

---

### 📺 `client.tv`

**Class:** `TVAPI` / `AsyncTVAPI`  
**Purpose:** The binge-worthy content. Interfaces with TV Show endpoints.

| Method | Description |
| :--- | :--- |
| `details` | Get the primary TV show details. |
| `popular` | Get a list of the current popular TV shows. |
| `top_rated` | Get the top rated TV shows. |
| `on_the_air` | Get a list of shows that are currently on the air. |
| `airing_today` | Get a list of shows that are airing today. |
| `credits` | Get the cast and crew. |
| `external_ids` | Get the external Ids for a TV show. |

<details>
<summary><b>📜 Method Details: TV</b></summary>

#### `details()`

- **Signature:** `details(tv_id: int, *, append_to_response: str | None = None) -> TVDetails`
- **Sync/Async:** Both.
- **Parameters:**
  - `tv_id` (int): The ID of the TV show.
  - `append_to_response` (str).
- **Returns:** `TVDetails` model.

#### `on_the_air()`

- **Signature:** `on_the_air(*, page: int = 1) -> TVPaginatedResponse`
- **Sync/Async:** Both.
- **Parameters:** `page` (int).
- **Returns:** `TVPaginatedResponse`.

#### `season()`

See `client.tv_seasons` for season-specific endpoints.

</details>

---

### 🧑 `client.people`

**Class:** `PeopleAPI` / `AsyncPeopleAPI`  
**Purpose:** The talent. Interfaces with People endpoints.

| Method | Description |
| :--- | :--- |
| `details` | Get the primary person details. |
| `movie_credits` | Get the movie credits for a person. |
| `tv_credits` | Get the TV credits for a person. |
| `combined_credits` | Get the combined movie and TV credits. |
| `images` | Get the images for a person. |
| `popular` | Get the list of popular people. |

<details>
<summary><b>📜 Method Details: People</b></summary>

#### `details()`

- **Signature:** `details(person_id: int, *, append_to_response: str | None = None) -> PersonDetails`
- **Sync/Async:** Both.
- **Parameters:** `person_id` (int).
- **Returns:** `PersonDetails` model.
- **Usage:**

  ```python
  brad = client.people.details(287)
  ```

#### `combined_credits()`

- **Signature:** `combined_credits(person_id: int) -> CombinedCredits`
- **Sync/Async:** Both.
- **Parameters:** `person_id` (int).
- **Returns:** `CombinedCredits` (cast/crew for both media types).

</details>

---

## 🎭 The Supporting Cast (Discovery & Metadata)

### 🔍 `client.search`

**Class:** `SearchAPI` / `AsyncSearchAPI`  
**Purpose:** Find what you're looking for.

| Method | Description |
| :--- | :--- |
| `movie` | Search for movies. |
| `tv` | Search for TV shows. |
| `person` | Search for people. |
| `multi` | Search for movies, TV shows, and people in a single request. |
| `collection` | Search for collections. |
| `company` | Search for companies. |
| `keyword` | Search for keywords. |

<details>
<summary><b>📜 Method Details: Search</b></summary>

#### `movie()`

- **Signature:** `movie(query: str, *, page: int = 1, year: int | None = None, ...) -> MoviePaginatedResponse`
- **Parameters:** `query` (str), `page`, `year`, `primary_release_year`, `include_adult`.
- **Usage:**

  ```python
  results = client.search.movie("The Matrix", year=1999)
  ```

#### `multi()`

- **Signature:** `multi(query: str, *, page: int = 1) -> MultiPaginatedResponse`
- **Returns:** Mixed list of `Movie`, `TV`, and `Person` objects. Check `media_type` attribute.

</details>

### 🧭 `client.discover`

**Class:** `DiscoverAPI` / `AsyncDiscoverAPI`  
**Purpose:** The engine of discovery. Filter content by detailed criteria.

| Method | Description |
| :--- | :--- |
| `movie` | Discover movies by specified criteria. |
| `tv` | Discover TV shows by specified criteria. |

<details>
<summary><b>📜 Method Details: Discover</b></summary>

#### `movie()`

- **Signature:** `movie(**kwargs) -> MoviePaginatedResponse`
- **Parameters:** Accepts all standard TMDB discover parameters as kwargs (`with_genres`, `sort_by`, `primary_release_year`, etc.).
- **Usage:**

  ```python
  # Find Action movies from 2023 sorted by revenue
  discoveries = client.discover.movie(
      with_genres=28,
      primary_release_year=2023,
      sort_by="revenue.desc"
  )
  ```

</details>

### 📈 `client.trending`

**Class:** `TrendingAPI` / `AsyncTrendingAPI`  
**Purpose:** What's hot right now.

| Method | Description |
| :--- | :--- |
| `movie` | Get trending movies. |
| `tv` | Get trending TV shows. |
| `person` | Get trending people. |
| `all` | Get trending content of all types. |

<details>
<summary><b>📜 Method Details: Trending</b></summary>

#### `all()`

- **Signature:** `all(time_window: str = "day") -> MultiPaginatedResponse`
- **Parameters:** `time_window`: "day" or "week".
- **Usage:**

  ```python
  trending = client.trending.all("week")
  ```

</details>

### 🕵️ `client.find`

**Class:** `FindAPI` / `AsyncFindAPI`  
**Purpose:** Locate TMDB objects by external IDs (IMDb, TVDB, Facebook, Instagram, Twitter).

#### `by_id()`

- **Signature:** `by_id(external_id: str, *, external_source: str) -> FindResponse`
- **Parameters:**
  - `external_id`: The ID from the external source.
  - `external_source`: "imdb_id", "tvdb_id", etc.
- **Returns:** `FindResponse` containing `movie_results`, `tv_results`, etc.

### 🏷️ `client.genres`

**Class:** `GenresAPI` / `AsyncGenresAPI`  
**Purpose:** Get the list of official genres.

| Method | Description |
| :--- | :--- |
| `movie_list` | Get the list of movie genres. |
| `tv_list` | Get the list of TV genres. |

### 📚 `client.collections`

**Class:** `CollectionsAPI` / `AsyncCollectionsAPI`  
**Purpose:** Get collection details (e.g., "The Avengers Collection").

#### `details()`

- **Signature:** `details(collection_id: int) -> CollectionDetails`

### 🏢 `client.companies`

**Class:** `CompaniesAPI` / `AsyncCompaniesAPI`  
**Purpose:** Get production company details.

#### `details()`

- **Signature:** `details(company_id: int) -> CompanyDetails`

### 🔑 `client.keywords`

**Class:** `KeywordsAPI` / `AsyncKeywordsAPI`  
**Purpose:** Get keyword details.

#### `details()`

- **Signature:** `details(keyword_id: int) -> Keyword`

### 📡 `client.networks`

**Class:** `NetworksAPI` / `AsyncNetworksAPI`  
**Purpose:** Get TV network details.

#### `details()`

- **Signature:** `details(network_id: int) -> Network`

### 📝 `client.reviews`

**Class:** `ReviewsAPI` / `AsyncReviewsAPI`  
**Purpose:** Get full review details.

#### `details()`

- **Signature:** `details(review_id: str) -> Review`
- **Note:** `review_id` is a string (UUID).

### 📺 `client.watch_providers`

**Class:** `WatchProvidersAPI` / `AsyncWatchProvidersAPI`  
**Purpose:** Get available watch providers and regions.

| Method | Description |
| :--- | :--- |
| `movie_providers` | Get available movie providers. |
| `tv_providers` | Get available TV providers. |
| `regions` | Get available regions. |

<details>
<summary><b>📜 Method Details: Watch Providers</b></summary>

#### `movie_providers()`

- **Signature:** `movie_providers(*, watch_region: str = "US") -> WatchProvidersList`
- **Parameters:** `watch_region`: ISO 3166-1 code.

</details>

---

---

## 🔐 Behind The Scenes (Account & Auth)

### 👤 `client.account` (V3)

**Class:** `AccountAPI` / `AsyncAccountAPI`  
**Purpose:** Manage user account data (V3).
**Requirement:** Most methods require a `session_id`.

| Method | Description |
| :--- | :--- |
| `details` | Get your account details. |
| `favorite_movies` | Get your favorite movies. |
| `favorite_tv` | Get your favorite TV shows. |
| `add_favorite` | Mark a movie or TV show as favorite. |
| `rated_movies` | Get movies you have rated. |
| `rated_tv` | Get TV shows you have rated. |
| `rated_episodes` | Get TV episodes you have rated. |
| `watchlist_movies` | Get movies on your watchlist. |
| `watchlist_tv` | Get TV shows on your watchlist. |
| `add_to_watchlist` | Add a movie or TV show to your watchlist. |
| `lists` | Get lists you have created. |

<details>
<summary><b>📜 Method Details: Account (V3)</b></summary>

#### `details()`

- **Signature:** `details(*, session_id: str) -> AccountDetails`
- **Parameters:** `session_id` (str).
- **Usage:**

  ```python
  details = client.account.details(session_id="your_session_id")
  print(f"Logged in as {details.username}")
  ```

#### `add_favorite()`

- **Signature:** `add_favorite(account_id: int, *, session_id: str, media_type: str, media_id: int, favorite: bool) -> StatusResponse`
- **Parameters:**
  - `account_id` (int): Your account ID.
  - `media_type` (str): "movie" or "tv".
  - `favorite` (bool): `True` to add, `False` to remove.
- **Usage:**

  ```python
  client.account.add_favorite(
      account_id=123,
      session_id="sess_id",
      media_type="movie",
      media_id=550,
      favorite=True
  )
  ```

</details>

### 🔑 `client.authentication` (V3)

**Class:** `AuthenticationAPI` / `AsyncAuthenticationAPI`  
**Purpose:** Create Sessions and Guest Sessions.

| Method | Description |
| :--- | :--- |
| `create_request_token` | Step 1: Create a temporary request token. |
| `create_session_with_login` | Step 2 (Optional): Validate token with username/password. |
| `create_session` | Step 3: Create a full session ID. |
| `create_guest_session` | Create a temporary guest session. |
| `delete_session` | Logout/Delete a session. |

<details>
<summary><b>📜 Method Details: Authentication</b></summary>

#### `create_request_token()`

- **Returns:** `RequestToken` (contains `request_token` string).
- **Note:** Redirect user to `https://www.themoviedb.org/authenticate/{request_token}` for approval if not using direct login.

#### `create_session()`

- **Signature:** `create_session(*, request_token: str) -> Session`
- **Parameters:** `request_token` (approved).
- **Returns:** `Session` (contains `session_id`).

</details>

### 🎫 `client.guest_session`

**Class:** `GuestSessionAPI` / `AsyncGuestSessionAPI`  
**Purpose:** Fetch rated items for a guest session.

| Method | Description |
| :--- | :--- |
| `rated_movies` | Get rated movies. |
| `rated_tv` | Get rated TV shows. |
| `rated_episodes` | Get rated episodes. |

---

## 🎬 The Sequel: Version 4 API

### 👤 `client.account_v4`

**Class:** `AccountV4API` / `AsyncAccountV4API`  
**Purpose:** Manage user account data using V4 Access Tokens.
**Requirement:** Use `access_token` in `Client` constructor.

| Method | Description |
| :--- | :--- |
| `lists` | Get lists. |
| `favorite_movies` | Get favorite movies. |
| `favorite_tv` | Get favorite TV shows. |
| `rated_movies` | Get rated movies. |
| `rated_tv` | Get rated TV shows. |
| `movie_recommendations` | Get movie recommendations. |
| `tv_recommendations` | Get TV recommendations. |
| `movie_watchlist` | Get movie watchlist. |
| `tv_watchlist` | Get TV watchlist. |

### 🔐 `client.auth_v4`

**Class:** `AuthV4API` / `AsyncAuthV4API`  
**Purpose:** Manage V4 Request Tokens and Access Tokens.

| Method | Description |
| :--- | :--- |
| `create_request_token` | Create a V4 request token. |
| `create_access_token` | Create a V4 access token. |
| `delete_access_token` | Delete an access token. |

### 📝 `client.lists_v4`

**Class:** `ListsV4API` / `AsyncListsV4API`  
**Purpose:** Manage V4 Lists.

| Method | Description |
| :--- | :--- |
| `details` | Get list details. |
| `create` | Create a list. |
| `update` | Update a list. |
| `delete` | Delete a list. |
| `add_items` | Add items to list. |
| `remove_items` | Remove items from list. |
| `check_item_status` | Check if item is in list. |

---

## 📅 Lists & Changes (V3)

### 📝 `client.lists` (V3)

**Class:** `ListsAPI` / `AsyncListsAPI`  
**Purpose:** Get details for public V3 lists.

#### `details()`

- **Signature:** `details(list_id: int | str) -> ListDetails`

### 🔄 `client.changes`

**Class:** `ChangesAPI` / `AsyncChangesAPI`  
**Purpose:** Get listing of ID changes.

| Method | Description |
| :--- | :--- |
| `movie` | Get movie change list. |
| `tv` | Get TV change list. |
| `person` | Get person change list. |

### 📜 `client.certifications`

**Class:** `CertificationsAPI` / `AsyncCertificationsAPI`  
**Purpose:** Get supported certifications.

| Method | Description |
| :--- | :--- |
| `movie_list` | Get movie certifications. |
| `tv_list` | Get TV certifications. |

---

---

# 📽️ DIRECTOR'S CUT   Advanced Usage

For the power users who want to go behind the scenes and tweak the lighting.

### 🔄 Pagination Automagic

The `paginate` method is a high-level abstraction, but under the hood, it's doing heavy lifting.

```python
# Create an async iterator for popular movies
iterator = client.paginate(client.movies.popular)

# Take 100 items, but skip the first 20
# This automatically handles page boundaries.
# If page 1 has 20 items, it skips page 1 entirely and starts fetching from page 2.
subset = iterator.skip(20).take(100)

async for movie in subset:
    print(movie.title)
```

### ⚡ Custom Transport & Caching

You can inject your own logging hooks or adjust the retry logic.

```python
def my_log_hook(method, url, status):
    print(f"🎥 [CUT] {method} {url} -> {status}")

client = TMDBClient(
    "api_key",
    retries=5,                # More persistence
    backoff_factor=1.0,       # Slower backoff
    cache_ttl=3600,           # Cache GET requests for 1 hour
    log_hook=my_log_hook
)
```

### 📦 Batching Operations

When you need to fetch data for a grid view (e.g., 20 movies at once), sequential requests are too slow. Use `batch()`.

```python
async with client.batch(concurrency=10) as batch:
    for movie_id in top_20_ids:
        # These are queued, not awaited immediately
        batch.add(client.movies.details(movie_id))

# All results return in the same order they were added
movies = batch.results
```

### 🖼️ Image Handling

The `images` utility helps you construct full URLs for posters and backdrops using the current configuration.

```python
# Ensure config is loaded
client.sync_config()

poster_path = movie.poster_path  # e.g., "/abc.jpg"
url = client.images.get_url(poster_path, "poster", "w500")
print(url)
# Output: https://image.tmdb.org/t/p/w500/abc.jpg
```

---

# 🎬 PRODUCTION NOTES   Design Decisions

### 🏗️ Architecture

We chose **msgspec** over `pydantic` for one reason: **Speed**. When deserializing thousands of movie objects for a data science pipeline or a high-traffic web app, `msgspec` offers significant performance gains. Every model is a `frozen=True` Struct, ensuring immutability and thread safety.

### 🧵 Sync vs Async

Rather than using a single client with `asyncio.run()` hacks for sync usage, we provide two distinct implementations (`TMDBClient` and `AsyncTMDBClient`) that share the same logical structure but use different transport layers (`httpx.Client` vs `httpx.AsyncClient`). This ensures that synchronous code is truly blocking and asynchronous code is truly non-blocking, without overhead.

### 🛡️ Type Safety

We employ `Strict` mode in our `mypy` configuration. `Optional` types are explicit. There are no loose dictionaries returned from the API; everything is validated against a schema. If the API changes and returns an unknown field, we ignore it by default to prevent crashing, but missing required fields will raise validation errors, alerting you to contract breaches immediately.

---

### ⚖️ License

Distributed under the **MIT License**. See `LICENSE` for more information.

<br />

<div align="center">

<h3><i>"This is your life and it's ending one minute at a time."</i></h3>
<h5>  Fight Club (1999)</h5>

<br />
<b>by Xsyncio</b><br/>
<i>Not endorsed or certified by TMDB.</i>

</div>
