Allaolevien tehtävien deadline on maanantai 9.11. klo 23:59

Apua tehtävien tekoon kurssin Discord-kanavalla sekä kampuksella pajassa BK107:

  • ma 16-19
  • ti 13-16
  • to 16-19
  • pe 13-16

Liittyminen kurssin Discord-kanavalle tapahtuu komennolla /join course TKT20006 - Ohjelmistotuotanto - ohtu

Muista myös tämän viikon monivalintatehtävät, joiden deadline on sunnuntai 8.11. klo 23:59:00 .

Viikon tehtävissä 1-4 tutustutaan riippuvuuksien hallintaan uv:lla ja ohjelmoidaan hieman paria kirjastoa hyödyntäen. Tehtävissä 6-8 tutustutaan koodin staattiseen analyysin Pylint-työkalun avulla. Tehtävässä 9 aloitetaan kurssin [tekoäly]-tehtävien sarja GitHub Copilotin parissa. Gitiin tutustuminen jatkuu tehtävissä 10-14.

Typoja tai epäselvyyksiä tehtävissä?

Tee korjausehdotus editoimalla tätä tiedostoa GitHubissa.

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/112400522/feedback

Ongelmia uv:n 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/ohtu-2026 välilehdelle “my submission”.

Tehtävät 6 ja 7 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ä.

GitHub Education

Viikoilla 2–7 on [tekoäly]-tehtäviä, joissa käytetään GitHubin Copilotia. Suurimman osan tehtävistä voi tehdä ilmaisella Copilot Free -versiolla, mutta sen kuukausikiintiö on rajallinen. Viikon 5 tehtävät edellyttävät opiskelijoille ilmaista Copilot Student -versiota, jonka saat käyttöösi aktivoimalla GitHub Education -jäsenyyden. Jos et ole vielä jäsen, hae jäsenyyttä nyt. Hakemuksen hyväksyminen kestää internetin mukaan jopa viikon.

Checkboxit tehtävien teon seurannan apuna

Kurssin tehtävänannot ovat välillä pitkiä. Tekstin seasta saattaa olla haastavaa bongata niitä askelia, jotka edellyttävät toimenpiteitä. Tehtävien selkeyttämiseksi ja seurannan helpottamiseksi tehtävien toimenpiteitä edellyttävät askeleet on merkitty ranskalaisten viivojen sijaan checkboxeilla.

Voit käyttää checkboxeja oman edistymisesi seurannan helpottamiseen. Kun askel on tehty, rastita boksi. Näin et mene sekaisin sen suhteen, missä kohtaa olet menossa. Rastit tallentuvat selaimesi paikalliseen muistiin (localStorageen), eli ne säilyvät vaikka lataat sivun uudelleen. Rastit eivät siirry toiselle koneelle tai selaimelle, eikä niitä lähetetä minnekään.

1. uv:n 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 uv-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 uv:lla.

Tämä tarkoittaa käytännössä sitä, että riippuvuudet asennetaan komennolla uv add kirjasto komennon pip install kirjasto sijaan.

Tässä tehtävässä harjoittelemme lisää uv:n käyttöä ja tutustumme semanttiseen versiointiin. Apua tehtävän tekoon saa mm. uv-ohjeesta ja uv:n 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 uv:tä riippuvuuksien hallinnassa.

Tee seuraavat toimenpiteet:

Alusta projekti uv:n avulla uv-web nimiseen hakemistoon tehtävien palautukseen käyttämäsi repositorion hakemiston viikko2 sisälle

Etsit Googlettamalla sopivia kirjastoja web-sovellusta varten ja törmäät Flask-viitekehykseen.

Asenna Flask projektin riippuvuudeksi uv:n avulla

Sovelluksessa ilmenee ensimmäinen bugi. Syynä on luultavasti se, ettei sovellukselle ole toteutettu vielä yhtään testiä. Päädyt käyttämään testauksessa pytest-kirjastoa.

