Ensimmäinen askel luotaessa CRUD-toimintoja on luoda funktio, joka hakee seuraavan vapaan ID:n tietokannasta. Tämä voidaan toteuttaa seuraavasti:

python
def get_next_id(): try:
with open(DATABASE_FILENAME, "r") as csvfile:
reader = csv.DictReader(csvfile) max_id =
max(int(row["id"]) for row in reader) return max_id + 1 except (FileNotFoundError, ValueError): return 1

Tässä funktiossa luetaan CSV-tiedosto ja etsitään suurin käytössä oleva ID, jonka jälkeen palautetaan seuraava käytettävissä oleva ID. Jos tiedostoa ei ole olemassa tai se on tyhjä, palautetaan ID-arvo 1, mikä aloittaa numeroinnin alusta.

Seuraavaksi luodaan funktio, joka kirjoittaa uuden tehtävän tietokantaan:

python
def write_task_into_csv(task: TaskWithID):
with open(DATABASE_FILENAME, mode="a", newline="") as file: writer = csv.DictWriter(file, fieldnames=column_fields) writer.writerow(task.model_dump())

Tässä funktiossa tehtävä tallennetaan CSV-tiedostoon, joka toimii tietokannan kaltaisena rakenteena. Tehtävän tiedot kirjoitetaan CSV-riviksi, jossa kenttäkokonaisuus määritellään column_fields-muuttujassa.

Tämän jälkeen voidaan luoda funktio, joka luo uuden tehtävän:

python
def create_task(task: Task) -> TaskWithID:
id = get_next_id() task_with_id = TaskWithID(id=id, **task.model_dump()) write_task_into_csv(task_with_id) return task_with_id

Tässä funktiossa luodaan uusi ID get_next_id-funktion avulla, yhdistetään tämä ID alkuperäisiin tehtävän tietoihin ja tallennetaan uusi tehtävä CSV-tiedostoon.

Seuraavaksi tarvitaan funktio tehtävän muokkaamiseen:

python
def modify_task(id: int, task: dict) -> Optional[TaskWithID]: updated_task: Optional[TaskWithID] = None tasks = read_all_tasks() for number, task_ in enumerate(tasks): if task_.id == id: tasks[number] = updated_task = task_.model_copy(update=task)
with open(DATABASE_FILENAME, mode="w", newline="") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=column_fields) writer.writeheader()
for task in tasks: writer.writerow(task.model_dump()) if updated_task: return updated_task

Tässä funktiossa etsitään muokattava tehtävä ID:n perusteella ja päivitetään se annettujen parametrien mukaan. Tämän jälkeen koko tehtävälista kirjoitetaan uudelleen CSV-tiedostoon.

Tehtävän poistaminen onnistuu seuraavalla tavalla:

python
def remove_task(id: int) -> bool: deleted_task: Optional[Task] = None tasks = read_all_tasks()
with open(DATABASE_FILENAME, mode="w", newline="") as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=column_fields) writer.writeheader()
for task in tasks: if task.id == id: deleted_task = task continue writer.writerow(task.model_dump()) if deleted_task: dict_task_without_id = deleted_task.model_dump() del dict_task_without_id["id"] return Task(**dict_task_without_id)

Tässä funktiossa etsitään poistettava tehtävä ID:n avulla ja poistetaan se tehtävälistasta. Poistettu tehtävä tallennetaan vielä erilliseen muuttujaan ilman ID:tä ja palautetaan uudessa muodossa.

Näiden perustoimintojen jälkeen voidaan siirtyä API:n luomiseen FastAPI:lla. FastAPI mahdollistaa CRUD-toimintojen tarjoamisen verkon yli luomalla reittejä (endpoints) HTTP-pyyntöjen käsittelemiseksi. Yksinkertaisimmillaan voidaan luoda seuraavat reitit:

  • Listaa tehtävät (GET /tasks): Tämä reitti palauttaa listan kaikista tehtävistä.

  • Hae tehtävä (GET /tasks/{task_id}): Tämä reitti hakee yksittäisen tehtävän ID:n perusteella.

  • Luo tehtävä (POST /task): Tämä reitti lisää uuden tehtävän.

  • Päivitä tehtävä (PUT /tasks/{task_id}): Tämä reitti muokkaa olemassa olevaa tehtävää.

  • Poista tehtävä (DELETE /tasks/{task_id}): Tämä reitti poistaa tehtävän.

FastAPI:n reitit voivat hyödyntää Pythonin tyypityksiä, mikä helpottaa pyynnön ja vastauksen validointia sekä datan serialisointia. Esimerkiksi seuraava koodi luo reitin, joka listaa kaikki tehtävät:

python
from fastapi import FastAPI from models import TaskWithID from operations import read_all_tasks app = FastAPI() @app.get("/tasks", response_model=list[TaskWithID]) def get_tasks(): tasks = read_all_tasks() return tasks

