aws-bootstrap-g4dn 0.6.0__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- aws_bootstrap/cli.py +348 -96
- aws_bootstrap/ec2.py +9 -8
- aws_bootstrap/output.py +106 -0
- aws_bootstrap/ssh.py +21 -20
- aws_bootstrap/tests/test_cli.py +280 -0
- aws_bootstrap/tests/test_output.py +192 -0
- {aws_bootstrap_g4dn-0.6.0.dist-info → aws_bootstrap_g4dn-0.7.0.dist-info}/METADATA +27 -1
- {aws_bootstrap_g4dn-0.6.0.dist-info → aws_bootstrap_g4dn-0.7.0.dist-info}/RECORD +12 -10
- {aws_bootstrap_g4dn-0.6.0.dist-info → aws_bootstrap_g4dn-0.7.0.dist-info}/WHEEL +0 -0
- {aws_bootstrap_g4dn-0.6.0.dist-info → aws_bootstrap_g4dn-0.7.0.dist-info}/entry_points.txt +0 -0
- {aws_bootstrap_g4dn-0.6.0.dist-info → aws_bootstrap_g4dn-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {aws_bootstrap_g4dn-0.6.0.dist-info → aws_bootstrap_g4dn-0.7.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Tests for the output formatting module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
from datetime import UTC, datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import yaml
|
|
10
|
+
from click.testing import CliRunner
|
|
11
|
+
|
|
12
|
+
from aws_bootstrap.output import OutputFormat, echo, emit, is_text
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_output_format_enum_values():
|
|
16
|
+
assert OutputFormat.TEXT.value == "text"
|
|
17
|
+
assert OutputFormat.JSON.value == "json"
|
|
18
|
+
assert OutputFormat.YAML.value == "yaml"
|
|
19
|
+
assert OutputFormat.TABLE.value == "table"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_serialize_datetime():
|
|
23
|
+
"""datetime objects should serialize to ISO format strings."""
|
|
24
|
+
dt = datetime(2025, 6, 15, 12, 30, 0, tzinfo=UTC)
|
|
25
|
+
|
|
26
|
+
@click.command()
|
|
27
|
+
@click.pass_context
|
|
28
|
+
def cli(ctx):
|
|
29
|
+
ctx.ensure_object(dict)
|
|
30
|
+
ctx.obj["output_format"] = OutputFormat.JSON
|
|
31
|
+
emit({"timestamp": dt}, ctx=ctx)
|
|
32
|
+
|
|
33
|
+
runner = CliRunner()
|
|
34
|
+
result = runner.invoke(cli, [])
|
|
35
|
+
assert result.exit_code == 0
|
|
36
|
+
data = json.loads(result.output)
|
|
37
|
+
assert data["timestamp"] == "2025-06-15T12:30:00+00:00"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_serialize_path():
|
|
41
|
+
"""Path objects should serialize to strings."""
|
|
42
|
+
p = Path("/home/user/.ssh/id_ed25519")
|
|
43
|
+
|
|
44
|
+
@click.command()
|
|
45
|
+
@click.pass_context
|
|
46
|
+
def cli(ctx):
|
|
47
|
+
ctx.ensure_object(dict)
|
|
48
|
+
ctx.obj["output_format"] = OutputFormat.JSON
|
|
49
|
+
emit({"path": p}, ctx=ctx)
|
|
50
|
+
|
|
51
|
+
runner = CliRunner()
|
|
52
|
+
result = runner.invoke(cli, [])
|
|
53
|
+
assert result.exit_code == 0
|
|
54
|
+
data = json.loads(result.output)
|
|
55
|
+
assert data["path"] == "/home/user/.ssh/id_ed25519"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_emit_json():
|
|
59
|
+
"""emit() should produce valid JSON in JSON mode."""
|
|
60
|
+
|
|
61
|
+
@click.command()
|
|
62
|
+
@click.pass_context
|
|
63
|
+
def cli(ctx):
|
|
64
|
+
ctx.ensure_object(dict)
|
|
65
|
+
ctx.obj["output_format"] = OutputFormat.JSON
|
|
66
|
+
emit({"key": "value", "count": 42}, ctx=ctx)
|
|
67
|
+
|
|
68
|
+
runner = CliRunner()
|
|
69
|
+
result = runner.invoke(cli, [])
|
|
70
|
+
assert result.exit_code == 0
|
|
71
|
+
data = json.loads(result.output)
|
|
72
|
+
assert data == {"key": "value", "count": 42}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_emit_yaml():
|
|
76
|
+
"""emit() should produce valid YAML in YAML mode."""
|
|
77
|
+
|
|
78
|
+
@click.command()
|
|
79
|
+
@click.pass_context
|
|
80
|
+
def cli(ctx):
|
|
81
|
+
ctx.ensure_object(dict)
|
|
82
|
+
ctx.obj["output_format"] = OutputFormat.YAML
|
|
83
|
+
emit({"key": "value", "count": 42}, ctx=ctx)
|
|
84
|
+
|
|
85
|
+
runner = CliRunner()
|
|
86
|
+
result = runner.invoke(cli, [])
|
|
87
|
+
assert result.exit_code == 0
|
|
88
|
+
data = yaml.safe_load(result.output)
|
|
89
|
+
assert data == {"key": "value", "count": 42}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_emit_table_list():
|
|
93
|
+
"""emit() should render a list of dicts as a table with headers."""
|
|
94
|
+
|
|
95
|
+
@click.command()
|
|
96
|
+
@click.pass_context
|
|
97
|
+
def cli(ctx):
|
|
98
|
+
ctx.ensure_object(dict)
|
|
99
|
+
ctx.obj["output_format"] = OutputFormat.TABLE
|
|
100
|
+
emit(
|
|
101
|
+
[{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}],
|
|
102
|
+
headers={"name": "Name", "age": "Age"},
|
|
103
|
+
ctx=ctx,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
runner = CliRunner()
|
|
107
|
+
result = runner.invoke(cli, [])
|
|
108
|
+
assert result.exit_code == 0
|
|
109
|
+
assert "Name" in result.output
|
|
110
|
+
assert "Age" in result.output
|
|
111
|
+
assert "Alice" in result.output
|
|
112
|
+
assert "Bob" in result.output
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_emit_table_dict():
|
|
116
|
+
"""emit() should render a single dict as key-value pairs."""
|
|
117
|
+
|
|
118
|
+
@click.command()
|
|
119
|
+
@click.pass_context
|
|
120
|
+
def cli(ctx):
|
|
121
|
+
ctx.ensure_object(dict)
|
|
122
|
+
ctx.obj["output_format"] = OutputFormat.TABLE
|
|
123
|
+
emit({"instance_id": "i-abc123", "state": "running"}, ctx=ctx)
|
|
124
|
+
|
|
125
|
+
runner = CliRunner()
|
|
126
|
+
result = runner.invoke(cli, [])
|
|
127
|
+
assert result.exit_code == 0
|
|
128
|
+
assert "instance_id" in result.output
|
|
129
|
+
assert "i-abc123" in result.output
|
|
130
|
+
assert "running" in result.output
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_echo_suppressed_in_json_mode():
|
|
134
|
+
"""echo() should produce no output when format is JSON."""
|
|
135
|
+
|
|
136
|
+
@click.command()
|
|
137
|
+
@click.pass_context
|
|
138
|
+
def cli(ctx):
|
|
139
|
+
ctx.ensure_object(dict)
|
|
140
|
+
ctx.obj["output_format"] = OutputFormat.JSON
|
|
141
|
+
echo("This should not appear")
|
|
142
|
+
|
|
143
|
+
runner = CliRunner()
|
|
144
|
+
result = runner.invoke(cli, [])
|
|
145
|
+
assert result.exit_code == 0
|
|
146
|
+
assert result.output == ""
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_echo_emits_in_text_mode():
|
|
150
|
+
"""echo() should work normally in text mode."""
|
|
151
|
+
|
|
152
|
+
@click.command()
|
|
153
|
+
@click.pass_context
|
|
154
|
+
def cli(ctx):
|
|
155
|
+
ctx.ensure_object(dict)
|
|
156
|
+
ctx.obj["output_format"] = OutputFormat.TEXT
|
|
157
|
+
echo("Hello world")
|
|
158
|
+
|
|
159
|
+
runner = CliRunner()
|
|
160
|
+
result = runner.invoke(cli, [])
|
|
161
|
+
assert result.exit_code == 0
|
|
162
|
+
assert "Hello world" in result.output
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_is_text_default():
|
|
166
|
+
"""is_text() should return True when no context is set (default behavior)."""
|
|
167
|
+
|
|
168
|
+
@click.command()
|
|
169
|
+
@click.pass_context
|
|
170
|
+
def cli(ctx):
|
|
171
|
+
ctx.ensure_object(dict)
|
|
172
|
+
ctx.obj["output_format"] = OutputFormat.TEXT
|
|
173
|
+
assert is_text(ctx) is True
|
|
174
|
+
|
|
175
|
+
runner = CliRunner()
|
|
176
|
+
result = runner.invoke(cli, [])
|
|
177
|
+
assert result.exit_code == 0
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_is_text_false_for_json():
|
|
181
|
+
"""is_text() should return False when format is JSON."""
|
|
182
|
+
|
|
183
|
+
@click.command()
|
|
184
|
+
@click.pass_context
|
|
185
|
+
def cli(ctx):
|
|
186
|
+
ctx.ensure_object(dict)
|
|
187
|
+
ctx.obj["output_format"] = OutputFormat.JSON
|
|
188
|
+
assert is_text(ctx) is False
|
|
189
|
+
|
|
190
|
+
runner = CliRunner()
|
|
191
|
+
result = runner.invoke(cli, [])
|
|
192
|
+
assert result.exit_code == 0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aws-bootstrap-g4dn
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Bootstrap AWS EC2 GPU instances for hybrid local-remote development
|
|
5
5
|
Author: Adam Ever-Hadani
|
|
6
6
|
License-Expression: MIT
|
|
@@ -15,6 +15,8 @@ Description-Content-Type: text/markdown
|
|
|
15
15
|
License-File: LICENSE
|
|
16
16
|
Requires-Dist: boto3>=1.35
|
|
17
17
|
Requires-Dist: click>=8.1
|
|
18
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
19
|
+
Requires-Dist: tabulate>=0.9.0
|
|
18
20
|
Dynamic: license-file
|
|
19
21
|
|
|
20
22
|
# aws-bootstrap-g4dn
|
|
@@ -232,6 +234,30 @@ Then install the [Nsight VSCE extension](https://marketplace.visualstudio.com/it
|
|
|
232
234
|
|
|
233
235
|
See [Nsight remote profiling guide](docs/nsight-remote-profiling.md) for more details on CUDA debugging and profiling workflows.
|
|
234
236
|
|
|
237
|
+
### 📤 Structured Output
|
|
238
|
+
|
|
239
|
+
All commands support `--output` / `-o` for machine-readable output — useful for scripting, piping to `jq`, or LLM tool-use:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
# JSON output (pipe to jq)
|
|
243
|
+
aws-bootstrap -o json status
|
|
244
|
+
aws-bootstrap -o json status | jq '.instances[0].instance_id'
|
|
245
|
+
|
|
246
|
+
# YAML output
|
|
247
|
+
aws-bootstrap -o yaml status
|
|
248
|
+
|
|
249
|
+
# Table output
|
|
250
|
+
aws-bootstrap -o table status
|
|
251
|
+
|
|
252
|
+
# Works with all commands
|
|
253
|
+
aws-bootstrap -o json list instance-types | jq '.[].instance_type'
|
|
254
|
+
aws-bootstrap -o json launch --dry-run
|
|
255
|
+
aws-bootstrap -o json terminate --yes
|
|
256
|
+
aws-bootstrap -o json cleanup --dry-run
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Supported formats: `text` (default, human-readable with color), `json`, `yaml`, `table`. Commands that require confirmation (`terminate`, `cleanup`) require `--yes` in structured output modes.
|
|
260
|
+
|
|
235
261
|
### 📋 Listing Resources
|
|
236
262
|
|
|
237
263
|
```bash
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
aws_bootstrap/__init__.py,sha256=kl_jvrunGyIyizdRqAP6ROb5P1BBrXX5PTq5gq1ipU0,82
|
|
2
|
-
aws_bootstrap/cli.py,sha256=
|
|
2
|
+
aws_bootstrap/cli.py,sha256=R9w_sTBQwM0JqGTx4-THNe_x7iN-LlX4lE0kD55rytg,36173
|
|
3
3
|
aws_bootstrap/config.py,sha256=p770XgjfuK1-wVkAEeBdtJSVkc58DKFHgaJlZ-zbGmk,967
|
|
4
|
-
aws_bootstrap/ec2.py,sha256=
|
|
4
|
+
aws_bootstrap/ec2.py,sha256=giyfRdrriqvC_jPvGmtbEv2LKkGG3Qa9-uTpD7UFzzs,16735
|
|
5
5
|
aws_bootstrap/gpu.py,sha256=WTnHR0s3mQHDlnzqRgqAC6omWz7nT5YtGpcs0Bf88jk,692
|
|
6
|
-
aws_bootstrap/
|
|
6
|
+
aws_bootstrap/output.py,sha256=3NKIcRiYbS66ZWllpj7tkB_UlnFMHqtgE9ixY8Vhmrc,3753
|
|
7
|
+
aws_bootstrap/ssh.py,sha256=0R1o3aUXxdYWqJ-uvYZZY8rnZO7mfc30PwjCJs_0Qow,23971
|
|
7
8
|
aws_bootstrap/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
9
|
aws_bootstrap/resources/gpu_benchmark.py,sha256=1eFt_3MXvoLhs9HahrRPhbxvtdjFaXG2Ty3GEg7Gud0,29366
|
|
9
10
|
aws_bootstrap/resources/gpu_smoke_test.ipynb,sha256=XvAOEIPa5H9ri5mRZqOdknmwOwKNvCME6DzBGuhRYfg,10698
|
|
@@ -13,17 +14,18 @@ aws_bootstrap/resources/requirements.txt,sha256=gpYl1MFCfWXiAhbIUgAjuTHONz3MKci2
|
|
|
13
14
|
aws_bootstrap/resources/saxpy.cu,sha256=1BSESEwGGCx3KWx9ZJ8jiPHQ42KzQN6i2aP0I28bPsA,1178
|
|
14
15
|
aws_bootstrap/resources/tasks.json,sha256=6U8pB1N8YIWgUCfFet4ne3nYnI92tWv5D5kPiQG3Zlg,1576
|
|
15
16
|
aws_bootstrap/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
aws_bootstrap/tests/test_cli.py,sha256=
|
|
17
|
+
aws_bootstrap/tests/test_cli.py,sha256=QHn7OY7QAlbV4Flm4bwB496la3Z5BhX-msBWikeUAUE,59382
|
|
17
18
|
aws_bootstrap/tests/test_config.py,sha256=vspSGoben_i7m4Fh6UGSes6Fkr789Y1eaOLe54fRSGc,1524
|
|
18
19
|
aws_bootstrap/tests/test_ebs.py,sha256=B2HrgSmS7yroz6zzRuPxKIXmQGlWesuGqOtybyZmHJQ,7582
|
|
19
20
|
aws_bootstrap/tests/test_ec2.py,sha256=Jmqsjv973hxXbZWfGgECtm6aa2156Lzji227sYMBuMg,10547
|
|
20
21
|
aws_bootstrap/tests/test_gpu.py,sha256=rbMuda_sIVbaCzkWXoLv9YIfnWztgRoP7NuVL8XHrUY,3871
|
|
22
|
+
aws_bootstrap/tests/test_output.py,sha256=RRSbxtGqi7jfC9i4CR4EZXP56SmdsYUZDpT5uj8DWKQ,5250
|
|
21
23
|
aws_bootstrap/tests/test_ssh_config.py,sha256=qy3UDdvkTfrALiF-W3m8aKvnQj3BeCrZdLjG75tcVJU,17131
|
|
22
24
|
aws_bootstrap/tests/test_ssh_ebs.py,sha256=ipt0xOzdf3kfkVt42Dgr_z7D6JDIMuRi3DqX0OP8sm0,2342
|
|
23
25
|
aws_bootstrap/tests/test_ssh_gpu.py,sha256=dRp86Og-8GqiATSff3rxhu83mBZdGgqI4UOnoC00Ln0,1454
|
|
24
|
-
aws_bootstrap_g4dn-0.
|
|
25
|
-
aws_bootstrap_g4dn-0.
|
|
26
|
-
aws_bootstrap_g4dn-0.
|
|
27
|
-
aws_bootstrap_g4dn-0.
|
|
28
|
-
aws_bootstrap_g4dn-0.
|
|
29
|
-
aws_bootstrap_g4dn-0.
|
|
26
|
+
aws_bootstrap_g4dn-0.7.0.dist-info/licenses/LICENSE,sha256=Hen77Mt8sazSQJ9DgrmZuAvDwo2vc5JAkR_avuFV-CM,1067
|
|
27
|
+
aws_bootstrap_g4dn-0.7.0.dist-info/METADATA,sha256=qTRVXvMZwgcepn6IAhhFzoCk3YCIhkTUzzDCAfDtYJ4,16700
|
|
28
|
+
aws_bootstrap_g4dn-0.7.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
29
|
+
aws_bootstrap_g4dn-0.7.0.dist-info/entry_points.txt,sha256=T8FXfOgmLEvFi8DHaFJ3tCzId9J3_d2Y6qT98OXxCjA,57
|
|
30
|
+
aws_bootstrap_g4dn-0.7.0.dist-info/top_level.txt,sha256=mix9gZRs8JUv0OMSB_rwdGcRnTKzsKgHrE5fyAn5zJw,14
|
|
31
|
+
aws_bootstrap_g4dn-0.7.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|