Skip to content

📌 Ne Zaman Kullanılır?

  • ✅ Veri analizi, otomasyon, web backend (Django/FastAPI), ML/AI, scripting
  • ⚠️ Real-time/WebSocket için Node.js daha yaygın
  • ❌ Frontend geliştirme, mobil uygulama (native)

Önerilen Kullanım: FastAPI/Django + PostgreSQL (web) veya veri bilimi/otomasyon Alternatifler: Node.js (web API), Go (performans), Ruby (Rails)

Python Rehberi -- Temelden İleri Seviyeye (EN + TR)

🐍 Complete English + Turkish guide for Python: syntax, OOP, data science (NumPy/Pandas), web (Flask), automation (Selenium), GUI (PyQt5/Kivy), visualization (Folium), game dev (Pygame), testing & deployment. (🐍 Python için kapsamlı rehber -- temel söz dizimi, OOP, veri bilimi, web, otomasyon, GUI, harita, oyun geliştirme, test ve dağıtım.)

1) Temeller (Basics)

python
# Değişkenler & Tipler (Variables & Types)
x = 42              # int
pi = 3.14            # float
name = "Fahri"       # str
is_ok = True         # bool
nums = [1, 2, 3]     # list
coords = (10, 20)    # tuple (değiştirilemez / immutable)
data = {"a": 1}      # dict
unique = {1, 2, 3}   # set
nothing = None       # NoneType
python
# String formatting (f-string)
age = 25
print(f"Merhaba, ben {name}. Yaşım {age}.")

# Çoklu satır (multiline)
text = """
Bu çok
satırlı bir string.
"""

# String methodları
"hello world".upper()          # "HELLO WORLD"
"hello world".split()          # ["hello", "world"]
"hello world".replace("o","0") # "hell0 w0rld"
"  space  ".strip()            # "space"

Type conversion (Tür dönüştürme):

python
int("42")       # 42
float("3.14")   # 3.14
str(42)         # "42"
list("abc")     # ["a", "b", "c"]
bool(0)         # False
bool("")        # False
bool([])        # False

2) Control Flow & Functions

python
# if / elif / else
if x > 10:
    print("Büyük")
elif x == 10:
    print("Eşit")
else:
    print("Küçük")

# Ternary
status = "ok" if x > 0 else "error"

# for döngüsü
for i in range(5):        # 0, 1, 2, 3, 4
    print(i)

for i, val in enumerate(["a", "b", "c"]):
    print(f"{i}: {val}")

# while döngüsü
while x > 0:
    x -= 1

# Fonksiyonlar
def greet(name: str = "world") -> str:
    """Selamlama fonksiyonu."""
    return f"Hello, {name}!"

# *args & **kwargs
def log(*args, **kwargs):
    print(args)    # tuple
    print(kwargs)  # dict

# Lambda
square = lambda n: n * n
sorted_list = sorted([3, 1, 2], key=lambda x: -x)  # [3, 2, 1]

# Decorators
def timer(func):
    import time
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__}: {time.time() - start:.3f}s")
        return result
    return wrapper

@timer
def slow_func():
    import time
    time.sleep(1)

3) Collections & Comprehensions

python
# List comprehension
squares = [n * n for n in range(10)]
evens = [n for n in range(20) if n % 2 == 0]

# Dict comprehension
mapping = {c: ord(c) for c in "abc"}
# {'a': 97, 'b': 98, 'c': 99}

# Set comprehension
unique = {x % 3 for x in range(6)}  # {0, 1, 2}

# Generator expression (bellek dostu / memory efficient)
gen = (n * n for n in range(1_000_000))

Sık kullanılan list/dict methodları:

MethodAçıklama
list.append(x)Sona ekle
list.extend([1,2])Listeyi genişlet
list.insert(i, x)Belirli indexe ekle
list.pop(i)Indexten çıkar
list.sort()Sırala (in-place)
dict.get(k, default)Güvenli erişim
dict.keys()Anahtarlar
dict.values()Değerler
dict.items()(key, value) çiftleri
dict.update({...})Güncelle/birleştir

4) Modules, Packages & Virtual Envs

bash
# Virtual environment oluştur (Create venv)
python -m venv .venv
source .venv/bin/activate        # Linux/Mac
# .venv\Scripts\activate         # Windows

# Paket yönetimi (Package management)
pip install requests flask numpy
pip freeze > requirements.txt
pip install -r requirements.txt

# Poetry (modern alternatif)
pip install poetry
poetry init
poetry add requests
poetry add --group dev pytest
poetry install
poetry build
python
# Kendi modülün (Your own module)
# mypkg/__init__.py
# mypkg/utils.py
from .utils import slugify

# Standart kütüphane örnekleri
import os, sys, json, re, datetime
from pathlib import Path
from collections import Counter, defaultdict
from itertools import chain, product

5) OOP, Dataclasses & Exceptions

python
# Temel sınıf (Basic class)
class Animal:
    def __init__(self, name: str, sound: str):
        self.name = name
        self.sound = sound

    def speak(self) -> str:
        return f"{self.name} says {self.sound}"

    def __repr__(self):
        return f"Animal({self.name!r})"

# Kalıtım (Inheritance)
class Dog(Animal):
    def __init__(self, name: str):
        super().__init__(name, "Woof")

    def fetch(self, item: str) -> str:
        return f"{self.name} fetches {item}"

# Dataclass (Python 3.7+)
from dataclasses import dataclass, field

@dataclass
class User:
    id: int
    name: str
    email: str
    tags: list[str] = field(default_factory=list)

user = User(1, "Fahri", "info@fahriaydin.dev")

# Exception handling
class AppError(Exception):
    """Custom application error."""
    pass

try:
    result = risky_operation()
except ValueError as e:
    print(f"Değer hatası: {e}")
except (TypeError, KeyError) as e:
    print(f"Tip/Anahtar hatası: {e}")
except Exception as e:
    print(f"Beklenmeyen hata: {e}")
finally:
    cleanup()

6) Typing & Static Analysis

python
from typing import Optional, Union, Literal
from collections.abc import Iterable, Callable

# Temel typing
def total(xs: Iterable[int]) -> int:
    return sum(xs)

age: Optional[int] = None                    # int | None
value: Union[str, int] = "hello"             # str veya int
mode: Literal["read", "write"] = "read"      # sadece belirli değerler

# Callable typing
Handler = Callable[[str, int], bool]

# Generic (Python 3.12+)
def first[T](items: list[T]) -> T | None:
    return items[0] if items else None

# TypedDict
from typing import TypedDict

class Config(TypedDict):
    host: str
    port: int
    debug: bool
bash
# mypy ile statik tip kontrolü
pip install mypy
mypy .

7) Files, Paths & CLI

python
from pathlib import Path

# Path işlemleri
p = Path("data/output.txt")
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text("hello", encoding="utf-8")
content = p.read_text(encoding="utf-8")

# Dosya listesi (glob)
for f in Path(".").rglob("*.py"):
    print(f)

# argparse (CLI)
from argparse import ArgumentParser

parser = ArgumentParser(description="Veri işleme aracı")
parser.add_argument("input", help="Girdi dosyası")
parser.add_argument("--output", "-o", default="out.json")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()

8) Data Handling: JSON/CSV/SQLite

python
import json, csv, sqlite3

# --- JSON ---
data = {"name": "Fahri", "skills": ["Python", "React"]}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
parsed = json.loads(json_str)

# Dosyaya yaz/oku
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

with open("data.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)

# --- CSV ---
with open("out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerow(["id", "name", "score"])
    writer.writerow([1, "Fahri", 95])

with open("out.csv", "r", encoding="utf-8") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["score"])

# --- SQLite ---
con = sqlite3.connect("app.db")
cur = con.cursor()
cur.execute("""
    CREATE TABLE IF NOT EXISTS users (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        name TEXT NOT NULL,
        email TEXT UNIQUE
    )
""")
cur.execute("INSERT INTO users(name, email) VALUES (?, ?)", ("Fahri", "info@fahriaydin.dev"))
con.commit()

for row in cur.execute("SELECT * FROM users"):
    print(row)
con.close()

9) XML Okuma & Yazma

XML, yapılandırılmış veri alışverişinde hala yaygın olarak kullanılır (RSS, sitemap, config, SOAP). (XML is still widely used for structured data exchange -- RSS, sitemaps, configs, SOAP.)

python
import xml.etree.ElementTree as ET

# --- XML Okuma (Parsing) ---
xml_str = """<?xml version="1.0" encoding="UTF-8"?>
<catalog>
    <book id="1" lang="tr">
        <title>Python Rehberi</title>
        <author>Fahri</author>
        <price currency="TRY">150.00</price>
    </book>
    <book id="2" lang="en">
        <title>Flask Web Dev</title>
        <author>Miguel</author>
        <price currency="USD">39.99</price>
    </book>
</catalog>"""

root = ET.fromstring(xml_str)          # String'den parse et
# tree = ET.parse("catalog.xml")       # Dosyadan parse et
# root = tree.getroot()

# Element bilgileri
print(root.tag)                        # "catalog"
print(root[0].tag)                     # "book"
print(root[0].attrib)                  # {'id': '1', 'lang': 'tr'}

# --- XPath ile Arama ---
for book in root.findall("book"):
    title = book.find("title").text
    author = book.find("author").text
    price = book.find("price").text
    lang = book.get("lang")
    print(f"[{lang}] {title} - {author} ({price})")

# Attribute filtresi ile arama
tr_books = root.findall(".//book[@lang='tr']")
print(f"Türkçe kitap sayısı: {len(tr_books)}")

# --- XML Oluşturma ---
catalog = ET.Element("catalog")
book = ET.SubElement(catalog, "book", id="3", lang="tr")
ET.SubElement(book, "title").text = "Django Rehberi"
ET.SubElement(book, "author").text = "Fahri"
ET.SubElement(book, "price", currency="TRY").text = "200.00"

tree = ET.ElementTree(catalog)
ET.indent(tree, space="    ")          # Python 3.9+ (pretty print)
tree.write("output.xml", encoding="utf-8", xml_declaration=True)

# --- lxml (daha gelismis XPath & XSLT destegi) ---
# pip install lxml
from lxml import etree

doc = etree.fromstring(xml_str.encode())
results = doc.xpath("//book[price > 100]/title/text()")
print(results)  # ['Python Rehberi']

Pratik: RSS Feed Parse Etme

python
import xml.etree.ElementTree as ET
import urllib.request

# RSS feed indir ve parse et
url = "https://blog.python.org/feeds/posts/default?alt=rss"
response = urllib.request.urlopen(url)
tree = ET.parse(response)
root = tree.getroot()

# RSS item'larini listele
for item in root.findall(".//item")[:5]:
    title = item.find("title").text
    link = item.find("link").text
    print(f"- {title}\n  {link}\n")

Pratik: Sitemap Parse Etme

python
import xml.etree.ElementTree as ET
import urllib.request

url = "https://example.com/sitemap.xml"
response = urllib.request.urlopen(url)
tree = ET.parse(response)
root = tree.getroot()

# Sitemap namespace
ns = {"sm": "http://www.sitemaps.org/schemas/sitemap/0.9"}
for url_elem in root.findall("sm:url", ns):
    loc = url_elem.find("sm:loc", ns).text
    lastmod = url_elem.find("sm:lastmod", ns)
    print(f"{loc}  ({lastmod.text if lastmod is not None else 'N/A'})")

10) NumPy -- Sayısal Hesaplama

NumPy, Python'da hızlı sayısal işlemler için temel kütüphanedir. (NumPy is the foundational library for fast numerical computing in Python.)

bash
pip install numpy
python
import numpy as np

# Array oluşturma (Creating arrays)
a = np.array([1, 2, 3, 4, 5])
zeros = np.zeros((3, 4))            # 3x4 sıfır matrisi
ones = np.ones((2, 3))              # 2x3 birler matrisi
rng = np.arange(0, 10, 2)           # [0, 2, 4, 6, 8]
lin = np.linspace(0, 1, 5)          # [0, 0.25, 0.5, 0.75, 1.0]
rand = np.random.rand(3, 3)         # 3x3 rastgele (0-1)
eye = np.eye(3)                     # 3x3 birim matris

# Temel işlemler (Basic operations)
b = np.array([10, 20, 30, 40, 50])
print(a + b)         # Eleman bazlı toplama
print(a * 2)         # Skaler çarpma
print(a ** 2)        # Kare alma
print(np.dot(a, b))  # İç çarpım (dot product)

# Şekil değiştirme (Reshaping)
matrix = np.arange(12).reshape(3, 4)
print(matrix.shape)   # (3, 4)
flat = matrix.flatten()

# İndeksleme & dilimleme (Indexing & slicing)
print(matrix[0, :])     # İlk satır
print(matrix[:, 1])     # İkinci sütun
print(matrix[1:, :2])   # Alt-sol blok

# İstatistik (Statistics)
data = np.array([23, 45, 12, 67, 34])
print(np.mean(data))    # Ortalama
print(np.median(data))  # Medyan
print(np.std(data))     # Standart sapma
print(np.min(data))     # Minimum
print(np.max(data))     # Maksimum
print(np.argmax(data))  # Max index

# Boolean indexing (Koşullu filtreleme)
filtered = data[data > 30]  # [45, 67, 34]

# Lineer cebir (Linear algebra)
A = np.array([[1, 2], [3, 4]])
inv = np.linalg.inv(A)       # Ters matris
det = np.linalg.det(A)       # Determinant
vals, vecs = np.linalg.eig(A)  # Özdeğer/özvektör

11) Pandas -- Veri Analizi

Pandas, tablo verisi (CSV, Excel, SQL) üzerinde analiz yapmak için kullanılır. (Pandas is used for tabular data analysis -- CSV, Excel, SQL and more.)

