or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

context.mdcore-application.mdhelpers.mdindex.mdrequest-response.mdsignals.mdtemplates.mdtesting.mdwebsocket.md

signals.mddocs/

0

# Signal System

1

2

Event-driven hooks for application lifecycle, request/response processing, WebSocket handling, error management, and template rendering with comprehensive signal support for monitoring and extending application behavior.

3

4

## Capabilities

5

6

### Application Lifecycle Signals

7

8

Signals fired during application context creation, destruction, and teardown phases.

9

10

```python { .api }

11

# Application context signals

12

appcontext_pushed: Namespace

13

"""

14

Signal sent when application context is pushed.

15

16

Sender: The application instance

17

Arguments: None

18

19

Usage:

20

@appcontext_pushed.connect

21

def on_app_context_pushed(sender, **extra):

22

# Application context is now available

23

pass

24

"""

25

26

appcontext_popped: Namespace

27

"""

28

Signal sent when application context is popped.

29

30

Sender: The application instance

31

Arguments: None

32

33

Usage:

34

@appcontext_popped.connect

35

def on_app_context_popped(sender, **extra):

36

# Application context is being removed

37

pass

38

"""

39

40

appcontext_tearing_down: Namespace

41

"""

42

Signal sent when application context is tearing down.

43

44

Sender: The application instance

45

Arguments:

46

exc: Exception that caused teardown (None for normal teardown)

47

48

Usage:

49

@appcontext_tearing_down.connect

50

def on_app_context_teardown(sender, exc=None, **extra):

51

# Clean up app context resources

52

if exc:

53

# Handle teardown due to exception

54

pass

55

"""

56

```

57

58

### Request Lifecycle Signals

59

60

Signals for HTTP request processing lifecycle events.

61

62

```python { .api }

63

# Request processing signals

64

request_started: Namespace

65

"""

66

Signal sent when request processing starts.

67

68

Sender: The application instance

69

Arguments: None

70

71

Usage:

72

@request_started.connect

73

def on_request_started(sender, **extra):

74

# Request processing has begun

75

g.start_time = time.time()

76

"""

77

78

request_finished: Namespace

79

"""

80

Signal sent when request processing finishes successfully.

81

82

Sender: The application instance

83

Arguments:

84

response: The response object

85

86

Usage:

87

@request_finished.connect

88

def on_request_finished(sender, response, **extra):

89

# Request completed successfully

90

duration = time.time() - g.start_time

91

log_request(request, response, duration)

92

"""

93

94

request_tearing_down: Namespace

95

"""

96

Signal sent when request context is tearing down.

97

98

Sender: The application instance

99

Arguments:

100

exc: Exception that caused teardown (None for normal teardown)

101

102

Usage:

103

@request_tearing_down.connect

104

def on_request_teardown(sender, exc=None, **extra):

105

# Clean up request resources

106

if hasattr(g, 'db_connection'):

107

g.db_connection.close()

108

"""

109

```

110

111

### WebSocket Lifecycle Signals

112

113

Signals for WebSocket connection lifecycle events.

114

115

```python { .api }

116

# WebSocket processing signals

117

websocket_started: Namespace

118

"""

119

Signal sent when WebSocket processing starts.

120

121

Sender: The application instance

122

Arguments: None

123

124

Usage:

125

@websocket_started.connect

126

def on_websocket_started(sender, **extra):

127

# WebSocket connection has begun

128

active_connections.add(websocket)

129

"""

130

131

websocket_finished: Namespace

132

"""

133

Signal sent when WebSocket processing finishes.

134

135

Sender: The application instance

136

Arguments: None

137

138

Usage:

139

@websocket_finished.connect

140

def on_websocket_finished(sender, **extra):

141

# WebSocket connection ended

142

active_connections.discard(websocket)

143

"""

144

145

websocket_tearing_down: Namespace

146

"""

147

Signal sent when WebSocket context is tearing down.

148

149

Sender: The application instance

150

Arguments:

151

exc: Exception that caused teardown (None for normal teardown)

152

153

Usage:

154

@websocket_tearing_down.connect

155

def on_websocket_teardown(sender, exc=None, **extra):

156

# Clean up WebSocket resources

157

cleanup_websocket_resources()

158

"""

159

```

160

161

### Template Signals

162

163

Signals fired during template rendering operations.

164

165

