Tvoříme klienta

Doteď jsme používali obecného klienta v podobě prohlížeče nebo programu curl. Obecného klienta musí ovládat člověk. To je přesně to, co potřebujeme, když si chceme nějaké API vyzkoušet, ale celý smysl API je v tom, aby je programy mohly využívat automaticky.

Varování

Na obsahu této části se stále pracuje.

Základ aplikace

Pokud chceme naprogramovat klienta pro konkrétní úkol, můžeme ve většině jazyků použít nějakou buďto vestavěnou, nebo doinstalovanou knihovnu. V případě jazyka Python použijeme Requests.

Vytvoříme si pro náš projekt nový adresář cojeapi-client a v něm virtuální prostředí, které si aktivujeme. Poté nainstalujeme Requests:

(venv)$ pip install requests

Pokud jste prošli kapitolou o tvorbě API serveru, ujistěte se, že pro klienta si vytváříte nový projekt - novou složku, nové virtuální prostředí, atd. Vytváříme novou, na serveru zcela nezávislou a samostatnou aplikaci.

Nyní můžeme začít s tvorbou klienta. Jenže specializovaný klient potřebuje nějaké API, na které by se mohl specializovat. K tomu se nám náramně hodí API z předešlé kapitoly. V adresáři cojeapi-client si vytvoříme nový soubor s názvem client.py a použijeme v něm Requests pro jednoduchý požadavek na server. Funkce requests.get nám umožní poslat požadavek metodou GET. Naše je veřejně dostupné na adrese https://cojeapi.honzajavorek.now.sh, takže ji použijeme jako cíl požadavku. Následně vypíšeme detaily odpovědi, kterou dostaneme:

import requests

response = requests.get("https://cojeapi.honzajavorek.now.sh/")

print(response.status_code)
print(response.headers)
print(response.text)

Napsali jsme program, který je ekvivalentem následujícího příkazu:

$ curl "https://cojeapi.honzajavorek.now.sh/"

Zkusme jej spustit, zatímco nám ve vedlejším okně jede naše API:

$ python client.py
200
{'Date': 'Sat, 04 May 2019 12:10:32 GMT', 'Content-Type': 'application/json', 'Connection': 'keep-alive', 'content-length': '129', 'cache-control': 'public, max-age=0, must-revalidate', 'x-now-cache': 'MISS', 'x-now-trace': 'zrh1', 'server': 'now', 'x-now-id': 'xdnnz-1556971832381-676f32a6396e2c1aff6d1100d3a5fd54', 'strict-transport-security': 'max-age=63072000'}
{"name": "Honza", "surname": "Javorek", "socks_size": "42", "movies_watchlist_url": "https://cojeapi.honzajavorek.now.sh/movies"}

A je to, udělali jsme svůj první požadavek na server! Vidíme, že se nám povedlo vypsat status kód odpovědi, hlavičky, i tělo. Hlavičky nám Requests rovnou poskytují jako Python slovník. Tělo odpovědi ale máme zatím jako řetězec.

Čteme JSON

Pokud bychom chtěli číst tělo zprávy, narazíme na fakt, že jej máme jako řetězec:

import requests

response = requests.get("https://cojeapi.honzajavorek.now.sh/")

print(response.status_code)
print(response.headers)
print(response.text['surname'])
$ python client.py
200
{'Date': 'Sat, 04 May 2019 12:19:48 GMT', 'Content-Type': 'application/json', 'Connection': 'keep-alive', 'content-length': '129', 'cache-control': 'public, max-age=0, must-revalidate', 'x-now-cache': 'MISS', 'x-now-trace': 'zrh1', 'server': 'now', 'x-now-id': 'mmxqr-1556972388219-44d1b59d32681aa7074f73fcc1182fbd', 'strict-transport-security': 'max-age=63072000'}
Traceback (most recent call last):
  File "client.py", line 7, in <module>
    print(response.text['surname'])
TypeError: string indices must be integers

Tělo je text ve formátu JSON (což nám sděluje i hlavička Content-Type). Nešlo by jej nějak dostat jako slovník? Šlo - přesně na toto mají Requests metodu .json():

import requests

response = requests.get("https://cojeapi.honzajavorek.now.sh/")

print(response.status_code)
print(response.headers)
print(response.json())

Nyní máme z textu ve formátu JSON obyčejný Python slovník:

$ python client.py
200
{'Date': 'Sat, 04 May 2019 12:15:59 GMT', 'Content-Type': 'application/json', 'Connection': 'keep-alive', 'content-length': '129', 'cache-control': 'public, max-age=0, must-revalidate', 'x-now-cache': 'MISS', 'x-now-trace': 'zrh1', 'server': 'now', 'x-now-id': 'xdnnz-1556972159138-9eb3b82b14895f7f03b76b8c336b6ca1', 'strict-transport-security': 'max-age=63072000'}
{'name': 'Honza', 'surname': 'Javorek', 'socks_size': '42', 'movies_watchlist_url': 'https://cojeapi.honzajavorek.now.sh/movies'}

