Testikattavuus on mittari, jota käytetään ohjelmistotestauksessa arvioimaan, kuinka laajasti ohjelman lähdekoodia suoritetaan tietyn testisuiteen aikana. Käytettäessä pytestiä testikattavuuden mittaamiseen, jos et ole asentanut tarvittavia paketteja requirements.txt-tiedostosta, sinun täytyy asentaa pytest-cov-paketti:

ruby
$ pip install pytest-cov

Testikattavuus toimii yksinkertaisesti: sinun täytyy määrittää lähdekoodin juurihakemisto, tässä tapauksessa protoapp-hakemisto, pytestin --cov-parametrille sekä testihakemisto, tässä tapauksessa tests. Komento on seuraava:

lua
$ pytest --cov protoapp tests

Tulosteessa näkyy taulukko, jossa listataan kattavuusprosentti jokaiselle moduulille:

markdown
Nimi Stmts Miss Cover
---------------------------------------------------- protoapp\database.py 16 0 100% protoapp\main.py 37 4 89% protoapp\schemas.py 8 8 0% ---------------------------------------------------- YHTEENSÄ 61 12 80%

Tässä esimerkissä .coverage-niminen tiedosto luodaan. Se on binaaritiedosto, joka sisältää testikattavuuden tiedot ja jota voidaan käyttää raporttien luomiseen lisätyökaluilla. Esimerkiksi, jos suoritat:

ruby
$ coverage html

Se luo htmlcov-kansion, jossa on index.html-sivu, joka näyttää kattavuussivun ja voit tarkastella sitä avaamalla sen selaimessa.

Testikattavuuden arvioimisessa ja yksikkötestien suorittamisessa pytestin avulla on olemassa monia vaihtoehtoja, ja niitä voit tutkia virallisista dokumentaatioista.


Lokiviestien käsittely ohjelmistokehityksessä

Lokiviestien tehokas hallinta ohjelmistokehityksessä ei pelkästään auta virheiden tunnistamisessa, vaan se tarjoaa myös arvokasta tietoa käyttäjävuorovaikutuksesta, järjestelmän suorituskyvystä ja mahdollisista tietoturvauhista. Se on keskeinen työkalu auditointiin, sääntöjen noudattamiseen ja resurssien käytön optimointiin, mikä parantaa ohjelmiston luotettavuutta ja skaalautuvuutta.

Tässä esitetään, kuinka tehokkaasti implementoidaan lokijärjestelmä FastAPI-sovelluksessa API-kutsujen seurantaan. Ensinnäkin, tarvitsemme perusominaisuuksia Pythonin lokitusjärjestelmästä. Vaikka esimerkki on yksinkertainen, virallisesta dokumentaatiosta voi tutustua tarkemmin liittyviin käsitteisiin, kuten logger, handler, formatter ja log level.

FastAPI:hin lokien lisäämiseksi varmistamme, että sovelluksesi on käynnissä. Voimme käyttää protoapp-hakemistoa, jonka olemme luoneet koko tämän luvun ajan. Ensimmäiseksi määrittelemme loggerin ja asetamme sen INFO-tasolle:

python
import logging
client_logger = logging.getLogger("client.logger") logger.setLevel(logging.INFO)

Koska haluamme lähettää lokiviestit sekä konsoliin että tallentaa ne tiedostoon, määrittelemme kaksi erillistä handleria:

python
console_handler = logging.StreamHandler()

Tämä handler lähettää lokiviestit konsoliin. Seuraavaksi määrittelemme värillisen formatterin ja liitämme sen juuri luotuun handleriin:

python
from uvicorn.logging import ColourizedFormatter
console_formatter = ColourizedFormatter( "%(levelprefix)s CLIENT CALL - %(message)s", use_colors=True ) console_handler.setFormatter(console_formatter)

Formatter tekee lokiviesteistä samankaltaisia kuin FastAPI:in oletusloggerin viestit. Sen jälkeen lisätään tämä handler loggeriin:

python
client_logger.addHandler(console_handler)

Nyt lokitiedot tulostuvat konsoliin. Toistamme samat vaiheet toisen handlerin luomiseksi, joka tallentaa lokiviestit tiedostoon:

python
from logging.handlers import TimedRotatingFileHandler
file_handler = TimedRotatingFileHandler("app.log") file_formatter = logging.Formatter( "time %(asctime)s, %(levelname)s: %(message)s", datefmt="%Y-%m-%d %H:%M:%S", ) file_handler.setFormatter(file_formatter) client_logger.addHandler(file_handler)