```python { .api }

166

# Template rendering signals

167

before_render_template: Namespace

168

"""

169

Signal sent before template rendering begins.

170

171

Sender: The application instance

172

Arguments:

173

template: The template object

174

context: Template context dictionary

175

176

Usage:

177

@before_render_template.connect

178

def on_before_render(sender, template, context, **extra):

179

# Modify context or log template access

180

context['render_start_time'] = time.time()

181

"""

182

183

template_rendered: Namespace

184

"""

185

Signal sent after template has been rendered.

186

187

Sender: The application instance

188

Arguments:

189

template: The template object

190

context: Template context dictionary

191

192

Usage:

193

@template_rendered.connect

194

def on_template_rendered(sender, template, context, **extra):

195

# Log template rendering or clean up

196

duration = time.time() - context.get('render_start_time', 0)

197

log_template_render(template.name, duration)

198

"""

199

```

200

201

### Exception Signals

202

203

Signals for error handling and exception management.

204

205

```python { .api }

206

# Exception handling signals

207

got_request_exception: Namespace

208

"""

209

Signal sent when request processing encounters an exception.

210

211

Sender: The application instance

212

Arguments:

213

exception: The exception object

214

215

Usage:

216

@got_request_exception.connect

217

def on_request_exception(sender, exception, **extra):

218

# Log or handle request exceptions

219

error_tracker.report_exception(exception, request)

220

"""

221

222

got_websocket_exception: Namespace

223

"""

224

Signal sent when WebSocket processing encounters an exception.

225

226

Sender: The application instance

227

Arguments:

228

exception: The exception object

229

230

Usage:

231

@got_websocket_exception.connect

232

def on_websocket_exception(sender, exception, **extra):

233

# Log or handle WebSocket exceptions

234

error_tracker.report_websocket_exception(exception, websocket)

235

"""

236

237

got_background_exception: Namespace

238

"""

239

Signal sent when background task encounters an exception.

240

241

Sender: The application instance

242

Arguments:

243

exception: The exception object

244

245

Usage:

246

@got_background_exception.connect

247

def on_background_exception(sender, exception, **extra):

248

# Handle background task exceptions

249

background_error_handler.handle(exception)

250

"""

251

252

got_serving_exception: Namespace

253

"""

254

Signal sent when server encounters an exception during serving.

255

256

Sender: The application instance

257

Arguments:

258

exception: The exception object

259

260

Usage:

261

@got_serving_exception.connect

262

def on_serving_exception(sender, exception, **extra):

263

# Handle server-level exceptions

264

server_monitor.report_exception(exception)

265

"""

266

```

267

268

### Flash Message Signals

269

270

Signals for flash message system events.

271

272

```python { .api }

273

# Flash messaging signals

274

message_flashed: Namespace

275

"""

276

Signal sent when a flash message is added.

277

278

Sender: The application instance

279

Arguments:

280

message: The flash message text

281

category: The message category

282

283

Usage:

284

@message_flashed.connect

285

def on_message_flashed(sender, message, category, **extra):

286

# Log or process flash messages

287

audit_log.record_flash_message(message, category)

288

"""

289

```

290

291

### Signal Availability

292

293

Constant indicating whether signal support is available.

294

295

```python { .api }

296

signals_available: bool

297

"""

298

Boolean indicating whether signal support is available.

299

300

Value: True if blinker is installed, False otherwise

301

302

Usage:

303

if signals_available:

304

# Use signal-based features

305

setup_signal_handlers()

306

else:

307

# Fallback to non-signal approach

308

setup_polling_monitor()

309

"""

310

```

311

312

### Usage Examples

313

314

#### Application Monitoring

315

316

```python

317

from quart import Quart, g

318

from quart.signals import (

319

request_started, request_finished, request_tearing_down,

320

got_request_exception, appcontext_pushed, appcontext_popped

321

)

322

import time

323

324

app = Quart(__name__)

325

326

# Request timing and monitoring

327

@request_started.connect_via(app)

328

def on_request_start(sender, **extra):

329

g.request_start_time = time.time()

330

g.request_id = generate_request_id()

331

332

@request_finished.connect_via(app)

333

def on_request_finish(sender, response, **extra):

334

duration = time.time() - g.request_start_time

335

336

# Log request metrics

337

metrics.record_request_duration(duration)

338

metrics.increment(f'requests.status.{response.status_code}')

339

340

# Add timing header

341

response.headers['X-Response-Time'] = f'{duration:.3f}s'

342

response.headers['X-Request-ID'] = g.request_id

343

344

@got_request_exception.connect_via(app)

345

def on_request_exception(sender, exception, **extra):

346

# Log exception with context

347

error_logger.error(

348

f"Request {g.request_id} failed: {exception}",

349

extra={

350

'request_id': g.request_id,

351

'path': request.path,

352

'method': request.method,

353

'user_agent': request.headers.get('User-Agent'),

354

'duration': time.time() - g.request_start_time

355

}

356

)

357

358

# Report to error tracking service

359

error_tracker.capture_exception(exception, {

360

'request_id': g.request_id,

361

'request': {

362

'url': request.url,

363

'method': request.method,

364

'headers': dict(request.headers)

365

}

366

})

367

368

@request_tearing_down.connect_via(app)

369

def cleanup_request(sender, exc=None, **extra):

370

# Clean up request-specific resources

371

if hasattr(g, 'database_connection'):

372

g.database_connection.close()

373

374

if hasattr(g, 'cache_client'):

375

g.cache_client.disconnect()

376

```

