or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

adapters.mdauthentication.mdcookies.mdexceptions.mdhooks.mdhttp-methods.mdindex.mdmodels.mdsessions.mdstatus-codes.mdstructures.md

hooks.mddocs/

0

# Event Hooks

1

2

Event hook system that allows custom functions to be called at specific points during request processing. Hooks provide a way to modify requests and responses or implement custom logging and monitoring.

3

4

## Capabilities

5

6

### Hook System

7

8

The requests library provides a simple but powerful hook system for intercepting and modifying HTTP requests and responses.

9

10

```python { .api }

11

# Available hook events

12

HOOKS: list[str] # ['response']

13

14

def default_hooks() -> dict:

15

"""

16

Return the default hooks structure.

17

18

Returns:

19

Dict with default hook configuration

20

"""

21

22

def dispatch_hook(key: str, hooks: dict, hook_data, **kwargs):

23

"""

24

Execute hooks for a given event.

25

26

Parameters:

27

- key: Hook event name

28

- hooks: Hook configuration dict

29

- hook_data: Data to pass to hook functions

30

- **kwargs: Additional arguments for hooks

31

32

Returns:

33

Result from hook execution

34

"""

35

```

36

37

### Hook Events

38

39

Currently, requests supports one hook event:

40

41

- **response**: Called after receiving a response, allows modifying the Response object

42

43

## Usage Examples

44

45

### Basic Hook Usage

46

47

```python

48

import requests

49

50

def response_hook(response, *args, **kwargs):

51

"""Custom response hook function."""

52

print(f"Received response: {response.status_code} from {response.url}")

53

# Optionally modify the response

54

response.custom_processed = True

55

return response

56

57

# Add hook to single request

58

response = requests.get('https://httpbin.org/get',

59

hooks={'response': response_hook})

60

61

print(hasattr(response, 'custom_processed')) # True

62

```

63

64

### Multiple Hooks

65

66

```python

67

import requests

68

69

def log_hook(response, *args, **kwargs):

70

"""Log response details."""

71

print(f"LOG: {response.request.method} {response.url} -> {response.status_code}")

72

return response

73

74

def timing_hook(response, *args, **kwargs):

75

"""Add timing information."""

76

print(f"TIMING: Request took {response.elapsed.total_seconds():.3f} seconds")

77

return response

78

79

def validation_hook(response, *args, **kwargs):

80

"""Validate response."""

81

if response.status_code >= 400:

82

print(f"WARNING: Error response {response.status_code}")

83

return response

84

85

# Multiple hooks for the same event

86

hooks = {

87

'response': [log_hook, timing_hook, validation_hook]

88

}

89

90

response = requests.get('https://httpbin.org/status/404', hooks=hooks)

91

# Prints:

92

# LOG: GET https://httpbin.org/status/404 -> 404

93

# TIMING: Request took 0.123 seconds

94

# WARNING: Error response 404

95

```

96

97

### Session-Level Hooks

98

99

```python

100

import requests

101

102

def session_response_hook(response, *args, **kwargs):

103

"""Hook applied to all session requests."""

104

# Add custom header to all responses

105

response.headers['X-Custom-Processed'] = 'true'

106

107

# Log all requests

108

print(f"Session request: {response.request.method} {response.url}")

109

110

return response

111

112

# Add hook to session - applies to all requests

113

session = requests.Session()

114

session.hooks['response'].append(session_response_hook)

115

116

# All requests through this session will trigger the hook

117

response1 = session.get('https://httpbin.org/get')

118

response2 = session.post('https://httpbin.org/post', json={'key': 'value'})

119

120

print(response1.headers['X-Custom-Processed']) # 'true'

121

print(response2.headers['X-Custom-Processed']) # 'true'

122

```

123

124

### Authentication Hook

125

126

```python

127

import requests

128

import hashlib

129

import time

130

131

def custom_auth_hook(response, *args, **kwargs):

132

"""Custom authentication logic."""

133

if response.status_code == 401:

134

print("Authentication required - could trigger token refresh")

135

# In real implementation, might refresh token and retry request

136

return response

137

138

def rate_limit_hook(response, *args, **kwargs):

139

"""Handle rate limiting."""

140

if response.status_code == 429:

141

retry_after = response.headers.get('Retry-After', '60')

142

print(f"Rate limited. Retry after {retry_after} seconds")

143

# Could implement automatic retry with backoff

144

return response

145

146

# Combine authentication and rate limiting hooks

147

auth_hooks = {

148

'response': [custom_auth_hook, rate_limit_hook]

149

}

150

151

session = requests.Session()

152

session.hooks = auth_hooks

153

154

response = session.get('https://httpbin.org/status/429')

155

# Prints: Rate limited. Retry after 60 seconds

156

```

