Osan 1 etapit
  • Olet asentanut kurssilla tarvittavat työvälineet.
  • Olet lukenut materiaalissa olevan lyhyen Python-oppaan ja kokeillut siinä olevia esimerkkejä.
  • Olet tutustunut web-sovellusten toimintaan ja toteuttanut tämän sivun ohjeistusta mukaillen ensimmäisen Flaskia käyttävän web-sovelluksesi.
    HUOM! Tarkoituksena on harjoitella sovelluksen toteuttamisen alkeita, älä kopioi tätä palautettavaan repositorioon!
  • Olet luonut tämän sivun ohjeistusta noudattaen projektille sivun Herokuun. Tämän osan tarkoituksena on harjoitella työkalujen käyttöä, ei vielä aloittaa omaa projektia.

Johdanto

Tämän osan alussa tarkastellaan web-sovelluksia, jonka jälkeen harjoitellaan oman sovelluksen tekemistä. Tämä osa on puhtaasti työkalujen käytön harjoittelua ja opastusta. Oma projekti aloitetaan seuraavassa osassa.

Kurssimateriaalin esimerkkisovelluksia ei kannata seurata kirjaimellisesti omaa työtä tehdessä. Esimerkkisovellus ei kokonaisuutena kata kurssin vaatimuksia, vaan on tarkoitettu esimerkiksi jonka kautta materiaalia voi soveltaa omaan työhönsä.

Web-sovellusten perusteet

Web-sovellukset koostuvat selain- ja palvelinpuolesta. Käyttäjä käyttää selainohjelmistoa (esim. Chrome, kännykän selain, pädin selain, Kindlen selain, ...), joka tekee käyttäjän toimien perusteella pyyntöjä verkossa sijaitsevalle palvelimelle. Kun palvelin vastaanottaa pyynnön, se käsittelee pyynnön ja rakentaa vastauksen. Vastaus voi sisältää esimerkiksi web-sivun HTML-koodia tai jossain muussa muodossa olevaa tietoa.

Palvelin on kone aivan samalla tavalla kuin kännykkä, kannettava tietokone, pädi ym on kone. Kumpikin on kiinni verkossa, ja kummallakin on (ainakin välillisesti) koneen tunnistava IP-osoite. Kun käyttäjä tekee selainohjelmistolla pyynnön, pyyntö ohjautuu käyttäjän koneen verkkoyhteyden kautta palvelimen koneelle, jossa palvelinohjelmisto vastanottaa pyynnön ja käsittelee sen. Vaikka käyttäjä hakee tyypillisesti tekstimuotoisia osoitteita kuten https://www.helsinki.fi/fi, ei koneet näistä ymmärrä -- verkossa on erilliset palvelut tekstimuotoisten osoitteiden muuntamiseksi IP-osoitteiksi.

Selaimen tekemät pyynnöt noudattavat HTTP-protokollaa, joka on määritelty standardi pyyntöjen tekemiseen sekä niiden käsittelyyn. Resurssien -- eli tietyn osoitteen polkujen -- hakeminen tapahtuu HTTP-protokollan GET-tyyppisillä pyynnöillä, kun taas tiedon lähettäminen tapahtuu HTTP-protokollan POST-tyyppisillä pyynnöillä.

Selaimet tarjoavat tyypillisesti mahdollisuuden haettavien resurssien tarkasteluun. Esimerkiksi Google Chromessa selaintyövälineet saa auki painamalla näppäinyhdistelmää Ctrl+Shift+I tai valitsemalla Chromen valikosta Developer Tools. Selaimen työvälineet näyttävät seuraavalta.

 

Kun selaimeen kirjoittaa osoitteen -- alla olevassa esimerkissä https://www.helsinki.fi/fi -- tekee selain GET-tyyppisen pyynnön osoitteeseen eli hakee osoitteessa olevalta palvelimelta polun osoittamaa resurssia.

