לדלג לתוכן

1.1 fastapi + postman הרצאה

פיתוח api

בבניית api עלינו להכיר מספר מושגים בסייסים
- הrouteים: הגדרה של כל הפניות האפשריות שניתן לבצע לapi שלנו- לדוגמה,
בקשת post ל api/comment/
בקשת get ל api/products/
- הschmaות: סכמות הן בדרך כלל מחלקות שמגדירות בדיוק איזה קלט המשתמש צריך להעביר לroute מסוים כדי שהroute יוכל לפעול כראוי. (נניח איזה סוג מידע route אמור לקבל בpost כדי לפעול כראוי)
- הcontrollerים: הקוד שמבצע מאחורי הקלעים פעולות אל מול מסד הנתונים- בדרך כלל נראה שהקוד של הrouteים פונה לקוד של הcontrollerים כדי לבצע פעולות אל מול מסדי הנתונים.
- הmodelים: קוד שמגדיר את כל סוגי האובייקטים/טבלאות שנמצאות במסד הנתונים, המודלים מאפשרים לנו בצורה נכונה לגשת למסד הנתונים.
Pasted image 20260127160008.png
פירוט של התהליך של בקשת API עד לתשובה מפורק לשלבים (הסתכלו על התמונה)
נניח שהבקשה היא בקשת GET /api/hotels ומטרתה להציג למשתמש את כל המלונות שבמערכת.
1. הAPI מקבל בקשת http מהלקוח GET /api/hotels ומעביר אותה ישירות לroute שאחראי לטפל בסוג בקשה זו.
2. הroute בודק שתוכן הבקשה תקין על פי הschma שהוגדרה לו (במקרה הזה מדובר בבקשת GET בלי תוכן, אז אין מה לבדוק)
3. הroute מבקש מהcontroller שאחראי על המלונות להביא רשימה של כל המלונות.
4. הcontroller ניגש לmodel של מלון במסד הנתונים, ועושה שליפה על כל המלונות דרך המודל.
5. הmodel מדבר עם מסד הנתונים ומביא את כל המידע.
6. הmodel מחזיר את המידע לcontroller שמחזיר לroute שמחזיר למשתמש בתשובת http תוכן json עם רשימה של כל המלונות.

כדי לבנות api אפשר להשתמש בכל שפת תכנות שנרצה, אלמד בהרצאה הבאה כיצד לפתח api עם שפת פייתון באמצעות ספריית fastapi.

בניית API עם FastAPI

כדי להתחיל, נתקין 3 סיפריות.
- הספרייה fastapi - ספרייה שמאפשרת לנו לבנות api
- הספרייה uvicorn - ספרייה שמאפשרת לנו להריץ שרת http עם fastapi
- הספרייה pydantic - ספרייה שמאפשרת לנו להגדיר modelים וschemaות בצורה נוחה

pip install fastapi uvicorn pydantic

יצירת הקובץ הראשי

ניצור קובץ בשם main.py ונכתוב בו את הקוד הבסיסי הבא:

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def home():
    return {"message": "ברוך הבא ל-API הראשון שלך!"}

הקוד הזה יוצר אפליקציית FastAPI ומגדיר route אחד – הבקשה GET / מחזירה הודעת JSON.

הרצת השרת

נריץ את הפקודה:

uvicorn main:app --reload --port 8000

זה ירים לנו את שרת הbackend על בהאזנה על פורט 8000 על המחשב שלנו.

לאחר מכן נוכל לגשת לדפדפן ולכתוב:

http://127.0.0.1:8000/

אם תשימו לב, אנחנו ניגשים לכתובת אייפי 127.0.0.1, זה placeholder לכתובת של המחשב שלנו עצמו.
ובגלל שהשרת רץ על המחשב שלנו, אז כדי לפנות אילו עם הדפדפן שלנו נצטרך לפנות לכתובת הזו.

ונראה את התגובה:

{"message": "ברוך הבא ל-API הראשון שלך!"}

תיעוד אוטומטי (Swagger)

