Ohjaus 1: Python perusteet ja PythonAnywhere
Näissä tehtävissä tutustutaan Pythonin perusteisiin ja viedään ensimmäinen Flask-sovellus palvelimelle. Tehtävät nojaavat kurssin omaan Python-luentomateriaaliin; kunkin tehtävän kohdalla on linkki siihen osioon, jossa tarvittava asia käsitellään.
Myös tehtävien vanha versio on edelleen saatavilla.
1. Valmistelut
- Asenna koneellesi jokin tuetuista Python 3 -versioista osoitteesta python.org/downloads.
- Tarkista asennus komennolla
python3 --version(Windowsissapy --version). - Kurssin materiaali: Python-kielen perusteita. Virallinen hakuteos: The Python Tutorial ja Python Standard Library.
Opettele käyttämään interaktiivista tulkkia ja sen
help-komentoa. Se on nopein tapa selvittää, mitä jokin
funktio oikeasti tekee.
$ python3
Python 3.12.3 (main, Jan 17 2026, 10:05:12) [GCC 13.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> help
Type help() for interactive help, or help(object) for help about object.
>>> import json
>>> help(json.dumps)
>>> dir(json)
>>> exit()
2. Hello World
-
Luo haluamallasi editorilla (esimerkiksi Visual Studio Code tai Notepad++) tiedosto
hello.py.Editorin täytyy tallentaa tiedosto UTF-8-muodossa ilman BOM-merkintää ja unix-muotoisin rivinvaihdoin (
LF), ei windows-muotoisin (CRLF). VS Code näyttää molemmat oikeassa alanurkassa. Notepad++:ssa rivinvaihdon tyyppi näkyy alalaidassa ja sen voi vaihtaa valinnalla Edit | EOL Conversion | Unix (LF). -
Kopioi ohjelmasi pohjaksi seuraava koodi:
#!/usr/bin/env python3 print("Hello world")Ensimmäinen rivi on unix-ympäristön shebang-rivi, joka kertoo, millä tulkilla ohjelma suoritetaan. Muoto
/usr/bin/env python3etsii tulkin polusta, joten sama rivi toimii sekä omalla koneellasi että palvelimella. Windows-ympäristössä riviä ei tarvita, mutta se ei myöskään haittaa.Erillistä
# -*- coding: utf-8 -*--riviä ei tarvita: Python 3 olettaa lähdekoodin olevan UTF-8:aa. Riittää, että editorisi tosiaan tallentaa UTF-8:na. -
Kokeile ohjelman toimintaa komentoriviltä. Siirry samaan kansioon, jossa
hello.pyon:$ python3 hello.py # Linux ja macOS > py hello.py # Windows
3. Tietorakenteet
Pythonissa lohkot merkitään sisentämällä. Sama sisennystaso tarkoittaa samaa lohkoa. Varmista, että editorisi korvaa tabulaattorit neljällä välilyönnillä, ja käytä sisennyksenä neljää välilyöntiä. Notepad++:ssa asetus on kohdassa Settings | Preferences | Language.
Lisää ohjelmaasi seuraava tietorakenne. Kyseessä on lista, jonka alkiot ovat sanakirjoja — kts. Sisäkkäiset tietorakenteet.
data = [
{
"nimi": "Kalle",
"ammatti": "Yliopistonopettaja",
"syntymävuosi": 1980,
"palkka": 2000,
},
{
"nimi": "Ville",
"ammatti": "Opiskelija",
"syntymävuosi": 1995,
"palkka": None,
},
{
"nimi": "Maija",
"ammatti": "Professori",
"syntymävuosi": 1970,
"palkka": 3000,
},
]
Toteuta seuraavat asiat:
-
Tulosta tietorakenteen sisältö seuraavaan tapaan:
nimi Kalle ammatti Yliopistonopettaja syntymävuosi 1980 palkka 2000 ...Jos yrität yhdistää merkkijonon ja kokonaisluvun
+-operaattorilla, saat virheen:TypeError: can only concatenate str (not "int") to strHelpoin ratkaisu on f-merkkijono, joka muuntaa arvot automaattisesti. Vaihtoehtoisesti voit antaa arvot
print-funktiolle erillisinä argumentteina tai muuntaa ne itsestr-funktiolla:luku = 1 print(f"merkkijono {luku}") # suositeltavin print("merkkijono", luku) # print erottaa argumentit välilyönnillä print("merkkijono " + str(luku))Tee tulostaminen niin, ettei se tunne avainten nimiä etukäteen vaan toimii myös uusien kenttien kanssa. Lisää Villelle kenttä
"kotipaikka": "Jyväskylä"ja varmista, että tulostus toimii edelleen. - Kokeile tulostamista kahdella tavalla: ensin uloimmassa silmukassa range()-funktiolla ja indeksillä, sitten ilman sitä käymällä lista suoraan läpi. Vertaa, kumpi on luettavampi. Kts. Toistorakenteet.
- Tulosta vain niiden henkilöiden tiedot, joiden palkka on suurempi kuin 2500.
- Tulosta vain henkilöiden ammatit.
-
Tulosta henkilöiden tiedot niin, että kentät tulevat avaimen mukaan aakkosjärjestyksessä.
Sanakirja säilyttää Python 3.7:stä lähtien alkioiden lisäysjärjestyksen — vanhoissa materiaaleissa toistuva väite satunnaisesta järjestyksestä ei pidä paikkaansa. Lisäysjärjestys ei silti yleensä ole se järjestys, jossa tiedot halutaan näyttää, joten järjestys pitää pyytää erikseen:
for avain in sorted(henkilo):. Kts. sorted ja key-funktiot. -
Tulosta vain kotipaikat. Varmista, että tulostaminen toimii, vaikka kaikilla ei ole kotipaikkaa määriteltynä. Ratkaise ongelma kahdella tavalla ja vertaa niitä:
# 1. get palauttaa oletusarvon, jos avainta ei ole print(henkilo.get("kotipaikka", "")) # 2. sama try..except-rakenteella try: print(henkilo["kotipaikka"]) except KeyError: passKumpaakin tapaa näkee Python-koodissa. Kun oletusarvo riittää,
geton lyhyempi ja selvempi;try–excepton paikallaan, kun puuttuva arvo vaatii oikeasti eri toimenpiteen. Kts. Why dict.get(key) instead of dict[key]? - Tulosta vain henkilöiden palkat. Jos palkkaa ei ole annettu,
tulosta tyhjä. Huomaa, että
"palkka": Noneon eri asia kuin puuttuva avain:getlöytää avaimen ja palauttaaNone, ei oletusarvoa. - Lisää ohjelmaasi rivi, joka lisää tietorakenteeseen omat tietosi
(
data.append({...})). Varmista, että edellä tehdyt tulosteet toimivat myös lisäyksen jälkeen. - Laske kaikki palkat yhteen ja tulosta summa sekä keskipalkka. Sinun pitää siis laskea myös, montako palkkaa löytyy. Jos jollekin ei ole määritelty palkkaa, käytä palkkana perustulokokeilun määrää eli 560 euroa.
-
Järjestä henkilöt nimen mukaan aakkosjärjestykseen ja tulosta ne. Sinun on kerrottava
sort- taisorted-kutsullekey-parametrilla, minkä kentän perusteella sanakirjoja verrataan.# järjestää datan palkan mukaan data.sort(key=lambda henkilo: henkilo["palkka"]) # sama erillisenä funktiona def palkka(henkilo): return henkilo["palkka"] data.sort(key=palkka) # käänteinen järjestys data.sort(key=palkka, reverse=True)Palkan mukaan järjestäminen kaatuu tällä aineistolla, koska yhden henkilön palkka on
NoneeikäNone-arvoa voi verrata lukuun. Anna puuttuvalle arvolle korvike:key=lambda h: h.get("palkka") or 0. Huomaa myös, ettäsortjärjestää listan paikallaan ja palauttaaNone, kun taassortedpalauttaa uuden listan.
4. Poikkeusten käsitteleminen
Kts. Poikkeukset. Nappaa aina se poikkeustyyppi, jonka osaat käsitellä:
try:
palkka = int(henkilo["palkka"])
except (KeyError, TypeError, ValueError):
palkka = 560
Älä kirjoita paljasta except:-lauseketta.
Se nappaa myös kirjoitusvirheet nimissä ja
Ctrl+C-keskeytyksen, ja piilottaa tulkin oman
virheilmoituksen. Jos joudut nappaamaan laajasti, kirjoita
except Exception as virhe:.
Kun haluat nähdä, mikä virhe todella tapahtui, tulosta poikkeus ja
tarvittaessa koko pinolistaus. Tämä korvaa vanhoissa esimerkeissä näkyvän
sys.exc_info()-kikkailun:
import traceback
try:
int("foo")
except Exception as virhe:
print(f"Virhe: {type(virhe).__name__}: {virhe}")
traceback.print_exc() # koko pinolistaus rivinumeroineen
Palvelinsovelluksessa sama kirjataan lokiin:
import logging
try:
kasittele()
except Exception:
logging.exception("Käsittely epäonnistui")
Omat poikkeusluokat
Omat poikkeukset periytetään luokasta Exception:
# yksinkertaisin mahdollinen
class OmaVirheFoo(Exception):
pass
# tähän versioon voi liittää viestin ja virhekoodin
class OmaVirhe(Exception):
def __init__(self, message="oma moka", error_code=0):
super().__init__(message)
self.error_code = error_code
self.message = message
def __str__(self):
return f"{self.message} (virhekoodi {self.error_code})"
try:
raise OmaVirhe("kauhea moka", 666)
except OmaVirhe as virhe:
print("omavirhe!", virhe)
try:
raise OmaVirheFoo()
except OmaVirheFoo as virhe:
print("omavirhefoo!", virhe)
5. Tiedostojen käsitteleminen ja JSON
Kts. Serialisointi ja Tiedostojen käsitteleminen.
-
Muunna tietorakenne JSON-muotoon:
import json print(json.dumps(data))Huomaat, että ääkköset näkyvät koodatussa muodossa (
Jyv\u00e4skyl\u00e4). Se on kelvollista JSONia, mutta lukukelvotonta. Kokeile, mitä tapahtuu parametreillaensure_ascii=Falsejaindent=2. -
Kopioi JSON-muotoinen tietorakenne omaan tekstitiedostoon ja tallenna se nimellä
tietorakenne.json. Poista sitten alkuperäinen tietorakenne ohjelmakoodista ja lataa se tiedostosta:import json with open("tietorakenne.json", encoding="utf-8") as tiedosto: data = json.load(tiedosto)Käytä
with-lohkoa, jolloin tiedosto sulkeutuu automaattisesti, ja anna ainaencoding-parametri. Muistisääntö funktioiden nimiin: loppu-s tarkoittaa merkkijonoa (dumps,loads), ilman s:ää käsitellään tiedosto-oliota (dump,load). -
Lisää ohjelmakoodissa tietorakenteeseen muutama henkilö ja tallenna rakenne takaisin tiedostoon json.dump-funktiolla. Tarkista, muuttuiko tiedoston sisältö.
with open("tietorakenne.json", "w", encoding="utf-8") as tiedosto: json.dump(data, tiedosto, indent=2, ensure_ascii=False)json.dumptarvitsee kaksi parametria: tallennettavan olion ja kirjoittamista varten avatun tiedosto-olion. Huomaa, että tila"w"tyhjentää tiedoston heti avattaessa. -
Kokeile samaan tapaan ladata tietorakenne verkko-osoitteesta malli.json. Vastaus toimii tiedosto-olion tapaan, joten sen voi antaa suoraan
json.load-funktiolle:import json import urllib.request osoite = "https://appro.mit.jyu.fi/ties4080/ohjaus/ohjaus1/malli.json" with urllib.request.urlopen(osoite, timeout=10) as vastaus: donitsit = json.load(vastaus)Tulosta tästä rakenteesta jokaisen donitsin nimi sekä sen päällysteet (
topping) ja kuorrutteet (batters). Rakenne on syvempi kuin edellinen: listan sisällä on sanakirjoja, joiden arvoina on lisää listoja. Tulosta rakenne ensin komennollajson.dumps(donitsit, indent=2, ensure_ascii=False)ja katso, miltä se todella näyttää, ennen kuin kirjoitat läpikäynnin.Mallina käytetty data on lainattu JSON Data Set Sample -sivulta.
6. PythonAnywhere ja WSGI
- Luo itsellesi ilmainen tunnus (beginner account) PythonAnywhere-palvelussa.
Huomioi, että käytät palvelinta osoitteessa
eu.pythonanywhere.com. - Siirry Web-välilehdelle ja luo uusi web app (Add a new web app). Valitse tyypiksi Manual configuration ja Python-versioksi 3.10 tai uudempi.
-
Luomisen jälkeen pääset sivulle, jossa listataan sovellukseesi liittyvät tiedot.
WSGI configuration file-kohdasta pääset muokkaamaan WSGI-koodia jaLog files-kohdasta löytyvät lokitiedostot:access.log: kuka on ladannut minkäkin resurssin ja milloinerror.log: virheilmoitukset eli kaikki, mitä sovellus on kirjoittanut STDERR-virtaan. Tärkein tiedosto, kun sovellus kaatuu.server.log: muu lokitieto, mukaan lukien STDOUT-tulosteet
import sys # WSGI-sovelluksessa: print("tämä näkyy server.logissa") print("tämä näkyy error.logissa", file=sys.stderr)Kaikissa ympäristöissä ei ole pääsyä lokeihin. Esimerkiksi users.jyu.fi-palvelimella et pääse lukemaan lokitiedostoja lainkaan.
-
Muuta
WSGI configuration fileseuraavanlaiseksi. Lue kuitenkin ensin, mitä valmiissa tiedostossa kerrotaan.# environ sisältää CGI-ympäristöä vastaavat muuttujat ja niiden arvot. # start_response on funktio, jolla kerrotaan palvelimelle HTTP-status ja otsakkeet. def application(environ, start_response): # content sisältää varsinaisen vastauksen, tässä tavallista tekstiä rivit = ["Ympäristömuuttujat"] for avain, arvo in environ.items(): rivit.append(f"{avain}\t:\t{arvo}") content = "\n".join(rivit).encode("utf-8") status = "200 OK" # vähintään Content-Type pitää löytyä response_headers = [ ("Content-Type", "text/plain;charset=UTF-8"), ("Content-Length", str(len(content))), ] start_response(status, response_headers) # start_response lähettää selaimelle otsakkeet ja niiden jälkeen tyhjän # rivin, joka kertoo otsakkeiden loppuvan. Sen jälkeen tulee sisältö. return [content]Huomaa
Content-Length: se lasketaan tavuista, ei merkeistä. Siksi merkkijono koodataan tavuiksi ennen pituuden laskemista — ääkkönen vie UTF-8:ssa kaksi tavua, ja väärä pituus katkaisee vastauksen. -
Kokeile sovellustasi osoitteessa
tunnus.eu.pythonanywhere.com. Muutokset eivät päivity itsestään, vaan sovellus on käynnistettävä uudelleen reload-painikkeella. Tuloksen pitäisi näyttää tältä:Ympäristömuuttujat QUERY_STRING : REQUEST_METHOD : GET REQUEST_URI : / PATH_INFO : / SERVER_PROTOCOL : HTTP/1.1 SERVER_NAME : tunnus.eu.pythonanywhere.com HTTP_HOST : tunnus.eu.pythonanywhere.com HTTP_USER_AGENT : Mozilla/5.0 ... HTTP_ACCEPT : text/html,application/xhtml+xml,... wsgi.version : (1, 0) wsgi.url_scheme : http ... - WSGI (ja CGI) on matalin taso, jolla web-sovelluksia voi ohjelmoida. Yleensä käytetään helpompia kirjastoja kuten Flaskia, joka toimii WSGI-rajapinnan päällä.
- Poista tekemäsi WSGI-sovellus ja luo tilalle uusi sovellus, jonka tyypiksi valitset Flaskin. Tutki, mitä WSGI-konfiguraatiotiedostoon nyt ilmestyi.
- Jos haluat jakaa PythonAnywheressa olevia tiedostojasi webissä, siirry Web-välilehden kohtaan Static Files: vasempaan sarakkeeseen kirjoitat URLin ja oikeaan tiedoston polun.
7. Flask omalla koneella
Kts. Virtuaaliympäristö ja pip. Ohjeita löytyy myös Flask Tutorial in Visual Studio Code -sivulta.
-
Luo ensimmäiseksi virtuaaliympäristö. Siirry kansioon, johon haluat sen luoda:
> py -3 -m venv venv # Windows $ python3 -m venv venv # Linux ja macOSSovellusta varten kannattaa lähes aina asentaa oma virtuaaliympäristö, jolloin sen kirjastot eivät riko muita sovelluksiasi.
-
Aktivoi ympäristö:
> .\venv\Scripts\activate # Windows (PowerShell) $ source venv/bin/activate # Linux ja macOSJos PowerShell antaa
PSSecurityException-virheen, salli allekirjoitettujen skriptien ajaminen omalle käyttäjällesi:> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUserVastaa kysymykseen Y ja yritä aktivointia uudelleen. Aktivoitumisen merkiksi kehotteen alkuun ilmestyy
(venv). -
Asenna Flask virtuaaliympäristöön:
(venv) $ pip install --upgrade pip (venv) $ pip install Flask -
Kirjoita yksinkertaisin mahdollinen Flask-ohjelma ja tallenna se nimellä
app.py:from flask import Flask app = Flask(__name__) @app.route("/") # tämä rivi kertoo osoitteen, josta sovellus löytyy def hello_world(): return "Hello, World!"Funktion edessä oleva
@app.routeon dekoraattori: se rekisteröi funktion vastaamaan annettuun osoitteeseen. -
Käynnistä sovellus. Kehitysvaiheessa kannattaa ottaa käyttöön debug-tila, jolloin virheet näkyvät selaimessa ja sovellus käynnistyy uudelleen tiedoston muuttuessa:
(venv) $ flask run --debugJos tiedoston nimi on jokin muu kuin
app.py, kerro se--app-valitsimella:(venv) $ flask --app flask_hello run --debugVanhemmissa ohjeissa neuvotaan asettamaan ympäristömuuttujat
FLASK_APPjaFLASK_ENV. Ne ovat Flask 2.2:ta edeltävää tapaa;FLASK_ENVei toimi enää lainkaan. Käytä yllä olevia komentoriviparametreja.Onnistuneen käynnistyksen tuloste näyttää suunnilleen tältä:
* Serving Flask app 'app' * Debug mode: on * Running on http://127.0.0.1:5000 (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 241-637-288 - Avaa sovelluksesi osoitteesta
http://127.0.0.1:5000/. Tee muutos palautettavaan tekstiin ja tarkista, päivittyykö sivu itsestään. -
Oletuksena Flask palauttaa HTML-dokumentteja. Mediatyypin voi vaihtaa Response-olion avulla:
from flask import Response @app.route("/teksti") def teksti(): return Response("Hello, World!", mimetype="text/plain;charset=UTF-8") - Virtuaaliympäristö suljetaan komennolla
deactivate.
8. PythonAnywhere ja Flask
-
Kopioi tekemäsi Flask-sovellus PythonAnywhere-palveluun. Riittää, että kopioit
.py-tiedostosi Source code -kansioon ja korvaat siellä valmiina olevan tiedoston. Älä koske WSGI configuration -tiedostoon. -
Jos sovellus kaatuu ja saat
Internal Server Error-virheilmoituksen, tarkistaerror.log-tiedostosta, mikä meni vikaan. Virheet näkee kätevämmin suoraan selaimessa, jos lisäät WSGI-konfiguraatiotiedostoon:from werkzeug.debug import DebuggedApplication application.debug = True application = DebuggedApplication(application)Debug-tila näyttää selaimessa lähdekoodia ja sallii koodin suorittamisen palvelimella. Käytä sitä vain kehitysvaiheessa, älä koskaan julkisessa tuotantosovelluksessa.
Mikä ihmeen Werkzeug? Se on WSGI-rajapinnan päälle rakennettu kirjasto, jonka päälle Flask on rakennettu. Siksi Flaskin dokumentaatio viittaa usein Werkzeugiin.
-
Jos haluat käsitellä virheet itse, voit lisätä Flask-tiedostoosi virhekäsittelijän:
import traceback from werkzeug.exceptions import InternalServerError @app.errorhandler(InternalServerError) def handle_500(e): alkuperainen = getattr(e, "original_exception", None) if alkuperainen is None: # suoraan nostettu 500, esimerkiksi abort(500) return "no error", 500 return traceback.format_exc(), 500 - Muutosten jälkeen sovellus on käynnistettävä uudelleen editorin oikean yläkulman tai Web-välilehden reload-painikkeella. Kts. Reload web app.
-
Siirrä myös aiemmin tekemäsi tietorakennetta käsittelevä koodi PythonAnywhere-palveluun. Se on lisättävä samaan
.py-tiedostoon, koska ilmaisella tunnuksella voi olla vain yksi sovellus.Flask-sovelluksessa et voi tulostaa
print-funktiolla — tuloste menee lokiin, ei selaimelle. Muuta funktiotasi niin, että se kerää tulosteet merkkijonoksi ja palauttaa sen. Kätevä tapa on kerätä rivit listaan ja yhdistää ne lopuksi:return "\n".join(rivit).Aseta funktio vastaamaan osoitteeseen
tunnus.eu.pythonanywhere.com/ohjaus1/muuttamalla reittiä:@app.route("/ohjaus1/")- Kokeile ensin tietorakenteella, joka on valmiiksi koodissa.
- Lataa seuraavaksi tietorakenne tiedostosta. Siirrä tiedosto PythonAnywheressa kotihakemistoosi.
- Kokeile myös tiedoston tallentamista.
- Kokeile vasta viimeisenä lataamista osoitteesta
https://appro.mit.jyu.fi/ties4080/ohjaus/ohjaus1/malli.json. Se ei onnistu, vaan sovellus kaatuu: ilmaisilla tunnuksilla saa hakea vain sallituista osoitteista. Kokeile, onnistuuko lataaminen osoitteestahttp://hazor.eu.pythonanywhere.com/malli.json.
- Tarvittaessa voit luoda PythonAnywhereen oman virtuaaliympäristön: How to use a virtualenv in your web app ja Setting up Flask applications on PythonAnywhere.
9. users.jyu.fi, CGI ja Flask
users.jyu.fi-palvelimella Flaskia ei voi ajaa suoraan, vaan se on suoritettava CGI-ohjelmana.
- Ota PuTTYllä tai PowerShellin
ssh-komennolla yhteys jalava- tai halava-palvelimeen. -
Luo W:-asemalle kansio
cgi-bin/ties4080/. Koko polku halava- ja jalava-koneissa on:/wwwhome/home/oma_tunnus/public_html/cgi-bin/ties4080/Kansion täytyy olla
w:\cgi-bineikä mikään muu. Kts. CGI/SSI-tekniikat users.jyu.fi-palvelimella.
Virtuaaliympäristön asennus palvelimelle
Siirry cgi-bin/ties4080/-kansioon ja luo ympäristö:
$ python3 -m venv venv
$ . venv/bin/activate # bash
$ source venv/bin/activate.csh # csh ja tcsh
(venv) $ pip install --upgrade pip
(venv) $ pip install Flask Flask-WTF
(venv) $ deactivate
Ympäristössä olevia ohjelmia suoritetaan asettamalla shebang-rivi
osoittamaan ympäristön omaan tulkkiin. Tässä nimenomaisessa tapauksessa
polku kirjoitetaan kokonaan eikä käytetä
env-muotoa, koska www-palvelimen on löydettävä juuri tämän
virtuaaliympäristön tulkki:
#!/home/oma_tunnus/public_html/cgi-bin/ties4080/venv/bin/python
Huomaa, että polku on eri kuin halava- ja jalava-koneissa: web-koodia
suorittava users.jyu.fi näkee kotihakemistosi polussa
/home/oma_tunnus/, kun taas kirjautuessasi
halavaan tai jalavaan sama hakemisto on
/wwwhome/home/oma_tunnus/. Shebang-riville tulee aina
users.jyu.fi-muotoinen polku.
Ensimmäinen CGI-ohjelma
-
Luo tiedosto
hello.cgikansioonw:\cgi-bin\ties4080\. Editorin on tallennettava se UTF-8:na, ilman BOMia ja unix-rivinvaihdoin (LF). Muista myös valita editorin kielitilaksi Python: sovellukset eivät osaa päätellä kieltä.cgi-päätteestä.#!/usr/bin/env python3 print("""Content-Type: text/plain; charset=UTF-8 Hello world! """)Nyt käytössä ei ole helpottavaa kehystä, vaan HTTP-protokollan edellyttämät otsakkeet on tulostettava itse. Huomaa tyhjä rivi, joka erottaa otsakkeet sisällöstä.
-
Ohjelma vaatii toimiakseen suoritusoikeuden kaikille käyttäjille, ja sen ryhmän on oltava
users:[tunnus@halava ties4080]$ chmod a+x hello.cgi [tunnus@halava ties4080]$ chgrp users hello.cgi [tunnus@halava ties4080]$ ls -al hello.cgi -rwxr-xr-x. 1 tunnus users 3777 Feb 5 09:59 hello.cgiCGI-ohjelmaan tai sen kansioon ei saa olla kirjoitusoikeutta muilla kuin käyttäjällä itsellään.
-
Kokeile ohjelmaa komentoriviltä. Se ei voi oikeasti toimia näin, mutta näet, onko koodissa syntaksivirheitä:
[tunnus@jalava ties4080]$ python3 hello.cgi - Kokeile ohjelmaa selaimella osoitteessa
https://users.jyu.fi/~omatunnus/cgi-bin/ties4080/hello.cgi. Jos saatInternal Server Error-virheen, tarkista seuraavat asiat.
Rivinvaihdot (CRLF vs. LF) ja BOM
Seuraava virhe tarkoittaa, että rivinvaihdot ovat DOS-tyyppisiä vaikka pitäisi olla unix-tyyppisiä:
/usr/bin/env: 'python3\r': No such file or directory
Sama vika voi näkyä myös muodossa
./hello.cgi: no such file or directory, ja selaimessa se
näkyy aina Internal Server Error -virheenä.
Rivinvaihdot voi tarkistaa file-komennolla. Merkintä
with CRLF line terminators kertoo, että ne ovat väärin:
[tunnus@halava ties4080]$ file hello.cgi oma.py
hello.cgi: a /usr/bin/env script, ASCII text executable, with CRLF line terminators
oma.py: Python script, UTF-8 Unicode text executable, with CRLF line terminators
Toinen tapa on cat -e tiedosto: jos rivien lopussa on
pelkkä $, kaikki on kunnossa; ^M$ tarkoittaa
vääriä rivinvaihtoja. Korjaaminen onnistuu avaamalla tiedosto
nano-editorilla -u-parametrin kera ja tallentamalla se:
$ nano -u hello.cgi
Varmista myös, ettei tiedoston alussa ole BOM-merkintää. Se aiheuttaa virheen:
Exec format error. Binary file not executable.
BOMin näkee esimerkiksi less-komennolla tai komennolla
head -c 3 tiedosto | xxd: BOM näkyy tavuina
ef bb bf.
Flask CGI-ohjelmana
-
Luo kansioon tiedosto
flask.cgi. Muista vaihtaa ensimmäiselle riville oma käyttäjätunnuksesi.#!/home/oma_tunnus/public_html/cgi-bin/ties4080/venv/bin/python # suorittaa Flask-sovelluksen CGI-ohjelmana users.jyu.fi-palvelimella import traceback from wsgiref.handlers import CGIHandler try: from werkzeug.debug import DebuggedApplication from oma import app as application application.debug = True handler = CGIHandler() handler.run(DebuggedApplication(application)) except Exception: # Tänne päädyttäessä Werkzeug ei toimi, joten HTTP-otsake on tulostettava # itse. STDOUT menee tässä tapauksessa suoraan selaimelle. print("Content-Type: text/plain;charset=UTF-8\n") print("Sovellus ei käynnistynyt:\n") print(traceback.format_exc())Tämä tiedosto vastaa sitä, minkä PythonAnywhere loi puolestasi WSGI-konfiguraatiotiedostoon. Oleellisin ero on, että tässä Flask-sovellus suoritetaan CGI-rajapinnan kautta.
-
Luo tiedosto
oma.py. Nimen on oltava täsmälleen tämä, vrt. edellisen tiedoston rivifrom oma import app as application.# Tässä tiedostossa ei tarvita shebang-riviä, koska tiedostoa ei suoriteta # suoraan vaan flask.cgi tuo sen moduulina. from flask import Flask, Response app = Flask(__name__) # @app.route määrää, mille osoitteelle tämä funktio suoritetaan @app.route("/") def hello_world(): return Response("Hello World", content_type="text/plain; charset=UTF-8") - Varmista, että tiedostojen ja kansioiden oikeudet ovat samat kuin aiemmin.
-
Kokeile sovellusta osoitteessa
https://users.jyu.fi/~omatunnus/cgi-bin/ties4080/flask.cgi/Viimeinen kauttaviiva on olennainen: se vastaa tiedostossa olevaa
@app.route("/")-riviä. -
Jos sovellus ei toimi, etsi syntaksivirheet komentoriviltä. Aktivoi ensin virtuaaliympäristö, jotta käytössä on oikea tulkki:
$ . venv/bin/activate (venv) $ python oma.py (venv) $ python flask.cgiFlask-sovellus ei voi komentoriviltä oikeasti toimia, koska se vaatii www-ympäristön, mutta koko sovelluksen kaatavat syntaksivirheet tulevat näkyviin.
Mitä tapahtuu, kun sivua pyydetään:
- WWW-palvelin suorittaa
flask.cgi-tiedoston sen ensimmäisellä rivillä kerrotulla tulkilla eli virtuaaliympäristön Pythonilla. - Sovellus tuo
oma.py-tiedoston sisällön (from oma import app as application). Jos siinä on syntaksivirhe, virhe näytetään sivulla. oma.py:ssä luodaan Flask-sovellus (app = Flask(__name__)).CGIHandlersuorittaa sovelluksen CGI-rajapinnan kautta.- Flask valitsee
@app.route("/")-merkinnän perusteella funktionhello_world(), joka asettaa mediatyypin ja palauttaa tekstin.
-
Kokeile saada PythonAnywhereen tekemäsi sovellus toimimaan myös users.jyu.fi-palvelimella. Kokeile ensin ilman tiedoston tallentamista ja lisää tallentaminen vasta lopuksi. Tallentaminen ei onnistu — miksi?
- PythonAnywhere on yhden käyttäjän hiekkalaatikko, jossa myös www-palvelinprosessilla on oikeus koskea tiedostoihisi.
- users.jyu.fi on monen käyttäjän ympäristö, jossa www-palvelimella (Apache) on oma tunnuksensa ja oikeutensa. Se ei pääse tiedostoihisi, ellet erikseen anna lupaa. Lisäksi CGI-ohjelmille on omia rajoitteitaan.
10. Laskuri
Onnistuuko laskurin rakentaminen? Kokeillaan. Kts. Näkyvyysalueet ja global.
-
Lisää
oma.py-tiedostoosi seuraavat rivit:count = 0 # globaali muuttuja @app.route("/laskuri") def laskuri(): global count count = count + 1 return str(count)Ilman
global-määrettä sijoitus loisi funktiolle oman paikallisen muuttujan ja kaatuisi virheeseenUnboundLocalError. -
Kokeile osoitteessa
https://users.jyu.fi/~omatunnus/cgi-bin/ties4080/flask.cgi/laskuri. Toimii muuten, mutta laskuri ei kasva. Miksi? -
Tee sama lisäys PythonAnywhere-sovellukseesi ja kokeile osoitteessa
tunnus.eu.pythonanywhere.com/laskuri. Nyt toimii. Miksi? Kokeile sitten reloadata sovellus ylläpitoliittymästä. Mitä laskuri näyttää? -
Sama sovellus voitaisiin viedä myös Cloud Run -palveluun, jossa laskuri antaisi aivan kummallisia lukuja. Miksi?
Älä käytä globaaleja muuttujia Flask-sovelluksessa. Globaali muuttuja on aina prosessikohtainen:
- CGI-sovellus: tulkki käynnistetään uudelleen jokaisella latauksella, joten globaalit nollautuvat aina. Tämä on myös hidasta; FastCGI nopeuttaa jättämällä tulkin muistiin.
- WSGI-sovellus: tulkki jää muistiin ja globaalit alustetaan sen käynnistyessä. Sama tulkki voi olla pystyssä hyvin pitkään tai hyvin lyhyen aikaa — PythonAnywhere voi käynnistää sovelluksen uudelleen milloin tahansa.
- Kuormantasaus: samasta sovelluksesta voi olla käynnissä useita instansseja, joilla jokaisella on omat globaalinsa. Ne voivat olla eri palvelimillakin.
Kunnollisen laskurin rakentaminen edellyttää joko sessioiden ja evästeiden käyttämistä tai sovelluksen tilan tallentamista HTML-dokumentin rakenteeseen. Näihin palataan myöhemmin.
11. Querystring
Sivun osoitteeseen voidaan lisätä parametreja querystringinä:
https://osoite.example/sivu?parametri=arvo¶metri2=arvo1¶metri2=arvo2
Polun perään lisätään ?-merkki, jonka jälkeen luetellaan
avaimia ja arvoja &-merkillä eroteltuina. Sama avain voi
esiintyä useita kertoja.
- Osoitteessa esiintyvät erikoismerkit on koodattava prosenttikoodauksella. Pythonissa tämän tekee urllib.parse.quote_plus.
- Jos querystringin sisältävän osoitteen sijoittaa HTML-dokumenttiin,
on muistettava koodata myös
&-merkit html.escape-funktiolla.
Näitä koodauksia tarvitaan vasta tulevissa tehtävissä. Nyt riittää, että osataan lukea annetut parametrit ohjelman käyttöön.
Parametreihin päästään käsiksi Flaskin Request-oliolla. Samalla tavalla käsitellään myöhemmin myös lomakkeiden lähettämät tiedot.
from flask import request
Request-olion tärkeimmät ominaisuudet:
request.args— URL-parametrit MultiDict-muodossarequest.form— lomakeparametrit MultiDict-muodossarequest.method— käytetty metodi (GET tai POST)request.query_string— koko querystring sellaisenaanrequest.environ— WSGI-ympäristö
request.args on MultiDict, joka toimii kuten tavallinen
sanakirja, mutta palauttaa get-metodilla vain ensimmäisen
arvon, jos avaimelle on annettu useampia. Kaikki arvot saa
getlist-metodilla.
nimi = request.args.get("nimi", "Tuntematon")
arvosanat = request.args.getlist("arvosana")
kurssit = request.args.getlist("kurssi")
Tehtävä
Tee ohjelma, joka osaa tulostaa seuraavan querystringin sisällön:
http://127.0.0.1:5000/query?nimi=Malli%20Henkil%C3%B6&arvosana=5&arvosana=3
&arvosana=2&kurssi=Helppo+kurssi&kurssi=Vaikea+kurssi&kurssi=%C3%84ll%C3%B6+kurssi
Vaihda mediatyypiksi text/plain, niin saat tulosteen
näyttämään tältä:
Henkilön nimi : Malli Henkilö
Helppo kurssi 5
Vaikea kurssi 3
Ällö kurssi 2
Keskiarvo : 3.33
Keskiarvo esitetään kahdella desimaalilla. Käytä muotoilua
f"{keskiarvo:.2f}", joka näyttää aina kaksi desimaalia,
tai round-funktiota,
joka pyöristää itse luvun.
Muuttele parametreja ja varmista, että ohjelmasi toimii, vaikka parametreja ei annettaisi lainkaan tai kursseja ja arvosanoja olisi eri määrä:
- Lue nimi
get-metodilla ja anna sille oletusarvo. - Lue kurssit ja arvosanat
getlist-metodilla. - Käy kurssit silmukassa läpi.
- Muunna arvosanat kokonaisluvuiksi. Jos muunnos epäonnistuu, käytä
arvosanana nollaa — tähän tarvitset
try–except ValueError-rakennetta. - Jos arvosanoja on annettu vähemmän kuin kursseja, käytä
puuttuvana arvosanana
"-"-merkkiä. Laske keskiarvo vain oikeasti annetuista arvosanoista — ja varo nollalla jakamista, jos arvosanoja ei ole yhtään.
Saatuasi ohjelman toimimaan omalla koneellasi siirrä se PythonAnywhere-palveluun ja varmista toiminta siellä.
Käyttäjien kommentit