bash
pip install pandas openpyxl
python
import pandas as pd

# --- DataFrame Oluşturma ---
df = pd.DataFrame({
    "name": ["Fahri", "Ali", "Ayşe", "Mehmet"],
    "age": [25, 30, 22, 35],
    "city": ["İstanbul", "Ankara", "İzmir", "İstanbul"],
    "salary": [8000, 12000, 7000, 15000]
})

# CSV okuma/yazma
df = pd.read_csv("data.csv")
df.to_csv("output.csv", index=False)

# Excel okuma/yazma
df = pd.read_excel("data.xlsx", sheet_name="Sheet1")
df.to_excel("output.xlsx", index=False)

# --- Temel İnceleme (Basic inspection) ---
print(df.head())          # İlk 5 satır
print(df.tail(3))         # Son 3 satır
print(df.info())          # Veri tipleri & null sayısı
print(df.describe())      # İstatistiksel özet
print(df.shape)           # (satır, sütun)
print(df.columns.tolist())  # Sütun isimleri

# --- Filtreleme (Filtering) ---
young = df[df["age"] < 30]
istanbul = df[df["city"] == "İstanbul"]
multi = df[(df["age"] > 25) & (df["salary"] > 10000)]

# --- Sıralama (Sorting) ---
df_sorted = df.sort_values("salary", ascending=False)

# --- Gruplama (GroupBy) ---
city_avg = df.groupby("city")["salary"].mean()
city_stats = df.groupby("city").agg(
    avg_salary=("salary", "mean"),
    count=("name", "count"),
    max_age=("age", "max")
)

# --- Yeni sütun ekleme ---
df["tax"] = df["salary"] * 0.15
df["category"] = df["age"].apply(lambda x: "genç" if x < 30 else "deneyimli")

# --- Eksik veri (Missing data) ---
df.isnull().sum()              # Sütun bazında null sayısı
df.dropna()                    # Null olanları çıkar
df.fillna(0)                   # Null'ları 0 ile doldur
df["age"].fillna(df["age"].median(), inplace=True)

# --- Birleştirme (Merge/Join) ---
orders = pd.DataFrame({"user_id": [1, 2, 1], "product": ["A", "B", "C"]})
users = pd.DataFrame({"user_id": [1, 2], "name": ["Fahri", "Ali"]})
merged = pd.merge(orders, users, on="user_id", how="left")

# --- Pivot table ---
pivot = df.pivot_table(values="salary", index="city", aggfunc="mean")

12) Flask -- Web Framework

Flask, hafif ve esnek bir Python web framework'üdür. (Flask is a lightweight and flexible Python web framework.)

bash
pip install flask
python
from flask import Flask, request, jsonify, render_template, redirect, url_for

app = Flask(__name__)

# --- Basit route'lar ---
@app.route("/")
def index():
    return render_template("index.html")

@app.get("/ping")
def ping():
    return jsonify(ok=True)

# --- Parametreli route ---
@app.route("/user/<int:user_id>")
def get_user(user_id):
    return jsonify(id=user_id, name="Fahri")

# --- POST & JSON ---
@app.route("/api/items", methods=["POST"])
def create_item():
    data = request.get_json()
    name = data.get("name")
    if not name:
        return jsonify(error="name is required"), 400
    return jsonify(id=1, name=name), 201

# --- Query params ---
@app.route("/search")
def search():
    q = request.args.get("q", "")
    page = request.args.get("page", 1, type=int)
    return jsonify(query=q, page=page)

# --- Form data ---
@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == "POST":
        username = request.form["username"]
        password = request.form["password"]
        # authenticate...
        return redirect(url_for("index"))
    return render_template("login.html")

# --- Error handlers ---
@app.errorhandler(404)
def not_found(e):
    return jsonify(error="Not found"), 404

@app.errorhandler(500)
def server_error(e):
    return jsonify(error="Internal server error"), 500

# --- Blueprint (modüler yapı) ---
from flask import Blueprint

api_bp = Blueprint("api", __name__, url_prefix="/api/v1")

@api_bp.route("/users")
def list_users():
    return jsonify(users=[])

app.register_blueprint(api_bp)

if __name__ == "__main__":
    app.run(debug=True, port=5000)

Proje yapısı (Project structure):

myapp/
├── app.py              # veya __init__.py
├── config.py
├── models/
├── routes/
│   ├── __init__.py
│   ├── auth.py
│   └── api.py
├── templates/
│   ├── base.html
│   └── index.html
├── static/
│   ├── css/
│   └── js/
└── requirements.txt

13) Django -- Web Framework

Django, "batteries included" felsefesiyle Python'un en kapsamli web framework'udur. (Django is Python's most comprehensive web framework with a "batteries included" philosophy.)

Kurulum & Proje Oluşturma

bash
# Kurulum
pip install django

# Proje olustur
django-admin startproject myproject .
# "." ile bulundugun dizinde olusturur (ekstra klasor yok)

# Uygulama (app) olustur
python manage.py startapp blog

# Gelistirme sunucusu
python manage.py runserver              # http://127.0.0.1:8000
python manage.py runserver 0.0.0.0:8080 # Farkli port

Proje Yapisi

myproject/
├── manage.py               # Yonetim komutu (CLI entry point)
├── myproject/
│   ├── __init__.py
│   ├── settings.py          # Proje ayarlari (DB, apps, middleware...)
│   ├── urls.py              # Ana URL yapilandirmasi
│   ├── asgi.py              # ASGI entry point
│   └── wsgi.py              # WSGI entry point
└── blog/                    # Uygulama (app)
    ├── __init__.py
    ├── admin.py             # Admin panel kayitlari
    ├── apps.py              # App yapilandirmasi
    ├── models.py            # Veritabani modelleri
    ├── views.py             # Gorunumler (views)
    ├── urls.py              # App URL'leri (kendin olustur)
    ├── serializers.py       # DRF serializer (kendin olustur)
    ├── templates/           # HTML sablonlari
    │   └── blog/
    │       └── post_list.html
    ├── static/              # Statik dosyalar (CSS, JS, img)
    ├── migrations/          # Veritabani migration dosyalari
    └── tests.py             # Testler
python
# settings.py - App ekleme
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    # 3rd party
    "rest_framework",
    # Local apps
    "blog",
]

Models & Migrations (ORM)

python
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.utils import timezone

class Category(models.Model):
    name = models.CharField(max_length=100, unique=True)
    slug = models.SlugField(unique=True)

    class Meta:
        verbose_name_plural = "categories"
        ordering = ["name"]

    def __str__(self):
        return self.name

class Post(models.Model):
    STATUS_CHOICES = [
        ("draft", "Taslak"),
        ("published", "Yayinda"),
    ]

    title = models.CharField(max_length=200)
    slug = models.SlugField(unique=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name="posts")
    category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True, blank=True)
    body = models.TextField()
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default="draft")
    created_at = models.DateTimeField(auto_now_add=True)
    updated_at = models.DateTimeField(auto_now=True)
    published_at = models.DateTimeField(null=True, blank=True)

    class Meta:
        ordering = ["-created_at"]

    def __str__(self):
        return self.title

    def publish(self):
        self.status = "published"
        self.published_at = timezone.now()
        self.save()
bash
# Migration olustur ve uygula
python manage.py makemigrations
python manage.py migrate

# ORM Shell
python manage.py shell
python
# ORM Sorgulari (Shell icerisinde)
from blog.models import Post, Category

# Olusturma
cat = Category.objects.create(name="Python", slug="python")
post = Post.objects.create(
    title="Django Rehberi",
    slug="django-rehberi",
    author=user,
    category=cat,
    body="Icerik burada...",
    status="published"
)

# Sorgulama
Post.objects.all()                              # Tum kayitlar
Post.objects.filter(status="published")         # Filtreleme
Post.objects.filter(title__icontains="django")  # Buyuk/kucuk harf duyarsiz
Post.objects.exclude(status="draft")            # Haric tutma
Post.objects.get(slug="django-rehberi")         # Tek kayit
Post.objects.filter(author__username="fahri")   # Iliski uzerinden filtre
Post.objects.order_by("-created_at")[:5]        # Son 5 yazi
Post.objects.count()                            # Sayi

# Guncelleme & Silme
post.title = "Yeni Baslik"
post.save()
Post.objects.filter(status="draft").update(status="published")
post.delete()

Views (Function-Based & Class-Based)

python
# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse
from django.views.generic import ListView, DetailView, CreateView
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from .models import Post

# --- Function-Based Views (FBV) ---
def post_list(request):
    posts = Post.objects.filter(status="published")
    return render(request, "blog/post_list.html", {"posts": posts})

def post_detail(request, slug):
    post = get_object_or_404(Post, slug=slug, status="published")
    return render(request, "blog/post_detail.html", {"post": post})

@login_required
def post_create(request):
    if request.method == "POST":
        title = request.POST["title"]
        body = request.POST["body"]
        post = Post.objects.create(
            title=title, body=body, author=request.user, slug=title.lower().replace(" ", "-")
        )
        return redirect("post_detail", slug=post.slug)
    return render(request, "blog/post_form.html")

def api_posts(request):
    """Basit JSON API"""
    posts = Post.objects.filter(status="published").values("id", "title", "slug", "created_at")
    return JsonResponse(list(posts), safe=False)

# --- Class-Based Views (CBV) ---
class PostListView(ListView):
    model = Post
    template_name = "blog/post_list.html"
    context_object_name = "posts"
    queryset = Post.objects.filter(status="published")
    paginate_by = 10

class PostDetailView(DetailView):
    model = Post
    template_name = "blog/post_detail.html"

class PostCreateView(LoginRequiredMixin, CreateView):
    model = Post
    fields = ["title", "body", "category", "status"]
    template_name = "blog/post_form.html"

    def form_valid(self, form):
        form.instance.author = self.request.user
        return super().form_valid(form)

URL Routing

python
# blog/urls.py
from django.urls import path
from . import views

app_name = "blog"  # namespace

urlpatterns = [
    path("", views.PostListView.as_view(), name="post_list"),
    path("post/<slug:slug>/", views.post_detail, name="post_detail"),
    path("post/new/", views.post_create, name="post_create"),
    path("api/posts/", views.api_posts, name="api_posts"),
]

# myproject/urls.py (ana URL dosyasi)
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("", include("blog.urls", namespace="blog")),
]

Templates (DTL Basics)

html
<!-- templates/blog/base.html -->
<!DOCTYPE html>
<html>
<head>
    <title>{% block title %}Blog{% endblock %}</title>
    {% load static %}
    <link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>
<body>
    <nav>
        <a href="{% url 'blog:post_list' %}">Ana Sayfa</a>
        {% if user.is_authenticated %}
            <span>{{ user.username }}</span>
            <a href="{% url 'logout' %}">Cikis</a>
        {% else %}
            <a href="{% url 'login' %}">Giris</a>
        {% endif %}
    </nav>

    <main>{% block content %}{% endblock %}</main>
</body>
</html>

<!-- templates/blog/post_list.html -->
{% extends "blog/base.html" %}
{% block title %}Yazilar{% endblock %}

{% block content %}
<h1>Blog Yazilari</h1>
{% for post in posts %}
    <article>
        <h2><a href="{% url 'blog:post_detail' slug=post.slug %}">{{ post.title }}</a></h2>
        <p>{{ post.body|truncatewords:30 }}</p>
        <small>{{ post.author.username }} - {{ post.created_at|date:"d M Y" }}</small>
    </article>
{% empty %}
    <p>Henuz yazi yok.</p>
{% endfor %}

<!-- Sayfalama (Pagination) -->
{% if is_paginated %}
<nav>
    {% if page_obj.has_previous %}
        <a href="?page={{ page_obj.previous_page_number }}">Onceki</a>
    {% endif %}
    <span>Sayfa {{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span>
    {% if page_obj.has_next %}
        <a href="?page={{ page_obj.next_page_number }}">Sonraki</a>
    {% endif %}
</nav>
{% endif %}
{% endblock %}

Django REST Framework (DRF)

bash
pip install djangorestframework
# settings.py'de INSTALLED_APPS'e "rest_framework" ekle
python
# blog/serializers.py
from rest_framework import serializers
from .models import Post, Category

class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ["id", "name", "slug"]

class PostSerializer(serializers.ModelSerializer):
    author = serializers.StringRelatedField(read_only=True)
    category = CategorySerializer(read_only=True)

    class Meta:
        model = Post
        fields = ["id", "title", "slug", "author", "category", "body", "status", "created_at"]
        read_only_fields = ["author", "created_at"]

# blog/views.py (DRF ViewSets)
from rest_framework import viewsets, permissions, filters
from rest_framework.decorators import action
from rest_framework.response import Response
from .serializers import PostSerializer, CategorySerializer

class PostViewSet(viewsets.ModelViewSet):
    queryset = Post.objects.filter(status="published")
    serializer_class = PostSerializer
    permission_classes = [permissions.IsAuthenticatedOrReadOnly]
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ["title", "body"]
    ordering_fields = ["created_at", "title"]
    lookup_field = "slug"

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    @action(detail=False, methods=["get"])
    def recent(self, request):
        recent_posts = self.queryset.order_by("-created_at")[:5]
        serializer = self.get_serializer(recent_posts, many=True)
        return Response(serializer.data)

class CategoryViewSet(viewsets.ReadOnlyModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer
    lookup_field = "slug"

# blog/urls.py (DRF Router)
from rest_framework.routers import DefaultRouter

router = DefaultRouter()
router.register(r"posts", PostViewSet)
router.register(r"categories", CategoryViewSet)

# urlpatterns'e ekle:
# path("api/", include(router.urls)),

Admin Panel

python
# blog/admin.py
from django.contrib import admin
from .models import Post, Category

@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = ["name", "slug"]
    prepopulated_fields = {"slug": ("name",)}

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ["title", "author", "category", "status", "created_at"]
    list_filter = ["status", "category", "created_at"]
    search_fields = ["title", "body"]
    prepopulated_fields = {"slug": ("title",)}
    raw_id_fields = ["author"]
    date_hierarchy = "created_at"
    ordering = ["-created_at"]
    list_editable = ["status"]
bash
# Superuser olustur (Admin panele giris icin)
python manage.py createsuperuser
# Admin panel: http://127.0.0.1:8000/admin/

Authentication

python
# settings.py
LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"
LOGIN_URL = "/accounts/login/"

# myproject/urls.py
from django.contrib.auth import views as auth_views

urlpatterns = [
    # ...
    path("accounts/login/", auth_views.LoginView.as_view(), name="login"),
    path("accounts/logout/", auth_views.LogoutView.as_view(), name="logout"),
    path("accounts/password_change/", auth_views.PasswordChangeView.as_view(), name="password_change"),
]
python
# DRF Authentication (settings.py)
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework.authentication.TokenAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticatedOrReadOnly",
    ],
    "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.PageNumberPagination",
    "PAGE_SIZE": 20,
}

Temel Komutlar

KomutAçıklama
django-admin startproject myproject .Yeni proje oluştur
python manage.py startapp blogYeni app oluştur
python manage.py runserverGeliştirme sunucusu
python manage.py makemigrationsMigration dosyasi oluştur
python manage.py migrateMigration'lari uygula
python manage.py createsuperuserAdmin kullanicisi oluştur
python manage.py shellDjango shell (ORM erisimi)
python manage.py collectstaticStatik dosyaları topla
python manage.py testTestleri çalıştır
python manage.py showmigrationsMigration durumunu göster
python manage.py dbshellVeritabani shell'ine baglan
python manage.py dumpdata blog > data.jsonVeriyi JSON'a aktar
python manage.py loaddata data.jsonJSON'dan veri yükle
python manage.py check --deployDeployment kontrolu

14) FastAPI -- Modern Async Web Framework

