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

- Name
- Youngju Kim
- @fjvbn20031
Intro — 2025 Python is not 2015 Python
If you learned Python 10 years ago and came back today, the biggest changes:
- uv (2024): a Rust-written unified tool replacing pip, poetry, pyenv, pip-tools. 10-100x faster
- Pydantic v2 (2023): validation engine rewritten in Rust. 5-50x faster
- FastAPI: the de facto standard for type-hint-based API frameworks
- Polars (2024+): Pandas replacement. Rust-based, 10-100x faster, memory efficient
- Python 3.13 (2024/10): Free-threading (No-GIL) experiment, JIT compiler, improved REPL
- LangChain, LangGraph, Pydantic AI (2024+): AI agent stack
- Ruff + mypy/pyright: Lint, Format, type check — all replaced with fast tooling
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
- GIL: only one thread executes Python bytecode at a time
- No-GIL: true multi-core parallelism
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:
- Multi-line editing
- Syntax highlighting
- Paste mode
- Block history
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:
- pip → install
- pip-tools → hash-pinned requirements
- pyenv → Python version management
- virtualenv/venv → virtual environments
- poetry → project management
- pipx → CLI tool installation
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
pydantic-corerewritten in Rust- 5-50x faster
- Strong constraints via
Annotated - Separated serialization API
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)
- Pydantic models are the request/response schemas
- OpenAPI auto-generated (
/docs) - Dependency Injection built-in
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
| Purpose | Library |
|---|---|
| Web | FastAPI |
| ORM | SQLAlchemy 2.0 (async), SQLModel |
| DB driver | asyncpg (Postgres), motor (Mongo) |
| Migration | Alembic |
| Queue | Dramatiq, Celery 5.x, Taskiq |
| Cache | Redis + aioredis |
| Test | pytest + httpx |
| Observability | OpenTelemetry + 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
- Mixing blocking code: use
httpx.AsyncClientinstead ofrequests.get - Nesting
asyncio.run: event loop conflict.nest_asynciois an anti-pattern - Failing to keep task references: store
asyncio.create_taskresults somewhere or GC eats them - CPU-bound work: offload via
run_in_executorto thread/process time.sleepinsideasync def: useasyncio.sleep
Part 6 — Polars: 2025 Pandas replacement
6.1 Why Polars
| Item | Pandas | Polars |
|---|---|---|
| Engine | NumPy (C) | Rust + Arrow |
| Speed | baseline | 5-100x faster |
| Memory | heavy | ~50% of Pandas |
| Parallelism | almost none | auto multi-thread |
| Lazy execution | no | yes (optimizer) |
| Large data | hard | streaming 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
- New project: Polars by default
- Existing Pandas project: incremental migration (
df.to_pandas()bridge) - Visualization may still be easier with Pandas (matplotlib/seaborn)
- ML library compatibility still favors Pandas (improving)
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.
- LangChain Core: base abstractions (Runnable)
- LangGraph: state-based agent graph (2025 standard)
- LangSmith: observability and evaluation
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
| Situation | Recommendation |
|---|---|
| Complex multi-step agent | LangGraph |
| Simple type-safe agent | Pydantic AI |
| RAG-focused | LlamaIndex |
| Minimal production deps | OpenAI SDK directly |
| Matching a TS team | Vercel 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
- Replaces Flake8, Black, isort, pyupgrade, pylint
- Written in Rust
- 800+ rules built-in
8.3 Type checker choice
| Tool | Traits |
|---|---|
| mypy | oldest, slow, conservative |
| pyright | MS-made, fast, strict |
| pyre | Meta-made, server mode |
| ty | Astral-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
- AI/ML: PyTorch, JAX, scikit-learn, Hugging Face
- Data engineering: Airflow, dbt, Dagster
- API servers: FastAPI productivity
- Scientific computing: NumPy, SciPy
- Scripting/automation: glue language
- Education: gentle curve
9.2 Does not shine
- CPU-bound high performance: Rust/Go/C++
- Mobile apps: Swift/Kotlin
- Embedded: C/Rust
- Game engines: C++
- Browser: JS/TS (Pyodide is niche)
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
- Full type hints (PEP 695, Union, Literal, Protocol)
- Project management with uv
- Ruff + mypy/pyright setup
Month 2: Pydantic + FastAPI
- Deep Pydantic v2 (Annotated, Validators)
- Building a real FastAPI
- Dependency Injection patterns
Month 3: AsyncIO
- TaskGroup, asyncio.timeout
- Async DB with httpx, asyncpg
- Concurrency limits (Semaphore)
Month 4: Data processing
- Polars mastery
- SQL + SQLAlchemy 2.0 async
- DuckDB + Parquet
Month 5: AI engineering
- OpenAI/Anthropic SDK
- LangGraph or Pydantic AI
- Vector DB (Qdrant, Pinecone, pgvector)
- RAG pipeline construction
Month 6: Operations
- Docker + Kubernetes deployment
- Observability (OpenTelemetry, Prometheus)
- CI/CD (GitHub Actions)
- Profiling (py-spy, scalene)
Part 11 — 12-item Python checklist
- Know the 4 tools uv replaces (pip/poetry/pyenv/pip-tools)
- Know Pydantic v1 vs v2 major changes
- Know FastAPI Dependency Injection usage
- Know TaskGroup vs asyncio.gather differences
- Know the benefits of Polars Lazy Evaluation
- Know how to use Protocol and TypeIs type hints
- Know the SQLAlchemy 2.0 async style
- Know the meaning of GIL and Free-threading
- Know the relationship between LangGraph and LangChain
- Know the tools Ruff replaces
- Can manage config via Pydantic Settings
- Can test async APIs via pytest + httpx
Part 12 — 10 Python anti-patterns
pip install+requirements.txtonly: weak reproducibility. Useuv+uv.lock- Writing Python without type hints: leaving bugs mypy/pyright would catch
from X import *: namespace pollution- Debugging with
print(): useloggingorrich except Exception: pass: silent failure. Use specific exceptions + logging- Mutable default args:
def f(x=[]):shares state. Usex=Nonethenx or [] iterrowsin Pandas: slow. Use vectorized ops or Polars- Sync libraries in async functions: blocks the event loop
- Not using
pathlibinstead ofos.path: pathlib is much cleaner pip install -r requirements.txtin Docker images: breaks layer caching. Useuv 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:
- Speed: solved by uv, Pydantic, Polars, Ruff with Rust engines
- Concurrency: free-threading experiment, AsyncIO matured
- Types: strengthened by 3.12-3.13 PEPs
- Ecosystem: the standard language for AI engineering
"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:
- How Transformer actually works
- Attention mechanism down to the equations
- Pre-training to SFT to RLHF to DPO flow
- Three generations of RAG (Naive to Advanced to Agentic)
- Agent design (ReAct, Plan-and-Execute, Multi-Agent)
- The real difficulty of LLM evaluation (limits of LM-as-a-Judge)
Time to pry open the black box. See you next.