Tässä vaiheessa olemme luoneet lokijärjestelmän, joka tulostaa viestit konsoliin ja tallentaa ne tiedostoon. Lokiviestien lisääminen koodiin onnistuu seuraavasti, käyttäen dedikoitua middlewarea main.py-moduulissa:

python
from protoapp.logging import client_logger @app.middleware("http")
async def log_requests(request: Request, call_next):
client_logger.info(
f"method: {request.method}, " f"call: {request.url.path}, " f"ip: {request.client.host}" ) response = await call_next(request) return response

Kun tämä on tehty ja palvelin on käynnistetty:

ruby
$ uvicorn protoapp.main:app

Voit soittaa mitä tahansa sovelluksessa määriteltyjä päätepisteitä ja nähdä lokiviestit terminaalissa sekä tiedostossa app.log.


Lokitusstrategian tärkeys

Lokitusstrategian määrittäminen vaatii oman opaskirjan, mutta kun lisäät lokijärjestelmän sovellukseen, on tärkeää noudattaa muutamia perusperiaatteita:

  • Käytä oikeita lokitasoja: Perinteinen lokitasojärjestelmä koostuu neljästä tasosta: INFO, WARNING, ERROR, CRITICAL. Nämä tasot auttavat viestien priorisoinnissa.

  • Johdonmukainen lokimuoto: Säilytä yhtenäinen lokimuoto koko sovelluksessa. Tämä auttaa viestien jäsentämisessä ja lokianalyysien automatisoinnissa.

  • Sisällytä kontekstuaalista tietoa: Tärkeä tieto, kuten käyttäjä-ID tai transaktio-ID, auttaa virheiden jäljittämisessä ja debuggauksessa.

  • Vältä arkaluonteista tietoa: Älä koskaan lokeita salasanoja, API-avaimia tai henkilökohtaisia tietoja. Jos on tarpeen, peitä tai tiivistä nämä tiedot.

  • Tehokas lokitus: Pidä mielessä lokituksen suorituskykyvaikutukset. Liiallinen lokitus voi hidastaa sovellusta ja tuottaa turhia viestejä, jotka tekevät hyödyllisten tietojen etsimisestä vaikeaa.

Miten käyttää riippuvuuksia ja luokkia FastAPI:ssa räätälöityjen välimuistien ja lokalisoinnin hallintaan

FastAPI tarjoaa tehokkaita työkaluja ja joustavia mekanismeja, kuten riippuvuudet ja välimuistit, joiden avulla voidaan kehittää skaalautuvia ja joustavia sovelluksia. Näiden ominaisuuksien avulla voidaan hallita monimutkaisia sovellusrakenteita, kuten kielen lokalisaatio, autentikointi ja virheenkäsittely. Tässä artikkelissa käymme läpi, miten luodaan luokkia, joita voidaan käyttää riippuvuuksina FastAPI:ssa, sekä tarkastelemme, miten kehitetään omia välimuistiratkaisuja ja hallitaan käyttäjän kielen valinta HTTP-otsikoiden avulla.

Kun rakennetaan API:ta FastAPI:lla, on usein tarpeen määritellä erillisiä riippuvuuksia, jotka voivat olla joko yksinkertaisia parametreja tai monimutkaisempia objekteja. FastAPI mahdollistaa luokkien käytön riippuvuuksina, mikä parantaa koodin luettavuutta ja ylläpidettävyyttä. Tällöin voidaan yhdistää useita parametreja yhdeksi luokaksi ja käyttää tätä luokkaa riippuvuuden hallintaan. Tämä lähestymistapa on erityisen hyödyllinen silloin, kun tarvitsemme useita parametreja, jotka liittyvät toisiinsa, mutta joita on vaikea käsitellä erillisinä osina.

Esimerkiksi, jos haluamme luoda uuden päätepisteen, joka käyttää kaikkia aikarajoihin, kategorioihin ja koodiin liittyviä parametreja, voimme luoda luokan, joka ryhmittelee kaikki nämä tiedot. Tämä helpottaa riippuvuuden käsittelyä ja parantaa sovelluksen selkeyttä ja laajennettavuutta. Luokan määrittäminen riippuvuudeksi FastAPI:ssa on yksinkertaista ja se tarjoaa selkeämmän tavan hallita useita parametreja yhdessä.

