or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli-commands.mdconfiguration.mdenvironment.mdhooks-plugins.mdindex.mdtask-execution.mdutilities.md

hooks-plugins.mddocs/

0

# Hooks and Plugin System

1

2

Event-driven plugin architecture enabling extensibility through Actions, Filters, and Contexts. The hooks system provides lifecycle management and data transformation capabilities, allowing plugins to customize every aspect of Tutor's behavior from configuration to deployment.

3

4

## Capabilities

5

6

### Action Hooks

7

8

Event callbacks that execute at specific lifecycle points without modifying data flow.

9

10

```python { .api }

11

class Action[T]:

12

"""

13

Action hook for event callbacks at specific lifecycle points.

14

"""

15

def add(self, priority: int = None) -> Callable:

16

"""

17

Decorator to add callback function with optional priority.

18

19

Args:

20

priority (int, optional): Execution priority (lower numbers run first)

21

22

Returns:

23

Callable: Decorator function

24

"""

25

26

def do(self, *args, **kwargs) -> None:

27

"""

28

Execute all registered callbacks with provided arguments.

29

30

Args:

31

*args: Positional arguments passed to callbacks

32

**kwargs: Keyword arguments passed to callbacks

33

"""

34

35

def do_from_context(self, context: Context, *args, **kwargs) -> None:

36

"""

37

Execute callbacks from specific context only.

38

39

Args:

40

context (Context): Context to execute callbacks from

41

*args: Positional arguments passed to callbacks

42

**kwargs: Keyword arguments passed to callbacks

43

"""

44

45

def clear(self, context: Context = None) -> None:

46

"""

47

Clear callbacks from specific context or all contexts.

48

49

Args:

50

context (Context, optional): Context to clear, or None for all

51

"""

52

```

53

54

### Filter Hooks

55

56

Data transformation chains that modify values as they pass through the system.

57

58

```python { .api }

59

class Filter[T1, T2]:

60

"""

61

Filter hook for data transformation chains.

62

"""

63

def add(self, priority: int = None) -> Callable:

64

"""

65

Decorator to add filter callback with optional priority.

66

67

Args:

68

priority (int, optional): Execution priority (lower numbers run first)

69

70

Returns:

71

Callable: Decorator function

72

"""

73

74

def apply(self, value: T1, *args, **kwargs) -> T2:

75

"""

76

Apply filter chain to transform the input value.

77

78

Args:

79

value (T1): Input value to transform

80

*args: Additional arguments passed to filters

81

**kwargs: Additional keyword arguments passed to filters

82

83

Returns:

84

T2: Transformed value after applying all filters

85

"""

86

87

def add_item(self, item: Any) -> None:

88

"""

89

Add single item to list-type filters.

90

91

Args:

92

item (Any): Item to add to the list

93

"""

94

95

def add_items(self, *items: Any) -> None:

96

"""

97

Add multiple items to list-type filters.

98

99

Args:

100

*items (Any): Items to add to the list

101

"""

102

```

103

104

### Context System

105

106

Isolated execution contexts for organizing hooks by scope and lifecycle.

107

108

```python { .api }

109

class Context:

110

"""

111

Context for isolating hooks by scope and lifecycle.

112

"""

113

def __init__(self, name: str):

114

"""

115

Create a new context.

116

117

Args:

118

name (str): Context name identifier

119

"""

120

121

# Context instances

122

class Contexts:

123

APP: Dict[str, Context] # Per-application contexts

124

PLUGINS: Context # Plugin-specific context

125

PLUGINS_V0_YAML: Context # YAML v0 plugin context

126

PLUGINS_V0_ENTRYPOINT: Context # Entrypoint plugin context

127

```

128

129

### Core Actions Catalog

130

131

Predefined actions for key lifecycle events in Tutor.

132

133

```python { .api }

134

# Core lifecycle actions

135

COMPOSE_PROJECT_STARTED: Action[str, Config, str] # Triggered when compose project starts

136

CONFIG_INTERACTIVE: Action[Config] # After interactive configuration questions

137

CONFIG_LOADED: Action[Config] # After configuration loading (read-only)

138

CORE_READY: Action[] # Core system ready for plugin discovery

139

DO_JOB: Action[str, Any] # Before job task execution

140

PLUGIN_LOADED: Action[str] # Single plugin loaded

141

PLUGINS_LOADED: Action[] # All plugins loaded

142

PLUGIN_UNLOADED: Action[str, str, Config] # Plugin unloaded

143

PROJECT_ROOT_READY: Action[str] # Project root directory ready

144

```