Kun palvelin palauttaa resurssin, tyypillisesti HTML-kielisen sivun, voi resurssissa olla linkkejä myös toisiin resursseihin. Selaimet on ohjelmointu siten, että esimerkiksi HTML-kielisen sivun resurssit haetaan automaattisesti. Yhtä sivua haettaessa saatetaan noutaakin todellisuudessa kymmeniä ellei satoja resursseja.

 

Yllä olevassa kuvassa osoitteessa https://www.helsinki.fi/fi olevan sivun lataus on vielä kesken. Sivua rakennettaessa on tehty 101 pyyntöä, ja tietoa on siirretty 1.6 megatavua.

Web-sovellusten kehitys on tapana jakaa selainohjelmistojen kehitykseen ja palvelinohjelmistojen kehitykseen, vaikkakin nykyään molemmat kattava fullstack-kehitys on yhä suositumpaa.

Selainohjelmistoja ja käyttöliittymää kehitettäessä painotetaan rakenteen, ulkoasun ja toiminnallisuuden erottamista toisistaan. Karkeasti voidaan sanoa, että selaimessa näkyvän sivun sisältö ja rakenne määritellään HTML-tiedostoilla, ulkoasu CSS-tiedostoilla ja toiminnallisuus JavaScript-tiedostoilla.

Palvelinpuolen toiminnallisuutta toteutettaessa keskitytään tyypillisesti selainohjelmiston tarvitsevan "APIn" suunnitteluun ja toteutukseen, sivujen muodostamiseen selainohjelmistoa varten, datan tallentamiseen ja käsittelyyn, sekä sellaisten laskentaoperaatioiden toteuttamiseen, joita selainohjelmistossa ei kannata tai voida tehdä.

Web-kehitys Flask-kirjastolla

Harjoitustyössä käytetään Pythonin Flask-kirjastoa web-sovelluksen toiminnallisuuden toteuttamiseen. Tutustutaan lyhyesti pienen Flask-sovelluksen toteuttamiseen.

Virtuaaliympäristön luominen

Luodaan ensin hakemisto sovellustamme varten ja luodaan sen sisälle Python-virtuaaliympäristö komennolla python3 -m venv venv. Kutsutaan sovellusta nimellä demo.

$ ls
$ mkdir demo
$ ls
demo
$ cd demo
~/demo$ python3 -m venv venv
~/demo$ ls
venv
$ 

Nyt luomallamme virtuaaliympäristöllä on kansio venv. Tarkastellaan sen sisältöä.

~/demo$ ls venv
bin  include  lib  lib64  pyvenv.cfg  share
$

Kansiossa on kansiot bin, include, lib ja share. Kansio lib64 on todellisuudessa linkki kansioon lib. Tiedosto pyvenv.cfg sisältää virtuaaliympäristön asetukset.

Kun haluamme ottaa luodun virtuaalisen Python-ympäristön käyttöön, aktivoimme sen komennolla source venv/bin/activate. Muuttuneesta komentokehoitteesta näkee että virtuaaliympäristö on aktiivinen.

~/demo$ source venv/bin/activate
(venv) ~/demo$

Haluamme lisätä käyttöömme Flask-kirjaston. Tämä onnistuu kansiossa bin olevalla pip-kemonnolla.

(venv) ~/demo$ pip install Flask
....
....
You are using pip version 8.1.1, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
(venv) ~/demo$ 

Flask-kirjaston asentamisen yhteydessä saimme tiedon siitä, että käytössä oleva pip on vanhahko. Päivitetään se samalla.

(venv) ~/demo$ pip install --upgrade pip
Collecting pip
  Using cached pip-9.0.1-py2.py3-none-any.whl
    Installing collected packages: pip
Found existing installation: pip 8.1.1
  Uninstalling pip-8.1.1:
    Successfully uninstalled pip-8.1.1
Successfully installed pip-9.0.1
(venv) ~/demo$ ls venv
bin  include  lib  lib64  pip-selfcheck.json  pyvenv.cfg  share
(venv) ~/demo$

