Ana içeriğe geç
Kesit Python
Orta · Modüller, paketler ve Git

Git ve proje yapısı

Temel Git akışını (init, add, commit, branch, merge, pull request) kavrar, .gitignore kurallarını fnmatch ile çözümler, src yerleşimli proje ağacı kurar, README yazar, config ve .env ile gizli bilgiyi koddan ayırır ve kod inceleme kültürünü tanırsın.

Uygulamada aç, 17 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 Git?

Ilgaz Bank'ın risk ekibinde üç analist aynı rapor betiğini geliştiriyor. "rapor_v2_son_FINAL.py" dosyaları e-postayla dolaşıyor, kimin hangi satırı değiştirdiği belli değil, dün çalışan sürüm kayboldu. Git, sürüm kontrol sistemi (version control system) olarak bu kaosu bitirir: her değişiklik kim, ne zaman, neden bilgisiyle kaydedilir; herkes aynı anda ayrı dallarda çalışır; istenirse herhangi bir güne geri dönülür. Git öğrenmek Python'dan bağımsız bir beceri ama Python geliştiricisi için şart: işe alım testlerinde ilk sorulan şey GitHub profilindir.

Bu derste Git komutları gösterilirTarayıcıda Git çalıştıramayız; komutları kendi bilgisayarında dene. Kod alıştırmaları Git ve proje yapısının Python tarafını işler: .gitignore kurallarını eşleştirmek, proje ağacını doğrulamak, ortam değişkeni okumak.

Temel akış: init, add, commit

Git üç bölgeyle çalışır: çalışma dizini (dosyaların), hazırlık alanı (staging, "sonraki kayda girecekler") ve depo (repository, kalıcı geçmiş). Günlük döngü şudur:

  1. git init — klasörü depo yapar (bir kez).
  2. Dosyaları düzenle; git status ile neyin değiştiğini gör.
  3. git add src/rapor.py — değişikliği hazırlık alanına koy (git add . hepsini).
  4. git commit -m "Şehir raporuna KDV sütunu eklendi" — anlamlı bir mesajla kaydet.
  5. git log --oneline — geçmişi gör.

İyi commit mesajı emir kipinde, kısa ve "ne, neden" sorusuna yanıt verir: "KDV hesabını Decimal'e taşı (float yuvarlama hatası)". "düzeltmeler", "wip", "asdf" gibi mesajlar üç ay sonra hiçbir şey anlatmaz.

Dal, birleştirme ve pull request

Dal (branch), ana koda dokunmadan deneme yapmanı sağlar. git switch -c sehir-raporu yeni dal açar; orada commit'lersin; iş bitince git switch main ve git merge sehir-raporu ile birleştirirsin. Ekip çalışmasında birleştirme doğrudan değil, pull request (PR; GitLab'da merge request) üzerinden yapılır: dalını sunucuya git push ile gönderirsin, PR açarsın, ekip arkadaşın inceler, onaylar, sonra main'e birleşir. Aynı satırı iki kişi farklı değiştirdiyse çakışma (conflict) çıkar; Git dosyaya <<<<<<< ve >>>>>>> işaretleri koyar, sen doğru halini seçip yeniden commit'lersin.

KomutNe yapar
git clone URLUzak depoyu indirir
git switch -c dal-adiYeni dal açıp ona geçer
git pullUzaktaki değişiklikleri alır
git push -u origin dal-adiDalı uzak depoya gönderir
git merge dal-adiDalı bulunduğun dala birleştirir
git diffKaydedilmemiş değişiklikleri gösterir
git restore dosyaDosyadaki kaydedilmemiş değişikliği geri alır
git stashYarım işi geçici rafa kaldırır

.gitignore: neyi depoya koymazsın?

Sanal ortam (virtual environment), önbellek, gizli anahtar ve büyük veri dosyaları depoya girmez. .gitignore dosyası satır satır desen (pattern) içerir; Git o desenlere uyan dosyaları görmezden gelir. Desenler kabuk joker karakterleriyle yazılır: * herhangi karakter dizisi, ? tek karakter, sonda / klasör, başta ! istisna. Python tarafında aynı jokerleri fnmatch modülü çözer; CI betiklerinde "bu dosya ignore'da mı" kontrolü böyle yazılır.