377

378

#### WebSocket Connection Management

379

380

```python

381

from quart import Quart, websocket

382

from quart.signals import (

383

websocket_started, websocket_finished, websocket_tearing_down,

384

got_websocket_exception

385

)

386

387

app = Quart(__name__)

388

389

# Track active WebSocket connections

390

active_websockets = set()

391

websocket_stats = {'total_connections': 0, 'active_connections': 0}

392

393

@websocket_started.connect_via(app)

394

def on_websocket_start(sender, **extra):

395

active_websockets.add(websocket)

396

websocket_stats['total_connections'] += 1

397

websocket_stats['active_connections'] = len(active_websockets)

398

399

# Log connection details

400

app.logger.info(f"WebSocket connected from {websocket.remote_addr}, "

401

f"total active: {len(active_websockets)}")

402

403

@websocket_finished.connect_via(app)

404

def on_websocket_finish(sender, **extra):

405

active_websockets.discard(websocket)

406

websocket_stats['active_connections'] = len(active_websockets)

407

408

app.logger.info(f"WebSocket disconnected, "

409

f"remaining active: {len(active_websockets)}")

410

411

@websocket_tearing_down.connect_via(app)

412

def cleanup_websocket(sender, exc=None, **extra):

413

# Clean up WebSocket-specific resources

414

if hasattr(g, 'websocket_subscriptions'):

415

for subscription in g.websocket_subscriptions:

416

subscription.unsubscribe()

417

418

@got_websocket_exception.connect_via(app)

419

def on_websocket_exception(sender, exception, **extra):

420

app.logger.error(f"WebSocket error: {exception}")

421

422

# Attempt graceful cleanup

423

try:

424

await websocket.close(code=1011, reason="Internal server error")

425

except:

426

pass

427

428

@app.websocket('/ws/stats')

429

async def websocket_stats_endpoint():

430

await websocket.accept()

431

432

try:

433

while True:

434

await websocket.send_json(websocket_stats)

435

await asyncio.sleep(5)

436

except ConnectionClosed:

437

pass

438

```

439

440

#### Template Rendering Analytics

441

442

```python

443

from quart import Quart, render_template

444

from quart.signals import before_render_template, template_rendered

445

import time

446

from collections import defaultdict, Counter

447

448

app = Quart(__name__)

449

450

# Template rendering statistics

451

template_stats = {

452

'render_counts': Counter(),

453

'render_times': defaultdict(list),

454

'context_sizes': defaultdict(list)

455

}

456

457

@before_render_template.connect_via(app)

458

def on_before_render(sender, template, context, **extra):

459

# Record template access

460

template_stats['render_counts'][template.name] += 1

461

462

# Store render start time in context

463

context['_render_start'] = time.time()

464

465

# Record context size

466

context_size = len(str(context))

467

template_stats['context_sizes'][template.name].append(context_size)

468

469

# Add common template variables

470

context['app_version'] = app.config.get('VERSION', '1.0.0')

471

context['render_timestamp'] = time.time()

472

473

@template_rendered.connect_via(app)

474

def on_template_rendered(sender, template, context, **extra):

475

# Calculate render time

476

if '_render_start' in context:

477

render_time = time.time() - context['_render_start']

478

template_stats['render_times'][template.name].append(render_time)

479

480

# Log slow templates

481

if render_time > 0.1: # 100ms threshold

482

app.logger.warning(f"Slow template render: {template.name} "

483

f"took {render_time:.3f}s")

484

485

@app.route('/template-stats')

486

async def template_statistics():

487

# Calculate statistics

488

stats = {}

489

for template_name in template_stats['render_counts']:

490

times = template_stats['render_times'][template_name]

491

sizes = template_stats['context_sizes'][template_name]

492

493

stats[template_name] = {

494

'render_count': template_stats['render_counts'][template_name],

495

'avg_render_time': sum(times) / len(times) if times else 0,

496

'max_render_time': max(times) if times else 0,

497

'avg_context_size': sum(sizes) / len(sizes) if sizes else 0

498

}

499

500

return await render_template('template_stats.html', stats=stats)

501

```