חלק מההיתרונות של FastAPI היא היצירה האוטומטית של ממשק גרפי שמציג את כל ה־Routes שלכם בצורה אינטראקטיבית.
כדי לראות את זה, ניגש לכתובת:

http://127.0.0.1:8000/docs

נראה דף תיעוד אינטראקטיבי שמאפשר להריץ בקשות ישירות מהדפדפן!

יצירת route נוסף

נוסיף עוד route שמחזיר מידע על משתמש לפי מזהה (ID):

@app.get("/user/{user_id}")
def get_user(user_id: int):
    return {"user_id": user_id, "name": "Amit", "role": "admin"}

אם נגלוש לכתובת:

http://127.0.0.1:8000/user/5

נקבל:

{"user_id": 5, "name": "Amit", "role": "admin"}

ולעומת זאת אם נגלוש ל-

http://127.0.0.1:8000/user/10

נקבל:

{"user_id": 10, "name": "Amit", "role": "admin"}

שליחת נתונים עם POST

עם בקשות post נוכל לקבל מידע מהלקוח (למשל טופס יצירת משתמש).
נשתמש במודול BaseModel של pydantic כדי להגדיר את הסכמות (הקלט שאנחנו נקבל מהמשתמש לroute):

from pydantic import BaseModel

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

@app.post("/user")
def create_user(user: User):
    return {"message": "משתמש נוצר בהצלחה", "user": user}

כאשר User היא הסכמה שיצרנו. כעט הroute שלנו (/user) יודע לקבל רק מידע מוגדר לפי הסכמה.

נוכל לבצע את בקשת הpost באמצעות הכלי postman.
https://www.postman.com/downloads/
אחרי שתסיימו להתקין, פשוט תשימו את הip של השרת (במקרה הזה זה localhost כי אנחנו מריצים את השרת על המחשב שלנו ולא על שרת מרוחק) והפורט שבחרנו (במקרה הזה 8000), אחרכך נוכל לבחור את הנתיב (במקרה הזה /user) ואז בגוף הבקשה, תוכלו לשים את הjson של הuser שנרצה לשלוח לapi.

אם נשלח בקשה מסוג POST לכתובת /user עם גוף הבקשה:

{
  "name": "Shir",
  "email": "shir@example.com",
  "age": 21
}

נקבל:

{
  "message": "משתמש נוצר בהצלחה",
  "user": {
    "name": "Shir",
    "email": "shir@example.com",
    "age": 21
  }
}

באמצעות המחלקה user שיצרנו, קבענו סכמה שנשתמש בה כדי לוודא שאנחנו מקבלים קלט תקין מהמשתמש בapi שלנו.

חיבור FastAPI למסד נתונים

נשתמש ב־SQLAlchemy כדי לעבוד מול מסד נתונים בצורה נוחה, בלי לכתוב SQL ידנית. (עבודה עם orm (כמו שלמדנו בקורס תכנות בסיסי))

נתחיל מהתקנת הספרייה:

pip install sqlalchemy

יצירת קובץ חיבור למסד הנתונים

ניצור תיקייה בשם database/ ובתוכה קובץ בשם connection.py:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, declarative_base

DATABASE_URL = "sqlite:///./test.db"

engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

כעת יצרנו מנגנון שמתחבר למסד נתונים מקומי בשם test.db. מסוג SQLite

יצירת מודל (Model)

ניצור תיקייה בשם models/ ובתוכה קובץ בשם user_model.py:

from sqlalchemy import Column, Integer, String
from database.connection import Base

class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String)
    email = Column(String)
    age = Column(Integer)

זהו מודל שמייצג טבלת משתמשים עם עמודות: מזהה, שם, אימייל וגיל.
בעתיד נוכל להשתמש במודל הזה כדי לגשת למידע ממסד הנתונים בטבלה של Users

מבנה מקצועי של פרויקט FastAPI

נבנה את המבנה הבא:

project/
├── main.py
├── database/
│   └── connection.py
├── models/
│   └── user_model.py
├── controllers/
│   └── users_controller.py
└── routes/
    └── users.py

controller – הלוגיקה של המשתמשים

