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
```