or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

arguments.mdcaching.mdconfiguration.mdcontrollers.mdextensions.mdfoundation.mdhooks.mdindex.mdinterface-handler.mdlogging.mdmail.mdoutput.mdplugins.mdtemplates.mdutilities.md

hooks.mddocs/

0

# Hook System

1

2

The hook system provides extension points throughout the application lifecycle, enabling custom functionality injection at defined points in application execution. Hooks allow developers to extend and customize application behavior without modifying core framework code.

3

4

## Capabilities

5

6

### Hook Manager

7

8

Manages application hooks including hook definition, registration, and execution.

9

10

```python { .api }

11

class HookManager:

12

"""

13

Manages application hooks for extending functionality at defined points.

14

15

The hook manager provides centralized control over hook registration,

16

execution, and lifecycle management.

17

"""

18

19

def define(self, name: str) -> None:

20

"""

21

Define a new hook in the system.

22

23

Args:

24

name: Hook name to define

25

"""

26

27

def defined(self, name: str) -> bool:

28

"""

29

Check if a hook is defined.

30

31

Args:

32

name: Hook name to check

33

34

Returns:

35

True if hook is defined, False otherwise

36

"""

37

38

def register(self, name: str, func: Callable, weight: int = 0) -> None:

39

"""

40

Register a function to run when hook is executed.

41

42

Args:

43

name: Hook name to register for

44

func: Function to execute when hook runs

45

weight: Execution weight (lower weights run first)

46

"""

47

48

def run(self, name: str, app: 'App', *args: Any, **kwargs: Any) -> None:

49

"""

50

Execute all registered functions for a hook.

51

52

Args:

53

name: Hook name to execute

54

app: Application instance

55

*args: Additional arguments passed to hook functions

56

**kwargs: Additional keyword arguments passed to hook functions

57

"""

58

```

59

60

## Built-in Hook Points

61

62

The framework provides several built-in hooks for extending functionality:

63

64

- `pre_setup` - Before application setup

65

- `post_setup` - After application setup

66

- `pre_run` - Before application run

67

- `post_run` - After application run

68

- `pre_close` - Before application close

69

- `post_close` - After application close

70

- `signal` - When signals are caught

71

- `pre_render` - Before output rendering

72

- `post_render` - After output rendering

73

74

## Usage Examples

75

76

### Registering Hook Functions

77

78

```python

79

from cement import App, Controller, ex

80

81

def my_pre_setup_hook(app):

82

"""Hook function that runs before app setup."""

83

print('Running pre-setup initialization')

84

# Initialize custom components

85

app.custom_data = {'initialized': True}

86

87

def my_post_setup_hook(app):

88

"""Hook function that runs after app setup."""

89

print('Running post-setup configuration')

90

# Validate configuration

91

if not hasattr(app, 'custom_data'):

92

raise Exception('Custom data not initialized')

93

94

def my_pre_run_hook(app):

95

"""Hook function that runs before app execution."""

96

print(f'Starting application: {app._meta.label}')

97

# Log application start

98

app.log.info('Application execution started')

99

100

def my_post_run_hook(app):

101

"""Hook function that runs after app execution."""

102

print('Application execution completed')

103

# Cleanup or final logging

104

app.log.info('Application execution completed')

105

106

class BaseController(Controller):

107

class Meta:

108

label = 'base'

109

110

@ex(help='test command')

111

def test(self):

112

"""Test command to demonstrate hooks."""

113

print('Executing test command')

114

print(f'Custom data: {self.app.custom_data}')

115

116

class MyApp(App):

117

class Meta:

118

label = 'myapp'

119

base_controller = 'base'

120

handlers = [BaseController]

121

122

def load_hooks(app):

123

"""Load application hooks."""

124

app.hook.register('pre_setup', my_pre_setup_hook)

125

app.hook.register('post_setup', my_post_setup_hook)

126

app.hook.register('pre_run', my_pre_run_hook)

127

app.hook.register('post_run', my_post_run_hook)

128

129

with MyApp() as app:

130

load_hooks(app)

131

app.run()

132

```

133

134

### Hook Weights and Execution Order

135

136

