Ana içeriğe geç
Kesit Python
Orta · Otomasyon, web ve araçlar

Flask ile web uygulaması

Flask uygulaması kurar; route, HTTP metotları, request/response, jsonify, Jinja2 şablonları, form doğrulama, hata işleyiciler, blueprint, uygulama fabrikası, test client ve gunicorn ile dağıtımı öğrenir, çatının içini mini bir yönlendirici yazarak kavrarsın.

Uygulamada aç, 28 alıştırmayı çözÜcretsiz, hesap gerekmez, kurulum yok.

Bu sayfa dersin okuma sürümüdür. Metin bir kutuyu çalıştırmanı ya da adıma geçmeni söylüyorsa dersi uygulamada aç.

Neden önemli?

API ve web dersinde HTTP'nin temellerini (metot, durum kodu, başlık, JSON gövde) ve bir API'yi istemci tarafından tüketmeyi gördün. Şimdi masanın öbür tarafına geçiyoruz: sunucuyu sen yazacaksın. Flask, Python'un en yaygın "mikro" web çatısıdır (micro framework): çekirdeği küçüktür, yalnız yönlendirme (routing), istek/yanıt ve şablon motorunu verir; veritabanı, oturum açma, form gibi ihtiyaçları eklentilerle seçersin. Şirket içi yönetim panelleri, raporlama ekranları, makine öğrenmesi modellerini sunan küçük servisler ve prototipler için çok sık tercih edilir.

Bu dersin özel bir yanı var: Flask bir sunucu çalıştırır ve ağ ister; bu yüzden tarayıcıdaki Python motorunda çalışmaz. Gerçek Flask kodunu gri kutularda göreceksin ve adım adım kendi bilgisayarında çalıştıracaksın. Alıştırmalarda ise Flask'ın içini taklit eden küçük parçaları (yönlendirici, yol parametresi ayrıştırıcı, şablon, form doğrulayıcı) kendin yazacaksın. Bir çatının içini yazan geliştirici, onu kullanırken hata ayıklamakta hiç zorlanmaz.

İlk uygulama ve kurulum

# app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def ana_sayfa():
    return "Anadolu Market'e hoş geldin!"

@app.route("/saglik")
def saglik():
    return {"durum": "ok"}          # sözlük döndürmek otomatik JSON yanıtı üretir
  1. Proje klasörü aç ve sanal ortam (virtual environment) kur: python -m venv .venv, ardından Windows'ta .venv\Scripts\activate, macOS/Linux'ta source .venv/bin/activate.
  2. Flask'ı kur: pip install flask
  3. Yukarıdaki kodu app.py olarak kaydet.
  4. Geliştirme sunucusunu başlat: python -m flask --app app run --debug
  5. Tarayıcıda http://127.0.0.1:5000/ ve http://127.0.0.1:5000/saglik adreslerini aç. Kodu değiştirip kaydettiğinde --debug sayesinde sunucu kendini yeniden başlatır.
Kendi bilgisayarında deneFlask bu sayfadaki motorda çalışmaz. Gri kutulardaki (pre.code) kodları kendi bilgisayarında, sanal ortam içinde dene. Yeşil "Çalıştır" düğmeli kutular ise Flask'ın arkasındaki fikirleri standart kütüphane ve jinja2 ile gösterir; onları burada çalıştırabilirsin.

Flask'ın kalbi: dekoratörle yönlendirme

@app.route("/") sihirli değildir. Dekoratörler (decorator) dersinde gördüğün parametreli dekoratörün ta kendisidir: fonksiyonu bir kayda (URL kuralları tablosu) ekler ve fonksiyonu değiştirmeden geri döndürür. Gelen istekte Flask bu tabloda yol ve metodu arar, eşleşen fonksiyonu (view function) çağırır. Aşağıda bu mekanizmanın 20 satırlık bir modeli var.