FastAPI, yüksek performanslı, otomatik dokümantasyonlu modern Python web framework'üdür. (FastAPI is a modern, high-performance Python web framework with automatic documentation.)

bash
pip install fastapi uvicorn[standard]

Temel Uygulama

python
from fastapi import FastAPI, HTTPException, Query, Path, Depends, status
from fastapi.responses import JSONResponse

app = FastAPI(
    title="Benim API'm",
    description="FastAPI ile oluşturulmuş REST API",
    version="1.0.0",
)

@app.get("/")
async def root():
    return {"message": "Merhaba, FastAPI!"}

@app.get("/items/{item_id}")
async def get_item(
    item_id: int = Path(..., title="Item ID", ge=1),
    q: str | None = Query(None, max_length=50),
):
    return {"item_id": item_id, "q": q}
bash
# Geliştirme sunucusu
uvicorn main:app --reload --port 8000
# Otomatik dokümantasyon: http://localhost:8000/docs (Swagger UI)
# Alternatif dokümantasyon: http://localhost:8000/redoc (ReDoc)

Pydantic Models

python
from pydantic import BaseModel, Field, EmailStr, field_validator
from datetime import datetime

class UserCreate(BaseModel):
    name: str = Field(..., min_length=2, max_length=100)
    email: EmailStr
    age: int = Field(..., ge=0, le=150)
    tags: list[str] = Field(default_factory=list)

    @field_validator("name")
    @classmethod
    def name_must_not_be_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("İsim boş olamaz")
        return v.strip()

class UserResponse(BaseModel):
    id: int
    name: str
    email: str
    age: int
    tags: list[str]
    created_at: datetime

    model_config = {"from_attributes": True}  # ORM mode

class UserUpdate(BaseModel):
    name: str | None = None
    email: EmailStr | None = None
    age: int | None = Field(None, ge=0, le=150)

# Request/Response ile kullanım
@app.post("/users", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
async def create_user(user: UserCreate):
    # user.name, user.email vs. otomatik validate edilir
    db_user = save_to_db(user)
    return db_user

@app.patch("/users/{user_id}", response_model=UserResponse)
async def update_user(user_id: int, user: UserUpdate):
    update_data = user.model_dump(exclude_unset=True)
    db_user = partial_update(user_id, update_data)
    return db_user

Dependency Injection

python
from fastapi import Depends, Header, HTTPException
from typing import Annotated

# Basit dependency
async def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

# Authentication dependency
async def get_current_user(
    authorization: str = Header(...),
):
    token = authorization.replace("Bearer ", "")
    user = verify_token(token)
    if not user:
        raise HTTPException(status_code=401, detail="Geçersiz token")
    return user

# Kullanım
@app.get("/profile")
async def profile(
    current_user: Annotated[User, Depends(get_current_user)],
    db: Annotated[Session, Depends(get_db)],
):
    return {"user": current_user.name}

# Sınıf bazlı dependency
class Pagination:
    def __init__(
        self,
        page: int = Query(1, ge=1),
        per_page: int = Query(20, ge=1, le=100),
    ):
        self.skip = (page - 1) * per_page
        self.limit = per_page

@app.get("/items")
async def list_items(pagination: Pagination = Depends()):
    items = db.query(Item).offset(pagination.skip).limit(pagination.limit).all()
    return items

Middleware

python
from fastapi import Request
from fastapi.middleware.cors import CORSMiddleware
import time

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Custom middleware -- istek süresi loglama
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.perf_counter()
    response = await call_next(request)
    process_time = time.perf_counter() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response

Background Tasks

python
from fastapi import BackgroundTasks

def send_email(email: str, message: str):
    """Arka planda çalışacak fonksiyon"""
    import smtplib
    # E-posta gönderme işlemi...
    print(f"E-posta gönderildi: {email}")

def write_log(message: str):
    with open("log.txt", "a") as f:
        f.write(f"{message}\n")

@app.post("/register")
async def register(
    user: UserCreate,
    background_tasks: BackgroundTasks,
):
    db_user = save_to_db(user)
    # Arka plan görevleri -- response'u bekletmez
    background_tasks.add_task(send_email, user.email, "Hoşgeldiniz!")
    background_tasks.add_task(write_log, f"Yeni kullanıcı: {user.name}")
    return {"id": db_user.id, "message": "Kayıt başarılı"}

WebSocket

python
from fastapi import WebSocket, WebSocketDisconnect

class ConnectionManager:
    def __init__(self):
        self.active_connections: list[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

@app.websocket("/ws/{client_id}")
async def websocket_endpoint(websocket: WebSocket, client_id: str):
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.broadcast(f"{client_id}: {data}")
    except WebSocketDisconnect:
        manager.disconnect(websocket)
        await manager.broadcast(f"{client_id} ayrıldı")

FastAPI + SQLAlchemy

python
# database.py
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, DeclarativeBase

DATABASE_URL = "postgresql://user:pass@localhost/mydb"

engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

class Base(DeclarativeBase):
    pass

# models.py
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, func
from sqlalchemy.orm import relationship
from database import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String(100), nullable=False)
    email = Column(String(255), unique=True, index=True)
    created_at = Column(DateTime, server_default=func.now())

    posts = relationship("Post", back_populates="author")

class Post(Base):
    __tablename__ = "posts"

    id = Column(Integer, primary_key=True, index=True)
    title = Column(String(200), nullable=False)
    body = Column(String, nullable=False)
    author_id = Column(Integer, ForeignKey("users.id"))

    author = relationship("User", back_populates="posts")

# schemas.py (Pydantic)
from pydantic import BaseModel

class PostOut(BaseModel):
    id: int
    title: str
    body: str
    author_id: int
    model_config = {"from_attributes": True}

# main.py
from fastapi import Depends
from sqlalchemy.orm import Session
from database import SessionLocal, engine, Base

Base.metadata.create_all(bind=engine)

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/posts", response_model=list[PostOut])
def list_posts(skip: int = 0, limit: int = 20, db: Session = Depends(get_db)):
    return db.query(Post).offset(skip).limit(limit).all()

@app.post("/posts", response_model=PostOut, status_code=201)
def create_post(title: str, body: str, author_id: int, db: Session = Depends(get_db)):
    post = Post(title=title, body=body, author_id=author_id)
    db.add(post)
    db.commit()
    db.refresh(post)
    return post

Proje yapısı (Project structure):

fastapi-project/
├── main.py              # FastAPI app ve router'lar
├── database.py          # SQLAlchemy engine & session
├── models.py            # SQLAlchemy ORM modelleri
├── schemas.py           # Pydantic şemaları
├── routers/
│   ├── __init__.py
│   ├── users.py
│   └── posts.py
├── dependencies.py      # Paylaşılan dependency'ler
├── middleware.py         # Custom middleware
├── alembic/             # Veritabanı migration'ları
├── tests/
│   ├── test_users.py
│   └── test_posts.py
├── alembic.ini
└── requirements.txt

15) Selenium -- Web Otomasyon

Selenium, web tarayıcısını otomatik kontrol etmek için kullanılır -- test, scraping, otomasyon. (Selenium automates web browsers -- testing, scraping, automation.)

bash
pip install selenium webdriver-manager
python
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager

# --- Tarayıcı başlatma (Launch browser) ---
options = webdriver.ChromeOptions()
options.add_argument("--headless")            # Görünmez mod (headless)
options.add_argument("--no-sandbox")
options.add_argument("--window-size=1920,1080")

driver = webdriver.Chrome(
    service=Service(ChromeDriverManager().install()),
    options=options
)

# --- Sayfa açma ---
driver.get("https://www.google.com")
print(driver.title)

# --- Element bulma (Finding elements) ---
search_box = driver.find_element(By.NAME, "q")
search_box.send_keys("Python Selenium")
search_box.send_keys(Keys.RETURN)

# --- Bekleme (Explicit wait) ---
results = WebDriverWait(driver, 10).until(
    EC.presence_of_all_elements_located((By.CSS_SELECTOR, "h3"))
)
for r in results[:5]:
    print(r.text)

# --- Tıklama & form doldurma ---
button = driver.find_element(By.ID, "submit-btn")
button.click()

input_field = driver.find_element(By.CSS_SELECTOR, "input[name='email']")
input_field.clear()
input_field.send_keys("info@fahriaydin.dev")

# --- Screenshot ---
driver.save_screenshot("screenshot.png")

# --- Sayfa kaynağı ---
html = driver.page_source

# --- Kapatma ---
driver.quit()

Sık kullanılan By seçicileri:

SeçiciKullanım
By.IDfind_element(By.ID, "myId")
By.NAMEfind_element(By.NAME, "email")
By.CLASS_NAMEfind_element(By.CLASS_NAME, "btn")
By.CSS_SELECTORfind_element(By.CSS_SELECTOR, "div.card > h2")
By.XPATHfind_element(By.XPATH, "//div[@class='item']")
By.TAG_NAMEfind_element(By.TAG_NAME, "a")
By.LINK_TEXTfind_element(By.LINK_TEXT, "Click here")

16) PyQt5 -- Masaüstü GUI

PyQt5, Qt framework ile masaüstü uygulamaları geliştirmek için kullanılır. (PyQt5 is used to build cross-platform desktop applications with Qt.)

bash
pip install PyQt5
python
import sys
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QLabel, QLineEdit, QTextEdit, QMessageBox,
    QFileDialog, QComboBox, QCheckBox, QTableWidget, QTableWidgetItem
)
from PyQt5.QtCore import Qt

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Benim Uygulamam")
        self.setGeometry(100, 100, 600, 400)

        # Ana widget & layout
        central = QWidget()
        self.setCentralWidget(central)
        layout = QVBoxLayout(central)

        # Label
        self.label = QLabel("Merhaba, PyQt5!")
        self.label.setAlignment(Qt.AlignCenter)
        layout.addWidget(self.label)

        # Input
        self.input = QLineEdit()
        self.input.setPlaceholderText("Adınızı girin...")
        layout.addWidget(self.input)

        # Buton satırı
        btn_row = QHBoxLayout()
        self.btn_greet = QPushButton("Selamla")
        self.btn_greet.clicked.connect(self.on_greet)
        btn_row.addWidget(self.btn_greet)

        self.btn_clear = QPushButton("Temizle")
        self.btn_clear.clicked.connect(lambda: self.input.clear())
        btn_row.addWidget(self.btn_clear)
        layout.addLayout(btn_row)

        # TextEdit (çok satırlı çıktı)
        self.output = QTextEdit()
        self.output.setReadOnly(True)
        layout.addWidget(self.output)

    def on_greet(self):
        name = self.input.text().strip()
        if name:
            self.output.append(f"Merhaba, {name}!")
        else:
            QMessageBox.warning(self, "Uyarı", "Lütfen adınızı girin.")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

Sık kullanılan widget'lar:

WidgetAçıklama
QLabelMetin/resim gösterme
QPushButtonButon
QLineEditTek satır input
QTextEditÇok satır metin alanı
QComboBoxDropdown/seçim kutusu
QCheckBoxOnay kutusu
QRadioButtonRadio button
QSliderKaydırıcı
QProgressBarİlerleme çubuğu
QTableWidgetTablo
QFileDialogDosya seçme dialogu
QMessageBoxUyarı/bilgi dialogu

Layout'lar: QVBoxLayout (dikey), QHBoxLayout (yatay), QGridLayout (ızgara), QFormLayout (form).


17) Kivy -- Cross-Platform GUI

Kivy, mobil ve masaüstü uygulamalar için dokunmatik destekli Python GUI framework'üdür. (Kivy is a cross-platform Python GUI framework with touch support -- desktop & mobile.)

bash
pip install kivy
python
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput

class MyApp(App):
    def build(self):
        self.title = "Kivy Uygulamam"
        layout = BoxLayout(orientation="vertical", padding=20, spacing=10)

        self.label = Label(text="Merhaba, Kivy!", font_size=24)
        layout.add_widget(self.label)

        self.input = TextInput(hint_text="Adınızı yazın...", multiline=False, size_hint_y=None, height=40)
        layout.add_widget(self.input)

        btn = Button(text="Selamla", size_hint_y=None, height=50)
        btn.bind(on_press=self.on_greet)
        layout.add_widget(btn)

        return layout

    def on_greet(self, instance):
        name = self.input.text.strip()
        self.label.text = f"Merhaba, {name}!" if name else "Lütfen ad girin."

if __name__ == "__main__":
    MyApp().run()

KV Language (deklaratif UI):

# myapp.kv
BoxLayout:
    orientation: "vertical"
    padding: 20
    spacing: 10

    Label:
        text: "Merhaba Kivy!"
        font_size: 24

    TextInput:
        id: name_input
        hint_text: "Adınız..."
        multiline: False
        size_hint_y: None
        height: 40

    Button:
        text: "Selamla"
        size_hint_y: None
        height: 50
        on_press: root.label.text = f"Merhaba, {name_input.text}!"

Kivy vs PyQt5:

ÖzellikPyQt5Kivy
Mobil destek-Var (Buildozer ile APK)
DokunmatikSınırlıTam destek
OlgunlukÇok olgunAktif geliştirme
GörünümNative OSKendi UI sistemi
LisansGPL/TicariMIT

18) Folium -- Harita Görselleştirme

Folium, Leaflet.js tabanlı interaktif haritalar oluşturur. (Folium creates interactive maps powered by Leaflet.js.)

bash
pip install folium
python
import folium

# --- Temel harita ---
m = folium.Map(location=[41.0082, 28.9784], zoom_start=12)  # İstanbul

# --- Marker ekleme ---
folium.Marker(
    location=[41.0082, 28.9784],
    popup="<b>İstanbul</b><br>Türkiye'nin en büyük şehri",
    tooltip="Tıkla!",
    icon=folium.Icon(color="red", icon="info-sign")
).add_to(m)

# --- Birden fazla marker ---
locations = [
    {"name": "Galata Kulesi", "lat": 41.0256, "lon": 28.9744},
    {"name": "Ayasofya", "lat": 41.0086, "lon": 28.9802},
    {"name": "Topkapı Sarayı", "lat": 41.0115, "lon": 28.9833},
]
for loc in locations:
    folium.Marker(
        [loc["lat"], loc["lon"]],
        popup=loc["name"],
        tooltip=loc["name"]
    ).add_to(m)

# --- Daire (Circle) ---
folium.Circle(
    location=[41.0082, 28.9784],
    radius=1000,  # metre
    color="blue",
    fill=True,
    fill_opacity=0.3,
    popup="1km çevresi"
).add_to(m)

# --- GeoJSON katmanı ---
folium.GeoJson("districts.geojson", name="İlçeler").add_to(m)

# --- Isı haritası (Heatmap) ---
from folium.plugins import HeatMap
heat_data = [[41.01, 28.97, 0.8], [41.02, 28.98, 0.5], [41.00, 28.96, 0.9]]
HeatMap(heat_data).add_to(m)

# --- Katman kontrolü ---
folium.LayerControl().add_to(m)

# --- Kaydet ---
m.save("istanbul_map.html")

19) Pygame -- Oyun Geliştirme

Pygame, 2D oyunlar ve multimedya uygulamaları için kullanılır. (Pygame is used for 2D games and multimedia applications.)

bash
pip install pygame
python
import pygame
import sys

# --- Başlatma (Initialize) ---
pygame.init()
WIDTH, HEIGHT = 800, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("İlk Oyunum")
clock = pygame.time.Clock()
FPS = 60

# --- Renkler ---
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)

# --- Oyuncu (Player) ---
player = pygame.Rect(WIDTH // 2 - 25, HEIGHT // 2 - 25, 50, 50)
player_speed = 5
player_color = GREEN

# --- Font ---
font = pygame.font.SysFont("Arial", 24)
score = 0

# --- Oyun döngüsü (Game loop) ---
running = True
while running:
    # Olaylar (Events)
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_ESCAPE:
                running = False

    # Girdi (Input)
    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT] or keys[pygame.K_a]:
        player.x -= player_speed
    if keys[pygame.K_RIGHT] or keys[pygame.K_d]:
        player.x += player_speed
    if keys[pygame.K_UP] or keys[pygame.K_w]:
        player.y -= player_speed
    if keys[pygame.K_DOWN] or keys[pygame.K_s]:
        player.y += player_speed

    # Sınır kontrolü (Boundary check)
    player.clamp_ip(screen.get_rect())

    # Çizim (Drawing)
    screen.fill(BLACK)
    pygame.draw.rect(screen, player_color, player)

    # Skor göster
    score_text = font.render(f"Skor: {score}", True, WHITE)
    screen.blit(score_text, (10, 10))

    # Ekranı güncelle
    pygame.display.flip()
    clock.tick(FPS)

pygame.quit()
sys.exit()

Sık kullanılan Pygame fonksiyonları:

FonksiyonAçıklama
pygame.display.set_mode()Pencere oluştur
pygame.draw.rect()Dikdörtgen çiz
pygame.draw.circle()Daire çiz
pygame.draw.line()Çizgi çiz
pygame.image.load()Resim yükle
pygame.mixer.Sound()Ses efekti yükle
pygame.mixer.music.load()Müzik yükle
pygame.font.SysFont()Sistem fontu
pygame.key.get_pressed()Basılı tuşlar
pygame.mouse.get_pos()Fare konumu
pygame.sprite.SpriteSprite sınıfı
pygame.sprite.GroupSprite grubu

Sprite tabanlı yapı:

python
class Player(pygame.sprite.Sprite):
    def __init__(self, x, y):
        super().__init__()
        self.image = pygame.Surface((50, 50))
        self.image.fill(GREEN)
        self.rect = self.image.get_rect(center=(x, y))
        self.speed = 5

    def update(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_LEFT]:  self.rect.x -= self.speed
        if keys[pygame.K_RIGHT]: self.rect.x += self.speed
        if keys[pygame.K_UP]:    self.rect.y -= self.speed
        if keys[pygame.K_DOWN]:  self.rect.y += self.speed

all_sprites = pygame.sprite.Group()
player = Player(400, 300)
all_sprites.add(player)

# Game loop içinde:
all_sprites.update()
all_sprites.draw(screen)

20) Concurrency: threads, asyncio

python
# --- Threading (I/O-bound işler) ---
from concurrent.futures import ThreadPoolExecutor
import requests

urls = ["https://httpbin.org/delay/1"] * 5

def fetch(url):
    return requests.get(url).status_code

with ThreadPoolExecutor(max_workers=5) as pool:
    results = list(pool.map(fetch, urls))
print(results)

# --- Multiprocessing (CPU-bound işler) ---
from concurrent.futures import ProcessPoolExecutor

def heavy(n):
    return sum(i * i for i in range(n))

with ProcessPoolExecutor() as pool:
    results = list(pool.map(heavy, [10**6] * 4))

# --- AsyncIO ---
import asyncio
import aiohttp

async def fetch_async(session, url):
    async with session.get(url) as resp:
        return await resp.text()

async def main():
    async with aiohttp.ClientSession() as session:
        tasks = [fetch_async(session, f"https://httpbin.org/get?i={i}") for i in range(5)]
        results = await asyncio.gather(*tasks)
        print(f"{len(results)} sonuç alındı")

asyncio.run(main())

21) asyncio Derinlemesine

asyncio, Python'un yerleşik asenkron I/O kütüphanesidir. I/O-bound işlemlerde yüksek performans sağlar.

async/await Temelleri

python
import asyncio

# Temel coroutine
async def say_hello(name: str, delay: float):
    await asyncio.sleep(delay)
    print(f"Merhaba, {name}!")
    return f"done-{name}"

# Birden fazla coroutine'i eşzamanlı çalıştırma
async def main():
    # gather -- tümünü paralel çalıştır, sonuçları topla
    results = await asyncio.gather(
        say_hello("Fahri", 1),
        say_hello("Ali", 0.5),
        say_hello("Ayşe", 0.7),
    )
    print(results)  # ['done-Fahri', 'done-Ali', 'done-Ayşe']

asyncio.run(main())
python
# Task oluşturma ve iptal etme
async def long_running():
    try:
        while True:
            print("Çalışıyor...")
            await asyncio.sleep(1)
    except asyncio.CancelledError:
        print("Görev iptal edildi!")
        raise  # CancelledError'u tekrar fırlat

async def main():
    task = asyncio.create_task(long_running())
    await asyncio.sleep(3)
    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("Görev başarıyla iptal edildi")

asyncio.run(main())
python
# Timeout ile çalışma
async def slow_operation():
    await asyncio.sleep(10)
    return "bitti"

async def main():
    try:
        result = await asyncio.wait_for(slow_operation(), timeout=3.0)
    except asyncio.TimeoutError:
        print("Zaman aşımı! 3 saniyede tamamlanamadı.")

    # TaskGroup (Python 3.11+) -- yapısal eşzamanlılık
    async with asyncio.TaskGroup() as tg:
        task1 = tg.create_task(say_hello("A", 1))
        task2 = tg.create_task(say_hello("B", 2))
    # Tüm görevler tamamlandığında buraya gelir
    print(task1.result(), task2.result())

asyncio.run(main())

asyncio Semaphore & Queue

python
import asyncio

# Eşzamanlı bağlantı sayısını sınırla
semaphore = asyncio.Semaphore(5)

async def rate_limited_fetch(url: str):
    async with semaphore:
        # Aynı anda en fazla 5 istek
        await asyncio.sleep(0.5)
        return f"Fetched: {url}"

# asyncio Queue -- üretici/tüketici deseni
async def producer(queue: asyncio.Queue):
    for i in range(10):
        await queue.put(f"item-{i}")
        await asyncio.sleep(0.1)
    await queue.put(None)  # Durdurma sinyali

async def consumer(queue: asyncio.Queue, name: str):
    while True:
        item = await queue.get()
        if item is None:
            queue.task_done()
            break
        print(f"[{name}] İşleniyor: {item}")
        await asyncio.sleep(0.3)
        queue.task_done()

async def main():
    queue = asyncio.Queue(maxsize=5)
    await asyncio.gather(
        producer(queue),
        consumer(queue, "Worker-1"),
        consumer(queue, "Worker-2"),
    )

asyncio.run(main())

aiohttp -- Asenkron HTTP İstemci/Sunucu

python
import aiohttp
import asyncio

# --- Asenkron HTTP istemci ---
async def fetch_many():
    async with aiohttp.ClientSession() as session:
        urls = [f"https://httpbin.org/get?id={i}" for i in range(10)]

        async def fetch_one(url):
            async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
                return await resp.json()

        results = await asyncio.gather(*[fetch_one(u) for u in urls])
        print(f"{len(results)} istek tamamlandı")

asyncio.run(fetch_many())

# --- POST isteği ---
async def post_data():
    async with aiohttp.ClientSession() as session:
        payload = {"name": "Fahri", "role": "developer"}
        async with session.post("https://httpbin.org/post", json=payload) as resp:
            data = await resp.json()
            print(data["json"])  # Gönderilen veri

asyncio.run(post_data())

asyncpg -- Asenkron PostgreSQL

python
import asyncpg
import asyncio

async def main():
    # Bağlantı havuzu (connection pool)
    pool = await asyncpg.create_pool(
        "postgresql://user:pass@localhost/mydb",
        min_size=5,
        max_size=20,
    )

    # Tablo oluştur
    async with pool.acquire() as conn:
        await conn.execute("""
            CREATE TABLE IF NOT EXISTS users (
                id SERIAL PRIMARY KEY,
                name TEXT NOT NULL,
                email TEXT UNIQUE
            )
        """)

    # Kayıt ekle
    async with pool.acquire() as conn:
        await conn.execute(
            "INSERT INTO users(name, email) VALUES($1, $2)",
            "Fahri", "info@fahriaydin.dev"
        )

    # Sorgu çalıştır
    async with pool.acquire() as conn:
        rows = await conn.fetch("SELECT * FROM users WHERE name = $1", "Fahri")
        for row in rows:
            print(dict(row))  # {'id': 1, 'name': 'Fahri', 'email': '...'}

    # Transaction
    async with pool.acquire() as conn:
        async with conn.transaction():
            await conn.execute("UPDATE users SET name=$1 WHERE id=$2", "Fahri A.", 1)
            # Hata olursa otomatik rollback

    await pool.close()

asyncio.run(main())

22) Type Hints Advanced

İleri seviye tip ipuçları, büyük projelerde kodun okunabilirliğini ve güvenliğini artırır.

Protocol -- Yapısal Alt Tipleme (Structural Subtyping)

python
from typing import Protocol, runtime_checkable

# Protocol -- interface gibi davranır, kalıtım gerektirmez
class Drawable(Protocol):
    def draw(self) -> str: ...

class Circle:
    def draw(self) -> str:
        return "Daire çizildi"

class Square:
    def draw(self) -> str:
        return "Kare çizildi"

def render(shape: Drawable) -> None:
    # Circle ve Square, Drawable'ı implement etmiş sayılır
    # çünkü draw() metodu var (duck typing + tip güvenliği)
    print(shape.draw())

render(Circle())  # Çalışır
render(Square())  # Çalışır

# runtime_checkable -- isinstance kontrolü yapılabilir
@runtime_checkable
class Sendable(Protocol):
    def send(self, data: bytes) -> None: ...

class EmailClient:
    def send(self, data: bytes) -> None:
        print("E-posta gönderildi")

client = EmailClient()
print(isinstance(client, Sendable))  # True