Asenna pytest projektin kehitysaikaiseksi riippuvuudeksi

  • Pohdi itseksesi, miksi on hyödyllistä määritellä riippuvuus erikseen kehityksen aikaiseksi riippuvuudeksi

Sovellus käyttää relaatiotietokantaa joten päädyt etsimään tarkoitukseen sopivia kirjastoja. Törmäät tarkoitukseen sopivaan kirjastoon nimeltä SQLAlchemy.

Asenna SQLAlchemy projektin riippuvuudeksi

Huomaat bugin SQLAlchemy-kirjastossa, joten alat tutkimaan sen GitHub repositorion issueita. Eräässä issuessa kerrotaan, että löytämäsi bugi ei ilmene kirjaston versiossa 1.4.54.

Asenna SQLAlchemy-kirjastosta versio 1.4.54

Tutustu semanttiseen versiointiin täällä

  • Pohdi, mitä hyötyjä semanttisesta versioinnista on. Jos kirjasto noudattaa semanttista versiointia, miksi kirjaston version 1.4.54 päivittäminen versioon 2.0.44 saattaa sisältää riskejä? Miksei samoja riskejä luultavasti ole versiosta 2.0.5 versioon 2.0.44?
  • Versiovaatimuksissa on mukana usein esim. >=-, ==- tai ~=-operaattori. Selvitä, mitä näillä ilmaistaan. Asiaa käsitellään mm. uv:n dokumentaatiossa

Kuulet kaveriltasi, että Flaskin sijaan kannattaisi käyttää FastAPI-kirjastoa.

Poista Flask projektin riippuvuuksista ja asenna FastAPI.

Palautettavasta uv-web-hakemistosta ei tarvitse löytyä muita tiedostoja kuin pyproject.toml ja uv.lock.

2. Riippuvuuksien hyödyntäminen: Pelaajalista

Hae kurssirepositorion hakemistossa viikko2/nhl-reader lähes tyhjä uv-projektin runko. Mukana on kohta tarvitsemasi luokka Player.

Kopioi projekti palautusrepositorioosi, 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/2024-25/players

Tehdään seuraavassa ohjelma, joka listaa tietyn maan 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 riippuvuudeksi. Käytä kirjastosta uusinta versiota (jonka uv asentaa automaattisesti).

Voit ottaa projektisi pohjaksi seuraavan tiedoston:

import requests
from player import Player

def main():
    url = "https://studies.cs.helsinki.fi/nhlstats/2024-25/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.

Tässä tehtävässä on tarkoituksena toteuttaa toiminnallisuus, jonka avulla on mahdollista tulostaa tietyn kansalaisuuden pelaajat, esim. suomalaiset.

Tee Player-luokkaan attribuutit kaikille JSON-datassa oleville kentille, joita ohjelmasi tarvitsee.

Viimeistele ohjelma siten, että se listaa suomalaisten pelaajien tilastot.

Ohjelmasi voi toimia esimerkiksi niin, että se tulostaisi pelaajat seuraavalla tavalla:

Players from FIN:

Juuso Välimäki team UTA  goals 2 assists 3
Anton Lundell team FLA  goals 17 assists 28
Ville Ottavainen team SEA  goals 0 assists 1
Roope Hintz team DAL  goals 28 assists 39
Rasmus Ristolainen team PHI  goals 4 assists 15
Ville Koivunen team PIT  goals 0 assists 7
Mikael Pyyhtia team CBJ  goals 4 assists 3
Mikko Rantanen team COL, CAR, DAL  goals 32 assists 56
...

Tulostusasu ei tässä tehtävässä ole oleellinen, eikä edes se, mitä pelaajien tiedoista tulostetaan.

3. Siistimpi pelaajalista

Tulosta suomalaiset pelaajat pisteiden (goals + assists) mukaan järjestettynä.

Tarkka tulostusasu ei taaskaan ole oleellinen, mutta se voi esimerkiksi näyttää seuraavalta:

Players from FIN

