Flask kniha aneb jak dostat Python na web Ondrej Sika 3. 3. 2015 1
Flask kniha aneb jak dostat Python na web Ondrej Sika ondrej@ondrejsika.com http://ondrejsika.com Domovska stranka knihy je https://ondrejsika.com/books/flask-kniha 2
Venovano Karoline, Barbore a Renate Sikove 3
Contents 1 Uvod 6 1.1 Predmluva.................... 6 1.2 Co je to Flask?................. 6 2 Instalace 8 3 Zakladni struktura 9 3.1 Hello world................... 9 3.2 Zakladni struktura............... 10 3.3 Debug...................... 10 4 Pohledy 12 4.1 Request objekt................. 12 4.2 Request methods................ 13 5 HTML 15 5.1 Syntaxe..................... 15 5.2 Zakladni formatovaci prvky.......... 15 5.3 Odkazy...................... 16 5.4 Formulare.................... 17 6 Sablony 19 6.1 Promenne.................... 19 6.1.1 Predane z pohledu........... 20 6.1.2 Definice v sablone............ 21 6.2 Vkladani (include)............... 21 6.3 Rozsirovani (extends).............. 22 6.4 Podminky.................... 23 6.5 Cykly...................... 23 7 Data od uzivatele 25 7.1 Parametry v URL................ 25 4
7.2 GET....................... 25 7.3 POST...................... 26 8 Databaze (Dataset) 27 8.1 Pripojeni k databazy.............. 27 8.1.1 Sqlite.................. 28 8.1.2 PostgreSQL............... 28 8.1.3 MySQL................. 28 8.2 Ukladani dat.................. 29 8.3 Nacitani dat................... 29 8.4 Informace o databazi.............. 30 9 Session 31 10 Deployment 32 10.1 Deploy2..................... 32 10.2 Cloud...................... 32 10.2.1 Heroku.................. 32 10.2.2 Openshift................ 32 10.3 Rucne...................... 32 10.3.1 Gunicorn................ 32 10.3.2 Supervisor................ 33 10.3.3 Nginx.................. 34 11 Shrnurti 35 5
1 Uvod 1.1 Predmluva Tato kniha je moji druhou knihou pro zacinajici programatory. V te prvni (Python kniha, http://url.os1.cz/pythonkniha) jsem popisoval uplne zacatky programovani v Pythonu. Tato kniha navazuje na predchozi, tim ze vse co umime chceme dat na web a umoznit uzivateli jednoduchou interakci. Kniha si neklade za cil naucit vas tvorit webove stranky (na to jich je spousta), ale ukazat ze programovani webu v Pythonu neni tak slozite a za PHP neni jedinou cestou jak delat weby. V knize je hodne ukazek kodu, repozitar s temito ukazkami je: http://url.os1.cz/flask-kniha-code-examples Odkaz najdete i na homepage knihy. Preji aby cteni bylo prijemne a posunulo vase znalosti v programovani webovych aplikaci. 1.2 Co je to Flask? Flask je micro framwork pro tvorbu webu pro Python. Framework zamena ze je to soubor funkci, ktere nam zjednodusuji psani vlastnio kodu. Mohli bychom psat webovy server sami, ale zabralo by nam to hodne casu, museli bychom se hodne veci naucit a vysledek 6
by nebyl lepsi nez ted. Existuji i jine frameworky pro tvorbu webu v Pythonu, nejznamejsi je z nich Django, ktere je ovsem o hodne slozitejsi, ale umi toho vic. Flask ma vyhody ze je velmi jednoduchy a vsechno se na nem da hezky posat a vysvetlit. Django ma podobnou strukturu, ale je mnohem komplikovanejsi pro zacatecnika zbytecne. Domovska stranka Flasku je http://flask.pocoo.org, kde najdete veskerou dokumentaci a podrobne priklady pouziti. Vyhodou Flasku o proti Djangu je i start nove aplikace rychlejsi a jednodusi. Zatimco django ma nekolik adresaru a musi se nastavit pomerne hodne hodnot nez se da vubec spustit, Flask aplikace je v jednom souboru a nic zvlastniho se nastavovat nemusi. 7
2 Instalace Predpokladejme ze Python a virtualenv mame nainstalovany. 8
3 Zakladni struktura 3.1 Hello world Ukazeme si tradicne prvni ukazkovou aplikaci ktera zobrazi v prohlizeci text Hello world. Priklad 01: Soubor: app.py from flask import Flask app = Flask( name ) @app.route( / ) def view(): return Hello world if name == main : app.run() Je takova konvence, ze hlavni soubor flask aplikace se jmenuje app.py, ja se ji budu drzet. Tento kod spustime jako standartni Python program: python3 app.py Program se spusti a vypise do terminalu: * Running on http://127.0.0.1:5000/ To znamena, ze je vse OK a mame si v prohlizeci otevrit url http://127.0.0.1:5000/. Kdyz se na ni podivame, uvidime na strance text Hello world. 9
3.2 Zakladni struktura Na tomto prikladu si muzeme ukazat jednotlive casti kodu a trosku si je popsat. Prvni radek je pouze import flasku. Na druhem se inicializuje Flask WSGI objekt, coz je neco jako kostra cele aplikace. Na tu se poto pripinaji ruzne pohledy (views). Prave takovy pohled je v treti casti kodu. Dekorator app.route aby se dana funkce vykonala pri dotazu na konkretni url, v nasem pripada na /. Posledni cast je jenom prikaz spuseni serveru, pokud soubor chceme spustit. Pokud jej budeme importovat napriklad do Gunicornu (WSGI server), kod se neprovede a server se nespusti. O to se pak stara Gunicorn. 3.3 Debug Pokud mam aplikaci v produkcnim prostredi, nechci aby pri chybe vypisovala chybove hlasky a uz vubec ne aby pri chybe byla dostupna debug konzole. Proto je tato funkcionalita by default vypnuta. Ale pri vyvoji se hodi presny popis chyb i konzole. Pro zapnuti debug rezimu staci pridat k app attribut debug = True. if name == main : app.debug = True app.run() Ukazka 02 - aplikace v debug modu s chybou Je lepsi to dat az za podminku lokalniho spousteni, aby tuto informaci nedostaval externi WSGI serve, napriklad Guni- 10
corn. Dale se take debug mod hodi, protoze pri editaci zdrojoveho souboru automaticky restartuje server, coz je velmi prijemne. 11
4 Pohledy Pohledy (neboli views) jsou funkce ktere se volaji pri pristupu na danou url ktera je definovana v dekoratoru app.route. Pohledu muze byt v aplikaci neomezene, kazde musi mit ovsem unikatni pojmenovani funkce. Priklad 03: from flask import Flask app = Flask( name ) @app.route( / ) def first_view(): return First @app.route( /second/ ) def second_view(): return Second if name == main : app.debug = True app.run() 4.1 Request objekt Request objekt obsahuje ruzna data z requestu, muzete k nim pristupovat prez objekt request s ruznymi atributy. Request objekt musime nejdrive naimportovat: from flask import request Potom se z pohledu muzete ptat na jeho atributy: 12
@app.route( / ) def some_view(): return "url: %s, path: %s" % (request.url, request.path Ted si popiseme nejake zakladni atributy requestu, podrobnejsi popis a popis vsech atributu naleznete v dokumentaci. form - obsahuje POST data (z formulare) args - obahuje GET data (z url) headers - obsahuje http hlavicky data - obsahuje telo requestu (vhodne pro RPC) files - obsahuje uploadovane soubory method - obsahuje metodu requestu (POST, GET etc.) path - obsahuje cestu za domenou (/about-me/) url - obsahuje celou url (https://ondrejsika.com/aboutme) 4.2 Request methods Pokud chceme vyuzivat jine requesty nez GET, musime to povolit v app.route. Musime je definovat v atributu methods. @app.route( /, methods=[ GET, POST ]) def first_view(): return First @app.route( /second/, methods=[ POST ]) def second_view(): return Second 13
Na prvni view se dostaneme objema typy requestu, na druhy pouze metodou POST. 14
5 HTML V teto kapitole chci popsat jen par prvku, ktere se pouzivaji pro interakci s aplikaci, odkazy, formulare,... Nechci vysvetlovat HTML detailne, navodu a knizek je plny internet. Jsou i v cestine. 5.1 Syntaxe Syntaxe vychazi z XML, to znamena ze jednotlive tagy jsou uzavreny v <a >. Existuji 2 druhy tagu, parove a neparove. Prikladem paroveho tagu je nadpis: <h1>nadpis</h1> Prikladem neparoveho tagu je zalomeni radky: <br> 5.2 Zakladni formatovaci prvky Aby nase aplikace mela alespon nejakou strukturu, popiseme si par zakladnich formatovacich prvku. Nadpisy Mame nadpisy h1 az h6, h1 je nejvissi uroven a h6 nejnizsi. 15
<h1>nadpis</h1> <h2>nadpis</h2> <h3>nadpis</h3> <h4>nadpis</h4> <h5>nadpis</h5> <h6>nadpis</h6> Text Odstavec textu je uzavren v tagu p, zalomeni radky se dela momoci tagu br. <p>nejaky text<br>dalsi radka</p> 5.3 Odkazy Odkazy se pouzivaji pro pohyb mezi strankami (pohledy). Obcas mohou udelat nejakou akci nebo neco nastavit. Odkazy pouzivaji parovy tag a s jednim povinym parametrem href, ktery ukazuje kam se ma uzivatel presunout po kliknuti na odkaz. Nejaky text <a href="/o-nas/">odkaz</a> Tato ukazka odkazuje na stranku /o-nas/. Aby straka spravne fungovala, musi mit pohled se spravnou routou. @app.route( /o-nas/ ) def o_nas_view():... 16
5.4 Formulare Formulare primarne slouzi k zadavani vstupnich dat od uzivatele. Maji 2 rezimy, jak posilaji data serveru GET a POST. GET parametry jsou predany v url a post parametry v tele requestu. GET parametry se pouzivaji pro formulare ktere nastavuji nejake hodnoty treba ve filtrovani a POST se pouziva pro vetsinu ostatnich formularu, vcetne prihlasovani. Formular se sklada z tagu form a tagu vstupnich poli. Jsou to tagy input, textarea, select. Select ma v sobe jeste opton tagy. Jednoduchy formular co podesle nejakou hodnotu pocet v GET parametru. Tag form ma par atributu, ktere je dobre znat. action - stranku na kterou posila pozadavek, pokud neni vyplnen pouzije soucasnou. method - vyber poslani parametru metodou GET nebo POST. Pokud neni nastaven, pouzije defaultne GET. <form action="/soucet/"> <input type="text" name="prvni"> <input type="text" name="druhy"> <input type="submit" value="odeslat"> </form> Kdyz se trochu podivame na tento kratky formular, vidime ze se posle na url /soucet/ a parametry budou prvni a druhy. Input type submit je tlacitko pro odeslani. 17
Kdyz vime ze se parametry predaji momoci GET, pokud vyplnime cisla 1 a 2, url bude vypadat takto: /soucet/?prvni=1&druhy=2. I inputy mohou mit atribut value, ten nastavuje defaultni hodnotu. Pokud jej nevyplnime, pole zustanou prazdne. Toto jsou zaklady prace s formularema, vice informaci urcite najdete na internetu. 18
6 Sablony Sablony jsou HTML soubory ktere jsou renderovane sablonovacim jazykem Jinja2 (od stejnych autoru jako flask). Sablony piseme do zvlastniho adresare templates, tam je hleda funkce render template, ktera zajistuje renderovani Jinja2. Priklad 04: Soubor: app.py from flask import Flask app = Flask( name ) @app.route( / ) def view(): return flask.render_template( index.html ) if name == main : app.debug = True app.run() Soubor: templates/index.html Hello from template Se sablonami Jinja2 umi delat kouzla, my si ukazeme jenom nektere zakladni z nich. 6.1 Promenne Zakladni funkcionalita, ktera je nezbytna pro praci s sablonami, jsou promenne. Promenne se zapisuji do dvojtych slozenych zavorek. Takto se do template vlozi promenna: 19
{{ promenna }} 6.1.1 Predane z pohledu Do sablony muzeme dosazovat promenne nejcasteji z pohledu a pracovat s nimi. a nastavuji se funkci render template jako kwargs. Priklad 05: Soubor: app.py @app.route( / ) def view(): now = datetime.datetime.now() return flask.render_template( index.html, now=now) Soubor: templates/index.html Now is {{ now }} Promennych muze byt dosazeno samozrejmne vice: @app.route( / ) def view(): now = datetime.datetime.now() timestamp = time.time() return flask.render_template( index.html, now=now, timestamp=timestamp) Soubor: templates/index.html Now is {{ now }}, timestamp is {{ timestamp }}. 20
6.1.2 Definice v sablone Promennou muzete take zadefinovat primo v sablone. Dela se to velmi jednoduse, pomoci tagu set. Soubor: templates/index.html {% set name = Ondrej %} Hello {{ name }}! a do browseru vypise Hello Ondrej! 6.2 Vkladani (include) Pokud do sablony chceme vlozit jiny soubor, muzeme pouzit tag include. Soubor: templates/index.html Hello from template {% include footer.html %} Soubor: templates/footer.html <p>my awesome site</p> V prohlizeci pak uvidite: Hello from template My awesome site Do includovane sablony se predavaji vsechny promenne, takze je muzete pouzivat. Pokud promennou nemate definovanou, muzete ji zadefinovat primo v include tagu. Soubor: templates/index.html Hello from template 21
{% include footer.html with author= Ondrej Sika %} Soubor: templates/footer.html <p>my awesome site ({{ author }})</p> V prohlizeci pak uvidite: Hello from template My awesome site (Ondrej Sika) 6.3 Rozsirovani (extends) Extends se v praxi pouziva mnohem vice nez include. Rozsirovani funguje obracene nez include. Nami definovane bloky z aktualni sablony vlozi na bloky sablony definovane v tagu extends. Priklad s patickou muzeme prepsat s pouzitim extends. Tag extends musi byt jako prvni v sablone. Definice bloku je mezi {% block %} a {% endblock %}. Soubor: templates/index.html {% extends base.html %} {% block content %} Hello from template {% endblock content %} Soubor: templates/base.html {% block content %}{% endblock %} <p>my awesome site</p> V prohlizeci pak uvidite to same jako s pouzitim include: Hello from template My awesome site 22
Na tomto kratkem prikladu se to zda zbytecne. Vyhodou je ze pokud chete definvat vice mist kam neco vkladat dynamicky a nekde mit zakladni prvky, staci mit pouze jeden base.html a vice blocku, s pouzitim include by to bylo velmi obtizne a museli by jste pouzit vice nez 1 vlozeny soubor. 6.4 Podminky V sablonach muzeme vyuzivat podminky IF. Jeji syntaxe je velmi intuitivni. {% if variable %} Value is {{ variable }} {% endif %} Nebo muzeme pouzit jeste pouzit block else. Potom to vypada takthe. {% if variable %} Value is {{ variable }} {% else %} No value {% endif %} 6.5 Cykly V sablone pouzivame pouze for cyklus. {% for element in iterable %} <p>prvek je "{{ element }}"</p> {% endif %} 23
Pokud chceme v cyklu zjistit, kolikata je to iterace slouzi nam k tomu promenna loop.index. {% for element in iterable %} <p>prvek je {{ loop.index }} "{{ element }}"</p> {% endif %} Pokud mame v promenne iterable seznam [ a, b, c ], potom prvni cyklus vypise: Prvek je "a" Prvek je "b" Prvek je "c" A druhy vypise: Prvek 1 je "a" Prvek 2 je "b" Prvek 3 je "c" 24
7 Data od uzivatele Uz umime delat mozne i nemozne veci se sablonami, udelat vice stranek, ale zatim neumime komunikovat s uzivatelem, respektive neumime ziskad od neho nejaka dynamicka data. A k tomu je tato kapitola. 7.1 Parametry v URL Pokud chceme mit elegani url bez nejakych otazniku a rovnase, muzeme definovat promenne jako soucast url. Toto je podle me (i podle Google) lepsi url /article/new-article/ nez /article/?slug=new-article. Jak to udelat? Do definice cesty v app route musime pridat parametr promenne a do funkce ktera definuje pohled jeden argument. Vypada to takto: @app.route( /article/<slug>/ ) def article_view(slug): #... 7.2 GET Pokud mame hodne ruznych atributu, kde nezalezi na poradi a ani na tom jak bude vypadat url, je vhodnejsi pouzit GET parametry. URL pak muze vypadat takto /api/?version=1&key=time K temto atributum pristupujeme ve Flasku prez promennou flask.request.args. Musime ji nejdriv importovat. 25
from flask import request @app.route( /api/ ) def api_view(): version = request.args.get( version ) key = request.args.get( key ) #... 7.3 POST U POST atributu je to podobne. POST atributy jsou vetsinou z nejakeho formulare, proto se k nim ve Flasku pristupujeme prez promenou request.post. Nezapomente na to ze v zakladu je v seznamu povolenych meod pouze GET, pokud chete pohled pouzivat s metodou POST, musite ji v hlavicce povolit. from flask import request @app.route( /form/, methods=[ POST ]) def form_view(): name = request.form.get( name ) email = request.form.get( email ) #... 26
8 Databaze (Dataset) Pristup do databaze prez sql je nad ramec teto knihy, proto popisu pristup k datazi prez velmi jednoduche ORM datasets. Jak auto popisuje Database for lazy people. Pristup je velmi jednoduchy a datasets umi komunikovat s SQLite, PostgreSQL i MySQL. 8.1 Pripojeni k databazy Pripojeni se vytvori funkci dataset.connect a jeji parametr je connection string. Obecny connection string ma tuto podobu: dialect://user:password@host/dbname Jednotlive casti znamenaji toto: dialect - druh databaze (sqlite, postgres,...) user, password - prihlasovaci udaje k databazi host - stroj na kterem databaze bezi dbname - nazev databaze Ukazka pripojeni k SQLite: import dataset db = dataset.connect( sqlite:///:memory: ) Tato ukazka je pripojeni k SQLite, databazi v souboru, ktera nevytvori databazovi soubor, ale zustane v pameti po dobu 27
behu prrogramu. Toto pripojeni k databazi budu pouzivat u vetsiny ukazek. 8.1.1 Sqlite SQLite je v Pythonu nativne podporovana, proto neni treba instalovat zadne knihovny. Pripojeni ke klasickemu SQLite databazovemu souboru vypada takto. db = dataset.connect( sqlite://test.sqlite3 ) Pripojeni do databaze v pameti je predchozi priklad. 8.1.2 PostgreSQL Pro pouziti postgresu je nutne mit naistalovanou knihovnu psycopg2. Nainstaluje ji prikazem: pip3 install psycopg2 Connectionstring potom vypada takto: db = dataset.connect( postgresql://postgres:pg@localhost:54 8.1.3 MySQL Pouziti MySQL je stejne jako Postgresu. Take je potreba doinstalovat knihovnu na praci s MySQL mysqldb. pip3 install mysql-db Ukazka mysql connection stringu: db = dataset.connect( mysql://root:asdf@localhost/test ) 28
8.2 Ukladani dat Data se uladaji do db ve formatu slovniku. Struktura se vytvari dynamicky, neni treba predem definovat. To je velmi prijemne. Pokud chceme pridat uzivatele do tabulky users (pokud tabulka neexistuje tak si ji dataset vytvori), staci zavolat na tabulku metodu insert se slovnikem s daty. table = db[ user ] table.insert({ name : sika, country : CZ }) table.insert({ name : karel, country : CZ }) Pokud budeme chtit pridat vice dat, dataset sam prida dalsi sloupecky. table.inser({ name : novak, country : SK, pass : hes Pokud v tabulce jsou nejaka data, nove sloupecky budou null. 8.3 Nacitani dat Do promenne users nacte vsechny radky z DB. users = db[ user ].all() Nebo se da pouzit bez te metody all. for user in db[ user ]: print(user[ name ]) Nacte seznam vsech ceskych uzivatelu. czech_users = table.find(country= CZ ) Vybere jeden radek. 29
sika = table.find_one(name= sika ) Vybere unikatni zaznamy podle zeme. Pro kazdy stat bude vracen jeden uzivatel. db[ user ].distinct( country ) 8.4 Informace o databazi Tabulky v databazi >>> print(db.tables) [u user ] Sloupecky tabulky >>> print(db[ user ].columns) [u id, u country, u age, u name, u gender ] Pocet radku v tabulce >>> print(len(db[ user ])) 2 30
9 Session Pro pouzivani sessions je dulezite pro jejich bezpecnost mit nastaveny secret key. Je dobre ho nastavit hned za inicializaci Flasku. app = Flask( name ) app.secret_key = ultra safe key 31
10 Deployment 10.1 Deploy2 10.2 Cloud 10.2.1 Heroku 10.2.2 Openshift 10.3 Rucne 10.3.1 Gunicorn Gunicorn je asynchroni WSGI server, ktery je navrzen na produkcni provoz. Muzeme samozrejme pouzit app.run(), ale vetsi zatez by tento server nezvladnul. Gunicorn nainstalujeme prikazem: apt-get install gunicorn Gunicorn ma hodne moznosti behu, ale nam bude stacit zakladni nastaveni. Ma jako argument nazev souboru:wsgi app. Pro nas to je app:app. Potom je pro nas dulezity -b, to je parametr ktery nastavuje kde posloucha muzeme pouzit unix socket nebo host a port. Ukazka spusteni host a port: gunicorn app:app -b 127.0.0.0:8000 gunicorn app:app -b unix:///tmp/testapp.sock 32
K unix socketu se pote pripoji nginx, ktery bude nase data dale servirovat svetu. 10.3.2 Supervisor Supervizor spravuje ruzne bezici procesy v systemu, zarizuje spusteni procesu po startu systemu atd. Supervisor ma konfiguracni soubory v /etc/supervisor a vlastni konfiguracni soubory piseme do /etc/supervisor/conf.d/ s priponou.conf. V konfiguracnim souboru je jmeno aplikace, prikaz kterym se spousti a adresar ze ktereho se spouzti. To nam pro zacatek staci. Ukazka supervisor konfigurace pro gunicorn s unix socketem: Soubor /etc/supervisor/conf.d/testapp.cz.conf : [application:testapp]... Po pridani konfigurace musime prenacist supervisor, dela se to prikazem: supervisor reload Za par vtenin aplikace nabootuje. Pokud chcete aplikaci vypnout, zapnout nebo restartovat, pouzijte tyto prikazy: supervisor stop testapp supervisor start testapp supervisor restart testapp Pokud aplikaci zmenite, nebo nasadite novou verzi, je nutne potom provest jeji restart. 33
10.3.3 Nginx Nginx je webovy server, ktery funguje jako proxy server k nasemu aplikacnimu serveru (gunicorn). Take se stara o staticke soubory. Je napsany v C a proto je hodne rychly. Nginx nainstalujeme prikazem: apt-get install nginx Konfiguracni soubory jsou v /etc/nginx. Definice jednotlivich serveru (mysleno aplikace) se nachazi v /etc/nginx/sitesavailable. Vytvorte tam soubor treba podle domeny: Soubor /etc/nginx/sites-available/testapp.cz: server { listen 80; #... } Pokud chcete tuto aplikaci spustit, musite pridat symlink do adresare /etc/nginx/sites-enabled. Link vytvorime takto: ln -s /etc/nginx/sites-available/testapp.cz \ /etc/nginx/sites-enabled/ Potom staci jen restartovat nginx prikazem: service nginx restart 34
11 Shrnurti Doufam, ze vam tato kniha dala potrebne rady k tomu, jak si zprovoznit svuj prvni web na Pythonu. Doufam, ze se vam libila. Prosim, napiste mi vas nazor a pripominky na ondrej@ondrejsika.com a neco tweetnete :) Za zpetnou vazbu budu velmi rad. 35