or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

application.mdcli-commands.mdconfiguration.mdenvironment-management.mdindex.mdplugin-system.mdproject-management.mdpython-management.md

plugin-system.mddocs/

0

# Plugin System

1

2

Extensible plugin architecture supporting environment types, publishers, project templates, and environment collectors. Provides hook specifications and plugin manager for registration and discovery.

3

4

## Capabilities

5

6

### Plugin Manager

7

8

Central manager for plugin discovery, registration, and lifecycle management across all plugin types.

9

10

```python { .api }

11

class PluginManager:

12

"""

13

Plugin manager for discovering and managing hatch plugins.

14

15

Handles registration and discovery of environment, publisher, template,

16

and environment collector plugins through the hook system.

17

"""

18

19

def __init__(self):

20

"""Initialize plugin manager."""

21

22

def initialize(self) -> None:

23

"""

24

Initialize plugin system and discover available plugins.

25

26

This method must be called before using any plugin functionality.

27

"""

28

29

def hatch_register_environment(self) -> dict[str, type]:

30

"""

31

Get registered environment plugins.

32

33

Returns:

34

Dict mapping plugin names to environment plugin classes

35

"""

36

37

def hatch_register_publisher(self) -> dict[str, type]:

38

"""

39

Get registered publisher plugins.

40

41

Returns:

42

Dict mapping plugin names to publisher plugin classes

43

"""

44

45

def hatch_register_template(self) -> dict[str, type]:

46

"""

47

Get registered template plugins.

48

49

Returns:

50

Dict mapping plugin names to template plugin classes

51

"""

52

53

def hatch_register_environment_collector(self) -> dict[str, type]:

54

"""

55

Get registered environment collector plugins.

56

57

Returns:

58

Dict mapping plugin names to collector plugin classes

59

"""

60

61

def list_name_plugin(self) -> list[tuple[str, str]]:

62

"""

63

List all registered plugins with their types.

64

65

Returns:

66

List of (plugin_name, plugin_type) tuples

67

"""

68

69

def get_plugin(self, plugin_type: str, plugin_name: str) -> type | None:

70

"""

71

Get specific plugin class by type and name.

72

73

Args:

74

plugin_type (str): Type of plugin ('environment', 'publisher', etc.)

75

plugin_name (str): Name of the plugin

76

77

Returns:

78

Plugin class or None if not found

79

"""

80

```

81

82

### Hook Specifications

83

84

Hook specifications define the plugin registration interface that plugins must implement to be discovered by hatch.

85

86

```python { .api }

87

def hatch_register_environment():

88

"""

89

Hook for registering environment plugins.

90

91

Environment plugins must implement the EnvironmentInterface and provide

92

isolated execution environments for project operations.

93

94

Returns:

95

Dict mapping environment type names to plugin classes

96

"""

97

98

def hatch_register_environment_collector():

99

"""

100

Hook for registering environment collector plugins.

101

102

Environment collectors discover and provide environment configurations

103

from various sources like Docker Compose, tox.ini, etc.

104

105

Returns:

106

Dict mapping collector names to collector classes

107

"""

108

109

def hatch_register_publisher():

110

"""

111

Hook for registering publisher plugins.

112

113

Publisher plugins handle uploading packages to various indices

114

and repositories with authentication and metadata support.

115

116

Returns:

117

Dict mapping publisher names to publisher classes

118

"""

119

120

def hatch_register_template():

121

"""

122

Hook for registering template plugins.

123

124

Template plugins provide project scaffolding and initialization

125

templates for creating new projects with predefined structures.

126

127

Returns:

128

Dict mapping template names to template classes

129

"""

130

131

def hatch_register_version_scheme():

132

"""

133

Hook for registering version scheme plugins.

134

135

Version scheme plugins handle version parsing, comparison,

136

and manipulation for different versioning systems.

137

138

Returns:

139

Dict mapping scheme names to version scheme classes

140

"""

141

```

142

143

### Environment Plugin Interface

144

145

Base interface that all environment plugins must implement to provide environment management capabilities.

146

147

