Python bağımlılıklarını sabitlemek: kilit dosyası, kurulum doğrulama ve zafiyet taraması
Python bağımlılık sabitleme: requirements.in ve kilit dosyası deseni, hash doğrulamalı pip kurulumu, çalışma anında sürüm kontrolü ve pip-audit zafiyet taraması.
Python bağımlılıklarını sabitlemek, bir otomasyonun bugün çalışan halini altı ay sonra yeni bir sunucuda ya da yeni bir ekip üyesinin bilgisayarında birebir yeniden kurabilmek demektir. Küçük ekiplerde betikler genellikle tek bir requirements.txt ile yaşar, içinde sürümsüz paket adları bulunur ve kurulum o gün PyPI'da ne varsa onu getirir. Betik aylarca sorunsuz çalışır; sonra sunucu taşınır, pip install yeni bir ana sürüm indirir ve gece görevi anlaşılmaz bir hatayla durur.
Sorunun ikinci yüzü güvenliktir. Kurulu paketlerden birinde bilinen bir zafiyet yayımlandığında bunu kimse ekibe haber vermez. Paket listesi sabitlenmemişse hangi sürümün kurulu olduğu bile belli değildir; sabitlenmişse ama taranmıyorsa, eski ve zafiyetli sürüm bilerek korunuyor demektir. İki uç da kötüdür ve ikisinin ortası küçük bir disiplinle kurulur.
Bu yazıda o disiplini adım adım anlatıyorum: sabitlemenin neden gerektiği, doğrudan bağımlılıklar ile kilit dosyasını ayıran iki dosya deseni, hash doğrulamalı kurulum, betiğin çalışma anında kendi kurulumunu kontrol etmesi, pip-audit ile zafiyet taraması ve bunları bozmayan bir güncelleme ritmi. Örnekler Python 3.9 ve üzeri ile çalışır; yalnız tarama adımı pip-audit paketini gerektirir.
Bağımlılık sabitleme neden gerekir
Bağımlılık sabitleme, projenin kullandığı her paketin ve o paketlerin kendi bağımlılıklarının tam sürümünü bir dosyaya yazıp kurulumu o dosyaya bağlamaktır. Amaç tekrarlanabilirliktir: aynı dosya, aynı Python sürümü ve aynı işletim sistemi ailesinde her zaman aynı paket kümesini kurar. Sürümsüz bir liste ise yalnız kurulduğu günü anlatır.
Sabitlemeyi atlamanın bedeli üç biçimde ortaya çıkar:
- Sessiz davranış değişikliği: paketin yeni sürümü bir varsayılanı değiştirir, betik hata vermez ama farklı sonuç üretir.
- Kırılan kurulum: yeni ana sürüm bir fonksiyonu kaldırır, betik ilk çalışmada çöker ve suçlu kodda aranır.
- Bulunamayan sürüm: bir sürüm PyPI'dan çekilir ya da platform desteği kalkar, eski sunucunun aynısı kurulamaz.
Sabitlemenin kendi bedeli de vardır: kilit dosyası bakım ister, aksi halde yıllarca eski sürümde kalınır. Bu yüzden yöntem, sabitlemeyi güncelleme ritmiyle birlikte kurar; yazının son bölümü bu ritmi anlatır.
İki dosya deseni: doğrudan bağımlılık ve kilit dosyası
İki dosya deseni, insanın yazdığı kısa bir doğrudan bağımlılık listesi ile araçtan üretilen tam sürümlü kilit dosyasını ayrı tutmak anlamına gelir. Birinci dosya requirements.in adını taşır ve yalnız projenin bilerek kullandığı paketleri, gerekiyorsa aralık kısıtıyla içerir. İkinci dosya requirements.txt olarak üretilir; her paketin tam sürümünü, hangi paket yüzünden geldiğini ve dosya hash'ini listeler.
Ayrımın değeri kod incelemesinde görülür. Birinin requests eklediğini requirements.in satırında tek bakışta görürsünüz; urllib3 ve certifi gibi dolaylı paketlerin sürüm değişiklikleri ise kilit dosyasının diff'inde ayrı durur. Üretim için pip-tools paketindeki pip-compile komutu yeterlidir; uv aracı da aynı iki dosyayı daha hızlı üretir ve komut yapısı benzerdir.
# requirements.in — elle yazılır, kısa tutulur
# requests>=2.31,<3
# openpyxl>=3.1
# duckdb
python -m pip install pip-tools
# Kilit dosyasını hash'lerle üret; sonuç requirements.txt
pip-compile --generate-hashes --output-file requirements.txt requirements.in
# Temiz bir sanal ortamda, yalnız kilit dosyasındaki hash'lere uyan paketleri kur
python -m venv .venv
.venv/bin/python -m pip install --require-hashes -r requirements.txt
# Kilit dosyasını güncellemek: tek paket ya da hepsi
pip-compile --generate-hashes --upgrade-package requests requirements.in
pip-compile --generate-hashes --upgrade requirements.in
--require-hashes ile kurulum, indirilen dosyanın kilit dosyasındaki hash'le eşleşmesini zorunlu kılar. Hash tutmuyorsa kurulum durur: paket deposunda aynı sürüm adıyla farklı bir dosya yayımlanmış ya da ağ üzerinde bir şey değişmiş demektir. Bu kontrol, sır yönetiminde .env dosyasından şifreli sır kasasına geçiş yazısında anlattığım yaklaşımın bağımlılık tarafıdır: dışarıdan gelen her şey doğrulanarak içeri alınır.
Her iki dosya da depoya girer. Windows ve Linux'ta ortak çalışan bir proje için kilit dosyası platform başına ayrı üretilir, çünkü bazı paketler yalnız bir tarafta bağımlılık ister; bu ayrımı Windows ve Linux otomasyon farkları yazısındaki taşınabilirlik ilkeleriyle birlikte düşünmek gerekir.
Kurulumu çalışma anında doğrulamak
Çalışma anında doğrulama, betiğin işe başlamadan önce kurulu paket sürümlerini kilit dosyasıyla karşılaştırması ve uyumsuzlukta açık bir mesajla durmasıdır. Kilit dosyası doğru olsa bile birisi sunucuda elle pip install --upgrade çalıştırmış olabilir; betik bunu kendisi fark etmezse hata gece saatlerinde ve başka bir yerde görünür. Kontrol standart kütüphanedeki importlib.metadata ile yapılır ve ek paket gerektirmez.
Aşağıdaki modül kilit dosyasını okur, paket==sürüm satırlarını ayrıştırır, hash ve yorum satırlarını atlar ve kurulu sürümlerle karşılaştırır. Eksik ya da farklı paket varsa listeyi standart hata akışına yazar ve sıfır dışı kodla çıkar. Betiklerin başında dogrula() çağrısı olarak, ya da bağımsız bir komut olarak dağıtım sonrası kontrolde kullanılır.
import re
import sys
from importlib import metadata
from pathlib import Path
SATIR = re.compile(r"^([A-Za-z0-9][A-Za-z0-9._-]*)\s*==\s*([^\s;\\]+)")
def kilit_oku(yol: Path) -> dict:
"""requirements.txt içindeki paket==sürüm çiftlerini döndürür."""
beklenen = {}
for ham in yol.read_text(encoding="utf-8").splitlines():
satir = ham.strip()
if not satir or satir.startswith(("#", "--", "-r")):
continue # yorum, hash satırı ve pip seçenekleri
m = SATIR.match(satir)
if m:
ad = m.group(1).lower().replace("_", "-")
beklenen[ad] = m.group(2)
return beklenen
def dogrula(yol: Path = Path("requirements.txt")) -> list:
"""Uyumsuz paketleri (ad, beklenen, kurulu) listesi olarak döndürür."""
sorunlar = []
for ad, surum in kilit_oku(yol).items():
try:
kurulu = metadata.version(ad)
except metadata.PackageNotFoundError:
sorunlar.append((ad, surum, "yok"))
continue
if kurulu != surum:
sorunlar.append((ad, surum, kurulu))
return sorunlar
if __name__ == "__main__":
yol = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("requirements.txt")
farklar = dogrula(yol)
if farklar:
print("kurulum kilit dosyasıyla uyuşmuyor:", file=sys.stderr)
for ad, beklenen, kurulu in farklar:
print(f" {ad}: beklenen {beklenen}, kurulu {kurulu}", file=sys.stderr)
sys.exit(2)
print(f"{yol}: kurulum kilit dosyasıyla uyumlu")
Kontrolün çıkış kodu 2, kullanım hatası anlamında seçildi; betiğin kendi iş hataları için 1 ayrılır. Böylece zamanlayıcı günlüğünde "bağımlılık uyumsuz" ile "veri hatalı" tek bakışta ayrılır. Kontrol birkaç milisaniye sürer, bu yüzden her çalışmada açık kalmasının bedeli yoktur.
pip-audit ile bilinen zafiyetleri taramak
pip-audit, Python Paketleme Otoritesi (PyPA) tarafından geliştirilen ve kilit dosyasındaki ya da kurulu ortamdaki paketleri açık zafiyet veritabanlarıyla karşılaştıran bir komut satırı aracıdır. Aracın girdisi aynı requirements.txt dosyasıdır; sabitleme yapılmış bir projede tarama bu yüzden kesin sürümler üzerinde çalışır ve sonuç tekrarlanabilir. Çıktı JSON olarak alınıp kendi raporunuza bağlanabilir.
# Kilit dosyasını tara; bulgu varsa sıfır dışı çıkış kodu
pip-audit -r requirements.txt --strict
# Sonucu JSON olarak kaydet (haftalık görev için)
pip-audit -r requirements.txt --format json --output denetim/pip-audit.json; echo "cikis=$?"
# Kurulu sanal ortamı tara ve düzeltilmiş sürümleri önermekle yetin
.venv/bin/pip-audit --desc
JSON çıktıyı okuyup kısa bir özet üreten ve bulgu varsa sıfır dışı kodla çıkan aşağıdaki betik, taramayı haftalık zamanlanmış göreve bağlamak için yeterlidir. Çıktı metni e-postaya ya da sohbet kanalına olduğu gibi yapıştırılabilir; her satır paket adı, kurulu sürüm, zafiyet kimliği ve varsa düzeltilmiş sürümü taşır.
import json
import sys
from pathlib import Path
def ozetle(rapor_yolu: Path) -> list:
"""pip-audit JSON raporundan (paket, sürüm, kimlik, düzeltme) satırları üretir."""
veri = json.loads(rapor_yolu.read_text(encoding="utf-8"))
bagimliliklar = veri.get("dependencies", veri if isinstance(veri, list) else [])
satirlar = []
for b in bagimliliklar:
for v in b.get("vulns", []):
duzeltme = ", ".join(v.get("fix_versions") or []) or "düzeltme yok"
satirlar.append((b["name"], b["version"], v["id"], duzeltme))
return satirlar
if __name__ == "__main__":
yol = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("denetim/pip-audit.json")
bulgular = ozetle(yol)
if not bulgular:
print("pip-audit: bilinen zafiyet yok")
sys.exit(0)
print(f"pip-audit: {len(bulgular)} bulgu")
for ad, surum, kimlik, duzeltme in bulgular:
print(f" {ad} {surum} {kimlik} düzeltilmiş sürüm: {duzeltme}")
sys.exit(1)
Bulgu her zaman acil değildir. Zafiyet, projenin hiç çağırmadığı bir fonksiyonda olabilir ya da yalnız ağa açık bir hizmette tetiklenebilir. Doğru tepki, bulguyu okuyup üç karardan birini kayda geçirmektir: düzeltilmiş sürüme geç, kullanılmayan paketi kaldır, ya da neden etkilenmediğini bir satırla yazıp bulguyu --ignore-vuln ile belirli bir tarihe kadar hariç tut. Kayıtsız hariç tutma, bir süre sonra hangi bulgunun neden susturulduğunun unutulmasına yol açar.
Güncelleme ritmi ve geri alma
Güncelleme ritmi, kilit dosyasının ne sıklıkla, hangi kapsamda ve hangi doğrulamayla yenileneceğini belirleyen yazılı bir kuraldır. Sabitleme olmadan her kurulum bir güncellemedir ve kimse fark etmez; sabitleme ile güncelleme bilinçli bir adıma dönüşür ve bu adımın takvimi yoksa hiç atılmaz. Küçük bir ekip için işleyen düzen şudur:
- Haftada bir zafiyet taraması otomatik çalışır; yalnız bulgu varsa bildirim gönderir.
- Ayda bir, ayrı bir dalda
pip-compile --upgradeçalıştırılır ve kilit dosyasının diff'i okunur. - Testler ve bir kuru çalıştırma yeni kilit dosyasıyla geçerse değişiklik ana dala alınır ve sunucuya kurulur.
- Kurulumdan sonra çalışma anı doğrulaması ve ilk gerçek koşunun günlüğü kontrol edilir.
Geri alma, kilit dosyası depoda durduğu için tek bir adımdır: önceki sürüm geri getirilir ve pip install --require-hashes yeniden çalıştırılır. Sanal ortamı her dağıtımda sıfırdan kurmak bu adımı daha da güvenilir yapar, çünkü eski ortamda kalmış bir paket kalıntısı yeni kurulumu etkileyemez. Ana sürüm atlamalarında, örneğin bir kütüphanenin 1.x'ten 2.x'e geçişinde, değişiklik notlarını okumadan güncellemek yerine o paketi bir sonraki aya ertelemek daha ucuzdur.
Sonuç: ilk adım
Bağımlılık sabitleme, otomasyonun geleceğe bıraktığı en ucuz sigortadır: kısa bir doğrudan bağımlılık listesi, araçtan üretilen hash'li bir kilit dosyası, kurulumun kilitle uyuştuğunu betiğin kendisinin kontrol etmesi, haftalık zafiyet taraması ve aylık bilinçli güncelleme. Her parça birkaç satırdır; birlikte, altı ay sonra taşınan bir sunucuda betiğin ilk denemede çalışmasını sağlar.
İlk adım olarak en kritik gece görevinizin klasöründe pip freeze çıktısını kilit dosyası olarak kaydedin, ardından onu bir requirements.in dosyasından yeniden üretin ve pip-audit ile bir kez tarayın. Çıkan listeyi okuduğunuzda hangi paketlerin neden orada olduğunu sorgulamaya başlarsınız; sabitlemenin asıl kazancı bu farkındalıktır. Projenizin bağımlılık düzenini birlikte gözden geçirmek isterseniz iletişim sayfasından ulaşabilirsiniz.