Skip to content

Commit 2ca4c04

Browse files
committed
feat: Context object
1 parent fb48ab9 commit 2ca4c04

23 files changed

+2431
-2447
lines changed

README.md

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ Create a file named `app.py` and get a server running in under a minute.
4545
```python
4646
# app.py
4747
from webspark.core import WebSpark, View, path
48-
from webspark.http import JsonResponse
48+
from webspark.http import Context
4949

5050
# 1. Define a View
5151
class HelloView(View):
5252
"""A view to handle requests to the root URL."""
53-
def handle_get(self, request):
53+
def handle_get(self, ctx: Context):
5454
# The `handle_get` method is automatically called for GET requests.
55-
return JsonResponse({"message": "Hello, from WebSpark! ✨"})
55+
ctx.json({"message": "Hello, from WebSpark! ✨"})
5656

5757
# 2. Create the application instance
5858
app = WebSpark()
@@ -113,25 +113,25 @@ app.add_paths([
113113
Views handle the logic for your routes. They are classes that inherit from `webspark.core.views.View`.
114114

115115
- **Method Dispatch**: Requests are automatically routed to `handle_<method>` methods (e.g., `handle_get`, `handle_post`).
116-
- **Request Object**: Each handler method receives a `Request` object with all the request details.
116+
- **Context Object**: Each handler method receives a `Context` object with all the request details.
117117

118118
```python
119119
from webspark.core import View
120-
from webspark.http import JsonResponse, HTMLResponse
120+
from webspark.http import Context
121121

122122
class UserView(View):
123-
def handle_get(self, request):
123+
def handle_get(self, ctx: Context):
124124
"""Handles GET /users/:id"""
125-
user_id = request.path_params.get('id')
126-
page = request.query_params.get('page', 1) # Access query params with a default
125+
user_id = ctx.path_params.get('id')
126+
page = ctx.query_params.get('page', 1) # Access query params with a default
127127

128-
return JsonResponse({"user_id": user_id, "page": page})
128+
ctx.json({"user_id": user_id, "page": page})
129129

130-
def handle_post(self, request):
130+
def handle_post(self, ctx):
131131
"""Handles POST /users"""
132-
data = request.body # Access parsed JSON body
132+
data = ctx.body # Access parsed JSON body
133133
# ... create a new user ...
134-
return JsonResponse({"created": True, "data": data}, status=201)
134+
ctx.json({"created": True, "data": data}, status=201)
135135
```
136136

137137
### 3. Schema Validation
@@ -155,13 +155,13 @@ Attach the schema to a view using the `body_schema` or `query_params_schema` att
155155
class CreateUserView(View):
156156
body_schema = UserSchema # Validate the request body
157157

158-
def handle_post(self, request):
158+
def handle_post(self, ctx: Context):
159159
# This method is only called if the body is valid.
160160
# Access the validated data:
161161
validated_data = self.validated_body()
162162

163163
# ... process the data ...
164-
return JsonResponse({"user": validated_data})
164+
ctx.json({"user": validated_data})
165165
```
166166

167167
#### Available Fields
@@ -170,44 +170,42 @@ WebSpark offers a rich set of fields for comprehensive validation: `StringField`
170170

171171
### 4. Responses
172172

173-
WebSpark provides convenient `Response` subclasses for common content types.
173+
WebSpark provides convenient `Context` that simplifies request handling and response generation.
174174

175175
```python
176-
from webspark.http import JsonResponse, HTMLResponse, TextResponse, StreamResponse, RedirectResponse
176+
from webspark.http import Context
177177

178178
# JSON response (most common for APIs)
179-
return JsonResponse({"message": "Success"})
179+
ctx.json({"message": "Success"})
180180

181181
# HTML response
182-
return HTMLResponse("<h1>Hello, World!</h1>")
182+
ctx.html("<h1>Hello, World!</h1>")
183183

184184
# Plain text response
185-
return TextResponse("OK")
185+
ctx.text("OK")
186186

187187
# Stream a large file without loading it all into memory
188-
return StreamResponse("/path/to/large/video.mp4")
188+
ctx.stream("/path/to/large/video.mp4")
189189

190190
# Redirect response
191-
return RedirectResponse("/new-url")
191+
ctx.redirect("/new-url")
192192
```
193193

194194
### 5. Cookies
195195

196-
Easily set and delete cookies on the `Response` object.
196+
Easily set and delete cookies on the `Context` object.
197197

198198
```python
199199
class AuthView(View):
200-
def handle_post(self, request):
200+
def handle_post(self, ctx: Context):
201201
# Set a cookie on login
202-
response = JsonResponse({"logged_in": True})
203-
response.set_cookie("session_id", "abc123", path="/", max_age=3600, httponly=True, secure=True)
204-
return response
202+
ctx.set_cookie("session_id", "abc123", path="/", max_age=3600, httponly=True, secure=True)
203+
ctx.json({"logged_in": True})
205204

206-
def handle_delete(self, request):
205+
def handle_delete(self, ctx: Context):
207206
# Delete a cookie on logout
208-
response = JsonResponse({"logged_out": True})
209-
response.delete_cookie("session_id")
210-
return response
207+
ctx.delete_cookie("session_id")
208+
ctx.json({"logged_out": True})
211209
```
212210

213211
### 6. Middleware (Plugins)
@@ -222,11 +220,10 @@ from webspark.core import Plugin
222220
class LoggingPlugin(Plugin):
223221
def apply(self, handler):
224222
# This method returns a new handler that wraps the original one.
225-
def wrapped_handler(request):
226-
print(f"Request: {request.method} {request.path}")
227-
response = handler(request) # The original view handler is called here
228-
print(f"Response: {response.status}")
229-
return response
223+
def wrapped_handler(ctx):
224+
print(f"Request: {ctx.method} {ctx.path}")
225+
handler(ctx) # The original view handler is called here
226+
print(f"Response: {ctx.status}")
230227
return wrapped_handler
231228

232229
# Register the plugin globally
@@ -246,15 +243,15 @@ Gracefully handle errors using `HTTPException`. When raised, the framework will
246243
from webspark.utils import HTTPException
247244

248245
class UserView(View):
249-
def handle_get(self, request):
250-
user_id = request.path_params.get('id')
246+
def handle_get(self, ctx):
247+
user_id = ctx.path_params.get('id')
251248
user = find_user_by_id(user_id) # Your database logic
252249

253250
if not user:
254251
# This will generate a 404 Not Found response
255252
raise HTTPException("User not found", status_code=404)
256253

257-
return JsonResponse({"user": user.serialize()})
254+
ctx.json({"user": user.serialize()})
258255
```
259256

260257
### 8. Custom Exception Handlers
@@ -264,22 +261,23 @@ WebSpark allows you to define custom handlers for specific HTTP status codes usi
264261
The handler function receives the `request` and the `exception` object and must return a `Response` object.
265262

266263
```python
267-
from webspark.http import HTMLResponse, TextResponse
264+
from webspark.http import Context
268265

269266
app = WebSpark(debug=True)
270267

271268
@app.handle_exception(404)
272-
def handle_not_found(request, exc):
269+
def handle_not_found(ctx: Context, exc: Exception):
273270
"""Custom handler for 404 Not Found errors."""
274-
return HTMLResponse("<h1>Oops! Page not found.</h1>", status=404)
271+
ctx.html("<h1>Oops! Page not found.</h1>", status=404)
275272