class MiniFlask:
    def __init__(self):
        self.kurallar = {}                       # (metot, yol) -> fonksiyon

    def route(self, yol, methods=("GET",)):
        def kaydet(fn):
            for m in methods:
                self.kurallar[(m, yol)] = fn
            return fn                            # fonksiyonu aynen geri ver
        return kaydet

    def dispatch(self, metot, yol):
        if (metot, yol) in self.kurallar:
            return self.kurallar[(metot, yol)](), 200
        if any(y == yol for _, y in self.kurallar):
            return "Method Not Allowed", 405
        return "Not Found", 404

app = MiniFlask()

@app.route("/")
def ana_sayfa():
    return "Hoş geldin"

@app.route("/sepet", methods=["GET", "POST"])
def sepet():
    return "Sepetin"

print(app.dispatch("GET", "/"))
print(app.dispatch("POST", "/sepet"))
print(app.dispatch("DELETE", "/sepet"))
print(app.dispatch("GET", "/yok"))

404 (bulunamadı) ile 405 (metot izinli değil) arasındaki farka dikkat: yol hiç yoksa 404, yol var ama bu metotla tanımlanmamışsa 405 döner. Gerçek Flask da tam olarak böyle davranır.

Yol parametreleri ve HTTP metotları

URL'nin bir parçasını değişken olarak almak için kuralda <ad> yazarsın; tür dönüştürücü (converter) eklersen Flask değeri dönüştürür ve uymayan URL'yi daha görünüm fonksiyonuna gelmeden 404 ile reddeder.

from flask import Flask, request, abort

app = Flask(__name__)
URUNLER = {1: {"ad": "Çay", "fiyat": 85.5}, 2: {"ad": "Kahve", "fiyat": 240.0}}

@app.get("/urunler/<int:urun_id>")             # @app.route(..., methods=["GET"]) kısayolu
def urun_getir(urun_id):
    if urun_id not in URUNLER:
        abort(404)
    return URUNLER[urun_id]

@app.route("/urunler", methods=["GET", "POST"])
def urunler():
    if request.method == "POST":
        veri = request.get_json()               # JSON gövde
        yeni_id = max(URUNLER) + 1
        URUNLER[yeni_id] = veri
        return {"id": yeni_id, **veri}, 201     # (gövde, durum kodu)
    kategori = request.args.get("kategori")     # ?kategori=İçecek
    sayfa = request.args.get("sayfa", 1, type=int)
    return {"sayfa": sayfa, "urunler": list(URUNLER.values())}
DönüştürücüEşleşirÖrnek
<ad> (string)Eğik çizgi içermeyen metin/sehir/İzmir
<int:ad>Pozitif tam sayı/urunler/42
<float:ad>Ondalıklı sayı/kur/32.5
<path:ad>Eğik çizgi dahil metin/dosya/rapor/2024/ocak.pdf
<uuid:ad>UUID biçimi/siparis/3f2a...

Flask'ın yönlendiricisi (Werkzeug) kuralları düzenli ifadelere çevirerek eşler. Aynı fikri re ile kurabilirsin:

import re

DONUSTURUCU = {"int": (r"\d+", int), "str": (r"[^/]+", str)}

def kural_derle(kural):
    tipler = {}
    def degistir(m):
        tip, ad = (m.group(1) or "str"), m.group(2)
        desen, cevir = DONUSTURUCU[tip]
        tipler[ad] = cevir
        return f"(?P<{ad}>{desen})"
    regex = re.sub(r"<(?:(\w+):)?(\w+)>", degistir, kural)
    return re.compile(f"^{regex}$"), tipler

desen, tipler = kural_derle("/musteri/<int:no>/siparis/<durum>")
for yol in ["/musteri/42/siparis/kargoda", "/musteri/abc/siparis/kargoda"]:
    m = desen.match(yol)
    print(yol, "->", {k: tipler[k](v) for k, v in m.groupdict().items()} if m else "404")

request ve response

Görünüm fonksiyonu içinde request nesnesi o anki isteği temsil eder. Döndürdüğün değeri Flask bir yanıta (response) çevirir.