Käytössämme on nyt Flask, jonka lisäksi versionhallinnassa käytetyn pipin versiota on päivitetty.

Ensimmäinen Flask-sovellus

Flask-kirjaston etusivulla on seuraava lähdekoodi.

from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World!"

Luodaan uusi tiedosto hello.py edellä luotuun demo-kansioomme ja kopioidaan esimerkkilähdekoodi tiedoston hello.py sisällöksi. Suoritetaan tämän jälkeen tiedoston hello.py lähdekoodi.

(venv) ~/demo$ ls
hello.py  venv
(venv) ~/demo$ python3 hello.py
(venv) ~/demo$

Mitään ei tapahtunut. Huraa?

Flask-sovellusten käynnistäminen suoraan tiedostosta vaatii sovellukseen vielä ohjeistuksen komentoriviltä käynnistämiseen. Lisätään hello.py-tiedostoon vielä muutama rivi, jotka ohjeistavat sovelluksen käynnistämiseen kun se suoritetaan komentoriviltä.

from flask import Flask
app = Flask(__name__)
  
@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

Kokeillaan sovelluksen käynnistämistä uudestaan.

(venv) ~/demo$ ls
hello.py  venv
(venv) ~/demo$ python3 hello.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Sovellus on nyt käynnissä. Kun menemme selaimella osoitteeseen http://127.0.0.1:5000/, näemme seuraavanlaisen sivun.

 

Sovellus on käynnissä! Huomaamme, että komentoriville on ilmestynyt muutamia lisärivejä. Rivit kuvaavat ovat palvelinohjelmiston logirivejä ja ne kertovat palvelimelle tulleista pyynnöistä.

Palvelimelle on tullut oikeastaan kaksi pyyntöä, vaikka haimme sivua vain kerran. Ensimmäinen pyyntö "GET / HTTP/1.1" haki tietoa sovelluksen juuripolusta. Tämä on oikeastaan juuri se, mitä sovelluksemme metodi hello() käsittelee. Toinen pyyntö "GET /favicon.ico HTTP/1.1" haki sivulle ikonia -- palvelimelta ei tällaista löytynyt.

(venv) ~/demo$ ls
hello.py  venv
(venv) ~/demo$ python3 hello.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [29/Feb/2019 18:07:31] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [29/Feb/2019 18:07:31] "GET /favicon.ico HTTP/1.1" 404 -

Palvelimen sammuttaminen onnistuu komentoriviltä painamalla CTRL+C, eli näppäimiä ctrl ja c samanaikaisesti.

Debug-moodi

Lähdekooditiedoston hello.py suorittaminen käynnistää palvelinohjelmiston.

(venv) ~/demo$ ls
hello.py  venv
(venv) ~/demo$ python3 hello.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

Kun sovellus on käynnissä, muokkaukset tiedostoon hello.py eivät näy kun tietoa haetaan selaimella uudestaan. Ohjelmistokehityksen nopeuden kannalta olisi kuitenkin hyödyllistä, jos muutoksia voisi tarkastella ilman että palvelinta käynnistellään jatkuvasti uudelleen.

Flaskille voi määritellä ns. debug-moodin, mikä muuntaa sovelluksen toimintaa muunmuassa siten, että sovelluksen uudelleenkäynnistäminen muutosten yhteydessä ei ole tarpeellista. Muokataan sovelluksen käynnistävää tiedostoa hello.py siten, että komento app.run() saa parametrinaan tiedon debug-tilasta. Tämä onnistuu seuraavasti.

from flask import Flask
app = Flask(__name__)
  
@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run(debug=True)

Kun sovelluksen sammuttaa ja käynnistää uudelleen, selaimella sivu näyttää yhä seuraavalta.

 

Terminaalissa tosin tulostus on hieman erilainen.

(venv) ~/demo$ ls
hello.py  venv
(venv) ~/demo$ python3 hello.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 231-450-049

Muokataan tiedostoa hello.py siten, että sivun pitäisi näyttää teksti "Hei maailma!".