Zpracováváme odpověď

Program, který dělá totéž co curl, není popravdě moc užitečný program. Pojďme zkusit využít naše API k napsání programu, jenž z něj zjistí seznam filmů a vypíše je.

Přepište program tak, aby posílal požadavek na https://cojeapi.honzajavorek.now.sh/movies, přečetl odpověď, prošel všechny filmy získané z těla odpovědi a pomocí print() vypsal název každého z nich.

import requests

response = requests.get("https://cojeapi.honzajavorek.now.sh/movies")
movies = response.json()
for movie in movies:
    print(movie['name'])

Pokud program spustíme, měl by vypsat všechny filmy z dotazovaného API:

$ python client.py
The Last Boy Scout
Mies vailla menneisyyttä
Sharknado
Mega Shark vs. Giant Octopus

Jestliže procházíte tento návod v rámci workshopu, například PyWorking, použijte ve vašem programu místo https://cojeapi.honzajavorek.now.sh adresu API někoho jiného z účastníků. Pokud tam má i jiné filmy než byly v návodu, měl by je program vypsat.

Získáváme doplňující data

Co kdybychom chtěli ke každému filmu vypsat i rok, kdy byl uveden? Rok není v seznamu filmů k dispozici, nachází se na detailu každého filmu. Budeme tedy muset udělat jeden požadavek na seznam filmů a poté ještě požadavek na každý film ze seznamu, abychom zjistili rok.

import requests

response = requests.get("https://cojeapi.honzajavorek.now.sh/movies")
movies = response.json()

for movie in movies:
    response = requests.get(movie['url'])
    movie_details = response.json()
    print(movie_details['name'], movie_details['year'])

Vidíme, že pro každý film děláme další požadavek na API a teprve z jeho výsledku vypisujeme jméno a rok. Pokud program spustíte, bude trvat podstatně déle, než skončí.

$ python client.py
The Last Boy Scout 1991
Mies vailla menneisyyttä 2002
Sharknado 2013
Mega Shark vs. Giant Octopus 2009

To, že musíme posílat požadavek na každý film zvlášť je buď důsledkem toho, že se snažíme z API zjistit kombinaci informací, která není úplně obvyklá, nebo důsledkem toho, že někdo API špatně navrhl. Narazili jsme přesně na tu situaci, která byla popsána v sekci Návrh API při tvorbě serveru.

Agregované informace

Data z API nemusíme jen číst a vypisovat. Zajímavější je, když je využijeme ke zjištění nových, dříve netušených informací. Pojďme například zjistit, který z filmů v seznamu je nejnovější.

import requests

response = requests.get("https://cojeapi.honzajavorek.now.sh/movies")
movies = response.json()

newest_movie = None
for movie in movies:
    response = requests.get(movie['url'])
    movie_details = response.json()

    if newest_movie is None:
        newest_movie = movie_details
    elif newest_movie['year'] < movie_details['year']:
        newest_movie = movie_details

print('Nejnovější film:', newest_movie['name'], newest_movie['year'])

Uvedený kód upravuje předchozí příklad, ale místo vypisování názvů filmů a jejich roků vydání rovnou zjišťuje, který z nich má rok s největší hodnotou. Takový film potom na konci vypíše.

$ python client.py
Nejnovější film: Sharknado 2013

Toto je jen malá ukázka toho, jak lze data z API agregovat, tzn. zjišťovat informace, jejichž výpočet protíná několik odpovědí.

Chyby

Varování

Tato kapitola není ještě připravena.

Zapisujeme

Varování

Tato kapitola není ještě připravena.

Mažeme

Varování

Tato kapitola není ještě připravena.

Kódování parametrů

Varování

Tato kapitola není ještě připravena.

Todo

co dáváme do parametrů se musí prohnat nějakym urlencoding příklady s nějakým (reverse) geocoding api (google, seznam?)

Zabezpečení

Varování

Tato kapitola není ještě připravena.

Todo

mechanismus http/https basic auth oauth většinou nějaký token (vysvětlit token), který se narve do hlavičky auth token - něco vygenerováno jen pro nás, co je tajné a neměli bychom to nikomu dávat a ukazovat

příklad s GitHubem, vygenerujeme token, dáme do ENV, nasosáme v programu a můžeme použít

Pracujeme s veřejnými API

OMDb

GitHub

Specializované knihovny (SDK)

Varování

Tato kapitola není ještě připravena.

Todo

vysvětlit specializovaného klienta příklady

Todo

připomenout, že než jdeme psát klienta na zelené louce, měli bychom ověřit, že už není nějaká hotová SDK knihovna (příklady z pypi)

základní příklady s requests, GET, POST https://github.com/honzajavorek/cojeapi/issues/2