LabHub

Blog

Python Complete Guide — FastAPI, AsyncIO, Pydantic, uv, Polars, AI Engineering (Season 2 Ep 5, 2025)

한국어English日本語

Intro — 2025 Python is not 2015 Python

If you learned Python 10 years ago and came back today, the biggest changes:

Python has firmly settled as "the lingua franca of AI engineering," and the entire toolchain is being rewritten in Rust, closing the speed gap.


Part 1 — Python 3.13: the future of the language

1.1 Free-threading (No-GIL) — PEP 703

An Experimental Build removing the 30-year-old GIL (Global Interpreter Lock), released 2024/10:

python3.13t # free-threaded build

Limits: currently experimental, ~10% single-thread regression, C extension compatibility issues. Expected to stabilize 2026-2027.

1.2 JIT Compiler — PEP 744

Copy-and-Patch JIT added (experimental):

python --enable-experimental-jit

Initial benchmarks modest (~5%), but long-term potential for PyPy-level performance.

1.3 Improved REPL

The standard REPL in 3.13 is finally usable:

The things you used PyPy/ipython for, now built in.

1.4 Type System improvements (3.12-3.13)

# PEP 695: Generic Syntax
def first[T](items: list[T]) -> T:
    return items[0]

class Stack[T]:
    def __init__(self) -> None:
        self.items: list[T] = []

# PEP 696: Type Parameter Defaults
def process[T = str](x: T) -> T: ...

# PEP 742: TypeIs
from typing import TypeIs
def is_str(x: object) -> TypeIs[str]:
    return isinstance(x, str)

Part 2 — uv: the package manager revolution

2.1 Why uv is a game changer

Rust-based unified tool by Astral (makers of Ruff). Replaces:

Speed: a pip install that takes 10 seconds runs in 0.1 seconds with uv.

2.2 Basic usage

# install
curl -LsSf https://astral.sh/uv/install.sh | sh

# initialize project
uv init my-app
cd my-app

# add dependencies (pyproject.toml auto-updated)
uv add fastapi uvicorn
uv add --dev pytest ruff mypy

# run
uv run python main.py
uv run pytest

# Python version management
uv python install 3.13
uv python pin 3.13

# lock file (uv.lock)
uv lock
uv sync

2.3 uv script (single-file Python)

# /// script
# requires-python = ">=3.12"
# dependencies = [
#     "httpx",
#     "rich",
# ]
# ///

import httpx
from rich import print

print(httpx.get("https://api.github.com").json())
uv run script.py
# dependencies auto-downloaded, then run

Starting new projects with uv is the 2025 standard.


Part 3 — Pydantic v2: type-based validation

3.1 The v2 revolution

3.2 Basic usage

from pydantic import BaseModel, EmailStr, Field
from typing import Annotated
from datetime import datetime

class User(BaseModel):
    id: int
    email: EmailStr
    name: Annotated[str, Field(min_length=1, max_length=100)]
    age: Annotated[int, Field(ge=0, le=150)]
    created_at: datetime

# validate + convert
user = User.model_validate({
    "id": 1,
    "email": "alice@example.com",
    "name": "Alice",
    "age": 30,
    "created_at": "2025-01-01T00:00:00Z"
})

# serialize
json_str = user.model_dump_json()
dict_data = user.model_dump()

3.3 Advanced features

from pydantic import field_validator, model_validator

class Post(BaseModel):
    title: str
    content: str
    tags: list[str] = []

    @field_validator('title')
    @classmethod
    def title_not_empty(cls, v: str) -> str:
        if not v.strip():
            raise ValueError('title cannot be empty')
        return v.strip()

    @model_validator(mode='after')
    def check_tags_limit(self) -> 'Post':
        if len(self.tags) > 10:
            raise ValueError('too many tags')
        return self

3.4 Pydantic Settings

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    database_url: str
    api_key: str
    debug: bool = False

    class Config:
        env_file = ".env"

settings = Settings()

The standard for env vars and config management.


Part 4 — FastAPI: the type-safe API standard

4.1 Basic example

from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel

app = FastAPI()

class UserCreate(BaseModel):
    email: str
    password: str

class UserResponse(BaseModel):
    id: int
    email: str

@app.post("/users", response_model=UserResponse)
async def create_user(user: UserCreate) -> UserResponse:
    # user is already validated
    # response auto-serialized as UserResponse
    return UserResponse(id=1, email=user.email)

4.2 Dependency Injection

async def get_db() -> AsyncGenerator[AsyncSession, None]:
    async with async_session() as session:
        yield session

async def get_current_user(token: str = Header(...)) -> User:
    # token validation logic
    return user

@app.get("/me")
async def me(user: User = Depends(get_current_user)) -> UserResponse:
    return user

4.3 2025 FastAPI stack

PurposeLibrary
WebFastAPI
ORMSQLAlchemy 2.0 (async), SQLModel
DB driverasyncpg (Postgres), motor (Mongo)
MigrationAlembic
QueueDramatiq, Celery 5.x, Taskiq
CacheRedis + aioredis
Testpytest + httpx
ObservabilityOpenTelemetry + Prometheus

Part 5 — AsyncIO in practice

5.1 Basic structure

import asyncio

async def fetch(url: str) -> str:
    # simulation
    await asyncio.sleep(1)
    return f"data from {url}"

async def main() -> None:
    # sequential
    r1 = await fetch("a")
    r2 = await fetch("b")

    # concurrent (2x faster)
    r1, r2 = await asyncio.gather(fetch("a"), fetch("b"))

asyncio.run(main())

5.2 TaskGroup (Python 3.11+)

async def main():
    async with asyncio.TaskGroup() as tg:
        t1 = tg.create_task(fetch("a"))
        t2 = tg.create_task(fetch("b"))
        t3 = tg.create_task(fetch("c"))
    # on context exit, all tasks complete
    # if any raises, others cancelled + ExceptionGroup
    print(t1.result(), t2.result(), t3.result())

TaskGroup beats asyncio.gather: structured concurrency, auto-cancel, clear errors.

5.3 asyncio.timeout (Python 3.11+)

async def fetch_with_timeout():
    async with asyncio.timeout(5.0):
        return await slow_operation()

5.4 Five AsyncIO pitfalls

  1. Mixing blocking code: use httpx.AsyncClient instead of requests.get
  2. Nesting asyncio.run: event loop conflict. nest_asyncio is an anti-pattern
  3. Failing to keep task references: store asyncio.create_task results somewhere or GC eats them
  4. CPU-bound work: offload via run_in_executor to thread/process
  5. time.sleep inside async def: use asyncio.sleep

Part 6 — Polars: 2025 Pandas replacement

6.1 Why Polars

ItemPandasPolars
EngineNumPy (C)Rust + Arrow
Speedbaseline5-100x faster
Memoryheavy~50% of Pandas
Parallelismalmost noneauto multi-thread
Lazy executionnoyes (optimizer)
Large datahardstreaming supported

6.2 Basic usage

import polars as pl

df = pl.read_csv("data.csv")

result = (
    df
    .filter(pl.col("age") > 30)
    .group_by("country")
    .agg([
        pl.col("salary").mean().alias("avg_salary"),
        pl.col("id").count().alias("count"),
    ])
    .sort("avg_salary", descending=True)
)

6.3 Lazy Evaluation

lazy_df = pl.scan_csv("huge.csv")  # not actually read

result = (
    lazy_df
    .filter(pl.col("year") == 2024)
    .group_by("category")
    .agg(pl.col("amount").sum())
    .collect()  # now executes (optimizer plans)
)

The query optimizer plans execution like a SQL DBMS.

6.4 2025 migration guide


Part 7 — 2025 AI engineering stack

7.1 Current state of LangChain

LangChain exploded in 2023, had growing pains in 2024, and 2025 is reorganized around LangGraph.

7.2 LangGraph example

from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI

class State(TypedDict):
    messages: Annotated[list, operator.add]

def agent(state: State):
    response = llm.invoke(state["messages"])
    return {"messages": [response]}

def should_continue(state: State) -> str:
    last = state["messages"][-1]
    if last.tool_calls:
        return "tools"
    return END

workflow = StateGraph(State)
workflow.add_node("agent", agent)
workflow.add_node("tools", tool_node)
workflow.set_entry_point("agent")
workflow.add_conditional_edges("agent", should_continue)
workflow.add_edge("tools", "agent")

app = workflow.compile()

Directed Graph + state machine model. Ideal for complex agent flows.

7.3 Pydantic AI (2024+)

A type-safe agent framework by the Pydantic team. Lightweight, intuitive.

from pydantic_ai import Agent
from pydantic import BaseModel

class WeatherResponse(BaseModel):
    city: str
    temp_c: float
    condition: str

agent = Agent(
    'openai:gpt-4o',
    result_type=WeatherResponse,
    system_prompt='You are a weather assistant.',
)

result = await agent.run('Weather in Seoul?')
print(result.data)  # typed as WeatherResponse

7.4 2025 AI stack recommendations

SituationRecommendation
Complex multi-step agentLangGraph
Simple type-safe agentPydantic AI
RAG-focusedLlamaIndex
Minimal production depsOpenAI SDK directly
Matching a TS teamVercel AI SDK (TS)

Part 8 — Modern Python dev environment

8.1 2025 standard toolchain

# pyproject.toml
[project]
name = "my-app"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
    "fastapi>=0.115",
    "pydantic>=2.9",
    "uvicorn>=0.30",
]

[dependency-groups]
dev = [
    "pytest>=8",
    "ruff>=0.7",
    "mypy>=1.13",
]

[tool.ruff]
line-length = 100
[tool.ruff.lint]
select = ["E", "F", "I", "N", "UP", "B", "S", "C4", "PL"]

[tool.mypy]
strict = true

8.2 Ruff — 10-100x faster linter/formatter

8.3 Type checker choice

ToolTraits
mypyoldest, slow, conservative
pyrightMS-made, fast, strict
pyreMeta-made, server mode
tyAstral-made (2025), Rust-based (in dev)

2025 pick: pyright for new, mypy for existing. Waiting on ty.

8.4 Testing

# pytest + httpx for FastAPI
import pytest
from httpx import AsyncClient, ASGITransport
from myapp.main import app

@pytest.mark.asyncio
async def test_create_user():
    async with AsyncClient(
        transport=ASGITransport(app=app), base_url="http://test"
    ) as ac:
        r = await ac.post("/users", json={"email": "a@b.com", "password": "pw"})
        assert r.status_code == 200

Part 9 — Where Python shines vs not

9.1 Shines

9.2 Does not shine

2025 reality: Python is the standard for "AI, data, API" but not "for everything."


Part 10 — 6-month Python mastery roadmap

Month 1: 3.13 fundamentals

Month 2: Pydantic + FastAPI

Month 3: AsyncIO

Month 4: Data processing

Month 5: AI engineering

Month 6: Operations


Part 11 — 12-item Python checklist

  1. Know the 4 tools uv replaces (pip/poetry/pyenv/pip-tools)
  2. Know Pydantic v1 vs v2 major changes
  3. Know FastAPI Dependency Injection usage
  4. Know TaskGroup vs asyncio.gather differences
  5. Know the benefits of Polars Lazy Evaluation
  6. Know how to use Protocol and TypeIs type hints
  7. Know the SQLAlchemy 2.0 async style
  8. Know the meaning of GIL and Free-threading
  9. Know the relationship between LangGraph and LangChain
  10. Know the tools Ruff replaces
  11. Can manage config via Pydantic Settings
  12. Can test async APIs via pytest + httpx

Part 12 — 10 Python anti-patterns

  1. pip install + requirements.txt only: weak reproducibility. Use uv + uv.lock
  2. Writing Python without type hints: leaving bugs mypy/pyright would catch
  3. from X import *: namespace pollution
  4. Debugging with print(): use logging or rich
  5. except Exception: pass: silent failure. Use specific exceptions + logging
  6. Mutable default args: def f(x=[]): shares state. Use x=None then x or []
  7. iterrows in Pandas: slow. Use vectorized ops or Polars
  8. Sync libraries in async functions: blocks the event loop
  9. Not using pathlib instead of os.path: pathlib is much cleaner
  10. pip install -r requirements.txt in Docker images: breaks layer caching. Use uv sync --frozen

Closing — Python is reborn in the AI era

Python's original strengths were "productivity and learning curve." Its weaknesses were "speed and concurrency."

2024-2025:

"Python is slow" was a pre-2020 story. In 2025 Python is "the lingua franca of the AI era, sped up by Rust tools."

Senior engineers cannot avoid Python for a reason. In AI, data, API, and scripting, Python remains — and will remain — dominant.


Next — "LLM Complete Guide: Transformer, Attention, RLHF, RAG, Agent, Evaluation"

Season 2 Ep 6 is the 2025 engineer's core literacy: the internals of LLMs. Next:

Time to pry open the black box. See you next.

Comments

No comments yet.

Sign in to leave a comment