TypedDict -- Tip Güvenli Dict

python
from typing import TypedDict, Required, NotRequired

class UserDict(TypedDict):
    name: str
    age: int
    email: str

class PartialUser(TypedDict, total=False):
    name: str
    age: int
    email: str

# Python 3.11+ -- Required / NotRequired
class Config(TypedDict):
    host: Required[str]
    port: Required[int]
    debug: NotRequired[bool]
    log_level: NotRequired[str]

def connect(config: Config) -> None:
    host = config["host"]
    port = config["port"]
    debug = config.get("debug", False)
    print(f"Bağlanılıyor: {host}:{port} (debug={debug})")

connect({"host": "localhost", "port": 8080})

Generic -- Tip Parametreleri

python
from typing import Generic, TypeVar

T = TypeVar("T")
K = TypeVar("K")
V = TypeVar("V")

# Generic sınıf
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        if not self._items:
            raise IndexError("Boş stack")
        return self._items.pop()

    def peek(self) -> T:
        return self._items[-1]

    def __len__(self) -> int:
        return len(self._items)

stack: Stack[int] = Stack()
stack.push(42)
stack.push(99)
value: int = stack.pop()  # Tip güvenli

# Generic fonksiyon
def merge_dicts(d1: dict[K, V], d2: dict[K, V]) -> dict[K, V]:
    return {**d1, **d2}

# Python 3.12+ söz dizimi (daha kısa)
def first[T](items: list[T]) -> T | None:
    return items[0] if items else None

class Pair[A, B]:
    def __init__(self, first: A, second: B):
        self.first = A
        self.second = B

# Bound TypeVar -- tip kısıtlaması
from typing import TypeVar
from numbers import Number

N = TypeVar("N", bound=Number)

def double(value: N) -> N:
    return value * 2  # type: ignore

Diğer Faydalı Tipler

python
from typing import (
    Annotated, Final, ClassVar, Self, TypeAlias,
    overload, get_type_hints,
)

# TypeAlias -- karmaşık tipler için takma ad
JSON: TypeAlias = dict[str, "JSON"] | list["JSON"] | str | int | float | bool | None

# Final -- değiştirilemez değer
MAX_RETRIES: Final = 3

# ClassVar -- sınıf değişkeni (instance değil)
class AppConfig:
    VERSION: ClassVar[str] = "1.0.0"
    name: str

# Self (Python 3.11+) -- zincirleme metotlar için
class Builder:
    def __init__(self) -> None:
        self._name = ""
        self._port = 8080

    def name(self, name: str) -> "Self":
        self._name = name
        return self

    def port(self, port: int) -> "Self":
        self._port = port
        return self

# overload -- farklı parametre kombinasyonları
@overload
def parse(data: str) -> dict: ...
@overload
def parse(data: bytes) -> dict: ...
@overload
def parse(data: str, raw: bool) -> str: ...

def parse(data, raw=False):
    if isinstance(data, bytes):
        data = data.decode()
    if raw:
        return data
    return json.loads(data)

# Annotated -- metadata ile tip
UserId = Annotated[int, "Kullanıcı kimlik numarası, pozitif tam sayı"]

23) Decorators Advanced

Decorator'lar, fonksiyonları veya sınıfları değiştirmeden davranışlarını genişletmenin güçlü yoludur.

functools.wraps -- Metadata Koruma

python
import functools
import time

# wraps olmadan decorator orijinal fonksiyonun adını ve docstring'ini kaybeder
def timer(func):
    @functools.wraps(func)  # Orijinal fonksiyonun metadata'sını korur
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)
        elapsed = time.perf_counter() - start
        print(f"{func.__name__} {elapsed:.4f}s sürdü")
        return result
    return wrapper

@timer
def compute(n: int) -> int:
    """N'e kadar olan sayıların toplamı."""
    return sum(range(n))

print(compute.__name__)  # "compute" (wraps sayesinde, yoksa "wrapper" olurdu)
print(compute.__doc__)   # "N'e kadar olan sayıların toplamı."

Parametreli Decorator

python
import functools

# Parametre alan decorator -- üç katman gerekir
def retry(max_attempts: int = 3, delay: float = 1.0):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    print(f"[{attempt}/{max_attempts}] {func.__name__} başarısız: {e}")
                    if attempt < max_attempts:
                        import time
                        time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator

@retry(max_attempts=3, delay=0.5)
def unstable_api_call():
    import random
    if random.random() < 0.7:
        raise ConnectionError("Bağlantı hatası")
    return {"status": "ok"}

# Erişim kontrolü decorator
def require_role(*roles: str):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(user, *args, **kwargs):
            if user.get("role") not in roles:
                raise PermissionError(f"{user.get('role')} rolü yetkisiz")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@require_role("admin", "moderator")
def delete_post(user, post_id):
    print(f"Post {post_id} silindi")

# Cache decorator (lru_cache alternatifi)
def memoize(func):
    cache = {}
    @functools.wraps(func)
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoize
def fibonacci(n: int) -> int:
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

# Standart kütüphanedeki cache
from functools import lru_cache, cache

@lru_cache(maxsize=128)
def expensive_calc(x: int, y: int) -> int:
    return x ** y

@cache  # sınırsız cache (Python 3.9+)
def get_config(key: str) -> str:
    return load_from_file(key)

Class Decorator

python
import functools
import time

# Sınıfa uygulanabilen decorator
def singleton(cls):
    """Bir sınıftan yalnızca tek instance oluşturulmasını sağlar."""
    instances = {}
    @functools.wraps(cls)
    def get_instance(*args, **kwargs):
        if cls not in instances:
            instances[cls] = cls(*args, **kwargs)
        return instances[cls]
    return get_instance

@singleton
class Database:
    def __init__(self, url: str):
        self.url = url
        print(f"Bağlantı kuruldu: {url}")

db1 = Database("postgresql://localhost/mydb")
db2 = Database("postgresql://localhost/other")
print(db1 is db2)  # True -- aynı instance

# Otomatik __repr__ ekleyen decorator
def auto_repr(cls):
    def __repr__(self):
        attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
        return f"{cls.__name__}({attrs})"
    cls.__repr__ = __repr__
    return cls

@auto_repr
class Point:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

print(Point(3.0, 4.0))  # Point(x=3.0, y=4.0)

# Metotlara uygulanabilen sınıf bazlı decorator
class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)
        self.func = func
        self.call_count = 0

    def __call__(self, *args, **kwargs):
        self.call_count += 1
        print(f"{self.func.__name__} {self.call_count}. kez çağrıldı")
        return self.func(*args, **kwargs)

@CountCalls
def process(data):
    return len(data)

process([1, 2, 3])  # process 1. kez çağrıldı
process([4, 5])     # process 2. kez çağrıldı
print(process.call_count)  # 2

24) Context Managers

Context manager'lar, kaynakların güvenli şekilde açılıp kapatılmasını sağlar (dosya, bağlantı, kilit vb.).

with İfadesi & enter / exit

python
# Sınıf bazlı context manager
class FileManager:
    def __init__(self, filename: str, mode: str = "r"):
        self.filename = filename
        self.mode = mode
        self.file = None

    def __enter__(self):
        self.file = open(self.filename, self.mode, encoding="utf-8")
        return self.file

    def __exit__(self, exc_type, exc_val, exc_tb):
        if self.file:
            self.file.close()
        # True dönerse exception yutulur, False/None dönerse exception yayılır
        return False

with FileManager("data.txt", "w") as f:
    f.write("Merhaba!")

# Zamanlayıcı context manager
import time

class Timer:
    def __enter__(self):
        self.start = time.perf_counter()
        return self

    def __exit__(self, *args):
        self.elapsed = time.perf_counter() - self.start
        print(f"Geçen süre: {self.elapsed:.4f}s")

with Timer() as t:
    # Ölçülecek kod
    total = sum(range(1_000_000))

print(f"Toplam: {total}, Süre: {t.elapsed:.4f}s")

# Transaction context manager
class Transaction:
    def __init__(self, connection):
        self.conn = connection

    def __enter__(self):
        self.conn.begin()
        return self.conn

    def __exit__(self, exc_type, exc_val, exc_tb):
        if exc_type is not None:
            self.conn.rollback()
            print(f"Rollback yapıldı: {exc_val}")
        else:
            self.conn.commit()
        return False

contextlib -- Decorator Tabanlı Context Manager

python
from contextlib import contextmanager, suppress, redirect_stdout

# @contextmanager ile basit context manager
@contextmanager
def timer(label: str = ""):
    start = time.perf_counter()
    try:
        yield  # with bloğu burada çalışır
    finally:
        elapsed = time.perf_counter() - start
        print(f"[{label}] {elapsed:.4f}s")

with timer("hesaplama"):
    result = sum(i**2 for i in range(100_000))

# Geçici dizin context manager
@contextmanager
def temp_directory():
    import tempfile, shutil
    dirpath = tempfile.mkdtemp()
    try:
        yield dirpath
    finally:
        shutil.rmtree(dirpath)

with temp_directory() as tmpdir:
    # tmpdir ile çalış, with bloğu bitince silinir
    from pathlib import Path
    Path(tmpdir, "test.txt").write_text("geçici veri")

# Veritabanı bağlantı havuzu
@contextmanager
def get_connection(pool):
    conn = pool.acquire()
    try:
        yield conn
    except Exception:
        conn.rollback()
        raise
    else:
        conn.commit()
    finally:
        pool.release(conn)

# suppress -- belirli exception'ları yok say
with suppress(FileNotFoundError):
    os.remove("olmayan_dosya.txt")
    # FileNotFoundError olursa sessizce devam eder

# redirect_stdout -- stdout'u yönlendir
import io
f = io.StringIO()
with redirect_stdout(f):
    print("Bu stdout'a gitmez")
output = f.getvalue()  # "Bu stdout'a gitmez\n"

Async Context Manager

python
import asyncio
from contextlib import asynccontextmanager

# Sınıf bazlı async context manager
class AsyncDBPool:
    def __init__(self, dsn: str):
        self.dsn = dsn
        self.pool = None

    async def __aenter__(self):
        import asyncpg
        self.pool = await asyncpg.create_pool(self.dsn)
        return self.pool

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        if self.pool:
            await self.pool.close()

async def main():
    async with AsyncDBPool("postgresql://localhost/mydb") as pool:
        async with pool.acquire() as conn:
            rows = await conn.fetch("SELECT 1")

# @asynccontextmanager ile
@asynccontextmanager
async def http_session():
    import aiohttp
    session = aiohttp.ClientSession()
    try:
        yield session
    finally:
        await session.close()

async def main():
    async with http_session() as session:
        async with session.get("https://httpbin.org/get") as resp:
            data = await resp.json()

# FastAPI lifespan (uygulama yaşam döngüsü)
from contextlib import asynccontextmanager
from fastapi import FastAPI

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Uygulama başlarken
    print("Veritabanı havuzu oluşturuluyor...")
    app.state.db_pool = await create_pool()
    yield
    # Uygulama kapanırken
    print("Bağlantılar kapatılıyor...")
    await app.state.db_pool.close()

app = FastAPI(lifespan=lifespan)

25) Python Kütüphane Ekosistemi

Python'un güçlü yanlarından biri zengin kütüphane ekosistemidir. Bu bölümde en sık kullanılan kütüphaneler anlatılmaktadır.

requests & httpx -- HTTP İstemci

bash
pip install requests httpx
python
import requests

# --- requests (senkron) ---
# GET isteği
resp = requests.get("https://api.github.com/users/fahri", timeout=10)
print(resp.status_code)    # 200
print(resp.json())         # JSON olarak parse et

# POST isteği
resp = requests.post(
    "https://httpbin.org/post",
    json={"name": "Fahri", "role": "dev"},
    headers={"Authorization": "Bearer TOKEN"},
    timeout=10,
)

# Session -- bağlantı yeniden kullanımı
session = requests.Session()
session.headers.update({"Authorization": "Bearer TOKEN"})
resp = session.get("https://api.example.com/data")

# Dosya indirme
resp = requests.get("https://example.com/file.zip", stream=True)
with open("file.zip", "wb") as f:
    for chunk in resp.iter_content(chunk_size=8192):
        f.write(chunk)
python
# --- httpx (senkron + asenkron) ---
import httpx

# Senkron kullanım (requests benzeri)
resp = httpx.get("https://httpbin.org/get", timeout=10)
print(resp.json())

# Asenkron kullanım
import asyncio

async def fetch():
    async with httpx.AsyncClient() as client:
        resp = await client.get("https://httpbin.org/get")
        return resp.json()

asyncio.run(fetch())

# HTTP/2 desteği
client = httpx.Client(http2=True)
resp = client.get("https://example.com")
print(resp.http_version)  # "HTTP/2"

Celery -- Dağıtık Görev Kuyruğu

bash
pip install celery[redis]
python
# celery_app.py
from celery import Celery

app = Celery(
    "tasks",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/1",
)

# Yapılandırma
app.conf.update(
    task_serializer="json",
    result_serializer="json",
    accept_content=["json"],
    timezone="Europe/Istanbul",
    task_track_started=True,
    result_expires=3600,
)

# Görev tanımla
@app.task(bind=True, max_retries=3)
def send_email(self, to: str, subject: str, body: str):
    try:
        # E-posta gönderme işlemi
        print(f"E-posta gönderiliyor: {to}")
    except Exception as exc:
        raise self.retry(exc=exc, countdown=60)

@app.task
def process_image(image_path: str) -> str:
    # Uzun süren görüntü işleme
    return f"İşlendi: {image_path}"