```python { .api }

148

class EnvironmentInterface:

149

"""

150

Base interface for environment plugins.

151

152

Environment plugins provide isolated execution environments for

153

project operations including dependency management and command execution.

154

"""

155

156

PLUGIN_NAME: str = '' # Must be overridden by implementations

157

158

def __init__(self, root, metadata, name, config, matrix_variables, data_directory, platform, verbosity, app):

159

"""

160

Initialize environment plugin.

161

162

Args:

163

root: Project root directory

164

metadata: Project metadata

165

name (str): Environment name

166

config (dict): Environment configuration

167

matrix_variables (dict): Matrix variables for this environment

168

data_directory: Data directory for environment storage

169

platform: Platform utilities

170

verbosity (int): Verbosity level

171

app: Application instance

172

"""

173

174

# Core abstract methods that must be implemented

175

def exists(self) -> bool:

176

"""Check if environment exists."""

177

178

def create(self) -> None:

179

"""Create the environment."""

180

181

def remove(self) -> None:

182

"""Remove the environment."""

183

184

def install_project(self) -> None:

185

"""Install project in the environment."""

186

187

def install_project_dev_mode(self) -> None:

188

"""Install project in development mode."""

189

190

def dependencies_in_sync(self) -> bool:

191

"""Check if dependencies are synchronized."""

192

193

def sync_dependencies(self) -> None:

194

"""Synchronize dependencies with configuration."""

195

```

196

197

### Publisher Plugin Interface

198

199

Base interface that all publisher plugins must implement to handle package publishing to various repositories.

200

201

```python { .api }

202

class PublisherInterface:

203

"""

204

Base interface for publisher plugins.

205

206

Publisher plugins handle uploading built packages to package repositories

207

with authentication, metadata validation, and upload progress tracking.

208

"""

209

210

PLUGIN_NAME: str = '' # Must be overridden by implementations

211

212

def __init__(self, app, root, cache_dir, project_config, plugin_config):

213

"""

214

Initialize publisher plugin.

215

216

Args:

217

app: Application instance

218

root: Project root directory

219

cache_dir: Cache directory for temporary files

220

project_config (dict): Project publishing configuration

221

plugin_config (dict): Plugin-specific configuration

222

"""

223

224

@property

225

def app(self):

226

"""Application instance."""

227

228

@property

229

def root(self):

230

"""Project root directory."""

231

232

@property

233

def project_config(self) -> dict:

234

"""Project publishing configuration."""

235

236

@property

237

def plugin_config(self) -> dict:

238

"""Plugin-specific configuration."""

239

240

@property

241

def disable(self) -> bool:

242

"""Whether this publisher is disabled."""

243

244

def publish(self, artifacts: list[str], options: dict) -> None:

245

"""

246

Publish artifacts to repository.

247

248

Args:

249

artifacts (list[str]): List of artifact file paths to publish

250

options (dict): Publishing options and metadata

251

"""

252

```

253

254

### Template Plugin Interface

255

256

Base interface that all template plugins must implement to provide project scaffolding capabilities.

257

258

```python { .api }

259

class TemplateInterface:

260

"""

261

Base interface for template plugins.

262

263

Template plugins provide project initialization and scaffolding

264

capabilities for creating new projects with predefined structures.

265

"""

266

267

PLUGIN_NAME: str = '' # Must be overridden by implementations

268

PRIORITY: int = 100 # Plugin priority for ordering

269

270

def __init__(self, plugin_config: dict, cache_dir, creation_time):

271

"""

272

Initialize template plugin.

273

274

Args:

275

plugin_config (dict): Plugin configuration

276

cache_dir: Cache directory for template files

277

creation_time: Template creation timestamp

278

"""

279

280

def initialize_config(self, config: dict) -> None:

281

"""

282

Initialize template configuration with user input.

283

284

Args:

285

config (dict): Configuration dictionary to populate

286

"""

287

288

def get_files(self, config: dict) -> list:

289

"""

290

Get list of template files to create.

291

292

Args:

293

config (dict): Template configuration

294

295

Returns:

296

List of File objects representing template files

297

"""

298

299

def finalize_files(self, config: dict, files: list) -> None:

300

"""

301

Finalize template files after creation.

302

303

Args:

304

config (dict): Template configuration

305

files (list): List of created File objects

306

"""

307

```

308

309

### Environment Collector Interface

310

311

Base interface for environment collector plugins that discover environment configurations from external sources.

312

313

