yield ile Dependency'ler¶
🌐 Yapay Zekâ ve İnsanlar Tarafından Çeviri
Bu çeviri, insanlar tarafından yönlendirilen bir yapay zekâ ile oluşturuldu. 🤝
Orijinal anlamın yanlış anlaşılması ya da kulağa doğal gelmeme gibi hatalar içerebilir. 🤖
Yapay zekâyı daha iyi yönlendirmemize yardımcı olarak bu çeviriyi iyileştirebilirsiniz.
FastAPI, işini bitirdikten sonra ek adımlar çalıştıran dependency'leri destekler.
Bunu yapmak için return yerine yield kullanın ve ek adımları (kodu) yield satırından sonra yazın.
İpucu
Her dependency için yalnızca bir kez yield kullandığınızdan emin olun.
Teknik Detaylar
Şunlarla kullanılabilen herhangi bir fonksiyon:
bir FastAPI dependency'si olarak kullanılabilir.
Hatta FastAPI bu iki decorator'ı içeride (internally) kullanır.
yield ile Bir Veritabanı Dependency'si¶
Örneğin bunu, bir veritabanı session'ı oluşturmak ve iş bittikten sonra kapatmak için kullanabilirsiniz.
Response oluşturulmadan önce yalnızca yield satırına kadar olan (ve yield dahil) kod çalıştırılır:
async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
yield edilen değer, path operation'lara ve diğer dependency'lere enjekte edilen (injected) değerdir:
async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
Response'dan sonra yield satırını takip eden kod çalıştırılır:
async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
İpucu
async ya da normal fonksiyonlar kullanabilirsiniz.
FastAPI, normal dependency'lerde olduğu gibi her ikisinde de doğru şekilde davranır.
yield ve try ile Bir Dependency¶
yield kullanan bir dependency içinde bir try bloğu kullanırsanız, dependency kullanılırken fırlatılan (raised) herhangi bir exception'ı alırsınız.
Örneğin, başka bir dependency'de veya bir path operation içinde çalışan bir kod, bir veritabanı transaction'ını "rollback" yaptıysa ya da başka bir exception oluşturduysa, o exception dependency'nizde size gelir.
Dolayısıyla except SomeException ile dependency içinde o spesifik exception'ı yakalayabilirsiniz.
Aynı şekilde, exception olsun ya da olmasın çıkış (exit) adımlarının çalıştırılmasını garanti etmek için finally kullanabilirsiniz.
async def get_db():
db = DBSession()
try:
yield db
finally:
db.close()
yield ile Alt Dependency'ler¶
Her boyutta ve şekilde alt dependency'ler ve alt dependency "ağaçları" (trees) oluşturabilirsiniz; bunların herhangi biri veya hepsi yield kullanabilir.
FastAPI, yield kullanan her dependency'deki "exit code"'un doğru sırayla çalıştırılmasını sağlar.
Örneğin, dependency_c, dependency_b'ye; dependency_b de dependency_a'ya bağlı olabilir:
from typing import Annotated
from fastapi import Depends
async def dependency_a():
dep_a = generate_dep_a()
try:
yield dep_a
finally:
dep_a.close()
async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
dep_b = generate_dep_b()
try:
yield dep_b
finally:
dep_b.close(dep_a)
async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
dep_c = generate_dep_c()
try:
yield dep_c
finally:
dep_c.close(dep_b)
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi import Depends
async def dependency_a():
dep_a = generate_dep_a()
try:
yield dep_a
finally:
dep_a.close()
async def dependency_b(dep_a=Depends(dependency_a)):
dep_b = generate_dep_b()
try:
yield dep_b
finally:
dep_b.close(dep_a)
async def dependency_c(dep_b=Depends(dependency_b)):
dep_c = generate_dep_c()
try:
yield dep_c
finally:
dep_c.close(dep_b)
Ve hepsi yield kullanabilir.
Bu durumda dependency_c, exit code'unu çalıştırabilmek için dependency_b'den gelen değerin (burada dep_b) hâlâ erişilebilir olmasına ihtiyaç duyar.
Aynı şekilde dependency_b de exit code'u için dependency_a'dan gelen değerin (burada dep_a) erişilebilir olmasına ihtiyaç duyar.
from typing import Annotated
from fastapi import Depends
async def dependency_a():
dep_a = generate_dep_a()
try:
yield dep_a
finally:
dep_a.close()
async def dependency_b(dep_a: Annotated[DepA, Depends(dependency_a)]):
dep_b = generate_dep_b()
try:
yield dep_b
finally:
dep_b.close(dep_a)
async def dependency_c(dep_b: Annotated[DepB, Depends(dependency_b)]):
dep_c = generate_dep_c()
try:
yield dep_c
finally:
dep_c.close(dep_b)
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi import Depends
async def dependency_a():
dep_a = generate_dep_a()
try:
yield dep_a
finally:
dep_a.close()
async def dependency_b(dep_a=Depends(dependency_a)):
dep_b = generate_dep_b()
try:
yield dep_b
finally:
dep_b.close(dep_a)
async def dependency_c(dep_b=Depends(dependency_b)):
dep_c = generate_dep_c()
try:
yield dep_c
finally:
dep_c.close(dep_b)
Benzer şekilde, bazı dependency'ler yield, bazıları return kullanabilir ve bunların bazıları diğerlerine bağlı olabilir.
Ayrıca birden fazla yield kullanan dependency gerektiren tek bir dependency'niz de olabilir, vb.
İstediğiniz herhangi bir dependency kombinasyonunu kullanabilirsiniz.
FastAPI her şeyin doğru sırada çalışmasını sağlar.
Teknik Detaylar
Bu, Python'un Context Managers yapısı sayesinde çalışır.
FastAPI bunu sağlamak için içeride onları kullanır.
yield ve HTTPException ile Dependency'ler¶
yield kullanan dependency'lerde try bloklarıyla bazı kodları çalıştırıp ardından finally sonrasında exit code çalıştırabileceğinizi gördünüz.
Ayrıca except ile fırlatılan exception'ı yakalayıp onunla bir şey yapabilirsiniz.
Örneğin HTTPException gibi farklı bir exception fırlatabilirsiniz.
İpucu
Bu biraz ileri seviye bir tekniktir ve çoğu durumda gerçekten ihtiyaç duymazsınız; çünkü exception'ları (HTTPException dahil) uygulamanızın geri kalan kodundan, örneğin path operation function içinden fırlatabilirsiniz.
Ama ihtiyaç duyarsanız diye burada. 🤓
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
data = {
"plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
"portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
}
class OwnerError(Exception):
pass
def get_username():
try:
yield "Rick"
except OwnerError as e:
raise HTTPException(status_code=400, detail=f"Owner error: {e}")
@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
if item_id not in data:
raise HTTPException(status_code=404, detail="Item not found")
item = data[item_id]
if item["owner"] != username:
raise OwnerError(username)
return item
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
data = {
"plumbus": {"description": "Freshly pickled plumbus", "owner": "Morty"},
"portal-gun": {"description": "Gun to create portals", "owner": "Rick"},
}
class OwnerError(Exception):
pass
def get_username():
try:
yield "Rick"
except OwnerError as e:
raise HTTPException(status_code=400, detail=f"Owner error: {e}")
@app.get("/items/{item_id}")
def get_item(item_id: str, username: str = Depends(get_username)):
if item_id not in data:
raise HTTPException(status_code=404, detail="Item not found")
item = data[item_id]
if item["owner"] != username:
raise OwnerError(username)
return item
Exception yakalayıp buna göre özel bir response oluşturmak istiyorsanız bir Custom Exception Handler oluşturun.
yield ve except ile Dependency'ler¶
yield kullanan bir dependency içinde except ile bir exception yakalar ve bunu tekrar fırlatmazsanız (ya da yeni bir exception fırlatmazsanız), FastAPI normal Python'da olduğu gibi bir exception olduğunu fark edemez:
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
class InternalError(Exception):
pass
def get_username():
try:
yield "Rick"
except InternalError:
print("Oops, we didn't raise again, Britney 😱")
@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
if item_id == "portal-gun":
raise InternalError(
f"The portal gun is too dangerous to be owned by {username}"
)
if item_id != "plumbus":
raise HTTPException(
status_code=404, detail="Item not found, there's only a plumbus here"
)
return item_id
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
class InternalError(Exception):
pass
def get_username():
try:
yield "Rick"
except InternalError:
print("Oops, we didn't raise again, Britney 😱")
@app.get("/items/{item_id}")
def get_item(item_id: str, username: str = Depends(get_username)):
if item_id == "portal-gun":
raise InternalError(
f"The portal gun is too dangerous to be owned by {username}"
)
if item_id != "plumbus":
raise HTTPException(
status_code=404, detail="Item not found, there's only a plumbus here"
)
return item_id
Bu durumda client, biz HTTPException veya benzeri bir şey fırlatmadığımız için olması gerektiği gibi HTTP 500 Internal Server Error response'u görür; ancak server hiç log üretmez ve hatanın ne olduğuna dair başka bir işaret de olmaz. 😱
yield ve except Kullanan Dependency'lerde Her Zaman raise Edin¶
yield kullanan bir dependency içinde bir exception yakalarsanız, başka bir HTTPException veya benzeri bir şey fırlatmıyorsanız, orijinal exception'ı tekrar raise etmelisiniz.
Aynı exception'ı raise ile tekrar fırlatabilirsiniz:
from typing import Annotated
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
class InternalError(Exception):
pass
def get_username():
try:
yield "Rick"
except InternalError:
print("We don't swallow the internal error here, we raise again 😎")
raise
@app.get("/items/{item_id}")
def get_item(item_id: str, username: Annotated[str, Depends(get_username)]):
if item_id == "portal-gun":
raise InternalError(
f"The portal gun is too dangerous to be owned by {username}"
)
if item_id != "plumbus":
raise HTTPException(
status_code=404, detail="Item not found, there's only a plumbus here"
)
return item_id
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi import Depends, FastAPI, HTTPException
app = FastAPI()
class InternalError(Exception):
pass
def get_username():
try:
yield "Rick"
except InternalError:
print("We don't swallow the internal error here, we raise again 😎")
raise
@app.get("/items/{item_id}")
def get_item(item_id: str, username: str = Depends(get_username)):
if item_id == "portal-gun":
raise InternalError(
f"The portal gun is too dangerous to be owned by {username}"
)
if item_id != "plumbus":
raise HTTPException(
status_code=404, detail="Item not found, there's only a plumbus here"
)
return item_id
Artık client yine aynı HTTP 500 Internal Server Error response'unu alır, ama server log'larda bizim özel InternalError'ımızı görür. 😎
yield Kullanan Dependency'lerin Çalıştırılması¶
Çalıştırma sırası kabaca aşağıdaki diyagramdaki gibidir. Zaman yukarıdan aşağı akar. Her sütun, etkileşime giren veya kod çalıştıran parçalardan birini temsil eder.
sequenceDiagram
participant client as Client
participant handler as Exception handler
participant dep as Dep with yield
participant operation as Path Operation
participant tasks as Background tasks
Note over client,operation: Can raise exceptions, including HTTPException
client ->> dep: Start request
Note over dep: Run code up to yield
opt raise Exception
dep -->> handler: Raise Exception
handler -->> client: HTTP error response
end
dep ->> operation: Run dependency, e.g. DB session
opt raise
operation -->> dep: Raise Exception (e.g. HTTPException)
opt handle
dep -->> dep: Can catch exception, raise a new HTTPException, raise other exception
end
handler -->> client: HTTP error response
end
operation ->> client: Return response to client
Note over client,operation: Response is already sent, can't change it anymore
opt Tasks
operation -->> tasks: Send background tasks
end
opt Raise other exception
tasks -->> tasks: Handle exceptions in the background task code
end
Bilgi
Client'a yalnızca tek bir response gönderilir. Bu, error response'lardan biri olabilir ya da path operation'dan dönen response olabilir.
Bu response'lardan biri gönderildikten sonra başka bir response gönderilemez.
İpucu
Path operation function içindeki koddan herhangi bir exception raise ederseniz, HTTPException dahil olmak üzere bu exception yield kullanan dependency'lere aktarılır. Çoğu durumda, doğru şekilde ele alındığından emin olmak için yield kullanan dependency'den aynı exception'ı (veya yeni bir tanesini) yeniden raise etmek istersiniz.
Erken Çıkış ve scope¶
Normalde yield kullanan dependency'lerin exit code'u, client'a response gönderildikten sonra çalıştırılır.
Ama path operation function'dan döndükten sonra dependency'yi kullanmayacağınızı biliyorsanız, Depends(scope="function") kullanarak FastAPI'ye dependency'yi path operation function döndükten sonra kapatmasını, ancak response gönderilmeden önce kapatmasını söyleyebilirsiniz.
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
def get_username():
try:
yield "Rick"
finally:
print("Cleanup up before response is sent")
@app.get("/users/me")
def get_user_me(username: Annotated[str, Depends(get_username, scope="function")]):
return username
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
from fastapi import Depends, FastAPI
app = FastAPI()
def get_username():
try:
yield "Rick"
finally:
print("Cleanup up before response is sent")
@app.get("/users/me")
def get_user_me(username: str = Depends(get_username, scope="function")):
return username
Depends() şu değerleri alabilen bir scope parametresi alır:
"function": dependency'yi request'i işleyen path operation function çalışmadan önce başlat, path operation function bittikten sonra bitir, ancak response client'a geri gönderilmeden önce sonlandır. Yani dependency fonksiyonu, path operation function'ın etrafında çalıştırılır."request": dependency'yi request'i işleyen path operation function çalışmadan önce başlat ("function"kullanımına benzer), ancak response client'a geri gönderildikten sonra sonlandır. Yani dependency fonksiyonu, request ve response döngüsünün etrafında çalıştırılır.
Belirtilmezse ve dependency yield kullanıyorsa, varsayılan olarak scope "request" olur.
Alt dependency'ler için scope¶
scope="request" (varsayılan) ile bir dependency tanımladığınızda, herhangi bir alt dependency'nin scope değeri de "request" olmalıdır.
Ancak scope değeri "function" olan bir dependency, hem "function" hem de "request" scope'una sahip dependency'lere bağlı olabilir.
Bunun nedeni, bir dependency'nin exit code'unu alt dependency'lerden önce çalıştırabilmesi gerekmesidir; çünkü exit code sırasında hâlâ onları kullanması gerekebilir.
sequenceDiagram
participant client as Client
participant dep_req as Dep scope="request"
participant dep_func as Dep scope="function"
participant operation as Path Operation
client ->> dep_req: Start request
Note over dep_req: Run code up to yield
dep_req ->> dep_func: Pass dependency
Note over dep_func: Run code up to yield
dep_func ->> operation: Run path operation with dependency
operation ->> dep_func: Return from path operation
Note over dep_func: Run code after yield
Note over dep_func: ✅ Dependency closed
dep_func ->> client: Send response to client
Note over client: Response sent
Note over dep_req: Run code after yield
Note over dep_req: ✅ Dependency closed
yield, HTTPException, except ve Background Tasks ile Dependency'ler¶
yield kullanan dependency'ler, zaman içinde farklı kullanım senaryolarını kapsamak ve bazı sorunları düzeltmek için gelişti.
FastAPI'nin farklı sürümlerinde nelerin değiştiğini görmek isterseniz, advanced guide'da şu bölümü okuyabilirsiniz: Advanced Dependencies - Dependencies with yield, HTTPException, except and Background Tasks.
Context Managers¶
"Context Managers" Nedir?¶
"Context Managers", with ifadesiyle kullanabildiğiniz Python nesneleridir.
Örneğin, bir dosyayı okumak için with kullanabilirsiniz:
with open("./somefile.txt") as f:
contents = f.read()
print(contents)
Temelde open("./somefile.txt"), "Context Manager" olarak adlandırılan bir nesne oluşturur.
with bloğu bittiğinde, exception olsa bile dosyanın kapatılmasını garanti eder.
yield ile bir dependency oluşturduğunuzda, FastAPI içeride bunun için bir context manager oluşturur ve bazı ilgili başka araçlarla birleştirir.
yield kullanan dependency'lerde context manager kullanma¶
Uyarı
Bu, az çok "ileri seviye" bir fikirdir.
FastAPI'ye yeni başlıyorsanız şimdilik bunu atlamak isteyebilirsiniz.
Python'da Context Manager'ları, iki method'a sahip bir class oluşturarak: __enter__() ve __exit__() yaratabilirsiniz.
Ayrıca dependency fonksiyonunun içinde with veya async with ifadeleri kullanarak FastAPI'de yield kullanan dependency'lerin içinde de kullanabilirsiniz:
class MySuperContextManager:
def __init__(self):
self.db = DBSession()
def __enter__(self):
return self.db
def __exit__(self, exc_type, exc_value, traceback):
self.db.close()
async def get_db():
with MySuperContextManager() as db:
yield db
İpucu
Bir context manager oluşturmanın başka bir yolu da şunlardır:
Bunları, tek bir yield içeren bir fonksiyonu decorate etmek için kullanabilirsiniz.
FastAPI, yield kullanan dependency'ler için içeride bunu yapar.
Ancak FastAPI dependency'leri için bu decorator'ları kullanmak zorunda değilsiniz (hatta kullanmamalısınız).
FastAPI bunu sizin yerinize içeride yapar.