276273
@app.handle_exception(500)
277-
def handle_server_error(request, exc):
274+
def handle_server_error(ctx: Context, exc: Exception):
278275
"""Custom handler for 500 Internal Server Error."""
279276
if app.debug:
280277
# In debug mode, show the full exception
281-
return TextResponse(str(exc), status=500)
282-
return HTMLResponse("<h1>A server error occurred.</h1>", status=500)
278+
ctx.text(str(exc), status=500)
279+
else:
280+
ctx.html("<h1>A server error occurred.</h1>", status=500)
283281
```
284282

285283
### 9. Proxy Configuration

examples/basic_api.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99

1010
from webspark.core import View, WebSpark, path
11-
from webspark.http import JsonResponse
11+
from webspark.http import Context
1212
from webspark.utils import HTTPException
1313

1414
# In-memory storage for our examples
@@ -22,14 +22,14 @@
2222
class ItemsView(View):
2323
"""Handle operations on the collection of items."""
2424

25-
def handle_get(self, request):
25+
def handle_get(self, ctx: Context):
2626
"""Return all items."""
27-
return JsonResponse({"items": items})
27+
ctx.json({"items": items})
2828

29-
def handle_post(self, request):
29+
def handle_post(self, ctx: Context):
3030
"""Create a new item."""
3131
global next_id
32-
data = request.body
32+
data = ctx.body
3333

3434
# Simple validation
3535
if not data or "name" not in data:
@@ -44,26 +44,26 @@ def handle_post(self, request):
4444
items.append(new_item)
4545
next_id += 1
4646

47-
return JsonResponse(new_item, status=201)
47+
ctx.json(new_item, status=201)
4848

4949

5050
class ItemDetailView(View):
5151
"""Handle operations on a single item."""
5252

53-
def handle_get(self, request):
53+
def handle_get(self, ctx: Context):
5454
"""Return a specific item by ID."""
55-
item_id = int(request.path_params["id"])
55+
item_id = int(ctx.path_params["id"])
5656
item = next((item for item in items if item["id"] == item_id), None)
5757

5858
if not item:
5959
raise HTTPException("Item not found", status_code=404)
6060

61-
return JsonResponse(item)
61+
ctx.json(item)
6262

63-
def handle_put(self, request):
63+
def handle_put(self, ctx: Context):
6464
"""Update a specific item."""
65-
item_id = int(request.path_params["id"])
66-
data = request.body
65+
item_id = int(ctx.path_params["id"])
66+
data = ctx.body
6767

6868
# Find the item
6969
item_index = next(
@@ -78,20 +78,20 @@ def handle_put(self, request):
7878
"description", items[item_index]["description"]
7979
)
8080

81-
return JsonResponse(items[item_index])
81+
ctx.json(items[item_index])
8282

83-
def handle_delete(self, request):
83+
def handle_delete(self, ctx: Context):
8484
"""Delete a specific item."""
8585
global items
86-
item_id = int(request.path_params["id"])
86+
item_id = int(ctx.path_params["id"])
8787

8888
# Find and remove the item
8989
item = next((item for item in items if item["id"] == item_id), None)
9090
if not item:
9191
raise HTTPException("Item not found", status_code=404)
9292

9393
items = [item for item in items if item["id"] != item_id]
94-
return JsonResponse({"message": "Item deleted"}, status=204)
94+
ctx.json({"message": "Item deleted"}, status=204)
9595

9696

9797
# Create the app

examples/config_example.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"""
99

1010
from webspark.core import View, WebSpark, path
11-
from webspark.http import HTMLResponse, JsonResponse
11+
from webspark.http import Context
1212
from webspark.utils import env
1313

1414

@@ -40,9 +40,9 @@ class AppConfig:
4040

4141
# Sample view to show configuration
4242
class ConfigView(View):
43-
def handle_get(self, request):
43+
def handle_get(self, ctx: Context):
4444
# In a real app, you wouldn't expose sensitive config like this
45-
return JsonResponse(
45+
ctx.json(
4646
{
4747
"debug": app.debug,
4848
"config": {
@@ -51,18 +51,18 @@ def handle_get(self, request):
5151
"DATABASE_URL": getattr(app.config, "DATABASE_URL", None),
5252
},
5353
"request_info": {
54-
"host": request.host,
55-
"scheme": request.scheme,
56-
"ip": request.ip,
57-
"method": request.method,
58-
"path": request.path,
54+
"host": ctx.host,
55+
"scheme": ctx.scheme,
56+
"ip": ctx.ip,
57+
"method": ctx.method,
58+
"path": ctx.path,
5959
},
6060
}
6161
)
6262

6363

6464
class HomeView(View):
65-
def handle_get(self, request):
65+
def handle_get(self, ctx: Context):
6666
html_content = (
6767
"""
6868
<!DOCTYPE html>
@@ -109,7 +109,7 @@ def handle_get(self, request):
109109
</html>
110110
"""
111111
)
112-
return HTMLResponse(html_content)
112+
ctx.html(html_content)
113113

114114

115115
# Add routes

0 commit comments

Comments
 (0)