```python { .api }

314

class EnvironmentCollectorInterface:

315

"""

316

Base interface for environment collector plugins.

317

318

Environment collectors discover and provide environment configurations

319

from external sources like Docker Compose files, tox.ini, etc.

320

"""

321

322

PLUGIN_NAME: str = '' # Must be overridden by implementations

323

324

def __init__(self, root, config):

325

"""

326

Initialize environment collector plugin.

327

328

Args:

329

root: Project root directory

330

config (dict): Collector configuration

331

"""

332

333

@property

334

def root(self):

335

"""Project root directory."""

336

337

@property

338

def config(self) -> dict:

339

"""Collector configuration."""

340

341

def get_initial_config(self) -> dict[str, dict]:

342

"""

343

Get initial environment configurations.

344

345

Returns:

346

Dict mapping environment names to configurations

347

"""

348

349

def finalize_config(self, config: dict[str, dict]) -> None:

350

"""

351

Finalize environment configurations.

352

353

Args:

354

config (dict): Environment configurations to finalize

355

"""

356

357

def finalize_environments(self, config: dict[str, dict]) -> None:

358

"""

359

Finalize environment setup after configuration.

360

361

Args:

362

config (dict): Final environment configurations

363

"""

364

```

365

366

### Plugin Registration

367

368

Utilities for registering and discovering plugins both from entry points and direct registration.

369

370

```python { .api }

371

def register_plugin(plugin_type: str, plugin_name: str, plugin_class: type) -> None:

372

"""

373

Register plugin programmatically.

374

375

Args:

376

plugin_type (str): Type of plugin ('environment', 'publisher', etc.)

377

plugin_name (str): Unique name for the plugin

378

plugin_class (type): Plugin implementation class

379

"""

380

381

def discover_plugins() -> dict[str, dict[str, type]]:

382

"""

383

Discover plugins from entry points and registered plugins.

384

385

Returns:

386

Dict mapping plugin types to dicts of plugin name -> class mappings

387

"""

388

389

def validate_plugin(plugin_class: type, plugin_type: str) -> list[str]:

390

"""

391

Validate plugin implementation against interface requirements.

392

393

Args:

394

plugin_class (type): Plugin class to validate

395

plugin_type (str): Expected plugin type

396

397

Returns:

398

List of validation errors (empty if valid)

399

"""

400

401

def load_plugin_config(plugin_name: str, config_section: dict) -> dict:

402

"""

403

Load and validate plugin configuration.

404

405

Args:

406

plugin_name (str): Name of the plugin

407

config_section (dict): Configuration section for plugin

408

409

Returns:

410

Validated plugin configuration

411

"""

412

```

413

414

## Usage Examples

415

416

### Implementing an Environment Plugin

417

418

```python

419

from hatch.env.plugin.interface import EnvironmentInterface

420

421

class DockerEnvironment(EnvironmentInterface):

422

"""Docker-based environment plugin."""

423

424

PLUGIN_NAME = 'docker'

425

426

def __init__(self, *args, **kwargs):

427

super().__init__(*args, **kwargs)

428

self.container_name = f'hatch-{self.name}-{self.root.name}'

429

430

def exists(self) -> bool:

431

"""Check if Docker container exists."""

432

result = self.platform.run_command(

433

['docker', 'inspect', self.container_name],

434

capture_output=True

435

)

436

return result.returncode == 0

437

438

def create(self) -> None:

439

"""Create Docker container environment."""

440

# Get Docker image from config

441

image = self.config.get('image', 'python:3.11')

442

443

# Create container

444

self.platform.run_command([

445

'docker', 'create',

446

'--name', self.container_name,

447

'--volume', f'{self.root}:/workspace',

448

'--workdir', '/workspace',

449

image,

450

'sleep', 'infinity'

451

])

452

453

# Start container

454

self.platform.run_command([

455

'docker', 'start', self.container_name

456

])

457

458

# Install project dependencies

459

self.sync_dependencies()

460

461

def remove(self) -> None:

462

"""Remove Docker container."""

463

self.platform.run_command([

464

'docker', 'rm', '-f', self.container_name

465

])

466

467

def sync_dependencies(self) -> None:

468

"""Install dependencies in container."""

469

if self.dependencies:

470

pip_cmd = ['pip', 'install'] + self.dependencies

471

self.run_command(pip_cmd)

472

473

def run_command(self, command: list[str], **kwargs):

474

"""Execute command in Docker container."""

475

docker_cmd = [

476

'docker', 'exec',

477

'-w', '/workspace',

478

self.container_name

479

] + command

480

481

return self.platform.run_command(docker_cmd, **kwargs)

482

483

# Register the plugin

484

def hatch_register_environment():

485

return {'docker': DockerEnvironment}

486

```

487

488

### Implementing a Publisher Plugin

489

490

