Neden önemli?
Ilgaz Bank'ın kredi başvuru servisi üç yerde çalışıyor: geliştiricinin bilgisayarında (dev), test sunucusunda (test) ve gerçek müşterilere hizmet veren canlı ortamda (prod). Kod aynı; değişen şeyler veritabanı adresi, günlük kredi limiti, log seviyesi, e-posta gönderilip gönderilmeyeceği ve API anahtarları. Bunları kodun içine yazarsan her ortam için kodu değiştirmen, yeniden paketlemen gerekir; daha kötüsü, prod parolası bir gün GitHub'a gider.
Yapılandırma (configuration, kısaca config), kod değişmeden programın davranışını değiştiren her şeydir. Bu derste yaygın beş biçimi okumayı, ortamları katmanlı birleştirmeyi, ayarı doğrulamayı ve sırları güvenle yönetmeyi öğreneceksin. JSON'un temelini JSON ve CSV dersinde, tomllib'e ilk bakışı Sanal ortam (virtual environment) ve paket yönetimi dersinde gördün; burada hepsini yapılandırma gözüyle birleştiriyoruz.
Biçimleri tanı
| Biçim | Okuma | Yorum | Tipler | Nerede görürsün? |
|---|---|---|---|---|
| JSON | json (standart) | Yok | str, sayı, bool, null, liste, nesne | API'ler, frontend ayarları |
| YAML | yaml.safe_load (pyyaml) | # | JSON + tarih, çok satırlı metin, referans | Docker Compose, Kubernetes, GitHub Actions |
| TOML | tomllib (3.11+, yalnız okuma) | # | Açık tipler, tarih-saat | pyproject.toml, Rust/Cargo |
| INI | configparser (standart) | # ; | Hepsi metin; getint/getboolean ile çevirirsin | Eski uygulamalar, setup.cfg, Windows |
| .env | os.environ + python-dotenv | # | Hepsi metin | Sırlar, ortama özel değerler |
Kural: insanın düzenleyeceği ayar dosyası için TOML ya da YAML, makinelerin alışverişi için JSON, sırlar ve ortama özgü küçük değerler için ortam değişkeni (environment variable).
YAML: okunaklı ama tuzaklı
YAML girinti ile yapı kurar, yorum destekler ve çok okunaklıdır. Python'da üçüncü parti pyyaml ile okursun (pip install pyyaml; bu uygulamada hazır). Her zaman yaml.safe_load kullan: yaml.load uygun olmayan bir yükleyiciyle dosyadaki etiketlerden (tag) Python nesnesi oluşturabilir, bu da güvenilmeyen dosyada kod çalıştırma açığı demektir.
import yaml metin = """ uygulama: kredi-servisi surum: "1.4" veritabani: host: db.ilgazbank.local port: 5432 subeler: [Ankara, İzmir, Bursa] eposta_gonder: true """ ayar = yaml.safe_load(metin) print(ayar["veritabani"]["port"] + 1, type(ayar["eposta_gonder"]).__name__) print(ayar["subeler"][1]) print(yaml.dump(ayar, allow_unicode=True, sort_keys=False))
YAML 1.1 kuralları (pyyaml bunları uygular) bazı değerleri sessizce başka tipe çevirir. Bu tuzaklar gerçek projelerde hataya yol açar:
import yaml
print(yaml.safe_load("ulke: NO\naktif: no\nevet: yes")) # NO ve no -> False!
print(yaml.safe_load("surum: 3.10")) # 3.1 (float)
print(yaml.safe_load("saat: 12:30")) # 750 (60'lık taban!)
print(yaml.safe_load("kod: 010")) # 8 (sekizlik taban)
print(yaml.safe_load('surum: "3.10"\nkod: "010"')) # tırnakla metin kalır
TOML: açık tipler
TOML, Python 3.11'den beri standart kütüphanededir (tomllib). Tipleri açıktır: metin her zaman tırnaklıdır, sayı ve tarih kendi tipindedir; YAML'daki sürprizler yoktur. tomllib yalnızca okur; yazmak için tomli-w paketi gerekir. Dosyadan okurken tomllib.load ikili modda açılmış dosya ister.
import tomllib
metin = """
[uygulama]
ad = "kredi-servisi"
baslangic = 2026-01-15
[veritabani]
host = "db.ilgazbank.local"
port = 5432
[[sube]]
ad = "Kızılay"
sehir = "Ankara"
[[sube]]
ad = "Alsancak"
sehir = "İzmir"
"""
ayar = tomllib.loads(metin)
print(ayar["uygulama"]["baslangic"].year, ayar["veritabani"]["port"])
print([s["ad"] for s in ayar["sube"]])
try:
tomllib.loads("port = 5432\nport = 5433")
except tomllib.TOMLDecodeError as e:
print("Hatalı TOML:", e)
[[sube]] bir tablo dizisi (array of tables) tanımlar; her blok listeye bir sözlük ekler. Aynı anahtarın iki kez yazılması TOML'da hatadır; YAML ise sessizce sonuncuyu alır.
INI: configparser
INI dosyası bölümlerden ([bolum]) ve anahtar = değer satırlarından oluşur. configparser her değeri metin olarak saklar; tipi sen istersin: getint, getfloat, getboolean. getboolean 1/yes/true/on ve 0/no/false/off değerlerini tanır. [DEFAULT] bölümündeki anahtarlar her bölüme miras kalır; anahtar adları büyük-küçük harf duyarsızdır.
import configparser
metin = """
[DEFAULT]
zaman_asimi = 30
[veritabani]
Host = localhost
port = 5432
debug = yes
[odeme_api]
url = https://odeme.example/v1
zaman_asimi = 5
"""
c = configparser.ConfigParser()
c.read_string(metin)
db = c["veritabani"]
print(db["host"], type(db["port"]).__name__, db.getint("port"))
print(db.getboolean("debug"), db.getint("zaman_asimi"))
print(c.getint("odeme_api", "zaman_asimi"), c.getint("odeme_api", "tekrar", fallback=3))
print(c.sections())
fallback eksik anahtar için varsayılan değer verir; vermezsen c.getint(bolum, anahtar) NoOptionError verir. [DEFAULT] bölümü sections() listesinde görünmez.
c["veritabani"].getint("yok") eksik anahtarda hata vermez, sessizce None döndürür. Zorunlu bir alanı bu yolla okursan hata ancak None + 1 gibi bir işlemde, çok sonra çıkar. Zorunlu alanlar için c.getint("veritabani", "port") ya da c["veritabani"]["port"] (KeyError verir) kullan.Ortam değişkenleri ve .env
İşletim sistemi her sürece bir ortam değişkenleri sözlüğü verir; Python'da os.environ'dur. Değerler her zaman metindir. Okurken os.environ["AD"] eksikse KeyError verir (zorunlu ayar için doğrusu bu), os.environ.get("AD", varsayilan) ise varsayılana düşer.
import os
os.environ["ANADOLU_PORT"] = "8080"
os.environ["ANADOLU_DEBUG"] = "false"
port = int(os.environ.get("ANADOLU_PORT", "8000"))
yanlis_debug = bool(os.environ["ANADOLU_DEBUG"]) # boş olmayan metin -> True!
dogru_debug = os.environ["ANADOLU_DEBUG"].lower() in {"1", "true", "yes", "on", "evet"}
print(port + 1, yanlis_debug, dogru_debug)
print(os.environ.get("ANADOLU_YOK", "varsayılan"))
try:
os.environ["ANADOLU_DB_PAROLA"]
except KeyError as e:
print("Zorunlu ayar eksik:", e)
Geliştirme sırasında onlarca değişkeni elle tanımlamak yerine proje kökünde bir .env dosyası tutulur ve python-dotenv paketi onu os.environ'a yükler:
# .env (DEPOYA GİRMEZ, .gitignore'da) APP_ENV=dev ANADOLU_DB_URL=postgresql://localhost:5432/kredi ANADOLU_DB_PAROLA="gizli parola" # uygulama.py from dotenv import load_dotenv import os load_dotenv() # .env'i okur, var olan değişkenleri EZMEZ print(os.environ["APP_ENV"])
pip install python-dotenv. Mantığı basittir; alıştırmalarda kendi .env ayrıştırıcını yazacaksın. load_dotenv varsayılan olarak var olan ortam değişkenini değiştirmez; böylece sunucuda tanımlanan gerçek değer, unutulmuş bir .env dosyasından üstün gelir.Ortama göre ayar: katmanlı birleştirme
Ölçeklenen projelerde ayar katmanlardan oluşur; sonraki katman öncekinin üzerine yazar:
- Koddaki güvenli varsayılanlar (her ortamda ortak).
- Ortak ayar dosyası (
ayar.yaml). - Ortama özel bölüm ya da dosya (
ayar.prod.yaml),APP_ENVdeğişkeniyle seçilir. - Ortam değişkenleri (sırlar ve son dakika değişiklikleri).
- Gerekirse komut satırı argümanları.
Sözlükleri {**a, **b} ya da a | b ile birleştirmek yüzeyseldir (shallow): iç içe bir sözlük komple değiştirilir. Prod dosyası yalnız veritabani.host değerini verdiyse port kaybolur. Bu yüzden iç içe (deep) birleştirme yazılır:
import copy
def birlestir(temel, ust):
sonuc = copy.deepcopy(temel)
for anahtar, deger in ust.items():
if isinstance(deger, dict) and isinstance(sonuc.get(anahtar), dict):
sonuc[anahtar] = birlestir(sonuc[anahtar], deger)
else:
sonuc[anahtar] = copy.deepcopy(deger)
return sonuc
varsayilan = {"veritabani": {"host": "localhost", "port": 5432}, "log": "DEBUG", "limit": 50_000}
ortamlar = {
"test": {"veritabani": {"host": "test-db"}},
"prod": {"veritabani": {"host": "prod-db"}, "log": "WARNING"},
}
print(varsayilan | ortamlar["prod"]) # yüzeysel: port kayboldu
for ad in ["dev", "test", "prod"]:
print(ad, birlestir(varsayilan, ortamlar.get(ad, {})))
print(varsayilan["veritabani"]["host"]) # varsayılan bozulmadı
Şema doğrulama: hızlı ve yüksek sesle hata ver
Yanlış yazılmış bir ayar (port yerine "5432a", limit yerine eksi sayı) programın başlangıcında yakalanmalı; gece 3'te ilk kredi başvurusunda değil. Bu ilkeye hızlı hata (fail fast) denir. Büyük projelerde pydantic ya da JSON Schema kullanılır; mantığı anlamak için elle yazmak öğreticidir. İyi bir doğrulayıcı ilk hatada durmaz, tüm hataları toplayıp birlikte söyler.
SEMA = {"host": str, "port": int, "debug": bool, "limit": float}
def dogrula(ayar):
hatalar = []
for anahtar, tur in SEMA.items():
if anahtar not in ayar:
hatalar.append(f"eksik: {anahtar}")
continue
deger = ayar[anahtar]
if tur is float and isinstance(deger, int) and not isinstance(deger, bool):
deger = float(deger) # 50000 da float sayılır
if not isinstance(deger, tur) or (tur is int and isinstance(deger, bool)):
hatalar.append(f"tür: {anahtar} {tur.__name__} olmalı")
if isinstance(ayar.get("port"), int) and not 1 <= ayar["port"] <= 65535:
hatalar.append("aralık: port 1-65535 olmalı")
return hatalar
print(dogrula({"host": "db", "port": 5432, "debug": False, "limit": 50000}))
print(dogrula({"host": "db", "port": True, "limit": "çok"}))
print(dogrula({"host": "db", "port": 70000, "debug": True, "limit": 1.5}))
bool, int'in alt sınıfıdır: isinstance(True, int) True döner. Port için True kabul etmemek adına bu durumu ayrıca eledik. Doğrulanan ayarı bir @dataclass(frozen=True) nesnesine çevirirsen program çalışırken kimse yanlışlıkla ayarı değiştiremez ve editörün otomatik tamamlama yapar.
Sırlar ve 12-factor
Parola, API anahtarı, token gibi sırlar (secrets) asla koda, depoya giren bir dosyaya ya da loga yazılmaz. Depoya yalnızca değerleri boş bir .env.example girer; gerçek .env .gitignore'dadır. Canlı ortamda sırlar bir sır yöneticisinden (Vault, AWS Secrets Manager, Azure Key Vault) ya da dağıtım platformunun ortam değişkenlerinden gelir. Bir sır bir kez depoya girdiyse, silmek yetmez: geçmişte durur; o sırrı değiştirmen (rotate) gerekir.
12-factor app, bulutta çalışan uygulamalar için yaygın kabul görmüş bir ilkeler listesidir. Yapılandırmayla ilgili olanlar:
| İlke | Ne der? | Pratikte |
|---|---|---|
| III. Config | Ortama göre değişen her şey ortamda tutulur, kodda değil | os.environ; kod depoyu açık kaynak yapsan bile sır sızmaz |
| IV. Backing services | Veritabanı, kuyruk gibi servisler adresle bağlanan kaynaklardır | DATABASE_URL değiştirerek kod değişmeden başka veritabanına geçersin |
| V. Build, release, run | Derleme çıktısı + config = sürüm | Aynı paket dev, test ve prod'a gider; yalnız config değişir |
| X. Dev/prod parity | Ortamlar birbirine benzesin | Dev'de SQLite, prod'da PostgreSQL farkı sürpriz hata doğurur |
def maskele(deger, gorunur=4):
deger = str(deger)
return "*" * max(len(deger) - gorunur, 3) + deger[-gorunur:] if len(deger) > gorunur else "***"
ayar = {"db_url": "postgresql://kredi@prod-db/kredi", "api_token": "tok_9f8e7d6c5b4a", "parola": "123"}
for anahtar, deger in ayar.items():
gizli = any(k in anahtar for k in ("token", "parola", "secret", "key"))
print(anahtar, "=", maskele(deger) if gizli else deger)
gitleaks, detect-secrets gibi tarayıcılar ve pre-commit kancaları ekler. Kod incelemesinde (code review) "bu değer nereden geliyor, prod'da ne olacak?" sorusu standarttır; başlangıçta ayarı doğrulayan ve eksik değişkenin adını açıkça söyleyen bir yükleyici, dağıtım gecesini kurtarır.İç içe ayarlar için ortam değişkeni adlandırma
Ortam değişkenleri düzdür; iç içe yapıyı ad içinde kodlarsın: ANADOLU_VERITABANI__HOST=prod-db. Önek (ANADOLU_) başka uygulamaların değişkenleriyle çakışmayı, çift alt çizgi (__) iç içe anahtarı gösterir. pydantic-settings ve Dynaconf bu kuralı hazır uygular; Meydan okuma alıştırmasında kendin yazacaksın.
Tipi varsayılandan çıkarmak
Ortamdan gelen her şey metin olduğu için, değeri varsayılan ayardaki tipe göre çevirmek pratik bir yoldur: varsayılan int ise int(...), bool ise evet/hayır tablosu. Bilinmeyen bir anahtar gelirse sessizce yok saymak yerine hata vermek, ANADOLU_VERITABNI__HOST gibi yazım hatalarını yakalar.
Değişmez ayar nesnesi
types.MappingProxyType(sozluk) salt okunur bir sözlük görünümü verir; @dataclass(frozen=True) ise alan atamasını engeller. Ayarı tek bir yerde yükleyip uygulamaya değişmez nesne olarak dağıtmak, "bu değeri kim değiştirdi" sorusunu ortadan kaldırır.
Özellik bayrakları
Yeni bir özelliği kodla birlikte kapalı yayına alıp yapılandırmayla açmak (feature flag), riskli değişiklikleri geri almayı kolaylaştırır. Bayrakların ömrü kısa tutulmalı; açıldıktan sonra kod temizlenmezse yapılandırma dosyası zamanla anlaşılmaz hâle gelir.