# Tutorial API filme Python

> Construiești un mic instrument Python în linia de comandă care primește titlul unui film, îl caută, apoi preia detaliile, distribuția și evaluările cu un singur apel în plus. Ai nevoie de Python 3.10 sau mai nou, biblioteca requests și o cheie amodb. Durează cam 15 minute și se încheie cu un script complet.

Vei construi `movie.py`, un instrument în linia de comandă. Îi dai un titlu, el caută, alege prima potrivire și afișează anul, durata, genurile, distribuția principală, evaluarea IMDb și un URL de poster. Are nevoie de Python 3.10 sau mai nou, biblioteca `requests` și o cheie amodb. Socotește 15 minute.

## Pasul 1: obține o cheie API

amodb este plătit în avans și fără nivel gratuit, deci alege mai întâi un plan la [prețuri](https://amodb.dev/ro/pricing/). Planurile de 20, 40 și 120 de cereri pe minut întorc toate aceleași date. Apoi creează o cheie în panoul contului tău. O cheie are 32 de caractere hexazecimale minuscule.

Exportă-o ca variabilă de mediu, ca să rămână în afara codului sursă:

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

În Windows PowerShell aceeași linie este `$env:AMODB_KEY = "YOUR_API_KEY"`.

## Pasul 2: configurează proiectul

Creează un mediu virtual și instalează `requests`:

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

În Windows rulează `.venv\Scripts\activate` în loc de a doua linie.

## Pasul 3: trimite prima cerere

Endpoint-ul de căutare este `GET /3/search/movie` cu un parametru `query`. Acest fragment afișează starea HTTP și ID-ul celei mai bune potriviri. Transmite mereu un timeout, pentru că `requests` așteaptă la nesfârșit implicit.

```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"])
```

Răspunsul are structura TMDB: un array `results` sortat după relevanță, plus `page` și `total_results`. Vezi [căutare](https://amodb.dev/ro/search/) pentru fiecare filtru.

## Pasul 4: preia detalii, distribuție și evaluări

Rezultatele căutării sunt scurte. Folosește ID-ul cu `GET /3/movie/{movie_id}` și adaugă `append_to_response=credits,ratings` ca să primești distribuția și scorurile IMDb, Kinopoisk, Metacritic, Rotten Tomatoes și TMDB în aceeași cerere. Fiecare scor este un obiect cu `value` și `votes`, sau `null` când sursa nu se cunoaște, deci testează înainte să afișezi. Detaliile sunt pe paginile [filme](https://amodb.dev/ro/movies/) și [evaluări](https://amodb.dev/ro/ratings/). În scriptul final `session` este un `requests.Session` care poartă deja antetul 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")
```

## Pasul 5: tratează erorile și limitele de cereri

Verifică starea înainte să parsezi corpul. Erorile folosesc corpul TMDB cu `status_code` și `status_message`.

| Stare         | Semnificație                  | Ce face scriptul                                                               |
| ------------- | ----------------------------- | ------------------------------------------------------------------------------ |
| 401           | Cheie invalidă sau suspendată | Se oprește și spune să verifici `AMODB_KEY`. O reîncercare nu ajută.           |
| 404           | Niciun astfel de ID           | Afișează `status_message`.                                                     |
| 429           | Ai depășit cererile pe minut  | Doarme `Retry-After` secunde (plafon 30), apoi reîncearcă de până la două ori. |
| 504           | Timeout la backend            | Afișat ca eroare. Rulează comanda din nou.                                     |
| Eșec de rețea | Fără conexiune sau timeout    | Prins ca `requests.RequestException` și raportat.                              |

Bucla de reîncercare este scurtă:

```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))
```

Mai multe coduri sunt listate la [erori](https://amodb.dev/ro/errors/), iar regulile ferestrei sunt la [limite de cereri](https://amodb.dev/ro/rate-limits/).

## Pasul 6: păstrează răspunsurile în cache pe disc

Marginea ține detaliile 6 ore și căutarea 10 minute. Propriul tău cache economisește drumul dus-întors și nu consumă nicio cerere din limita ta. Scriptul final stochează fiecare răspuns într-un fișier denumit după un hash al căii și parametrilor și reutilizează fișierul cât timp este mai tânăr decât TTL.

## Pasul 7: programul complet

Salvează asta ca `movie.py` și rulează `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()
```

## Pașii următori

- Afișează un poster într-o altă dimensiune. Gazda de imagini acceptă oricare dintre dimensiunile listate la [imagini](https://amodb.dev/ro/images-api/) și nu cere cheie.
- Adaugă un filtru `year` la apelul de căutare sau treci la [descoperire](https://amodb.dev/ro/discover/) pentru liste filtrate.
- Arată un indicator de atmosferă lângă evaluare adăugând `moods` la `append_to_response`. Fiecare intrare din `moods.results` are un `name` și un `avg` pe o scară de la 0 la 10.
- Citește [ghidul de pornire rapidă](https://amodb.dev/ro/quickstart/) pentru același flux în curl, apoi încearcă [tutorialul JavaScript](https://amodb.dev/ro/javascript-movie-api/) pentru o variantă în lot.

## Întrebări și răspunsuri

### De ce versiune de Python și ce biblioteci am nevoie?

Python 3.10 sau mai nou și biblioteca requests. Restul vine din biblioteca standard. Cheia călătorește într-un antet Authorization Bearer, deci nu apare niciodată în URL-uri, în istoricul shell-ului sau în logurile serverului.

### Cum rămân sub limita de cereri?

Pune răspunsurile în cache și respectă Retry-After. Fiecare cheie primește 20, 40 sau 120 de cereri pe minut într-o fereastră glisantă de 60 de secunde. Scriptul reîncearcă un 429 de două ori, așteaptă numărul de secunde din antet și plafonează așteptarea la 30.

### Pot reutiliza cod scris pentru un client TMDB v3?

Da, pentru apelurile de citire. Căile, parametrii și JSON-ul se potrivesc cu TMDB v3, deci schimbi URL-ul de bază în https://api.amodb.dev/3 și trimiți o cheie amodb. Metodele de scriere, precum evaluarea unui film, întorc 405 cu status_code 37.

### Cheia ar trebui pusă în api_key sau într-un antet?

Ambele merg. Antetul ține cheia în afara URL-urilor, a logurilor de proxy și a liniilor de cerere, deci acest tutorial folosește Authorization: Bearer. Parametrul de interogare api_key rămâne disponibil pentru uneltele care nu pot seta antete.

### Cât ar trebui să țin în cache căutarea și detaliile?

Folosește 10 minute pentru căutare și 6 ore pentru detalii, aceleași durate ca la cache-ul de la margine. Un cache local mai scurt doar adaugă cereri, pentru că copia de la margine este partajată între chei și are deja acele durate.

## Pagini conexe

- [API căutare](https://amodb.dev/ro/search/): API căutare cu căile și JSON-ul TMDB v3. Găsește filme, seriale, persoane, colecții, companii și cuvinte-cheie după text, cu filtre de an și paginare.
- [API detalii film](https://amodb.dev/ro/movies/): API detalii film cu aceleași căi și același JSON ca TMDB v3. Preia un film după ID, adaugă credite, evaluări și atmosferă cu append_to_response, în 14 limbi.
- [Evaluări pentru filme și TV](https://amodb.dev/ro/ratings/): API evaluări filme ce întoarce scorurile IMDb, Kinopoisk, Metacritic, Rotten Tomatoes și TMDB pentru un titlu într-un apel, cu null pentru sursele necunoscute.
- [Ghid rapid](https://amodb.dev/ro/quickstart/): Fă primul apel amodb: ia o cheie, cere detaliile unui film în curl, JavaScript, Python, PHP sau Go, apoi încarcă posterul de pe img.amodb.dev. Sub 2 minute.
- [Limite de cereri](https://amodb.dev/ro/rate-limits/): amodb limitează fiecare cheie în cereri pe minut, în fereastră glisantă de 60 de secunde: 20, 40 sau 120. Citește antetele și tratează 429 cu Retry-After.
- [Erori](https://amodb.dev/ro/errors/): Fiecare eroare amodb, cu statusul HTTP, status_code TMDB și mesajul, plus forma erorii OMDb. Ce înseamnă fiecare și ce ar trebui să facă clientul tău.

Actualizat: 2026-10-18. Source: https://amodb.dev/ro/python-movie-api/