İstek bilgisiFlask'taDöndürülen değerFlask ne yapar?
Sorgu dizesi ?sayfa=2request.args.get("sayfa", 1, type=int)"metin"200, text/html
HTML form alanırequest.form["eposta"]{"a": 1} ya da liste200, JSON (jsonify)
JSON gövderequest.get_json()(govde, 201)Durum kodunu ayarlar
Başlıklarrequest.headers["Authorization"](govde, 200, {"X-Kaynak": "AM"})Başlık ekler
Yüklenen dosyarequest.files["fatura"]redirect(url_for("liste"))302 yönlendirme

Sorgu dizesi her zaman metin gelir; type=int verirsen dönüştürme başarısız olduğunda Flask hata vermez, varsayılanı döndürür. Aynı davranışı standart kütüphaneyle şöyle görebilirsin:

from urllib.parse import urlsplit, parse_qs

url = "/urunler?kategori=%C4%B0%C3%A7ecek&sayfa=2&etiket=yeni&etiket=indirim&limit=abc"
parcalar = urlsplit(url)
args = parse_qs(parcalar.query)
print(parcalar.path)
print(args)

def get(args, anahtar, varsayilan=None, type=None):
    if anahtar not in args:
        return varsayilan
    deger = args[anahtar][0]
    try:
        return type(deger) if type else deger
    except ValueError:
        return varsayilan

print(get(args, "sayfa", 1, type=int), get(args, "limit", 20, type=int), get(args, "kategori"))

Şablonlar: Jinja2

HTML sayfa üretirken Python içinde metin birleştirmek hem çirkin hem tehlikelidir. Flask, Jinja2 şablon motorunu kullanır: templates/ klasörüne HTML dosyası koyar, render_template("liste.html", urunler=urunler) ile değişkenleri gönderirsin. Şablonda {{ ifade }} değer basar, {% for %} ve {% if %} akış kurar, | ile filtre uygularsın. jinja2 bu motorda çalışır:

from jinja2 import Environment, DictLoader

sablonlar = {
    "taban.html": "<title>{% block baslik %}Anadolu Market{% endblock %}</title>\n{% block icerik %}{% endblock %}",
    "liste.html": """{% extends "taban.html" %}
{% block baslik %}Ürünler ({{ urunler|length }}){% endblock %}
{% block icerik %}
<ul>
{% for u in urunler %}  <li class="{{ 'tukendi' if u.stok == 0 else 'var' }}">{{ u.ad }}: {{ "%.2f"|format(u.fiyat) }} TL</li>
{% else %}  <li>Ürün yok</li>
{% endfor %}</ul>
<p>Yorum: {{ yorum }}</p>
{% endblock %}""",
}
env = Environment(loader=DictLoader(sablonlar), autoescape=True, trim_blocks=True)
urunler = [{"ad": "Çay", "fiyat": 85.5, "stok": 10}, {"ad": "Kahve", "fiyat": 240, "stok": 0}]
print(env.get_template("liste.html").render(urunler=urunler, yorum="<script>alert(1)</script>"))

İki şeye dikkat: {% extends %} ve {% block %} ile tüm sayfaların ortak iskeletini tek dosyada tutarsın (şablon kalıtımı (inheritance)). autoescape=True kullanıcıdan gelen <script> gibi içeriği zararsız &lt;script&gt; metnine çevirir; Flask bunu .html şablonlarında varsayılan olarak açar. |safe filtresiyle kaçışı kapatmak, içeriğin güvenli olduğundan emin olmadıkça XSS (siteler arası betik) açığı demektir.

Form işleme ve POST/Redirect/GET

from flask import Flask, render_template, request, redirect, url_for, flash

app = Flask(__name__)
app.config["SECRET_KEY"] = "gelistirme-icin"     # flash ve oturum için gerekli