from fnmatch import fnmatch

gitignore = """
# Python
__pycache__/
*.pyc
.venv/
# Gizli bilgi
.env
# Veri ve çıktı
data/*.csv
*.log
!onemli.log
"""

def kurallar(metin: str) -> list[str]:
    return [s.strip() for s in metin.splitlines() if s.strip() and not s.strip().startswith("#")]

def yok_sayilir_mi(yol: str, kurallar: list[str]) -> bool:
    sonuc = False
    for k in kurallar:
        istisna = k.startswith("!")
        desen = k[1:] if istisna else k
        if desen.endswith("/"):
            uydu = yol.startswith(desen) or ("/" + desen) in ("/" + yol)
        else:
            uydu = fnmatch(yol, desen) or fnmatch(yol.rsplit("/", 1)[-1], desen)
        if uydu:
            sonuc = not istisna
    return sonuc

k = kurallar(gitignore)
for yol in ["src/rapor.py", "src/__pycache__/rapor.cpython-312.pyc", ".env", "data/satis.csv", "data/README.md", "hata.log", "onemli.log", ".venv/bin/python"]:
    print(f"{yol:40} {'YOK SAY' if yok_sayilir_mi(yol, k) else 'izle'}")

Kurallar sırayla uygulanır ve son eşleşen kazanır; bu yüzden !onemli.log satırı *.log'dan sonra gelince istisna olur. Gerçek Git'in eşleştirme kuralları biraz daha zengindir (**, kök bağıl yollar) ama bu basit model günlük ihtiyacın yüzde doksanını karşılar.

Bir kez push edilen gizli bilgi geri alınamaz.env dosyasını yanlışlıkla commit'leyip push ettiysen silmek yetmez: geçmişte kalır. Anahtarı derhal iptal edip yenisini üret. .env satırını .gitignore'a ilk commit'ten önce yaz.

Proje yapısı: src yerleşimi

Küçük bir betik tek dosya olabilir; ama test, bağımlılık ve ekip devreye girince standart bir ağaç gerekir. Python topluluğunun önerdiği src yerleşimi (src layout), paketi src/ altına koyar; böylece testler yanlışlıkla kurulmamış yerel kodu değil, kurulmuş paketi test eder.

import tempfile
from pathlib import Path

AGAC = {
    "README.md": "# Anadolu Rapor\n\nŞehir bazlı satış raporu aracı.\n\n## Kurulum\n\n    pip install -e .\n",
    ".gitignore": ".venv/\n__pycache__/\n.env\n",
    "pyproject.toml": '[project]\nname = "anadolu-rapor"\nversion = "0.1.0"\n',
    ".env.example": "DB_URL=sqlite:///market.db\nAPI_KEY=buraya-anahtar\n",
    "src/anadolu_rapor/__init__.py": "",
    "src/anadolu_rapor/rapor.py": "def sehir_ozeti(satirlar):\n    return {}\n",
    "tests/test_rapor.py": "import unittest\n",
}

with tempfile.TemporaryDirectory() as d:
    kok = Path(d) / "anadolu-rapor"
    for yol, icerik in AGAC.items():
        hedef = kok / yol
        hedef.parent.mkdir(parents=True, exist_ok=True)
        hedef.write_text(icerik, encoding="utf-8")
    for p in sorted(kok.rglob("*")):
        derinlik = len(p.relative_to(kok).parts) - 1
        print("  " * derinlik + ("+ " if p.is_dir() else "- ") + p.name)
    print("README ilk satır:", (kok / "README.md").read_text(encoding="utf-8").splitlines()[0])

README (benioku) dosyası projenin kapısıdır: ne yapar, nasıl kurulur, nasıl çalıştırılır, nasıl test edilir. Dört başlık yeterlidir. .env.example ise gerçek değerler olmadan hangi ortam değişkenlerinin gerektiğini gösterir; depoya girer, .env girmez.

Config ve .env: gizli bilgi koddan ayrı

Veritabanı parolası, API anahtarı, e-posta şifresi kodda yazmaz. Bunlar ortam değişkeni (environment variable) olarak işletim sisteminden okunur: os.environ bir sözlük gibi davranır. Geliştirme makinesinde değişkenleri .env dosyasından yüklersin (python-dotenv paketi ya da birkaç satırlık kendi okuyucun); sunucuda ise dağıtım sistemi onları doğrudan tanımlar. Kod iki ortamda da aynıdır.

