משימה 1: האפליקציה וה‑repository שלה

במשימה הזאת יוצרים את ה‑repository ואת האפליקציה שה‑pipeline יעביר.

צרו את ה‑repository

  • ב‑GitHub, repository חדש ציבורי (public): ‏<you>/devops-project.
    • זה ה‑portfolio שלכם: מעסיק פוטנציאלי יכול לקרוא את הקוד, את ה‑pipeline ואת כל run.
    • משימה 3 מגינה על main עם branch protection rule, ובתוכניות החינמיות של GitHub זה עובד רק ב‑repository ציבורי.
    • repository פרטי צריך תוכנית בתשלום, GitHub Pro או Team.
  • בזוג: אחד מכם יוצר אותו, ואז מוסיף את השני ב‑Settings → Collaborators עם הרשאת write.
  • עשו לו clone, ו‑push של commit ראשון ל‑main: עד משימה 3 שניכם עושים push ישר ל‑main.

חלקו את העבודה ב‑git

בחרו את הבלוק של צורת העבודה שלכם, ועקבו רק אחרי הצעדים שלו.

לבד: commit ל‑main

עד משימה 3 עובדים על main עצמו: commit אחד לכל route, ו‑push תוך כדי.

  1. כתבו route אחד וה‑tests שלו, והריצו את ה‑tests.

  2. עשו commit ו‑push. הוסיפו את הקבצים בשמם, אלה שה‑commit הזה עוסק בהם, ולעולם לא git add .: הוא היה סוחף פנימה כל דבר אחר שיש בתיקייה, ‏.venv/ או __pycache__/.

    1$ git status
    2$ git add app/main.py tests/test_books.py
    3$ git commit -m "Add POST /books"
    4$ git push
    
  3. המשיכו ל‑route הבא.

בזוג: branch לכל אחד, ו‑merge ל‑main

מחלקים את ה‑routes, כל אחד כותב את שלו ב‑branch משלו, וכל אחד עושה merge ל‑branch שלו לתוך main בעצמו. עדיין בלי pull requests: הם מגיעים במשימה 3.

  1. כתבו את החלק המשותף ביחד, מול מקלדת אחת: האפליקציה, האחסון והבדיקות של שדות הספר, שכל route משתמש בהם. עשו לו push ל‑main.

  2. חלקו את ה‑routes לשני issues ב‑GitHub, כל אחד משויך לאחד מכם, כל route עם ה‑tests שלו:

    • issue #1, סטודנט A: ‏POST /books, ‏GET /books ו‑GET /books/<id>;
    • issue #2, סטודנט B: ‏PUT /books/<id>, ‏DELETE /books/<id> ו‑GET /healthz.
  3. כל אחד עובד ב‑branch שנקרא על שם ה‑issue, כמו ש התוכנית קוראת להם: ‏feature/1-create-and-read ו‑feature/2-update-and-delete.

    1$ git switch main
    2$ git pull
    3$ git switch -c feature/1-create-and-read
    
  4. עשו commit תוך כדי, ו‑push ל‑branch, כדי שהשותף יראה אותו.

    • הוסיפו את הקבצים בשמם, ולעולם לא git add .: הוא היה סוחף פנימה כל דבר אחר שיש בתיקייה.
    • שמרו את ה‑tests שלכם בקובץ משלכם, ‏tests/test_create_read.py או tests/test_update_delete.py, כך ששניכם משנים רק קובץ משותף אחד.
    1$ git status
    2$ git add app/main.py tests/test_create_read.py
    3$ git commit -m "Add POST /books"
    4$ git push -u origin feature/1-create-and-read
    
  5. עשו merge לתוך main כשה‑routes שלכם וה‑tests שלהם עוברים:

    1$ git switch main
    2$ git pull
    3$ git merge feature/1-create-and-read
    4$ git push
    
    • מי שעושה merge ראשון מוצא את main כמו שהיה בצעד 1: ‏git מקדם את main, בלי conflict.
  6. מי שעושה merge שני מריץ את אותן הפקודות, ו‑git pull מביא קודם את ה‑routes של השותף.

    • שניכם הוספתם routes באותו קובץ, ולכן git merge עלול לעצור שם עם conflict.
    • פתחו את הקובץ, השאירו את ה‑routes של שני הצדדים, ומחקו את סימני ה‑conflict (‏<<<<<<<, ‏=======, ‏>>>>>>>).
    • הריצו את כל ה‑tests, של שני הסטודנטים, ואז סמנו את הקובץ כפתור בכך שתוסיפו אותו, סיימו את ה‑merge ועשו push:
    1$ git add app/main.py
    2$ git commit
    3$ git push
    
  7. שניכם עושים pull ל‑main, ומריצים את ה‑tests: עכשיו לכל אחד יש את כל ה‑API.

  • אם git push ל‑main נדחה כי השותף עשה push קודם, הריצו git pull, ואז push שוב.

כתבו את ה‑API

ה‑API מנהל רשימת קריאה: הספרים שקראתם או שאתם מתכוונים לקרוא.

  • resource אחד, ה‑book, וחמש הפעולות עליו: יצירה, רשימה, קריאה, עדכון, מחיקה.
  • כל גוף של request ושל response הוא JSON.
  • אחסון: TODO: להחליט — בזיכרון, SQLite, או PostgreSQL ממשימה 8.

הספר

שדהtypeמי קובע אותוכלל
idintegerה‑API, ביצירהייחודי, לעולם לא ממוחזר, לעולם לא משתנה
titlestringה‑clientחובה, 1–200 תווים
authorstringה‑clientחובה, 1–100 תווים
yearintegerה‑clientרשות; אם נשלח, מ‑0 ועד השנה הנוכחית
readbooleanה‑clientרשות; false כשלא נשלח