```python

491

from hatch.publish.plugin.interface import PublisherInterface

492

import httpx

493

494

class CustomIndexPublisher(PublisherInterface):

495

"""Publisher for custom package index."""

496

497

PLUGIN_NAME = 'custom-index'

498

499

def publish(self, artifacts: list[str], options: dict) -> None:

500

"""Publish packages to custom index."""

501

# Get index URL from config

502

index_url = self.plugin_config.get('url', 'https://pypi.example.com')

503

username = self.plugin_config.get('username')

504

password = self.plugin_config.get('password')

505

506

# Create HTTP client

507

client = httpx.Client()

508

509

for artifact_path in artifacts:

510

self.app.display_info(f'Uploading {artifact_path}...')

511

512

# Prepare upload data

513

with open(artifact_path, 'rb') as f:

514

files = {'file': f}

515

data = {

516

'name': self.project_config.get('name'),

517

'version': self.project_config.get('version')

518

}

519

520

# Upload artifact

521

response = client.post(

522

f'{index_url}/upload',

523

files=files,

524

data=data,

525

auth=(username, password) if username else None

526

)

527

528

if response.status_code != 200:

529

self.app.abort(f'Upload failed: {response.text}')

530

531

self.app.display_success(f'Successfully uploaded {artifact_path}')

532

533

# Register the plugin

534

def hatch_register_publisher():

535

return {'custom-index': CustomIndexPublisher}

536

```

537

538

### Implementing a Template Plugin

539

540

```python

541

from hatch.template.plugin.interface import TemplateInterface

542

from hatch.template import File

543

544

class FastAPITemplate(TemplateInterface):

545

"""FastAPI project template."""

546

547

PLUGIN_NAME = 'fastapi'

548

PRIORITY = 50

549

550

def initialize_config(self, config: dict) -> None:

551

"""Initialize template configuration."""

552

config.setdefault('package_name', config['project_name'].replace('-', '_'))

553

config.setdefault('use_database', False)

554

config.setdefault('use_auth', False)

555

556

def get_files(self, config: dict) -> list:

557

"""Get template files to create."""

558

files = []

559

package_name = config['package_name']

560

561

# Main application file

562

files.append(File(

563

f'{package_name}/main.py',

564

'''from fastapi import FastAPI

565

566

app = FastAPI()

567

568

@app.get("/")

569

def read_root():

570

return {"Hello": "World"}

571

'''

572

))

573

574

# Requirements file

575

requirements = ['fastapi>=0.68.0', 'uvicorn[standard]>=0.15.0']

576

if config['use_database']:

577

requirements.append('sqlalchemy>=1.4.0')

578

if config['use_auth']:

579

requirements.append('python-jose[cryptography]>=3.3.0')

580

581

files.append(File(

582

'requirements.txt',

583

'\n'.join(requirements) + '\n'

584

))

585

586

# pyproject.toml

587

files.append(File(

588

'pyproject.toml',

589

f'''[project]

590

name = "{config['project_name']}"

591

version = "0.1.0"

592

description = "FastAPI application"

593

dependencies = {requirements}

594

595

[project.scripts]

596

dev = "uvicorn {package_name}.main:app --reload"

597

'''

598

))

599

600

return files

601

602

def finalize_files(self, config: dict, files: list) -> None:

603

"""Finalize template after file creation."""

604

# Could add post-processing here

605

pass

606

607

# Register the plugin

608

def hatch_register_template():

609

return {'fastapi': FastAPITemplate}

610

```

611

612

### Using the Plugin System

613

614

```python

615

from hatch.plugin.manager import PluginManager

616

617

# Initialize plugin manager

618

plugins = PluginManager()

619

plugins.initialize()

620

621

# Discover available plugins

622

env_plugins = plugins.hatch_register_environment()

623

publisher_plugins = plugins.hatch_register_publisher()

624

template_plugins = plugins.hatch_register_template()

625

626

print(f"Environment plugins: {list(env_plugins.keys())}")

627

print(f"Publisher plugins: {list(publisher_plugins.keys())}")

628

print(f"Template plugins: {list(template_plugins.keys())}")

629

630

# Get specific plugin

631

docker_plugin = plugins.get_plugin('environment', 'docker')

632

if docker_plugin:

633

print(f"Found Docker plugin: {docker_plugin.PLUGIN_NAME}")

634

635

# List all plugins

636

all_plugins = plugins.list_name_plugin()

637

for name, plugin_type in all_plugins:

638

print(f"{plugin_type}: {name}")

639

```