from flask import Flask
app = Flask(__name__)
  
@app.route("/")
def hello():
    return "Hei maailma!"

if __name__ == "__main__":
    app.run(debug=True)

Kun tallennamme tiedoston, huomaamme, että terminaalissa näkyvä tulostus päivittyy.

(venv) ~/demo$ ls
hello.py  venv
(venv) ~/demo$ python3 hello.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 231-450-049
* Detected change in '/polku/demo/hello.py', reloading
* Restarting with stat
* Debugger is active!
* Debugger PIN: 231-450-049

Kun sivun lataa selaimessa uudestaan, sivulla näkyy nyt päivitetty teksti.

 

Debug-tilasta on muitakin hyötyjä. Pääset esimerkiksi tarkastelemaan selaimesta sovelluksen tilaa virhetilanteen yhteydessä. Tämän takia on myös hyvä varmistaa, ettei debug-tila ole koskaan päällä kun sovellus viedään tuotantoon -- debug-tila mahdollistaa myös mm. tietokannan salasanojen ym tarkastelun. Ei hyvä asia tuotannossa.

HTML-näkymien luominen

Tutustutaan seuraavaksi HTML-kielellä toteutettavien näkymien luomiseen. Mikäli HTML-kieli ei ole tuttu, käy läpi ainakin osoitteessa https://developer.mozilla.org/en-US/docs/Learn/HTML oleva opas.

Jatketaan aiemmin luodun sovelluksen parissa.

(venv) ~/demo$ ls
hello.py  venv

Luodaan HTML-näkymiä varten kansio nimeltä templates ja mennään kansioon.

(venv) ~/demo$ mkdir templates
(venv) ~/demo$ cd templates
(venv) ~/demo/templates$ ls
(venv) ~/demo/templates$

Kansio on toistaiseksi tyhjä. Luodaan kansioon kaksi tiedostoa layout.html ja index.html. Tiedostoon layout.html tulee sivun yleinen rakenne, menuvalikko ym. Tiedostoon index.html taas sivun etusivun sisältö.

Asetetaan sivun layout.html sisällöksi seuraava.

  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <title>Demo</title>
    </head>

    <body>
      {% block body %}
      <p>
	Tähän tuodaan sisältö muualta.
      </p>
      {% endblock %}
    </body>
  </html>

Ja sivun index.html sisällöksi seuraava.

{% extends "layout.html" %}

{% block body %}
<p>
  Hei maailma!
</p>
{% endblock %}
Jinja 2

Flask käyttää oletuksena Jinja-nimistä kirjastoa käyttäjälle näytettävien sivujen luomiseen. Jinja on hieman tietokantojen perusteista tutun Thymeleafin kaltainen kirjasto, jota käytetään HTML-sivujen luomiseen. Jinja mahdollistaa myös esimerkiksi toistolauseiden ym. käyttämisen osana HTML-näkymiä -- sivut luodaan palvelimella.

Nyt sovelluksemme kansion templates sisällön pitäisi olla seuraavanlainen.

(venv) ~/demo/templates$ ls
index.html  layout.html
(venv) ~/demo/templates$

Palataan tiedostohierarkiassa aiempaan kansioon.

(venv) ~/demo/templates$ ls
index.html  layout.html
(venv) ~/demo/templates$ cd ..
(venv) ~/demo$

Muokataan kansiossa olevaa hello.py-tiedostoa. Emme näytäkään käyttäjälle suoraan merkkijonoa Hei maailma!, vaan luomme käyttäjälle näkymän juuri luomiemme tiedostojen pohjalta.

Tarvitsemme sovelluksen käyttöön Flaskin render_template-funktion. Funktio lisätään sekä tiedoston alussa olevaan import-lauseeseen, että pyynnön käsittelevään funktioon hello, jossa kyseistä funktiota kutsutaan. Funktiolle render_template annetaan kutsuttaessa parametrina HTML-tiedoston nimi, josta käyttäjälle palautettava sivu luodaan.

from flask import Flask, render_template
app = Flask(__name__)
  
