0
# Core Server Functionality
1
2
This document covers the core server-side components of BlackSheep: Application management, routing, middleware, dependency injection, and static file serving.
3
4
## Application Class
5
6
The `Application` class is the main entry point for BlackSheep web applications, providing configuration, routing, middleware management, and lifecycle control.
7
8
### Basic Application Setup
9
10
```python { .api }
11
from blacksheep import Application
12
from rodi import Container
13
from typing import Optional
14
15
# Basic application
16
app = Application()
17
18
# Application with configuration
19
app = Application(
20
debug=True, # Enable debug mode
21
show_error_details=True, # Show detailed error pages
22
services=Container(), # Custom DI container
23
router=None, # Custom router (optional)
24
mount=None # Mount registry (optional)
25
)
26
```
27
28
### Application Configuration Methods
29
30
```python { .api }
31
# CORS configuration
32
cors_strategy = app.use_cors(
33
allow_origins="*",
34
allow_methods=["GET", "POST", "PUT", "DELETE"],
35
allow_headers=["Content-Type", "Authorization"],
36
allow_credentials=True
37
)
38
39
# Named CORS policies
40
app.add_cors_policy("api",
41
allow_origins=["https://app.example.com"],
42
allow_methods=["GET", "POST"]
43
)
44
45
# Authentication setup
46
auth_strategy = app.use_authentication()
47
auth_strategy.add_jwt_bearer(
48
valid_audiences=["api"],
49
authority="https://auth.example.com"
50
)
51
52
# Authorization setup
53
authz_strategy = app.use_authorization()
54
55
# Session management
56
app.use_sessions(
57
secret_key="your-secret-key",
58
session_cookie="session",
59
session_max_age=3600
60
)
61
62
# Template engines
63
app.use_templates()
64
65
# Controller registration
66
app.use_controllers()
67
```
68
69
### Static File Serving
70
71
```python { .api }
72
from pathlib import Path
73
from blacksheep import DefaultFileOptions
74
75
# Basic static file serving
76
app.serve_files("./static", root_path="/static")
77
78
# Advanced static file configuration
79
app.serve_files(
80
source_folder=Path("./static"),
81
discovery=True, # Allow directory listing
82
cache_time=86400, # Cache for 24 hours
83
extensions={".jpg", ".png", ".css", ".js"}, # Allowed extensions
84
root_path="/assets", # URL prefix
85
index_document="index.html", # Directory index
86
fallback_document="404.html", # Fallback for SPA
87
allow_anonymous=True, # No auth required
88
default_file_options=DefaultFileOptions(
89
cache_time=3600,
90
content_disposition_type="inline"
91
)
92
)
93
```
94
95
### Template Engine Integration
96
97
BlackSheep provides built-in Jinja2 template engine integration for server-side HTML rendering.
98
99
```python { .api }
100
from blacksheep import Application, use_templates
101
from blacksheep.server.templating import view, view_async
102
from jinja2 import PackageLoader, Environment
103
104
# Setup templates with PackageLoader
105
app = Application()
106
loader = PackageLoader("myapp", "templates")
107
view_function = use_templates(app, loader, enable_async=True)
108
109
# Template rendering functions
110
def template_name(name: str) -> str:
111
"""Ensures template name has .html extension"""
112
if not name.endswith(".html"):
113
return name + ".html"
114
return name
115
116
def render_template(template: Template, *args, **kwargs) -> str:
117
"""Render template synchronously"""
118
return template.render(*args, **kwargs)
119
120
async def render_template_async(template: Template, *args, **kwargs) -> str:
121
"""Render template asynchronously"""
122
return await template.render_async(*args, **kwargs)
123
124
def view(jinja_environment: Environment, name: str, model: Any = None, **kwargs) -> Response:
125
"""Returns HTML Response from synchronous template rendering"""
126
pass
127
128
async def view_async(jinja_environment: Environment, name: str, model: Any = None, **kwargs) -> Response:
129
"""Returns HTML Response from asynchronous template rendering"""
130
pass
131
```
132
133
#### Template Usage Examples
134
135
```python
136
# Using templates in route handlers
137
@app.get("/users/{user_id}")
138
async def user_profile(user_id: int, jinja: Environment):
139
user = await get_user(user_id)
140
return await view_async(jinja, "profile", {"user": user})
141
142
# Using the view function returned by use_templates
143
view_function = use_templates(app, PackageLoader("myapp", "templates"))
144
145
@app.get("/dashboard")
146
async def dashboard():
147
data = {"title": "Dashboard", "items": await get_items()}
148
return await view_function("dashboard", data)
149
```
150
151
### Application Events
152
153
```python { .api }
154
# Startup events
155
@app.on_start
156
async def configure_database():
157
"""Initialize database connections"""
158
await database.connect()
159
160
@app.after_start
161
async def log_startup():
162
"""Log after startup complete"""
163
print("Application started successfully")
164
165
# Shutdown events
166
@app.on_stop
167
async def cleanup():
168
"""Cleanup resources"""
169
await database.disconnect()
170
171
# Middleware configuration event
172
@app.on_middlewares_configuration
173
def configure_middlewares():
174
"""Configure middleware after all setup"""
175
app.middlewares.append(custom_middleware)
176
```
177
178
### Exception Handling
179
180
```python { .api }
181
from blacksheep.exceptions import HTTPException, BadRequest
182
183
# Handle specific HTTP status codes
184
@app.exception_handler(404)
185
async def not_found_handler(app: Application, request: Request, exc: HTTPException):
186
return Response(404, content=TextContent("Custom 404 page"))
187
188
# Handle specific exception types
189
@app.exception_handler(ValueError)
190
async def value_error_handler(app: Application, request: Request, exc: ValueError):
191
return Response(400, content=JSONContent({"error": str(exc)}))
192
193
# Handle all HTTP exceptions
194
@app.exception_handler(HTTPException)
195
async def http_exception_handler(app: Application, request: Request, exc: HTTPException):
196
return Response(exc.status, content=JSONContent({
197
"error": "HTTP Error",
198
"status": exc.status,
199
"message": str(exc)
200
}))
201
```
202
203
### Lifecycle Management
204
205
```python { .api }
206
# Lifespan context managers
207
@app.lifespan
208
@asynccontextmanager
209
async def app_lifespan():
210
"""Manage application lifecycle"""
211
# Startup
212
await initialize_resources()
213
yield
214
# Shutdown
215
await cleanup_resources()
216
217
# Manual startup/shutdown
218
await app.start() # Start application
219
await app.stop() # Stop application
220
221
# Application mounting
222
sub_app = Application()
223
app.mount("/api/v2", sub_app) # Mount sub-application
224
```
225
226
## Routing System
227
228
BlackSheep provides a powerful routing system with pattern matching, parameter extraction, and type conversion.
229
230
### Route Patterns
231
232
```python { .api }
233
from blacksheep import Route, Router
234
235
# Basic route patterns
236
@app.route("/") # Static route
237
@app.route("/users") # Static path
238
@app.route("/users/{user_id}") # Parameter capture
239
@app.route("/posts/{post_id}/comments") # Multiple segments
240
241
# Typed parameters
242
@app.route("/users/{user_id:int}") # Integer parameter
243
@app.route("/files/{file_path:path}") # Path parameter (allows /)
244
@app.route("/products/{price:float}") # Float parameter
245
@app.route("/items/{uuid:uuid}") # UUID parameter
246
@app.route("/docs/{name:str}") # String parameter (default)
247
```
248
249
### HTTP Method Decorators
250
251
```python { .api }
252
# HTTP method decorators
253
@app.get("/users")
254
async def get_users():
255
return json([{"id": 1, "name": "Alice"}])
256
257
@app.post("/users")
258
async def create_user(data: FromJSON[dict]):
259
return json({"created": True, "data": data.value})
260
261
@app.put("/users/{user_id:int}")
262
async def update_user(user_id: int, data: FromJSON[dict]):
263
return json({"id": user_id, "updated": True})
264
265
@app.delete("/users/{user_id:int}")
266
async def delete_user(user_id: int):
267
return json({"id": user_id, "deleted": True})
268
269
@app.patch("/users/{user_id:int}")
270
async def partial_update(user_id: int, data: FromJSON[dict]):
271
return json({"id": user_id, "patched": True})
272
273
# Multiple methods
274
@app.route("/users/{user_id:int}", methods=["GET", "PUT"])
275
async def user_handler(request: Request, user_id: int):
276
if request.method == "GET":
277
return json({"id": user_id})
278
elif request.method == "PUT":
279
data = await request.json()
280
return json({"id": user_id, "updated": data})
281
```
282
283
### Router Class
284
285
```python { .api }
286
from blacksheep.server.routing import Router, RouteMatch
287
288
# Manual router usage
289
router = Router()
290
router.add("GET", "/users", get_users_handler)
291
router.add("POST", "/users", create_user_handler)
292
293
# Find matching routes
294
match = router.get_match("GET", b"/users/123")
295
if match:
296
handler = match.handler
297
route_values = match.values # {"user_id": "123"}
298
299
# Route sorting (by specificity)
300
router.sort_routes()
301
```
302
303
### Route Registry
304
305
```python { .api }
306
from blacksheep.server.routing import RoutesRegistry, RegisteredRoute
307
308
# Route storage without matching
309
registry = RoutesRegistry()
310
registry.add("GET", "/users", handler)
311
312
# Iterate routes
313
for route in registry:
314
print(f"{route.method} {route.pattern} -> {route.handler}")
315
```
316
317
## Middleware System
318
319
BlackSheep uses a middleware pipeline for request/response processing with both built-in and custom middleware support.
320
321
### Built-in Middleware
322
323
```python { .api }
324
# CORS middleware
325
app.use_cors(
326
allow_origins=["https://example.com"],
327
allow_methods=["GET", "POST", "PUT", "DELETE"],
328
allow_headers=["Content-Type", "Authorization"],
329
allow_credentials=True,
330
max_age=3600
331
)
332
333
# Authentication middleware (automatic when using auth)
334
app.use_authentication()
335
336
# Authorization middleware (automatic when using authz)
337
app.use_authorization()
338
339
# Session middleware (automatic when using sessions)
340
app.use_sessions("secret-key")
341
```
342
343
### Custom Middleware
344
345
```python { .api }
346
from typing import Callable, Awaitable
347
348
# Simple middleware function
349
async def logging_middleware(request: Request, handler: Callable[[Request], Awaitable[Response]]) -> Response:
350
"""Log all requests"""
351
print(f"{request.method} {request.url.path}")
352
start_time = time.time()
353
354
response = await handler(request)
355
356
duration = time.time() - start_time
357
print(f"Response: {response.status} ({duration:.3f}s)")
358
359
return response
360
361
# Add to application
362
app.middlewares.append(logging_middleware)
363
364
# Class-based middleware
365
class TimingMiddleware:
366
async def __call__(self, request: Request, handler: Callable) -> Response:
367
start = time.time()
368
response = await handler(request)
369
response.headers.add(b"X-Response-Time", f"{time.time() - start:.3f}".encode())
370
return response
371
372
app.middlewares.append(TimingMiddleware())
373
```
374
375
### Middleware Order
376
377
```python { .api }
378
# Middleware execution order (first added = outermost)
379
app.middlewares.append(cors_middleware) # 1st - outermost
380
app.middlewares.append(auth_middleware) # 2nd
381
app.middlewares.append(logging_middleware) # 3rd
382
app.middlewares.append(timing_middleware) # 4th - innermost
383
384
# Request flow: cors -> auth -> logging -> timing -> handler
385
# Response flow: handler -> timing -> logging -> auth -> cors
386
```
387
388
## Dependency Injection
389
390
BlackSheep uses the `rodi` library for dependency injection, providing service registration and automatic resolution.
391
392
### Service Registration
393
394
```python { .api }
395
from rodi import Container
396
from typing import Protocol
397
398
# Service interfaces
399
class DatabaseService(Protocol):
400
async def get_user(self, user_id: int) -> dict: ...
401
402
class UserRepository:
403
def __init__(self, db: DatabaseService):
404
self.db = db
405
406
async def find_by_id(self, user_id: int) -> dict:
407
return await self.db.get_user(user_id)
408
409
# Service registration
410
container = Container()
411
container.add_singleton(DatabaseService, PostgresDatabase)
412
container.add_scoped(UserRepository) # New instance per request
413
container.add_transient(SomeService) # New instance each injection
414
415
app = Application(services=container)
416
417
# Service resolution in handlers
418
@app.get("/users/{user_id:int}")
419
async def get_user(user_id: int, repo: FromServices[UserRepository]):
420
user = await repo.find_by_id(user_id)
421
return json(user)
422
```
423
424
### Service Lifetimes
425
426
```python { .api }
427
# Singleton - single instance for application
428
container.add_singleton(DatabaseConnection)
429
430
# Scoped - single instance per request
431
container.add_scoped(UserService)
432
433
# Transient - new instance each time
434
container.add_transient(TemporaryService)
435
436
# Instance registration
437
container.add_instance(config_instance)
438
439
# Factory registration
440
container.add_factory(lambda: create_complex_service())
441
```
442
443
### Service Configuration
444
445
```python { .api }
446
# Configure services with settings
447
@app.on_start
448
async def configure_services():
449
"""Configure services at startup"""
450
db_service = app.services.get(DatabaseService)
451
await db_service.initialize()
452
453
cache_service = app.services.get(CacheService)
454
await cache_service.connect()
455
456
# Access service provider
457
service_provider = app.service_provider
458
user_service = service_provider.get(UserService)
459
```
460
461
## Application Mounting
462
463
Mount other ASGI applications within BlackSheep applications.
464
465
### Basic Mounting
466
467
```python { .api }
468
from blacksheep.server.routing import MountRegistry
469
470
# Mount FastAPI app
471
import fastapi
472
fastapi_app = fastapi.FastAPI()
473
app.mount("/legacy", fastapi_app)
474
475
# Mount static ASGI app
476
def static_app(scope, receive, send):
477
# Custom ASGI application
478
pass
479
480
app.mount("/custom", static_app)
481
482
# Mount with registry
483
mount_registry = MountRegistry(auto_events=True, handle_docs=True)
484
app = Application(mount=mount_registry)
485
```
486
487
### Mount Configuration
488
489
```python { .api }
490
# Mount registry options
491
mount_registry = MountRegistry(
492
auto_events=True, # Bind lifecycle events
493
handle_docs=False # Don't handle documentation
494
)
495
496
# Multiple mounts
497
app.mount("/api/v1", v1_app)
498
app.mount("/api/v2", v2_app)
499
app.mount("/admin", admin_app)
500
app.mount("/docs", docs_app)
501
```
502
503
## Error Handling
504
505
Comprehensive error handling with custom error pages and logging.
506
507
### HTTP Exceptions
508
509
```python { .api }
510
from blacksheep.exceptions import (
511
HTTPException, BadRequest, Unauthorized, Forbidden,
512
NotFound, InternalServerError, InvalidArgument,
513
BadRequestFormat, RangeNotSatisfiable, MessageAborted,
514
NotImplementedByServer, InvalidOperation
515
)
516
517
# Raise HTTP exceptions
518
async def protected_handler():
519
if not user.is_authenticated:
520
raise Unauthorized("Please log in")
521
522
if not user.has_permission("admin"):
523
raise Forbidden("Admin access required")
524
525
if not user.exists:
526
raise NotFound()
527
528
# Additional HTTP exceptions
529
async def advanced_error_handling():
530
# Bad request with detailed format error
531
try:
532
data = parse_invalid_json()
533
except ValueError as e:
534
raise BadRequestFormat("Invalid JSON format", e)
535
536
# Range request handling
537
if not range_satisfiable(request_range):
538
raise RangeNotSatisfiable("Requested range not available")
539
540
# Feature not implemented
541
if feature_not_supported():
542
raise NotImplementedByServer("Feature not available")
543
544
# Invalid operation
545
if operation_not_allowed():
546
raise InvalidOperation("Operation not permitted in current state")
547
548
# Message aborted during upload
549
@app.post("/upload")
550
async def handle_upload(request: Request):
551
try:
552
content = await request.body()
553
except MessageAborted:
554
return Response(400, content=TextContent("Upload interrupted"))
555
556
# Custom HTTP exception
557
class RateLimitExceeded(HTTPException):
558
def __init__(self):
559
super().__init__(429, "Rate limit exceeded")
560
561
@app.exception_handler(RateLimitExceeded)
562
async def handle_rate_limit(app, request, exc):
563
return Response(429, content=JSONContent({
564
"error": "Too many requests",
565
"retry_after": 60
566
}))
567
```
568
569
### Error Details Handler
570
571
```python { .api }
572
from blacksheep.server.errors import ServerErrorDetailsHandler
573
574
# Custom error details
575
class CustomErrorHandler(ServerErrorDetailsHandler):
576
async def handle_error_details(self, request: Request, exc: Exception) -> Response:
577
if app.debug:
578
return Response(500, content=JSONContent({
579
"error": str(exc),
580
"type": type(exc).__name__,
581
"traceback": traceback.format_exc()
582
}))
583
return Response(500, content=TextContent("Internal Server Error"))
584
585
app.server_error_details_handler = CustomErrorHandler()
586
```
587
588
This comprehensive core server functionality provides the foundation for building scalable web applications with BlackSheep. The framework's modular design allows you to use only the components you need while maintaining high performance and type safety.