ספר כמו שה‑API שולח אותו:

1{"id": 1, "title": "Dune", "author": "Frank Herbert", "year": 1965, "read": false}

ה‑routes

method ו‑pathמה הוא עושהנשלחמתקבל
POST /booksיוצר ספרספר בלי id‏201 והספר החדש, עם ה‑id שלו
GET /booksמציג את כל הספריםכלום‏200 ורשימת ספרים, הישן ראשון
GET /books/<id>קורא ספר אחדכלום‏200 והספר
PUT /books/<id>מחליף ספר אחדספר שלם בלי id‏200 והספר כמו שנשמר
DELETE /books/<id>מוחק ספר אחדכלום‏204, בלי גוף
GET /healthzאומר שהאפליקציה פועלתכלום‏200 ו‑{"status": "ok"}

יצירת ספר

נשלח: השדות של ה‑client; ה‑API קובע את ה‑id.

1POST /books HTTP/1.1
2Content-Type: application/json
3
4{"title": "Dune", "author": "Frank Herbert", "year": 1965}

מתקבל: ‏201 Created, header בשם Location עם ה‑URL של הספר החדש, והספר כמו שנשמר, עם read ממולא.

1HTTP/1.1 201 Created
2Location: /books/1
3Content-Type: application/json
4
5{"id": 1, "title": "Dune", "author": "Frank Herbert", "year": 1965, "read": false}

רשימת הספרים

מתקבל: ‏200 OK ורשימת JSON, הישן ראשון; רשימה ריקה [] כשאין ספרים, ולעולם לא 404.

1HTTP/1.1 200 OK
2Content-Type: application/json
3
4[{"id": 1, "title": "Dune", "author": "Frank Herbert", "year": 1965, "read": false},
5 {"id": 2, "title": "Emma", "author": "Jane Austen", "year": 1815, "read": true}]

קריאת ספר אחד

נשלח: ‏GET /books/1. מתקבל: ‏200 OK והספר הזה, באותה צורה כמו ביצירה.

עדכון ספר

  • ‏PUT מחליף את כל הספר: כל שדה חובה נשלח שוב, ו‑year או read שחסרים חוזרים לערך ברירת המחדל שלהם.
  • ה‑id מגיע מה‑URL, ו‑id בגוף ה‑request לא נלקח בחשבון.
1PUT /books/1 HTTP/1.1
2Content-Type: application/json
3
4{"title": "Dune", "author": "Frank Herbert", "year": 1965, "read": true}

מתקבל: ‏200 OK והספר כמו שהוא שמור עכשיו.

מחיקת ספר

נשלח: ‏DELETE /books/1. מתקבל: ‏204 No Content, בלי גוף. ה‑id של הספר לעולם לא ניתן לספר אחר.

השגיאות

כל שגיאה היא status code וגוף JSON שאומר מה השתבש.

מקרהstatus
אין ספר עם ה‑id הזה: קריאה, עדכון או מחיקה404 Not Found
הגוף אינו JSON, או ששדה חסר, מ‑type לא נכון או מחוץ לטווח שלו422 Unprocessable Content
1HTTP/1.1 404 Not Found
2Content-Type: application/json
3
4{"detail": "no book with id 7"}
  • ה‑status codes הם החוזה: ה‑tests, ה‑integration tests של ה‑pipeline במשימה 8 וה‑smoke test במשימה 6 בודקים אותם, כך ששני המסלולים שומרים עליהם.
  • המפתח היחיד בגוף הוא detail, כמו ש‑FastAPI כותב אותו; הטקסט שלו לבחירתכם.
אפשרות 1: Flask
  • route הוא function רגילה לכל method ו‑path.
  • את הבדיקות מטבלת השדות כותבים בעצמכם: Flask נותן לכם את ה‑JSON המפוענח ותו לא.
  • Flask עונה 422 רק כשאתם מחזירים אותו, עם גוף השגיאה, מהבדיקות שלכם.
  • על גוף שאינו JSON, ‏Flask עונה 400 מעצמו: קראו את הגוף במצב שמחזיר כלום במקום זה, וענו 422.
אפשרות 2: FastAPI
  • הספר הוא Pydantic model: טבלת השדות, כתובה כ‑types ומגבלות.
  • FastAPI בודק כל גוף מול ה‑model ועונה 422 מעצמו.
    • גם גוף שאינו JSON הוא 422.
    • גוף השגיאה שלו הוא {"detail": [...]}, עם רשימה של כל השדות שנכשלו; השאירו אותו כך.
  • ספר שחסר — עליכם לענות עליו: זרקו את ה‑HTTP exception של FastAPI עם 404.
  • ה‑model גם מצייר את דף ה‑OpenAPI, ב‑/docs: נסו שם כל route.

הוסיפו health endpoint

  • ‏GET /healthz עונה 200 ו‑{"status": "ok"}.
  • ה‑deployment שואל אותו אם האפליקציה פועלת, ממשימה 6: הוא חייב לענות מהר, ולא לגעת באחסון.

כתבו את ה‑tests

  • ‏pytest, עם ה‑test client של ה‑framework, ששולח requests לאפליקציה בלי שרת.
  • test אחד לכל שורה בטבלת ה‑routes, ואחד לכל שורה בטבלת השגיאות.
  • מבנה: ‏app/, ‏tests/, ‏requirements.txt, ‏requirements-dev.txt.

הריצו את זה על המחשב שלכם

  • הריצו את ה‑API, וקראו לכל route עם curl.
  • הריצו את ה‑tests.

Translations: