Backend development: Python

Sefism.

Chapter 01

Python essentials for the backend

How Python runs web code, how async and the GIL behave, and how projects, types and frameworks are set up.

async/await and ASGI

Also called ASGI, WSGI, asyncio, Uvicorn

What
ASGI is the async interface between a Python web app and its server (Uvicorn, Granian). async def endpoints run on the event loop; FastAPI runs plain def endpoints in a thread pool.
Why
Async lets one worker wait on many slow network calls at once. WSGI, the older sync interface, handles one request per thread or process.
Python
@app.get("/report")
async def report():
    await asyncio.sleep(1)  # other requests keep running
    return {"ok": True}

@app.get("/legacy")
def legacy():
    time.sleep(1)  # fine: FastAPI runs def in a thread pool
    return {"ok": True}

Watch out

Calling blocking code (requests, time.sleep, a sync DB driver) inside async def freezes every request on that worker.

The GIL

Also called Global Interpreter Lock, free-threaded Python

What
In the standard CPython build, the Global Interpreter Lock lets only one thread execute Python bytecode at a time. Threads still help for I/O, because waiting releases the lock. A free-threaded build without the GIL is supported but not the default.
Why
It decides how you scale: async or threads for I/O-bound work, multiple processes (Gunicorn or Uvicorn workers, Celery) for CPU-bound work.
Example
A FastAPI app resizing images in request handlers uses one core however many threads it has; running 4 worker processes or pushing resizing to a Celery queue uses all 4 cores.

Watch out

Adding threads to speed up CPU-heavy Python code and seeing no gain.

Virtual environments, pip and uv

Also called venv, pyproject.toml, uv, pip, virtual environment

What
A virtual environment is an isolated folder of packages for one project. pyproject.toml declares dependencies; uv (or pip with a lockfile) installs them reproducibly.
Why
Projects need different versions of the same library. Isolation and a lockfile keep your laptop, CI and production in step.
Shell
uv init shop-api && cd shop-api
uv add "fastapi[standard]" "psycopg[binary]"
uv run fastapi dev app/main.py

# the classic way
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

Watch out

Installing into the system Python, then wondering why production has different versions.

Type hints

Also called typing, mypy, pyright

What
Annotations that describe the types of variables, parameters and return values. Python 3.12 adds type aliases and generic syntax like def first[T].
Why
Editors and checkers (mypy, pyright) catch bugs before runtime, and FastAPI and Pydantic read hints to validate and document requests.
Python
type UserId = int

def find_user(user_id: UserId) -> User | None: ...

def first[T](items: list[T]) -> T | None:
    return items[0] if items else None

Watch out

Assuming hints are enforced: plain Python ignores them at runtime unless a library like Pydantic checks them.

Django URLs, views and apps

Also called Django, DRF

What
Django maps a URL pattern to a view function or class, which uses models (the ORM) and returns a response. Code is split into apps, each with models, views and migrations.
Why
It ships auth, admin, ORM, migrations and forms in one framework. Django REST Framework adds serializers and viewsets for JSON APIs.
Python
# urls.py
urlpatterns = [path("users/<int:pk>/", views.user_detail)]

# views.py
def user_detail(request, pk):
    user = get_object_or_404(User, pk=pk)
    return JsonResponse({"id": user.pk, "email": user.email})

Watch out

Putting business logic in views; keep it in models or service functions so it can be tested and reused.

Python core: types, collections, comprehensions

Also called list, dict, comprehension

What
Built-in list, dict, set and tuple cover most data handling, and comprehensions build new collections in one readable line. Names are references to objects, so two names can point at the same list.
Why
Request data, query results and config are all dicts and lists, so fluent collection code is most of everyday backend Python.
Python
orders = [{"id": 1, "total": 40}, {"id": 2, "total": 90}]
big = [o["id"] for o in orders if o["total"] > 50]
by_id = {o["id"]: o for o in orders}
tags = set(["api", "db", "api"])  # {'api', 'db'}

def add_tag(tag, tags=None):  # never tags=[]
    tags = tags or []
    tags.append(tag)
    return tags

Watch out

A mutable default argument such as def f(x=[]), which is shared between calls.

Python classes and exceptions

Also called class, try except, raise

What
Classes group data and behaviour, and dataclasses remove boilerplate for plain data. Errors are raised as exceptions and handled with try and except, catching the narrowest type you can.
Why
Services and models in Django and FastAPI are built from these, and custom exceptions let one handler map each failure to the right status code.
Python
class OrderNotFound(Exception):
    """Raised when an order id does not exist."""
def get_order(orders: dict, order_id: int):
    try:
        return orders[order_id]
    except KeyError:
        raise OrderNotFound(order_id) from None

try:
    get_order({}, 7)
except OrderNotFound as e:
    print("404 for order", e)

Watch out

A bare except: that swallows every error, including typos and Ctrl+C.

01 · Python essentials for the backend1 / 10