בcontroller אנחנו נשתמש בmodel שבנינו כדי לממש 2 פונקציות, פונקציה שאחראית לשלוף על כל הuser-ים בטבלה, ופונקציה שאחראית להוסיף user לטבלה.
controllers/users_controller.py:

from sqlalchemy.orm import Session
from models.user_model import User

def get_users(db: Session):
    return db.query(User).all()

def create_user(db: Session, name: str, email: str, age: int):
    user = User(name=name, email=email, age=age)
    db.add(user)
    db.commit()
    db.refresh(user)
    return user

למעשה כתבנו controller שיודע לבצע פעולות במסד הנתונים באמצעות הmodel-ים בהנחה והוא מקבל Session (אובייקט שמהווה חיבור לDB).


הrouteים של הapi

לפני שאנחנו כותבים את הRoute-ים אנחנו צריכים ליצור חיבור למסד הנתונים ונעשה זאת ככה:

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

עלינו לוודא שבכל route שלנו, הקוד שלנו באמת יוצר חיבור עם מסד הנתונים (שלא תהיה שגיאה), בfastapi יש פונקציה מובנית בשם 'depends' שמטפלת בדיוק בזה.
כפרמטר לroute-ים נוכל להוסיף שהroute תלוי (באמצעות depends) בתוצאה של פונקציה מסוימת.
@router.get("/users")
def read_users(db: Session = Depends(get_db)):
    return get_users(db)

ככה אנחנו יכולים לוודא שבroute יהיה לנו גישה לאובייקט db, ושבלי תוצאה מget_db הפונקציה תקרוס.

הנה הקובץ route.
routes/users.py:

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from database.connection import SessionLocal
from controllers.users_controller import get_users, create_user

router = APIRouter()

class UserSchema(BaseModel):
    name: str
    email: str
    age: int

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

@router.get("/users")
def read_users(db: Session = Depends(get_db)):
    return get_users(db)

@router.post("/users")
def add_user(user: UserSchema, db: Session = Depends(get_db)):
    existing = db.query(create_user.__annotations__['db'].query(UserSchema)).filter_by(email=user.email).first()
    if existing:
        raise HTTPException(status_code=400, detail="משתמש עם אימייל זה כבר קיים")
    return create_user(db, user.name, user.email, user.age)

למעשה הקוד בסוף רק מגדיר את המשתנה router, שיש לו את כל הroute-ים שלנו בפרויקט.

main.py

from fastapi import FastAPI
from database.connection import Base, engine
from models.user_model import User
from routes import users

Base.metadata.create_all(bind=engine)

app = FastAPI()
app.include_router(users.router)

בקובץ main אנחנו קוראים לBase.metadata.create_all(bind=engine) כדי ליצור את כל הטבלאות אם הם לא נוצרו עדיין
ואנחנו מאתחלים אובייקט של FastAPI ומביאים לו את הrouter שבנינו עם כל הroute-ים.

ככה בנינו פרויקט, שבו אנחנו מאתחלים מסד נתונים + כל הroute-ים של הapi.
וכאשר כל ניתוב מבצע פעולות שמוגדרות בcontroller-ים
כאשר הפונקציות של הcontroller-ים, משתמשות במודולים כדי לגשת למסד נתונים.
זה העיצוב הנהוג לכתיבת api מודרני.


טיפול בשגיאות (Error Handling)

ב־FastAPI אפשר לטפל בשגיאות בצורה נוחה בעזרת HTTPException.

דוגמה:

from fastapi import HTTPException

@app.get("/product/{id}")
def get_product(id: int):
    product = None  # נניח שהמוצר לא נמצא
    if not product:
        raise HTTPException(status_code=404, detail="המוצר לא נמצא")
    return product

כמו כן, ניתן להגדיר global error handler – שיתפוס כל שגיאה במערכת ויחזיר תשובה יפה למשתמש:

from fastapi.responses import JSONResponse
from fastapi.requests import Request

@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
    return JSONResponse(
        status_code=500,
        content={"error": str(exc), "message": "שגיאה כללית בשרת"}
    )