@app.route("/iletisim", methods=["GET", "POST"])
def iletisim():
    hatalar = {}
    if request.method == "POST":
        ad = request.form.get("ad", "").strip()
        eposta = request.form.get("eposta", "").strip()
        if len(ad) < 2:
            hatalar["ad"] = "Ad en az 2 karakter olmalı"
        if "@" not in eposta:
            hatalar["eposta"] = "Geçerli bir e-posta gir"
        if not hatalar:
            # ... kaydet ...
            flash("Mesajın alındı, teşekkürler!")
            return redirect(url_for("iletisim"))   # POST'tan sonra yönlendir
    return render_template("iletisim.html", hatalar=hatalar, form=request.form)

Başarılı bir POST'tan sonra sayfayı doğrudan döndürmek yerine yönlendirirsin (POST/Redirect/GET kalıbı). Böylece kullanıcı sayfayı yenilediğinde form ikinci kez gönderilmez; çift sipariş, çift ödeme gibi sorunlar önlenir. url_for("iletisim") URL'yi fonksiyon adından üretir; URL değişse bile kod kırılmaz. Gerçek projelerde formlar Flask-WTF ile doğrulanır; bu eklenti CSRF (siteler arası istek sahteciliği) korumasını da ekler.

Hata işleyiciler ve durum kodları

from flask import Flask, jsonify, abort

app = Flask(__name__)

class StokYetersiz(Exception):
    pass

@app.errorhandler(404)
def bulunamadi(hata):
    return jsonify(hata="Kaynak bulunamadı"), 404

@app.errorhandler(StokYetersiz)
def stok_hatasi(hata):
    return jsonify(hata=str(hata)), 409

@app.errorhandler(500)
def sunucu_hatasi(hata):
    app.logger.exception("Beklenmeyen hata")
    return jsonify(hata="Sunucu hatası"), 500   # iç ayrıntıyı kullanıcıya sızdırma
KodAnlamNe zaman döndürürsün?
200 OKBaşarılıGET, başarılı güncelleme
201 CreatedOluşturulduPOST ile yeni kayıt
204 No ContentGövdesiz başarıDELETE
302/303YönlendirmeForm gönderiminden sonra
400 Bad Requestİstek bozukGeçersiz JSON, eksik alan
401 / 403Kimlik yok / yetki yokGiriş yapılmamış / izin yok
404 / 405Yol yok / metot yokKayıt bulunamadı / yanlış metot
409 ConflictDurum çakışmasıStok yetersiz, aynı e-posta kayıtlı
500Sunucu hatasıYakalanmamış istisna

Uygulama fabrikası, blueprint ve yapılandırma

Büyüyen bir projede tüm route'ları tek dosyaya koymak sürdürülemez. Blueprint, ilgili route'ları (ör. ürünler, siparişler, yönetim) ayrı modüllerde gruplar ve bir URL ön ekiyle uygulamaya bağlar. Uygulama fabrikası (application factory) ise uygulamayı modül düzeyinde değil bir fonksiyonda oluşturur: test için farklı, üretim için farklı yapılandırmayla istediğin kadar bağımsız uygulama üretebilirsin.

# magaza/urunler.py
from flask import Blueprint
bp = Blueprint("urunler", __name__, url_prefix="/api/urunler")

@bp.get("/")
def liste():
    return {"urunler": []}

# magaza/__init__.py
import os
from flask import Flask

def create_app(test_config=None):
    app = Flask(__name__)
    app.config.from_mapping(SECRET_KEY="dev", VERITABANI="sqlite:///magaza.db")
    app.config.from_prefixed_env()           # FLASK_SECRET_KEY gibi ortam değişkenleri
    if test_config:
        app.config.update(test_config)
    from . import urunler
    app.register_blueprint(urunler.bp)
    return app

# Çalıştırma: python -m flask --app magaza run --debug
Gizli anahtarlar koda yazılmazSECRET_KEY, veritabanı şifresi ve API anahtarları ortam değişkeninden ya da gizli yönetim servisinden okunur. Yapılandırma dersindeki kurallar burada da geçerli. debug=True üretimde asla açık olmamalı: hata sayfasındaki etkileşimli konsol, sunucuda kod çalıştırmaya izin verir.

Test client ile test

# tests/test_urunler.py   (pip install pytest)
import pytest
from magaza import create_app