@app.route("/")
def hello():
    return render_template("index.html")

if __name__ == "__main__":
    app.run(debug=True)

Käynnistetään sovellus.

(venv) ~/demo$ ls
hello.py  templates  venv
(venv) ~/demo$ python3 hello.py 
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 231-450-049

Kun menemme osoitteeseen http://127.0.0.1:5000/, näemme seuraavanlaisen sivun.

 

Nopealla vilkaisulla mikään ei ole muuttunut. Kun tarkastelemme sivuun liittyvää lähdekoodia, huomaamme kuitenkin selkeitä muutoksia. Lähdekoodin tarkastelu onnistuu selaimessa klikkaamalla sivua oikealla hiirennäppäimellä ja valitsemalla View page source (pikanäppäin Ctrl+S).

Lähdekoodi paljastaa totuuden. Näyttää siltä, että sivun lähdekoodin runko on tullut templates-kansion tiedostosta layout.html ja sisältö tiedostosta index.html.

 

Voimme siis määritellä sivustolle ulkoasun tiedostossa layout.html. Sivukohtainen sisältö määritellään taas sivukohtaisessa tiedostossa -- esimerkiksi juurisivun sisältö löytyy tiedostosta index.html.

Tiedon lisääminen näkymään

Funktiolle render_template voidaan antaa parametreina muuttujia, joiden arvoja voidaan lisätä näkymiin. Tarkastellaan tässä muuttujien, listojen sekä olioita sisältävien listojen käsittelyä.

Näkymien luomiseen käytettävä Jinja-kirjasto etsii HTML-koodista kaksinkertaisilla aaltosuluilla rajattuja alueita sekä niiden sisällä olevaa koodia. Mikäli aaltosulkujen sisältä löytyy render_template-funktiolle parametrina annetun muuttujan nimi, aaltosulkujen paikalle vaihdetaan muuttujan arvo.

Kontrollirakenteita kuten if ja for voi käyttää Jinjassa yksinkertaisten aaltosulkujen avulla siten, että avaavaa aaltosulkua seuraa prosenttimerkki, ja lopettavaa aaltosulkua edeltää prosenttimerkki. Esimerkiksi {% for arvo in lista %}{{ arvo }}{% endfor %} kävisi jokaisen listalla olevan arvon läpi ja lisäisi sen sivulle.

Luodaan kansioon templates tiedosto demo.html, jonka avulla demonstroidaan useampaa tapaa muuttujien käyttöön. Asetetaan tiedoston sisällöksi seuraava.

  {% extends "layout.html" %}

  {% block body %}
  <p>
    Hei maailma!
  </p>
  <p>
    <!-- etsitään muuttujaa nimeltä nimi ja asetetaan sen arvo tähän -->
    {{ nimi }}
  </p>

  <ul>
    <!-- Yritetään käydä lista-nimistä tietorakennetta läpi.
	 Jokaiselle tietorakenteen alkiolle luodaan li-elementti, 
	 jonka sisälle asetetaan alkion arvo. -->
    {% for arvo in lista %}
    <li>{{ arvo }}</li>
    {% endfor %}
  </ul>

  <table>
    <tr>
      <th>Nimi</th>
    </tr>
    <!-- Yritetään käydä tietorakenne nimeltä esineet läpi. 
	 Jokaiselle esineelle luodaan oma rivi taulukkoon. 
	 Jokaista taulukon solua luodessa oletetaan, että 
	 esine on olio, ja että alkiolla on muuttuja name. 
	 Muuttujan arvo asetetaan solun arvoksi. -->
    {% for esine in esineet %}
    <tr>
      <td>{{ esine.name }}</td>
    </tr>
    {% endfor %}
  </table>

  {% endblock %}

Edellä oletetaan, että käytössä on kolme muuttujaa: nimi, lista, ja esineet. Tämän lisäksi jokaisella esineellä tulee olla muuttuja name.