145

146

### Core Filters Catalog

147

148

Predefined filters for data transformation throughout Tutor.

149

150

```python { .api }

151

# Application and CLI filters

152

APP_PUBLIC_HOSTS: Filter[list[str], list[str]] # Public application hostnames

153

CLI_COMMANDS: Filter[list[click.Command], list[click.Command]] # CLI command list

154

CLI_DO_COMMANDS: Filter[list[Callable], list[Callable]] # "do" subcommands

155

CLI_DO_INIT_TASKS: Filter[list[tuple[str, str]], list[tuple[str, str]]] # Initialization tasks

156

157

# Configuration filters

158

CONFIG_DEFAULTS: Filter[list[tuple[str, Any]], list[tuple[str, Any]]] # Default configuration

159

CONFIG_OVERRIDES: Filter[list[tuple[str, Any]], list[tuple[str, Any]]] # Configuration overrides

160

CONFIG_UNIQUE: Filter[list[tuple[str, Any]], list[tuple[str, Any]]] # Unique configuration values

161

CONFIG_USER: Filter[list[tuple[str, Any]], list[tuple[str, Any]]] # User configuration

162

163

# Environment and template filters

164

ENV_PATCHES: Filter[list[tuple[str, str]], list[tuple[str, str]]] # Template patches

165

ENV_PATTERNS_IGNORE: Filter[list[str], list[str]] # Ignored template patterns

166

ENV_PATTERNS_INCLUDE: Filter[list[str], list[str]] # Included template patterns

167

ENV_TEMPLATE_FILTERS: Filter[list[tuple[str, Callable]], list[tuple[str, Callable]]] # Jinja2 filters

168

ENV_TEMPLATE_ROOTS: Filter[list[str], list[str]] # Template root directories

169

ENV_TEMPLATE_TARGETS: Filter[list[tuple[str, str]], list[tuple[str, str]]] # Template targets

170

ENV_TEMPLATE_VARIABLES: Filter[list[tuple[str, Any]], list[tuple[str, Any]]] # Template variables

171

172

# Docker and image filters

173

DOCKER_BUILD_COMMAND: Filter[list[str], list[str]] # Docker build command modification

174

IMAGES_BUILD: Filter[list[tuple], list[tuple]] # Images to build

175

IMAGES_BUILD_REQUIRED: Filter[list[str], list[str]] # Required images for build

176

IMAGES_BUILD_MOUNTS: Filter[list[tuple[str, str]], list[tuple[str, str]]] # Build-time mounts

177

IMAGES_PULL: Filter[list[tuple[str, str]], list[tuple[str, str]]] # Images to pull

178

IMAGES_PUSH: Filter[list[tuple[str, str]], list[tuple[str, str]]] # Images to push

179

180

# Plugin and mount filters

181

COMPOSE_MOUNTS: Filter[list[tuple[str, str]], list[tuple[str, str]]] # Compose bind mounts

182

MOUNTED_DIRECTORIES: Filter[list[tuple[str, str]], list[tuple[str, str]]] # Auto-mounted directories

183

PLUGIN_INDEXES: Filter[list[str], list[str]] # Plugin index URLs

184

PLUGINS_INFO: Filter[list[tuple[str, str]], list[tuple[str, str]]] # Plugin information

185

PLUGINS_INSTALLED: Filter[list[str], list[str]] # Installed plugins list

186

PLUGINS_LOADED: Filter[list[str], list[str]] # Loaded plugins list

187

```

188

189

### Plugin Management

190

191

Functions for installing, enabling, and managing plugins.

192

193

```python { .api }

194

def is_installed(name: str) -> bool:

195

"""

196

Check if a plugin is installed.

197

198

Args:

199

name (str): Plugin name

200

201

Returns:

202

bool: True if plugin is installed

203

"""

204

205

def iter_installed() -> Iterator[str]:

206

"""

207

Iterate over installed plugin names.

208

209

Returns:

210

Iterator[str]: Iterator of installed plugin names

211

"""

212

213

def iter_info() -> Iterator[tuple[str, Optional[str]]]:

214

"""

215

Iterate over plugin information (name, description).

216

217

Returns:

218

Iterator[tuple[str, Optional[str]]]: Iterator of (name, description) tuples

219

"""

220

221

def is_loaded(name: str) -> bool:

222

"""

223

Check if a plugin is currently loaded.

224

225

Args:

226

name (str): Plugin name

227

228

Returns:

229

bool: True if plugin is loaded

230

"""

231

232

def load_all(names: Iterable[str]) -> None:

233

"""

234

Load multiple plugins.

235

236

Args:

237

names (Iterable[str]): Plugin names to load

238

"""

239

240

def load(name: str) -> None:

241

"""

242

Load a single plugin.

243

244

Args:

245

name (str): Plugin name to load

246

"""

247

248

def iter_loaded() -> Iterator[str]:

249

"""

250

Iterate over loaded plugin names.

251

252

Returns:

253

Iterator[str]: Iterator of loaded plugin names

254

"""

255

256

def iter_patches(name: str) -> Iterator[str]:

257

"""

258

Get template patches provided by a plugin.

259

260

Args:

261

name (str): Plugin name

262

263

Returns:

264

Iterator[str]: Iterator of patch names

265

"""

266

267

def unload(plugin: str) -> None:

268

"""

269

Unload a plugin.

270

271

Args:

272

plugin (str): Plugin name to unload

273

"""

274

```

275

276

### Hook Utilities

277

278

Utility functions for hook management and caching.

279

280

```python { .api }

281

def clear_all(context: Context = None) -> None:

282

"""

283

Clear all hooks from specific context or all contexts.

284

285

Args:

286

context (Context, optional): Context to clear, or None for all

287

"""

288

289

def lru_cache(func: Callable) -> Callable:

290

"""

291

LRU cache decorator that clears when plugins change.

292

293

Args:

294

func (Callable): Function to cache

295

296

Returns:

297

Callable: Cached function

298

"""

299

300

# Priority constants

301

class priorities:

302

HIGH = 10

303

DEFAULT = 50

304

LOW = 90

305

```

306

307

## Usage Examples

308

309

### Creating Action Hooks

310

311

```python

312

from tutor import hooks

313

314

# Define custom action

315

MY_CUSTOM_ACTION = hooks.Actions.create("my-custom-action")

316

317

# Add callback to action

318

@MY_CUSTOM_ACTION.add()

319

def my_callback(arg1: str, arg2: int) -> None:

320

print(f"Action called with {arg1} and {arg2}")

321

322

# Trigger the action

323

MY_CUSTOM_ACTION.do("hello", 42)

324

```

325

326

### Creating Filter Hooks

327

328

```python

329

from tutor import hooks

330

331

# Define custom filter

332

MY_CUSTOM_FILTER = hooks.Filters.create("my-custom-filter")

333

334

# Add filter callback

335

@MY_CUSTOM_FILTER.add()

336

def my_filter(value: str, context: str) -> str:

337

return f"{context}: {value.upper()}"

338

339

# Apply the filter

340

result = MY_CUSTOM_FILTER.apply("hello world", "debug")

341

# Result: "debug: HELLO WORLD"

342

```

343

344

### Plugin Development

345

346

```python

347

from tutor import hooks

348

from tutor.plugins import base

349

350

class MyPlugin(base.BasePlugin):

351

def __init__(self):

352

super().__init__("my-plugin", "My Custom Plugin")

353

354

def configure(self) -> None:

355

# Add configuration defaults

356

hooks.Filters.CONFIG_DEFAULTS.add_items([

357

("MY_PLUGIN_SETTING", "default_value"),

358

("MY_PLUGIN_ENABLED", True),

359

])

360

361

# Add template patches

362

hooks.Filters.ENV_PATCHES.add_items([

363

("docker-compose.yml", "my-plugin/docker-compose.patch"),

364

("lms.yml", "my-plugin/lms.patch"),

365

])

366

367

# Add CLI commands

368

@hooks.Filters.CLI_COMMANDS.add()

369

def add_my_command():

370

return [self.create_command()]

371

372

# React to lifecycle events

373

@hooks.Actions.PLUGINS_LOADED.add()

374

def on_plugins_loaded():

375

print("All plugins loaded, initializing my plugin")

376

```

377

378

### Working with Built-in Hooks

379

380

```python

381

from tutor import hooks

382

383

# Add custom CLI command

384

@hooks.Filters.CLI_COMMANDS.add()

385

def add_custom_command():

386

import click

387

388

@click.command()

389

def my_command():

390

"""My custom command."""

391

click.echo("Custom command executed!")

392

393

return [my_command]

394

395

# Add configuration default

396

hooks.Filters.CONFIG_DEFAULTS.add_items([

397

("CUSTOM_SETTING", "my_value")

398

])

399

400

# React to configuration loading

401

@hooks.Actions.CONFIG_LOADED.add()

402

def on_config_loaded(config):

403

print(f"Configuration loaded with {len(config)} settings")

404

```