502

503

#### Error Tracking and Alerting

504

505

```python

506

from quart import Quart, request

507

from quart.signals import (

508

got_request_exception, got_websocket_exception,

509

got_background_exception, got_serving_exception

510

)

511

from collections import deque

512

import asyncio

513

514

app = Quart(__name__)

515

516

# Error tracking

517

recent_errors = deque(maxlen=100) # Keep last 100 errors

518

error_counts = Counter()

519

520

async def send_alert(error_type, exception, context=None):

521

"""Send alert for critical errors."""

522

if app.config.get('ALERTS_ENABLED'):

523

alert_data = {

524

'type': error_type,

525

'exception': str(exception),

526

'timestamp': time.time(),

527

'context': context or {}

528

}

529

530

# Send to monitoring service

531

await monitoring_service.send_alert(alert_data)

532

533

@got_request_exception.connect_via(app)

534

def on_request_error(sender, exception, **extra):

535

error_type = type(exception).__name__

536

error_counts[error_type] += 1

537

538

error_data = {

539

'type': 'request_error',

540

'exception': str(exception),

541

'exception_type': error_type,

542

'path': request.path,

543

'method': request.method,

544

'timestamp': time.time()

545

}

546

547

recent_errors.append(error_data)

548

549

# Send alert for critical errors

550

if error_type in ['DatabaseError', 'ExternalServiceError']:

551

asyncio.create_task(send_alert('request_error', exception, {

552

'path': request.path,

553

'method': request.method

554

}))

555

556

@got_websocket_exception.connect_via(app)

557

def on_websocket_error(sender, exception, **extra):

558

error_type = type(exception).__name__

559

error_counts[f'websocket_{error_type}'] += 1

560

561

error_data = {

562

'type': 'websocket_error',

563

'exception': str(exception),

564

'exception_type': error_type,

565

'path': websocket.path,

566

'timestamp': time.time()

567

}

568

569

recent_errors.append(error_data)

570

571

@got_background_exception.connect_via(app)

572

def on_background_error(sender, exception, **extra):

573

error_type = type(exception).__name__

574

error_counts[f'background_{error_type}'] += 1

575

576

# Background errors are often critical

577

asyncio.create_task(send_alert('background_error', exception))

578

579

@app.route('/error-dashboard')

580

async def error_dashboard():

581

return await render_template('error_dashboard.html',

582

recent_errors=list(recent_errors),

583

error_counts=dict(error_counts))

584

```

585

586

#### Signal-Based Plugin System

587

588

```python

589

from quart import Quart

590

from quart.signals import appcontext_pushed

591

from blinker import Namespace

592

593

app = Quart(__name__)

594

595

# Custom application signals

596

app_signals = Namespace()

597

user_logged_in = app_signals.signal('user-logged-in')

598

data_updated = app_signals.signal('data-updated')

599

600

# Plugin registration system

601

@appcontext_pushed.connect_via(app)

602

def load_plugins(sender, **extra):

603

if not hasattr(g, 'plugins_loaded'):

604

# Load and initialize plugins

605

for plugin_name in app.config.get('ENABLED_PLUGINS', []):

606

plugin = load_plugin(plugin_name)

607

plugin.initialize(app)

608

609

g.plugins_loaded = True

610

611

# Plugin example: Audit logging

612

class AuditLogPlugin:

613

def initialize(self, app):

614

user_logged_in.connect(self.log_user_login, app)

615

data_updated.connect(self.log_data_update, app)

616

617

def log_user_login(self, sender, user, **extra):

618

audit_log.info(f"User {user.username} logged in from {request.remote_addr}")

619

620

def log_data_update(self, sender, model, action, **extra):

621

audit_log.info(f"Data update: {model.__class__.__name__} {action}")

622

623

# Usage in application code

624

@app.route('/login', methods=['POST'])

625

async def login():

626

user = await authenticate_user(request.form)

627

if user:

628

session['user_id'] = user.id

629

630

# Emit custom signal

631

user_logged_in.send(app, user=user)

632

633

return redirect(url_for('dashboard'))

634

635

@app.route('/api/data', methods=['POST'])

636

async def update_data():

637

data = await request.get_json()

638

model = await update_model(data)

639

640

# Emit custom signal

641

data_updated.send(app, model=model, action='update')

642

643

return jsonify({'status': 'updated'})

644

```