oed-cli 0.1.2__tar.gz → 0.1.3__tar.gz
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.
- {oed_cli-0.1.2/src/oed_cli.egg-info → oed_cli-0.1.3}/PKG-INFO +2 -2
- {oed_cli-0.1.2 → oed_cli-0.1.3}/README.md +1 -1
- {oed_cli-0.1.2 → oed_cli-0.1.3}/pyproject.toml +1 -1
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/__init__.py +6 -6
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/invoke.py +8 -1
- {oed_cli-0.1.2 → oed_cli-0.1.3/src/oed_cli.egg-info}/PKG-INFO +2 -2
- {oed_cli-0.1.2 → oed_cli-0.1.3}/tests/test_cli.py +1 -1
- {oed_cli-0.1.2 → oed_cli-0.1.3}/tests/test_dynamic.py +137 -15
- {oed_cli-0.1.2 → oed_cli-0.1.3}/LICENSE +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/setup.cfg +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/__main__.py +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/cli.py +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/discovery.py +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/dynamic.py +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/errors.py +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/http.py +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/main.py +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli/py.typed +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli.egg-info/SOURCES.txt +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli.egg-info/dependency_links.txt +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli.egg-info/entry_points.txt +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli.egg-info/requires.txt +0 -0
- {oed_cli-0.1.2 → oed_cli-0.1.3}/src/oed_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oed-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: oed — openEuler Infra command line. Auto-discovered, AI-friendly.
|
|
5
5
|
Author: oed-cli contributors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -97,7 +97,7 @@ pip install -e .
|
|
|
97
97
|
Verify:
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
|
-
oed --version # → oed, version 0.1.
|
|
100
|
+
oed --version # → oed, version 0.1.3
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
---
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
"""oed-cli — openEuler Infra command line, auto-discovered, AI-friendly."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
__version__ = "0.1.
|
|
6
|
-
__all__ = ["__version__"]
|
|
1
|
+
"""oed-cli — openEuler Infra command line, auto-discovered, AI-friendly."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.3"
|
|
6
|
+
__all__ = ["__version__"]
|
|
@@ -160,6 +160,12 @@ def call_operation(
|
|
|
160
160
|
query_params = {k: v for k, v in query_params.items() if v is not None}
|
|
161
161
|
url = f"{op.base_url}{filled_path}"
|
|
162
162
|
|
|
163
|
+
# Discourse (forum) authenticates via Api-Key/Api-Username; inert sentinel
|
|
164
|
+
# until login lands, so the request shape stays visible without leaking a real credential.
|
|
165
|
+
api_headers: dict[str, str] = {}
|
|
166
|
+
if op.service_name == "forum":
|
|
167
|
+
api_headers = {"Api-Key": "oed-placeholder", "Api-Username": "oed-placeholder"}
|
|
168
|
+
|
|
163
169
|
request_headers: dict[str, str] = {"User-Agent": _resolve_user_agent(user_agent)}
|
|
164
170
|
if body is not None:
|
|
165
171
|
request_headers["Content-Type"] = "application/json"
|
|
@@ -167,7 +173,7 @@ def call_operation(
|
|
|
167
173
|
"method": op.backend.method,
|
|
168
174
|
"url": url,
|
|
169
175
|
"query": query_params,
|
|
170
|
-
"headers": request_headers,
|
|
176
|
+
"headers": {**request_headers, **api_headers},
|
|
171
177
|
"body": body,
|
|
172
178
|
}
|
|
173
179
|
|
|
@@ -191,6 +197,7 @@ def call_operation(
|
|
|
191
197
|
url,
|
|
192
198
|
params=query_params if query_params else None,
|
|
193
199
|
body=body,
|
|
200
|
+
headers=api_headers,
|
|
194
201
|
timeout=timeout,
|
|
195
202
|
user_agent=user_agent,
|
|
196
203
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: oed-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: oed — openEuler Infra command line. Auto-discovered, AI-friendly.
|
|
5
5
|
Author: oed-cli contributors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -97,7 +97,7 @@ pip install -e .
|
|
|
97
97
|
Verify:
|
|
98
98
|
|
|
99
99
|
```bash
|
|
100
|
-
oed --version # → oed, version 0.1.
|
|
100
|
+
oed --version # → oed, version 0.1.3
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
---
|
|
@@ -220,7 +220,7 @@ def test_help_enumerates_discovered_services(runner, patched_discovery):
|
|
|
220
220
|
# Quickstart now leads with the per-parameter flag style and the canonical
|
|
221
221
|
# CVE example; it should not mention the legacy API_<NAME> placeholder.
|
|
222
222
|
assert "API_<NAME>" not in text
|
|
223
|
-
assert "--<
|
|
223
|
+
assert "--<kebab-case>" in text
|
|
224
224
|
assert "getSecurityNoticeByCveId --cve-id" in text
|
|
225
225
|
|
|
226
226
|
|
|
@@ -6,7 +6,6 @@ layers, so they need no gateway access.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
-
import json
|
|
10
9
|
import sys
|
|
11
10
|
from pathlib import Path
|
|
12
11
|
|
|
@@ -158,7 +157,9 @@ def patched(monkeypatch):
|
|
|
158
157
|
|
|
159
158
|
captured = {}
|
|
160
159
|
|
|
161
|
-
def _fake_do_call(
|
|
160
|
+
def _fake_do_call(
|
|
161
|
+
method, url, params=None, body=None, timeout=30.0, headers=None, user_agent=None
|
|
162
|
+
):
|
|
162
163
|
class _R:
|
|
163
164
|
status_code = 200
|
|
164
165
|
content = b'{"code":"","msg":"","data":{"ok":true}}'
|
|
@@ -172,6 +173,7 @@ def patched(monkeypatch):
|
|
|
172
173
|
captured["url"] = url
|
|
173
174
|
captured["params"] = params
|
|
174
175
|
captured["body"] = body
|
|
176
|
+
captured["headers"] = headers or {}
|
|
175
177
|
captured["user_agent"] = user_agent
|
|
176
178
|
return _R()
|
|
177
179
|
|
|
@@ -515,6 +517,44 @@ def test_call_operation_post_sends_body(patched):
|
|
|
515
517
|
assert patched["captured"]["body"] == body
|
|
516
518
|
|
|
517
519
|
|
|
520
|
+
def test_call_operation_forum_sends_api_headers(patched):
|
|
521
|
+
"""Forum (Discourse) requests carry the Api-Key / Api-Username sentinels
|
|
522
|
+
on the real request and in the dry-run view."""
|
|
523
|
+
|
|
524
|
+
from oed_cli.dynamic import operations_table
|
|
525
|
+
from oed_cli.invoke import call_operation
|
|
526
|
+
|
|
527
|
+
table = operations_table(SAMPLE_SPEC, "forum", base_url="https://apig.osinfra.cn")
|
|
528
|
+
op = table["API_getSoftwarePackage"]
|
|
529
|
+
|
|
530
|
+
call_operation(op, params={"id": "1"})
|
|
531
|
+
assert patched["captured"]["headers"] == {
|
|
532
|
+
"Api-Key": "oed-placeholder",
|
|
533
|
+
"Api-Username": "oed-placeholder",
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
dry = call_operation(op, params={"id": "1"}, dry_run=True)
|
|
537
|
+
assert dry["request"]["headers"]["Api-Key"] == "oed-placeholder"
|
|
538
|
+
assert dry["request"]["headers"]["Api-Username"] == "oed-placeholder"
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def test_call_operation_non_forum_omits_api_headers(patched):
|
|
542
|
+
"""Non-forum services get no extra auth headers."""
|
|
543
|
+
|
|
544
|
+
from oed_cli.dynamic import operations_table
|
|
545
|
+
from oed_cli.invoke import call_operation
|
|
546
|
+
|
|
547
|
+
table = operations_table(
|
|
548
|
+
SAMPLE_SPEC, "software-package-server", base_url="https://apig.osinfra.cn"
|
|
549
|
+
)
|
|
550
|
+
op = table["API_getSoftwarePackage"]
|
|
551
|
+
|
|
552
|
+
call_operation(op, params={"id": "1"})
|
|
553
|
+
headers = patched["captured"]["headers"]
|
|
554
|
+
assert "Api-Key" not in headers
|
|
555
|
+
assert "Api-Username" not in headers
|
|
556
|
+
|
|
557
|
+
|
|
518
558
|
def test_call_operation_missing_path_param_raises_user_error(patched):
|
|
519
559
|
from oed_cli.dynamic import operations_table
|
|
520
560
|
from oed_cli.errors import UserError
|
|
@@ -620,7 +660,6 @@ def test_service_level_help_with_method_rejected(patched, monkeypatch, capsys):
|
|
|
620
660
|
|
|
621
661
|
|
|
622
662
|
def test_spec_missing_returns_exit_4(patched, runner, monkeypatch, capsys):
|
|
623
|
-
from oed_cli import dynamic as dyn
|
|
624
663
|
from oed_cli import main as oed_main
|
|
625
664
|
from oed_cli.errors import NotFoundError
|
|
626
665
|
|
|
@@ -646,14 +685,17 @@ def test_spec_missing_returns_exit_4(patched, runner, monkeypatch, capsys):
|
|
|
646
685
|
def test_describe_helpers_and_main_dispatch(patched, monkeypatch, capsys):
|
|
647
686
|
"""Compact sweep covering describe_*, main per-param flag dispatch,
|
|
648
687
|
and parse_json_arg / coerce_flag_value edge cases."""
|
|
688
|
+
from oed_cli import main as oed_main
|
|
649
689
|
from oed_cli.discovery import ServiceMeta
|
|
650
690
|
from oed_cli.dynamic import (
|
|
651
|
-
|
|
652
|
-
|
|
691
|
+
coerce_flag_value,
|
|
692
|
+
collect_operations,
|
|
693
|
+
operations_table,
|
|
694
|
+
parse_json_arg,
|
|
695
|
+
resolve_operation,
|
|
653
696
|
)
|
|
654
|
-
from oed_cli.invoke import describe_service, describe_operation_help, call_operation
|
|
655
|
-
from oed_cli import main as oed_main
|
|
656
697
|
from oed_cli.errors import UserError
|
|
698
|
+
from oed_cli.invoke import call_operation, describe_operation_help, describe_service
|
|
657
699
|
|
|
658
700
|
svc = ServiceMeta.from_raw(SAMPLE_FEED["communities"]["openeuler"][0])
|
|
659
701
|
ops = collect_operations(SAMPLE_SPEC, svc.service_name)
|
|
@@ -705,14 +747,84 @@ def test_describe_helpers_and_main_dispatch(patched, monkeypatch, capsys):
|
|
|
705
747
|
assert coerce_flag_value({"schema": {"type": "number"}}, "abc") == "abc"
|
|
706
748
|
|
|
707
749
|
|
|
750
|
+
def test_resolve_json_body_schema_edge_cases():
|
|
751
|
+
"""Non-dict media / schema blocks resolve to ``None`` instead of crashing."""
|
|
752
|
+
|
|
753
|
+
from oed_cli.dynamic import _resolve_json_body_schema
|
|
754
|
+
|
|
755
|
+
assert _resolve_json_body_schema({"content": {}}, SAMPLE_SPEC) is None
|
|
756
|
+
assert _resolve_json_body_schema(
|
|
757
|
+
{"content": {"application/json": {"schema": "not-a-dict"}}}, SAMPLE_SPEC
|
|
758
|
+
) is None
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
def test_operations_for_one_shot_helper(patched):
|
|
762
|
+
"""``operations_for`` resolves, fetches, and bakes the runtime base_url in."""
|
|
763
|
+
|
|
764
|
+
from oed_cli.dynamic import operations_for
|
|
765
|
+
|
|
766
|
+
svc, table, spec = operations_for("software-package-server")
|
|
767
|
+
assert svc.service_name == "software-package-server"
|
|
768
|
+
assert spec is SAMPLE_SPEC
|
|
769
|
+
op = table["API_getSoftwarePackage"]
|
|
770
|
+
assert op.base_url == "https://apig.osinfra.cn"
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
def test_body_field_summary_edge_cases():
|
|
774
|
+
"""Non-dict schema returns [] and non-dict properties are skipped."""
|
|
775
|
+
|
|
776
|
+
from oed_cli.invoke import _body_field_summary
|
|
777
|
+
|
|
778
|
+
assert _body_field_summary(None) == []
|
|
779
|
+
out = _body_field_summary(
|
|
780
|
+
{
|
|
781
|
+
"properties": {
|
|
782
|
+
"bad": "not-a-dict",
|
|
783
|
+
"good": {"type": "string", "description": "a description"},
|
|
784
|
+
"plain": {"type": "integer"},
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
)
|
|
788
|
+
assert [e["name"] for e in out] == ["good", "plain"]
|
|
789
|
+
assert out[0]["description"] == "a description"
|
|
790
|
+
assert "description" not in out[1]
|
|
791
|
+
|
|
792
|
+
|
|
793
|
+
def test_describe_operation_help_with_body(patched):
|
|
794
|
+
"""Operation help for a POST with a body exposes params + body field summary."""
|
|
795
|
+
|
|
796
|
+
from oed_cli.dynamic import operations_table
|
|
797
|
+
from oed_cli.invoke import describe_operation_help
|
|
798
|
+
|
|
799
|
+
svc = patched["feed"].services[0]
|
|
800
|
+
op = operations_table(SAMPLE_SPEC, svc.service_name)["API_applyNewSoftwarePackage"]
|
|
801
|
+
doc = describe_operation_help(op, svc)
|
|
802
|
+
assert doc["ok"] is True
|
|
803
|
+
assert doc["usage"] and doc["examples"]
|
|
804
|
+
assert doc["body_required"] is True
|
|
805
|
+
assert doc["parameters"] == []
|
|
806
|
+
assert doc["body_required_fields"] == []
|
|
807
|
+
assert doc["body_schema"] == {"type": "object"}
|
|
808
|
+
|
|
809
|
+
|
|
810
|
+
def test_dispatch_dynamic_help_legacy_wrapper(patched, capsys):
|
|
811
|
+
"""Legacy ``_dispatch_dynamic_help`` resolves + collects and lists operations."""
|
|
812
|
+
|
|
813
|
+
from oed_cli.main import _dispatch_dynamic_help
|
|
814
|
+
|
|
815
|
+
code = _dispatch_dynamic_help("software-package-server", [])
|
|
816
|
+
out = capsys.readouterr()
|
|
817
|
+
assert code == 0 and "listSoftwarePackages" in (out.out + out.err)
|
|
818
|
+
|
|
819
|
+
|
|
708
820
|
# ---------- main() dispatch edges ----------
|
|
709
821
|
|
|
710
822
|
|
|
711
823
|
def test_main_dispatch_comprehensive(patched, monkeypatch, capsys):
|
|
712
824
|
"""Sweep main dispatch surfaces: reserved routes, --key=value, parse-error,
|
|
713
825
|
dry-run short-circuit, service-only listing, and call_operation errors."""
|
|
714
|
-
from oed_cli import main as oed_main
|
|
715
826
|
from oed_cli import http as http_mod
|
|
827
|
+
from oed_cli import main as oed_main
|
|
716
828
|
from oed_cli.errors import NetworkError
|
|
717
829
|
|
|
718
830
|
monkeypatch.setenv("OED_COMMUNITY", "openeuler")
|
|
@@ -758,8 +870,13 @@ def test_main_dispatch_comprehensive(patched, monkeypatch, capsys):
|
|
|
758
870
|
code = oed_main.main(["software-package-server", "API_getSoftwarePackage", "--id", "1"])
|
|
759
871
|
out = capsys.readouterr()
|
|
760
872
|
assert code == 2 and "network_error" in (out.out + out.err)
|
|
761
|
-
monkeypatch.setattr(
|
|
762
|
-
|
|
873
|
+
monkeypatch.setattr(
|
|
874
|
+
http_mod,
|
|
875
|
+
"get_request",
|
|
876
|
+
lambda *a, **k: type(
|
|
877
|
+
"R", (), {"status_code": 503, "text": "", "content": b"", "headers": {}}
|
|
878
|
+
)(),
|
|
879
|
+
)
|
|
763
880
|
code = oed_main.main(["software-package-server", "API_getSoftwarePackage", "--id", "1"])
|
|
764
881
|
assert code == 3
|
|
765
882
|
|
|
@@ -770,7 +887,7 @@ def test_main_dispatch_comprehensive(patched, monkeypatch, capsys):
|
|
|
770
887
|
def test_resolve_user_agent_precedence(monkeypatch):
|
|
771
888
|
"""Explicit arg > ``OED_USER_AGENT`` env > bundled default; empty string falls through."""
|
|
772
889
|
|
|
773
|
-
from oed_cli.http import
|
|
890
|
+
from oed_cli.http import DEFAULT_USER_AGENT, _resolve_user_agent
|
|
774
891
|
|
|
775
892
|
monkeypatch.delenv("OED_USER_AGENT", raising=False)
|
|
776
893
|
assert _resolve_user_agent(None) == DEFAULT_USER_AGENT
|
|
@@ -836,16 +953,21 @@ def test_dispatch_user_agent_flag_flows_to_http(patched, monkeypatch, capsys):
|
|
|
836
953
|
assert patched["captured"]["user_agent"] == "browser-mock/9.9"
|
|
837
954
|
|
|
838
955
|
|
|
839
|
-
def test_dispatch_user_agent_env_only(patched, monkeypatch):
|
|
840
|
-
"""No flag, only env → env value
|
|
956
|
+
def test_dispatch_user_agent_env_only(patched, monkeypatch, capsys):
|
|
957
|
+
"""No --user-agent flag, only OED_USER_AGENT env → env value lands in the
|
|
958
|
+
outbound User-Agent header (resolved downstream in the HTTP layer)."""
|
|
841
959
|
|
|
842
960
|
from oed_cli import main as oed_main
|
|
843
961
|
|
|
844
962
|
monkeypatch.setenv("OED_COMMUNITY", "openeuler")
|
|
845
963
|
monkeypatch.setenv("OED_USER_AGENT", "env-only/3.0")
|
|
846
|
-
code = oed_main.main([
|
|
964
|
+
code = oed_main.main([
|
|
965
|
+
"software-package-server", "API_getSoftwarePackage",
|
|
966
|
+
"--id", "1", "--dry-run",
|
|
967
|
+
])
|
|
847
968
|
assert code == 0
|
|
848
|
-
|
|
969
|
+
out = capsys.readouterr().out
|
|
970
|
+
assert '"User-Agent": "env-only/3.0"' in out
|
|
849
971
|
|
|
850
972
|
|
|
851
973
|
def test_dispatch_user_agent_flag_overrides_env(patched, monkeypatch):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|