Mikko Rantanen        COL, CAR, DAL   32 + 56 = 88
Sebastian Aho         CAR             29 + 45 = 74
Aleksander Barkov     FLA             20 + 51 = 71
Roope Hintz           DAL             28 + 39 = 67
Mikael Granlund       SJS, DAL        22 + 44 = 66
Teuvo Teravainen      CHI             15 + 43 = 58
Anton Lundell         FLA             17 + 28 = 45
Artturi Lehkonen      COL             27 + 18 = 45
Kaapo Kakko           NYR, SEA        14 + 30 = 44
Eeli Tolvanen         SEA             23 + 12 = 35
...
  • Vihje 1: Täällä on kerrottu miten järjestäminen Pythonilla tapahtuu
  • Vihje 2: voit halutessasi hyödyntää filter-funktiota.
  • Vihje 3: kokeile, mitä f"{self.name:20}" tekee merkkijonoesitykselle Player-luokan __str__-metodissa.
  • Erityisesti vihje 2 on heikko, katso alta miten saat apua tekoälyltä
  • Myös Ohjelmoinnin MOOCin osa 11 ja osa 12 käsittelevät tehtävän kannalta hyödyllisiä asioita
Vihje: tekoälyn hyödyntäminen tehtävässä

Vihje 2 kehotti tutustumaan Pythonin filter-funktioon. Dokumentaatio on kehnoa, kuten aika monesti Pythonin tapauksessa on.

Tekoäly antaa varsin hyvät ohjeet promptilla miten pythonin funktio filter toimii

Esimerkeissä esiintyy lambda, jonka toimintaperiaate on päässyt unohtumaan. Kysytään AI:ltä seuraavalla promptilla: kerro miten pythonin lambda toimii.

Idea toivon mukaan selkiytyy ja tehtävä ratkeaa.

Myös vihjeessä 3 mainittujen f-merkkijonojen toiminnasta saa tekoälyltä hyviä vastauksia, promptina voi olla esim. kertaa pythonin f-merkkijonojen toimintaperiaatteet

Perusteiden jälkeen voi kokeilla hieman tarkemmin tehtävään soveltuvaa promptia: miten f-merkkijonoilla saa sarakkeistetun tulostuksen

4. 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 luokalle: 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/2024-25/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ä.

5. 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 tai tekoälyltä.

Sovelluksen 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:

Tämä tehtävä tehdään palautusrepositorioon, siis samaan mihin teit edellisen tehtävän

6. 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.

Aloita 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ö on toistaiseksi tämän tiedoston sisällön mukainen.

Pylintin tarkistamat säännöt konfiguroidaan .pylintrc-tiedostoon oikeiden osioiden alle. [main]-osio sisältää yleisiä konfiguraatioita, 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ä 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, Googlettamalla tai kysymällä tekoälyltä. 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 source .venv/bin/activate ja suorita sen sisällä komento pylint 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
  • Yksi sisennystason leveys on 4 merkkiä
  • 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)
  • Funktiossa tai metodissa on enintään 10 lausetta (statements). Etsi sääntö dokumentaatiosta
    • voit lyhentää index.py:ssä olevaa funktiota main siten, että se täyttää ehdon
  • Määrittele myös jokin itse valitsemasi, mielenkiintoiselta/hyödylliseltä kuulostava sääntö

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.

7. 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 Actions -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:

8. Precommit hook ja Pylint

GitHub Action siis varmistaa, että repositorioon päätyneet Pylint-virheet huomataan. Parempi olisi toki, että sovelluskehittäjä suorittaisi Pylintin aina omalla koneellaan ja korjaisi virheet ennen kuin niitä pääsee livahtamaan GitHubin puolelle. Sovelluskehittäjät ovat kuitenkin usein laiskoja ja huonomuistisia, näinpä Pylint herkästi jää suorittamatta paikallisesti. Git tarjoaa kätevän ominaisuuden hookit, jonka avulla voimme automatisoida erilaisia esim. ennen committia suoritettavia toimenpiteitä.

