# Poradnik API filmów w Pythonie

> Zbuduj małe narzędzie wiersza poleceń w Pythonie, które przyjmuje tytuł filmu, wyszukuje go, a potem jednym dodatkowym wywołaniem pobiera szczegóły, obsadę i oceny. Potrzebujesz Pythona 3.10 lub nowszego, biblioteki requests i klucza amodb. Zajmuje około 15 minut i kończy się kompletnym skryptem.

Zbudujesz `movie.py`, narzędzie wiersza poleceń. Podajesz mu tytuł, a ono wyszukuje, wybiera pierwsze dopasowanie i wypisuje rok, czas trwania, gatunki, główną obsadę, ocenę IMDb i URL plakatu. Potrzebuje Pythona 3.10 lub nowszego, biblioteki `requests` i klucza amodb. Zaplanuj 15 minut.

## Krok 1: Zdobądź klucz API

amodb jest przedpłacone bez darmowego planu, więc najpierw wybierz plan na stronie [cennik](https://amodb.dev/pl/pricing/). Plany z 20, 40 i 120 żądaniami na minutę zwracają te same dane. Potem utwórz klucz w panelu konta. Klucz ma 32 małe znaki szesnastkowe.

Wyeksportuj go jako zmienną środowiskową, aby został poza kodem źródłowym:

```bash
export AMODB_KEY="YOUR_API_KEY"
```

W PowerShell w Windows ta sama linia to `$env:AMODB_KEY = "YOUR_API_KEY"`.

## Krok 2: Skonfiguruj projekt

Utwórz środowisko wirtualne i zainstaluj `requests`:

```bash
python -m venv .venv
source .venv/bin/activate
pip install requests
```

W Windows uruchom `.venv\Scripts\activate` zamiast drugiej linii.

## Krok 3: Wyślij pierwsze żądanie

Endpoint wyszukiwania to `GET /3/search/movie` z parametrem `query`. Ten fragment wypisuje status HTTP i ID najlepszego dopasowania. Zawsze przekazuj timeout, bo `requests` domyślnie czeka bez końca.

```python
import os
import requests

res = requests.get(
    "https://api.amodb.dev/3/search/movie",
    params={"query": "fight club", "language": "en-US"},
    headers={"Authorization": f"Bearer {os.environ['AMODB_KEY']}"},
    timeout=10,
)
print(res.status_code, res.json()["results"][0]["id"])
```

Odpowiedź ma kształt TMDB: tablica `results` posortowana według trafności oraz `page` i `total_results`. Wszystkie filtry zobacz na stronie [wyszukiwanie](https://amodb.dev/pl/search/).

## Krok 4: Pobierz szczegóły, obsadę i oceny

Wyniki wyszukiwania są krótkie. Użyj ID z `GET /3/movie/{movie_id}` i dodaj `append_to_response=credits,ratings`, by dostać obsadę oraz wyniki z IMDb, Kinopoisk, Metacritic, Rotten Tomatoes i TMDB w tym samym żądaniu. Każdy wynik to obiekt z `value` i `votes` albo `null`, gdy źródło nie jest znane, więc sprawdzaj przed wypisaniem. Szczegóły są na stronach [filmy](https://amodb.dev/pl/movies/) i [oceny](https://amodb.dev/pl/ratings/). W końcowym skrypcie `session` to `requests.Session`, który już niesie nagłówek Authorization.

```python
params = {"language": "en-US", "append_to_response": "credits,ratings"}
movie = session.get(f"{BASE}/movie/{movie_id}", params=params, timeout=10).json()
imdb = (movie.get("ratings") or {}).get("imdb")
```

## Krok 5: Obsłuż błędy i limity żądań

Sprawdź status, zanim sparsujesz treść. Błędy używają treści TMDB z `status_code` i `status_message`.

| Status       | Znaczenie                          | Co robi skrypt                                                           |
| ------------ | ---------------------------------- | ------------------------------------------------------------------------ |
| 401          | Nieprawidłowy lub zawieszony klucz | Zatrzymuje się i radzi sprawdzić `AMODB_KEY`. Ponawianie nie pomoże.     |
| 404          | Nie ma takiego ID                  | Wypisuje `status_message`.                                               |
| 429          | Przekroczono żądania na minutę     | Śpi `Retry-After` sekund (maksimum 30), potem ponawia najwyżej dwa razy. |
| 504          | Timeout backendu                   | Wypisywany jako błąd. Uruchom polecenie ponownie.                        |
| Awaria sieci | Brak połączenia lub timeout        | Przechwytywana jako `requests.RequestException` i zgłaszana.             |

Pętla ponowień jest krótka:

```python
for attempt in range(3):
    res = session.get(url, params=params, timeout=10)
    if res.status_code != 429 or attempt == 2:
        break
    time.sleep(min(int(res.headers.get("Retry-After", "1")), 30))
```

Więcej kodów wymienia strona [błędy](https://amodb.dev/pl/errors/), a reguły okna strona [limity żądań](https://amodb.dev/pl/rate-limits/).

## Krok 6: Zapisuj odpowiedzi na dysku

Edge trzyma szczegóły przez 6 godzin, a wyszukiwanie przez 10 minut. Twój własny cache oszczędza podróż w obie strony i nie zużywa żadnego żądania z twojego limitu. Końcowy skrypt zapisuje każdą odpowiedź w pliku nazwanym od skrótu ścieżki i parametrów i używa pliku, dopóki jest młodszy niż TTL.

## Krok 7: Kompletny program

Zapisz to jako `movie.py` i uruchom `python movie.py "fight club"`.

```python
import hashlib
import json
import os
import sys
import time
from pathlib import Path

import requests

BASE = "https://api.amodb.dev/3"
POSTER = "https://img.amodb.dev/t/p/w342"
CACHE_DIR = Path.home() / ".cache" / "amodb-movie-cli"
SEARCH_TTL = 10 * 60
DETAILS_TTL = 6 * 60 * 60


class ApiError(Exception):
    pass


def error_message(res):
    try:
        return res.json()["status_message"]
    except (ValueError, KeyError):
        return res.reason


def get(session, path, params, ttl):
    CACHE_DIR.mkdir(parents=True, exist_ok=True)
    stamp = f"{path}?{json.dumps(params, sort_keys=True)}"
    cache_file = CACHE_DIR / (hashlib.sha256(stamp.encode()).hexdigest() + ".json")
    if cache_file.exists() and time.time() - cache_file.stat().st_mtime < ttl:
        return json.loads(cache_file.read_text(encoding="utf-8"))

    for attempt in range(3):
        try:
            res = session.get(BASE + path, params=params, timeout=10)
        except requests.RequestException as exc:
            raise ApiError(f"network error: {exc}") from exc
        if res.status_code != 429 or attempt == 2:
            break
        time.sleep(min(int(res.headers.get("Retry-After", "1")), 30))

    if res.status_code == 401:
        raise ApiError("401: the key is invalid or suspended. Check AMODB_KEY.")
    if res.status_code != 200:
        raise ApiError(f"{res.status_code}: {error_message(res)}")
    cache_file.write_text(res.text, encoding="utf-8")
    return res.json()


def search_movie(session, title):
    data = get(session, "/search/movie", {"query": title, "language": "en-US"}, SEARCH_TTL)
    if not data["results"]:
        raise ApiError(f"no movie found for {title!r}")
    return data["results"][0]["id"]


def movie_details(session, movie_id):
    params = {"language": "en-US", "append_to_response": "credits,ratings"}
    return get(session, f"/movie/{movie_id}", params, DETAILS_TTL)


def print_card(movie):
    year = (movie.get("release_date") or "")[:4] or "unknown year"
    genres = ", ".join(g["name"] for g in movie["genres"])
    cast = ", ".join(p["name"] for p in movie["credits"]["cast"][:3])
    imdb = (movie.get("ratings") or {}).get("imdb")
    print(f"{movie['title']} ({year}), {movie.get('runtime') or '?'} min")
    print(f"Genres: {genres or 'none listed'}")
    print(f"Cast: {cast or 'none listed'}")
    print(f"IMDb: {imdb['value']} ({imdb['votes']} votes)" if imdb else "IMDb: not known")
    if movie.get("poster_path"):
        print(f"Poster: {POSTER}{movie['poster_path']}")
    print(movie.get("overview") or "No overview in this language.")


def main():
    if len(sys.argv) < 2:
        sys.exit('Usage: python movie.py "fight club"')
    key = os.environ.get("AMODB_KEY")
    if not key:
        sys.exit("Set AMODB_KEY first.")
    session = requests.Session()
    session.headers["Authorization"] = f"Bearer {key}"
    try:
        movie_id = search_movie(session, " ".join(sys.argv[1:]))
        print_card(movie_details(session, movie_id))
    except ApiError as exc:
        sys.exit(f"error: {exc}")


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

## Kolejne kroki

- Wypisz plakat w innym rozmiarze. Host obrazów przyjmuje dowolny z rozmiarów wymienionych na stronie [obrazy](https://amodb.dev/pl/images-api/) i nie wymaga klucza.
- Dodaj filtr `year` do wywołania wyszukiwania albo przejdź na [discover](https://amodb.dev/pl/discover/) po filtrowane listy.
- Pokaż miernik nastroju obok oceny, dodając `moods` do `append_to_response`. Każdy wpis w `moods.results` ma `name` i `avg` w skali od 0 do 10.
- Przeczytaj [szybki start](https://amodb.dev/pl/quickstart/) z tym samym przepływem w curl, potem wypróbuj [poradnik JavaScript](https://amodb.dev/pl/javascript-movie-api/) z wersją wsadową.

## Pytania i odpowiedzi

### Jakiej wersji Pythona i jakich bibliotek potrzebuję?

Pythona 3.10 lub nowszego i biblioteki requests. Wszystko inne pochodzi z biblioteki standardowej. Klucz podróżuje w nagłówku Authorization Bearer, więc nigdy nie pojawia się w URL, historii powłoki ani w logach serwera.

### Jak zmieścić się w limicie żądań?

Zapisuj odpowiedzi w cache i respektuj Retry-After. Każdy klucz ma 20, 40 lub 120 żądań na minutę w przesuwanym 60-sekundowym oknie. Skrypt ponawia 429 dwa razy, czeka liczbę sekund z nagłówka i ogranicza czekanie do 30.

### Czy mogę użyć kodu napisanego pod klienta TMDB v3?

Tak, dla wywołań odczytu. Ścieżki, parametry i JSON zgadzają się z TMDB v3, więc zmieniasz bazowy URL na https://api.amodb.dev/3 i wysyłasz klucz amodb. Metody zapisu, takie jak ocenianie filmu, zwracają 405 z status_code 37.

### Czy klucz ma iść w api_key, czy w nagłówku?

Działają oba. Nagłówek trzyma klucz poza URL, logami proxy i liniami żądań, więc ten poradnik używa Authorization: Bearer. Parametr zapytania api_key pozostaje dla narzędzi, które nie potrafią ustawiać nagłówków.

### Jak długo zapisywać w cache wyszukiwanie i szczegóły?

Użyj 10 minut dla wyszukiwania i 6 godzin dla szczegółów, czyli tych samych czasów co cache na edge. Krótszy lokalny cache tylko dodaje żądania, bo kopia na edge jest współdzielona między kluczami i już trzyma te czasy.

## Powiązane strony

- [API wyszukiwania](https://amodb.dev/pl/search/): API wyszukiwania z tymi samymi ścieżkami i JSON co TMDB v3. Znajdź filmy, seriale, osoby, kolekcje, firmy i słowa kluczowe po tekście, z filtrem roku.
- [API szczegółów filmu](https://amodb.dev/pl/movies/): API szczegółów filmu z tymi samymi ścieżkami i JSON co TMDB v3. Pobierz film po ID, dołącz obsadę, oceny i nastroje przez append_to_response, 14 języków.
- [Oceny filmów i seriali](https://amodb.dev/pl/ratings/): API ocen filmów zwraca wyniki z IMDb, Kinopoisk, Metacritic, Rotten Tomatoes i TMDB dla tytułu jednym wywołaniem, z null dla każdego nieznanego źródła.
- [Szybki start](https://amodb.dev/pl/quickstart/): Pierwsze wywołanie amodb: zdobądź klucz, pobierz szczegóły filmu w curl, JavaScript, Python, PHP lub Go, potem załaduj plakat z img.amodb.dev. Poniżej 2 minut.
- [Limity żądań](https://amodb.dev/pl/rate-limits/): amodb limituje klucz liczbą żądań na minutę w przesuwanym oknie 60 sekund: 20, 40 lub 120 zależnie od planu. Czytaj nagłówki limitu i obsłuż 429 z Retry-After.
- [Błędy](https://amodb.dev/pl/errors/): Każdy błąd amodb ze statusem HTTP, status_code i komunikatem TMDB oraz kształt błędu OMDb. Co oznacza każdy z nich i co powinien zrobić twój klient dalej.

Zaktualizowano: 2026-10-18. Source: https://amodb.dev/pl/python-movie-api/