Tässä reitissä read_all_tasks-funktio palauttaa kaikki tehtävät, ja FastAPI huolehtii niiden serialisoinnista ja palautuksesta asiakkaalle.

Vastaavasti yksittäisen tehtävän hakeminen onnistuu näin:

python
@app.get("/task/{task_id}")
def get_task(task_id: int): task = read_task(task_id) if not task: raise HTTPException(status_code=404, detail="task not found") return task

API:n testaaminen on tärkeää varmistaakseen, että kaikki toiminnot toimivat odotetusti. FastAPI tukee pytestin kaltaisia testauskehityksiä, joita voidaan käyttää API-pyyntöjen testaamiseen ja validointiin. On suositeltavaa luoda erillinen testitietokanta, jotta tuotantotietokannan tietoja ei vahingossa muokata tai tuhota.

Erityisesti RESTful API:n suunnittelussa on tärkeää noudattaa oikeita käytäntöjä, kuten selkeästi määriteltyjä reittejä ja oikeita HTTP-menetelmiä, jotta API on intuitiivinen ja helposti käytettävä. FastAPI:n dokumentointityökalut, kuten automaattisesti luodut OpenAPI-dokumentit, auttavat testaamista ja dokumentointia kehityksen aikana.

Miten turvata RESTful API OAuth2:lla FastAPI:ssa?

FastAPI on tehokas ja monipuolinen kehys RESTful API -palveluiden rakentamiseen. Sen avulla voidaan helposti luoda turvallisia API-päätteitä, jotka hyödyntävät OAuth2-tunnistautumista. OAuth2 on suosittu avoin standardi, joka mahdollistaa sovellusten suojatun kommunikoinnin ilman, että käyttäjän salasanaa täytyy jakaa. Tässä osassa tutustumme siihen, miten OAuth2:ta käytetään FastAPI:ssa, ja miten sen avulla voidaan suojata API-päätteet tehokkaasti.

Ensimmäinen askel on luoda tunnistautumispiste, joka vastaanottaa käyttäjän kirjautumistiedot ja palauttaa turvallisen tunnuksen. FastAPI:n OAuth2PasswordRequestForm luokka tarjoaa helpon tavan käsitellä kirjautumispyynnöt. Käyttäjänimi ja salasana vastaanotetaan ja tarkistetaan tietokannan vastineista. Jos tiedot ovat virheellisiä, API palauttaa virheen. Jos käyttäjätiedot ovat oikeat, sovellus luo ja palauttaa OAuth2-tunnuksen, joka voidaan liittää myöhempiin pyyntöihin.

Tunnuksen luomisen jälkeen voidaan siirtyä turvattujen päätteiden toteuttamiseen. Tällöin hyödyntämämme Depends()-funktio mahdollistaa riippuvuuksien injektoinnin FastAPI:ssa, jolloin tunnistusprosessi voidaan siirtää erilliselle funktiolle, joka tarkistaa tunnuksen ja palauttaa käyttäjätiedot. Tämä estää luvattoman pääsyn API:n resursseihin ja varmistaa, että vain oikeudet saaneet käyttäjät voivat käyttää suojattuja päätteitä.

Esimerkiksi get_user_from_token-funktio tarkistaa käyttäjän tunnuksen ja palauttaa käyttäjäobjektin. Jos tunnus on virheellinen, se heittää HTTPException-poikkeuksen, joka estää pääsyn suojattuihin tietoihin. Tämä on keskeinen osa FastAPI:n turvatoimintaa, joka mahdollistaa dynaamisen ja joustavan suojauksen ilman, että käyttäjän tarvitsee manuaalisesti huolehtia joka kerta tunnuksen tarkistuksesta.

Kun turvattu päätepiste on luotu ja tunnistus toimii oikein, voidaan siirtyä testaamaan sitä käytännössä. FastAPI tarjoaa interaktiivisen dokumentaation Swaggerin ja Redocin avulla, jonka kautta voi testata API:n toimivuutta ja varmistaa, että autentikointi toimii odotetusti. Näiden työkalujen avulla kehittäjät voivat helposti tarkistaa, että vain validoitu käyttäjä saa käyttöönsä suojatut resurssit.

Lisäksi OAuth2-tunnukselle voidaan määrittää "scope"-parametri, joka määrittää, mitä oikeuksia tunnus antaa käyttäjälle. Tämä on tärkeä osa OAuth2:n käyttöä, sillä se mahdollistaa erilaisten roolien ja käyttöoikeuksien hallinnan. FastAPI:ssa "scope" määritellään sanakirjana, jossa avaimet ovat roolien nimiä ja arvot niiden kuvauksia. Tämä antaa kehittäjille joustavuutta käyttöoikeuksien hallintaan ja lisää tietoturvaa, sillä vain tietyt käyttäjät voivat suorittaa tiettyjä toimintoja.

