/review slash komutunun PR audit'inde referans aldığı repo-spesifik konvansiyonlar. Her madde gerçek bir dosyaya path:line ile bağlıdır. Generic best-practice yoktur.
| Katman | Seçim |
|---|---|
| Framework | FastAPI 0.129 (fastapi[standard]) |
| Runtime | Python 3.12+ (requires-python = ">=3.12") |
| Package mgr | uv (pip yasak) |
| Tip kontrolü | Pyright strict mode + reportAny=error |
| Lint/Format | Ruff (E,W,F,I,B,C4,UP,ARG001,TID252,ANN401) |
| ORM | SQLAlchemy 2.0 async + asyncpg |
| Validation | Pydantic v2 + pydantic-settings (@t3-oss/env-vari) |
| Auth | PyJWT (HS256) + bcrypt + HttpOnly cookie / Bearer fallback |
| Cache / Rate limit / Token blacklist | Redis 7 |
| Rate limit | slowapi (memory in local, async-redis in prod) |
| Background jobs | arq (Redis-backed) |
| Migrations | Alembic |
| Observability | OpenTelemetry (opt-in via env), Prometheus (gated /metrics), Sentry |
| Test | pytest + pytest-asyncio + httpx ASGI + aiosqlite + fakeredis |
| Pre-commit / Pre-push | pre-commit (ruff) / pytest |
| CI | GitHub Actions (ruff + pytest) |
Kanıt: pyproject.toml, .pre-commit-config.yaml, .github/workflows/test.yml.
app/
├── main.py # 37 satır — sadece composition (init_sentry, register_*, init_*)
├── api/
│ ├── deps.py # SessionDep, CurrentUser/ActiveUser/AdminUser/SuperAdmin, require_permission(s), ensure_permission, user_has_permission
│ ├── decorators.py # audit_unexpected_failure
│ ├── exception_handlers.py# register_exception_handlers + slowapi limiter wiring
│ ├── main.py # api_router (health, auth, users, support, admin)
│ └── routes/{auth,users,support,health}.py + admin/{users,admins,files,support,activities,stats}
├── services/ # async fonksiyonlar — class YASAK
│ ├── auth_service.py
│ ├── user_service.py
│ └── admin/
├── repositories/ # async DB queries — class YASAK
│ ├── user.py
│ ├── user_activity.py
│ ├── token_blacklist.py
│ └── admin/
├── use_cases/ # cross-cutting (log_activity) — birden fazla servisten çağrılan
├── models/ # SQLAlchemy DeclarativeBase — sadece ORM, mantık yok
├── schemas/ # Pydantic DTO'lar (Create/Update/Response ayrı)
├── core/
│ ├── config.py # pydantic-settings — boot-time validate
│ ├── db.py # async engine + AsyncSessionLocal
│ ├── security.py # JWT + bcrypt
│ ├── messages/{error,success}_message.py # ⚠️ Tüm hata/başarı string'leri
│ ├── lifespan.py # init/close redis + ensure_first_superadmin
│ ├── realtime.py # generic Redis pub/sub ↔ WebSocket bus (support + account topics)
│ ├── bootstrap.py # ensure_first_superadmin (startup superadmin seed)
│ ├── middleware.py # origin_check + CORS
│ ├── exception_handlers içinde (api/) ama register middleware/metrics/telemetry/sentry/openapi
│ ├── rate_limit.py # slowapi limiter + decorator factory'ler
│ ├── redis.py
│ ├── metrics.py # Prometheus instrumentator + gated /metrics
│ ├── telemetry.py # OTel — OTEL_EXPORTER_OTLP_ENDPOINT set ise
│ ├── sentry.py
│ ├── openapi.py # custom_generate_unique_id
│ └── email.py
├── worker/ # arq jobs (delete_expired_accounts vs.)
├── utils/ # pure helpers (datetime_utils, email_templates)
├── alembic/ # migration scripts
└── tests/ # pytest — sqlite+aiosqlite + fakeredis
Yeni domain: model → schema → repository → service → route (her katman sırayla). Model değiştiyse Alembic migration zorunlu.
api → services → repositories → models— bu sıra bozulamaz.api/routes/repository çağırmaz, her zaman service üzerinden geçer (CLAUDE.md:84).- Services başka service çağırmaz — paylaşılan repo fonksiyonu veya
use_cases/ile çözülür. Depends()sadece route handler'larda (deps.py:107-111). Service/repository fonksiyonları parametre olaraksession: AsyncSessionalır.- Route'tan ham ORM objesi değil, Pydantic schema response döner (users.py:25-28:
response_model=UserPublic).
- Sync DB call yasak. Tüm queries
async/await. SQLAlchemy 2.0select(...)+await session.execute(...)paterni (repositories/user.py:17-21). - Engine core/db.py:11-19:
create_async_engine+async_sessionmaker(expire_on_commit=False).pool_pre_ping=True.
- Services ve repositories saf async fonksiyonlardır (CLAUDE.md:98). Class wrapper ekleme.
- Repository fonksiyonu:
async def <verb>_<entity>(session: AsyncSession, ...)(repositories/user.py:12-43).
- Domain başına
Create/Update/UpdateMe/Public/UpdateResponseayrı sınıflar (schemas/user.py:34-79). model_config = ConfigDict(from_attributes=True)ORM → Pydantic mapping için zorunlu (schemas/user.py:22).- Validator hata mesajları
ErrorMessages.X'tan gelir, hardcode yasak (schemas/user.py:43). field_validator+@classmethodpaterni (schemas/user.py:39-44).
- Tüm hata mesajları app/core/messages/error_message.py —
ErrorMessages.Xclass attribute olarak. - Tüm başarı mesajları app/core/messages/success_message.py.
- Inline string kesinlikle yasak (
detail="User not found"❌;detail=ErrorMessages.USER_NOT_FOUND✅) (CLAUDE.md:107, 123-126). - i18n key'leri
error.<domain>.<reason>veyaerror.account.<state>formatında — frontend bu key'i çevirir. - Sistem hataları (validation, 401, 403, 404, 409, 429, 500) için yukarı seviye sabitler error_message.py:1-9.
- JWT logic sadece app/core/security.py — başka yerde
jwt.encode/decodeyasak. HS256. - Token tipleri:
access,refresh,password_reset,new_account—typeclaim ile ayrılır (security.py:88-90). Yanlış tip →None. - Password hashing bcrypt (security.py:107-140) — plain-text password log/return yasak.
- Auth flow: cookie
access_token(HttpOnly) öncelikli, fallbackAuthorization: Bearer(deps.py:42). is_token_blacklistedRedis kontrolü herget_current_userçağrısında (deps.py:51-56).CurrentUserdeletion grace window'undakileri kabul eder;CurrentActiveUserred eder (deps.py). Admin paneli içinCurrentAdminUser(admin veya superadmin), superadmin-özel uçlar içinCurrentSuperAdmin; ince taneli yetki içinrequire_permission(Permission.X)tekDepends— bkz. 3.18 RBAC.- Logout/deactivate token blacklist'e eklenir (user_service.py:74-80).
- Cookie path:
access_token"/",refresh_tokenf"{API_V1_STR}/auth/refresh"(users.py:82-85).
slowapiile core/rate_limit.py. Local'de in-memory, prod'daasync+redis://...(replica'lar arası paylaşım).- Üç decorator factory:
rate_limit_public(10/min default),rate_limit_authenticated(100/min),rate_limit_strict(3/min — şifre reset/hesap silme gibi). - Decorator route'ta kullanılır ve route signature'ında
request: Requestzorunlu (slowapi limiter scope'tan okur). - Limiter
app.state.limiterüzerinden register edilir (exception_handlers.py:91).
@audit_unexpected_failure(activity_type, resource_type, endpoint)decorator'u (api/decorators.py:31-71) — beklenmeyen exception'dalog_activityile kayıt + re-raise.- Service'lerde manuel
log_activityçağrısı login fail / deactivate fail gibi domain-spesifik durumlarda (user_service.py:55-64). log_activityuse case use_cases/log_activity.py — request'ten IP + user-agent extract eder.- IP/user-agent sadece
log_activityüzerinden alınır; route içinde manuelrequest.client.hostyazılmaz.
- Model değişti → Alembic migration zorunlu (CLAUDE.md:108).
uv run alembic revision --autogenerate -m "...". - Partial/GIN index gibi özel index'ler model'in
__table_args__'ında deklare edilir (models/user.py:17-46) — autogenerate aksi takdirde drop önerir. passive_deletes=True+ FKON DELETE CASCADEpaterni (models/user.py:78-83).
os.getenv/os.environdoğrudan kullanılmaz —from app.core.config import settings.pydantic-settingsboot-time validate eder (core/config.py:24-29).extra="ignore",env_ignore_empty=True._check_default_secret"changethis"değerlerini local dışında fail eder (core/config.py:112-121).SECRET_KEYnon-local'de explicit env zorunlu (core/config.py:135-139).- Yeni env:
Settingsfield'ı +.env.examplegüncelleme.
app/main.py sadece kompozisyon — 37 satır, mantık yok:
init_sentry()(lifespan başlamadan)FastAPI(...)instance +lifespan+custom_generate_unique_idregister_exception_handlers(app)(slowapi limiterapp.state'e burada bağlanır)register_middleware(app)(origin_check + CORS)app.include_router(api_router, prefix=settings.API_V1_STR)init_telemetry(app)(OTel —OTEL_EXPORTER_OTLP_ENDPOINTset değilse no-op)init_metrics(app)(Prometheus + gated/metrics)
Yeni global concern → kendi modülüne register_X(app) / init_X(app) ile eklenir, main.py şişmez.
- core/middleware.py: yabancı origin'e 404 döner (
ErrorMessages.RESOURCE_NOT_FOUND) — hangi origin'lerin trusted olduğunu sızdırmaz. allow_credentials=True+"*"wildcard kombinasyonu runtime'da reddedilir (middleware.py:57-63) — explicit origin gerekir.- Same-origin request'lere izin verilir (middleware.py:32-39).
- core/metrics.py:
/metricsnon-local'deAuthorization: Bearer <METRICS_TOKEN>ister; aksi halde 404 (origin check ile aynı şekil — endpoint varlığını sızdırmaz). /health/.*ve/metricsinstrumentation'dan exclude edilir.
- core/telemetry.py:
OTEL_EXPORTER_OTLP_ENDPOINTset değilse no-op return, sıfır overhead. - Auto-instrument:
FastAPIInstrumentor+SQLAlchemyInstrumentor+RedisInstrumentor+HTTPXClientInstrumentor. /metricsve/healthexcluded.
app/tests/altındatest_*.py. In-memory SQLite + aiosqlite (conftest.py:14-24).- Her test fresh DB (autouse fixture);
fake_redisautouse fixture ile gerçek Redis swap'lanır. httpx.AsyncClient+ASGITransportile FastAPI app'e direkt vurulur (conftest.py:60-68).mock_email_sendvemock_email_validationautouse — gerçek SMTP/MX lookup test'lerden bypass edilir.pytest-asyncioasyncio_mode = "auto"(pyproject.toml:46-49).
- Modül adı snake_case; class adı PascalCase; fonksiyon snake_case.
- Her fonksiyonun docstring'i olmalı — minimum bir satır, İngilizce (CLAUDE.md:111).
- Repository:
<verb>_<entity>(get_user_by_id,create_user,deactivate_user). - Service:
<verb>_<resource>_service(update_user_service,deactivate_own_account_service). - Schema:
<Entity><Action>(UserCreate,UserUpdate,UserUpdateMe,UserPublic).
- Conventional Commits zorunlu (
feat:,fix:,refactor:,chore:,docs:). - Pre-commit: ruff check (--fix) + ruff format. Pre-push:
uv run pytest(.pre-commit-config.yaml:22-31). uv.lockdependency değişikliği ile birlikte commit edilir (CLAUDE.md:109).pip installyasak.
- Üç rol:
user/admin/superadmin(schemas/user.pySystemRole). Superadmin tüm izin kontrolünü bypass eder ve admin'leri oluşturur/siler + izin atar. Root superadmin (is_root_superadminkolonu, models/user.py) ek olarak superadmin katmanını yönetir: admin→superadmin terfi, superadmin→admin düşürme, root devri. İlk seed edilen superadmin root'tur (core/bootstrap.py). - İzinler
resource:actionkey'leri (12 adet) (schemas/admin_permission.pyPermissionenum;users:roleyok). Plain admin'in grant'larıadmin_permissiontablosunda (models/admin_permission.py); superadmin hepsine implicit sahip. - Her admin endpoint tek
Depends(require_permission(Permission.X))ile korunur (deps.py).require_permission/require_permissions→ensure_permissionzincirine iner (auth → active → admin rolü → grant; superadmin short-circuit). Payload-koşullu izinlerrequire_permissions(base, conditional={...})ile raw body alanına göre ek izin ister (ticket atama →support:update). - Admin-yönetim uçları (routes/admin/admins.py)
CurrentSuperAdminile korunur: list / katalog / create (POST) / izin-set (PATCH) / delete account (DELETE /{id}) / promote→superadmin (POST /{id}/promote) / demote→admin (POST /{id}/demote) / root devri (POST /transfer-root+/confirm). Son üçü serviste ayrıca root kontrolü ister (ONLY_ROOT_SUPERADMIN). - Rol/katman invariant'ları serviste (services/admin/admin_service.py, user_service.py): users yüzeyinde user↔admin rol geçişi yok (admin'ler hesap olarak oluşturulur/silinir;
AdminUserUpdate'terolealanı yok); superadmin katmanı root-only (root değilseONLY_ROOT_SUPERADMIN); root'un kendi rolü değişmez (self-demoteSUPERADMIN_ROLE_IMMUTABLE, yalnızca OTP devriyle aktarılır); plain admin superadmin hedefe dokunamaz (_guard_superadmin_target); son superadmin korunur (is_last_active_superadmin). - Root devri email-OTP ile (services/admin/admin_service.py + repositories/root_transfer.py): root
/transfer-rootile mevcut bir superadmin hedef seçer → 6 haneli OTP root'un kendi mailine gider, Redis'te hash'li + TTL (3 dk);/transfer-root/confirmkodu doğrular, root bayrağını taşır, iki tarafapermissions_updatedbasar. /users/meadmin'epermissionsdöndürür (superadmin/user'a alanı hiç göndermez) + her kullanıcıyais_root_superadmin— FE gating birebir bunu yansıtır.- İzin/katman değişince hedef hesab(lar)a
permissions_updatedrealtime event'i basılır (core/realtime.pyaccount:{id}topic) — FE re-fetch eder.
- Class-based service/repository. Sadece async fonksiyonlar. (CLAUDE.md:98)
- Service/repository içinde
Depends(). Sadece route handler'da; aşağıya parametre olarak geçilir. - Route'tan repository'ye doğrudan çağrı — service katmanı atlanmamalı.
- Service başka service çağırma —
use_cases/veya repository. - Sync DB call. Hepsi
async/await. - Inline error/success string (
detail="User not found") —ErrorMessages.Xzorunlu. - JWT işlemini
core/security.pydışında yapma (jwt.encode/decodebaşka modülde). os.getenv/os.environdoğrudan —settings.- Plain-text password log/return.
Authorization: Bearerheader'ı doğrulamadan kullanma (cookie öncelikli, blacklist kontrolü zorunlu).- Model değişimini Alembic migration'sız bırakma.
- Ham ORM objesi response — Pydantic schema mapping zorunlu.
pip install—uv add.uv.lockcommit etmeden dependency değişimi.request.client.host/request.headers["user-agent"]route içinde manuel okuma —log_activityuse case kullanılır.- Yeni
slowapilimiter instance yaratma — teklimitercore/rate_limit.py üzerinden gider. @router.<verb>üzerinderequest: Requestparametresi olmadan rate-limit decorator — slowapi scope alamaz, runtime exception.allow_credentials=True+"*"CORS wildcard — middleware bunu zaten reddeder, ama PR'da gelirse blocker.
- Route → repository doğrudan çağrı (service atlanmış).
- Service → service çağrısı (use_case veya repository olmalı).
- Service/repository signature'ında
Depends(). - Route handler'da business logic (validation/transform/policy) — service'e taşınmalı.
- Repository fonksiyonunda HTTPException raise (repository persistence katmanıdır; HTTP servis/route işi).
- Sync DB call (
session.query(...)SQLAlchemy 1.x stili,session.executeawait edilmemiş). awaitunutulmuş async çağrı → coroutine return ediliyor.expire_on_commit=Falseoverride'ı (session config kırılır).- N+1 query: list endpoint'inde her item için ek
await session.execute(...). with_for_update()lock'u kaldırma (repositories/user.py:63-65) — concurrent deactivate/reactivate race condition.
- Inline
detail="..."string —ErrorMessages.Xzorunlu. ErrorMessages'a yeni eklenmeden kullanılmış sabit (NameError).- Backend i18n key formatı dışında kalan ham mesaj (
detail="Bir hata oluştu"). - Success mesajının
SuccessMessagesdışından gelmesi.
jwt.encode/decodecore/security.pydışında — token logic single source of truth.get_password_hash/verify_passwordbypass'i — bcrypt kullanılmamış.- Password log'lama / response'a koyma (
return {"password": ...}). get_current_userbypass eden route (Depends(get_current_user)olmadan korumalı route).is_token_blacklistedkontrolünü atlama.- Token'ı URL query/path/log/error'a sızdırma.
- Cookie path/domain ayarını değiştirme (
access_token"/",refresh_tokenf"{API_V1_STR}/auth/refresh") — diğer parça kırılır. - HttpOnly/SameSite/Secure flag'larını kaldırma.
@router.delete("/users/me")veya silme/şifre değişimi gibi endpoint'lerde rate limit decorator yok.verify_tokenexpected_typeparametresini değiştirme/kaldırma — yanlış tip token kabul edilebilir.- Admin endpoint'i
require_permission(...)/CurrentAdminUserolmadan açık — RBAC yetki kapısı yok. - Admin-yönetim ucu (
/admin/admins/*)CurrentSuperAdminolmadan — superadmin-özel kapı atlanmış. - Koşullu izni (ticket atama →
support:update) enforce etmeyen mutation — raw payload izni atlanırsa yanlış işlem 403 yerine geçer. - Superadmin/root koruma invariant'larını gevşetme (
_guard_superadmin_target,is_last_active_superadmin,SUPERADMIN_ROLE_IMMUTABLE) veya superadmin-katman aksiyonlarının (promote / demote / transfer-root)ONLY_ROOT_SUPERADMINkontrolünü kaldırma. /users/mepayload'ına superadmin/user içinpermissionsekleme (yalnızca plain admin'e dönmeli).
- ORM modeli response'ta —
response_modelPydantic schema olmalı. from_attributes=Trueeksik schema (ORM mapping çalışmaz).field_validatorhata mesajının inline string olması.UserPublic'e password/hashed_password sızması.model_dump()yerinedict()(Pydantic v2'de deprecated).
- Model değişikliği var ama
app/alembic/versions/altında migration yok. - Migration file'ında
op.execute(...)raw SQL — gerekçe yoksa flag (idempotent değil olabilir). __table_args__'tan partial/GIN index silinmesi (models/user.py:17-46) — autogenerate sürekli drop önerir.passive_deletes=Trueile birlikte FK'daondelete="CASCADE"eksikliği.- Yeni
uniqueconstraint migration'ında index drop/create sıralaması yanlış.
os.getenv("X")/os.environ["X"]doğrudan kullanım —settings.X.- Yeni env değeri
Settings'e eklenmemiş — runtimeAttributeError. .env.examplegüncellenmemiş yeni env.SECRET_KEYveya benzer secret'ı default literal'la fallback (os.getenv("SECRET_KEY", "default")).
- Hassas endpoint (
/auth/login,/auth/forgot-password,/users/meDELETE,/users/me/reactivate) decorator'sız. - Rate limit decorator var ama
request: Requestparametresi route signature'ında yok. - Yeni
Limiter(...)instance core/rate_limit.py dışında. - Beklenmeyen failure'lı route'ta
@audit_unexpected_failureeksik (kritik domain mutation'larında). log_activityparametre olmadan IP/user-agent manuel toplanmış (request.client.hostroute içinde).
- Yeni endpoint için test yok (auth/security/payment-impacting'se major).
- Test'te gerçek SMTP/Redis'e vurma (autouse fixture'ları override etmiş).
get_dboverride edilmemiş test (production DB'ye bağlanır).pytest-asynciodecorator'ları olmadan async test (@pytest.mark.asyncioveya autouse mode olmazsa skip).- DB değiştiren test fixture'ı temizlemiyor (test isolation kırılır).
Anykullanımı — PyrightreportAny=error.unknownveya generic narrow olmalı.# type: ignoregerekçesiz ekleme (config.py:45, 97 gibi yorum açıklayıcı olmalı).- Function return type annotation eksik (Ruff ANN).
- Ruff
E,W,F,I,B,C4,UP,ARG001,TID252ihlali. - Relative import (
from ..foo import bar) — Ruff TID252 yasak. - Unused function argument (Ruff ARG001).
- Yeni fonksiyonun docstring'i yok (CLAUDE.md:111).
- Türkçe docstring (İngilizce zorunlu).
- core/sentry.py/core/telemetry.py içine ek
Sentry.init(...)/TracerProvider(...)çağrısı (duplicate init). /metricsveya/healthinstrumentation exclude listesinden çıkarılması.METRICS_TOKENBearer kontrolünün gevşetilmesi/kaldırılması (metrics.py).- OTel
init_telemetry'nin opt-in (OTEL_EXPORTER_OTLP_ENDPOINTenv) kontrolünü kaldırma.
Bu maddeleri flag etme:
- Ruff/Pyright zaten yakalıyorsa (unused import, unused arg, missing return type) — pre-commit/CI fail eder.
- PR diff'inde olmayan satır — pre-existing kabul.
- Mevcut
# type: ignoreyorumlu/gerekçeli (config.py:45, 97[prop-decorator]gibi). - Formatting (line-length, quote style) — ruff format halleder.
- Eski dosyada
Any/inline string PR'da değişmediyse. Yeni eklenen flag. - TODO ticket/sahip referansı varsa kabul; sahipsizse minor.
- Test eksikliği non-critical endpoint için minor öneri, blocking değil. Auth/payment/silme'de major.
response_modelopsiyonel return tip annotation (FastAPI zaten serialize eder).- Generic best-practice önerileri (clean code, hexagonal, DDD) — bu repo idiomatic FastAPI; mimari öneri review skopu dışı.
B904(raise from) — pyproject'te ignored, gerekçe orada.B008(Depends() default) — pyproject'te ignored, FastAPI paterni.E501line too long — formatter halleder.async defiçindeawaityok uyarısı — bilinçli olabilir (FastAPI dependency uyumluluğu).HTTPExceptionraise hem service hem route'ta — repo bilinçli (CLAUDE.md:117). Sadece repository'de raise flag.- Print/console — ruff yakalar, low-noise.
- Türkçe, kısa, doğrudan.
- "Bu değişiklik X kuralını ihlal ediyor: <REVIEW.md alıntısı>" — soyut kural değil somut kanıt.
- Her bulgu: dosya + satır + kural + öneri (tek satırda anlaşılır düzeltme).
- Confidence < 80 ise yorumlama (slash komutu zaten filtreliyor).
- "Görmezden gelinecekler" listesindeki şeyleri yorumlama.
Son güncelleme: 2026-06-09.