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.
@@ -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.6.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=n3Ep_7zhBiRSU4ZUeGVqTRb81nzo98mxzQSKdAuiopY,27788
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=uNqxWWfPfGCbujQ3eonvqjjxLE76fsEyNchPS6byR6c,16719
4
+ aws_bootstrap/ec2.py,sha256=giyfRdrriqvC_jPvGmtbEv2LKkGG3Qa9-uTpD7UFzzs,16735
5
5
  aws_bootstrap/gpu.py,sha256=WTnHR0s3mQHDlnzqRgqAC6omWz7nT5YtGpcs0Bf88jk,692
6
- aws_bootstrap/ssh.py,sha256=xY0Yn5q4aA0Xb3ejNY-KCbooZArXRGpimSnbJiBLI_w,24059
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=OOYzopo8gkp6fO1MT10fsqAhIs5pN9cCpJyOzWNXHCg,48638
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.6.0.dist-info/licenses/LICENSE,sha256=Hen77Mt8sazSQJ9DgrmZuAvDwo2vc5JAkR_avuFV-CM,1067
25
- aws_bootstrap_g4dn-0.6.0.dist-info/METADATA,sha256=Ot9yCJfJup1ZzW-0cq99zT9bbswHnRJ4SWxEsJ-pK58,15859
26
- aws_bootstrap_g4dn-0.6.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
27
- aws_bootstrap_g4dn-0.6.0.dist-info/entry_points.txt,sha256=T8FXfOgmLEvFi8DHaFJ3tCzId9J3_d2Y6qT98OXxCjA,57
28
- aws_bootstrap_g4dn-0.6.0.dist-info/top_level.txt,sha256=mix9gZRs8JUv0OMSB_rwdGcRnTKzsKgHrE5fyAn5zJw,14
29
- aws_bootstrap_g4dn-0.6.0.dist-info/RECORD,,
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,,