0
# API Package
1
2
## Overview
3
4
The xonsh.api package provides a pure Python interface to xonsh functionality, designed for use by external tools and scripts that need xonsh capabilities without running a full xonsh shell. This package includes subprocess wrappers and OS utilities that leverage xonsh's enhanced command execution.
5
6
## Subprocess Module
7
8
### Enhanced Subprocess Functions
9
10
```python { .api }
11
from xonsh.api.subprocess import run, check_call, check_output
12
13
def run(cmd: list[str], cwd: str = None, check: bool = False) -> object:
14
"""Drop-in replacement for subprocess.run with xonsh enhancements.
15
16
Parameters
17
----------
18
cmd : list[str]
19
Command and arguments to execute
20
cwd : str, optional
21
Working directory for command execution
22
check : bool, default False
23
Whether to raise exception on non-zero return code
24
25
Returns
26
-------
27
object
28
Process object with stdout, stderr, returncode attributes
29
30
Examples
31
--------
32
# Basic command execution
33
result = run(['ls', '-la'])
34
print(f"Return code: {result.returncode}")
35
36
# With working directory
37
result = run(['git', 'status'], cwd='/path/to/repo')
38
39
# With error checking
40
result = run(['make', 'build'], check=True) # Raises on failure
41
"""
42
43
def check_call(cmd: list[str], cwd: str = None) -> int:
44
"""Execute command and raise exception on failure.
45
46
Parameters
47
----------
48
cmd : list[str]
49
Command and arguments to execute
50
cwd : str, optional
51
Working directory for command execution
52
53
Returns
54
-------
55
int
56
Return code (always 0 if successful)
57
58
Raises
59
------
60
CalledProcessError
61
If command returns non-zero exit code
62
63
Examples
64
--------
65
# Successful command
66
check_call(['mkdir', '-p', '/tmp/test'])
67
68
# Command with working directory
69
check_call(['npm', 'install'], cwd='/path/to/project')
70
71
# Will raise exception if git command fails
72
check_call(['git', 'push', 'origin', 'main'])
73
"""
74
75
def check_output(cmd: list[str], cwd: str = None) -> bytes:
76
"""Execute command and return output, raising exception on failure.
77
78
Parameters
79
----------
80
cmd : list[str]
81
Command and arguments to execute
82
cwd : str, optional
83
Working directory for command execution
84
85
Returns
86
-------
87
bytes
88
Command output as bytes
89
90
Raises
91
------
92
CalledProcessError
93
If command returns non-zero exit code
94
95
Examples
96
--------
97
# Get command output
98
output = check_output(['git', 'rev-parse', 'HEAD'])
99
commit_hash = output.decode().strip()
100
101
# Command with working directory
102
files = check_output(['ls', '-1'], cwd='/tmp')
103
file_list = files.decode().strip().split('\n')
104
105
# Will raise exception if command fails
106
version = check_output(['python', '--version'])
107
"""
108
```
109
110
## OS Module
111
112
### Enhanced OS Utilities
113
114
```python { .api }
115
from xonsh.api.os import rmtree, indir
116
117
def rmtree(dirname: str, force: bool = False) -> None:
118
"""Remove directory tree with cross-platform compatibility.
119
120
Handles read-only files on Windows that standard rmtree cannot remove.
121
Uses xonsh subprocess execution for reliable cross-platform operation.
122
123
Parameters
124
----------
125
dirname : str
126
Directory path to remove
127
force : bool, default False
128
If True, force removal (adds 'f' flag on Unix systems)
129
130
Examples
131
--------
132
# Basic directory removal
133
rmtree('/tmp/test_dir')
134
135
# Force removal (useful for git repositories with read-only files)
136
rmtree('/tmp/repo_clone', force=True)
137
138
# Windows-compatible removal
139
import sys
140
if sys.platform == 'win32':
141
rmtree('C:\\temp\\test') # Uses rmdir /S/Q
142
else:
143
rmtree('/tmp/test') # Uses rm -r
144
"""
145
146
def indir(dirname: str):
147
"""Context manager for temporary directory changes.
148
149
Alias to xonsh.dirstack.with_pushd for API consistency.
150
Changes to specified directory and restores original directory on exit.
151
152
Parameters
153
----------
154
dirname : str
155
Directory to change to temporarily
156
157
Returns
158
-------
159
context manager
160
Context manager for directory change
161
162
Examples
163
--------
164
import os
165
166
# Temporary directory change
167
print(f"Original: {os.getcwd()}")
168
with indir('/tmp'):
169
print(f"Inside context: {os.getcwd()}") # /tmp
170
# Do work in /tmp
171
print(f"Restored: {os.getcwd()}") # Original directory
172
173
# Nested directory changes
174
with indir('/var'):
175
print(f"First level: {os.getcwd()}")
176
with indir('log'):
177
print(f"Nested: {os.getcwd()}") # /var/log
178
print(f"Back to first: {os.getcwd()}") # /var
179
"""
180
181
# Direct alias for convenience
182
from xonsh.dirstack import with_pushd
183
indir = with_pushd # Context manager for directory changes
184
```
185
186
## Integration Patterns
187
188
### Using API in External Scripts
189
190
```python { .api }
191
#!/usr/bin/env python3
192
"""Example external script using xonsh API."""
193
194
from xonsh.api.subprocess import run, check_output
195
from xonsh.api.os import indir, rmtree
196
197
def deploy_project(project_path: str, deploy_path: str) -> bool:
198
"""Deploy project using xonsh API functions.
199
200
Parameters
201
----------
202
project_path : str
203
Source project directory
204
deploy_path : str
205
Deployment target directory
206
207
Returns
208
-------
209
bool
210
True if deployment successful
211
"""
212
try:
213
# Clean deployment directory
214
rmtree(deploy_path, force=True)
215
216
# Build project
217
with indir(project_path):
218
result = run(['npm', 'run', 'build'], check=True)
219
220
# Get build info
221
build_info = check_output(['npm', 'run', 'build:info'])
222
print(f"Build info: {build_info.decode().strip()}")
223
224
# Copy build artifacts
225
result = run(['cp', '-r', f'{project_path}/dist', deploy_path])
226
227
return result.returncode == 0
228
229
except Exception as e:
230
print(f"Deployment failed: {e}")
231
return False
232
233
# Usage
234
if __name__ == '__main__':
235
success = deploy_project('/src/myapp', '/var/www/myapp')
236
print(f"Deployment {'succeeded' if success else 'failed'}")
237
```
238
239
### Library Integration
240
241
```python { .api }
242
"""Library that uses xonsh API for enhanced subprocess operations."""
243
244
from xonsh.api.subprocess import run, check_output
245
from xonsh.api.os import indir
246
import json
247
import os
248
249
class ProjectManager:
250
"""Project management using xonsh API."""
251
252
def __init__(self, project_root: str):
253
"""Initialize project manager.
254
255
Parameters
256
----------
257
project_root : str
258
Root directory of the project
259
"""
260
self.project_root = project_root
261
262
def get_git_info(self) -> dict:
263
"""Get git repository information.
264
265
Returns
266
-------
267
dict
268
Git repository information
269
"""
270
with indir(self.project_root):
271
try:
272
# Get current branch
273
branch = check_output(['git', 'rev-parse', '--abbrev-ref', 'HEAD'])
274
branch = branch.decode().strip()
275
276
# Get commit hash
277
commit = check_output(['git', 'rev-parse', 'HEAD'])
278
commit = commit.decode().strip()
279
280
# Get status
281
status_result = run(['git', 'status', '--porcelain'])
282
is_clean = status_result.returncode == 0 and not status_result.stdout
283
284
return {
285
'branch': branch,
286
'commit': commit,
287
'is_clean': is_clean,
288
'has_git': True
289
}
290
except:
291
return {'has_git': False}
292
293
def run_tests(self) -> bool:
294
"""Run project tests.
295
296
Returns
297
-------
298
bool
299
True if tests pass
300
"""
301
with indir(self.project_root):
302
# Try different test runners
303
test_commands = [
304
['npm', 'test'],
305
['python', '-m', 'pytest'],
306
['python', '-m', 'unittest'],
307
['make', 'test']
308
]
309
310
for cmd in test_commands:
311
if self._command_exists(cmd[0]):
312
result = run(cmd)
313
return result.returncode == 0
314
315
return False
316
317
def _command_exists(self, command: str) -> bool:
318
"""Check if command exists."""
319
result = run(['which', command])
320
return result.returncode == 0
321
322
# Usage example
323
project = ProjectManager('/path/to/project')
324
git_info = project.get_git_info()
325
test_result = project.run_tests()
326
```
327
328
### Configuration and Environment
329
330
```python { .api }
331
"""Configuration utilities using xonsh API."""
332
333
from xonsh.api.subprocess import check_output, run
334
from xonsh.api.os import indir
335
import os
336
import json
337
338
def detect_project_type(project_path: str) -> str:
339
"""Detect project type from files and configuration.
340
341
Parameters
342
----------
343
project_path : str
344
Project directory path
345
346
Returns
347
-------
348
str
349
Project type ('python', 'node', 'go', 'rust', 'unknown')
350
"""
351
with indir(project_path):
352
# Check for specific files
353
if os.path.exists('package.json'):
354
return 'node'
355
elif os.path.exists('pyproject.toml') or os.path.exists('setup.py'):
356
return 'python'
357
elif os.path.exists('go.mod'):
358
return 'go'
359
elif os.path.exists('Cargo.toml'):
360
return 'rust'
361
elif os.path.exists('Makefile'):
362
return 'make'
363
else:
364
return 'unknown'
365
366
def get_project_dependencies(project_path: str) -> dict:
367
"""Get project dependencies using appropriate package manager.
368
369
Parameters
370
----------
371
project_path : str
372
Project directory path
373
374
Returns
375
-------
376
dict
377
Dependencies information
378
"""
379
project_type = detect_project_type(project_path)
380
381
with indir(project_path):
382
if project_type == 'node':
383
try:
384
output = check_output(['npm', 'list', '--json'])
385
return json.loads(output.decode())
386
except:
387
return {}
388
389
elif project_type == 'python':
390
try:
391
# Try pip list
392
output = check_output(['pip', 'list', '--format=json'])
393
packages = json.loads(output.decode())
394
return {pkg['name']: pkg['version'] for pkg in packages}
395
except:
396
return {}
397
398
return {}
399
400
def setup_development_environment(project_path: str) -> bool:
401
"""Setup development environment for project.
402
403
Parameters
404
----------
405
project_path : str
406
Project directory path
407
408
Returns
409
-------
410
bool
411
True if setup successful
412
"""
413
project_type = detect_project_type(project_path)
414
415
with indir(project_path):
416
try:
417
if project_type == 'node':
418
result = run(['npm', 'install'], check=True)
419
return True
420
421
elif project_type == 'python':
422
# Create virtual environment if needed
423
if not os.path.exists('venv'):
424
run(['python', '-m', 'venv', 'venv'], check=True)
425
426
# Install dependencies
427
if os.path.exists('requirements.txt'):
428
run(['venv/bin/pip', 'install', '-r', 'requirements.txt'], check=True)
429
elif os.path.exists('pyproject.toml'):
430
run(['venv/bin/pip', 'install', '-e', '.'], check=True)
431
432
return True
433
434
return False
435
436
except Exception as e:
437
print(f"Setup failed: {e}")
438
return False
439
```
440
441
## Error Handling and Best Practices
442
443
### Robust Error Handling
444
445
```python { .api }
446
from xonsh.api.subprocess import run, check_output
447
from xonsh.api.os import indir
448
import logging
449
450
def safe_command_execution(cmd: list[str], cwd: str = None,
451
timeout: int = 30) -> tuple[bool, str]:
452
"""Safely execute command with comprehensive error handling.
453
454
Parameters
455
----------
456
cmd : list[str]
457
Command to execute
458
cwd : str, optional
459
Working directory
460
timeout : int, default 30
461
Command timeout in seconds
462
463
Returns
464
-------
465
tuple[bool, str]
466
(success, output_or_error_message)
467
"""
468
try:
469
if cwd:
470
with indir(cwd):
471
result = run(cmd, check=False)
472
else:
473
result = run(cmd, check=False)
474
475
if result.returncode == 0:
476
output = result.stdout if hasattr(result, 'stdout') else ""
477
return True, output
478
else:
479
error = result.stderr if hasattr(result, 'stderr') else f"Command failed with code {result.returncode}"
480
return False, error
481
482
except FileNotFoundError:
483
return False, f"Command '{cmd[0]}' not found"
484
except PermissionError:
485
return False, f"Permission denied executing '{cmd[0]}'"
486
except Exception as e:
487
return False, f"Unexpected error: {e}"
488
489
# Usage with error handling
490
success, result = safe_command_execution(['git', 'status'], cwd='/repo')
491
if success:
492
print(f"Git status: {result}")
493
else:
494
logging.error(f"Git command failed: {result}")
495
```
496
497
The API package provides a clean, pure Python interface to xonsh's powerful subprocess and OS utilities, making it easy to integrate xonsh capabilities into external tools and libraries without requiring a full xonsh shell environment.