import os

ENV_ORNEK = """
# Anadolu Market yerel ayarlar
DB_URL=sqlite:///market.db
API_KEY="abc-123"
DEBUG=true
"""

def env_yukle(metin: str) -> dict[str, str]:
    degerler = {}
    for satir in metin.splitlines():
        satir = satir.strip()
        if not satir or satir.startswith("#") or "=" not in satir:
            continue
        ad, deger = satir.split("=", 1)
        degerler[ad.strip()] = deger.strip().strip('"').strip("'")
    return degerler

for ad, deger in env_yukle(ENV_ORNEK).items():
    os.environ.setdefault(ad, deger)   # gerçek ortamda zaten varsa ezme

def ayar(ad: str, varsayilan: str | None = None) -> str:
    deger = os.environ.get(ad, varsayilan)
    if deger is None:
        raise RuntimeError(f"Eksik ortam değişkeni: {ad} (.env dosyasını kontrol et)")
    return deger

print("DB_URL :", ayar("DB_URL"))
print("API_KEY:", ayar("API_KEY")[:3] + "***")
print("DEBUG  :", ayar("DEBUG").lower() == "true")
print("PORT   :", ayar("PORT", "8000"))
try:
    ayar("SMTP_SIFRE")
except RuntimeError as e:
    print("hata:", e)

setdefault önemli: sunucuda tanımlı gerçek değer varsa .env'deki değeri yazmaz. Eksik değişkende programın ilk saniyede anlaşılır bir mesajla durması, iki saat sonra "None" hatasıyla çökmesinden iyidir.

Anahtarı loglamaHata ayıklarken print(os.environ) yazmak tüm gizli bilgileri log dosyasına döker. Değeri göstermen gerekiyorsa ilk üç karakter artı yıldız yeter.

Kod inceleme kültürü

Pull request'in asıl değeri kodun ikinci bir göz tarafından okunmasıdır. İnceleyen (reviewer) şunlara bakar: iş kuralı doğru mu, test var mı, isimler anlaşılır mı, gizli bilgi sızmış mı, aynı şey daha basit yazılabilir mi? Yorum yazarken kişiyi değil kodu konuş: "Bu döngü O(n²), sözlükle tek geçişte olur" iyi; "Bunu nasıl böyle yazarsın" kötü. Yazar olarak ise PR'ı küçük tut (bir PR bir iş), açıklamaya "neden"i yaz ve yorumları savunma değil öğrenme fırsatı olarak gör. İncelemeyi bekletmemek de nezakettir: küçük PR aynı gün incelenir.

Derinleş

Derinleş: Git'i gerçekten kullanmak

Temel akışı (add, commit, dal, PR) ve proje ağacını gördün. İşte ilk haftanda ise şu sorular gelir: "Git'i nasıl kurarım, kimliğimi nasıl tanıtırım?", "Yanlış commit'i nasıl geri alırım?", "Birleştirmede çakışma çıktı, bu <<<<<<< işaretleri ne?", "Windows'ta çalışan arkadaşımın dosyalarında neden her satır değişmiş görünüyor?", "Hangi commit hatayı getirdi?". Bu bölüm bunları yanıtlar ve Git çıktılarını Python'la işleyen küçük araçlar yazdırır. Otomatik kalite kontrolleri (pre-commit, GitHub Actions) kalite-ci, paket sürümleme ve yayımlama paketleme-dagitim dersinde.

Kurulum ve ilk ayarlar

# Windows:  winget install --id Git.Git -e   (ya da git-scm.com; Git Bash da gelir)
# macOS:    xcode-select --install   ya da   brew install git
# Ubuntu:   sudo apt install git     | Fedora: sudo dnf install git
git --version

git config --global user.name "Ayşe Yılmaz"
git config --global user.email "ayse@ilgazbank.example"
git config --global init.defaultBranch main
git config --global pull.rebase true          # pull sırasında gereksiz merge commit'i olmasın
git config --global core.autocrlf true        # yalnız Windows: depoda LF, diskte CRLF
git config --global core.autocrlf input       # macOS/Linux
git config --list --show-origin               # hangi ayar nereden geliyor

