Back in the venv appendix, we talked about pip install-ing packages other people have written. PyPI - the Python Package Index - hosts hundreds of thousands of these, but a small handful show up in an enormous share of real-world Python projects. Let’s meet four of the most popular: requests, pydantic, SQLAlchemy, and FastAPI.
We’re not going to make you an expert in any of these - that’s well beyond a beginner’s book. The goal here is just recognition: when you see one of these names later in your coding life, you’ll already know roughly what it does and why people reach for it.
Almost every program eventually needs to fetch data from somewhere out on the internet - checking the weather, pulling stock prices, hitting some company’s API. Python’s built-in tools for this are clunky. requests makes it feel almost as simple as print().
pip install requestsimport requests
response = requests.get("https://api.github.com")
print(response.status_code) # -> 200, meaning "OK, success!"
print(response.json()) # -> a dictionary, parsed straight from the responseThat .get() call reaches out over the internet and fetches whatever’s at that address - the same basic thing your web browser does every time you visit a page, just done from your own code. .status_code tells you whether it worked (200 means success; you’ll also see 404 for "not found," and others), and .json() conveniently turns the response straight into a Python dictionary, ready to use.
Sending data works about the same way, using .post() instead of .get():
new_player = {"name": "Sam", "health": 100}
response = requests.post("https://example.com/players", json=new_player)
print(response.status_code) # -> 201, meaning "Created!"If requests has one core lesson, it’s this: your program isn’t limited to the data sitting on your own computer. It can reach out and talk to almost anything on the web.
Remember type hints, from a few chapters back? We mentioned that Python doesn’t actually enforce them - they’re just polite suggestions. pydantic is a package built entirely around fixing that: it uses your type hints to actually validate data, at the moment your program receives it.
pip install pydanticYou describe your data’s shape once, as a class, using the same type hints you already know:
from pydantic import BaseModel
class Player(BaseModel):
name: str
health: int = 100
sam = Player(name="Sam", health=87)
print(sam.health) # -> 87That looks a lot like the classes from the Objects chapter, except we didn’t have to write our own __init__. BaseModel, the class we inherited from, builds one for us automatically, based entirely on the type hints we wrote.
Here’s the real payoff - what happens when the data is wrong:
bad_player = Player(name="Sam", health="a lot")
# pydantic.ValidationError: 1 validation error for Player
# health
# Input should be a valid integerpydantic looked at our hint, health: int, and refused to accept the string "a lot" in its place. Instead of your program silently accepting bad data and crashing later, somewhere confusing, pydantic stops it immediately, right at the door, with a clear error explaining exactly what went wrong.
Most real programs need to remember things even after they stop running - player accounts, orders, inventory. That’s what a database is for. SQLAlchemy lets you work with a database using ordinary Python classes and objects, instead of writing raw database query language by hand.
pip install sqlalchemyfrom sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import declarative_base, Session
Base = declarative_base()
class Player(Base):
__tablename__ = "players"
id = Column(Integer, primary_key=True)
name = Column(String)
health = Column(Integer)
engine = create_engine("sqlite:///game.db")
Base.metadata.create_all(engine)|
Note
|
That __tablename__ looks a lot like the dunder methods from a few chapters back, but it isn’t one - it’s not a method Python itself ever calls. It’s just a special attribute name that SQLAlchemy itself looks for, as part of its own class-building machinery, to know what to name the table. Still handy to recognize the double-underscore naming pattern showing up again, in a different context.
|
Once that Player class exists, we can save new players and look them back up, all with ordinary-looking Python:
with Session(engine) as session:
session.add(Player(name="Sam", health=100))
session.commit()
sam = session.query(Player).filter_by(name="Sam").first()
print(sam.health) # -> 100Notice we never wrote a single line of raw database query syntax. SQLAlchemy translated session.query(Player).filter_by(name="Sam") into the appropriate database query behind the scenes, and handed us back a real Player object - the very same kind of class we defined ourselves, complete with a .health attribute we can just read.
requests let us call someone else’s web service. FastAPI flips that around - it’s for building your own. It’s one of the most popular ways to build a web API in Python today, largely because it leans so heavily on type hints and pydantic, both of which you already know.
pip install fastapi uvicorn|
Note
|
uvicorn is a second package you install alongside FastAPI - it’s the actual program that runs your API and listens for incoming requests. FastAPI describes what your API does; uvicorn is what keeps it running.
|
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Player(BaseModel):
name: str
health: int = 100
@app.get("/")
def read_root():
return {"message": "Welcome to the dungeon API!"}
@app.post("/players")
def create_player(player: Player):
return {"greeting": "Welcome, " + player.name + "!"}main.py
That @app.get("/") line is a decorator - something we haven’t covered in this book, but you can read it simply as "run the function right below this when someone visits this address." The second one, @app.post("/players"), does the same thing for incoming data - and notice the parameter, player: Player. That type hint isn’t just documentation here; FastAPI and pydantic team up to automatically validate every incoming request against our Player model, before our function even runs.
You’d start this API from a terminal with:
uvicorn main:app --reloadand FastAPI even generates interactive, browsable documentation for your API automatically, for free, just from your type hints - no extra work required.
Picture a simple game server. FastAPI handles incoming web requests; pydantic (working right alongside FastAPI) validates that the data in each request actually makes sense; SQLAlchemy saves and retrieves player data from a real database; and some other program, maybe even one you write yourself, uses requests to call your finished API from the outside, the exact same way it might call anyone else’s.
Four different jobs, four different packages - and every single one of them is built on ideas you already know: classes, type hints, dictionaries, and dunder-style naming conventions. That’s really the whole point of a solid beginner’s foundation: once you have it, even "advanced" packages like these turn out to be built from pieces you already recognize.