Muokataan taas tiedostoa hello.py. Luodaan tiedostoon esinettä kuvaava luokka Item, jolla on oliomuuttuja name. Luodaan tiedostoon myös muuttujat nimi, lista, ja esineet ja asetetaan näihin esimerkkiä varten arvot.

Luodaan lopulta erillinen funktio, joka kuuntelee polkuun /demo tehtäviä pyyntöjä. Kun pyyntö vastaanotetaan, käyttäjälle palautetaan funktion render_template luoma sivua kuvaava merkkijono. Funktiolle render_template annetaan parametrina aiemmin luotua demosivua kuvaava demo.html sekä muuttujat nimi, lista, ja esineet. Jokaiselle muuttujalle asetetaan myös nimi kutsun funktiokutsun yhteydessä.

from flask import Flask, render_template
app = Flask(__name__)

class Item:
    def __init__(self, name):
        self.name = name

nimi = "Essi Esimerkki"

lista = [1, 1, 2, 3, 5, 8, 11]

esineet = []
esineet.append(Item("Eka"))
esineet.append(Item("Toka"))
esineet.append(Item("Kolmas"))
esineet.append(Item("Neljäs"))
  
@app.route("/")
def hello():
    return render_template("index.html")

@app.route("/demo")
def content():
    return render_template("demo.html", nimi=nimi, lista=lista, esineet=esineet)

if __name__ == "__main__":
    app.run(debug=True)

Mikäli sovellus ei ole käynnissä, käynnistetään se.

(venv) ~/demo$ python3 hello.py 
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 231-450-049

Osoitteessa http://127.0.0.1:5000/ on tuttu sivu, joka näyttää viestin "Hei maailma!". Kun menemme osoitteeseen http://127.0.0.1:5000/demo, pyyntö ohjautuu hello.py-tiedoston funktiolle nimeltä content. Näemme lopulta seuraavanlaisen sivun.

 

Sovelluksen tallentaminen ja siirto pilveen

Seuraava osuus saattaa vaikuttaa tutulta edellisestä osasta erityisesti GitHubin osalta, ja osittain ne ovatkin samanlaiset. Osassa 0 luotiin jo repositorio omaa aihetta varten. Nyt meillä onkin jo websovellus, joka tarvitsee myös tallentaa repositorioon. Sen lisäksi haluamme, että sovellus toimii muuallakin kuin omalla koneella. Käytämme tässä palvelintarjoaja Herokua, jolta saamme käyttöömme ilmaista tallennustilaa sovellustamme varten.

Älä tallenna tätä työtä samaan repositorioon, jonka teit Osassa 0. Tätä ei tarvitse myöskään palauttaa Labtooliin. Tämän osan tarkoitus on olla harjoittelua, jotta seuraavassa osassa aloitettava oma työ sujuu mallikkaasti ja helposti.

Luo harjoitustyölle github-repositorio

Luo github-tunnuksesi alle uusi repositorio. Kuten edellisessä osassa, anna repositorion nimeksi haluamasi nimi.

Siirrä sovellus Githubiin

Lisää ensin projektin kansioon .gitignore-tiedosto, johon lisätään versionhallinnasta poisjätettävät tiedostot. Emme halua, että virtuaaliympäristön sisältämät binäärit ovat versionhallinnassa, joten lisätään tiedostoon rivi "venv". Tämä tarkoittaa sitä, että kansiota venv ei lisätä versionhallintaan.

(venv) ~/demo$ echo "venv" > .gitignore
(venv) ~/demo$ cat .gitignore
venv
(venv) ~/demo$

Saat luomasi github-repositorion osoitteen selville Githubista. Tässä oletetaan, että osoite on https://github.com/avihavai/tsoha.git.

Luodaan projektikansiolle git-versionhallinta komennolla git init. Tämä luo mahdollisuuden projektin versiointiin ja luo kansioon .git-nimisen kansion.

(venv) ~/demo$ git init
(venv) ~/demo$ 