# GitHub/GitLab için SSH anahtarı (parola yerine)
ssh-keygen -t ed25519 -C "ayse@ilgazbank.example"
cat ~/.ssh/id_ed25519.pub        # bu satırı GitHub > Settings > SSH keys'e yapıştır
ssh -T git@github.com            # "Hi ayse! You've successfully authenticated"

Satır sonu farkı (Windows CRLF \r\n, Linux/macOS LF \n) karışık ekiplerde "her satır değişti" görünen diff'lerin bir numaralı nedenidir. Ekip genelinde kalıcı çözüm depo köküne bir .gitattributes koymaktır: * text=auto eol=lf ve *.bat text eol=crlf.

dosyalar = {
    "src/rapor.py": b"import csv\nprint('ok')\n",
    "src/kargo.py": b"UCRET = 45\r\nKDV = 0.2\r\n",
    "scripts/kur.bat": b"@echo off\r\npy -m venv .venv\r\n",
    "README.md": b"# Rapor\r\nKurulum\n",
}
for yol, icerik in dosyalar.items():
    crlf = icerik.count(b"\r\n")
    lf = icerik.count(b"\n") - crlf
    tur = "KARIŞIK" if crlf and lf else "CRLF" if crlf else "LF"
    uyari = "" if tur == "LF" or yol.endswith(".bat") else "  <- .gitattributes ile LF'e çevrilmeli"
    print(f"{yol:18} {tur}{uyari}")

Geri alma: hangi komut ne zaman?

DurumKomutPaylaşılmış geçmişte güvenli mi?
Dosyadaki kaydedilmemiş değişikliği atgit restore dosyaEvet (yerel)
Yanlışlıkla add ettingit restore --staged dosyaEvet (yerel)
Son commit mesajı yanlış / dosya unutuldugit commit --amendYalnız push etmediysen
Son commit'i geri al, değişiklikler kalsıngit reset --soft HEAD~1Yalnız push etmediysen
Push edilmiş commit'i geri algit revert <hash>Evet: ters commit üretir
"Kaybettim" sandığın commit'i bulgit reflogEvet (yerel kayıt)
Yarım işi rafa kaldır / geri algit stash / git stash popEvet
reset --hard ve push --forcegit reset --hard kaydedilmemiş işi geri dönüşsüz siler. Paylaşılan dalda geçmişi yeniden yazıp push --force yapmak ekip arkadaşlarının işini bozar; zorunluysan --force-with-lease kullan ve önce haber ver. Ana dalda geri almanın yolu her zaman revert'tür.

Birleştirme çakışması (merge conflict)

İki dal aynı satırı farklı değiştirdiyse Git karar veremez ve dosyaya işaretler koyar. <<<<<<< HEAD ile ======= arası senin dalın, ======= ile >>>>>>> arası gelen dal. Doğru sonucu elle yazar, işaretleri silersin, sonra git add dosya ve git commit (rebase sırasındaysan git rebase --continue). Vazgeçmek için git merge --abort. İşaretlerin yanlışlıkla commit'lenmemesi için CI'da basit bir kontrol çok işe yarar:

dosya = """VERGI_ORANI = 0.20
<<<<<<< HEAD
KARGO_UCRETI = 45
=======
KARGO_UCRETI = 49.90
>>>>>>> kargo-zammi
PARA_BIRIMI = "TL"
"""

def cakismalar(metin):
    bloklar, durum, bizim, onlarin = [], None, [], []
    for satir in metin.splitlines():
        if satir.startswith("<<<<<<<"):
            durum, bizim, onlarin = "bizim", [], []
        elif satir.startswith("=======") and durum == "bizim":
            durum = "onlarin"
        elif satir.startswith(">>>>>>>") and durum == "onlarin":
            bloklar.append({"bizim": bizim, "onlarin": onlarin, "dal": satir[8:].strip()})
            durum = None
        elif durum == "bizim":
            bizim.append(satir)
        elif durum == "onlarin":
            onlarin.append(satir)
    return bloklar

for b in cakismalar(dosya):
    print(f"main: {b['bizim']}  |  {b['dal']}: {b['onlarin']}")

Rebase, etiket ve geçmişte arama