```python

137

from cement import App

138

139

def first_hook(app):

140

"""Hook with lowest weight runs first."""

141

print('First hook (weight: -10)')

142

143

def middle_hook(app):

144

"""Hook with default weight."""

145

print('Middle hook (weight: 0)')

146

147

def last_hook(app):

148

"""Hook with highest weight runs last."""

149

print('Last hook (weight: 10)')

150

151

class MyApp(App):

152

class Meta:

153

label = 'myapp'

154

155

def load_hooks(app):

156

"""Load hooks with different weights."""

157

app.hook.register('pre_setup', first_hook, weight=-10)

158

app.hook.register('pre_setup', middle_hook, weight=0)

159

app.hook.register('pre_setup', last_hook, weight=10)

160

161

with MyApp() as app:

162

load_hooks(app)

163

app.setup()

164

app.run()

165

166

# Output:

167

# First hook (weight: -10)

168

# Middle hook (weight: 0)

169

# Last hook (weight: 10)

170

```

171

172

### Custom Hook Definition

173

174

```python

175

from cement import App, Controller, ex

176

177

def database_connect_hook(app, connection_string):

178

"""Custom hook for database connection."""

179

print(f'Connecting to database: {connection_string}')

180

# Simulate database connection

181

app.db_connected = True

182

183

def database_migrate_hook(app):

184

"""Custom hook for database migration."""

185

if not getattr(app, 'db_connected', False):

186

raise Exception('Database not connected')

187

print('Running database migrations')

188

app.db_migrated = True

189

190

def validate_database_hook(app):

191

"""Custom hook for database validation."""

192

if not getattr(app, 'db_migrated', False):

193

raise Exception('Database not migrated')

194

print('Database validation successful')

195

196

class DatabaseController(Controller):

197

class Meta:

198

label = 'database'

199

stacked_on = 'base'

200

stacked_type = 'nested'

201

202

@ex(help='initialize database')

203

def init(self):

204

"""Initialize database system."""

205

# Define and run custom hooks

206

if not self.app.hook.defined('database_connect'):

207

self.app.hook.define('database_connect')

208

if not self.app.hook.defined('database_migrate'):

209

self.app.hook.define('database_migrate')

210

if not self.app.hook.defined('database_validate'):

211

self.app.hook.define('database_validate')

212

213

# Run hooks in sequence

214

self.app.hook.run('database_connect', self.app, 'postgresql://localhost/myapp')

215

self.app.hook.run('database_migrate', self.app)

216

self.app.hook.run('database_validate', self.app)

217

218

print('Database initialization completed')

219

220

class BaseController(Controller):

221

class Meta:

222

label = 'base'

223

224

class MyApp(App):

225

class Meta:

226

label = 'myapp'

227

base_controller = 'base'

228

handlers = [BaseController, DatabaseController]

229

230

def load_hooks(app):

231

"""Load custom database hooks."""

232

# Register hooks (they will be defined when needed)

233

app.hook.register('database_connect', database_connect_hook)

234

app.hook.register('database_migrate', database_migrate_hook)

235

app.hook.register('database_validate', validate_database_hook)

236

237

with MyApp() as app:

238

load_hooks(app)

239

app.run()

240

241

# Usage:

242

# myapp database init

243

```

244

245

### Extension Loading Hooks

246

247

```python

248

from cement import App

249

250

def load_redis_extension(app):

251

"""Hook to load Redis extension if needed."""

252

if app.config.get('myapp', 'cache_handler') == 'redis':

253

print('Loading Redis extension for caching')

254

app.extend('redis', app)

255

256

def load_jinja2_extension(app):

257

"""Hook to load Jinja2 extension if needed."""

258

if app.config.get('myapp', 'template_handler') == 'jinja2':

259

print('Loading Jinja2 extension for templating')

260

app.extend('jinja2', app)

261

262

def validate_extensions(app):

263

"""Hook to validate required extensions are loaded."""

264

required_extensions = ['colorlog', 'yaml']

265

for ext in required_extensions:

266

if ext not in app.loaded_extensions:

267

print(f'Warning: Required extension {ext} not loaded')

268

269

class MyApp(App):

270

class Meta:

271

label = 'myapp'

272

extensions = ['colorlog', 'yaml']

273

274

def load_hooks(app):

275

"""Load extension management hooks."""

276

app.hook.register('post_setup', load_redis_extension, weight=-5)

277

app.hook.register('post_setup', load_jinja2_extension, weight=-5)

278

app.hook.register('post_setup', validate_extensions, weight=5)

279

280

with MyApp() as app:

281

load_hooks(app)

282

app.setup()

283

app.run()

284

```

285

286

### Configuration Validation Hooks

287

288

