משימה 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 תוך כדי.
כתבו route אחד וה‑tests שלו, והריצו את ה‑tests.
עשו 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המשיכו ל‑route הבא.
בזוג: branch לכל אחד, ו‑merge ל‑main
מחלקים את ה‑routes, כל אחד כותב את שלו ב‑branch משלו, וכל אחד עושה merge ל‑branch שלו לתוך main
בעצמו. עדיין בלי pull requests: הם מגיעים במשימה 3.
כתבו את החלק המשותף ביחד, מול מקלדת אחת: האפליקציה, האחסון והבדיקות של שדות הספר, שכל route משתמש בהם. עשו לו push ל‑
main.חלקו את ה‑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.
- issue #1, סטודנט A:
כל אחד עובד ב‑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עשו 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- הוסיפו את הקבצים בשמם, ולעולם לא
עשו 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.
- מי שעושה merge ראשון מוצא את
מי שעושה 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- שניכם הוספתם routes באותו קובץ, ולכן
שניכם עושים pull ל‑
main, ומריצים את ה‑tests: עכשיו לכל אחד יש את כל ה‑API.
- אם
git pushל‑mainנדחה כי השותף עשה push קודם, הריצוgit pull, ואז push שוב.
כתבו את ה‑API
ה‑API מנהל רשימת קריאה: הספרים שקראתם או שאתם מתכוונים לקרוא.
- resource אחד, ה‑book, וחמש הפעולות עליו: יצירה, רשימה, קריאה, עדכון, מחיקה.
- כל גוף של request ושל response הוא JSON.
- אחסון: TODO: להחליט — בזיכרון, SQLite, או PostgreSQL ממשימה 8.
הספר
| שדה | type | מי קובע אותו | כלל |
|---|---|---|---|
id | integer | ה‑API, ביצירה | ייחודי, לעולם לא ממוחזר, לעולם לא משתנה |
title | string | ה‑client | חובה, 1–200 תווים |
author | string | ה‑client | חובה, 1–100 תווים |
year | integer | ה‑client | רשות; אם נשלח, מ‑0 ועד השנה הנוכחית |
read | boolean | ה‑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": [...]}, עם רשימה של כל השדות שנכשלו; השאירו אותו כך.
- גם גוף שאינו JSON הוא
- ספר שחסר — עליכם לענות עליו: זרקו את ה‑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.