p2d: add Dependency Injection tutorial — basic, auth, DB, class-based, scope, nesting
swadhinbiswas committed 3 months ago
3bb94a5
1 parent 949b1c7
commit 3bb94a5
1 file changed
+180 -0
Showing 1 changed file+180-0
▼docs_site/src/content/docs/tutorials/dependency-injection.md
+180-0
| @@ -0,0 +1,180 @@ | ||
1 | +--- | |
2 | +title: Dependency Injection | |
3 | +description: Use Depends to inject reusable components into your route handlers. | |
4 | +--- | |
5 | + | |
6 | +Dependency Injection (DI) lets you declare reusable components that are automatically resolved and injected into your route handlers. JustAPI's DI system mirrors FastAPI's, making migration seamless. | |
7 | + | |
8 | +## Basic Dependency | |
9 | + | |
10 | +A dependency is any callable that returns a value: | |
11 | + | |
12 | +```python | |
13 | +from justapi import JustAPIApp, Depends | |
14 | + | |
15 | +app = JustAPIApp() | |
16 | + | |
17 | + | |
18 | +def common_params(q: str | None = None, skip: int = 0, limit: int = 100): | |
19 | + return {"q": q, "skip": skip, "limit": limit} | |
20 | + | |
21 | + | |
22 | +@app.get("/items/") | |
23 | +def list_items(request, params: dict = Depends(common_params)): | |
24 | + return {"endpoint": "items", **params} | |
25 | + | |
26 | + | |
27 | +@app.get("/users/") | |
28 | +def list_users(request, params: dict = Depends(common_params)): | |
29 | + return {"endpoint": "users", **params} | |
30 | +``` | |
31 | + | |
32 | +```bash | |
33 | +curl "http://127.0.0.1:8000/items/?skip=10&limit=5" | |
34 | +# Output: {"endpoint":"items","q":null,"skip":10,"limit":5} | |
35 | +``` | |
36 | + | |
37 | +## Authentication Dependency | |
38 | + | |
39 | +Dependencies can raise `HTTPException` to reject requests: | |
40 | + | |
41 | +```python | |
42 | +from justapi import JustAPIApp, Depends, HTTPException, Header | |
43 | + | |
44 | +app = JustAPIApp() | |
45 | + | |
46 | + | |
47 | +def verify_token(authorization: str = Header(...)): | |
48 | + if not authorization.startswith("Bearer "): | |
49 | + raise HTTPException(status_code=401, detail="Invalid authorization header") | |
50 | + token = authorization.removeprefix("Bearer ") | |
51 | + if token != "secret-token": | |
52 | + raise HTTPException(status_code=401, detail="Invalid token") | |
53 | + return {"user_id": 42, "role": "admin"} | |
54 | + | |
55 | + | |
56 | +@app.get("/protected") | |
57 | +def protected_route(request, user: dict = Depends(verify_token)): | |
58 | + return {"message": "Access granted", "user": user} | |
59 | +``` | |
60 | + | |
61 | +```bash | |
62 | +curl http://127.0.0.1:8000/protected | |
63 | +# Output: {"detail":"Invalid authorization header"} | |
64 | + | |
65 | +curl http://127.0.0.1:8000/protected \ | |
66 | + -H "Authorization: Bearer secret-token" | |
67 | +# Output: {"message":"Access granted","user":{"user_id":42,"role":"admin"}} | |
68 | +``` | |
69 | + | |
70 | +## Database Session Dependency | |
71 | + | |
72 | +```python | |
73 | +def get_db(request): | |
74 | + db = app.db | |
75 | + try: | |
76 | + yield db | |
77 | + finally: | |
78 | + pass # Connection pooling handles cleanup | |
79 | + | |
80 | + | |
81 | +@app.get("/users") | |
82 | +def list_users(request, db=Depends(get_db)): | |
83 | + return db.query("SELECT * FROM users") | |
84 | +``` | |
85 | + | |
86 | +## Class-Based Dependencies | |
87 | + | |
88 | +```python | |
89 | +class Pagination: | |
90 | + def __init__(self, skip: int = 0, limit: int = 100): | |
91 | + self.skip = skip | |
92 | + self.limit = limit | |
93 | + | |
94 | + | |
95 | +@app.get("/items/") | |
96 | +def list_items(request, pagination: Pagination = Depends(Pagination)): | |
97 | + return {"skip": pagination.skip, "limit": pagination.limit} | |
98 | +``` | |
99 | + | |
100 | +## Dependency Scope & Caching | |
101 | + | |
102 | +By default, dependencies are called once per request and the result is cached. All route handlers and nested dependencies share the same instance: | |
103 | + | |
104 | +```python | |
105 | +def get_db(request): | |
106 | + print("Creating DB session") # Called once per request | |
107 | + return {"connection": "db-1"} | |
108 | + | |
109 | +def get_user(request, db=Depends(get_db)): | |
110 | + return {"user": "alice", "db": db} | |
111 | + | |
112 | +@app.get("/profile") | |
113 | +def profile(request, user=Depends(get_user), db=Depends(get_db)): | |
114 | + return {"user": user, "db_again": db} | |
115 | +``` | |
116 | + | |
117 | +In this example, `get_db` is called **once** and its result is reused by `get_user` and `profile`. | |
118 | + | |
119 | +## Dependency with `Path()`, `Query()`, `Header()` | |
120 | + | |
121 | +```python | |
122 | +from justapi import Path, Query, Header | |
123 | + | |
124 | + | |
125 | +def pagination( | |
126 | + skip: int = Query(0, description="Number of items to skip"), | |
127 | + limit: int = Query(10, description="Max items per page"), | |
128 | +): | |
129 | + return {"skip": skip, "limit": limit} | |
130 | + | |
131 | + | |
132 | +def auth( | |
133 | + user_id: int = Path(..., description="User ID"), | |
134 | + authorization: str = Header(..., description="Bearer token"), | |
135 | +): | |
136 | + if authorization != "Bearer secret": | |
137 | + raise HTTPException(401) | |
138 | + return {"user_id": user_id} | |
139 | + | |
140 | + | |
141 | +@app.get("/users/{user_id}/items/") | |
142 | +def list_items( | |
143 | + request, | |
144 | + auth_data: dict = Depends(auth), | |
145 | + pagination_data: dict = Depends(pagination), | |
146 | +): | |
147 | + return {**auth_data, **pagination_data} | |
148 | +``` | |
149 | + | |
150 | +## Nested Dependencies | |
151 | + | |
152 | +Dependencies can depend on other dependencies: | |
153 | + | |
154 | +```python | |
155 | +def get_config(request): | |
156 | + return {"db_url": "postgres://localhost/mydb"} | |
157 | + | |
158 | + | |
159 | +def get_db(request, config: dict = Depends(get_config)): | |
160 | + return {"connection": f"Connected to {config['db_url']}"} | |
161 | + | |
162 | + | |
163 | +@app.get("/status") | |
164 | +def status(request, db: dict = Depends(get_db)): | |
165 | + return {"db_status": db["connection"]} | |
166 | +``` | |
167 | + | |
168 | +## How It Works Internally | |
169 | + | |
170 | +1. JustAPI inspects function signatures at registration time | |
171 | +2. Parameters with `Depends()` are resolved before the handler runs | |
172 | +3. Dependencies execute in order (innermost first) | |
173 | +4. Results are cached to avoid redundant calls | |
174 | +5. `HTTPException` raised in a dependency short-circuits the request | |
175 | + | |
176 | +## Next Steps | |
177 | + | |
178 | +- [Middleware](/tutorials/middleware/) — Request/response interception | |
179 | +- [Error Handling](/tutorials/error-handling/) — Custom exception handlers | |
180 | +- [Database Integration](/tutorials/database-integration/) — Real database dependencies | |
0 | 181 | |