```python

289

from cement import App, init_defaults

290

291

CONFIG = init_defaults('myapp', 'database')

292

CONFIG['myapp']['environment'] = 'development'

293

CONFIG['database']['host'] = 'localhost'

294

295

def validate_environment_config(app):

296

"""Validate environment-specific configuration."""

297

env = app.config.get('myapp', 'environment')

298

299

if env == 'production':

300

required_keys = ['database.host', 'database.password', 'database.ssl']

301

for key_path in required_keys:

302

section, key = key_path.split('.')

303

try:

304

value = app.config.get(section, key)

305

if not value:

306

raise ValueError(f'Production environment requires {key_path}')

307

except KeyError:

308

raise ValueError(f'Production environment requires {key_path}')

309

print('Production configuration validated')

310

else:

311

print(f'Configuration validated for {env} environment')

312

313

def setup_logging_config(app):

314

"""Configure logging based on environment."""

315

env = app.config.get('myapp', 'environment')

316

317

if env == 'production':

318

app.log.set_level('WARNING')

319

print('Logging set to WARNING level for production')

320

else:

321

app.log.set_level('DEBUG')

322

print('Logging set to DEBUG level for development')

323

324

class MyApp(App):

325

class Meta:

326

label = 'myapp'

327

config_defaults = CONFIG

328

329

def load_hooks(app):

330

"""Load configuration hooks."""

331

app.hook.register('post_setup', validate_environment_config, weight=-10)

332

app.hook.register('post_setup', setup_logging_config, weight=-5)

333

334

with MyApp() as app:

335

load_hooks(app)

336

app.setup()

337

app.run()

338

```

339

340

### Signal Handling Hooks

341

342

```python

343

from cement import App

344

import signal

345

346

def cleanup_resources(app, signum, frame):

347

"""Hook to cleanup resources when signal received."""

348

print(f'Received signal {signum}, cleaning up resources')

349

350

# Close database connections

351

if hasattr(app, 'db_connection'):

352

app.db_connection.close()

353

print('Database connection closed')

354

355

# Save application state

356

if hasattr(app, 'state'):

357

with open('/tmp/app_state.json', 'w') as f:

358

json.dump(app.state, f)

359

print('Application state saved')

360

361

def log_signal_received(app, signum, frame):

362

"""Hook to log signal reception."""

363

signal_names = {

364

signal.SIGTERM: 'SIGTERM',

365

signal.SIGINT: 'SIGINT',

366

signal.SIGHUP: 'SIGHUP'

367

}

368

signal_name = signal_names.get(signum, f'Signal {signum}')

369

app.log.warning(f'Received {signal_name}, initiating shutdown')

370

371

class MyApp(App):

372

class Meta:

373

label = 'myapp'

374

catch_signals = [signal.SIGTERM, signal.SIGINT, signal.SIGHUP]

375

376

def load_hooks(app):

377

"""Load signal handling hooks."""

378

app.hook.register('signal', cleanup_resources, weight=-10)

379

app.hook.register('signal', log_signal_received, weight=-5)

380

381

with MyApp() as app:

382

load_hooks(app)

383

app.setup()

384

385

# Simulate some application state

386

app.state = {'last_action': 'startup', 'user_count': 0}

387

388

app.run()

389

```

390

391

### Render Hooks for Output Processing

392

393

```python

394

from cement import App, Controller, ex

395

import json

396

397

def add_metadata_to_output(app, data, rendered_output):

398

"""Hook to add metadata to rendered output."""

399

if isinstance(data, dict):

400

# Add timestamp and version metadata

401

import datetime

402

data['_metadata'] = {

403

'timestamp': datetime.datetime.now().isoformat(),

404

'version': app.config.get('myapp', 'version', '1.0.0'),

405

'environment': app.config.get('myapp', 'environment', 'development')

406

}

407

408

def log_render_operation(app, data, rendered_output):

409

"""Hook to log render operations."""

410

data_size = len(str(data))

411

output_size = len(rendered_output)

412

app.log.debug(f'Rendered {data_size} bytes of data to {output_size} bytes of output')

413

414

class BaseController(Controller):

415

class Meta:

416

label = 'base'

417

418

@ex(help='get user information')

419

def user_info(self):

420

"""Get user information with metadata."""

421

user_data = {

422

'id': 123,

423

'name': 'John Doe',

424

'email': 'john@example.com',

425

'status': 'active'

426

}

427

428

# Render with hooks

429

output = self.app.render(user_data)

430

print(output)

431

432

class MyApp(App):

433

class Meta:

434

label = 'myapp'

435

base_controller = 'base'

436

handlers = [BaseController]

437

438

def load_hooks(app):

439

"""Load render hooks."""

440

app.hook.register('pre_render', add_metadata_to_output)

441

app.hook.register('post_render', log_render_operation)

442

443

with MyApp() as app:

444

load_hooks(app)

445

app.run()

446

447

# Usage:

448

# myapp user-info

449

```