Python-projekteissa hookien konfigurointi on helppoa precommit-työkalun avulla.

Asenna pre-commit ohtuvarastoon seuraavalla komennolla

uv add pre-commit --dev

Lisää projektin juureen tiedosto .pre-commit-config.yaml ja sille seuraava sisältö

repos:
- repo: local
  hooks:
    - id: pylint
      name: pylint
      entry: pylint
      language: system
      types: [python]
      require_serial: true

Määrittelyn jälkeen precommit hook tulee vielä asentaa suorittamalla seuraava komento projektin virtuaaliympäristössä

pre-commit install

Huomaa, että komento on suoritettava aina, kun tiedostoa .pre-commit-config.yaml muutetaan!

Kun nyt suoritat komennon git commit -m"viesti" suoritetaan Pylint ennen committia, ja jos koodissa on virheitä, ei commit onnistu.

Tee koodiin Pylint-virhe, ja varmista, että precommit hook havaitsee virheen.

Lisää Pylint myös tehtävien 2-5 projektiin nhl-reader, ja korjaa mahdolliset virheet. Käytä samanlaista konfiguraatiota, jonka teit tehtävässä 6. Voit lisätä rivin maksimipituutta, 80 on modernina aikana turhan vähän.

Bonus: palautusrepositorio ja precommit hook

Konfiguroi palautusrepositioriosi siten, että se suorittaa Pylintin precommit hookissa tehtävien 2-5 projektille nhl-reader. Tämä tehtävä on hieman haasteellisempi, sillä uv-ympäristöä ei ole määritelty repositorion juuressa.

precommit-työkalun sijaan tässä tehtävässä saattaa olla helpompaa tehdä precommit hook “käsin” projektin juuressa olevaan tiedostoon .git/hooks/pre-commit.

Apuna kannattaa käyttää kaikkea mahdollista aina googlesta tekoälyyn.

9. Ensikosketus Copilotiin [tekoäly]

Tämä on kurssin ensimmäinen [tekoäly]-tehtävä. Lue ennen tehtävää materiaalin Tekoäly ohjelmistotuotannossa luvut Mikä kielimalli on, AI-avusteisen ohjelmoinnin muodot ja Tuntemattoman koodin ymmärtäminen.

Tehtävässä käytetään VS Coden GitHub Copilotia. Tehtävän voi tehdä ilmaisella Copilot Free -versiolla, eli GitHub Education -hakemuksesi ei tarvitse olla vielä hyväksytty. Free-version kuukausikiintiö on rajallinen, joten älä tuhlaa sitä turhiin kokeiluihin.

Jatketaan tehtävien 2-5 ja 8 nhl-reader-projektin parissa. Varmista, että kaikki muutokset on commitoitu ja että Pylint on projektissa käytössä, ennen kuin aloitat.

Ota Copilot käyttöön VS Codessa: kirjaudu GitHub-tunnuksellasi Copilot-näkymän kautta. Katso tarvittaessa ohjeet täältä

Avaa hakemisto viikko2/nhl-reader VS Codessa omana workspacenaan, eli esim. komennolla code . projektin hakemistossa

Koodin täydennys

Kirjoita luokkaan PlayerStats uuden metodin määrittely ja sitä kuvaava docstring, esim.

def top_goal_scorers(self, how_many):
  """Palauttaa how_many eniten maaleja tehnyttä pelaajaa maalimäärän mukaan laskevassa järjestyksessä"""

ja odota, että Copilot ehdottaa metodin runkoa.

Lue ehdotus ennen kuin hyväksyt sen (Tab). Tekeekö koodi sen mitä docstring lupaa? Onko koodi samaa tyyliä kuin luokan muut metodit?

Chat: koodin ja virheiden selittäminen

Avaa Copilotin Chat-näkymä ja valitse tilaksi Ask. Jos Ask-tilaa ei löydy valikosta, ks. tämä. Pyydä Copilotia selittämään, miten projektin koodi toimii, esim. “Selitä miten #file:player_reader.py toimii”