git rebase main dalındaki commit'leri main'in en güncel hâlinin üstüne taşır; geçmiş düz kalır. Kural: yalnızca kendi, paylaşılmamış dalını rebase et. Sürüm yayımlarken git tag -a v1.4.0 -m "Kasım sürümü" ve git push origin v1.4.0. Hatanın hangi commit'le geldiğini bulmak için git bisect ikili arama yapar: iyi ve kötü commit'i söylersin, Git ortadakini açar, sen test edersin; birkaç adımda suçlu bulunur. git blame dosya her satırı son değiştireni, git log -S "KARGO_UCRETI" --oneline o metni ekleyen/silen commit'leri gösterir.

git log --oneline --graph --all -20       # dalları görsel izle
git log --since="2 weeks" --author="Ayşe" -- src/
git show v1.4.0 --stat
git bisect start; git bisect bad; git bisect good v1.3.0
git bisect run python -m pytest tests/test_kargo.py   # otomatik ikili arama
git bisect reset

Git'in "makine dostu" çıktılarını betiklerle işlemek CI ve sürüm otomasyonunun temelidir. git status --porcelain her satırda iki harfli durum kodu verir (M değişti, A eklendi, ?? izlenmiyor); Conventional Commits (feat:, fix:, feat!:) mesajlarından ise sürüm numarası ve değişiklik günlüğü (CHANGELOG) otomatik üretilir:

import re

log = """a1b2c3d feat(rapor): şehir kırılımı eklendi
9f8e7d6 fix: KDV yuvarlaması Decimal'e taşındı
5c4b3a2 docs: README kurulum adımları
1a2b3c4 feat!: CSV ayırıcısı ; oldu"""

gruplar = {"Yeni özellikler": [], "Hata düzeltmeleri": [], "Kırıcı değişiklikler": []}
for satir in log.splitlines():
    h, mesaj = satir.split(" ", 1)
    m = re.match(r"(\w+)(\([\w-]+\))?(!)?: (.+)", mesaj)
    if not m:
        continue
    tur, _, kirici, ozet = m.groups()
    if kirici:
        gruplar["Kırıcı değişiklikler"].append(f"{ozet} ({h})")
    elif tur == "feat":
        gruplar["Yeni özellikler"].append(f"{ozet} ({h})")
    elif tur == "fix":
        gruplar["Hata düzeltmeleri"].append(f"{ozet} ({h})")
for baslik, maddeler in gruplar.items():
    if maddeler:
        print("##", baslik)
        print("\n".join("- " + x for x in maddeler))
README, LICENSE, CHANGELOGİyi bir depoda kökte en az şunlar bulunur: ne işe yaradığını ve nasıl kurulup çalıştırılacağını anlatan README.md, kullanım hakkını belirten LICENSE (şirket içi kodda genellikle "tescilli"), sürüm farklarını anlatan CHANGELOG.md, örnek ayar .env.example ve satır sonu kuralı için .gitattributes.
Gerçek işteIlgaz Bank'ta ana dal korumalıdır: doğrudan push yasaktır, her değişiklik PR ile gelir, en az bir onay ve yeşil CI gerekir, birleştirme "squash" ile tek commit'e indirilir. Canlıda hata çıkınca önce git revert ile hızla geri dönülür, sonra kök neden ayrı bir dalda düzeltilir. Sürüm etiketleri ve CHANGELOG, Conventional Commits mesajlarından otomatik üretilir; bu yüzden commit mesajı kuralı PR kontrolünde denetlenir.
Derinleş

Ortak çalışma: issue, inceleme, birleştirme kararı

Git komutları işin yarısıdır; diğer yarısı insanlarla anlaşmaktır. Depo paylaşılınca değişiklik bir sohbetle başlar, bir kararla biter.

