20 KiB
Daha Büyük Uygulamalar - Birden Fazla Dosya
Bir uygulama veya web API geliştirirken, her şeyi tek bir dosyaya sığdırabilmek nadirdir.
FastAPI, tüm esnekliği korurken uygulamanızı yapılandırmanıza yardımcı olan pratik bir araç sunar.
/// info | Bilgi
Flask'ten geliyorsanız, bu yapı Flask'in Blueprints'ine denk gelir.
///
Örnek Bir Dosya Yapısı
Diyelim ki şöyle bir dosya yapınız var:
.
├── app
│ ├── __init__.py
│ ├── main.py
│ ├── dependencies.py
│ └── routers
│ │ ├── __init__.py
│ │ ├── items.py
│ │ └── users.py
│ └── internal
│ ├── __init__.py
│ └── admin.py
/// tip | İpucu
Birden fazla __init__.py dosyası var: her dizinde veya alt dizinde bir tane.
Bu sayede bir dosyadaki kodu diğerine import edebilirsiniz.
Örneğin app/main.py içinde şöyle bir satırınız olabilir:
from app.routers import items
///
appdizini her şeyi içerir. Ayrıca boş birapp/__init__.pydosyası olduğu için bir "Python package" (bir "Python module" koleksiyonu) olur:app.- İçinde bir
app/main.pydosyası vardır. Bir Python package'in (içinde__init__.pydosyası olan bir dizinin) içinde olduğundan, o package'in bir "module"’üdür:app.main. - Benzer şekilde
app/dependencies.pydosyası da bir "module"’dür:app.dependencies. app/routers/adında bir alt dizin vardır ve içinde başka bir__init__.pydosyası bulunur; dolayısıyla bu bir "Python subpackage"’dir:app.routers.app/routers/items.pydosyasıapp/routers/package’i içinde olduğundan bir submodule’dür:app.routers.items.app/routers/users.pyiçin de aynı şekilde, başka bir submodule’dür:app.routers.users.app/internal/adında bir alt dizin daha vardır ve içinde başka bir__init__.pydosyası bulunur; dolayısıyla bu da bir "Python subpackage"’dir:app.internal.- Ve
app/internal/admin.pydosyası başka bir submodule’dür:app.internal.admin.
Aynı dosya yapısı, yorumlarla birlikte:
.
├── app # "app" bir Python package'idir
│ ├── __init__.py # bu dosya, "app"i bir "Python package" yapar
│ ├── main.py # "main" module'ü, örn. import app.main
│ ├── dependencies.py # "dependencies" module'ü, örn. import app.dependencies
│ └── routers # "routers" bir "Python subpackage"idir
│ │ ├── __init__.py # "routers"ı bir "Python subpackage" yapar
│ │ ├── items.py # "items" submodule'ü, örn. import app.routers.items
│ │ └── users.py # "users" submodule'ü, örn. import app.routers.users
│ └── internal # "internal" bir "Python subpackage"idir
│ ├── __init__.py # "internal"ı bir "Python subpackage" yapar
│ └── admin.py # "admin" submodule'ü, örn. import app.internal.admin
APIRouter
Diyelim ki sadece kullanıcıları yönetmeye ayrılmış dosyanız /app/routers/users.py içindeki submodule olsun.
Kullanıcılarla ilgili path operation’ları, kodun geri kalanından ayrı tutmak istiyorsunuz; böylece düzenli kalır.
Ancak bu hâlâ aynı FastAPI uygulaması/web API’sinin bir parçasıdır (aynı "Python Package" içinde).
Bu module için path operation’ları APIRouter kullanarak oluşturabilirsiniz.
APIRouter Import Edin
FastAPI class’ında yaptığınız gibi import edip bir "instance" oluşturursunuz:
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[1,3] title["app/routers/users.py"] *}
APIRouter ile Path Operations
Sonra bunu kullanarak path operation’larınızı tanımlarsınız.
FastAPI class’ını nasıl kullanıyorsanız aynı şekilde kullanın:
{* ../../docs_src/bigger_applications/app_an_py39/routers/users.py hl[6,11,16] title["app/routers/users.py"] *}
APIRouter’ı "mini bir FastAPI" class’ı gibi düşünebilirsiniz.
Aynı seçeneklerin hepsi desteklenir.
Aynı parameters, responses, dependencies, tags, vb.
/// tip | İpucu
Bu örnekte değişkenin adı router. Ancak istediğiniz gibi adlandırabilirsiniz.
///
Bu APIRouter’ı ana FastAPI uygulamasına ekleyeceğiz; ama önce dependency’lere ve bir diğer APIRouter’a bakalım.
Dependencies
Uygulamanın birden fazla yerinde kullanılacak bazı dependency’lere ihtiyacımız olacağını görüyoruz.
Bu yüzden onları ayrı bir dependencies module’üne koyuyoruz (app/dependencies.py).
Şimdi, özel bir X-Token header'ını okumak için basit bir dependency kullanalım:
{* ../../docs_src/bigger_applications/app_an_py39/dependencies.py hl[3,6:8] title["app/dependencies.py"] *}
/// tip | İpucu
Örneği basit tutmak için uydurma bir header kullanıyoruz.
Ancak gerçek senaryolarda, entegre Security yardımcı araçlarını{.internal-link target=_blank} kullanarak daha iyi sonuç alırsınız.
///
APIRouter ile Başka Bir Module
Diyelim ki uygulamanızdaki "items" ile ilgili endpoint'ler de app/routers/items.py module’ünde olsun.
Şunlar için path operation’larınız var:
/items//items/{item_id}
Bu, app/routers/users.py ile aynı yapıdadır.
Ancak biraz daha akıllı davranıp kodu sadeleştirmek istiyoruz.
Bu module’deki tüm path operation’ların şu ortak özelliklere sahip olduğunu biliyoruz:
- Path
prefix:/items. tags: (tek bir tag:items).- Ek
responses. dependencies: hepsinin, oluşturduğumuzX-Tokendependency’sine ihtiyacı var.
Dolayısıyla bunları her path operation’a tek tek eklemek yerine APIRouter’a ekleyebiliriz.
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[5:10,16,21] title["app/routers/items.py"] *}
Her path operation’ın path’i aşağıdaki gibi / ile başlamak zorunda olduğundan:
@router.get("/{item_id}")
async def read_item(item_id: str):
...
...prefix’in sonunda / olmamalıdır.
Yani bu örnekte prefix /items olur.
Ayrıca, bu router içindeki tüm path operation’lara uygulanacak bir tags listesi ve ek responses da ekleyebiliriz.
Ve router’daki tüm path operation’lara eklenecek, her request için çalıştırılıp çözülecek bir dependencies listesi de ekleyebiliriz.
/// tip | İpucu
path operation decorator’larındaki dependency’lerde{.internal-link target=_blank} olduğu gibi, path operation function’ınıza herhangi bir değer aktarılmayacağını unutmayın.
///
Sonuç olarak item path’leri artık:
/items//items/{item_id}
...tam da istediğimiz gibi olur.
- Hepsi, içinde tek bir string
"items"bulunan bir tag listesiyle işaretlenir.- Bu "tags", özellikle otomatik interaktif dokümantasyon sistemleri (OpenAPI) için çok faydalıdır.
- Hepsi önceden tanımlı
responses’ları içerir. - Bu path operation’ların hepsinde, öncesinde
dependencieslistesi değerlendirilip çalıştırılır.- Ayrıca belirli bir path operation içinde dependency tanımlarsanız, onlar da çalıştırılır.
- Önce router dependency’leri, sonra decorator’daki
dependencies{.internal-link target=_blank}, sonra da normal parametre dependency’leri çalışır. - Ayrıca
scopesileSecuritydependency’leri{.internal-link target=_blank} de ekleyebilirsiniz.
/// tip | İpucu
APIRouter içinde dependencies kullanmak, örneğin bir grup path operation için kimlik doğrulamayı zorunlu kılmakta kullanılabilir. Dependency’leri tek tek her birine eklemeseniz bile.
///
/// check | Ek bilgi
prefix, tags, responses ve dependencies parametreleri (çoğu başka örnekte olduğu gibi) kod tekrarını önlemenize yardımcı olan, FastAPI’nin bir özelliğidir.
///
Dependency'leri Import Edin
Bu kod app.routers.items module’ünde, yani app/routers/items.py dosyasında duruyor.
Dependency function’ını ise app.dependencies module’ünden, yani app/dependencies.py dosyasından almamız gerekiyor.
Bu yüzden dependency’ler için .. ile relative import kullanıyoruz:
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[3] title["app/routers/items.py"] *}
Relative Import Nasıl Çalışır
/// tip | İpucu
Import’ların nasıl çalıştığını çok iyi biliyorsanız, bir sonraki bölüme geçin.
///
Tek bir nokta ., örneğin:
from .dependencies import get_token_header
şu anlama gelir:
- Bu module’ün (yani
app/routers/items.pydosyasının) bulunduğu package içinden başla (app/routers/dizini)... dependenciesmodule’ünü bul (app/routers/dependencies.pygibi hayali bir dosya)...- ve oradan
get_token_headerfunction’ını import et.
Ama o dosya yok; bizim dependency’lerimiz app/dependencies.py dosyasında.
Uygulama/dosya yapımızın nasıl göründüğünü hatırlayın:
İki nokta .., örneğin:
from ..dependencies import get_token_header
şu anlama gelir:
- Bu module’ün bulunduğu package içinden başla (
app/routers/dizini)... - üst (parent) package’e çık (
app/dizini)... - burada
dependenciesmodule’ünü bul (app/dependencies.pydosyası)... - ve oradan
get_token_headerfunction’ını import et.
Bu doğru şekilde çalışır! 🎉
Aynı şekilde, üç nokta ... kullansaydık:
from ...dependencies import get_token_header
şu anlama gelirdi:
- Bu module’ün bulunduğu package içinden başla (
app/routers/dizini)... - üst package’e çık (
app/dizini)... - sonra bir üstüne daha çık (orada bir üst package yok;
appen üst seviye 😱)... - ve orada
dependenciesmodule’ünü bul (app/dependencies.pydosyası)... - ve oradan
get_token_headerfunction’ını import et.
Bu, app/ dizininin üstünde, kendi __init__.py dosyası olan başka bir package’e işaret ederdi. Ama bizde böyle bir şey yok. Dolayısıyla bu örnekte hata verirdi. 🚨
Artık nasıl çalıştığını bildiğinize göre, uygulamalarınız ne kadar karmaşık olursa olsun relative import’ları kullanabilirsiniz. 🤓
Özel tags, responses ve dependencies Ekleyin
/items prefix’ini ya da tags=["items"] değerini her path operation’a tek tek eklemiyoruz; çünkü bunları APIRouter’a ekledik.
Ama yine de belirli bir path operation’a uygulanacak ek tags tanımlayabilir, ayrıca o path operation’a özel responses ekleyebiliriz:
{* ../../docs_src/bigger_applications/app_an_py39/routers/items.py hl[30:31] title["app/routers/items.py"] *}
/// tip | İpucu
Bu son path operation’da tag kombinasyonu şöyle olur: ["items", "custom"].
Ayrıca dokümantasyonda iki response da görünür: biri 404, diğeri 403.
///
Ana FastAPI
Şimdi app/main.py module’üne bakalım.
Burada FastAPI class’ını import edip kullanırsınız.
Bu dosya, uygulamanızda her şeyi bir araya getiren ana dosya olacak.
Mantığın büyük kısmı artık kendi module’lerinde yaşayacağı için ana dosya oldukça basit kalır.
FastAPI Import Edin
Normal şekilde bir FastAPI class’ı oluşturursunuz.
Hatta her APIRouter için olan dependency’lerle birleştirilecek global dependencies{.internal-link target=_blank} bile tanımlayabilirsiniz:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[1,3,7] title["app/main.py"] *}
APIRouter Import Edin
Şimdi APIRouter içeren diğer submodule’leri import ediyoruz:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[4:5] title["app/main.py"] *}
app/routers/users.py ve app/routers/items.py dosyaları aynı Python package’i olan app’in parçası olan submodule’ler olduğu için, onları "relative import" ile tek bir nokta . kullanarak import edebiliriz.
Import Nasıl Çalışır
Şu bölüm:
from .routers import items, users
şu anlama gelir:
- Bu module’ün (yani
app/main.pydosyasının) bulunduğu package içinden başla (app/dizini)... routerssubpackage’ini bul (app/routers/dizini)...- ve buradan
itemssubmodule’ünü (app/routers/items.py) veuserssubmodule’ünü (app/routers/users.py) import et...
items module’ünün içinde router adında bir değişken vardır (items.router). Bu, app/routers/items.py dosyasında oluşturduğumuz aynı değişkendir; bir APIRouter nesnesidir.
Sonra aynı işlemi users module’ü için de yaparız.
Ayrıca şöyle de import edebilirdik:
from app.routers import items, users
/// info | Bilgi
İlk sürüm "relative import"tur:
from .routers import items, users
İkinci sürüm "absolute import"tur:
from app.routers import items, users
Python Packages ve Modules hakkında daha fazlası için, Python'ın Modules ile ilgili resmi dokümantasyonunu okuyun.
///
İsim Çakışmalarını Önleyin
items submodule’ünü doğrudan import ediyoruz; sadece içindeki router değişkenini import etmiyoruz.
Çünkü users submodule’ünde de router adlı başka bir değişken var.
Eğer şöyle sırayla import etseydik:
from .routers.items import router
from .routers.users import router
users içindeki router, items içindeki router’ın üstüne yazardı ve ikisini aynı anda kullanamazdık.
Bu yüzden ikisini de aynı dosyada kullanabilmek için submodule’leri doğrudan import ediyoruz:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[5] title["app/main.py"] *}
users ve items için APIRouter’ları Dahil Edin
Şimdi users ve items submodule’lerindeki router’ları dahil edelim:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[10:11] title["app/main.py"] *}
/// info | Bilgi
users.router, app/routers/users.py dosyasının içindeki APIRouter’ı içerir.
items.router ise app/routers/items.py dosyasının içindeki APIRouter’ı içerir.
///
app.include_router() ile her bir APIRouter’ı ana FastAPI uygulamasına ekleyebiliriz.
Böylece o router içindeki tüm route’lar uygulamanın bir parçası olarak dahil edilir.
/// note | Teknik Detaylar
Aslında içeride, APIRouter içinde tanımlanan her path operation için bir path operation oluşturur.
Yani perde arkasında, her şey tek bir uygulamaymış gibi çalışır.
///
/// check | Ek bilgi
Router’ları dahil ederken performans konusunda endişelenmeniz gerekmez.
Bu işlem mikrosaniyeler sürer ve sadece startup sırasında olur.
Dolayısıyla performansı etkilemez. ⚡
///
Özel prefix, tags, responses ve dependencies ile Bir APIRouter Dahil Edin
Şimdi, kurumunuzun size app/internal/admin.py dosyasını verdiğini düşünelim.
Bu dosyada, kurumunuzun birden fazla proje arasında paylaştığı bazı admin path operation’larını içeren bir APIRouter var.
Bu örnekte çok basit olacak. Ancak kurum içinde başka projelerle paylaşıldığı için, bunu değiştirip prefix, dependencies, tags vs. doğrudan APIRouter’a ekleyemediğimizi varsayalım:
{* ../../docs_src/bigger_applications/app_an_py39/internal/admin.py hl[3] title["app/internal/admin.py"] *}
Yine de bu APIRouter’ı dahil ederken özel bir prefix ayarlamak istiyoruz ki tüm path operation’ları /admin ile başlasın; ayrıca bu projede hâlihazırda kullandığımız dependencies ile güvene almak, tags ve responses eklemek istiyoruz.
Orijinal APIRouter’ı değiştirmeden, bu parametreleri app.include_router()’a vererek hepsini tanımlayabiliriz:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[14:17] title["app/main.py"] *}
Böylece orijinal APIRouter değişmeden kalır; yani aynı app/internal/admin.py dosyasını kurum içindeki diğer projelerle de paylaşmaya devam edebiliriz.
Sonuç olarak, uygulamamızda admin module’ündeki her bir path operation şunlara sahip olur:
/adminprefix’i.admintag’i.get_token_headerdependency’si.418response’u. 🍵
Ancak bu sadece bizim uygulamamızdaki o APIRouter için geçerlidir; onu kullanan diğer kodlar için değil.
Dolayısıyla örneğin diğer projeler aynı APIRouter’ı farklı bir authentication yöntemiyle kullanabilir.
Bir Path Operation Dahil Edin
Path operation’ları doğrudan FastAPI uygulamasına da ekleyebiliriz.
Burada bunu yapıyoruz... sadece yapabildiğimizi göstermek için 🤷:
{* ../../docs_src/bigger_applications/app_an_py39/main.py hl[21:23] title["app/main.py"] *}
ve app.include_router() ile eklenen diğer tüm path operation’larla birlikte doğru şekilde çalışır.
/// info | Çok Teknik Detaylar
Not: Bu oldukça teknik bir detay; büyük ihtimalle direkt geçebilirsiniz.
APIRouter’lar "mount" edilmez; uygulamanın geri kalanından izole değildir.
Çünkü path operation’larını OpenAPI şemasına ve kullanıcı arayüzlerine dahil etmek istiyoruz.
Onları tamamen izole edip bağımsız şekilde "mount" edemediğimiz için, path operation’lar doğrudan eklenmek yerine "klonlanır" (yeniden oluşturulur).
///
Otomatik API Dokümanını Kontrol Edin
Şimdi uygulamanızı çalıştırın:
$ fastapi dev app/main.py
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Ve dokümanları http://127.0.0.1:8000/docs adresinde açın.
Tüm submodule’lerdeki path’leri, doğru path’ler (ve prefix’ler) ve doğru tag’lerle birlikte içeren otomatik API dokümanını göreceksiniz:
Aynı Router'ı Farklı prefix ile Birden Fazla Kez Dahil Edin
.include_router() ile aynı router’ı farklı prefix’ler kullanarak birden fazla kez de dahil edebilirsiniz.
Örneğin aynı API’yi /api/v1 ve /api/latest gibi farklı prefix’ler altında sunmak için faydalı olabilir.
Bu, muhtemelen ihtiyacınız olmayan ileri seviye bir kullanımdır; ancak gerekirse diye mevcut.
Bir APIRouter’ı Başka Birine Dahil Edin
Bir APIRouter’ı FastAPI uygulamasına dahil ettiğiniz gibi, bir APIRouter’ı başka bir APIRouter’a da şu şekilde dahil edebilirsiniz:
router.include_router(other_router)
router’ı FastAPI uygulamasına dahil etmeden önce bunu yaptığınızdan emin olun; böylece other_router içindeki path operation’lar da dahil edilmiş olur.