{
  "cells": [
    {
      "cell_type": "markdown",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Hafta 5: Sürekli Entegrasyon (CI) ile Pipeline Otomasyonu\n",
        "\n",
        "**Dersin Hedefleri:**\n",
        "1.  Sürekli Entegrasyon (Continuous Integration - CI) kavramını ve MLOpstaki rolünü anlamak.\n",
        "2.  CI'ın temel faydalarını (hızlı geri bildirim, artan kalite, azalan risk) kavramak.\n",
        "3.  **GitHub Actions** kullanarak basit bir CI iş akışı (workflow) oluşturmak.\n",
        "4.  Oluşturulan iş akışının, kodda yapılan her değişiklikte otomatik olarak çalışmasını sağlamak.\n",
        "5.  CI pipeline'ına kod stili kontrolü (linting) ve testleri (pytest) otomatik olarak dahil etmek.\n",
        "\n",
        "## 1. Sürekli Entegrasyon (CI) Nedir?\n",
        "\n",
        "Sürekli Entegrasyon, birden fazla geliştiricinin üzerinde çalıştığı bir projede, kod değişikliklerinin düzenli olarak (genellikle günde birden çok kez) merkezi bir repository'ye entegre edildiği bir yazılım geliştirme pratiğidir. Her entegrasyon, otomatik bir \"build\" ve \"test\" süreci tarafından doğrulanır.\n",
        "\n",
        "**Veri Bilimi için CI'ın Anlamı:**\n",
        "\n",
        "Bir takım üyesi, `git push` komutuyla kodunu GitHub'a gönderdiğinde veya bir Pull Request açtığında, bir sunucu otomatik olarak şunları yapar:\n",
        "- Kodun bir kopyasını indirir.\n",
        "- Gerekli kütüphaneleri kurar.\n",
        "- **Kod stili kontrolü (Linting)** yapar: Kodun belirli bir stil standardına (örn: PEP8) uygun olup olmadığını denetler.\n",
        "- **Testleri çalıştırır:** Hafta 2'de yazdığımız tüm `pytest` testlerini çalıştırarak hiçbir şeyin bozulmadığını doğrular.\n",
        "\n",
        "Eğer bu adımlardan herhangi biri başarısız olursa, CI pipeline'ı durur ve geliştiriciye anında geri bildirim verir. Bu, hataların çok erken bir aşamada yakalanmasını sağlar.\n",
        "\n",
        "## 2. GitHub Actions ile Tanışma\n",
        "\n",
        "**GitHub Actions**, CI/CD süreçlerini doğrudan GitHub repository'niz içinde otomatikleştirmeyi sağlayan bir platformdur. İş akışları (workflows), projenizin kök dizinindeki `.github/workflows/` klasöründe bulunan `YAML` dosyaları ile tanımlanır.\n",
        "\n",
        "**Temel GitHub Actions Kavramları:**\n",
        "\n",
        "- **Workflow (İş Akışı):** Belirli bir olaya (event) tepki olarak çalışan, otomatize edilmiş süreç. Bir YAML dosyası bir workflow'u temsil eder.\n",
        "- **Event (Olay):** Bir workflow'u tetikleyen aktivite. Örnekler: `push` (bir branch'e kod gönderildiğinde), `pull_request` (bir PR açıldığında veya güncellendiğinde).\n",
        "- **Job (İş):** Bir workflow içindeki görevler bütünü. Job'lar paralel veya sıralı çalışabilir.\n",
        "- **Step (Adım):** Bir job içindeki en küçük yürütme birimi. Bir komut çalıştırmak veya bir \"action\" (hazır bir görev paketi) kullanmak olabilir.\n",
        "- **Runner:** Workflow'u çalıştıran sunucu. GitHub, Linux, Windows ve macOS için sanal makineler sağlar.\n",
        "\n",
        "## 3. Pratik Uygulama: İlk CI Workflow'umuzu Oluşturma\n",
        "\n",
        "Bu bölümde, Hafta 2'de oluşturduğumuz proje yapısı için bir CI pipeline'ı tasarlayacağız. Bu uygulama, pratik olarak bir metin editörü ve terminal kullanılarak yapılır, ancak süreci burada Python script'leri ile simüle edeceğiz.\n",
        "\n",
        "**Proje Yapımız:**\n",
        "```\n",
        ".\n",
        "├── .github/\n",
        "│   └── workflows/\n",
        "│       └── python-ci.yml   <-- Workflow dosyamız\n",
        "├── src/\n",
        "│   ├── __init__.py\n",
        "│   └── data_cleaning.py\n",
        "├── tests/\n",
        "│   ├── __init__.py\n",
        "│   └── test_data_cleaning.py\n",
        "└── requirements.txt        <-- Gerekli kütüphaneler\n",
        "```\n",
        "\n",
        "### Adım 1: Gerekli Dosyaları Oluşturma\n",
        "\n",
        "Önce, projemizin ihtiyaç duyduğu kütüphaneleri listeleyen bir `requirements.txt` dosyası oluşturalım."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import os\n",
        "import yaml\n",
        "import shutil\n",
        "\n",
        "# --- Proje Ortamını Hazırlama ---\n",
        "project_root = \"hafta5_projesi\"\n",
        "if os.path.exists(project_root):\n",
        "    shutil.rmtree(project_root)\n",
        "\n",
        "# Önceki haftalardaki gibi klasörleri ve dosyaları oluşturalım\n",
        "src_dir = os.path.join(project_root, \"src\")\n",
        "tests_dir = os.path.join(project_root, \"tests\")\n",
        "os.makedirs(src_dir)\n",
        "os.makedirs(tests_dir)\n",
        "open(os.path.join(src_dir, \"__init__.py\"), 'w').close()\n",
        "open(os.path.join(tests_dir, \"__init__.py\"), 'w').close()\n",
        "\n",
        "# Hafta 2'deki kodlarımız\n",
        "with open(os.path.join(src_dir, \"data_cleaning.py\"), \"w\") as f:\n",
        "    f.write(\"\"\"\n",
        "import pandas as pd\n",
        "import numpy as np\n",
        "def clean_patient_data(df: pd.DataFrame) -> pd.DataFrame:\n",
        "    df_copy = df.copy()\n",
        "    df_copy.loc[df_copy['age'] < 0, 'age'] = np.nan\n",
        "    return df_copy\n",
        "\"\"\")\n",
        "\n",
        "with open(os.path.join(tests_dir, \"test_data_cleaning.py\"), \"w\") as f:\n",
        "    f.write(\"\"\"\n",
        "import pytest\n",
        "import pandas as pd\n",
        "from src.data_cleaning import clean_patient_data\n",
        "def test_clean_patient_data_removes_negative_age():\n",
        "    dirty_data = pd.DataFrame({'age': [34, -5, 45]})\n",
        "    cleaned_data = clean_patient_data(dirty_data)\n",
        "    assert (cleaned_data['age'] >= 0).all() or cleaned_data['age'].isnull().all()\n",
        "\"\"\")\n",
        "\n",
        "# --- `requirements.txt` Oluşturma ---\n",
        "requirements_content = \"\"\"\n",
        "pandas\n",
        "numpy\n",
        "pytest\n",
        "flake8\n",
        "\"\"\"\n",
        "with open(os.path.join(project_root, \"requirements.txt\"), \"w\") as f:\n",
        "    f.write(requirements_content)\n",
        "print(f\"'requirements.txt' oluşturuldu.\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Adım 2: Workflow YAML Dosyasını Oluşturma\n",
        "\n",
        "Şimdi en önemli kısım olan `.github/workflows/python-ci.yml` dosyasını oluşturalım. Bu dosya, GitHub'a ne yapacağını söyleyen talimatları içerir."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# --- Workflow Klasörünü ve Dosyasını Oluşturma ---\n",
        "workflow_dir = os.path.join(project_root, \".github\", \"workflows\")\n",
        "os.makedirs(workflow_dir)\n",
        "\n",
        "# YAML içeriğini bir Python dictionary olarak tanımlayalım\n",
        "ci_workflow_dict = {\n",
        "    'name': 'Python CI Workflow',\n",
        "    \n",
        "    # Ne zaman çalışacak? `main` branch'ine push yapıldığında VEYA bir PR açıldığında.\n",
        "    'on': {\n",
        "        'push': {'branches': ['main']},\n",
        "        'pull_request': {'branches': ['main']}\n",
        "    },\n",
        "    \n",
        "    'jobs': {\n",
        "        'build-and-test': {\n",
        "            'runs-on': 'ubuntu-latest', # Hangi işletim sisteminde çalışacak?\n",
        "            'steps': [\n",
        "                # Adım 1: Kodu indir (checkout)\n",
        "                {\n",
        "                    'name': 'Check out repository code',\n",
        "                    'uses': 'actions/checkout@v3'\n",
        "                },\n",
        "                # Adım 2: Python'ı kur\n",
        "                {\n",
        "                    'name': 'Set up Python 3.9',\n",
        "                    'uses': 'actions/setup-python@v3',\n",
        "                    'with': {\n",
        "                        'python-version': '3.9'\n",
        "                    }\n",
        "                },\n",
        "                # Adım 3: Bağımlılıkları kur\n",
        "                {\n",
        "                    'name': 'Install dependencies',\n",
        "                    'run': \"\"\"\n",
        "                    python -m pip install --upgrade pip\n",
        "                    pip install -r requirements.txt\n",
        "                    \"\"\"\n",
        "                },\n",
        "                # Adım 4: Linting (Kod stili kontrolü)\n",
        "                {\n",
        "                    'name': 'Lint with flake8',\n",
        "                    'run': 'flake8 src tests'\n",
        "                },\n",
        "                # Adım 5: Testleri çalıştır\n",
        "                {\n",
        "                    'name': 'Run tests with pytest',\n",
        "                    'run': 'pytest'\n",
        "                }\n",
        "            ]\n",
        "        }\n",
        "    }\n",
        "}\n",
        "\n",
        "# Python dictionary'sini YAML formatında dosyaya yaz\n",
        "with open(os.path.join(workflow_dir, \"python-ci.yml\"), \"w\") as f:\n",
        "    yaml.dump(ci_workflow_dict, f, default_flow_style=False, sort_keys=False)\n",
        "\n",
        "print(f\"'{os.path.join(workflow_dir, 'python-ci.yml')}' oluşturuldu.\")\n",
        "\n",
        "# YAML dosyasının içeriğini de görelim\n",
        "with open(os.path.join(workflow_dir, \"python-ci.yml\"), 'r') as f:\n",
        "    print(\"\\n--- YAML Dosya İçeriği ---\")\n",
        "    print(f.read())\n",
        "    \n",
        "# Temizlik\n",
        "shutil.rmtree(project_root)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Süreç Nasıl İşler?\n",
        "\n",
        "1.  Yukarıda oluşturulan dosya yapısını yerel `git` repository'nize commit'lersiniz.\n",
        "2.  Bu commit'i `git push origin main` komutu ile GitHub'daki remote repository'nize gönderirsiniz.\n",
        "3.  GitHub, `.github/workflows` klasöründe yeni bir YAML dosyası olduğunu algılar ve `push` olayını tetikler.\n",
        "4.  GitHub Actions, bir `ubuntu-latest` runner'ı (sanal makine) başlatır.\n",
        "5.  Runner, YAML dosyasındaki `steps` altında belirtilen adımları sırayla yürütür:\n",
        "    *   Kodunuzu sanal makineye indirir.\n",
        "    *   Python 3.9'u kurar.\n",
        "    *   `requirements.txt` içindeki `pandas`, `pytest`, `flake8` gibi kütüphaneleri kurar.\n",
        "    *   `flake8` ile kodunuzun stilini denetler. Bir hata bulursa (örn. kullanılmayan import), pipeline burada durur ve başarısız olur.\n",
        "    *   `pytest` komutunu çalıştırarak tüm testlerinizi yürütür. Bir test başarısız olursa, pipeline burada durur ve başarısız olur.\n",
        "6.  Tüm adımlar başarılı olursa, pipeline \"başarılı\" (yeşil tik) olarak işaretlenir. Başarısız olursa \"başarısız\" (kırmızı çarpı) olarak işaretlenir.\n",
        "\n",
        "Sonucu, GitHub repository'nizdeki \"Actions\" sekmesinden canlı olarak takip edebilirsiniz.\n",
        "\n",
        "### Alıştırma: CI Pipeline'ını Başarısız Hale Getirme\n",
        "\n",
        "Gerçek bir geliştirme senaryosunu deneyimlemek için CI pipeline'ını kasten bozalım.\n",
        "\n",
        "1.  Yerel projenizde `tests/test_data_cleaning.py` dosyasını açın.\n",
        "2.  Mevcut testi, kesinlikle başarısız olacak bir `assert` ifadesi ekleyerek değiştirin. Örneğin:\n",
        "    ```python\n",
        "    def test_clean_patient_data_removes_negative_age():\n",
        "        assert 1 == 2 # Bu test kesinlikle başarısız olacak\n",
        "    ```\n",
        "3.  Bu değişikliği commit'leyin ve GitHub'a `push`'layın.\n",
        "4.  GitHub'daki \"Actions\" sekmesine gidin ve yeni başlayan workflow'u izleyin.\n",
        "5.  \"Run tests with pytest\" adımında pipeline'ın hata vererek durduğunu ve kırmızı bir çarpı ile işaretlendiğini gözlemleyin. Hata detaylarına tıklayarak `pytest`'in hangi `assert` ifadesinde başarısız olduğunu görebilirsiniz.\n",
        "6.  Hatayı düzeltin, tekrar commit'leyip push'layın ve pipeline'ın bu sefer başarılı (yeşil tik) olduğunu görün."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Haftanın Özeti\n",
        "\n",
        "Bu hafta, projelerimize profesyonel bir kalite güvence katmanı ekledik.\n",
        "\n",
        "- **Sürekli Entegrasyon (CI)**, hataları erken yakalamak ve kod kalitesini sürekli yüksek tutmak için vazgeçilmez bir pratiktir.\n",
        "- **GitHub Actions**, bu otomasyonu doğrudan kodumuzun yaşadığı yerde, kolayca yapılandırmamızı sağlar.\n",
        "- Bir CI pipeline'ı, kod stili kontrolü (linting) ve otomatik testler gibi adımları içermelidir.\n",
        "- Başarılı bir CI süreci, bir projenin \"sağlıklı\" olduğunun en önemli göstergelerinden biridir.\n",
        "\n",
        "### Sonraki Adımlar\n",
        "\n",
        "Projemiz artık versiyon kontrollü, test edilebilir ve kalite kontrolü otomatize edilmiş durumda. Bu, bir modeli üretime almak için gereken sağlam altyapıdır. Bir sonraki hafta, **Hafta 6: Model Dağıtım Stratejileri**'nde, Hafta 4'te `MLflow` ile kaydettiğimiz bir modeli, dış dünyanın kullanabileceği bir API servisine nasıl dönüştüreceğimizi ve bunu `Docker` ile nasıl paketleyeceğimizi öğreneceğiz.\n",
        "\n",
        "---\n",
        "\n",
        "## **Hafta 6: Model Dağıtım Stratejileri (API & Konteynerler)**\n",
        "```python"
      ]
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "venv",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.11.2"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}