Skip to content

Commit ad2478e

Browse files
authored
Merge pull request #3 from lubomir/requests-click
Finish lesson about requests and click
2 parents 96f1af0 + d4d002d commit ad2478e

File tree

3 files changed

+390
-2
lines changed

3 files changed

+390
-2
lines changed
+385
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,385 @@
1+
# Requests + Click
2+
3+
## Co je cílem tohoto cvičení?
4+
5+
Po projití této lekce byste měli být obeznámeni se základním použitím knihoven
6+
`requests` a `click`. Skončíme s programem, který bude umět převádět peníze z
7+
českých korun do jiných měn podle aktuálního kurzu.
8+
9+
## Předpoklady
10+
11+
Předpokládáme základní znalost Pythonu. Měli byste mít počítač s nainstalovaným
12+
interpretem jazyka Python ve verzi aspoň 3.6. Pro začátek si také vytvořte nové
13+
virtuální prostředí.
14+
15+
Dále se vám bude hodit základní přehled o tom, jak funguje internet, co je to
16+
URL a podobné drobnosti. Pokud si nejste jistí, začněte [tímto shrnutím pro
17+
začátečníky]({{ lesson_url('fast-track/http') }}).
18+
19+
20+
## Requests
21+
22+
Začneme seznámením s knihovnou [requests]. Je to knihovna určená pro HTTP
23+
požadavky na straně klienta. Poskytuje mnohem pohodlnější rozhraní než
24+
standardní knihovna Pythonu.
25+
26+
[requests]: http://docs.python-requests.org/en/master/
27+
28+
Prvním krokem by měla být instalace ve virtuálním prostředí:
29+
30+
```console
31+
(venv) $ python -m pip install requests
32+
```
33+
34+
První pokus je ideální provádět v interaktivní konzoli Pythonu. Začneme tím, že
35+
si naimportujeme modul `requests`. Komunikace přes protokol HTTP používá model
36+
požadavek/odpověď (*request*/*response*). Klient tedy nejprve pošle požadavek,
37+
a server potom odpovídá. Takto se střídají, dokud klient nemá vše, co
38+
potřebuje, nebo nedojde k chybě.
39+
40+
Pro začátek se podíváme na stránku `https://example.com`.
41+
42+
```pycon
43+
>>> import requests
44+
>>> response = requests.get("https://example.com/")
45+
>>> response
46+
<Response [200]>
47+
```
48+
49+
Takto vypsaná odpověď není příliš užitečná. To naštěstí není zase takový
50+
problém. V proměnné `response` teď máme object, který má potřebná data uložená
51+
v různých atributech.
52+
53+
Zkuste si vypsat, co obsahují atributy `response.text`, `response.status_code`,
54+
`response.encoding` a `response.history`. Taky vyzkoušejte zavolat metodu
55+
`response.json()`. Existuje jich mnohem více, ale tyto jsou docela zajímavé a
56+
relativně často užívané.
57+
58+
Na tyto experimenty použijte dvě jiné adresy (protože `example.com` není příliž
59+
zajímavý web).
60+
61+
* `https://httpbin.org/get`
62+
* `https://httpbin.org/redirect-to?url=http://example.com&status_code=301`
63+
64+
> [note]
65+
> <https://httpbin.org/> je velice užitečná služba, pokud si potřebujete
66+
> vyzkoušet komunikaci přes HTTP. Bude vám odpovídat na všemožné požadavky
67+
> podle toho, jak si řeknete. Podívejte se v prohlížeči a uvidíte docela pěkný
68+
> seznam všech možností (akorát v angličtině)
69+
70+
Pojďme se tedy podívat, co dělají zmíněné jednotlivé atributy:
71+
72+
Atribut `text` obsahuje tělo odpovědi, tak jak nám oze serveru přišla. Pro
73+
většinu stránek to bude kód v jazyku HTML, nebo v data v různých formátech.
74+
75+
Každá odpověď od serveru obsahuje číselný kód, který popisuje výsledek akce.
76+
Tento kód si můžete přečíst z atributu `status_code`. `1xx` jsou informační
77+
zprávy, na které moc často nenarazíte. `2xx` jsou úspěšné odpovědi. Někdy se
78+
může stát, že server místo odpovědi, kterou chcete, odešle *přesměrování*. To
79+
má podobu odpovědi s kódem `3xx`. Přímo tuto odpověď neuvidíte, protože
80+
knihovna `requests` ví, že je to přesměrování a proto automaticky půjde na
81+
adresu, kam vás server poslal.
82+
83+
Ke každému číselnému kódu existuje i texotvý popis. Ty najdete třeba na
84+
[Wikipedii](), nebo můžete použít <https://http.cat>.
85+
86+
Pokud dojde k přesměrování (a může jich být i několik), můžete se podívat na
87+
jednotlivé odpovědi v atributu `history`. Je to seznam, který bude pro každé
88+
přesměrování obsahovat jeden objekt.
89+
90+
Atribut `encoding` je užitečný v případě, že vám správně nefungují české znaky
91+
v odpovědi. Můžete se v něm podívat, co vám server tvrdí o datech, která vám
92+
posílá.
93+
94+
Nakonec nám zůstává metoda `json()`. JSON je datový formát, který používá mnoho
95+
různých webových služeb. Proto `requests` nabízí tuto zkratku, jak se k datům
96+
dostat. Ale pozor! Pokud v odpovědit nejsou data v tomto formátu, dostanete
97+
chybu! (A toto je očekávané chování u druhé testovací URL.)
98+
99+
100+
### Parametry pro GET
101+
102+
Ve druhé testovací URL si můžete všimnout, že obsahuje otazník a za ním nějaké
103+
další informace. Toto jsou parametry pro server, které mu říkají, co přesně od
104+
něj chceme. Typický příklad ze života je vyhledávací políčko na libovolném
105+
webu. Vyhledávaná fráze se na server stejným způsobem jako parametr.
106+
107+
Ruční zpracování a přilepení k samotné URL ale není úplně jednoduché. Musíte
108+
myslet na to, že některé znaky je potřeba zakódovat. Proto `requests` poskytují
109+
lepší možnost, jak s parametry pracovat.
110+
111+
Můžeme si nadefinovat slovník, kde klíče budou názvy parametrů (které obvykle
112+
závisí na tom, co server očekává), a hodnoty budou samotná data, která chceme
113+
posílat.
114+
115+
116+
```pycon
117+
>>> parametry = {"status_code": 301, "url": "https://example.com"}
118+
>>> r = requests.get("https://httpbin.org/redirect-to", params=parametry)
119+
```
120+
121+
V tomto případě *httpbin* potřebuje informaci o tom, kam a jak nás má
122+
přesměrovat.
123+
124+
### Posílání dat
125+
126+
Knihovna `requests` umí data nejenom přijímat, ale i posílat. K tomu slouží
127+
metoda `post()`.
128+
129+
Jendoduchý příklad je:
130+
131+
```pycon
132+
>>> r = requests.post("https://httpbin.org/post", {"ahoj": "svete"})
133+
```
134+
135+
V praxi bývá často potřeba řešit situaci, že server vyžaduje přihlášení. A tam
136+
je potřeba pracovat případ od případu. Každopádně knihovna `requests` vám
137+
umožní použít všechny obvyklé přihlašovací metody.
138+
139+
### Stažení velkého souboru
140+
141+
Jeden detail, který je poměrně snadné přehlédnout, je to, že všechny příklady
142+
výše provedou požadavek, a potom stáhnou celou odpoveď a uloží ji v paměti
143+
počítače. To je v pohodě, pokud to je něco relativně malého. Pokud budete
144+
stahovat třeba video, úplně fajn to není. Proto můžete použít tento recept,
145+
který vytvoří spojení se serverem, potom čte kousky dat po 8 kilobajtech a
146+
rovnou je zapisuje do souboru.
147+
148+
```python
149+
import requests
150+
with requests.get("https://placekitten.com/400/600") as r:
151+
r.raise_for_status()
152+
with open("kitten.jpg", "w") as f:
153+
for chunk in r.iter_content(8196):
154+
if chunk:
155+
f.write(chunk)
156+
```
157+
158+
Za vypíchnutí tady stojí jedna nová metoda: `raise_for_status()`. Po provedení
159+
požadavku je potřeba zkontrolovat, jestli se nám to podařilo. Klasicky se to
160+
dělá kontrolou hodnoty atributu `status_code`. Metoda `raise_for_status()` je
161+
zkratka: pokud nám server vrátil nějakou chybu, tato metoda vyhodí výjimku,
162+
kterou můžeme zpracovat. Pro úspěšnou odpověď tato metoda neědělá nic.
163+
164+
165+
### Cvičení
166+
167+
Česká národní banka zveřejňuje denní kurzy, které je možné si stáhnout. Navíc
168+
jsou v pěkném textovém formátu, se kterým se nám bude pěkně pracovat.
169+
170+
Adresa je
171+
<http://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt?date=01.04.2019>.
172+
Datum je ve formátu den.měsíc.rok. Pokud datum nezadáte nebo je špatně,
173+
dostanete poslední kurzy.
174+
175+
Napište si funkci, která dostane jeden argument: datum ve správném formátu
176+
(jako řetezec). Tato funkce stáhne kurzovní lístek a vrátí data v libovolné
177+
podobě, se kterou se nám bude dále pracovat.
178+
179+
Mohla by se vám hodit tato funkce, která přečte textovou odpoveď, rozseká ji na
180+
kousky a vrátí slovník. Klíče jsou zkratky měn, hodnoty jsou kurzy.
181+
182+
```python
183+
def parse_rates(text):
184+
hlavicka, jmena, *radky = text.splitlines()
185+
kurzy = {}
186+
for radek in radky:
187+
_, _, castka, mena, hodnota = radek.replace(",", ".").split("|")
188+
kurzy[mena] = float(castka) / float(hodnota)
189+
return kurzy
190+
```
191+
192+
Řešení najdete na konci této stránky.
193+
194+
195+
## Click
196+
197+
Když instalujete knihovnu, zadáváte příkaz `python -m pip install foo`. V tomto
198+
případě `python` je název příkazu, který chcete spustit, a ostatní slova na
199+
tomto řádku (oddělená mezerami), jsou argumenty tohoto příkazu.
200+
201+
Dříve nebo později narazíte na to, že vaše programy budou potřebovat nějaký
202+
vstup od uživatele. Číst je vždy přes funkci `input()` není úplně pohodlné ani
203+
pro uživatele, ani pro programátora. Proto je dobré vědět, jak definovat a
204+
používat argumenty.
205+
206+
Existuje hodně knihoven, které umožňují zpracovávat argumenty na příkazové
207+
řádce. Jenom samotná standardní knihovna Pythonu má `getopt`, `optparse` a
208+
`argparse`. Ty ale nejsou úplně příjemné na používání.
209+
210+
Oproti tomu knihovna [click] poskytuje rozhraní, ve kterém můžete jednoduché
211+
programy sekat jako Baťa cvičky. Cenou je lehce magický způsob, jak argumenty
212+
definovat, a taky ztráta možnosti ovlivnit do nejjemnějších detailů, jak se
213+
program má chovat. To ale obvykle není problém.
214+
215+
[click]: https://click.palletsprojects.com/en/7.x/
216+
217+
### Trocha teorie
218+
219+
Různé systémy používají různé konvence, jak by měly argumenty vypadat a
220+
fungovat. Tady si popíšeme, jak se slušně vychované programy chovají na Linuxu
221+
(nebo na Macu).
222+
223+
Existují dvě základní kategorie: argumenty a přepínače. Argumenty jsou většinou
224+
(ale ne vždy) vyžadované, přepínače obvykle potřeba nejsou. Argumenty jsou dané
225+
pořadím (pokud jich je víc), přepínače mají jména.
226+
227+
Jména přepínačů obvykle začínají dvěmi pomlčkami, pokud mají hezké čitelné
228+
jméno, nebo jednou pomlčkou, pokud je to jenom jedno písmeno. Dost často jeden
229+
přepínač může mít jak jednopísmenné jméno, tak i delší a čitelnější.
230+
231+
232+
### Instalace
233+
234+
Nic překvapivého:
235+
236+
```console
237+
(venv) $ python -m pip install click
238+
```
239+
240+
### Hello world
241+
242+
Na tomto jednoduchém programu si ukážeme, jak se dá funkce změnit v něco, co
243+
bude pěkně použitelné na příkazové řádce.
244+
245+
```python
246+
import click
247+
248+
@click.command()
249+
@click.option("--kolikrat", default=1, help="Kolikrát budeme zdravit")
250+
@click.option("--jmeno", prompt="Tvoje jméno",
251+
help="Koho budeme zdravit")
252+
def hello(kolikrat, jmeno):
253+
for x in range(kolikrat):
254+
click.echo(f"Ahoj {jmeno}!")
255+
256+
257+
if __name__ == "__main__":
258+
hello()
259+
```
260+
261+
Funguje to takto:
262+
263+
```console
264+
(venv) $ python hello.py --kolikrat 3 --jmeno Adame
265+
Ahoj Adame!
266+
Ahoj Adame!
267+
Ahoj Adame!
268+
```
269+
270+
Příkazům začínajícím zavináčem před definicí funkce říkáme dekorátory. Je to
271+
možnost, jak v Pythonu můžeme ovlivnit chování funkce (a pravděpodobně se jim
272+
budeme věnovat trochu více v některé následující lekci).
273+
274+
První řádek `@click.command()` říká, že následující funkce by se měla chovat
275+
jako příkaz.
276+
277+
Další dva řádky definují přepínače tohoto příkazu.
278+
279+
První z nich se jmenuje `--kolikrat`, a pokud ho nezadáme, dostane výchozí
280+
hodnotu 1. *Click* z této výchozí hodnoty pozná, že hodnotou toho přepínače
281+
bude vždy číslo. Takže když zkusíme zadat jiný text, dostaneme chybu. Argument
282+
předaný do funkce `hello()` bude už typu `int`.
283+
284+
Druhý argument bude jméno. Typ nijak nespecifikuje, takže to bude řetězec.
285+
`prompt` říká, že pokud přepínač nezadáme, program se nás zeptá.
286+
287+
Zkuste si s tímto programem chvilku hrát. Nezapomeňte, že *click* vypíše pěknou
288+
nápovědu, pokud program spustíte s přepínačem `--help`.
289+
290+
291+
### Další možnosti
292+
293+
294+
Možné typy přepínačů (použití: `@click.option(…, type=click.X, …`):
295+
296+
* `click.INT` – celé číslo
297+
* `click.FLOAT` – číslo s desetinnou tečkou
298+
* `click.FILE` – název souboru na příkazové řádce, ale funkce už dostane
299+
otevřený soubor a *click* se sám postará i o zavření
300+
301+
Další možnosti jsou třeba `multiple=True`. Tím přepínač změníme tak, že ho bude
302+
možné zadávat několikrát. Funkce potom dostane n-tici hodnot.
303+
304+
Argumenty se definují velmi podobně jako přepínače. Jediný rozdíl je v použitém
305+
dekorátoru `@click.argument()`. Jména argumentů se zadávají bez úvodních
306+
pomlček.
307+
308+
*Click* taky umožňuje vypisování na výstup. `click.echo` se chová velmi podobně
309+
jako `print`, akorát se snaží lépe fungovat, pokud máte rozbitý terminál.
310+
311+
312+
### Cvičení
313+
314+
Napište program, který bude vypisovat tuto nápovědu:
315+
316+
```console
317+
(venv) $ python cnb.py --help
318+
Usage: cnb.py [OPTIONS] CASTKA
319+
320+
Options:
321+
--datum TEXT
322+
--mena TEXT může být zadaný vícekrát
323+
--help Show this message and exit.
324+
```
325+
326+
327+
## Dokončení programu
328+
329+
Zkombinujte výsledky obou cvičení do jednoho programu. Tento program bude
330+
vyžadovat jedno číslo. To bude částka v korunách. Program načte buď kurzy podle
331+
zadaného data, nebo poslední zveřejněné.
332+
333+
Pokud nebude zadaná žádná měna, program převede částku do všech dostupných měn
334+
a vypíše je v nějakém pěkném formátu. Pokud budou nějaké měny zadané, bude
335+
převádět jen do nich.
336+
337+
338+
## Řešení
339+
340+
Zkus si ale cvičení nejdřív vyřešit bez pomoci :)
341+
342+
```python
343+
import click
344+
import requests
345+
346+
347+
def parse_rates(text):
348+
hlavicka, jmena, *radky = text.splitlines()
349+
kurzy = {}
350+
for radek in radky:
351+
_, _, castka, mena, hodnota = radek.replace(",", ".").split("|")
352+
kurzy[mena] = float(castka) / float(hodnota)
353+
return kurzy
354+
355+
356+
def get_exchange_rates(datum=None):
357+
parametry = {}
358+
if datum:
359+
# Pokud máme datum, použijeme ho. Prázdný slovník parametrů nemá na
360+
# výsledek žádný vliv.
361+
parametry["date"] = datum
362+
response = requests.get(
363+
"http://www.cnb.cz/cs/financni_trhy/devizovy_trh/kurzy_devizoveho_trhu/denni_kurz.txt",
364+
params=parametry,
365+
)
366+
return parse_rates(response.text)
367+
368+
369+
@click.command()
370+
@click.option("--datum")
371+
@click.option("--mena", multiple=True)
372+
@click.argument("castka", type=click.FLOAT)
373+
def cnb(castka, datum, mena):
374+
kurzy = get_exchange_rates(datum)
375+
for zkratka_meny in sorted(kurzy):
376+
# Pokud nemáme žádné měny, nebo tato měna byla zadaná …
377+
if not mena or zkratka_meny in mena:
378+
# … tak převedeme částku a vypíšeme ji.
379+
prevedeno = castka * kurzy[zkratka_meny]
380+
click.echo(f"{castka} CZK = {prevedeno} {zkratka_meny}")
381+
382+
383+
if __name__ == "__main__":
384+
cnb()
385+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
title: Requests a Click
2+
style: md
3+
attribution: Miro Hrončok, Petr Viktorin, Lubomír Sedlář a další, 2016-2019.
4+
license: cc-by-sa-40

0 commit comments

Comments
 (0)