Kun siirrytään edistyneempiin ominaisuuksiin, kuten välimuistien luomiseen, FastAPI tarjoaa myös joustavat mekanismit tälle. Välimuisti on sovelluksen osa, joka mahdollistaa saapuvien pyyntöjen ja lähtevien vastausten muokkaamisen. Tämä voi olla hyödyllistä esimerkiksi autentikoinnin, lokituksen tai virheenkäsittelyn toteuttamisessa. Välimuistin luominen FastAPI:ssa edellyttää oman välimuistin luokan määrittämistä, joka perii BaseHTTPMiddleware-luokan Starlette-kirjastosta. Välimuistiluokan avulla voidaan tarkastella saapuvia pyyntöjä, kerätä tietoja, kuten asiakas-IP-osoite ja pyydetty polku, ja tehdä tarvittavat toimenpiteet ennen kuin pyyntö etenee varsinaiselle päätepisteelle.

Esimerkiksi, jos haluamme tulostaa asiakkaan IP-osoitteen ja pyydetyn polun lokiin, voimme luoda seuraavanlaisen välimuistin:

python
import logging
from fastapi import Request from starlette.middleware.base import BaseHTTPMiddleware logger = logging.getLogger("uvicorn.error") class ClientInfoMiddleware(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next): host_client = request.client.host requested_path = request.url.path method = request.method
logger.info(f"host client {host_client} requested {method} {requested_path} endpoint")
return await call_next(request)

Tässä esimerkissä määrittelemme välimuistiluokan, joka tarkastelee saapuvia pyyntöjä ja tulostaa niistä tietoja lokiin. Tämä voi olla erittäin hyödyllistä, kun halutaan tarkkailla käyttäjätoimintaa tai seurata API-pyyntöjen virheitä.

Kun välimuisti on luotu, se lisätään FastAPI-sovellukseen add_middleware-metodilla. Tämä metodi lisää välimuistin sovellukseen, ja kaikki saapuvat pyynnöt kulkevat sen läpi ennen päätepisteen suorittamista.

python
from fastapi import FastAPI from app.middleware import ClientInfoMiddleware app = FastAPI() app.add_middleware(ClientInfoMiddleware)

Tämä perusvälimuisti antaa mahdollisuuden kerätä tietoja pyynnöistä ja käsitellä niitä sovelluksen tarpeiden mukaan. Välimuistin monimutkaisuutta voidaan helposti kasvattaa lisäämällä tarkempia toimintoja, kuten pyyntöjen uudelleenohjaaminen IP-osoitteen perusteella tai IP-osoitteiden estäminen.

Kun sovellus kasvaa ja kansainvälistyy, on tärkeää ottaa huomioon myös kielten lokalisaatio ja sovelluksen mukauttaminen eri alueille ja kulttuureille. Tämä voidaan toteuttaa käyttämällä Accept-Language-otsikkoa, joka on yleinen tapa kertoa palvelimelle käyttäjän kieli ja alue. Tämä otsikko on keskeinen osa kansainvälistämistä (i18n) ja lokalisaatiota (l10n), sillä sen avulla voidaan määrittää, mitä kieltä sovellus käyttää ja miten sisältöä mukautetaan.

FastAPI:ssa kansainvälistämistä ja lokalisaatiota voidaan hallita luomalla erillinen funktio, joka käsittelee Accept-Language-otsikon ja valitsee parhaiten tuetun kielen. Tämän saavuttamiseksi on tarpeen määritellä tuetut kielet ja käyttää niiden perusteella tarvittavat käännökset ja formaatit.

Esimerkiksi:

python
SUPPORTED_LOCALES = ['en_US', 'fr_FR']
def get_best_locale(accept_language_header, supported_locales): # Funktion logiikka kielen valitsemiseksi return 'en_US'

Tämä yksinkertainen esimerkki antaa perusidean siitä, miten kielivalinta tehdään ja miten se voidaan yhdistää sovelluksen muihin osiin, kuten käyttäjäkohtaisiin kokemuksiin ja sisällön käännöksiin.

Kansainvälistämisen ja lokalisaation toteuttaminen on tärkeää erityisesti globaalisti toimiville sovelluksille, jotka palvelevat käyttäjiä eri puolilta maailmaa. FastAPI:n avulla tämä prosessi on yksinkertaistettu ja voidaan helposti mukauttaa tarpeen mukaan.

On myös tärkeää huomioida, että välimuistin ja kielivalinnan lisäksi sovelluksen skaalautuvuus, tietoturva ja virheenkäsittely ovat avainasemassa, kun sovellus kasvaa ja monimutkaistuu. Näiden eri osien hallinta FastAPI:ssa mahdollistaa erittäin tehokkaan ja joustavan sovelluskehityksen, jossa voidaan helposti lisätä uusia ominaisuuksia ilman, että koodin selkeys ja ylläpidettävyys kärsivät.

Kuinka suojata WebSocket-yhteys OAuth2:lla FastAPI:ssa

