aidp-cli 1.0.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.
aidp_cli/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ # Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
2
+
3
+ """Command line wrapper for the AIDP data plane Python SDK."""
@@ -0,0 +1,166 @@
1
+ # Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Any
7
+
8
+ from aidp_cli.manifest import BodyField, CommandDefinition
9
+
10
+
11
+ MAX_BODY_SCAN_DEPTH = 20
12
+
13
+
14
+ def _normalized_identifier(value: str) -> str:
15
+ return re.sub(r"[^A-Za-z0-9]", "", value).lower()
16
+
17
+
18
+ SENSITIVE_BODY_FIELD_NAMES = {
19
+ _normalized_identifier(name)
20
+ for name in (
21
+ "apiKey",
22
+ "accessToken",
23
+ "clientSecret",
24
+ "credentialDetails",
25
+ "passphrase",
26
+ "password",
27
+ "personalAccessToken",
28
+ "privateApiKey",
29
+ "privateKey",
30
+ "refreshToken",
31
+ "secret",
32
+ "secretContentBase64",
33
+ "secretId",
34
+ "secretKey",
35
+ "secretTokenPair",
36
+ "secretValue",
37
+ "token",
38
+ )
39
+ }
40
+
41
+
42
+ def json_body_argument_source(value: str) -> str:
43
+ if value == "-":
44
+ return "stdin"
45
+ if value.startswith("@") or value.startswith("file://"):
46
+ return "file"
47
+ return "inline"
48
+
49
+
50
+ def command_has_sensitive_body_fields(command: CommandDefinition) -> bool:
51
+ return _fields_contain_sensitive_body_field(
52
+ command,
53
+ _root_body_fields(command),
54
+ "",
55
+ (),
56
+ set(),
57
+ 0,
58
+ )
59
+
60
+
61
+ def body_contains_sensitive_field(command: CommandDefinition, body: Any) -> bool:
62
+ return _value_contains_sensitive_field(command, body, _root_body_model_name(command), (), 0)
63
+
64
+
65
+ def _fields_contain_sensitive_body_field(
66
+ command: CommandDefinition,
67
+ fields: tuple[BodyField, ...],
68
+ model_name: str,
69
+ parent_path: tuple[str, ...],
70
+ seen_models: set[str],
71
+ depth: int,
72
+ ) -> bool:
73
+ if depth >= MAX_BODY_SCAN_DEPTH:
74
+ return False
75
+
76
+ for field in fields:
77
+ path = (*parent_path, field.name)
78
+ if _is_sensitive_field(field.name, model_name, path):
79
+ return True
80
+ if not field.model_name or field.model_name in seen_models:
81
+ continue
82
+ model = command.body_models.get(field.model_name)
83
+ if model is None:
84
+ continue
85
+ next_seen = set(seen_models)
86
+ next_seen.add(field.model_name)
87
+ if _fields_contain_sensitive_body_field(
88
+ command,
89
+ model.fields,
90
+ field.model_name,
91
+ path,
92
+ next_seen,
93
+ depth + 1,
94
+ ):
95
+ return True
96
+ return False
97
+
98
+
99
+ def _value_contains_sensitive_field(
100
+ command: CommandDefinition,
101
+ value: Any,
102
+ model_name: str,
103
+ parent_path: tuple[str, ...],
104
+ depth: int,
105
+ ) -> bool:
106
+ if depth >= MAX_BODY_SCAN_DEPTH:
107
+ return False
108
+ if isinstance(value, list):
109
+ return any(
110
+ _value_contains_sensitive_field(command, item, model_name, parent_path, depth + 1)
111
+ for item in value
112
+ )
113
+ if not isinstance(value, dict):
114
+ return False
115
+
116
+ for key, nested_value in value.items():
117
+ path = (*parent_path, str(key))
118
+ if _is_sensitive_field(str(key), model_name, path):
119
+ return True
120
+ field = _body_field_for_key(command, model_name, str(key))
121
+ next_model_name = field.model_name if field is not None else ""
122
+ if _value_contains_sensitive_field(command, nested_value, next_model_name, path, depth + 1):
123
+ return True
124
+ return False
125
+
126
+
127
+ def _is_sensitive_field(field_name: str, model_name: str, path: tuple[str, ...]) -> bool:
128
+ normalized_name = _normalized_identifier(field_name)
129
+ if normalized_name in SENSITIVE_BODY_FIELD_NAMES:
130
+ return True
131
+ if normalized_name == "credential":
132
+ return _normalized_identifier(model_name) == "gitconfig" or _parent_path_name(path) == "gitconfig"
133
+ return False
134
+
135
+
136
+ def _body_field_for_key(command: CommandDefinition, model_name: str, key: str) -> BodyField | None:
137
+ normalized_key = _normalized_identifier(key)
138
+ return next(
139
+ (
140
+ field
141
+ for field in _fields_for_model(command, model_name)
142
+ if _normalized_identifier(field.name) == normalized_key
143
+ ),
144
+ None,
145
+ )
146
+
147
+
148
+ def _fields_for_model(command: CommandDefinition, model_name: str) -> tuple[BodyField, ...]:
149
+ if model_name and model_name in command.body_models:
150
+ return command.body_models[model_name].fields
151
+ return _root_body_fields(command)
152
+
153
+
154
+ def _root_body_fields(command: CommandDefinition) -> tuple[BodyField, ...]:
155
+ model_name = _root_body_model_name(command)
156
+ if model_name and model_name in command.body_models:
157
+ return command.body_models[model_name].fields
158
+ return command.body_fields
159
+
160
+
161
+ def _root_body_model_name(command: CommandDefinition) -> str:
162
+ return command.body_model if command.body_model and command.body_model in command.body_models else ""
163
+
164
+
165
+ def _parent_path_name(path: tuple[str, ...]) -> str:
166
+ return _normalized_identifier(path[-2]) if len(path) >= 2 else ""
aidp_cli/discovery.py ADDED
@@ -0,0 +1,34 @@
1
+ # Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
2
+
3
+ from __future__ import annotations
4
+
5
+ import inspect
6
+ import re
7
+ from types import ModuleType
8
+
9
+
10
+ def discover_clients(sdk_module: ModuleType) -> dict[str, type]:
11
+ clients: dict[str, type] = {}
12
+ for class_name, client_cls in inspect.getmembers(sdk_module, inspect.isclass):
13
+ if not is_generated_client(class_name, client_cls, sdk_module):
14
+ continue
15
+ clients[service_name_from_client_class(class_name)] = client_cls
16
+ return dict(sorted(clients.items()))
17
+
18
+
19
+ def is_generated_client(class_name: str, client_cls: type, sdk_module: ModuleType) -> bool:
20
+ if not class_name.endswith("Client"):
21
+ return False
22
+ if class_name.endswith("CompositeOperations"):
23
+ return False
24
+ return client_cls.__module__.startswith(sdk_module.__name__)
25
+
26
+
27
+ def service_name_from_client_class(class_name: str) -> str:
28
+ stem = class_name.removesuffix("Client")
29
+ words = re.findall(r"[A-Z]+(?=[A-Z][a-z]|\d|$)|[A-Z]?[a-z]+|\d+", stem)
30
+ return "-".join(word.lower() for word in words)
31
+
32
+
33
+ def client_class_from_service_name(service_name: str) -> str:
34
+ return "".join(part.capitalize() for part in service_name.split("-")) + "Client"