nooa-cli 0.0.7__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.
- nooa_cli/AGENTS.md +162 -0
- nooa_cli/__init__.py +70 -0
- nooa_cli/__main__.py +8 -0
- nooa_cli/_common.py +45 -0
- nooa_cli/commands/__init__.py +106 -0
- nooa_cli/commands/_otlp_helpers.py +122 -0
- nooa_cli/commands/_template.py +89 -0
- nooa_cli/commands/config.py +356 -0
- nooa_cli/commands/delete_traces.py +63 -0
- nooa_cli/commands/eval.py +68 -0
- nooa_cli/commands/import_harbor.py +512 -0
- nooa_cli/commands/import_traces.py +189 -0
- nooa_cli/commands/start_dev.py +145 -0
- nooa_cli/commands/traces.py +316 -0
- nooa_cli/completion.py +253 -0
- nooa_cli/tools/__init__.py +0 -0
- nooa_cli/tools/_tree_sitter_backend.py +351 -0
- nooa_cli/tools/pyp/__init__.py +71 -0
- nooa_cli/tools/pyp/errors.py +92 -0
- nooa_cli/tools/pyp/sources.py +458 -0
- nooa_cli/tools/pyp/stream.py +527 -0
- nooa_cli/tools/pyp/tests/__init__.py +0 -0
- nooa_cli/tools/pyp/tests/test_pyp.py +394 -0
- nooa_cli/tools/repo_tools.py +902 -0
- nooa_cli-0.0.7.dist-info/METADATA +52 -0
- nooa_cli-0.0.7.dist-info/RECORD +28 -0
- nooa_cli-0.0.7.dist-info/WHEEL +4 -0
- nooa_cli-0.0.7.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""Tests for pysh library — method-chaining, async-native."""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from nooa_cli.tools.pyp.errors import PipeError, Result, make_pipe_error
|
|
10
|
+
from nooa_cli.tools.pyp.sources import cat, empty, find, glob, items, lines, run, seq
|
|
11
|
+
from nooa_cli.tools.pyp.stream import Stream
|
|
12
|
+
|
|
13
|
+
# ─── Stream basics ───────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.asyncio
|
|
17
|
+
async def test_stream_from_iterable():
|
|
18
|
+
s = Stream(iter(["a", "b", "c"]))
|
|
19
|
+
assert await s.collect() == ["a", "b", "c"]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.mark.asyncio
|
|
23
|
+
async def test_stream_chaining():
|
|
24
|
+
s = items(["hello", "world", "help"]).grep("hel")
|
|
25
|
+
assert await s.collect() == ["hello", "help"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@pytest.mark.asyncio
|
|
29
|
+
async def test_stream_pipe_with_callable():
|
|
30
|
+
async def upper(ait):
|
|
31
|
+
async for x in ait:
|
|
32
|
+
yield x.upper()
|
|
33
|
+
|
|
34
|
+
s = items(["a", "b", "c"]) | upper
|
|
35
|
+
assert await s.collect() == ["A", "B", "C"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.asyncio
|
|
39
|
+
async def test_stream_repr():
|
|
40
|
+
s = items(["x"]).grep("x").head(1)
|
|
41
|
+
r = repr(s)
|
|
42
|
+
assert "grep" in r
|
|
43
|
+
assert "head" in r
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# ─── Error types ─────────────────────────────────────────────────────
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_result_ok():
|
|
50
|
+
r = Result(lines=["a", "b"], returncode=0)
|
|
51
|
+
assert r.ok
|
|
52
|
+
assert len(r) == 2
|
|
53
|
+
assert r.text == "a\nb"
|
|
54
|
+
assert bool(r) is True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_result_failure():
|
|
58
|
+
r = Result(lines=[], returncode=1, stderr="oops")
|
|
59
|
+
assert not r.ok
|
|
60
|
+
assert bool(r) is False
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_pipe_error_fields():
|
|
64
|
+
e = make_pipe_error("failed", cmd="ls", returncode=2, stderr="no such file")
|
|
65
|
+
assert e.cmd == "ls"
|
|
66
|
+
assert e.returncode == 2
|
|
67
|
+
assert "no such file" in e.stderr
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# ─── Sources ─────────────────────────────────────────────────────────
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@pytest.mark.asyncio
|
|
74
|
+
async def test_cat(tmp_path):
|
|
75
|
+
f = tmp_path / "test.txt"
|
|
76
|
+
f.write_text("line1\nline2\nline3\n")
|
|
77
|
+
result = await cat(str(f)).collect()
|
|
78
|
+
assert result == ["line1", "line2", "line3"]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@pytest.mark.asyncio
|
|
82
|
+
async def test_cat_multiple(tmp_path):
|
|
83
|
+
f1 = tmp_path / "a.txt"
|
|
84
|
+
f2 = tmp_path / "b.txt"
|
|
85
|
+
f1.write_text("a\n")
|
|
86
|
+
f2.write_text("b\n")
|
|
87
|
+
result = await cat(str(f1), str(f2)).collect()
|
|
88
|
+
assert result == ["a", "b"]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@pytest.mark.asyncio
|
|
92
|
+
async def test_run_success():
|
|
93
|
+
result = await run("echo hello").collect()
|
|
94
|
+
assert result == ["hello"]
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.mark.asyncio
|
|
98
|
+
async def test_run_failure():
|
|
99
|
+
with pytest.raises(PipeError):
|
|
100
|
+
await run("false").collect()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@pytest.mark.asyncio
|
|
104
|
+
async def test_run_check_false():
|
|
105
|
+
r = await run("false", check=False).result()
|
|
106
|
+
assert not r.ok
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@pytest.mark.asyncio
|
|
110
|
+
async def test_find_files(tmp_path):
|
|
111
|
+
(tmp_path / "a.py").write_text("")
|
|
112
|
+
(tmp_path / "b.txt").write_text("")
|
|
113
|
+
sub = tmp_path / "sub"
|
|
114
|
+
sub.mkdir()
|
|
115
|
+
(sub / "c.py").write_text("")
|
|
116
|
+
result = await find(str(tmp_path), name="*.py").collect()
|
|
117
|
+
assert len(result) == 2
|
|
118
|
+
assert all(".py" in r for r in result)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@pytest.mark.asyncio
|
|
122
|
+
async def test_glob_files(tmp_path):
|
|
123
|
+
(tmp_path / "a.py").write_text("")
|
|
124
|
+
(tmp_path / "b.py").write_text("")
|
|
125
|
+
(tmp_path / "c.txt").write_text("")
|
|
126
|
+
result = await glob("*.py", root=str(tmp_path)).collect()
|
|
127
|
+
assert len(result) == 2
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@pytest.mark.asyncio
|
|
131
|
+
async def test_lines_source():
|
|
132
|
+
text = "foo\nbar\nbaz"
|
|
133
|
+
assert await lines(text).collect() == ["foo", "bar", "baz"]
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@pytest.mark.asyncio
|
|
137
|
+
async def test_items_source():
|
|
138
|
+
assert await items([1, 2, 3]).collect() == [1, 2, 3]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@pytest.mark.asyncio
|
|
142
|
+
async def test_empty_source():
|
|
143
|
+
assert await empty().collect() == []
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@pytest.mark.asyncio
|
|
147
|
+
async def test_seq():
|
|
148
|
+
assert await seq(5).collect() == ["1", "2", "3", "4", "5"]
|
|
149
|
+
assert await seq(2, 10, step=3).collect() == ["2", "5", "8"]
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# ─── Transforms (method chaining) ───────────────────────────────────
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
@pytest.mark.asyncio
|
|
156
|
+
async def test_grep_basic():
|
|
157
|
+
result = await items(["apple", "banana", "apricot"]).grep("ap").collect()
|
|
158
|
+
assert result == ["apple", "apricot"]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@pytest.mark.asyncio
|
|
162
|
+
async def test_grep_invert():
|
|
163
|
+
result = await items(["apple", "banana", "apricot"]).grep("ap", invert=True).collect()
|
|
164
|
+
assert result == ["banana"]
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@pytest.mark.asyncio
|
|
168
|
+
async def test_grep_ignore_case():
|
|
169
|
+
result = await items(["Apple", "BANANA"]).grep("apple", ignore_case=True).collect()
|
|
170
|
+
assert result == ["Apple"]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@pytest.mark.asyncio
|
|
174
|
+
async def test_grep_fixed():
|
|
175
|
+
result = await items(["a.b", "axb", "a*b"]).grep("a.b", fixed=True).collect()
|
|
176
|
+
assert result == ["a.b"]
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@pytest.mark.asyncio
|
|
180
|
+
async def test_head():
|
|
181
|
+
result = await items(range(100)).head(3).collect()
|
|
182
|
+
assert result == [0, 1, 2]
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
@pytest.mark.asyncio
|
|
186
|
+
async def test_tail():
|
|
187
|
+
result = await items(range(100)).tail(3).collect()
|
|
188
|
+
assert result == [97, 98, 99]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@pytest.mark.asyncio
|
|
192
|
+
async def test_sort_basic():
|
|
193
|
+
result = await items(["c", "a", "b"]).sort().collect()
|
|
194
|
+
assert result == ["a", "b", "c"]
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@pytest.mark.asyncio
|
|
198
|
+
async def test_sort_reverse():
|
|
199
|
+
result = await items(["a", "b", "c"]).sort(reverse=True).collect()
|
|
200
|
+
assert result == ["c", "b", "a"]
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@pytest.mark.asyncio
|
|
204
|
+
async def test_sort_numeric():
|
|
205
|
+
result = await items(["10", "2", "1", "20"]).sort(numeric=True).collect()
|
|
206
|
+
assert result == ["1", "2", "10", "20"]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
@pytest.mark.asyncio
|
|
210
|
+
async def test_uniq_consecutive():
|
|
211
|
+
result = await items(["a", "a", "b", "b", "a"]).uniq().collect()
|
|
212
|
+
assert result == ["a", "b", "a"]
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@pytest.mark.asyncio
|
|
216
|
+
async def test_uniq_all():
|
|
217
|
+
result = await items(["a", "b", "a", "c", "b"]).uniq(all_unique=True).collect()
|
|
218
|
+
assert result == ["a", "b", "c"]
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@pytest.mark.asyncio
|
|
222
|
+
async def test_cut_basic():
|
|
223
|
+
data = ["Alice 30 NYC", "Bob 25 LA"]
|
|
224
|
+
result = await items(data).cut(fields=[0, 2]).collect()
|
|
225
|
+
assert result == ["Alice\tNYC", "Bob\tLA"]
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@pytest.mark.asyncio
|
|
229
|
+
async def test_cut_with_sep():
|
|
230
|
+
data = ["a,b,c", "d,e,f"]
|
|
231
|
+
result = await items(data).cut(fields=[0, 2], sep=",", out_sep=",").collect()
|
|
232
|
+
assert result == ["a,c", "d,f"]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@pytest.mark.asyncio
|
|
236
|
+
async def test_sed_basic():
|
|
237
|
+
result = await items(["hello world", "hello python"]).sed("hello", "hi").collect()
|
|
238
|
+
assert result == ["hi world", "hi python"]
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
@pytest.mark.asyncio
|
|
242
|
+
async def test_sed_regex():
|
|
243
|
+
result = await items(["foo123bar"]).sed(r"(\d+)", "NUM").collect()
|
|
244
|
+
assert result == ["fooNUMbar"]
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@pytest.mark.asyncio
|
|
248
|
+
async def test_map():
|
|
249
|
+
result = await items(["a", "b", "c"]).map(str.upper).collect()
|
|
250
|
+
assert result == ["A", "B", "C"]
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
@pytest.mark.asyncio
|
|
254
|
+
async def test_filter():
|
|
255
|
+
result = (
|
|
256
|
+
await items(["short", "a very long line indeed"]).filter(lambda x: len(x) > 10).collect()
|
|
257
|
+
)
|
|
258
|
+
assert result == ["a very long line indeed"]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
@pytest.mark.asyncio
|
|
262
|
+
async def test_wc():
|
|
263
|
+
result = await items(["a", "b", "c"]).wc().collect()
|
|
264
|
+
assert result == ["3"]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@pytest.mark.asyncio
|
|
268
|
+
async def test_wc_full():
|
|
269
|
+
result = await items(["hello world", "foo"]).wc(lines_only=False).collect()
|
|
270
|
+
parts = result[0].split()
|
|
271
|
+
assert parts[0] == "2"
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@pytest.mark.asyncio
|
|
275
|
+
async def test_tee(tmp_path):
|
|
276
|
+
outfile = str(tmp_path / "tee_out.txt")
|
|
277
|
+
result = await items(["a", "b", "c"]).tee(outfile).collect()
|
|
278
|
+
assert result == ["a", "b", "c"]
|
|
279
|
+
content = Path(outfile).read_text()
|
|
280
|
+
assert "a\n" in content
|
|
281
|
+
assert "c\n" in content
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
@pytest.mark.asyncio
|
|
285
|
+
async def test_skip():
|
|
286
|
+
result = await items(["header", "row1", "row2"]).skip(1).collect()
|
|
287
|
+
assert result == ["row1", "row2"]
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@pytest.mark.asyncio
|
|
291
|
+
async def test_take_while():
|
|
292
|
+
result = await items([1, 2, 3, 10, 4]).take_while(lambda x: x < 5).collect()
|
|
293
|
+
assert result == [1, 2, 3]
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
@pytest.mark.asyncio
|
|
297
|
+
async def test_drop_while():
|
|
298
|
+
result = await items([1, 2, 3, 10, 4]).drop_while(lambda x: x < 5).collect()
|
|
299
|
+
assert result == [10, 4]
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@pytest.mark.asyncio
|
|
303
|
+
async def test_flatten():
|
|
304
|
+
result = await items(["a,b,c", "d,e"]).flatten(sep=",").collect()
|
|
305
|
+
assert result == ["a", "b", "c", "d", "e"]
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
@pytest.mark.asyncio
|
|
309
|
+
async def test_strip():
|
|
310
|
+
result = await items([" hello ", " world "]).strip().collect()
|
|
311
|
+
assert result == ["hello", "world"]
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
# ─── Composition ─────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@pytest.mark.asyncio
|
|
318
|
+
async def test_chained_pipeline():
|
|
319
|
+
result = (
|
|
320
|
+
await items(["banana", "apple", "avocado", "cherry"]).grep("a").sort().head(2).collect()
|
|
321
|
+
)
|
|
322
|
+
assert result == ["apple", "avocado"]
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
@pytest.mark.asyncio
|
|
326
|
+
async def test_full_pipeline(tmp_path):
|
|
327
|
+
f = tmp_path / "data.txt"
|
|
328
|
+
f.write_text("ERROR: disk full\nINFO: ok\nERROR: timeout\nDEBUG: trace\n")
|
|
329
|
+
result = await cat(str(f)).grep("ERROR").sed("ERROR: ", "").sort().collect()
|
|
330
|
+
assert result == ["disk full", "timeout"]
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
@pytest.mark.asyncio
|
|
334
|
+
async def test_pipeline_with_run():
|
|
335
|
+
result = await run("echo -e 'foo\\nbar\\nbaz'").grep("ba").sort().collect()
|
|
336
|
+
assert "bar" in result
|
|
337
|
+
assert "baz" in result
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
# ─── Sinks ───────────────────────────────────────────────────────────
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@pytest.mark.asyncio
|
|
344
|
+
async def test_first():
|
|
345
|
+
assert await items(["a", "b", "c"]).first() == "a"
|
|
346
|
+
assert await empty().first() is None
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
@pytest.mark.asyncio
|
|
350
|
+
async def test_last():
|
|
351
|
+
assert await items(["a", "b", "c"]).last() == "c"
|
|
352
|
+
assert await empty().last() is None
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
@pytest.mark.asyncio
|
|
356
|
+
async def test_count():
|
|
357
|
+
assert await items(range(42)).count() == 42
|
|
358
|
+
assert await empty().count() == 0
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
@pytest.mark.asyncio
|
|
362
|
+
async def test_write(tmp_path):
|
|
363
|
+
out = tmp_path / "out.txt"
|
|
364
|
+
n = await items(["x", "y", "z"]).write(str(out))
|
|
365
|
+
assert n == 3
|
|
366
|
+
assert out.read_text() == "x\ny\nz\n"
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
@pytest.mark.asyncio
|
|
370
|
+
async def test_to_set():
|
|
371
|
+
assert await items(["a", "b", "a"]).to_set() == {"a", "b"}
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@pytest.mark.asyncio
|
|
375
|
+
async def test_to_dict():
|
|
376
|
+
assert await items(["k1=v1", "k2=v2"]).to_dict() == {"k1": "v1", "k2": "v2"}
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
@pytest.mark.asyncio
|
|
380
|
+
async def test_json():
|
|
381
|
+
data = await items(['{"a": 1, "b": 2}']).json()
|
|
382
|
+
assert data == {"a": 1, "b": 2}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
@pytest.mark.asyncio
|
|
386
|
+
async def test_text():
|
|
387
|
+
assert await items(["hello", "world"]).text() == "hello\nworld"
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
@pytest.mark.asyncio
|
|
391
|
+
async def test_table():
|
|
392
|
+
table = await items(["Alice 30 NYC", "Bob 25 LA"]).table()
|
|
393
|
+
assert "Alice" in table
|
|
394
|
+
assert "Bob" in table
|