157

158

### Request Modification Hook

159

160

```python

161

import requests

162

163

def add_user_agent_hook(response, *args, **kwargs):

164

"""Add custom user agent information to response for logging."""

165

original_ua = response.request.headers.get('User-Agent', 'Unknown')

166

print(f"Request made with User-Agent: {original_ua}")

167

168

# Add metadata to response for later use

169

response.user_agent_info = {

170

'original_ua': original_ua,

171

'timestamp': time.time()

172

}

173

return response

174

175

# Apply hook

176

response = requests.get('https://httpbin.org/user-agent',

177

hooks={'response': add_user_agent_hook})

178

179

print(response.user_agent_info)

180

```

181

182

### Error Handling Hook

183

184

```python

185

import requests

186

import logging

187

188

# Set up logging

189

logging.basicConfig(level=logging.INFO)

190

logger = logging.getLogger(__name__)

191

192

def error_logging_hook(response, *args, **kwargs):

193

"""Log errors and add context."""

194

if response.status_code >= 400:

195

logger.error(f"HTTP Error {response.status_code}: {response.url}")

196

logger.error(f"Response: {response.text[:200]}...")

197

198

# Add error context to response

199

response.error_logged = True

200

response.error_timestamp = time.time()

201

202

return response

203

204

def retry_header_hook(response, *args, **kwargs):

205

"""Extract retry information from headers."""

206

if response.status_code in [429, 503]:

207

retry_after = response.headers.get('Retry-After')

208

if retry_after:

209

response.retry_after_seconds = int(retry_after)

210

logger.info(f"Server suggests retry after {retry_after} seconds")

211

212

return response

213

214

# Error handling hook chain

215

error_hooks = {

216

'response': [error_logging_hook, retry_header_hook]

217

}

218

219

response = requests.get('https://httpbin.org/status/503', hooks=error_hooks)

220

print(f"Error logged: {getattr(response, 'error_logged', False)}")

221

```

222

223

### Performance Monitoring Hook

224

225

```python

226

import requests

227

import time

228

229

class PerformanceMonitor:

230

"""Performance monitoring via hooks."""

231

232

def __init__(self):

233

self.stats = {}

234

235

def response_hook(self, response, *args, **kwargs):

236

"""Monitor response performance."""

237

url = response.url

238

elapsed = response.elapsed.total_seconds()

239

status = response.status_code

240

size = len(response.content)

241

242

# Track stats

243

if url not in self.stats:

244

self.stats[url] = []

245

246

self.stats[url].append({

247

'elapsed': elapsed,

248

'status': status,

249

'size': size,

250

'timestamp': time.time()

251

})

252

253

print(f"PERF: {url} -> {status} ({elapsed:.3f}s, {size} bytes)")

254

return response

255

256

def get_stats(self):

257

"""Get performance statistics."""

258

return self.stats

259

260

# Use performance monitor

261

monitor = PerformanceMonitor()

262

263

session = requests.Session()

264

session.hooks['response'].append(monitor.response_hook)

265

266

# Make several requests

267

urls = [

268

'https://httpbin.org/get',

269

'https://httpbin.org/json',

270

'https://httpbin.org/html'

271

]

272

273

for url in urls:

274

response = session.get(url)

275

276

# View stats

277

stats = monitor.get_stats()

278

for url, measurements in stats.items():

279

avg_time = sum(m['elapsed'] for m in measurements) / len(measurements)

280

print(f"Average time for {url}: {avg_time:.3f}s")

281

```

282

283

### Custom Response Processing Hook

284

285

```python

286

import requests

287

import json

288

289

def json_response_hook(response, *args, **kwargs):

290

"""Automatically parse JSON responses."""

291

content_type = response.headers.get('content-type', '')

292

293

if 'application/json' in content_type:

294

try:

295

response.parsed_json = response.json()

296

response.json_parsed = True

297

except ValueError:

298

response.json_parsed = False

299

response.parsed_json = None

300

else:

301

response.json_parsed = False

302

response.parsed_json = None

303

304

return response

305

306

def xml_response_hook(response, *args, **kwargs):

307

"""Handle XML responses."""

308

content_type = response.headers.get('content-type', '')

309

310

if 'xml' in content_type:

311

response.is_xml = True

312

# Could parse XML here with lxml or xml module

313

else:

314

response.is_xml = False

315

316

return response

317

318

# Content processing hooks

319

content_hooks = {

320

'response': [json_response_hook, xml_response_hook]

321

}

322

323

response = requests.get('https://httpbin.org/json', hooks=content_hooks)

324

print(f"JSON parsed: {response.json_parsed}")

325

print(f"Data: {response.parsed_json}")

326

```

