Supercharge your development with Python's fastest web framework "FastAPI"
Summary of Content
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
lname = Column(String)
fname = Column(String)
email = Column(String, unique=True, index=True)
todos = relationship("TODO", back_populates="owner", cascade="all, delete-orphan")
class TODO(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
text = Column(String, index=True)
completed = Column(Boolean, default=False)
owner_id = Column(Integer, ForeignKey("users.id"))
owner = relationship("User", back_populates="todos")
To ensure seamless interaction between SQLAlchemy and the database, we'll create a configuration file that specifies connection details."
Let's provide SQLAlchemy with the necessary connection instructions for the database by defining a configuration file.
import os
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = os.environ['SQLALCHEMY_DATABASE_URL']
engine = create_engine(
SQLALCHEMY_DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
Revealing the Magic of Pydantic Type
Before we set sail on the high seas of request handlers, let's first slay the Data Dragon guarding our API. This fearsome beast thrives on messy, inconsistent inputs and outputs, hindering the smooth flow of our digital galleons. But fear not, brave coders, for we wield the mighty Pydantic shield!
With Pydantic models, we forge the blueprints for our incoming and outgoing data, specifying their structure and expected contents. These models act as our data wranglers, automatically converting and validating each byte, ensuring clean inputs and pristine outputs. Remember, these models are like bouncers – only data that matches the dress code gets entry!
Now, consider the noble User, often arriving in two guises: the wide-eyed Newbie seeking to join the TODO tribe, and the Seasoned Warrior returning to conquer their tasks. While both involve User data, their forms differ greatly. The Newbie, eager to prove their worth, bares all - name, email, and even passwords. The Warrior, a seasoned veteran, needs only flash their badge (email and password) to gain entry.
Therefore, we craft two Pydantic models: the grand UserCreate, a detailed dossier for the aspiring TODO champion, and the minimalist UserBase, a simple token of recognition for the returning hero. With these models as our gatekeepers, only valid data shall pass, making our API a fortress of order and clarity.
So, let's charge into battle, wielding Pydantic like a shining blade! Define the UserCreate schema, a detailed map for signup data, and the UserBase schema, a victory banner for successful registration. With these models in place, our API shall stand strong, its data tamed and flowing freely, a testament to the power of preparation and Pydantic prowess!
from pydantic import BaseModel
from pydantic import EmailStr
class UserBase(BaseModel):
email: EmailStr
class UserCreate(UserBase):
lname: str
fname: str
password: str
Let's craft user details like a master tailor, ensuring a perfect fit with Pydantic's precision tools! We'll stitch together names and passwords with exacting string constraints—no loose threads allowed! Then, we'll unfurl a blueprint for TODO items, ready to capture life's tasks with clarity and structure."
"Imagine a well-organized closet where every item has its designated space. That's the harmony we'll achieve with our TODO schema, designed to tame even the wildest task lists!
class TODOCreate(BaseModel):
text: str
completed: bool
Sprouting a branch on the task tree: To nurture ongoing progress, we'll graft a new structure onto the existing plan.
class TODOUpdate(TODOCreate):
id: int
Schema scaffolding complete! Now, let's unleash the data wranglers - the request handlers - to flex their schema muscles and transform, validate, and conquer!
Registering new Users
No spacesuit, no space travel! Before we unleash users into the cosmic unknown, we need to make sure they're properly suited up. The UserCreate and UserBase schemas are our cosmic outfitters, stitching together accounts with care, while our first request handler acts as the friendly gatekeeper, ensuring no unauthorized stowaways sneak aboard.
@app.post("/api/users", response_model=schemas.User)
def signup(user_data: schemas.UserCreate, db: Session = Depends(get_db)):
"""add new user"""
user = crud.get_user_by_email(db, user_data.email)
if user:
raise HTTPException(status_code=409,
detail="Email already registered.")
signedup_user = crud.create_user(db, user_data)
return signedup_user
Imagine a skilled orchestra conductor, baton poised, ready to unleash a symphony of code. With a few graceful strokes, they direct a whirlwind of actions:
In this compact composition, each element plays a vital role, blending seamlessly to create a powerful API performance.
Stay tuned for the next movement, where we'll explore the captivating melodies of dependency injection and its harmonious interactions with the database
Data Defenders on Patrol: "API Security"
Attention developers! Operation "Fortress FastAPI" commences. Your objective: build ironclad walls around user data. Tools at your disposal?
领英推荐
Passlib's cryptographic vault for password encryption, and JWT's impenetrable force shields for access control. We'll craft digital keys so secure, even the sneakiest hackers will be left staring at stardust. Prepare to unleash the fury of secure authentication!
from passlib.context import CryptContext
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
def verify_password(plain_password, hashed_password):
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password):
return pwd_context.hash(password)
def authenticate_user(db, email: str, password: str):
user = crud.get_user_by_email(db, email)
if not user:
return False
if not verify_password(password, user.hashed_password):
return False
return user
Unlocking the vault of secure access: Let's dive into the magic of JWTs, crafting digital keys that unlock user identities and decode hidden credentials. Buckle up, for we're defining functions to wield this cryptographic power!
# install PyJWT
import jwt
from fastapi.security import OAuth2PasswordBearer
SECRET_KEY = os.environ['SECRET_KEY']
ALGORITHM = os.environ['ALGORITHM']
def create_access_token(*, data: dict, expires_delta: timedelta = None):
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=15)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
return encoded_jwt
def decode_access_token(db, token):
credentials_exception = HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
try:
payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
email: str = payload.get("sub")
if email is None:
raise credentials_exception
token_data = schemas.TokenData(email=email)
except PyJWTError:
raise credentials_exception
user = crud.get_user_by_email(db, email=token_data.email)
if user is None:
raise credentials_exception
return user
Unlock the Galaxy Gates: "Upon login, grant access with keys forged from stardust(Tokens)"
Brace yourselves, developers! We're about to crack the code to secure access like a digital Fort Knox. Today's mission: building the Login endpoint, armed with the mighty OAuth2 password flow. Buckle up, hand over your email and password – if they pass the database gauntlet, a coveted JSON web token awaits! But beware, only the valiant shall prevail!
@app.post("/api/token", response_model=schemas.Token)
def login_for_access_token(db: Session = Depends(get_db),
form_data: OAuth2PasswordRequestForm = Depends()):
"""generate access token for valid credentials"""
user = authenticate_user(db, form_data.username, form_data.password)
if not user:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Bearer"},
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = create_access_token(data={"sub": user.email},
expires_delta=access_token_expires)
return {"access_token": access_token, "token_type": "bearer"}
Plugging Leaks in the System: "Securing Endpoints with Dependency Injection's Magic Sauce"
Once upon a digital frontier, logins blossomed into shiny tokens, passports to secret API kingdoms. Users clutched these gems in their local storage, whispering them to hidden gateways whenever they sought treasures beyond the login wall. Each protected endpoint, a vigilant guardian, scrutinized these tokens, revealing the requester's true identity. But this grand authentication dance wasn't a solo act. It demanded a grand orchestrator, a dependency weaving its magic across every protected domain. FastAPI, the API whisperer, conjured the "get_current_user" spell, a gateway to user secrets. Yet, a tangled web awaited: database whispers, OAuth2PasswordBearer's secret handshakes. Fear not, for with dependency chains, a tapestry of power unfurled, empowering "get_current_user" to unravel any knot and unveil the true king behind the token.
def get_db():
"""provide db session to path operation functions"""
try:
db = SessionLocal()
yield db
finally:
db.close()
def get_current_user(db: Session = Depends(get_db),
token: str = Depends(oauth2_scheme)):
return decode_access_token(db, token)
@app.get("/api/me", response_model=schemas.User)
def read_logged_in_user(current_user: models.User = Depends(get_current_user)):
"""return user settings for current user"""
return current_user
API Access Unleashes CRUD Power: Logged-in Users Command the TODOverse
Before we unleash our path operation heroes to conquer the land of CRUD (Create, Read, Update, Delete), let's assemble their trusty companions, the helper functions! These silent but mighty sidekicks will handle the behind-the-scenes database magic, ensuring our heroes' every move is swift and precise.
Think of them as the Pathfinders, Data Whisperers, Updaters of All Things, and Banishers of Outdated Information. They'll be the unseen force behind every TODO task, ready to make our CRUD dreams a reality.
def create_todo(db: Session, current_user: models.User, todo_data: schemas.TODOCreate):
todo = models.TODO(text=todo_data.text,
completed=todo_data.completed)
todo.owner = current_user
db.add(todo)
db.commit()
db.refresh(todo)
return todo
def update_todo(db: Session, todo_data: schemas.TODOUpdate):
todo = db.query(models.TODO).filter(models.TODO.id == id).first()
todo.text = todo_data.text
todo.completed = todo.completed
db.commit()
db.refresh(todo)
return todo
def delete_todo(db: Session, id: int):
todo = db.query(models.TODO).filter(models.TODO.id == id).first()
db.delete(todo)
db.commit()
def get_user_todos(db: Session, userid: int):
return db.query(models.TODO).filter(models.TODO.owner_id == userid).all()
These database functions are incorporated into the following REST endpoints
@app.get("/api/mytodos", response_model=List[schemas.TODO])
def get_own_todos(current_user: models.User = Depends(get_current_user),
db: Session = Depends(get_db)):
"""return a list of TODOs owned by current user"""
todos = crud.get_user_todos(db, current_user.id)
return todos
@app.post("/api/todos", response_model=schemas.TODO)
def add_a_todo(todo_data: schemas.TODOCreate,
current_user: models.User = Depends(get_current_user),
db: Session = Depends(get_db)):
"""add a TODO"""
todo = crud.create_meal(db, current_user, meal_data)
return todo
@app.put("/api/todos/{todo_id}", response_model=schemas.TODO)
def update_a_todo(todo_id: int,
todo_data: schemas.TODOUpdate,
current_user: models.User = Depends(get_current_user),
db: Session = Depends(get_db)):
"""update and return TODO for given id"""
todo = crud.get_todo(db, todo_id)
updated_todo = crud.update_todo(db, todo_id, todo_data)
return updated_todo
@app.delete("/api/todos/{todo_id}")
def delete_a_meal(todo_id: int,
current_user: models.User = Depends(get_current_user),
db: Session = Depends(get_db)):
"""delete TODO of given id"""
crud.delete_meal(db, todo_id)
return {"detail": "TODO Deleted"}
Crafting Comprehensive Tests for Our Application
Let's write a few tests for our TODO API. FastAPI provides a TestClient class that's based on the popular Requests library, and we can run the tests with Pytest.
To make sure only logged-in users can create a TODO, we can write something like this:
from starlette.testclient import TestClient
from .main import app
client = TestClient(app)
def test_unauthenticated_user_cant_create_todos(): todo=dict(text="run a mile", completed=False)
response = client.post("/api/todos", data=todo)
assert response.status_code == 401
Prepare for battle! This test throws down the gauntlet at the login endpoint, demanding valid credentials in exchange for a JWT victory token.
def test_user_can_obtain_auth_token():
response = client.post("/api/token", data=good_credentials)
assert response.status_code == 200
assert 'access_token' in response.json()
assert 'token_type' in response.json()
The main takeaways
Remember that feeling when your to-do list morphs into a scribbled post-it note graveyard? The deadlines looming like storm clouds, the tasks multiplying like dust bunnies under the couch? Yeah, we've all been there.
But fret not, weary warrior! Enter FastAPI, your digital Swiss Army knife for conquering chaos. This ain't your grandma's framework – it's Python on steroids, laced with the magic of type hints and dependency injection.
Think of data validation as your personal bouncer, kicking out rogue tasks before they wreak havoc. And role-based access? Picture a moat filled with alligators guarding your most precious to-dos. Performance? Faster than a sugar rush on a playground. Documentation? Clearer than a freshly cleaned window.
So ditch the paper scraps and embrace the future. With FastAPI, you're not just writing code, you're wielding a lightsaber against the forces of to-do list tyranny. Go forth and slay your tasks, one by one, and remember – with FastAPI, the fun never stops (unless you run out of coffee, of course).