Yksi keskeinen etu OAuth2:n käyttöönotossa on sen skaalautuvuus ja joustavuus. FastAPI:n dokumentaatio ja viralliset resurssit tarjoavat tarkempia esimerkkejä siitä, miten OAuth2:n käyttöä voidaan laajentaa ja mukauttaa erilaisiin tarpeisiin. OAuth2:n avulla voidaan rakentaa hyvin suojattuja ja käyttäjäystävällisiä rajapintoja, joissa käyttäjä voi olla varma siitä, että hänen tietonsa ovat turvassa.

On tärkeää huomata, että OAuth2 ei ole pelkästään salasanan ja tunnuksen varassa oleva järjestelmä. Se on laajempi malli, joka mahdollistaa monitasoisen valvonnan ja autorisoinnin. Vaikka tunnistetiedot ovat tärkeä osa järjestelmää, OAuth2:n käyttö ulottuu myös muihin valvontarakenteisiin, kuten rooleihin ja pääsyyn resursseihin tietyissä tilanteissa. Tämä lisää järjestelmän joustavuutta ja turvaa.

OAuth2:n integrointi FastAPI:n kanssa on suhteellisen yksinkertaista, mutta se vaatii huolellista suunnittelua ja toteutusta. Tärkeintä on varmistaa, että kaikki API-päätteet, jotka tarvitsevat suojaa, on asianmukaisesti määritelty OAuth2:n avulla ja että oikeudet ja roolit on määritelty selkeästi. Tämä varmistaa, että järjestelmä on turvallinen ja että vain valtuutetut käyttäjät pääsevät käsiksi sensitiivisiin tietoihin.

Lopuksi, kun toteutetaan OAuth2-tunnistautumista FastAPI:ssa, on syytä tutustua myös siihen, kuinka dokumentointi voidaan räätälöidä juuri omiin tarpeisiin. FastAPI tarjoaa joustavat työkalut API:n dokumentoinnin hallintaan, mikä voi helpottaa kehitystä ja käyttäjien ymmärrystä sovelluksen toiminnoista. API:n dokumentoinnin räätälöinti on tärkeä osa kehitystyötä, sillä se voi parantaa käyttäjäkokemusta ja auttaa välttämään virheitä ja väärinkäsityksiä.

Miten integroida GitHubin OAuth2-todennus sovellukseen: Käytännön opas

OAuth2-todennus on vakiintunut menetelmä käyttäjien todennukseen kolmansien osapuolten palveluilla, kuten GitHubilla. Sen avulla voidaan tarjota käyttäjille sujuva ja turvallinen kirjautuminen ilman, että heidän tarvitsee luoda uutta tunnusta sovellukseen. Tämä on erityisen hyödyllistä SaaS (Software as a Service) -sovelluksille, jotka haluavat hyödyntää käyttäjien olemassa olevia tilitietoja, kuten GitHubin, Google tai Twitterin kautta. Tässä käsitellään, kuinka GitHubin OAuth2-todennusta voidaan käyttää FastAPI-sovelluksessa.

Ensimmäinen askel on rekisteröidä OAuth2-sovellus GitHubissa. Siirry GitHubin henkilökohtaiselle sivulle, napsauta oikeassa yläkulmassa olevaa profiilikuvaketta ja siirry asetuksiin. Sieltä valitse Developer settings ja sen jälkeen OAuth Apps. Luo uusi OAuth-sovellus täyttämällä lomake seuraavilla tiedoilla:

  • Sovelluksen nimi: Esimerkiksi SaasFastAPIapp.

  • Etusivun URL: Sovelluksesi kotisivun URL-osoite, kuten http://localhost:8000/.

  • Valtuutuspalautus-URL: Tämä on sovelluksen päätepiste, johon GitHub ohjaa käyttäjän onnistuneen kirjautumisen jälkeen. Esimerkiksi http://localhost:8000/github/auth/token.

Kun sovellus on rekisteröity, GitHub antaa sinulle client ID:n ja client secretin. Näitä tietoja tarvitaan myöhemmin, jotta voidaan toteuttaa GitHubin OAuth2-todennus FastAPI-sovelluksessa.

GitHubin OAuth2-todennuksen toteuttaminen FastAPI:lla

Toteutetaan kolmannen osapuolen todennus luomalla uusi moduuli nimeltä third_party_login.py, johon tallennetaan tarvittavat tiedot ja apufunktiot GitHubin todennukseen. Tässä moduulissa määritellään GitHubin OAuth2-sovelluksen tiedot:

python
GITHUB_CLIENT_ID = "your_github_client_id" GITHUB_CLIENT_SECRET = "your_github_client_secret" GITHUB_REDIRECT_URI = "http://localhost:8000/github/auth/token" GITHUB_AUTHORIZATION_URL = "https://github.com/login/oauth/authorize"

GITHUB_CLIENT_ID ja GITHUB_CLIENT_SECRET saadaan GitHubin rekisteröimältä sovellukselta. Tässä vaiheessa on tärkeää huomioida, että tuotantoympäristössä ei tulisi koskaan kovakoodata käyttäjätunnuksia tai asiakastunnuksia koodiin.

Seuraavaksi luodaan apufunktio resolve_github_token, joka käsittelee GitHubin palauttaman access tokenin ja palauttaa käyttäjän tiedot:

python
import httpx
from fastapi import Depends, HTTPException from sqlalchemy.orm import Session from models import User, get_session from operations import get_user def resolve_github_token(access_token: str = Depends(OAuth2()), session: Session = Depends(get_session)) -> User: user_response = httpx.get("https://api.github.com/user", headers={"Authorization": access_token}).json() username = user_response.get("login", "") user = get_user(session, username) if not user: email = user_response.get("email", "") user = get_user(session, email) if not user: raise HTTPException(status_code=403, detail="Token not valid") return user

GitHubiin ohjaaminen ja palautus

Nyt luodaan päätepisteet, jotka ohjaavat käyttäjän GitHubin todennussivulle ja käsittelevät GitHubista saadun tokenin. Uusi moduuli, kuten github_login.py, määrittelee päätepisteen, joka palauttaa GitHubin kirjautumissivun URL-osoitteen:

python
from fastapi import APIRouter from security import Token from third_party_login import GITHUB_AUTHORIZATION_URL, GITHUB_CLIENT_ID router = APIRouter() @router.get("/auth/url") def github_login(): return { "auth_url": GITHUB_AUTHORIZATION_URL + f"?client_id={GITHUB_CLIENT_ID}" }

Kun käyttäjä klikkaa tätä linkkiä, hän ohjataan GitHubin kirjautumissivulle, jossa hän voi antaa sovellukselle luvan käyttää hänen GitHub-tiliään. On tärkeää huomata, että tämä ohjausprosessi tapahtuu frontendissä, eikä tätä kirjaa käsitellä.

GitHubin todentamisen jälkeen käyttäjä ohjataan takaisin sovellukseen, mutta koska palautuspäätepisteemme ei ole vielä luotu, hänet ohjataan virhesivulle. Luodaan siis päätepiste, joka käsittelee palautetun koodin ja vaihtaa sen käyttöoikeustunnukseen (access token):

python
@router.get("/github/auth/token", response_model=Token)
async def github_callback(code: str): token_response = httpx.post( "https://github.com/login/oauth/access_token", data={ "client_id": GITHUB_CLIENT_ID, "client_secret": GITHUB_CLIENT_SECRET, "code": code, "redirect_uri": GITHUB_REDIRECT_URI }, headers={"Accept": "application/json"} ).json() access_token = token_response.get("access_token") if not access_token: raise HTTPException(status_code=401, detail="User not registered") token_type = token_response.get("token_type", "bearer") return {"access_token": access_token, "token_type": token_type}

Käyttäjän tunnistaminen

Kun käyttäjä on kirjautunut GitHubin kautta, voidaan käyttää saatuja tunnistustietoja sovelluksessa. Luo päätepiste, joka hyväksyy GitHubin antaman tokenin ja tunnistaa käyttäjän:

python
from third_party_login import resolve_github_token
@router.get("/home") def homepage(user: UserCreateResponse = Depends(resolve_github_token)): return {"message": f"logged in {user.username}!"}

Tämä päätepiste palauttaa viestin, joka kertoo, että käyttäjä on onnistuneesti kirjautunut sisään GitHubin kautta.

Testaus

Kun kaikki on oikein määritelty ja palvelin on käynnistetty, voit testata kirjautumista. Käytä ensin /auth/url -päätepistettä saadaksesi kirjautumislinkin GitHubiin. Kirjaudu GitHubiin ja saat takaisin tokenin, jonka voit käyttää testaamalla /home -päätepistettä.

Muiden tarjoajien integrointi

Vaikka tässä keskityimme GitHubin OAuth2-todennukseen, muiden tarjoajien kuten Googlen ja Twitterin OAuth2-todennus toimii samankaltaisella tavalla. Prosessi eroaa lähinnä siinä, miten kunkin tarjoajan sovellusrekisteröinti ja API:t ovat toteutettu.

Tärkeää on myös huomioida, että vaikka GitHubin todennuksessa käytetään yksinkertaista URL-osoitteen muotoa, muiden palveluiden kanssa voi olla pieniä eroja, jotka tulee huomioida dokumentaation avulla.