Riko koodistasi jokin tehtävässä 6 määrittelemistäsi Pylint-säännöistä, suorita komentoriviltä pylint src. Kopioi virheilmoitus Copilotin chattiin ja pyydä Copilotia selittämään, mistä Pylintin varoituksessa on kyse ja miksi sääntö on olemassa

Tarkista Copilotin selitys Pylintin dokumentaatiosta. Pitikö selitys paikkansa?

Korjaa koodi takaisin sääntöjen mukaiseksi

Agentti: uusi toiminnallisuus ja testit

Valitse Chat-näkymässä tilaksi Agent ja pyydä agenttia lisäämään sovellukseen mahdollisuus näyttää halutun joukkueen pelaajat pisteiden mukaan järjestettynä

Pyydä agenttia tekemään luokalle PlayerStats yksikkötestit. Vaadi, että testit eivät käytä verkkoyhteyttä, vaan PlayerReader-luokan tilalla käytetään stubia viikon 1 tehtävän 16 tapaan

Lue jokainen komento ennen kuin annat agentille luvan suorittaa sen.

Varmista itse, että testit menevät läpi (uv run pytest) ja että ne eivät tee HTTP-pyyntöjä.

On mahdollista, että testien suorittaminen komentoriviltä aiheuttaa virheen (joka johtuu siitä, mihin agentti on viestit sijoittanut). Jos komento ei toimi, pyydä agenttia suorittamaan se, ja korjaamaan tilanne.

Käy agentin tekemä muutos läpi komennolla git diff. Muokkasiko agentti jotain, mihin sitä ei pyydetty koskemaan?

Suorita pylint src ja varmista, että agentin koodi noudattaa samoja sääntöjä kuin omasi. Jos Pylint löytää virheitä, korjaa ne itse tai pyydä agenttia korjaamaan ne. Jos teit bonustehtävän precommit hookin, se tekee tarkastuksen automaattisesti commitoinnin yhteydessä

Commitoi muutokset

Kirjoita raportti kokemuksistasi palautusrepositorion hakemistoon viikko2 talletettavaan tiedostoon copilot.md

Kerro raportissa

  • Kuinka hyödyllisiksi koit koodin täydennyksen ehdotukset?
  • Pitikö Copilotin selitys Pylint-varoituksesta paikkansa?
  • Tekikö agentti toimivan ratkaisun, ja testit?
  • Eroaako testeissä joku siitä miten testejä tehtiin edellisellä viikolla?
  • Löysikö Pylint agentin koodista virheitä?
  • Mitä muuta opit?

10. 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

Kannattaa huomioida myös melko 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

Gitinkin suhteen kielimallit ovat myös varsin osaavia, eli hätätapauksessa voit turvautua vaikkapa CurreChatiin.

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 hakemistoon tiedosto index.py jonka sisältö on seuraava

x = int(input("luku 1: "))
y = int(input("luku 2: "))