@pytest.fixture
def client():
    app = create_app({"TESTING": True})
    return app.test_client()

def test_liste(client):
    yanit = client.get("/api/urunler/")
    assert yanit.status_code == 200
    assert yanit.get_json() == {"urunler": []}

def test_olmayan_yol(client):
    assert client.get("/yok").status_code == 404

def test_urun_ekle(client):
    yanit = client.post("/api/urunler/", json={"ad": "Çay", "fiyat": 85.5})
    assert yanit.status_code in (201, 405)

Test client gerçek bir sunucu başlatmadan isteği uygulamaya doğrudan iletir; testler milisaniyeler içinde çalışır. Fabrika kalıbının asıl faydası burada ortaya çıkar: her test temiz bir uygulama alır.

Eklentiler: Flask-SQLAlchemy ve Flask-Login (gösterim)

# pip install flask-sqlalchemy flask-login
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_required, current_user
from sqlalchemy.orm import Mapped, mapped_column

db = SQLAlchemy()
login = LoginManager()

class Kullanici(UserMixin, db.Model):
    id: Mapped[int] = mapped_column(primary_key=True)
    eposta: Mapped[str] = mapped_column(unique=True)

@login.user_loader
def yukle(kullanici_id):
    return db.session.get(Kullanici, int(kullanici_id))

def create_app():
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///magaza.db"
    app.config["SECRET_KEY"] = "dev"
    db.init_app(app)
    login.init_app(app)

    @app.get("/hesabim")
    @login_required
    def hesabim():
        return {"eposta": current_user.eposta}

    return app

Flask-SQLAlchemy, SQLAlchemy dersinde öğrendiğin modelleri ve session'ı istek yaşam döngüsüne bağlar. Flask-Login oturum çerezini yönetir ve @login_required ile korunan sayfalara girişsiz erişimi engeller.

Dağıtım: gunicorn

flask run yalnız geliştirme içindir; tek işlemlidir ve güvenlik için tasarlanmamıştır. Üretimde bir WSGI sunucusu kullanılır:

pip install gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 "magaza:create_app()"
# Windows'ta gunicorn çalışmaz; waitress kullan:
pip install waitress
waitress-serve --port=8000 --call magaza:create_app

Önüne genellikle nginx gibi bir ters vekil (reverse proxy) konur: HTTPS, statik dosyalar ve yük dengeleme orada yapılır. Docker ve dağıtım dersinde bu yapıyı konteynere taşıyacaksın.

Gerçek işteAnadolu Market'in depo ekibi, sabah sevkiyat listesini Excel'den kopyalıyordu. Bir geliştirici iki günde küçük bir Flask uygulaması yazdı: /sevkiyat sayfası veritabanından günün siparişlerini Jinja2 tablosuyla gösteriyor, /sevkiyat/<int:no>/tamamla POST isteği durumu güncelliyor, giriş Flask-Login ile korunuyor. Uygulama fabrikası sayesinde testler bellek içi SQLite ile, üretim PostgreSQL ile çalışıyor; gunicorn ve nginx arkasında şirket ağında yayında. Flask'ın gücü tam olarak bu: küçük bir iç araç için bir öğleden sonrada çalışan bir sistem.
Derinleş

Derinleş: HTMX ile kısmi güncelleme, form ve fetch ile JSON

Flask şablonları her istekte tam sayfa üretir. Modern arayüzlerde ise sayfanın yalnız bir parçası değişir: arama kutusuna yazdıkça liste süzülür, "Sepete ekle"ye basınca yalnız sepet rozeti güncellenir. Bunun üç yolu var ve bir web geliştiricisinin üçünü de tanıması gerekir.

YaklaşımSunucu ne döndürür?ArtısıEksisi
Sunucu tarafı oluşturma (SSR, klasik)Tam HTML sayfaBasit, SEO dostu, JavaScript gerekmezHer tıklamada sayfa yenilenir
HTMX ("HTML over the wire")HTML parçası (partial)SSR'nin basitliği + kısmi güncelleme; tek dil PythonÇok zengin istemci etkileşiminde sınırlı
SPA (React, Vue) + JSON APIJSONZengin, uygulama gibi arayüz; mobil ile aynı APIİki kod tabanı, derleme araçları, durum yönetimi

