0
# Environment Management
1
2
Environment plugin system for creating, managing, and executing commands in isolated Python environments. Supports virtual environments, Docker containers, and custom environment types through plugins.
3
4
## Capabilities
5
6
### Environment Interface
7
8
Base interface for all environment plugins providing standardized operations for environment lifecycle management.
9
10
```python { .api }
11
class EnvironmentInterface:
12
"""
13
Base interface for environment plugins.
14
15
All environment plugins must inherit from this class and implement
16
the abstract methods to provide environment management capabilities.
17
"""
18
19
PLUGIN_NAME: str = '' # Must be set by plugin implementations
20
21
def __init__(self, root, metadata, name, config, matrix_variables, data_directory, platform, verbosity, app):
22
"""
23
Initialize environment interface.
24
25
Args:
26
root: Project root directory
27
metadata: Project metadata
28
name (str): Environment name
29
config (dict): Environment configuration
30
matrix_variables (dict): Matrix variables for environment
31
data_directory: Directory for environment data
32
platform: Platform utilities
33
verbosity (int): Verbosity level
34
app: Application instance
35
"""
36
37
@property
38
def root(self):
39
"""Project root directory."""
40
41
@property
42
def metadata(self):
43
"""Project metadata."""
44
45
@property
46
def name(self) -> str:
47
"""Environment name."""
48
49
@property
50
def config(self) -> dict:
51
"""Environment configuration."""
52
53
@property
54
def matrix_variables(self) -> dict:
55
"""Matrix variables for this environment."""
56
57
@property
58
def data_directory(self):
59
"""Directory for environment data storage."""
60
61
@property
62
def platform(self):
63
"""Platform utilities for command execution."""
64
65
@property
66
def app(self):
67
"""Application instance."""
68
69
@property
70
def env_vars(self) -> dict:
71
"""
72
Environment variables for this environment.
73
74
Returns:
75
Dict of environment variables to set during execution
76
"""
77
78
@property
79
def dependencies(self) -> list:
80
"""
81
List of dependencies for this environment.
82
83
Returns:
84
List of dependency specifications
85
"""
86
87
@property
88
def features(self) -> list[str]:
89
"""
90
List of features enabled for this environment.
91
92
Returns:
93
List of feature names
94
"""
95
96
@property
97
def scripts(self) -> dict[str, str]:
98
"""
99
Scripts defined for this environment.
100
101
Returns:
102
Dict mapping script names to commands
103
"""
104
105
def exists(self) -> bool:
106
"""
107
Check if environment exists.
108
109
Returns:
110
True if environment exists and is ready for use
111
"""
112
113
def create(self) -> None:
114
"""
115
Create the environment.
116
117
This should create all necessary resources for the environment
118
including installing base dependencies.
119
"""
120
121
def remove(self) -> None:
122
"""
123
Remove the environment.
124
125
This should clean up all resources associated with the environment.
126
"""
127
128
def install_project(self) -> None:
129
"""
130
Install the project in the environment.
131
132
This should install the project package in the environment
133
without development mode.
134
"""
135
136
def install_project_dev_mode(self) -> None:
137
"""
138
Install the project in development mode.
139
140
This should install the project package in editable/development mode
141
so changes to source code are immediately reflected.
142
"""
143
144
def dependencies_in_sync(self) -> bool:
145
"""
146
Check if dependencies are synchronized with configuration.
147
148
Returns:
149
True if installed dependencies match configuration
150
"""
151
152
def sync_dependencies(self) -> None:
153
"""
154
Synchronize dependencies with configuration.
155
156
This should install, upgrade, or remove dependencies to match
157
the current environment configuration.
158
"""
159
160
def run_command(
161
self,
162
command: list[str],
163
*,
164
shell: bool = False,
165
env_vars: dict | None = None,
166
capture_output: bool = False
167
):
168
"""
169
Run command in the environment.
170
171
Args:
172
command (list[str]): Command and arguments to execute
173
shell (bool): Whether to use shell execution
174
env_vars (dict, optional): Additional environment variables
175
capture_output (bool): Whether to capture command output
176
177
Returns:
178
Command execution result
179
"""
180
181
def enter_shell(self, name: str, path: str, args: list[str]):
182
"""
183
Enter interactive shell in the environment.
184
185
Args:
186
name (str): Shell name
187
path (str): Shell executable path
188
args (list[str]): Shell arguments
189
"""
190
191
def build_environment(self, targets: list[str], env_vars: dict):
192
"""
193
Build artifacts in the environment.
194
195
Args:
196
targets (list[str]): Build targets
197
env_vars (dict): Environment variables for build
198
"""
199
200
def get_build_process(self, build_environment, **kwargs):
201
"""
202
Get build process for this environment.
203
204
Args:
205
build_environment: Build environment configuration
206
**kwargs: Additional build arguments
207
208
Returns:
209
Build process instance
210
"""
211
```
212
213
### Virtual Environment Plugin
214
215
Built-in virtual environment plugin providing standard Python virtual environment support.
216
217
```python { .api }
218
class VirtualEnvironment(EnvironmentInterface):
219
"""
220
Virtual environment plugin using Python's venv module.
221
222
Provides isolated Python environments with dependency management
223
and project installation support.
224
"""
225
226
PLUGIN_NAME = 'virtual'
227
228
@property
229
def python_info(self) -> dict:
230
"""Information about Python installation in environment."""
231
232
@property
233
def python_path(self) -> str:
234
"""Path to Python executable in environment."""
235
236
@property
237
def site_packages_path(self) -> str:
238
"""Path to site-packages directory in environment."""
239
240
@property
241
def scripts_path(self) -> str:
242
"""Path to scripts/bin directory in environment."""
243
244
def activate_environment(self) -> dict[str, str]:
245
"""
246
Get environment variables for activating virtual environment.
247
248
Returns:
249
Dict of environment variables to set for activation
250
"""
251
252
def deactivate_environment(self) -> dict[str, str]:
253
"""
254
Get environment variables for deactivating virtual environment.
255
256
Returns:
257
Dict of environment variables to unset for deactivation
258
"""
259
260
def install_packages(self, packages: list[str], *, dev: bool = False) -> None:
261
"""
262
Install packages in the virtual environment.
263
264
Args:
265
packages (list[str]): Package specifications to install
266
dev (bool): Whether to install development dependencies
267
"""
268
269
def uninstall_packages(self, packages: list[str]) -> None:
270
"""
271
Uninstall packages from the virtual environment.
272
273
Args:
274
packages (list[str]): Package names to uninstall
275
"""
276
277
def list_packages(self) -> list[str]:
278
"""
279
List installed packages in the environment.
280
281
Returns:
282
List of installed package specifications
283
"""
284
```
285
286
### Environment Configuration
287
288
Configuration system for defining environments including dependencies, scripts, environment variables, and plugin settings.
289
290
```python { .api }
291
class EnvironmentConfig:
292
"""Configuration for a single environment."""
293
294
@property
295
def type(self) -> str:
296
"""Environment type/plugin name."""
297
298
@property
299
def dependencies(self) -> list[str]:
300
"""List of dependencies for this environment."""
301
302
@property
303
def extra_dependencies(self) -> list[str]:
304
"""Extra dependencies beyond project dependencies."""
305
306
@property
307
def features(self) -> list[str]:
308
"""Project features to install."""
309
310
@property
311
def dev_mode(self) -> bool:
312
"""Whether to install project in development mode."""
313
314
@property
315
def skip_install(self) -> bool:
316
"""Whether to skip project installation."""
317
318
@property
319
def python(self) -> str:
320
"""Python version or path for this environment."""
321
322
@property
323
def matrix(self) -> dict:
324
"""Matrix variables for environment variants."""
325
326
@property
327
def env_vars(self) -> dict[str, str]:
328
"""Environment variables to set."""
329
330
@property
331
def env_include(self) -> list[str]:
332
"""Environment variables to include from host."""
333
334
@property
335
def env_exclude(self) -> list[str]:
336
"""Environment variables to exclude."""
337
338
@property
339
def scripts(self) -> dict[str, str]:
340
"""Scripts defined for this environment."""
341
342
@property
343
def pre_install_commands(self) -> list[str]:
344
"""Commands to run before installing dependencies."""
345
346
@property
347
def post_install_commands(self) -> list[str]:
348
"""Commands to run after installing dependencies."""
349
350
@property
351
def template(self) -> str:
352
"""Template environment to inherit from."""
353
```
354
355
### Environment Discovery
356
357
Utilities for discovering, validating, and collecting environment configurations from various sources.
358
359
```python { .api }
360
def discover_environments(project) -> dict[str, EnvironmentConfig]:
361
"""
362
Discover environment configurations from project.
363
364
Args:
365
project: Project instance
366
367
Returns:
368
Dict mapping environment names to configurations
369
"""
370
371
def validate_environment_config(config: dict) -> list[str]:
372
"""
373
Validate environment configuration.
374
375
Args:
376
config (dict): Environment configuration to validate
377
378
Returns:
379
List of validation errors (empty if valid)
380
"""
381
382
def resolve_environment_dependencies(
383
base_dependencies: list[str],
384
env_config: EnvironmentConfig,
385
features: list[str]
386
) -> list[str]:
387
"""
388
Resolve final dependency list for environment.
389
390
Args:
391
base_dependencies (list[str]): Project base dependencies
392
env_config (EnvironmentConfig): Environment configuration
393
features (list[str]): Enabled features
394
395
Returns:
396
Final list of dependencies to install
397
"""
398
399
def expand_environment_matrix(
400
env_config: EnvironmentConfig
401
) -> list[tuple[str, dict]]:
402
"""
403
Expand environment matrix into individual configurations.
404
405
Args:
406
env_config (EnvironmentConfig): Environment with matrix
407
408
Returns:
409
List of (environment_name, matrix_variables) tuples
410
"""
411
```
412
413
### Environment Operations
414
415
High-level operations for managing environments including creation, synchronization, cleanup, and status reporting.
416
417
```python { .api }
418
def create_environment(app, env_name: str) -> None:
419
"""
420
Create named environment.
421
422
Args:
423
app: Application instance
424
env_name (str): Environment name to create
425
"""
426
427
def remove_environment(app, env_name: str) -> None:
428
"""
429
Remove named environment.
430
431
Args:
432
app: Application instance
433
env_name (str): Environment name to remove
434
"""
435
436
def sync_environment(app, env_name: str) -> None:
437
"""
438
Synchronize environment dependencies.
439
440
Args:
441
app: Application instance
442
env_name (str): Environment name to synchronize
443
"""
444
445
def prune_environments(app) -> list[str]:
446
"""
447
Remove unused environments.
448
449
Args:
450
app: Application instance
451
452
Returns:
453
List of removed environment names
454
"""
455
456
def show_environment_info(app, env_name: str) -> dict:
457
"""
458
Get environment information.
459
460
Args:
461
app: Application instance
462
env_name (str): Environment name
463
464
Returns:
465
Dict with environment information
466
"""
467
468
def find_environment_path(app, env_name: str) -> str | None:
469
"""
470
Find path to environment directory.
471
472
Args:
473
app: Application instance
474
env_name (str): Environment name
475
476
Returns:
477
Path to environment or None if not found
478
"""
479
```
480
481
## Usage Examples
482
483
### Creating and Using Environments
484
485
```python
486
from hatch.cli.application import Application
487
from hatch.env.plugin.interface import EnvironmentInterface
488
489
app = Application(lambda code: exit(code))
490
491
# Get environment interface
492
env = app.get_environment("test")
493
494
# Create environment if it doesn't exist
495
if not env.exists():
496
print("Creating test environment...")
497
env.create()
498
499
# Install project in development mode
500
env.install_project_dev_mode()
501
502
# Synchronize dependencies
503
if not env.dependencies_in_sync():
504
print("Synchronizing dependencies...")
505
env.sync_dependencies()
506
507
# Run command in environment
508
result = env.run_command(
509
["python", "-m", "pytest", "tests/"],
510
capture_output=True
511
)
512
print(f"Test result: {result.returncode}")
513
```
514
515
### Environment Configuration
516
517
```python
518
from hatch.env import discover_environments
519
520
# Discover environments from project
521
project = app.project
522
environments = discover_environments(project)
523
524
# Access environment configuration
525
test_env_config = environments.get("test")
526
if test_env_config:
527
print(f"Test dependencies: {test_env_config.dependencies}")
528
print(f"Test scripts: {test_env_config.scripts}")
529
print(f"Python version: {test_env_config.python}")
530
531
# Validate configuration
532
errors = validate_environment_config(test_env_config.__dict__)
533
if errors:
534
print(f"Configuration errors: {errors}")
535
```
536
537
### Custom Environment Plugin
538
539
```python
540
from hatch.env.plugin.interface import EnvironmentInterface
541
542
class DockerEnvironment(EnvironmentInterface):
543
"""Custom Docker-based environment plugin."""
544
545
PLUGIN_NAME = 'docker'
546
547
def exists(self) -> bool:
548
# Check if Docker container exists
549
result = self.platform.run_command([
550
'docker', 'inspect', self.container_name
551
], capture_output=True)
552
return result.returncode == 0
553
554
def create(self) -> None:
555
# Create Docker container
556
self.platform.run_command([
557
'docker', 'create',
558
'--name', self.container_name,
559
'--volume', f'{self.root}:/workspace',
560
self.config.get('image', 'python:3.11'),
561
'sleep', 'infinity'
562
])
563
564
# Start container
565
self.platform.run_command([
566
'docker', 'start', self.container_name
567
])
568
569
def remove(self) -> None:
570
# Remove Docker container
571
self.platform.run_command([
572
'docker', 'rm', '-f', self.container_name
573
])
574
575
@property
576
def container_name(self) -> str:
577
return f'hatch-{self.name}-{self.root.name}'
578
579
def run_command(self, command: list[str], **kwargs):
580
# Execute command in Docker container
581
docker_cmd = [
582
'docker', 'exec',
583
'-w', '/workspace',
584
self.container_name
585
] + command
586
587
return self.platform.run_command(docker_cmd, **kwargs)
588
```
589
590
### Environment Matrix
591
592
```python
593
from hatch.env import expand_environment_matrix
594
595
# Environment configuration with matrix
596
config = EnvironmentConfig({
597
'matrix': {
598
'python': ['3.9', '3.10', '3.11'],
599
'django': ['3.2', '4.0', '4.1']
600
},
601
'dependencies': [
602
'django=={matrix:django}'
603
]
604
})
605
606
# Expand matrix into individual environments
607
environments = expand_environment_matrix(config)
608
609
for env_name, matrix_vars in environments:
610
print(f"Environment: {env_name}")
611
print(f"Variables: {matrix_vars}")
612
# Result: test-py3.9-django3.2, test-py3.10-django4.0, etc.
613
```