Lisätään githubissa oleva repositorio kansion paikallisen versionhallinnan etäpisteeksi. Projektin osoite löytyy githubista (se on tyypillisesti muotoa https://github.com/käyttäjätunnus/projektin-nimi.git.

(venv) ~/demo$ git remote add origin github-projektin osoite
(venv) ~/demo$ 

Lisätään tiedostot githubiin.

(venv) ~/demo$ git add .
(venv) ~/demo$ git push -u origin master
..-
(venv) ~/demo$ 

Nyt projektin lähdekoodit löytyvät githubista.

Siirrä sovellus Herokuun

Sovelluksen Herokuun siirtämistä varten sovellukselle tulee lisätä web-palvelin, jota Heroku osaa käyttää. Valitsemme käyttöön Gunicorn-palvelimen.

~/demo$ source venv/bin/activate
(venv) ~/demo$ pip install gunicorn
Collecting gunicorn
Downloading gunicorn-19.7.1-py2.py3-none-any.whl (111kB)
100% |████████████████████████████████| 112kB 2.0MB/s 
Installing collected packages: gunicorn
Successfully installed gunicorn-19.7.1
(venv) ~/demo$ 

Jäädytetään seuraavaksi projektin tämän hetkiset riippuvuudet, jotta Heroku tietää mitä riippuvuuksia tulee asentaa. Tämä onnistuu komennolla pip freeze. Komennon tulostus ohjataan tiedostoon requirements.txt. Alla tehdään sekä jäädytys että tulostetaan riippuvuudet sisältävän tiedoston requirements.txt sisältö.

(venv) ~/demo$ pip freeze > requirements.txt
(venv) ~/demo$ cat requirements.txt
click==6.7
Flask==0.12.2
gunicorn==19.7.1
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
pkg-resources==0.0.0
Werkzeug==0.14.1
(venv) ~/demo$ 

Tiedostossa oleva rivi pkg-resources==0.0.0 aiheuttaa ongelmia Herokussa, joten poistetaan se toistaiseksi. Tiedoston requirements.txt sisältö on tämän jälkeen seuraava.

(venv) ~/demo$ cat requirements.txt
click==6.7
Flask==0.12.2
gunicorn==19.7.1
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
Werkzeug==0.14.1
(venv) ~/demo$ 

Voit nyt tarvittaessa asentaa projektisi riippuvuudet requirements.txt-tiedoston avulla komennolla pip install -r requirements.txt. Jos jossain vaiheessa esimerkiksi kloonaat repositoriosi koodin toiselle koneelle ja aloitat siten työskentelyn "puhtaasta" koodista, voit asentaa riippuvuudet luomaasi uuteen virtuaaliseen ympäristöön.

Lisätään seuraavaksi projektille Procfile-niminen tiedosto. Tiedosto Procfile on Herokun odottama tiedosto, joka sisältää Herokun tarvitsemat ohjeet sovelluksen käynnistämiseen. Lisätään ohjeiksi "web: gunicorn --preload --workers 1 hello:app", eli käynnistä hello.py-tiedostosta app-sovellus web-prosessina gunicorn-palvelinta käyttäen.

(venv) ~/demo$ echo "web: gunicorn --preload --workers 1 hello:app" > Procfile
(venv) ~/demo$ cat Procfile
web: gunicorn --preload --workers 1 hello:app
(venv) ~/demo$

Luodaan tämän jälkeen sovellukselle paikka Herokuun. Tässä oletuksena on, että käytössä on Herokun käyttäjätunnus sekä Herokun työvälineet komentoriville (Heroku CLI). Paikan luominen tapahtuu komennolla heroku create, jota seuraa toivottu sovelluksen nimi. Sovelluksen nimen tulee olla uniikki, eli alla oleva tsoha-python-demo tulee vaihtaa omaa sovellusta kuvaavaksi. Mikäli nimen jättää pois, Heroku luo satunnaisen nimen sovellukselle, mikä riittää tällä kurssilla.

(venv) ~/demo$ heroku create tsoha-python-demo
Creating ⬢ tsoha-python-demo... done
https://tsoha-python-demo.herokuapp.com/ | https://git.heroku.com/tsoha-python-demo.git
(venv) ~/demo$

Nyt sovellukselle on paikka Herokussa. Lisätään vielä paikalliseen versionhallintaan tieto Herokusta.

(venv) ~/demo$ git remote add heroku https://git.heroku.com/tsoha-python-demo.git
(venv) ~/demo$ 

Ja lähetetään projekti Herokuun.

(venv) ~/demo$ git add .
(venv) ~/demo$ git commit -m "Initial commit"
(venv) ~/demo$ git push heroku master
Counting objects: 13, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (13/13), 1.79 KiB | 0 bytes/s, done.
Total 13 (delta 2), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> Python app detected
remote: -----> Installing python-3.6.4
remote: -----> Installing pip
remote: -----> Installing requirements with pip
remote:        Collecting click==6.7 (from -r /tmp/build_eb15306f4a3962d45ca30ea90e9b4cb6/requirements.txt (line 1))
remote:          Downloading click-6.7-py2.py3-none-any.whl (71kB)
remote:        Collecting Flask==0.12.2 (from -r /tmp/build_eb15306f4a3962d45ca30ea90e9b4cb6/requirements.txt (line 2))
remote:          Downloading Flask-0.12.2-py2.py3-none-any.whl (83kB)
remote:        Collecting gunicorn==19.7.1 (from -r /tmp/build_eb15306f4a3962d45ca30ea90e9b4cb6/requirements.txt (line 3))
remote:          Downloading gunicorn-19.7.1-py2.py3-none-any.whl (111kB)
remote:        Collecting itsdangerous==0.24 (from -r /tmp/build_eb15306f4a3962d45ca30ea90e9b4cb6/requirements.txt (line 4))
remote:          Downloading itsdangerous-0.24.tar.gz (46kB)
remote:        Collecting Jinja2==2.10 (from -r /tmp/build_eb15306f4a3962d45ca30ea90e9b4cb6/requirements.txt (line 5))
remote:          Downloading Jinja2-2.10-py2.py3-none-any.whl (126kB)
remote:        Collecting MarkupSafe==1.0 (from -r /tmp/build_eb15306f4a3962d45ca30ea90e9b4cb6/requirements.txt (line 6))
remote:          Downloading MarkupSafe-1.0.tar.gz
remote:        Collecting Werkzeug==0.14.1 (from -r /tmp/build_eb15306f4a3962d45ca30ea90e9b4cb6/requirements.txt (line 7))
remote:          Downloading Werkzeug-0.14.1-py2.py3-none-any.whl (322kB)
remote:        Installing collected packages: click, itsdangerous, Werkzeug, MarkupSafe, Jinja2, Flask, gunicorn
remote:          Running setup.py install for itsdangerous: started
remote:            Running setup.py install for itsdangerous: finished with status 'done'
remote:          Running setup.py install for MarkupSafe: started
remote:            Running setup.py install for MarkupSafe: finished with status 'done'
remote:        Successfully installed Flask-0.12.2 Jinja2-2.10 MarkupSafe-1.0 Werkzeug-0.14.1 click-6.7 gunicorn-19.7.1 itsdangerous-0.24
remote: 
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote: 
remote: -----> Compressing...
remote:        Done: 42.7M
remote: -----> Launching...
remote:        Released v3
remote:        https://tsoha-python-demo.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/tsoha-python-demo.git
* [new branch]      master -> master
(venv) ~/demo$

Nyt sovellus on tarkasteltavana osoitteessa https://tsoha-python-demo.herokuapp.com/. Osoite on toki riippuvainen sovellukselle valitusta nimestä.

Sovellus on myös hyvä lähettää Githubiin, sillä siihen on tullut päivityksiä.

(venv) ~/demo$ git push origin master
(venv) ~/demo$ 

Nyt kun työkalut on hallussa, on aika siirtyä oman harjoitustyön pariin!

Sisällysluettelo