Temel HTML formu

Her şeyin temeli, JavaScript olmadan da çalışan formdur. Alanların name özniteliği request.form anahtarını belirler; label ile for erişilebilirlik içindir; tarayıcı doğrulaması (required, min) kullanıcıya kolaylıktır ama sunucu doğrulamasının yerini tutmaz.

<form method="post" action="/sepet">
  <label for="urun">Ürün</label>
  <select id="urun" name="urun_id">
    <option value="19">Bakır Cezve</option>
    <option value="20">Seramik Kupa Seti</option>
  </select>
  <label for="adet">Adet</label>
  <input id="adet" name="adet" type="number" min="1" max="10" value="1" required>
  <button type="submit">Sepete ekle</button>
</form>

HTMX: öznitelikle AJAX

HTMX küçük bir JavaScript kütüphanesidir; sayfaya tek <script> etiketiyle eklenir ve HTML'e yeni öznitelikler kazandırır: hx-get/hx-post hangi adrese istek atılacağını, hx-target gelen HTML'in nereye konacağını, hx-swap nasıl konacağını (innerHTML, outerHTML, beforeend), hx-trigger ne zaman tetikleneceğini söyler. Sunucu JSON değil HTML parçası döndürür.

<script src="https://unpkg.com/htmx.org@2.0.4"></script>

<input type="search" name="q" placeholder="Ürün ara..."
       hx-get="/urunler" hx-trigger="input changed delay:300ms, search"
       hx-target="#sonuclar" hx-swap="innerHTML">
<table><tbody id="sonuclar">{% include "_urun_satirlari.html" %}</tbody></table>
from flask import Flask, render_template, request

@app.get("/urunler")
def urunler():
    q = request.args.get("q", "")
    liste = urun_ara(q)
    if request.headers.get("HX-Request") == "true":            # HTMX isteği: yalnız parça
        return render_template("_urun_satirlari.html", urunler=liste)
    return render_template("urunler.html", urunler=liste, q=q)  # normal istek: tam sayfa

HTMX her isteğe HX-Request: true başlığı ekler. Aynı adres böylece hem tam sayfayı (doğrudan açılınca, arama motoru gelince) hem parçayı (HTMX çağırınca) sunar; buna aşamalı geliştirme (progressive enhancement) denir. Parça şablonu (_urun_satirlari.html) tam sayfa şablonunda {% include %} ile kullanıldığı için HTML iki kez yazılmaz. Mantığı Anadolu Market'in gerçek ürün tablosuyla tarayıcıda dene:

import veri
from jinja2 import Environment

urunler = veri.kayitlar("anadolu_market", "products")
env = Environment(autoescape=True)
parca = env.from_string(
    "{% for u in urunler %}<tr><td>{{ u.name }}</td><td>{{ u.category }}</td>"
    "<td>{{ '%.0f'|format(u.price) }} TL</td></tr>\n{% endfor %}")

def yanit(basliklar, q):
    liste = [u for u in urunler if q.lower() in u["name"].lower()]
    govde = parca.render(urunler=liste)
    if basliklar.get("HX-Request") == "true":
        return govde
    return "<html><body><h1>Ürünler</h1><table>" + govde + "</table></body></html>"

print(yanit({"HX-Request": "true"}, "set"))
print(yanit({}, "kupa")[:60], "...")

Kategori sütunundaki Ev & Yaşam değerinin çıktıda &amp; olarak kaçırıldığına dikkat et: autoescape parçalarda da geçerlidir ve XSS'e karşı korur. Başarılı bir HTMX formundan sonra yönlendirme (302) yerine güncellenmiş parçayı döndür; başka bir bölgeyi de haberdar etmek için HX-Trigger: sepetGuncellendi yanıt başlığıyla bir olay yayınla.