# Periyodik görevler (Celery Beat)
app.conf.beat_schedule = {
    "temizlik-her-saat": {
        "task": "tasks.cleanup",
        "schedule": 3600.0,  # saniye
    },
}
python
# Görev çağırma
result = send_email.delay("info@fahriaydin.dev", "Test", "Merhaba!")
print(result.id)         # Görev ID
print(result.status)     # PENDING, STARTED, SUCCESS, FAILURE
print(result.get(timeout=30))  # Sonucu bekle

# Zincirleme görevler
from celery import chain, group, chord

# Sıralı çalıştırma
chain(task1.s(arg1), task2.s(), task3.s())()

# Paralel çalıştırma
group(task1.s(i) for i in range(10))()
bash
# Celery worker başlat
celery -A celery_app worker --loglevel=info

# Beat scheduler başlat (periyodik görevler için)
celery -A celery_app beat --loglevel=info

Alembic -- Veritabanı Migration

bash
pip install alembic
bash
# Alembic başlat
alembic init alembic

# Migration oluştur
alembic revision --autogenerate -m "create users table"

# Migration uygula
alembic upgrade head

# Bir adım geri al
alembic downgrade -1

# Migration geçmişini gör
alembic history
python
# alembic/env.py -- SQLAlchemy modellerini bağla
from myapp.models import Base
target_metadata = Base.metadata

# Migration dosyası örneği (otomatik oluşturulur)
# alembic/versions/001_create_users.py
from alembic import op
import sqlalchemy as sa

def upgrade():
    op.create_table(
        "users",
        sa.Column("id", sa.Integer(), primary_key=True),
        sa.Column("name", sa.String(100), nullable=False),
        sa.Column("email", sa.String(255), unique=True),
        sa.Column("created_at", sa.DateTime(), server_default=sa.func.now()),
    )
    op.create_index("ix_users_email", "users", ["email"])

def downgrade():
    op.drop_index("ix_users_email")
    op.drop_table("users")

SQLAlchemy -- ORM Detaylı

bash
pip install sqlalchemy psycopg2-binary
python
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey, DateTime, func, select
from sqlalchemy.orm import DeclarativeBase, relationship, sessionmaker, Session

# Engine ve Base
engine = create_engine("postgresql://user:pass@localhost/mydb", echo=True)

class Base(DeclarativeBase):
    pass

# Modeller
class Author(Base):
    __tablename__ = "authors"

    id = Column(Integer, primary_key=True)
    name = Column(String(100), nullable=False)
    email = Column(String(255), unique=True)

    books = relationship("Book", back_populates="author", cascade="all, delete-orphan")

    def __repr__(self):
        return f"Author(id={self.id}, name={self.name!r})"

class Book(Base):
    __tablename__ = "books"

    id = Column(Integer, primary_key=True)
    title = Column(String(200), nullable=False)
    author_id = Column(Integer, ForeignKey("authors.id"), nullable=False)
    published_at = Column(DateTime, server_default=func.now())

    author = relationship("Author", back_populates="books")

# Tablo oluştur
Base.metadata.create_all(engine)
SessionLocal = sessionmaker(bind=engine)

# CRUD işlemleri
with SessionLocal() as session:
    # Create
    author = Author(name="Fahri", email="info@fahriaydin.dev")
    author.books.append(Book(title="Python Rehberi"))
    session.add(author)
    session.commit()

    # Read
    authors = session.query(Author).filter(Author.name.ilike("%fahri%")).all()

    # SQLAlchemy 2.0 tarzı (select)
    stmt = select(Book).where(Book.title.contains("Python")).order_by(Book.published_at.desc())
    books = session.scalars(stmt).all()

    # Update
    author.name = "Fahri Aydın"
    session.commit()

    # Delete
    session.delete(author)
    session.commit()

    # Join sorgusu
    stmt = (
        select(Book, Author)
        .join(Author)
        .where(Author.name == "Fahri")
    )
    results = session.execute(stmt).all()

    # Aggregation
    from sqlalchemy import func as sa_func
    count = session.query(sa_func.count(Book.id)).scalar()

Pillow -- Görüntü İşleme

bash
pip install Pillow
python
from PIL import Image, ImageDraw, ImageFont, ImageFilter

# Resim aç ve bilgilerini gör
img = Image.open("foto.jpg")
print(img.size)     # (1920, 1080)
print(img.format)   # JPEG
print(img.mode)     # RGB

# Boyutlandırma
thumbnail = img.resize((300, 200))
img.thumbnail((800, 600))  # En-boy oranını korur (in-place)

# Kırpma (crop)
cropped = img.crop((100, 100, 500, 400))  # (left, top, right, bottom)

# Döndürme
rotated = img.rotate(45, expand=True)

# Filtre uygula
blurred = img.filter(ImageFilter.GaussianBlur(radius=5))
sharpened = img.filter(ImageFilter.SHARPEN)

# Metin ekleme
draw = ImageDraw.Draw(img)
draw.text((50, 50), "Merhaba!", fill="white")

# Format dönüştürme ve kaydetme
img.save("output.png")
img.save("output.webp", quality=85)

# Toplu işlem -- dizindeki tüm resimleri küçült
from pathlib import Path

for f in Path("photos").glob("*.jpg"):
    with Image.open(f) as im:
        im.thumbnail((800, 600))
        im.save(f"thumbnails/{f.name}")

BeautifulSoup -- HTML/XML Parsing

bash
pip install beautifulsoup4 lxml
python
from bs4 import BeautifulSoup
import requests

# HTML parse et
html = requests.get("https://example.com").text
soup = BeautifulSoup(html, "lxml")

# Element seçme
title = soup.find("title").text
links = soup.find_all("a", href=True)
for link in links:
    print(link["href"], link.text)

# CSS seçicileri
items = soup.select("div.item > h2.title")
first_para = soup.select_one("p.intro")

# Attribute ile arama
images = soup.find_all("img", attrs={"class": "thumbnail"})

# Metin çıkarma
text = soup.get_text(separator="\n", strip=True)

# Tablo parse etme
table = soup.find("table")
rows = []
for tr in table.find_all("tr"):
    cells = [td.text.strip() for td in tr.find_all(["td", "th"])]
    rows.append(cells)

Scrapy -- Web Scraping Framework

bash
pip install scrapy
python
# myspider.py
import scrapy

class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = ["https://quotes.toscrape.com/"]

    def parse(self, response):
        for quote in response.css("div.quote"):
            yield {
                "text": quote.css("span.text::text").get(),
                "author": quote.css("small.author::text").get(),
                "tags": quote.css("a.tag::text").getall(),
            }

        # Sonraki sayfa
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            yield response.follow(next_page, self.parse)
bash
# Spider çalıştır
scrapy runspider myspider.py -o quotes.json

python-dotenv -- Ortam Değişkenleri

bash
pip install python-dotenv
python
# .env dosyası
# DATABASE_URL=postgresql://user:pass@localhost/mydb
# SECRET_KEY=super-secret-key-123
# DEBUG=true

from dotenv import load_dotenv
import os

load_dotenv()  # .env dosyasını yükle

db_url = os.getenv("DATABASE_URL")
secret = os.getenv("SECRET_KEY")
debug = os.getenv("DEBUG", "false").lower() == "true"

click & typer -- Modern CLI Araçları

bash
pip install click typer[all]
python
# --- click ---
import click

@click.command()
@click.argument("name")
@click.option("--count", "-c", default=1, help="Tekrar sayısı")
@click.option("--shout/--no-shout", default=False)
def greet(name, count, shout):
    """NAME kişisini selamla."""
    msg = f"Merhaba, {name}!"
    if shout:
        msg = msg.upper()
    for _ in range(count):
        click.echo(msg)

if __name__ == "__main__":
    greet()
python
# --- typer (tip ipuçları ile CLI) ---
import typer
from typing import Annotated

app = typer.Typer(help="Dosya yönetim aracı")

@app.command()
def process(
    input_file: Annotated[str, typer.Argument(help="Girdi dosyası")],
    output: Annotated[str, typer.Option("--output", "-o")] = "out.json",
    verbose: Annotated[bool, typer.Option("--verbose", "-v")] = False,
):
    """Dosyayı işle ve çıktı oluştur."""
    if verbose:
        typer.echo(f"İşleniyor: {input_file}")
    typer.echo(f"Çıktı: {output}")

if __name__ == "__main__":
    app()

rich -- Zengin Terminal Çıktısı

bash
pip install rich
python
from rich.console import Console
from rich.table import Table
from rich.progress import track
from rich import print as rprint
import time

console = Console()

# Renkli çıktı
console.print("[bold red]Hata![/] Dosya bulunamadı.", style="italic")
rprint("[green]Başarılı[/green]")

# Tablo
table = Table(title="Kullanıcılar")
table.add_column("ID", style="cyan")
table.add_column("Ad", style="magenta")
table.add_column("E-posta")
table.add_row("1", "Fahri", "info@fahriaydin.dev")
table.add_row("2", "Ali", "ali@example.com")
console.print(table)

# Progress bar
for item in track(range(100), description="İşleniyor..."):
    time.sleep(0.02)

# Loglama
from rich.logging import RichHandler
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(message)s",
    handlers=[RichHandler()],
)
log = logging.getLogger("myapp")
log.info("Uygulama başlatıldı")

schedule & APScheduler -- Zamanlanmış Görevler

bash
pip install schedule APScheduler
python
# --- schedule (basit) ---
import schedule
import time

def job():
    print("Görev çalıştı!")

schedule.every(10).seconds.do(job)
schedule.every().hour.do(job)
schedule.every().day.at("09:00").do(job)
schedule.every().monday.at("08:30").do(job)

while True:
    schedule.run_pending()
    time.sleep(1)
python
# --- APScheduler (gelişmiş) ---
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.triggers.cron import CronTrigger

scheduler = BackgroundScheduler()

def daily_report():
    print("Günlük rapor oluşturuluyor...")

def cleanup():
    print("Temizlik yapılıyor...")

# Cron tarzı zamanlama
scheduler.add_job(daily_report, CronTrigger(hour=9, minute=0))
scheduler.add_job(cleanup, "interval", hours=6)

scheduler.start()

boto3 -- AWS SDK

bash
pip install boto3
python
import boto3

# S3 işlemleri
s3 = boto3.client("s3")

# Dosya yükle
s3.upload_file("local.txt", "my-bucket", "remote/path/file.txt")

# Dosya indir
s3.download_file("my-bucket", "remote/path/file.txt", "downloaded.txt")

# Dosyaları listele
response = s3.list_objects_v2(Bucket="my-bucket", Prefix="uploads/")
for obj in response.get("Contents", []):
    print(obj["Key"], obj["Size"])

# Presigned URL oluştur (geçici erişim)
url = s3.generate_presigned_url(
    "get_object",
    Params={"Bucket": "my-bucket", "Key": "private/file.pdf"},
    ExpiresIn=3600,  # 1 saat
)

marshmallow & Pydantic -- Veri Doğrulama

bash
pip install marshmallow pydantic[email]
python
# --- marshmallow ---
from marshmallow import Schema, fields, validate, post_load

class UserSchema(Schema):
    name = fields.Str(required=True, validate=validate.Length(min=2, max=100))
    email = fields.Email(required=True)
    age = fields.Int(validate=validate.Range(min=0, max=150))
    tags = fields.List(fields.Str())

    @post_load
    def make_user(self, data, **kwargs):
        return User(**data)

schema = UserSchema()
result = schema.load({"name": "Fahri", "email": "info@fahriaydin.dev", "age": 25})
errors = schema.validate({"name": "", "email": "invalid"})
# errors: {'name': ['Shorter than minimum length 2.'], 'email': ['Not a valid email address.']}
python
# --- Pydantic (daha modern) ---
from pydantic import BaseModel, EmailStr, field_validator

class User(BaseModel):
    name: str
    email: EmailStr
    age: int

    @field_validator("age")
    @classmethod
    def check_age(cls, v):
        if v < 0 or v > 150:
            raise ValueError("Geçersiz yaş")
        return v

user = User(name="Fahri", email="info@fahriaydin.dev", age=25)
print(user.model_dump())       # dict
print(user.model_dump_json())  # JSON string

26) Veri Bilimi Ek Araçlar

NumPy ve Pandas dışında veri bilimi ekosisteminin önemli kütüphaneleri.

Matplotlib & Seaborn -- Veri Görselleştirme

bash
pip install matplotlib seaborn
python
import matplotlib.pyplot as plt
import numpy as np

# --- Temel çizgi grafiği ---
x = np.linspace(0, 2 * np.pi, 100)
y1 = np.sin(x)
y2 = np.cos(x)

plt.figure(figsize=(10, 6))
plt.plot(x, y1, label="sin(x)", color="blue", linewidth=2)
plt.plot(x, y2, label="cos(x)", color="red", linestyle="--")
plt.xlabel("x")
plt.ylabel("y")
plt.title("Trigonometrik Fonksiyonlar")
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig("trig.png", dpi=150, bbox_inches="tight")
plt.show()

# --- Bar grafiği ---
categories = ["Python", "JavaScript", "Go", "Rust"]
values = [85, 72, 45, 38]

plt.figure(figsize=(8, 5))
bars = plt.bar(categories, values, color=["#3776ab", "#f7df1e", "#00add8", "#ce422b"])
plt.ylabel("Popülerlik Skoru")
plt.title("Programlama Dilleri")
for bar, val in zip(bars, values):
    plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, str(val), ha="center")
plt.tight_layout()
plt.savefig("languages.png")

# --- Alt grafikler (subplots) ---
fig, axes = plt.subplots(2, 2, figsize=(12, 8))

axes[0, 0].plot(x, y1)
axes[0, 0].set_title("sin(x)")

axes[0, 1].scatter(np.random.rand(50), np.random.rand(50), alpha=0.6)
axes[0, 1].set_title("Scatter")

