Supercharge your development with Python's fastest web framework "FastAPI"

Supercharge your development with Python's fastest web framework "FastAPI"

Summary of Content

  • Dive into FastAPI's core concepts by building a TODO app for managing user to-do lists. This example showcases key features like user authentication, CRUD operations (Create, Read, Update, Delete) for tasks, and more."
  • "Get hands-on with FastAPI through a practical TODO app. Users can register, add/manage tasks, and experience FastAPI's power in action.Imagine an app powered by just two heroes: User and TODO. SQLAlchemy equips them with superpowers to manage data!

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:

  • The decorator takes center stage, setting the tempo with a precise HTTP verb and URI, harmonizing with a schema that promises successful responses.
  • The request body steps forward, its movements guided by a strict UserCreate schema, ensuring each note of data hits the perfect pitch.
  • And in a dramatic twist, dependency injection enters the spotlight, extending a hand to the database, their duet revealing hidden secrets later in the FastAPI concerto.

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).


要查看或添加评论,请登录

David Segun的更多文章

社区洞察

其他会员也浏览了