lisää ja committaa tiedosto versionhallintaan

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 (komennollagit 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

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")

Commitoi 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

Luo branchiin tiedosto erotus.py jolla on seuraava sisältö

def erotus(x, y):
    return x-y

lisää ja committaa tiedosto versionhallintaan

Siirry takaisin main-branchiin

Tarkasta että laskut-branchiin lisätyt muutokset eivät ole mainissa

Repositorion historia näyttää nyt seuraavalta. Haarat ovat erkaantuneet toisistaan ensimmäisen commitin jälkeen:

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 Homebrewlla komennolla brew install git-gui
    • jos asennus ei onnistu, on hyvä korvaaja gitk:lle sourcetree

Mergaa branchin laskut sisältö mainiin (tämä tapahtuu komennollagit merge laskut)

  • Mergaaminen aiheuttaa ns. merge-commitin, ja avaa tekstieditorin, johon 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

Katso komennolla gitk –all miten tilanne on muuttunut

Muuta tiedostoa index.py seuraavasti:

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")

lisää ja committaa muutos versionhallintaan

Katso jälleen miltä näyttää gitk --all-komennolla

Lopputulos näyttää seuraavalta. Merge-commitilla on kaksi edeltäjää, joten main sisältää nyt myös laskut-haaran muutokset. Haara laskut osoittaa edelleen samaan committiin kuin ennen mergeä:

11. Git: branchit ja staging-alue [versionhallinta]

Varmista, että olet 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öä

Tee komento git status ja varmista, että tulos on

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 README.md 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

Olet edelleen branchissa laskut. 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:   LICENSE
  • Olet siis branchissa laskut ja LICENSE on lisätty staging-alueelle, sitä ei kuitenkaan ole vielä committoitu

Siirry nyt branchiin main ja suorita git status

  • Komennon 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

Tilanne näyttää nyt seuraavalta. LICENSE odottaa staging-alueella, ja kun suoritat komennon git commit, se menee siihen haaraan, johon HEAD osoittaa, eli mainiin:

Committoi nyt staging-alueen muutokset eli LICENSE haaraan main

Varmista, komennolla git status 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

Mergaa main-branch branchiin laskut

Siirry nyt takaisin branchiin main ja tuhoa branch 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

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.

12. Git: konflikti! [versionhallinta]

Jatketaan edellisen tehtävän repositorion parissa

Muuta main-haarassa 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

lisää ja committaa muutos versionhallintaan

Tee uusi branch bugikorjaus, mene branchiin ja editoi tiedoston index.py loppua (esim. 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 ohjelma")
print("goodbye!") # lisäys bugikorjaus-branchissa

Lisää ja committoi muutos versionhallintaan

Mene takaisin main-branchiin,

Editoi tiedoston index.py alkupuolta esim. seuraavasti (muutos on funktion logger parametrissa)

# 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")

Lisää ja committoi muutos versionhallintaan

Mergaa branchin bugikorjaus sisältö mainiin

  • Katso tiedoston index.py-sisältöä, sen pitäisi sisältää nyt molemmissa brancheissa tehdyt muutokset
  • Git osaa siis mergetä muutokset automaattisesti sillä ne eivät ole keskenään ristiriidassa
  • 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!")

Lisää ja committoi muutos

Siirry branchiin bugikorjaus ja muuta 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!")

Lisää ja committoi muutos versionhallintaan

Mergaa branchin main sisältö branchiin bugikorjaus

  • Nyt pitäisi syntyä 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.

Kuva havainnollistaa tilannetta. Kumpikin haara on muuttanut print-komentoja, eli samoja rivejä, sen jälkeen kun haarat on viimeksi yhdistetty:

Ratkaise konflikti:

  • Editoi tiedoston index.py sisältö haluamaksesi
  • Ja toimi edellä mainitun artikkelien ohjeen mukaan, eli lisää konfliktoinut tiedosto staging-alueelle ja committoi
  • Ohjeet mergen loppuunsuorittamiseksi antaa myös komento git status (jonka joudut todennäköisesti suorittamaan kaksi kertaa…)

Visual Studio Code sisältää sisäänrakennetusti niin sanotun merge toolin, joka osaa jossain määrin helpottaa konfliktien ratkaisua:

13. 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

  • Komennon antama palaute kertoo jälleen miten saat komennon toimimaan

Varmista, että näet GitHubissa molemmat brachit

Kloonaa GitHub-repositoriosta koneellesi toinen klooni:

$ git clone git@github.com:omatunnus/palautusrepositorio.git kopio
$ cd kopio

simuloimme tällä tilannetta, missä GitHubissa olevalla koodilla on kaksi yhtäaikaista käyttäjää

Seuraavassa tästä toisesta kloonista käytetään nimeä kopio.

Katso komennolla git branch mitä brancheja kopiossa on paikallisesti näkyvissä

  • Oletusarvoisesti mukana tulee kloonatessa ainoastaan main

Kopio tietää kuitenkin kaikki GitHubin haarat. Ne ovat siellä niin sanottuina etähaaroina (remote-tracking branch), jotka näet komennolla git branch -r. Träkkäävä haara tehdään etähaaran pohjalta:

Mitä etähaarat ovat?

Etähaarat, esim. origin/main ja origin/bugikorjaus, ovat paikallisen repositoriosi kirjanmerkkejä siitä, missä commitissa GitHubin haarat olivat, kun viimeksi olit yhteydessä GitHubiin. Ne ovat tallessa omalla koneellasi, eivätkä ne ole yhteys GitHubiin. Ne päivittyvät vain komennoilla git clone, git fetch, git pull ja git push, joten muulloin ne voivat olla vanhentuneita.

Etähaaroihin ei voi commitoida. Jos haluat tehdä muutoksia, tehdään etähaarasta paikallinen träkkäävä haara (tracking branch). Se on tavallinen paikallinen haara, joka on kytketty etähaaraan. Kytkennän ansiosta git pull ja git push toimivat ilman lisäparametreja, ja git status kertoo esim. “Your branch is ahead of ‘origin/main’ by 1 commit”. Vertailu tehdään etähaaraan eikä suoraan GitHubiin.

Tehtävässä on siis kolme eri asiaa:

Mikä Missä Esimerkki
GitHubin haara (remote branch) GitHubissa bugikorjaus GitHubissa
etähaara (remote-tracking branch) omalla koneella, vain luettavissa origin/bugikorjaus
träkkäävä haara (tracking branch) omalla koneella, tavallinen haara paikallinen bugikorjaus

Tee kopioon haara 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” haaraan tiedosto changelog.txt

Lisää ja commitoi muutow versionhallintaa ja pushaa haara GitHubiin

Tarkastele GitHub-repositoriota selaimella, varmista että haara päivittyy

Tee kopioon uusi branch 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!")

Lisää ja commitoi sekä pushaa kopion branchin tulo muutokset GitHubiin ja varmista, että ne näkyvät siellä

  • Pushatessa saatat saada virheilmoituksen, ilmoitus kertoo mitä tulee tehdä

Mene GitHub-repositorion alkuperäiseen paikalliseen kopioon

  • Äsken luotu branch ei ole vielä alkuperäisessä kopiossa

Tee alkuperäiseen repositorioon branchia tulo träkkäävä branch

  • Huom: Joudut tekemään ensin komennon git fetch, jotta paikallinen kopio pääsee jyvälle siitä että GitHubiin on lisätty tavaraa

Tilanne näyttää nyt seuraavalta. Haara tulo kulki kopiosta GitHubin kautta alkuperäiseen: git push vei sen GitHubiin, git fetch toi alkuperäiseen etähaaran origin/tulo, ja sen pohjalta tehtiin paikallinen träkkäävä haara:

Mergaa haara tulo haaraan main ja tuhoa haara tulo sekä paikallisesti että GitHubista

Tee nyt uusi haara, nimeltään osamaara, lisää branchiin tiedosto osamaara.py ja pushaa se GitHubiin

Mene jälleen hetki sitten luotuun repositorion klooniin ja haaraan main

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 haara tulo löytyy vielä paikallisesti, kun taas uutta branchia osamaara ei paikallisesti vielä ole.

Kuvana tilanne näyttää seuraavalta:

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. feature-branchit:

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.

14. Git: epäajantasaisen kloonin pushaaminen [versionhallinta]

Demonstroidaan vielä (viime viikon tehtävässä 11 mainittu) usein esiintyvä tilanne, jossa epäajantasaisen repositorion pushaaminen GitHubissa olevaan etärepositorioon epäonnistuu.

Mene edellisen tehtävän alkuperäisen kloonin main-haaraan, tee jokin muutos, commitoi ja pushaa se GitHubiin

Mene kopion haaraan main 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.

Toimi yllä kuvatulla tavalla ja varmista, että tekemäsi muutokset menevät GitHubiin

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/ohtu-2026