Viikko 2
- tiistai 26.11. 12-14 Ville Nordberg Trail openers ja Kristiina Vainio Houston Inc
- maanantai 2.12. 12-14 Jami Kousa ja Aleksandr Tereshchenko Unity
- maanantai 9.12. 12-14 Kasper Hirvikoski Unity ja Luomus Fossil Database Team
- tiistai 10.12. 12-14 Hannu Kokko Elisa
Allaolevien tehtävien deadline on maanantai 11.11. klo 23:59
Apua tehtävien tekoon kurssin Discord-kanavalla, kampuksella pajassa BK107:
- ma 14-16
- to 13-15
- pe 12-14
Muista myös monivalintatehtävät joiden deadline on sunnuntai 10.11. klo 23:59:00
Viikon ensimmäisessä ja toisessa tehtävässä tutustutaan koodin staattiseen analyysin Pylint-työkalun avulla. Gitiin tutustuminen jatkuu tehtävissä 5-9. Laskarien lopuksi jatketaan riippuvuuksien injektoinnin parissa. Tehtävissä 10-12 koodataan ja refaktoroidaan koodia siistimmäksi.
Typoja tai epäselvyyksiä tehtävissä?
Tee korjausehdotus editoimalla tätä tiedostoa GitHubissa.
- tiistai 26.11. 12-14 Ville Nordberg Trail openers ja Kristiina Vainio Houston Inc
- maanantai 2.12. 12-14 Jami Kousa ja Aleksandr Tereshchenko Unity
- maanantai 9.12. 12-14 Kasper Hirvikoski Unity ja Luomus Fossil Database Team
- tiistai 10.12. 12-14 Hannu Kokko Elisa
Kurssipalaute
Kurssilla on käytössä normaalin lopussa kerättävän palautteen lisäksi ns. jatkuva palaute: voit antaa milloin vain kurssihenkilökunnalle anonyymiä palautetta osoitteessa https://norppa.helsinki.fi/targets/83083419/feedback
Ongelmia Poetryn kanssa?
Muutamia ohjeita täällä
Tehtävien palauttaminen
Tehtävät palautetaan GitHubiin, sekä merkitsemällä tehdyt tehtävät palautussovellukseen https://study.cs.helsinki.fi/stats/courses/ohtu2024 välilehdelle “my submission”.
Tehtävät 3 ja 4 laajentavat viime viikon ensimmäistä tehtäväsarjaa, eli ne palautetaan ohtuvarasto-repositorioon, siis samaan mitä käytettiin viikon 1 tehtävissä 2-13. Muut tehtävät palautetaan palautusrepositorioon, eli samaan mihin palautit ensimmäisen viikon tehtävät 14-17.
Katso tarkempi ohje palautusrepositorioita koskien täältä.
1. Poetryn harjoittelua
Tämä tehtävä tehdään palautusrepositorioon, siis samaan mihin tehtiin viikon 1 tehtävät 14-17
- Tee palautusrepositorioon hakemisto viikko2 ja sen sisälle hakemisto poetry-web tätä tehtävää varten
- Lue täältä lisää tehtävien palautusrepositorioista
Varoitus: pip
Olet saattanut asentaa Pythonin tarvitsemia riippuvuuksia pip-komennolla. Älä käytä pipiä tällä kurssilla sillä jos teet niin, teet 99.9% todennäköisyydellä jotain väärin.
Tällä kurssilla riippuvuudet asennetaan poetryllä.
Tämä tarkoittaa käytännössä sitä, että riippuvuudet asennetaan komennolla poetry add kirjasto komennon pip install kirjasto sijaan.
Tässä tehtävässä harjoittelemme lisää Poetryn käyttöä ja tutustumme semanttiseen versiointiin. Apua tehtävän tekoon saa mm. Ohjelmistotekniikka-kurssilta lainatusta Poetry-ohjeesta ja Poetryn dokumentaatiosta.
Kuvitellaan tilanne, jossa työskentelet ohjelmistokehittäjänä kehitystiimissä, joka on alkamassa kehittämään web-sovellusta. Olette päätyneet kehittämään sovelluksen Pythonilla ja käyttämään Poetrya riippuvuuksien hallinnassa.
Tee seuraavat toimenpiteet:
- Aluksi Poetry-pohjainen projekti täytyy alustaa. Alusta projekti Poetryn avulla poetry-web nimiseen hakemistoon tehtävien palautukseen käyttämäsi repositorion hakemiston viikko2 sisälle. Muista käyttää alustuksessa komentoa
poetry init --python "^3.10"
, jotta projektin Python-version vaatimus asetetaan oikein. - Etsit Googlettamalla sopivia kirjastoja web-sovellusta varten ja törmäät Flask-viitekehykseen. Asenna Flask projektin riippuvuudeksi Poetryn avulla
- Sovelluksessa ilmenee ensimmäinen bugi. Syynä oli luultavasti se, ettei sovellukselle ole toteutettu vielä yhtään testiä. Päädyt käyttämään testauksessa pytest-viitekehystä. Asenna pytest projektin kehityksen aikaiseksi riippuvuudeksi
- Pohdi itseksesi, miksi on hyödyllistä määritellä riippuvuus erikseen kehityksen aikaiseksi riippuvuudeksi
- Sovelluksessa käsitellään paljon JSON-muotoista dataa, joten päädyt etsimään sen serialisointiin ja deserialisointiin sopivia kirjastoja. Törmäät tarkoitukseen sopivaan kirjastoon nimeltä jsonpickle. Asenna jsonpickle projektin riippuvuudeksi
- Huomaat bugin jsonpickle-kirjastossa, joten alat tutkimaan sen GitHub repositorion issueita. Eräässä issuessa kerrotaan, että löytämäsi bugi ei ilmene kirjaston versiossa
2.2.0
. Asenna jsonpickle-kirjastosta versio2.2.0
.- Tutustu semanttiseen versiointiin täällä
- Pohdi itseksesi, mitä hyötyjä semanttisesta versioinnista on. Jos kirjasto noudattaa semanttista versiointia, miksi kirjaston version
2.2.0
päivittäminen versioon3.2.2
saattaa sisältää riskejä? Miksei samoja riskejä luultavasti ole version3.0.3
kanssa? - Versiovaatimuksissa on mukana usein
^
-, tai~
-etuliite. Pohdi itseksesi, mitä näillä ilmaistaan. Asiaa käsitellään mm. Poetryn dokumentaatiossa
- Päätät, että jsonpickle-kirjastosta on ollut vain harmia ja voit helposti toteuttaa sen tarjoaman toiminnallisuuden itse. Poista jsonpickle projektin riippuvuuksien joukosta
Palautettavasta poetry-web-hakemistosta ei tarvitse löytyä muita tiedostoja kuin pyproject.toml ja poetry.lock.
2. Riippuvuuksien hyödyntäminen
Tämä tehtävä tehdään palautusrepositorioon, siis samaan mihin teit edellisen tehtävän
Ohjelmistokehittäjälle tulee usein vastaan tilanne, jossa pitäisi löytää tiettyyn käyttötarkoitukseen sopiva kirjasto. Harjoittelemme kyseistä tilannetta tässä tehtävässä.
TOML on eräs helppolukuinen datan esitysformaatti, jota käytetään usein konfiguraatiotiedostoissa, kuten Poetryn pyproject.toml-tiedostossa. Kurssirepositorion hakemistosta viikko2/project-reader on pohja ohjelmalle, jonka tarkoituksena on lukea projektin tietoja annetusta osoitteesta löytyvästä pyproject.toml-tiedostosta.
- Kopioi aluksi projekti palautusrepositorioon hakemiston viikko2 sisälle.
Tehtävänäsi on ensin löytää sopiva kirjasto, jonka avulla TOML-muotoisista merkkijonoista voi muodostaa Pythonin tietorakenteita. Voit hyödyntää tässä esimerkiksi PyPI-sivuston hakua tai Googlea. PyPI:ssä eräs hyvä hakusana voisi olla esimerkiksi “toml”. Tutustu kirjastojen kuvauksiin ja päättele sen perusteella, sopiiko kirjasto käyttötarkoitukseen. Kun löydät sopivan kirjaston, asenna se projektiin Poetryn avulla.
HUOM: PyPI:n asennusohjeista löytyy usein pip-asennuksen ohje pip install <kirjasto>
. Kaikki kirjastot pystyy kuitenkin asentamaan yhtä lailla Poetryn avulla komennolla poetry add <kirjasto>
.
Ota sen jälkeen kirjasto käyttöön projektin src/project_reader.py-tiedoston ProjectReader
-luokan metodissa get_project
. Metodin content
-muuttujaan on tallennettu tiedoston sisältö:
def get_project(self):
# tiedoston merkkijonomuotoinen sisältö
content = request.urlopen(self._url).read().decode("utf-8")
print(content)
# deserialisoi TOML-formaatissa oleva merkkijono ja muodosta Project-olio sen tietojen perusteella
return Project("Test name", "Test description", [], [])
Tulosta jokainen välivaihe (tiedoston sisältö ja kirjaston avulla deserialisoitu sisältö) print
-funktion avulla, jotta tiedät, minkä muotoista data on. Toki voit käyttää samaan tarkoitukseen myös VS Coden debuggeria.
Muodosta tämän jälkeen tiedoista Project
-olio antamalla sille konstruktorin kautta projektin nimi, kuvaus, lista riippuvuuksista ja lista kehityksen aikaisista riippuvuuksista. Kun ohjelma toimii halutulla tavalla, voit poistaa debuggauksessa käytetyt tulostukset.
Ohjelman voi käynnistää virtuaaliympäristössä komennolla python3 src/index.py
. Esimerkkinä käytetyn pyproject.toml-tiedoston tapauksessa ohjelman tulostuksen tulisi olla seuraava:
Name: Ohtutesting app
Description: Sovellus joka toimii testisyötteenä ohtun viikon 2 laskareihin
Dependencies: python, Flask, editdistance
Development dependencies: coverage, robotframework, robotframework-seleniumlibrary, requests
HUOM ohjelma ei saa sisältää kuin ainoastaan tiedostossa index.py olevan print-komennon, joka tulostaa Project
-olion merkkijonoesityksen!
Laajenna ja hio vielä ratkaisua siten, että esimerkkiprojektin osalta lopputulos näyttää suunnilleen seuraavalta
Name: Ohtutesting app
Description: Sovellus joka toimii testisyötteenä ohtun viikon 2 laskareihin
License: MIT
Authors:
- Matti Luukkainen
- Kalle Ilves
Dependencies:
- python
- Flask
- editdistance
Development dependencies:
- coverage
- robotframework
- robotframework-seleniumlibrary
- requests
3. Pylint ja koodin staattinen analyysi
Tämä ja seuraava tehtävä tehdään viime viikon tehtävissä 2-13 käytettyyn ohtuvarasto-repositorioon
Kurssin kolmannessa osassa teemana on ohjelmien laadun varmistaminen. Eräs ohjelman laatua useimmiten edistävä tekijä on järkevän koodityylin noudattaminen. Koodin tyyliä voidaan tarkkailla automatisoidusti niin sanottujen staattisen analyysin työkaluilla.
Tutustutaan nyt staattisen analyysin työkaluun nimeltään Pylint. Pylint on jo ehkä tullut tutuksi kurssilta Ohjelmistotekniikka. Ennen kuin syvennymme aiheeseen, tutustu pylintin käyttöön lukemalla kurssilta Ohjelmistotekniikka lainattu Pylint-ohje.
Mene nyt viikon 1 varasto-projektiin liittyvien tehtävien palautusrepositorioosi.
Ota varasto-projektissa käyttöön Pylint noudattamalla lukemiasi ohjeita. Konfiguraationa käytettävän .pylintrc-tiedoston sisältö tulee toistaiseksi olla tämän tiedoston sisällön mukainen.
Pylintin tarkistamat säännöt konfiguroidaan .pylintrc-tiedostoon oikeiden osioiden alle. [main]
-osio sisältää yleistä konfiguraatio, kuten mitkä hakemistot tai tiedostot pitäisi jättää tarkistuksien ulkopuolelle. [MESSAGE CONTROL]
-osiossa taas voidaan määritellä esimerkiksi tarkistuksia, joista ei tarvitse huomauttaa. Loput osiot ovat eri sääntöjen konfigurointia varten, jotka on dokumentoitu Pylintin dokumentaatiossa.
Jos haluamme esimerkiksi asettaa funktioiden ja metodien argumenttien maksimilukumäärään neljään, voimme lisätä sen [DESIGN]
-osioon seuraavasti:
[DESIGN]
max-args=4
Sääntö max-args on määritelty dokumentaatiossa seuraavasti:
Säännön nimen alussa olevaa kahta viivaa ei siis tule kirjoittaa tiedostoon .pylintrc
Helpoin tapa löytää sääntöjä on hakemalla sopivalla hakusanalla niitä dokumentaatiosta tai Googlettamalla. Oikean osion löytää dokumentaatiosta (esimerkiksi max-args
-sääntö löytyy dokumentaatiosta Design checker -osion alta).
Toimi nyt seuraavasti:
- Siirry virtuaaliympäristöön komennolla
poetry shell
ja suorita sen sisällä komentopylint src
. Jos tarkistuksissa löytyy virheitä, korjaa ne -
Määrittele nyt tiedostoon .pylintrc seuraavat säännöt (katso lista säännöistä Pylintin dokumentaatiosta):
- Rivin pituus on maksimissaan 80 merkkiä
- Vinkki: sääntö löytyy Format checker -osiosta ja tulee määrittää
[FORMAT]
-osion alle
- Vinkki: sääntö löytyy Format checker -osiosta ja tulee määrittää
- Ei yli kahta sisäkkäistä lohkoa (esimerkiksi if- tai for-lohkoa) funktion tai metodin sisällä
- Vinkki: sääntö löytyy Refactoring checker -osiosta ja tulee määrittää
[REFACTORING]
-osion alle)
- Vinkki: sääntö löytyy Refactoring checker -osiosta ja tulee määrittää
- Funktiossa tai metodissa on enintään 15 lausetta (statements), etsi sääntö dokumentaatiosta
- Määrittele myös jokin itse valitsemasi, mielenkiintoiselta/hyödylliseltä kuulostava sääntö
- Rivin pituus on maksimissaan 80 merkkiä
- Muuta koodiasi siten, että saat jokaisen määritellyistä Pylint-säännöistä rikkoutumaan
- Korjaa koodisi ja varmista, että se noudattaa kaikkia sääntöjä
Usein .pylintrc-konfiguraatiota ei ole järkevää kirjoittaa tyhjästä käsin, vaan käytetään lähtökohtana Pylintin suosittelemaa konfiguraatiota. Suositellun konfiguraation voi tulostaa komentoriville komennolla pylint --generate-rcfile
.
4. Koodin staattinen analyysi ja GitHub Actionit
Tämä tehtävä tehdään viime viikon tehtävissä 2-13 käytettyyn ohtuvarasto-repositorioon
Laajenna ohtuvarastosi GitHub Actionien määritelmää siten, että myös Pylint-tarkastukset suoritetaan aina kun koodi pushataan GitHubiin.
Varmista, että GitHub huomaa tilanteen, missä koodi rikkoo projektin Pylint-sääntöjä:
Varmista myös, että kun korjaat koodin, kaikki toimii taas moitteettomasti:
5. Git: branchit [versionhallinta]
Tämä tehtävä tehdään palautusrepositorioon, siis samaan mihin tehtiin tehtävät 1 ja 2
Lue brancheja käsittelevät osuudet seuraavasta https://git-scm.com/book/en/v2/Git-Branching-Basic-Branching-and-Merging
- jos haluat lukea hieman perusteellisemman selityksen asiasta, lue https://git-scm.com/book/en/v2:n luku kolme kokonaisuudessaan
Kannattaa huomioida myös erittäin hyvä brancheja käsittelevä visuaalinen materiaali osoitteessa https://learngitbranching.js.org/
Varsin selkeältä vaikuttaa myös https://www.atlassian.com/git/tutorials/using-branches
Huom: kun liikut branchien välillä kannattaa pitää working tree ja staging -alue tyhjinä!
Tee palautusrepositorion viikon 2 tehtävien hakemistoon alihakemisto git-branch-harjoitus tämän ja muutaman seuraavan tehtävän koodia varten.
-
Mene luomaasi hakemistoon
-
Varmista että olet haarassa main, ja että kaikki muutokset on committoitu:
$ git status
On branch main
nothing to commit, working tree clean
- Luo ja committaa hakemistoon tiedosto index.py jonka sisältö on seuraava
x = int(input("luku 1: "))
y = int(input("luku 2: "))
- Luo branch laskut, siirry branchiin (tämä tapahtuu esim. komennolla
git checkout -b laskut
), luo sinne tiedosto summa.py jolla on seuraava sisältö
def summa(x, y):
return x+y
-
lisää ja committaa tiedosto versionhallintaan
- Siirry takaisin main-branchiin (komennolla
git checkout main
), tiedoston summa.py ei pitäisi nyt näkyä- huom: muistutus vielä siitä, että kun siirryt branchista toiseen varmista aina komennolla
git status
että kaikki muutokset on committoitu
- huom: muistutus vielä siitä, että kun siirryt branchista toiseen varmista aina komennolla
- Luo tiedosto logger.py, jolla on seuraava sisältä
from datetime import datetime
def logger(viesti):
print(f"{datetime.now()}: {viesti}")
- Muuta myös tiedostoa index.py seuraavasti:
from logger import logger
logger("aloitetaan")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
logger("lopetetaan")
-
Committaa nämä muutokset main-haaraan
- Mene branchiin laskut ja tarkasta, että mainiin lisätty tiedosto ei ole branchissa ja että tiedostoon index.py tehty muutos ei näy
- Lisää ja committaa branchiin tiedosto erotus.py jolla on seuraava sisältö
def erotus(x, y):
return x-y
- Siirry takaisin main-branchiin
- Tarkasta että laskut-branchiin lisätyt muutokset eivät ole mainissa
- Tarkastele komennolla
gitk --all
miltä repositorio ja branchit näyttävät (gitk
-komento toimii Windowsilla ainakin GitHub for Windowsin Git Shellissä.)- Saat asennettua Maciin
gitk
:n tämän ohjeen avulla- jos asennus ei onnistu, on hyvä korvaaja gitk:lle sourcetree
- Saat asennettua Maciin
- Mergeä branchin laskut sisältö mainiin (tämä tapahtuu komennolla
git merge laskut
)- Mergeäminen aiheuttaa ns. merge-commitin, ja avaa tekstieditorin mihin joudut kirjoittamaan commit-viestin
- Jos et ole määritellyt gitille editoria viime viikon tehtävän 2 ohjeiden mukaan, avautuu ehkä gitin oletusarvoinen editori vim
- Vimistä poistuminen saattaa osoittautua ensikertalaiselle hankalaksi, Google auttaa tarvittaessa
- Mergeäminen aiheuttaa ns. merge-commitin, ja avaa tekstieditorin mihin joudut kirjoittamaan commit-viestin
- Muuta tiedostoa index.py seuraavasti ja commitoi muutos:
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{summa(x, y)}")
print(f"{erotus(x, y)}")
logger("lopetetaan")
- Katso jälleen miltä näyttää
gitk --all
-komennolla
6. Git: branchit ja staging-alue [versionhallinta]
- Olet nyt repositoriosi main-haarassa
- Luo uusi tiedosto README.md, älä kuitenkaan lisää ja commitoi tiedostoa versionhallintaan
- Tiedoston sisällöllä ei ole merkitystä, se voi olla esim. seuraava
## git-harjoituksia
Harjoitellaan branchien käyttöä
- Komennon
git status
tulostuksen pitäisi olla seuraava
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)
- Siirry nyt branchiin laskut
- Suorita uudelleen komento
git status
- Huomaat, että tulostus on edelleen sama, tiedosto ei edelleenkään ole versionhallinnan alla
- Eli vaikka olit main-haarassa kun loit tiedoston, ei main-haara eikä koko git tiedä tiedostosta vielä mitään ennen kuin lisäät sen versionhallinnan alaisuuteen komennolla
git add
- Lisää tiedosto nyt versionhallinnan alaisuuteen ja commitoi se
- Tiedosto menee nykyiseen branchiisi, eli branchiin laskut, main ei edelleenkään tiedä tiedostosta mitään
- Luo uusi tiedosto LICENSE ja lisää se versionhallintaan (komennolla add), älä kuitenkaan commitoi
- Tiedoston sisällöllä ei ole merkitystä, se voi olla esim. seuraava
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
For more information, please refer to <https://unlicense.org>
- Tarkasta että komennon
git status
tulos on seuraava:
On branch laskut
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: LICENCE
- Olet siis branchissa laskut ja LICENSE on lisätty staging-alueelle, sitä ei kuitenkaan ole vielä committoitu
- Siirry nyt branchiin main
- Komennon
git status
tulos on edelleen sama, LICENSE on edelleen staging-alueella mutta committoimattomana - Staging-alue ei kuulu mihinkään branchiin, eli jos staging-alueella on committoimattomia muutoksia ja vaihdat branchia, säilyvät samat asiat stagingissa
- Muutokset siirtyvät stagingista branchiin ainoastaan komennolla
git commit
- Committoi nyt staging-alueen muutokset eli LICENSE mainiin
- Komennon
git status
tulos kertoo nyt että staging-alue on tyhjä:
On branch main
nothing to commit, working tree clean
- Siirry jälleen branchiin laskut ja huomaat, että LICENSE ei ole olemassa
- Mergeä main-branch branchiin laskut
- Siirry nyt takaisin branchiin main ja tuhoa branchi laskut
- Tuhoaminen ei onnistu suoraan komennolla
git branch -d
, jos branchin sisältö ei ole kokonaisuudessaan mergetty mainiin. Jos näin on, tee ensin merge mainiin, tai jos tarkoituksena on poistaa branch silti vaikka siinä on vielä eriäviä muutoksia, käytägit branch -D
poistaaksesi branch eriävine muutoksineen
- Tuhoaminen ei onnistu suoraan komennolla
- Tämän tehtävän ideana oli siis havainnollistaa, että working tree (muutokset joista Git ei ole tietoinen) ja staging (gitiin lisättyihin tiedostoihin tehdyt committoimattomat muutokset)
eivät liity mihinkään branchiin, muutokset siirtyvät staging-alueelta branchiin ainoastaan komennon
git commit
suorituksen seurauksena
7. Git: konflikti! [versionhallinta]
Tee paikalliseen Git-repositorioon seuraavat
- Muuta main-branchin tiedostoa index.py seuraavasti:
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{summa(x, y)}")
print(f"{erotus(x, y)}")
logger("lopetetaan")
- alkuun on siis lisätty kommentti ja tyhjä rivi
-
committaa muutos
- Tee uusi branchi bugikorjaus, mene branchiin ja editoi tiedoston index.py loppua (esim. seuraavasti ) ja committaa
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{summa(x, y)}")
print(f"{erotus(x, y)}")
logger("lopetetaan ohjelma")
print("goodbye!") # lisäys bugikorjaus-branchissa
- Mene takaisin main-branchiin, editoi tiedoston index.py alkupuolta esim.seuraavasti (muutos on funktion logger parametrissa) ja committaa muutokset:
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan ohjelma") # muutos mainissa
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{summa(x, y)}")
print(f"{erotus(x, y)}")
logger("lopetetaan ohjelma")
-
Mergeä branchin bugikorjaus sisältö mainiin
- Katso tiedoston index.py-sisältöä, sen pitäisi sisältää nyt molemmissa brancheissa tehdyt muutokset
- Huom: jo tässä vaiheessa saattaa syntyä konflikti jos olet vahingossa muuttanut merkkejä väärästä kohtaa tiedostoa! Toimi tällöin ao. ohjeen mukaan.
-
Olet edelleen branchissa main. Muuta tiedostoa print-komentojen osalta seuraavasti
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan ohjelma")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{x} + {y} = {summa(x, y)}") # muutos mainissa
print(f"{x} - {y} = {erotus(x, y)}") # muutos mainissa
logger("lopetetaan ohjelma")
print("goodbye!")
-
Committoi muutokset
- Siirry branchiin bugikorjaus
- Muuta nyt tiedostoa (jälleen print-komentojen osalta) seuraavasti ja committaa
# tehdään alussa importit
from logger import logger
from summa import summa
from erotus import erotus
logger("aloitetaan ohjelma")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"Lukujen {x} ja {y} summa on {summa(x, y)}") # muutos bugikorjaus-branchissa
print(f"Lukujen {x} ja {y} erotus on {erotus(x, y)}") # muutos bugikorjaus-branchissa
logger("lopetetaan ohjelma")
print("goodbye!")
- Mergeä branchin main sisältö branchiin bugikorjaus
- Nyt pitäisi aiheutua konflikti, komento aiheuttaa tulostuksen
Auto-merging index.py
CONFLICT (content): Merge conflict in index.py
Automatic merge failed; fix conflicts and then commit the result.
-
Git ei siis osannut yhdistää tiedostoon tehtyjä muutoksia, koska ne kohdistuvat samoille riveille, seurauksena on konflikti.
-
Ratkaise konflikti:
- Editoi tiedoston index.py sisältö haluamaksesi
- Ja toimi edellä mainitun artikkelien ohjeen mukaan, eli lisää konfliktoinut tiedosto staging-alueelle ja committoi
Jotkut editorit, esim Visual Studio Code sisältävät sisäänrakennetusti niin sanotun merge toolin, joka osaa jossain määrin helpottaa konfliktien ratkaisua:
8. Git: branchit ja GitHub [versionhallinta]
Aloita lukemalla ProGit-kirjasta luku Remote Branches.
- Palaa branchiin main ja pushaa se GitHubiin. Jos päädyt seuraavaan virheilmoitukseen, toimi se ohjeen mukaan ja pushaa uudelleen:
´´´ fatal: The current branch main has no upstream branch. To push the current branch and set the remote as upstream, use
git push --set-upstream origin main
To have this happen automatically for branches without a tracking upstream, see ‘push.autoSetupRemote’ in ‘git help config’. ´´´
-
Mene branchiin bugikorjaus ja pushaa se GitHubiin.
-
Varmista, että näet GitHubissa molemmat brachit
Kloonaa GitHub-repositoriosta koneellesi toinen klooni:
- Kuten huomaat (komennolla
git branch
) ainoastaan haara main tulee mukana oletusarvoisesti kloonatessa - Tee klooniin branch joka “träkkää” GitHubissa olevan projektisi branchia bugikorjaus (ks. https://git-scm.com/book/en/v2/Git-Branching-Remote-Branches kohta Tracking Branches)
- Lisää “träkkäävään” branchiin tiedosto changelog.txt, committaa ja pushaa branchi GitHubiin
- Tarkastele GitHub-repositoriota selaimella, varmista että branchi päivittyy
- Tee klooniin uusi branchi tulo ja sinne kahden luvun tulon laskeva funktio tiedostoon
tulo.py
- Muuta ohjelmaa seuraavasti
from logger import logger
from summa import summa
from erotus import erotus
from tulo import tulo
logger("aloitetaan ohjelma")
x = int(input("luku 1: "))
y = int(input("luku 2: "))
print(f"{x} + {y} = {summa(x, y)}")
print(f"{x} - {y} = {erotus(x, y)}")
print(f"{x} * {y} = {tulo(x, y)}")
logger("lopetetaan")
print("goodbye!")
- Committaa ja pushaa kloonin branchin tulo muutokset GitHubiin ja varmista, että ne näkyvät siellä
Mene GitHub-repositorion alkuperäiseen paikalliseen kopioon:
- Äsken luotu branchi ei ole vielä alkuperäisessä kopiossa
- Tee alkuperäiseen kopioon branchia tulo träkkäävä branchi
- Mergeä branchi tulo mainiin ja tuhoa branchi tulo sekä paikallisesti että GitHubista
- Tee nyt uusi branchi, nimeltaan osamaara, lisää branchiin tiedosto
osamaara.py
ja pushaa se GitHubiin
Mene jälleen hetki sitten luotuun repositorion klooniin
- Anna komento
git remote show origin
. Komennon tulostuksen pitäisi näyttää seuraavalta:
* remote origin
Fetch URL: git@github.com:mluukkai/ohtu-palautukset.git
Push URL: git@github.com:mluukkai/ohtu-palautukset.git
HEAD branch: main
Remote branches:
bugikorjaus tracked
main tracked
osamaara new (next fetch will store in remotes/origin)
refs/remotes/origin/tulo stale (use 'git remote prune' to remove)
Local branches configured for 'git pull':
bugikorjaus merges with remote bugikorjaus
main merges with remote main
tulo merges with remote tulo
Local refs configured for 'git push':
bugikorjaus pushes to bugikorjaus (up to date)
main pushes to main (up to date)
- Komento kertoo, että Remote (eli GitHub) ja Local (eli paikallinen klooni) eivät ole branchien suhteen samassa tilassa. Jo tuhottu branchi tulo löytyy vielä paikallisesti, kun taas uutta branchia osamaara ei paikallisesti vielä ole.
- Korjaa tilanne siten, että
git remote show origin
tulostaa
* remote origin
Fetch URL: git@github.com:mluukkai/ohtu-palautukset.git
Push URL: git@github.com:mluukkai/ohtu-palautukset.git
HEAD branch: main
Remote branches:
bugikorjaus tracked
main tracked
osamaara tracked
Local branches configured for 'git pull':
bugikorjaus merges with remote bugikorjaus
main merges with remote main
osamaara merges with remote osamaara
Local refs configured for 'git push':
bugikorjaus pushes to bugikorjaus (up to date)
main pushes to main (up to date)
osamaara pushes to osamaara (up to date)
Branchien kanssa työskentely voi aluksi tuntua sekavalta varsinkin jos GitHubissa on myös useita brancheja.
Mihin brancheja käytetään?
Ohjelmistokehitystiimi voi soveltaa Gitin branchaystä hyvin monella eri tyylillä. Artikkeli https://www.atlassian.com/git/tutorials/comparing-workflows esittelee tähän muutamia vaihtoehtoja. Eräs yleinen tapa branchien käyttöön ovat ns. featurebranchit:
The core idea behind the Feature Branch Workflow is that all feature development should take place in a dedicated branch instead of the main branch. This encapsulation makes it easy for multiple developers to work on a particular feature without disturbing the main codebase. It also means the main branch will never contain broken code, which is a huge advantage for continuous integration environments.
Jos kiinnostaa, lue lisää yllä olevasta dokumentista.
9. Git: epäajantasaisen kloonin pushaaminen [versionhallinta]
Demonstroidaan vielä (viime viikon tehtävässä 11 mainittu) usein esiintyvä tilanne, missä epäajantasaisen repositorion pushaaminen GitHubissa olevaan etärepositorioon epäonnistuu.
- Mene alkuperäisen repositorion paikallisen kloonin main-haaraan, tee jokin muutos, commitoi ja pushaa se GitHubiin
- Mene toisen kloonin main-haaraan ja tee sinne jokin muutos
- commitoi ja pushaa muutos GitHubiin
- Kaikki ei kuitenkaan mene hyvin, seurauksena on seuraavantyylinen virheilmoitus:
$ git push
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'git@github.com:mluukkai/ohtu-palautukset.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Virheen syynä on se, että GitHubissa oleva main-haara oli edellä paikallisen repositorion main-haaraa.
Ongelma ratkeaa seuraavasti. Tee ensin komento git pull
. Saat gitiltä pitkän valitusviestin:
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.
Käytännössä Git haluaa tietää minkälaisella strategialla paikallisen ja etärepositoriosi koodi tulisi yhdistää. Vaihtoehdoista kannattanee valita keskimmäinen, eli anna komentorivillä komento
git config pull.rebase true
Käytännössä valittu vaihtoehto tarkoittaa sitä, että Git suorittaa uudet lokaalit commitit etärepositoriossa olevien committien perään.
Voit nyt pullata koodin uudelleen komennolla git pull
. Komento git push
onnistuu nyt. Jatkossa vastaavista tilanteista selviää komennoilla git pull
ja git push
.
- Eli toimi näin ja varmista, että tekemäsi muutokset menevät GitHubiin
10. Pelaajalista
Hae kurssirepositorion hakemistossa viikko2/nhl-reader lähes tyhjä Poetry-projektin runko. Mukana on kohta tarvitsemasi luokka Player
.
- Kopioi projekti palatusrepositorioosi, hakemiston viikko2 sisälle.
Tehdään ohjelma, jonka avulla voi hakea jääkiekon NHL-liigan eri kausien tilastotietoja.
Näet tilastojen JSON-muotoisen raakadatan web-selaimella osoitteesta https://studies.cs.helsinki.fi/nhlstats/2023-24/players
Tee ohjelma, joka listaa suomalaisten pelaajien tilastot. Tarvitset ohjelmassa yhtä kirjastoa, eli riippuvuutta. Kyseinen kirjasto on requests-kirjasto, jonka avulla voi tehdä HTTP-pyyntöjä. Huomaa, että Pythonilla on myös valmiita moduuleja tähän tarkoitukseen, mutta requests-kirjaston käyttö on huomattavasti näitä moduuleja helpompaa.
Asenna siis requests-kirjasto projektin riippuvuuksiksi. Käytä kirjastosta uusinta versiota (jonka Poetry asentaa automaattisesti).
Voit ottaa projektisi pohjaksi seuraavan tiedoston:
import requests
from player import Player
def main():
url = "https://studies.cs.helsinki.fi/nhlstats/2023-24/players"
response = requests.get(url).json()
print("JSON-muotoinen vastaus:")
print(response)
players = []
for player_dict in response:
player = Player(player_dict)
players.append(player)
print("Oliot:")
for player in players:
print(player)
Tehtäväpohjassa on valmiina luokan Player
koodin runko. Edellä esitetyssä koodissa requests.get(url)
tekee HTTP-pyynnön, jonka jälkeen json
-metodin kutsu muuttaa JSON-muotoisen vastauksen Python-tietorakenteiksi. Tässä tilanteessa response
sisältää listan dictionaryja. Tästä listasta muodostetaan lista Player
-olioita for-silmukan avulla.
Tee Player
-luokkaan attribuutit kaikille JSON-datassa oleville kentille, joita ohjelmasi tarvitsee. Ohjelmasi voi toimia esimerkiksi niin, että se tulostaisi pelaajat seuraavalla tavalla:
Players from FIN
Erik Haula team NJD goals 14 assists 27
Otto Koivula team NYI goals 0 assists 2
Robin Salo team NYI goals 2 assists 2
Aatu Raty team VAN goals 2 assists 1
Niko Mikkola team STL goals 1 assists 5
Kaapo Kakko team NYR goals 18 assists 22
Rasmus Ristolainen team PHI goals 3 assists 17
Mikael Granlund team NSH goals 10 assists 31
Kasperi Kapanen team STL goals 15 assists 19
Joona Koppanen team BOS goals 0 assists 1
Henri Jokiharju team BUF goals 3 assists 10
Ukko-Pekka Luukkonen team BUF goals 0 assists 0
Joel Armia team MTL goals 7 assists 7
...
Tulostusasu ei tässä tehtävässä ole oleellista, eikä edes se mitä pelaajien tiedoista tulostetaan.
11. Siistimpi pelaajalista
Tulosta suomalaiset pelaajat pisteiden (goals + assists) mukaan järjestettynä. Tarkka tulostusasu ei ole taaskaan oleellinen, mutta se voi esimerkiksi näyttää seuraavalta:
Players from FIN
Mikko Rantanen COL 55 + 50 = 105
Aleksander Barkov FLA 23 + 55 = 78
Roope Hintz DAL 37 + 38 = 75
Miro Heiskanen DAL 11 + 62 = 73
Sebastian Aho CAR 36 + 31 = 67
Patrik Laine CBJ 22 + 30 = 52
Artturi Lehkonen COL 21 + 30 = 51
Matias Maccelli ARI 11 + 38 = 49
Jesperi Kotkaniemi CAR 18 + 25 = 43
Eetu Luostarinen FLA 17 + 26 = 43
Erik Haula NJD 14 + 27 = 41
...
- Vinkki 1: voit halutessasi hyödyntää filter-funktiota.
- Vinkki 2: kokeile, mitä
f"{self.name:20}"
tekee merkkijonoesityksellePlayer
-luokan__str__
-metodissa.
12. Pelaajalistan refaktorointi
Tällä hetkellä suurin osa pelaajatietoihin liittyvästä koodista on luultavasti main
-funktiossa. Funktion koheesion aste on melko matala, koska se keskittyy usean toiminnallisuuden toteuttamiseen. Koodi kaipaisi siis pientä refaktorointia.
Jaa toiminnallisuuden vastuut kahdelle luokkalle: PlayerReader
ja PlayerStats
. PlayerReader
-luokan vastuulla on hakea JSON-muotoiset pelaajat konstruktorin parametrin kautta annetusta osoitteesta ja muodostaa niistä Player
-olioita. Tämä voi tapahtua esimerkiksi luokan get_players
-metodissa. PlayerStats
-luokan vastuulla on muodostaa PlayerReader
-luokan tarjoamien pelaajien perusteella erilaisia tilastoja. Tässä tehtävässä riittää, että luokalla on metodi top_scorers_by_nationality
, joka palauttaa parametrina annetun kansalaisuuden pelaajat pisteiden mukaan laskevassa järjestyksessä (suurin pistemäärä ensin).
Refaktoroinnin jälkeen main
-funktion tulee näyttää suurin piirtein seuraavalta:
def main():
url = "https://studies.cs.helsinki.fi/nhlstats/2023-24/players"
reader = PlayerReader(url)
stats = PlayerStats(reader)
players = stats.top_scorers_by_nationality("FIN")
for player in players:
print(player)
Funktion pitäisi tulostaa samat pelaajat samassa järjestyksessä kuin edellisessä tehtävässä.
13. Graafinen pelaajalista
Laajenna sovellustasi lisäämällä siihen toiminnallisuutta ja muotoilemalla tulostus kirjaston Rich avulla. Ohjeita kirjaston käyttöön löytyy sen dokumentaatiosta ja googlaamalla.
Sovelluksella tulee pystyä näyttämään käyttäjän haluaman maan pelaajien tilastot käyttäjän määrittelemältä kaudelta.
Sovelluksen toiminta voi näyttää esimerkiksi seuraavalta:
Tehtävien palautus
Pushaa kaikki tekemäsi tehtävät (paitsi ne, joissa mainitaan, että tehtävää ei palauteta mihinkään) GitHubiin palautusrepositorioosi ja merkkaa tekemäsi tehtävät palautussovellukseen https://study.cs.helsinki.fi/stats/courses/ohtu2024