Aşamaİyi uygulama
Issue (kayıt)Ne bekleniyor, ne oluyor, nasıl tekrar edilir, hangi sürüm. Karar ve gerekçe de buraya yazılır.
Dal ve commitTek amaçlı dal, anlaşılır mesaj: Kupon tutarını Decimal'e taşı. Issue numarasını mesaja ekle.
Pull requestNeyi, neden değiştirdiğin ve nasıl denediğin. Küçük tutulan değişiklik daha iyi incelenir.
Kod inceleme (code review)Kişiye değil koda, somut ve gerekçeli yorum: "Boş liste gelirse burada sıfıra bölünür, bir test ekleyelim mi?"
Yoruma cevapKabul ettiysen düzelt, etmediysen nedenini yaz. Sessizce kapatma; karar kayıtta kalsın.
CI (sürekli entegrasyon)Her değişiklikte aynı testler aynı ortamda çalışır. "Bende çalışıyor" tartışmasını bitirir.
Birleştirme kararıTestler geçti, inceleme onaylandı, açık yorum kalmadı. Biri eksikse birleştirme.

Çakışma (merge conflict) en çok iki kişi aynı satırı değiştirince çıkar. Sık git pull, küçük değişiklik ve işi önceden konuşmak çakışmayı azaltır. Çıktığında iki tarafın niyetini oku, gerekiyorsa arkadaşına sor, sonra testleri yeniden çalıştır.

Derinleş

Bir işe alımcı portföy deponu ortalama bir-iki dakika inceler ve çoğu zaman yalnız README'yi okur. Bu bölüm, bir analiz ya da yazılım projesini GitHub'da "okunur" bir portföy parçasına çevirmeyi anlatıyor: README şablonu, bulgu özeti, ekran görüntüleri ve depoyu yayına alma adımları.

Portföy deposunun yapısı

anadolu-market-analizi/
├── README.md              ← vitrin: 1 dakikada okunur
├── notebooks/             ← keşif (çıktıları temizlenmiş, nbstripout)
├── src/analiz/            ← tekrar kullanılan kod
├── raporlar/rapor.html    ← nbconvert/Quarto çıktısı
├── gorseller/             ← README'de kullanılan PNG'ler (küçük, sıkıştırılmış)
├── data/README.md         ← veri nereden, nasıl indirilir (ham veri depoda değil)
├── tests/
├── pyproject.toml / requirements.txt
└── LICENSE

README şablonu

İyi bir portföy README'si bir hikâye anlatır: sorun neydi, ne yaptın, ne buldun, nasıl tekrar üretilir. Aşağıdaki başlıklar hem analiz hem yazılım projelerinde işe yarar:

# Anadolu Market: Şehir bazlı satış ve iade analizi

> 910 siparişlik e-ticaret verisinde ciro ve iadeyi şehir, kanal ve zamana göre inceledim.
> En önemli bulgu: ... (tek cümle, rakamla)

![Şehir bazlı ciro](gorseller/sehir_ciro.png)

## Sorun
Yönetim hangi şehirlerde büyümeye yatırım yapılacağını soruyor.

## Veri
- Kaynak: Kesit Python "anadolu_market" kurgusal veri seti (10 tablo, 6.236 satır)
- Dönem: 2022-06 – 2024-12

## Yöntem
SQL (sqlite3) ile birleştirme → pandas ile temizlik → kohort ve RFM → görselleştirme

## Bulgular
| Şehir | Teslim edilen sipariş | Ciro (TL) |
|---|---|---|
| İstanbul | 162 | 199.387,25 |
| ...

1. ...
2. ...

## Öneriler ve sınırlamalar

## Nasıl çalıştırılır
    git clone https://github.com/kullanici/anadolu-market-analizi
    cd anadolu-market-analizi && pip install -r requirements.txt
    python -m analiz.rapor        # raporlar/rapor.html üretir

## Teknolojiler
Python 3.12 · pandas · SQLite · matplotlib · pytest
Bulgu özeti nasıl yazılır?Her bulgu bir cümle, bir rakam ve bir "neden önemli" içersin: "İzmir, İstanbul'un yarısı kadar siparişle onun cirosunun üçte ikisini üretiyor; sipariş başı ciro İstanbul'dan %26 yüksek." Yöntemi bulgunun önüne koyma; okuyucu önce sonucu görmek ister. Sınırlamaları dürüstçe yaz (kurgusal veri, kısa dönem, örneklem küçük): bu bir zayıflık değil, olgunluk işaretidir.

Bulgu tablosunu elle yazmak yerine koddan üret; README sayıları ile rapor sayıları hiç ayrışmaz:

import veri
conn = veri.baglan("anadolu_market", tablolar=["orders", "order_items", "customers"])
satirlar = conn.execute("""
    SELECT c.city, COUNT(DISTINCT o.order_id),
           ROUND(SUM(oi.quantity * oi.unit_price * (1 - oi.discount)), 2) AS ciro
    FROM orders o JOIN customers c USING (customer_id) JOIN order_items oi USING (order_id)
    WHERE o.status = 'delivered' AND c.city IS NOT NULL
    GROUP BY c.city ORDER BY ciro DESC LIMIT 3""").fetchall()

tl = lambda x: f"{x:,.2f}".replace(",", "X").replace(".", ",").replace("X", ".")
print("| Şehir | Teslim edilen sipariş | Ciro (TL) |")
print("|---|---|---|")
for sehir, n, ciro in satirlar:
    print(f"| {sehir} | {n} | {tl(ciro)} |")

Ekran görüntüleri ve görseller

README'nin en üstündeki tek bir grafik ya da uygulama ekran görüntüsü, okuyucunun kalmasını sağlar. Görselleri gorseller/ klasörüne koy, göreli yolla bağla (![açıklama](gorseller/x.png)), alternatif metni anlamlı yaz, boyutu küçük tut (genişlik 1200 px civarı, birkaç yüz KB). Etkileşimli bir uygulama ise kısa bir GIF ya da canlı demo bağlantısı (Streamlit Community Cloud, Render, GitHub Pages) çok etkilidir.

GitHub'da yayın

  1. Gizli bilgi taraması: .env, parola, kişisel veri depoda olmamalı (git log -p | grep -i parola; GitHub secret scanning da uyarır).
  2. Büyük dosya: GitHub 100 MB üstü dosyayı reddeder, 50 MB'ta uyarır. Veriyi depoya koyma; nasıl indirileceğini data/README.md'de anlat ya da Git LFS kullan.
  3. Notebook çıktılarını temizle (nbstripout) ama rapor HTML'ini raporlar/'a koy.
  4. gh repo create anadolu-market-analizi --public --source=. --push ya da web arayüzünden depo oluşturup git remote add origin ... + git push -u origin main.
  5. Depo sayfasında "About" kısmına tek cümlelik açıklama ve konular (topics: python, pandas, data-analysis) ekle; profilinde en iyi 4-6 depoyu sabitle (pin).
  6. Rapor HTML'ini GitHub Pages ile yayınla (Settings → Pages → main/docs); README'den bağlantı ver.
Gerçek işteİşe alımcılar çok sayıda "Titanic" ve "Iris" deposu görür. Seni ayıran; gerçekçi bir iş sorusu, koddan üretilen ve tekrar edilebilen sonuçlar, temiz klasör yapısı, testler ve tek komutla çalışan kurulumdur. Kesit Python, Kesit SQL ve Kesit Power BI'daki aynı veri setleriyle yaptığın SQL, Python ve Power BI analizlerini tek bir portföy hikâyesinde birleştirebilirsin.
Uzmanlaş

Dal stratejisi ve otomasyon

Ekipler büyüdükçe dal düzeni kural haline gelir. En yaygın model "GitHub flow": main her zaman çalışır durumdadır; her iş kısa ömürlü bir dalda yapılır (feature/sehir-raporu, fix/kdv-yuvarlama); PR ile birleşir; birleşince dal silinir. Daha katı kurumlarda (bankacılık gibi) develop ve release dalları da olur. Hangi modeli kullanırsan kullan, kural aynı: main'e doğrudan push yok, her değişiklik incelenir.

Otomasyon bunu destekler. Pre-commit kancaları (hook) commit'ten önce biçimlendiriciyi (ruff, black) ve basit kontrolleri çalıştırır; böylece inceleme, girinti ve boşluk tartışmasına harcanmaz. CI (GitHub Actions, GitLab CI) her PR'da testleri koşar ve kırmızıysa birleştirmeyi engeller. Commit mesajlarında "Conventional Commits" düzeni (feat:, fix:, docs:) değişiklik günlüğünü otomatik üretmeyi sağlar. İleri seviyedeki "Kalite ve CI" dersinde bu zinciri uçtan uca kuracaksın; bugün için hedefin küçük: anlamlı commit, temiz .gitignore, gizli bilgi .env'de.

Alıştırmalara geç (17)