327

328

### Hook Registration Patterns

329

330

```python

331

import requests

332

333

# Pattern 1: Direct function assignment

334

def my_hook(response, *args, **kwargs):

335

return response

336

337

hooks = {'response': my_hook}

338

339

# Pattern 2: List of functions

340

hooks = {'response': [hook1, hook2, hook3]}

341

342

# Pattern 3: Adding to existing hooks

343

session = requests.Session()

344

session.hooks['response'].append(my_hook)

345

346

# Pattern 4: Replacing all hooks

347

session.hooks['response'] = [my_hook]

348

349

# Pattern 5: Using Request object

350

req = requests.Request('GET', 'https://httpbin.org/get', hooks=hooks)

351

prepared = req.prepare()

352

353

with requests.Session() as session:

354

response = session.send(prepared)

355

```

356

357

## Hook Best Practices

358

359

### Hook Function Signature

360

361

```python

362

def my_hook(response, *args, **kwargs):

363

"""

364

Standard hook function signature.

365

366

Parameters:

367

- response: Response object being processed

368

- *args: Additional positional arguments

369

- **kwargs: Additional keyword arguments

370

371

Returns:

372

Modified or original Response object

373

"""

374

# Process response

375

return response

376

```

377

378

### Error Handling in Hooks

379

380

```python

381

def safe_hook(response, *args, **kwargs):

382

"""Hook with proper error handling."""

383

try:

384

# Hook logic here

385

response.custom_data = process_response(response)

386

except Exception as e:

387

# Log error but don't break the request

388

print(f"Hook error: {e}")

389

response.hook_error = str(e)

390

391

return response

392

```

393

394

### Performance Considerations

395

396

```python

397

def efficient_hook(response, *args, **kwargs):

398

"""Efficient hook implementation."""

399

# Only process if needed

400

if should_process(response):

401

# Minimal processing

402

response.processed = True

403

404

return response

405

406

def should_process(response):

407

"""Determine if processing is needed."""

408

return response.headers.get('content-type') == 'application/json'

409

```

410

411

## Hook Limitations

412

413

1. **Limited Events**: Only 'response' hook is currently supported

414

2. **No Request Hooks**: Cannot modify requests before sending

415

3. **Exception Handling**: Hook exceptions can break request processing

416

4. **Performance Impact**: Hooks add processing overhead

417

5. **State Management**: Hooks are stateless - use closures or classes for state

418

419

## Advanced Hook Patterns

420

421

### Hook Factory

422

423

```python

424

def create_logging_hook(logger_name):

425

"""Factory function to create logging hooks."""

426

import logging

427

logger = logging.getLogger(logger_name)

428

429

def logging_hook(response, *args, **kwargs):

430

logger.info(f"{response.request.method} {response.url} -> {response.status_code}")

431

return response

432

433

return logging_hook

434

435

# Create specialized hooks

436

api_hook = create_logging_hook('api_client')

437

web_hook = create_logging_hook('web_scraper')

438

439

# Use different hooks for different purposes

440

api_session = requests.Session()

441

api_session.hooks['response'].append(api_hook)

442

443

web_session = requests.Session()

444

web_session.hooks['response'].append(web_hook)

445

```

446

447

### Conditional Hooks

448

449

```python

450

def conditional_hook(condition_func):

451

"""Create a hook that only runs when condition is met."""

452

def decorator(hook_func):

453

def wrapper(response, *args, **kwargs):

454

if condition_func(response):

455

return hook_func(response, *args, **kwargs)

456

return response

457

return wrapper

458

return decorator

459

460

# Conditional hook decorators

461

@conditional_hook(lambda r: r.status_code >= 400)

462

def error_only_hook(response, *args, **kwargs):

463

print(f"Error response: {response.status_code}")

464

return response

465

466

@conditional_hook(lambda r: 'json' in r.headers.get('content-type', ''))

467

def json_only_hook(response, *args, **kwargs):

468

print("Processing JSON response")

469

return response

470

```