OAuth2-turvallisuusprotokolla on yhä enemmän käytössä nykypäivän web-sovelluksissa, ja se tuo lisäsuojaa erityisesti, kun käsitellään arkaluonteista dataa ja reaaliaikaisia yhteyksiä, kuten WebSocket-yhteyksiä. WebSocketit mahdollistavat jatkuvan yhteyden ylläpitämisen asiakas- ja palvelinpuolen välillä, mutta kuten muissakin yhteyksissä, myös ne voivat olla alttiita tietoturvauhkille, jos niitä ei suojata asianmukaisesti. Tässä esimerkissä käytämme FastAPI:ta ja OAuth2-protokollaa WebSocket-yhteyksien suojaamiseen.

Aluksi luomme oauth2_scheme_for_ws-objektin, joka määrittelee OAuth2-todennusprosessin WebSocket-yhteyksiä varten. Tämä objektin määrittely on seuraava:

python
from app.ws_password_bearer import OAuth2WebSocketPasswordBearer
oauth2_scheme_for_ws = OAuth2WebSocketPasswordBearer( tokenUrl="/token" )

tokenUrl-argumentti määrittelee takaisinsoittopisteen, jota käytetään tokenin hakemiseen. Tämä URL-osoite tulee rakentaa käyttämäsi tokenin ratkaisemismenetelmän mukaan.

Seuraavaksi luomme funktion, joka hakee käyttäjänimen tokenista:

python
def get_username_from_token( token: str = Depends(oauth2_scheme_for_ws), ) -> str: user = fake_token_resolver(token) if not user: raise WebSocketException( code=status.HTTP_401_UNAUTHORIZED, reason="Invalid authentication credentials" ) return user.username

fake_token_resolver-funktion tarkoitus on simuloida tokenin ratkaisuprosessia. Tämä funktio on löydettävissä security.py-tiedostosta GitHub-repositoriossasi. Esimerkissä käytämme kahta käyttäjää: johndoe ja janedoe, jotka toimivat testikäyttäjinä. On kuitenkin tärkeää huomata, että tämä lähestymistapa ei tarjoa oikeaa turvallisuutta ja sitä tulee käyttää vain esimerkkinä. Tuotantoympäristössä suosittelemme käyttämään JWT-tunnistautumista tai ulkopuolista palveluntarjoajaa tokenin ratkaisemiseen.

WebSocket-yhteyksien suojaaminen OAuth2:lla voidaan tehdä seuraavalla tavalla pääohjelmassa:

python
from fastapi import Depends
from app.security import get_username_from_token @app.websocket("/secured-ws") async def secured_websocket( websocket: WebSocket, username: Annotated[ get_username_from_token, Depends() ] ): # loppuosa WebSocket-pisteestä

Tässä esimerkissä olemme lisänneet OAuth2-tarkistuksen WebSocket-pisteeseen. Kun yhteyspyyntö tehdään, OAuth2-tunnistautuminen varmistaa, että käyttäjä on valtuutettu ennen kuin yhteys muodostetaan. Jos käyttäjä ei tarjoa oikeaa tunnistetta, WebSocket-yhteys hylätään ennen kättelyprosessia.

Kun kokeilet yhdistää WebSocket-pisteeseen työkalulla kuten Postman, virheelliset tunnistetiedot johtavat virheilmoitukseen, ja yhteys estetään. Tässä vaiheessa käyttäjän täytyy liittää oikea token-päätteen mukaan, jotta yhteys hyväksytään. Token voidaan hakea omasta /token-pisteestä tai käyttää esimerkin mukaista fake-token-generaattoria. Esimerkiksi käyttäjälle johndoe token voisi olla tokenizedjohndoe, ja se lisätään WebSocket-pyynnön otsikoihin seuraavasti:

makefile
Authorization: bearer tokenizedjohndoe

Kun tämä token on lisätty otsikoihin, yhteys voidaan muodostaa ja viestintä WebSocket-pisteen kanssa on mahdollista.

Tässä esitetty malli on vain yksi tapa suojata WebSocket-yhteys OAuth2:lla FastAPI:ssa. Käyttämällä tätä lähestymistapaa voit parantaa sovelluksesi tietoturvaa ja estää luvatonta pääsyä reaaliaikaisiin yhteyksiin.

Lisäksi on tärkeää huomata, että OAuth2-protokollan ja WebSocket-yhteyksien yhdistäminen on aihe, joka kehittyy jatkuvasti. Tämänkaltaisia keskusteluja voidaan seurata FastAPI:n GitHub-repositoriossa, jossa käydään läpi uusimpia kehitysaskeleita ja parannuksia OAuth2:n ja WebSocketin integroimisessa. Tällaisten turvallisuusprotokollien jatkuva kehittäminen on tärkeää, jotta sovellukset voivat vastata aina muuttuvan kyberuhkakentän haasteisiin.