axes[1, 0].hist(np.random.randn(1000), bins=30, edgecolor="black")
axes[1, 0].set_title("Histogram")

axes[1, 1].pie([30, 25, 20, 15, 10], labels=["A", "B", "C", "D", "E"], autopct="%1.1f%%")
axes[1, 1].set_title("Pasta")

plt.tight_layout()
plt.savefig("subplots.png")
python
# --- Seaborn (istatistiksel görselleştirme) ---
import seaborn as sns
import pandas as pd

# Seaborn teması
sns.set_theme(style="whitegrid")

# Örnek veri
df = pd.DataFrame({
    "ay": ["Oca", "Şub", "Mar", "Nis", "May", "Haz"] * 2,
    "satış": [120, 135, 148, 162, 178, 195, 95, 108, 122, 138, 145, 168],
    "bölge": ["İstanbul"] * 6 + ["Ankara"] * 6,
})

# Çizgi grafiği
plt.figure(figsize=(10, 6))
sns.lineplot(data=df, x="ay", y="satış", hue="bölge", marker="o")
plt.title("Aylık Satışlar")
plt.savefig("sales.png")

# Heatmap (korelasyon matrisi)
numeric_df = pd.DataFrame(np.random.rand(5, 5), columns=list("ABCDE"))
plt.figure(figsize=(8, 6))
sns.heatmap(numeric_df.corr(), annot=True, cmap="coolwarm", center=0)
plt.title("Korelasyon Matrisi")
plt.savefig("correlation.png")

# Box plot
plt.figure(figsize=(8, 5))
sns.boxplot(data=df, x="bölge", y="satış")
plt.title("Bölgeye Göre Satış Dağılımı")
plt.savefig("boxplot.png")

scikit-learn -- Makine Öğrenimi Temelleri

bash
pip install scikit-learn
python
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
import numpy as np

# Örnek veri (Iris dataset)
from sklearn.datasets import load_iris

iris = load_iris()
X, y = iris.data, iris.target

# Veriyi böl
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)

# Ölçeklendirme
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# Model eğitimi
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train_scaled, y_train)

# Tahmin ve değerlendirme
y_pred = model.predict(X_test_scaled)
print(f"Doğruluk: {accuracy_score(y_test, y_pred):.4f}")
print(classification_report(y_test, y_pred, target_names=iris.target_names))

# Pipeline -- ön işleme + model
from sklearn.pipeline import Pipeline

pipeline = Pipeline([
    ("scaler", StandardScaler()),
    ("classifier", LogisticRegression(max_iter=200)),
])

pipeline.fit(X_train, y_train)
score = pipeline.score(X_test, y_test)
print(f"Pipeline doğruluk: {score:.4f}")

# Cross-validation
from sklearn.model_selection import cross_val_score

scores = cross_val_score(pipeline, X, y, cv=5, scoring="accuracy")
print(f"CV Ortalama: {scores.mean():.4f} (+/- {scores.std():.4f})")

OpenCV -- Görüntü İşleme Temelleri

bash
pip install opencv-python
python
import cv2
import numpy as np

# Resim okuma ve gösterme
img = cv2.imread("foto.jpg")
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
print(img.shape)  # (yükseklik, genişlik, kanal)

# Boyutlandırma
resized = cv2.resize(img, (640, 480))
resized2 = cv2.resize(img, None, fx=0.5, fy=0.5)

# Bulanıklaştırma
blurred = cv2.GaussianBlur(img, (5, 5), 0)

# Kenar algılama (Canny)
edges = cv2.Canny(gray, 50, 150)

# Yüz algılama (Haar Cascade)
face_cascade = cv2.CascadeClassifier(
    cv2.data.haarcascades + "haarcascade_frontalface_default.xml"
)
faces = face_cascade.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5)
for (x, y, w, h) in faces:
    cv2.rectangle(img, (x, y), (x+w, y+h), (0, 255, 0), 2)

# Kaydetme
cv2.imwrite("output.jpg", img)

# Video yakalama (webcam)
cap = cv2.VideoCapture(0)
while cap.isOpened():
    ret, frame = cap.read()
    if not ret:
        break
    cv2.imshow("Kamera", frame)
    if cv2.waitKey(1) & 0xFF == ord("q"):
        break
cap.release()
cv2.destroyAllWindows()

Jupyter Notebook

bash
pip install jupyterlab notebook
bash
# Jupyter Lab başlat
jupyter lab

# Klasik notebook başlat
jupyter notebook

# Komut satırından notebook çalıştır
jupyter nbconvert --to script notebook.ipynb    # .py'ye dönüştür
jupyter nbconvert --execute notebook.ipynb      # Çalıştır ve çıktıları kaydet
jupyter nbconvert --to html notebook.ipynb      # HTML'e dönüştür
python
# Notebook içinde sık kullanılan sihirli komutlar (magic commands)

# %timeit -- performans ölçümü
# %timeit sum(range(10000))

# %%time -- hücre süresi ölçümü
# %%time
# result = heavy_computation()

# %matplotlib inline -- grafikleri notebook içinde göster

# !pip install paket -- hücre içinden paket kur

# %who -- tanımlı değişkenleri listele

# %load_ext autoreload
# %autoreload 2  -- modül değişikliklerini otomatik yükle

27) Güvenlik (Security)

Web uygulamalarında güvenlik kritik öneme sahiptir. Bu bölümde yaygın güvenlik açıkları ve önlemleri anlatılmaktadır.

Input Validation

python
import re
from pydantic import BaseModel, field_validator, EmailStr

# Kullanıcı girdisini ASLA doğrudan kullanma
# Her zaman validate et ve sanitize et

class RegisterInput(BaseModel):
    username: str
    email: EmailStr
    password: str

    @field_validator("username")
    @classmethod
    def validate_username(cls, v):
        if not re.match(r"^[a-zA-Z0-9_]{3,20}$", v):
            raise ValueError("Kullanıcı adı 3-20 karakter, alfanümerik olmalı")
        return v

    @field_validator("password")
    @classmethod
    def validate_password(cls, v):
        if len(v) < 8:
            raise ValueError("Şifre en az 8 karakter olmalı")
        if not re.search(r"[A-Z]", v):
            raise ValueError("En az bir büyük harf gerekli")
        if not re.search(r"[0-9]", v):
            raise ValueError("En az bir rakam gerekli")
        return v

SQL Injection Koruması

python
# YANLIS -- SQL injection'a açık
def get_user_unsafe(username):
    query = f"SELECT * FROM users WHERE name = '{username}'"
    cursor.execute(query)  # TEHLİKELİ!

# DOGRU -- Parametreli sorgu kullan
def get_user_safe(username):
    cursor.execute("SELECT * FROM users WHERE name = %s", (username,))

# SQLAlchemy ile -- ORM zaten parametreli sorgu kullanır
user = session.query(User).filter(User.name == username).first()

# Ham sorgu gerekirse
from sqlalchemy import text
result = session.execute(
    text("SELECT * FROM users WHERE name = :name"),
    {"name": username}
)

XSS (Cross-Site Scripting) Koruması

python
import html

# Kullanıcı girdisini HTML'de gösterirken escape et
user_input = '<script>alert("hack")</script>'
safe_output = html.escape(user_input)
# &lt;script&gt;alert(&quot;hack&quot;)&lt;/script&gt;

# Jinja2 template'lerde otomatik escape aktiftir
# {{ user_input }}  -- otomatik escape edilir
# {{ user_input | safe }}  -- TEHLIKELI! Sadece güvenli veri için kullan

# Markdown render ederken
import bleach

allowed_tags = ["b", "i", "u", "a", "p", "br", "ul", "ol", "li", "code", "pre"]
allowed_attrs = {"a": ["href", "title"]}

clean_html = bleach.clean(
    user_html,
    tags=allowed_tags,
    attributes=allowed_attrs,
    strip=True,
)

CSRF (Cross-Site Request Forgery) Koruması

python
# Flask-WTF ile CSRF koruması
# pip install flask-wtf

from flask_wtf import FlaskForm, CSRFProtect
from wtforms import StringField, SubmitField

app.config["SECRET_KEY"] = "gizli-anahtar"
csrf = CSRFProtect(app)

class PostForm(FlaskForm):
    title = StringField("Başlık")
    submit = SubmitField("Gönder")

# Template'de form kullanımı:
# <form method="POST">
#     {{ form.hidden_tag() }}  <!-- CSRF token -->
#     {{ form.title() }}
#     {{ form.submit() }}
# </form>

# Django'da CSRF varsayılan olarak aktiftir
# Template'de: {% csrf_token %}

# FastAPI'de CSRF genellikle gereksiz (token-based auth kullanılır)
# Ama cookie-based auth ile kullanılıyorsa:
# pip install fastapi-csrf-protect

pickle & eval Tehlikeleri

python
# TEHLIKE: pickle güvenilmeyen veriyle kullanılmamalı
# pickle, rastgele Python kodu çalıştırabilir!
import pickle

# YANLIS -- güvenilmeyen kaynaktan gelen pickle verisi
# data = pickle.loads(untrusted_data)  # TEHLIKELI!

# DOGRU -- JSON veya güvenli format kullan
import json
data = json.loads(trusted_json_string)

# eval() ASLA kullanıcı girdisiyle kullanılmamalı
# YANLIS
# result = eval(user_input)  # TEHLIKELI!

# DOGRU -- ast.literal_eval sadece literal ifadeler için güvenli
import ast
safe_result = ast.literal_eval("[1, 2, 3]")  # Sadece literal değerler

Dosya Yükleme Güvenliği

python
import os
from pathlib import Path
from werkzeug.utils import secure_filename

UPLOAD_DIR = Path("uploads")
ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".pdf"}
MAX_FILE_SIZE = 5 * 1024 * 1024  # 5 MB

def is_safe_upload(filename: str, file_size: int) -> bool:
    # Dosya uzantısını kontrol et
    ext = Path(filename).suffix.lower()
    if ext not in ALLOWED_EXTENSIONS:
        return False

    # Dosya boyutunu kontrol et
    if file_size > MAX_FILE_SIZE:
        return False

    # Güvenli dosya adı oluştur
    safe_name = secure_filename(filename)
    if not safe_name:
        return False

    return True

def save_upload(file, filename: str) -> Path:
    safe_name = secure_filename(filename)

    # Path traversal saldırısını engelle
    dest = UPLOAD_DIR / safe_name
    dest = dest.resolve()
    if not str(dest).startswith(str(UPLOAD_DIR.resolve())):
        raise ValueError("Geçersiz dosya yolu")

    file.save(str(dest))
    return dest

CORS Yapılandırması

python
# Flask
from flask_cors import CORS

# Spesifik origin'lere izin ver (geniş açma!)
CORS(app, origins=["https://mysite.com", "https://app.mysite.com"])

# FastAPI
from fastapi.middleware.cors import CORSMiddleware

app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://mysite.com"],  # ["*"] kullanma!
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Authorization", "Content-Type"],
)

secrets Modülü -- Güvenli Rastgele Değerler

python
import secrets

# Güvenli token oluştur (random modülü KULLANMA!)
token = secrets.token_hex(32)          # 64 karakter hex string
url_token = secrets.token_urlsafe(32)  # URL-safe base64 string

# Güvenli şifre oluştur
import string
alphabet = string.ascii_letters + string.digits + string.punctuation
password = "".join(secrets.choice(alphabet) for _ in range(16))

# Güvenli karşılaştırma (timing attack'e karşı)
secrets.compare_digest(user_token, stored_token)

# Şifre hashleme (bcrypt veya argon2 kullan)
# pip install bcrypt
import bcrypt

password = "gizli_sifre".encode()
hashed = bcrypt.hashpw(password, bcrypt.gensalt())
is_valid = bcrypt.checkpw(password, hashed)

Güvenlik Kontrol Listesi

KonuÖnlem
SQL InjectionParametreli sorgu veya ORM kullan
XSSHTML escape, Content-Security-Policy header
CSRFCSRF token, SameSite cookie
Şifre saklamabcrypt/argon2 ile hashle, düz metin ASLA
Token/Keysecrets modülü, .env dosyası, git'e ekleme
Dosya yüklemeUzantı kontrol, boyut sınırı, secure_filename
CORSSpesifik origin, wildcard kullanma
HTTPSProduction'da her zaman HTTPS
DependencyGüncel tut, pip audit ile tarama yap
Hata mesajıProduction'da detaylı hata gösterme

28) Testing: pytest Advanced

Kapsamlı test yazımı, güvenilir yazılımın temelidir.

bash
pip install pytest pytest-cov pytest-mock hypothesis

pytest Temelleri ve Parametrize

python
# tests/test_utils.py
import pytest

def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError("Sıfıra bölme hatası")
    return a / b

def test_add():
    assert add(2, 3) == 5

def test_add_negative():
    assert add(-1, 1) == 0

# Parametrize -- birden fazla test vakası
@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (0, 0, 0),
    (-1, -1, -2),
    (100, 200, 300),
    (1.5, 2.5, 4.0),
])
def test_add_params(a, b, expected):
    assert add(a, b) == expected

# Exception test etme
def test_divide_by_zero():
    with pytest.raises(ValueError, match="Sıfıra bölme"):
        divide(10, 0)

# Yaklaşık eşitlik (float karşılaştırma)
def test_float_division():
    assert divide(1, 3) == pytest.approx(0.333, abs=0.001)

# Koşullu atla
@pytest.mark.skipif(sys.platform == "win32", reason="Sadece Linux'ta çalışır")
def test_unix_feature():
    pass

# Beklenen başarısızlık
@pytest.mark.xfail(reason="Bilinen bug, düzeltilecek")
def test_known_bug():
    assert add(1, 1) == 3

Fixtures & conftest.py

python
# tests/conftest.py -- paylaşılan fixture'lar
import pytest
import tempfile
from pathlib import Path