fetch ile JSON çağrısı

Bir sayfada küçük bir JavaScript etkileşimi gerekiyorsa (sepet tutarını anında hesaplamak gibi) tarayıcının yerleşik fetch fonksiyonu yeterlidir. Flask tarafında request.get_json() gövdeyi okur, jsonify ya da sözlük döndürmek JSON yanıt üretir. Hatalarda da JSON ve doğru durum kodu dön ki istemci r.ok ile ayırt edebilsin.

// static/sepet.js
const r = await fetch("/api/sepet", {
  method: "POST",
  headers: {"Content-Type": "application/json"},
  body: JSON.stringify({urun_id: 19, adet: 2}),
});
const veri = await r.json();
if (r.ok) { document.querySelector("#tutar").textContent = veri.tutar + " TL"; }
else { alert(veri.hata); }
@app.post("/api/sepet")
def sepet_api():
    govde = request.get_json(silent=True) or {}
    durum, sonuc = sepet_hesapla(govde)        # doğrulama + hesap, saf Python
    return sonuc, durum                         # Flask sözlüğü JSON'a çevirir
Gerçek işteİç araçlar, yönetim panelleri ve küçük e-ticaret siteleri için Flask + Jinja + HTMX üçlüsü, ayrı bir React projesine göre çok daha az kodla aynı kullanıcı deneyimini verir. Mobil uygulama da aynı veriyi istiyorsa JSON API (FastAPI ya da Flask) ve SPA tercih edilir. Mülakatta "SSR mı SPA mı?" sorusunun iyi cevabı ihtiyaçtan başlar: SEO, etkileşim yoğunluğu, ekip becerisi ve kaç farklı istemcinin olduğu.
Uzmanlaş

WSGI: Flask aslında bir fonksiyondur

Flask uygulaması, WSGI standardına (PEP 3333) uyan çağrılabilir bir nesnedir: app(environ, start_response). gunicorn her istekte bunu çağırır. Standart kütüphanedeki wsgiref ile en yalın WSGI uygulamasını yazıp sahte bir istekle çağırabilirsin:

import json

def uygulama(environ, start_response):
    yol = environ["PATH_INFO"]
    if yol == "/saglik":
        govde, durum = {"durum": "ok"}, "200 OK"
    else:
        govde, durum = {"hata": "bulunamadı"}, "404 NOT FOUND"
    veri = json.dumps(govde, ensure_ascii=False).encode("utf-8")
    start_response(durum, [("Content-Type", "application/json"), ("Content-Length", str(len(veri)))])
    return [veri]

def sahte_istek(yol):
    yakalanan = {}
    def start_response(durum, basliklar):
        yakalanan["durum"], yakalanan["basliklar"] = durum, dict(basliklar)
    govde = b"".join(uygulama({"REQUEST_METHOD": "GET", "PATH_INFO": yol}, start_response))
    return yakalanan["durum"], govde.decode()

print(sahte_istek("/saglik"))
print(sahte_istek("/yok"))

Bağlam yerelleri: request ve g

request küresel bir değişken gibi görünür ama her istek (ve her iş parçacığı) kendi değerini görür; Flask bunu bağlam yerelleri (context locals, contextvars) ile sağlar. İstek boyunca paylaşılacak nesneler (veritabanı bağlantısı, giriş yapan kullanıcı) flask.g içinde tutulur. Bu yüzden görünüm fonksiyonu dışında (ör. arka plan iş parçacığında) request'e erişmek "Working outside of request context" hatası verir.

Flask mı, FastAPI mı, Django mı?

Sunucu tarafında HTML üreten panellerde ve küçük servislerde Flask; tip ipuçlarından (type hint) otomatik doğrulama ve dokümantasyon isteyen JSON API'lerde FastAPI; yönetim paneli, ORM, kimlik doğrulama hepsi hazır gelsin isteyen büyük içerik uygulamalarında Django öne çıkar. Bir sonraki ders FastAPI; karşılaştırma tablosunu orada bulacaksın.

Alıştırmalara geç (28)