Miten ASGI-middleware muokkaa pyyntöjä ja vastauksia FastAPI-sovelluksessa?

Middleware toimii FastAPI- ja Starlette-sovelluksissa keskeisenä väylänä HTTP-pyyntöjen ja -vastausten käsittelyssä. ASGI-middleware mahdollistaa sekä pyyntöjen että vastausten muokkaamisen ennen kuin ne saavuttavat varsinaisen sovelluslogiikan tai lähetetään takaisin asiakkaalle. Tällainen joustava kerros voi sisältää esimerkiksi tietoturvaominaisuuksia, logitusta, pyyntöjen validointia tai vastauksen räätälöintiä.

Esimerkkinä toimiva middleware-luokka voidaan toteuttaa Pythonissa käyttäen Starlette-tyyppejä, kuten ASGIApp, Scope, Receive ja Send. Middleware-luokka ottaa konstruktorissaan sovelluksen ja mahdollisesti muita parametreja, kuten sallitut polut, joiden kohdalla muokkaus suoritetaan. call-metodissa middleware tarkistaa, onko kyseessä HTTP-pyyntö ja kuuluuko pyyntö sallituille poluille; jos ei, pyyntö etenee käsittelemättä.

Pyyntöjen muokkaaminen edellyttää receive-korutiinin käsittelyä, joka hakee pyynnön rungon. Tämä runko voidaan esimerkiksi hashata käyttäen sha1-algoritmia, minkä jälkeen korvattu runko siirtyy eteenpäin sovellukselle. Näin middleware voi muuttaa pyynnön sisältöä, kuten suojaus- tai tunnistusmielessä. Vastaanotettu ja muokattu pyyntö välitetään FastAPI-sovellukselle sen normaalein reittiä pitkin.

Vastaavasti middleware voi käsitellä vastauksia. Se voi lisätä vasteeseen ylimääräisiä header-kenttiä tai muokata sisältöä ennen kuin vastaus lähetetään asiakkaalle. Tämä mahdollistaa esimerkiksi dynaamisen sisällön muokkauksen, suorituskyvyn optimoinnin tai auditointiin liittyvien tietojen liittämisen.

Middleware-luokan lisääminen FastAPI-instanssiin onnistuu kätevästi add_middleware-metodilla, jossa määritellään middleware-luokka ja sen parametrit. Käytännössä middleware toimii ketjuna, jossa kukin middleware käsittelee pyynnön tai vastauksen vuorollaan, ja lopputuloksena syntyy sovelluksen tai asiakkaan vastaanottama muokattu data.

Middleware-kerroksen toteuttaminen vaatii ymmärrystä ASGI-spesifikaatiosta ja Starlette-kirjaston tarjoamista tyypeistä. Lisäksi on tärkeää hallita asynkronisen Pythonin korutiinien käsittelyä, jotta middleware pystyy vastaanottamaan ja välittämään viestit oikein. ASGI tarjoaa standardin rajapinnan asynkroniseen web-palveluiden käsittelyyn, joka mahdollistaa näiden joustavien ja tehokkaiden ratkaisujen rakentamisen.

Tämän perusteella middleware ei ole pelkästään yksinkertainen välikerros, vaan keskeinen osa modernia web-sovellusarkkitehtuuria. Sen avulla kehittäjä voi tarkasti kontrolloida pyynnön kulkua, turvallisuutta ja vasteiden muokkausta ilman, että sovelluksen ydintoiminnallisuuksiin tarvitsee tehdä muutoksia.

On huomionarvoista, että middlewareä suunniteltaessa tulee kiinnittää huomiota siihen, että muutokset pysyvät ennakoitavina ja suorituskykyä ei heikennetä merkittävästi. Lisäksi pyynnön runkoa muokattaessa on varmistettava, ettei tiedon eheys tai tarvittavat otsikot menetä merkitystään. Ymmärrys siitä, mitkä HTTP-metodit sallivat rungon (kuten POST ja PUT) on olennaista, jotta muokkaus kohdistetaan oikein. Lisäksi middleware voi toimia myös turvallisuuskerroksena esimerkiksi estämällä haitalliset pyyntöjen sisällöt tai lisäämällä autentikointitietoja.

Lopuksi middleware-rakenteen joustavuus mahdollistaa monipuoliset käyttötarkoitukset: analytiikasta lokitukseen, turvallisuudesta dynaamiseen sisällönhallintaan. Tämä tekee middlewarestä välttämättömän työkalun nykyaikaisten asynkronisten web-sovellusten kehityksessä.