@pytest.fixture
def sample_data():
    """Test verisi sağlar."""
    return {
        "users": [
            {"id": 1, "name": "Fahri", "email": "info@fahriaydin.dev"},
            {"id": 2, "name": "Ali", "email": "ali@example.com"},
        ]
    }

@pytest.fixture
def temp_dir():
    """Geçici dizin oluşturur, test sonunda temizler."""
    with tempfile.TemporaryDirectory() as tmpdir:
        yield Path(tmpdir)

@pytest.fixture(scope="session")
def db_connection():
    """Tüm test oturumu boyunca tek veritabanı bağlantısı."""
    conn = create_test_database()
    yield conn
    conn.close()
    drop_test_database()

@pytest.fixture(autouse=True)
def reset_db(db_connection):
    """Her testten önce veritabanını sıfırla."""
    db_connection.execute("DELETE FROM users")
    db_connection.commit()
python
# tests/test_users.py
def test_user_count(sample_data):
    assert len(sample_data["users"]) == 2

def test_temp_file(temp_dir):
    file_path = temp_dir / "test.txt"
    file_path.write_text("test verisi")
    assert file_path.read_text() == "test verisi"

# Fixture parametrize
@pytest.fixture(params=["sqlite", "postgresql"])
def db_engine(request):
    if request.param == "sqlite":
        return create_sqlite_engine()
    return create_postgres_engine()

def test_query(db_engine):
    # Her iki veritabanı ile de çalışır
    result = db_engine.execute("SELECT 1")
    assert result is not None

Mock & Patch

python
from unittest.mock import patch, MagicMock, AsyncMock
import pytest

# Fonksiyon mocklama
def get_weather(city):
    import requests
    resp = requests.get(f"https://api.weather.com/{city}")
    return resp.json()

def test_get_weather():
    mock_response = MagicMock()
    mock_response.json.return_value = {"temp": 25, "city": "Istanbul"}

    with patch("requests.get", return_value=mock_response) as mock_get:
        result = get_weather("istanbul")
        assert result["temp"] == 25
        mock_get.assert_called_once_with("https://api.weather.com/istanbul")

# pytest-mock ile (daha temiz söz dizimi)
def test_get_weather_mocker(mocker):
    mock_resp = MagicMock()
    mock_resp.json.return_value = {"temp": 25}
    mocker.patch("requests.get", return_value=mock_resp)

    result = get_weather("istanbul")
    assert result["temp"] == 25

# Async fonksiyon mocklama
async def fetch_data(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.json()

@pytest.mark.asyncio
async def test_fetch_data():
    with patch("aiohttp.ClientSession") as mock_session:
        mock_resp = AsyncMock()
        mock_resp.json.return_value = {"data": "test"}
        mock_session.return_value.__aenter__.return_value.get.return_value.__aenter__.return_value = mock_resp

        result = await fetch_data("https://example.com/api")
        assert result["data"] == "test"

Flask/Django/FastAPI Test Client

python
# --- Flask test ---
import pytest
from myapp import create_app

@pytest.fixture
def client():
    app = create_app(testing=True)
    app.config["TESTING"] = True
    with app.test_client() as client:
        yield client

def test_index(client):
    resp = client.get("/")
    assert resp.status_code == 200

def test_create_item(client):
    resp = client.post("/api/items", json={"name": "Test"})
    assert resp.status_code == 201
    assert resp.json["name"] == "Test"
python
# --- Django test ---
from django.test import TestCase, Client

class PostViewTest(TestCase):
    def setUp(self):
        self.client = Client()
        self.user = User.objects.create_user("testuser", password="pass123")

    def test_post_list(self):
        response = self.client.get("/api/posts/")
        self.assertEqual(response.status_code, 200)

    def test_create_post_unauthorized(self):
        response = self.client.post("/api/posts/", {"title": "Test"})
        self.assertEqual(response.status_code, 403)

    def test_create_post_authorized(self):
        self.client.login(username="testuser", password="pass123")
        response = self.client.post("/api/posts/", {
            "title": "Test Post",
            "body": "Test body",
        })
        self.assertEqual(response.status_code, 201)
python
# --- FastAPI test ---
from fastapi.testclient import TestClient
from myapp.main import app

client = TestClient(app)

def test_root():
    resp = client.get("/")
    assert resp.status_code == 200
    assert resp.json() == {"message": "Merhaba, FastAPI!"}

def test_create_user():
    resp = client.post("/users", json={
        "name": "Fahri",
        "email": "info@fahriaydin.dev",
        "age": 25,
    })
    assert resp.status_code == 201
    data = resp.json()
    assert data["name"] == "Fahri"
    assert "id" in data

# Async test
import pytest
from httpx import AsyncClient, ASGITransport

@pytest.mark.asyncio
async def test_async_root():
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        resp = await ac.get("/")
        assert resp.status_code == 200

# Dependency override
from myapp.dependencies import get_db

def override_get_db():
    db = TestSessionLocal()
    try:
        yield db
    finally:
        db.close()

app.dependency_overrides[get_db] = override_get_db

tox -- Çoklu Ortam Test

bash
pip install tox
ini
# tox.ini
[tox]
envlist = py310, py311, py312

[testenv]
deps =
    pytest
    pytest-cov
commands =
    pytest --cov=myapp --cov-report=term-missing {posargs}

[testenv:lint]
deps = ruff
commands = ruff check .
bash
# Tüm ortamlarda test çalıştır
tox

# Sadece belirli ortamda
tox -e py312

# Lint ortamını çalıştır
tox -e lint

hypothesis -- Property-Based Testing

python
from hypothesis import given, strategies as st, settings

# Property-based test -- rastgele veri ile test et
@given(st.integers(), st.integers())
def test_add_commutative(a, b):
    """Toplama işlemi değişmeli olmalı (a + b == b + a)"""
    assert add(a, b) == add(b, a)

@given(st.integers(), st.integers(), st.integers())
def test_add_associative(a, b, c):
    """Toplama işlemi birleşmeli olmalı"""
    assert add(add(a, b), c) == add(a, add(b, c))

@given(st.text(min_size=1, max_size=100))
def test_string_reverse(s):
    """String'i iki kez ters çevirmek orijinali verir"""
    assert s[::-1][::-1] == s

@given(st.lists(st.integers()))
def test_sort_idempotent(lst):
    """Sıralanmış listeyi tekrar sıralamak sonucu değiştirmez"""
    sorted_lst = sorted(lst)
    assert sorted(sorted_lst) == sorted_lst

# Daha karmaşık stratejiler
@given(st.dictionaries(
    keys=st.text(min_size=1, max_size=10),
    values=st.integers(min_value=0, max_value=100),
    min_size=1,
    max_size=5,
))
@settings(max_examples=200)
def test_dict_operations(d):
    assert len(d) <= 5
    for key in d:
        assert isinstance(d[key], int)

Coverage -- Kod Kapsama

bash
# Test çalıştır ve kapsama raporu oluştur
pytest --cov=myapp --cov-report=html --cov-report=term-missing

# Minimum kapsama oranı zorunlu kıl
pytest --cov=myapp --cov-fail-under=80
ini
# pyproject.toml veya .coveragerc
[tool.coverage.run]
source = ["myapp"]
omit = ["*/tests/*", "*/migrations/*"]

[tool.coverage.report]
fail_under = 80
show_missing = true
exclude_lines = [
    "pragma: no cover",
    "if TYPE_CHECKING:",
    "if __name__ == .__main__.",
]

29) Lint/Format & Tooling

bash
# Ruff (modern, hızlı -- black + isort + flake8 yerine)
pip install ruff
ruff check .           # Lint
ruff format .          # Format

# Klasik araçlar
pip install black isort flake8 mypy
black .                # Kod formatlama
isort .                # Import sıralama
flake8                 # Stil kontrolü
mypy .                 # Tip kontrolü

# Pre-commit
pip install pre-commit
# .pre-commit-config.yaml oluştur, sonra:
pre-commit install

30) Deployment & Docker

dockerfile
FROM python:3.12-slim

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 5000
CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]
yaml
# docker-compose.yml
services:
  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      - FLASK_ENV=production
      - DATABASE_URL=postgresql://user:pass@db/mydb
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      POSTGRES_DB: mydb
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

31) Tips & Best Practices

Python'da daha iyi, daha okunabilir ve daha performanslı kod yazma ipuçları.

Sanal Ortam (venv/poetry) Her Zaman Kullan

bash
# Her proje için ayrı sanal ortam
python -m venv .venv
source .venv/bin/activate

# Veya poetry ile (pyproject.toml yönetimi)
poetry init
poetry add fastapi uvicorn
poetry install

# Bağımlılıkları kilitle
pip freeze > requirements.txt       # pip
poetry lock                          # poetry

# .gitignore'a ekle
# .venv/
# __pycache__/
# *.pyc

Type Hints Her Yerde Kullan

python
# YANLIS -- tip belirsiz
def process(data, flag):
    if flag:
        return data.upper()
    return len(data)

# DOGRU -- tip açık
def process(data: str, flag: bool) -> str | int:
    if flag:
        return data.upper()
    return len(data)

# Fonksiyon imzası hem dokümantasyon hem de IDE desteği sağlar
def fetch_users(
    page: int = 1,
    per_page: int = 20,
    active_only: bool = True,
) -> list[dict[str, str]]:
    ...

f-string Formatlama

python
name = "Fahri"
score = 95.678

# Temel
f"Merhaba, {name}!"

# Sayı formatlama
f"Skor: {score:.2f}"           # "Skor: 95.68"
f"Büyük sayı: {1_000_000:,}"  # "Büyük sayı: 1,000,000"
f"Yüzde: {0.956:.1%}"         # "Yüzde: 95.6%"

# Hizalama
f"{'sol':<10}|{'orta':^10}|{'sag':>10}"

# Debug (Python 3.8+)
x = 42
f"{x = }"                     # "x = 42"
f"{x + 1 = }"                 # "x + 1 = 43"

# Tarih formatlama
from datetime import datetime
now = datetime.now()
f"{now:%Y-%m-%d %H:%M:%S}"    # "2026-04-09 14:30:00"

pathlib > os.path

python
from pathlib import Path

# YANLIS (eski yol)
import os
path = os.path.join("data", "output", "file.txt")
os.makedirs(os.path.dirname(path), exist_ok=True)

# DOGRU (modern yol)
path = Path("data") / "output" / "file.txt"
path.parent.mkdir(parents=True, exist_ok=True)

# Dosya işlemleri
path.write_text("içerik", encoding="utf-8")
content = path.read_text(encoding="utf-8")

# Glob
for py_file in Path("src").rglob("*.py"):
    print(py_file)

# Dosya bilgileri
path.exists()
path.is_file()
path.is_dir()
path.suffix      # ".txt"
path.stem        # "file"
path.name        # "file.txt"

dataclass Kullan

python
# YANLIS -- boilerplate fazla
class User:
    def __init__(self, id, name, email):
        self.id = id
        self.name = name
        self.email = email

    def __repr__(self):
        return f"User(id={self.id}, name={self.name!r})"

    def __eq__(self, other):
        return self.id == other.id

# DOGRU -- dataclass
from dataclasses import dataclass, field

@dataclass
class User:
    id: int
    name: str
    email: str
    tags: list[str] = field(default_factory=list)

# __init__, __repr__, __eq__ otomatik oluşturulur
# frozen=True ile immutable yapılabilir
@dataclass(frozen=True)
class Point:
    x: float
    y: float

Comprehension vs Loop

python
# Basit dönüşümler için comprehension tercih et
# YANLIS (loop)
result = []
for x in range(10):
    if x % 2 == 0:
        result.append(x ** 2)

# DOGRU (comprehension)
result = [x ** 2 for x in range(10) if x % 2 == 0]

# Karmaşık mantık için loop kullan (okunabilirlik)
# Comprehension tek satırda anlaşılmıyorsa loop daha iyi

# Generator -- büyük veri için
total = sum(x ** 2 for x in range(1_000_000))  # Bellek dostu

logging > print

python
import logging

# logging yapılandırması
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    handlers=[
        logging.StreamHandler(),
        logging.FileHandler("app.log"),
    ],
)

logger = logging.getLogger(__name__)

# YANLIS
print("Kullanıcı giriş yaptı")
print(f"HATA: {e}")

# DOGRU
logger.info("Kullanıcı giriş yaptı: %s", username)
logger.error("Veritabanı hatası: %s", e, exc_info=True)
logger.warning("Rate limit aşıldı: IP=%s", ip_address)
logger.debug("Sorgu sonucu: %d kayıt", len(results))

# Avantajlar:
# - Seviye kontrolü (DEBUG, INFO, WARNING, ERROR, CRITICAL)
# - Dosyaya yazma
# - Format kontrolü
# - Production'da debug kapatma

32) Hızlı Referans (Cheat Sheet)

KonuKomut / Bilgi
Venv oluşturpython -m venv .venv
Venv aktif etsource .venv/bin/activate
Paket kurpip install paket
Freezepip freeze > requirements.txt
Script çalıştırpython script.py
REPLpython veya ipython
Testpytest -v
Formatruff format . veya black .
Lintruff check . veya flake8
Tip kontrolmypy .
Profilingpython -m cProfile script.py
Flask çalıştırflask run --debug
Django çalıştırpython manage.py runserver
Django migrationpython manage.py makemigrations && python manage.py migrate
FastAPI çalıştıruvicorn main:app --reload
Gunicorngunicorn -b 0.0.0.0:5000 app:app
Celery workercelery -A app worker --loglevel=info
Jupyterjupyter lab
Coveragepytest --cov=myapp --cov-report=html
Güvenlik taramapip audit

Ilgili Rehberler

Backend

Diger Kategoriler

Developer Guides & Technical References