day2 0.2.0.dev1__tar.gz → 0.2.2__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.
- {day2-0.2.0.dev1 → day2-0.2.2}/PKG-INFO +3 -11
- {day2-0.2.0.dev1 → day2-0.2.2}/README.md +2 -10
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/__init__.py +18 -1
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/client/base.py +56 -50
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/client/config.py +15 -34
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/models/__init__.py +2 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/models/assessment.py +196 -19
- day2-0.2.2/day2/models/cost.py +81 -0
- day2-0.2.2/day2/models/tenant.py +104 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/resources/__init__.py +2 -1
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/resources/assessment.py +8 -9
- day2-0.2.2/day2/resources/cost.py +62 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/resources/tenant.py +39 -3
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/session.py +38 -5
- {day2-0.2.0.dev1 → day2-0.2.2}/day2.egg-info/PKG-INFO +3 -11
- {day2-0.2.0.dev1 → day2-0.2.2}/day2.egg-info/SOURCES.txt +6 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/cli.py +2 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/commands/assessment.py +32 -6
- day2-0.2.2/day2_cli/commands/cost.py +129 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/commands/tenant.py +83 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/pyproject.toml +7 -1
- {day2-0.2.0.dev1 → day2-0.2.2}/tests/test_cli_config.py +1 -1
- day2-0.2.2/tests/test_client_base.py +231 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/tests/test_config.py +3 -3
- {day2-0.2.0.dev1 → day2-0.2.2}/tests/test_models_assessment.py +12 -11
- day2-0.2.2/tests/test_models_cost.py +112 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/tests/test_models_tenant.py +79 -15
- {day2-0.2.0.dev1 → day2-0.2.2}/tests/test_resources_assessment.py +2 -0
- day2-0.2.2/tests/test_resources_cost.py +90 -0
- day2-0.2.2/tests/test_resources_tenant.py +230 -0
- day2-0.2.0.dev1/day2/models/tenant.py +0 -45
- day2-0.2.0.dev1/tests/test_resources_tenant.py +0 -100
- {day2-0.2.0.dev1 → day2-0.2.2}/LICENSE +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/_version.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/auth/__init__.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/auth/credentials.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/client/__init__.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2/exceptions.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2.egg-info/dependency_links.txt +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2.egg-info/entry_points.txt +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2.egg-info/requires.txt +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2.egg-info/top_level.txt +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/__init__.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/commands/__init__.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/commands/auth.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/commands/config.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/utils/__init__.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/utils/config.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/utils/formatters.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/day2_cli/utils/output_formatter.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/setup.cfg +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/tests/test_cli_auth.py +0 -0
- {day2-0.2.0.dev1 → day2-0.2.2}/tests/test_session.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: day2
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: MontyCloud DAY2 SDK for Python
|
|
5
5
|
Author-email: MontyCloud <info@montycloud.com>
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -152,7 +152,7 @@ The SDK supports authentication with both an API key and an API secret key. You
|
|
|
152
152
|
1. Directly in code:
|
|
153
153
|
```python
|
|
154
154
|
# Simplified API
|
|
155
|
-
session = day2.Session(api_key="your-api-key")
|
|
155
|
+
session = day2.Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
156
156
|
|
|
157
157
|
# With API secret key
|
|
158
158
|
session = day2.Session(
|
|
@@ -191,7 +191,7 @@ The SDK supports multi-tenant operations through session-based tenant context:
|
|
|
191
191
|
|
|
192
192
|
```python
|
|
193
193
|
# Create a session
|
|
194
|
-
session = day2.Session(api_key="your-api-key")
|
|
194
|
+
session = day2.Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
195
195
|
|
|
196
196
|
# Set tenant context
|
|
197
197
|
session.set_tenant("org-123")
|
|
@@ -276,14 +276,6 @@ except Exception as e:
|
|
|
276
276
|
print(f"Unexpected error: {e}")
|
|
277
277
|
```
|
|
278
278
|
|
|
279
|
-
## Development
|
|
280
|
-
|
|
281
|
-
Refer to [DEVELOPER.md](DEVELOPER.md) for development instructions.
|
|
282
|
-
|
|
283
|
-
## Release
|
|
284
|
-
|
|
285
|
-
Refer to [RELEASE.md](RELEASE.md) for release instructions.
|
|
286
|
-
|
|
287
279
|
## License
|
|
288
280
|
|
|
289
281
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -114,7 +114,7 @@ The SDK supports authentication with both an API key and an API secret key. You
|
|
|
114
114
|
1. Directly in code:
|
|
115
115
|
```python
|
|
116
116
|
# Simplified API
|
|
117
|
-
session = day2.Session(api_key="your-api-key")
|
|
117
|
+
session = day2.Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
118
118
|
|
|
119
119
|
# With API secret key
|
|
120
120
|
session = day2.Session(
|
|
@@ -153,7 +153,7 @@ The SDK supports multi-tenant operations through session-based tenant context:
|
|
|
153
153
|
|
|
154
154
|
```python
|
|
155
155
|
# Create a session
|
|
156
|
-
session = day2.Session(api_key="your-api-key")
|
|
156
|
+
session = day2.Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
157
157
|
|
|
158
158
|
# Set tenant context
|
|
159
159
|
session.set_tenant("org-123")
|
|
@@ -238,14 +238,6 @@ except Exception as e:
|
|
|
238
238
|
print(f"Unexpected error: {e}")
|
|
239
239
|
```
|
|
240
240
|
|
|
241
|
-
## Development
|
|
242
|
-
|
|
243
|
-
Refer to [DEVELOPER.md](DEVELOPER.md) for development instructions.
|
|
244
|
-
|
|
245
|
-
## Release
|
|
246
|
-
|
|
247
|
-
Refer to [RELEASE.md](RELEASE.md) for release instructions.
|
|
248
|
-
|
|
249
241
|
## License
|
|
250
242
|
|
|
251
243
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
@@ -12,6 +12,7 @@ from day2.session import Session
|
|
|
12
12
|
|
|
13
13
|
if TYPE_CHECKING:
|
|
14
14
|
from day2.resources.assessment import AssessmentClient
|
|
15
|
+
from day2.resources.cost import CostClient
|
|
15
16
|
from day2.resources.tenant import TenantClient
|
|
16
17
|
|
|
17
18
|
|
|
@@ -54,4 +55,20 @@ def assessment() -> "AssessmentClient":
|
|
|
54
55
|
return get_default_session().assessment
|
|
55
56
|
|
|
56
57
|
|
|
57
|
-
|
|
58
|
+
def cost() -> "CostClient":
|
|
59
|
+
"""Get a cost client using the default session.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
CostClient: A client for interacting with cost resources
|
|
63
|
+
"""
|
|
64
|
+
return get_default_session().cost
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
__all__ = [
|
|
68
|
+
"Session",
|
|
69
|
+
"__version__",
|
|
70
|
+
"tenant",
|
|
71
|
+
"assessment",
|
|
72
|
+
"cost",
|
|
73
|
+
"get_default_session",
|
|
74
|
+
]
|
|
@@ -84,12 +84,6 @@ class BaseClient:
|
|
|
84
84
|
if self.session.tenant_id:
|
|
85
85
|
headers["x-tenant-id"] = str(self.session.tenant_id)
|
|
86
86
|
|
|
87
|
-
# Add any additional headers from config
|
|
88
|
-
if self._config.additional_headers:
|
|
89
|
-
# Ensure all header values are strings
|
|
90
|
-
for key, value in self._config.additional_headers.items():
|
|
91
|
-
headers[key] = str(value)
|
|
92
|
-
|
|
93
87
|
return headers
|
|
94
88
|
|
|
95
89
|
def _handle_response(self, response: requests.Response) -> Dict[str, Any]:
|
|
@@ -112,10 +106,10 @@ class BaseClient:
|
|
|
112
106
|
|
|
113
107
|
try:
|
|
114
108
|
data: Dict[str, Any] = response.json()
|
|
115
|
-
logger.
|
|
109
|
+
logger.debug("Response data: %s", json.dumps(data, indent=2)[:1000])
|
|
116
110
|
except ValueError:
|
|
117
111
|
data = {"Message": response.text}
|
|
118
|
-
logger.
|
|
112
|
+
logger.debug("Response text: %s", response.text[:1000])
|
|
119
113
|
|
|
120
114
|
if 400 <= response.status_code < 500:
|
|
121
115
|
message = data.get("Message", "Client error")
|
|
@@ -134,63 +128,75 @@ class BaseClient:
|
|
|
134
128
|
|
|
135
129
|
return dict(data)
|
|
136
130
|
|
|
137
|
-
@retry(
|
|
138
|
-
stop=stop_after_attempt(3),
|
|
139
|
-
wait=wait_exponential(multiplier=1, min=2, max=10),
|
|
140
|
-
retry=retry_if_exception_type(ServerError),
|
|
141
|
-
reraise=True,
|
|
142
|
-
)
|
|
143
131
|
def _request_with_retry(
|
|
144
132
|
self, method: str, url: str, **kwargs: Any
|
|
145
133
|
) -> Dict[str, Any]:
|
|
146
|
-
"""Make an API request with retry logic.
|
|
134
|
+
"""Make an API request with retry logic based on configuration.
|
|
147
135
|
|
|
148
136
|
Args:
|
|
149
|
-
method: HTTP method (GET, POST,
|
|
150
|
-
url:
|
|
151
|
-
**kwargs: Additional arguments to pass to requests.
|
|
137
|
+
method: HTTP method (GET, POST, etc.)
|
|
138
|
+
url: URL to request
|
|
139
|
+
**kwargs: Additional arguments to pass to requests.request
|
|
152
140
|
|
|
153
141
|
Returns:
|
|
154
|
-
|
|
142
|
+
Dict containing the parsed response
|
|
155
143
|
|
|
156
144
|
Raises:
|
|
157
|
-
ClientError: For client errors (4xx)
|
|
158
|
-
ServerError: For server errors (5xx)
|
|
145
|
+
ClientError: For client errors (4xx)
|
|
146
|
+
ServerError: For server errors (5xx)
|
|
159
147
|
"""
|
|
160
|
-
headers = self._get_headers()
|
|
161
|
-
|
|
162
|
-
# Update headers with any provided in kwargs
|
|
163
|
-
if "headers" in kwargs:
|
|
164
|
-
headers.update(kwargs.pop("headers"))
|
|
165
|
-
|
|
166
|
-
# Log request details
|
|
167
|
-
logger.debug(
|
|
168
|
-
"Making %s request to %s with headers %s and kwargs %s",
|
|
169
|
-
method,
|
|
170
|
-
url,
|
|
171
|
-
headers,
|
|
172
|
-
kwargs,
|
|
173
|
-
)
|
|
174
148
|
|
|
175
|
-
|
|
176
|
-
|
|
149
|
+
# Define the actual request function to retry
|
|
150
|
+
def _do_request() -> Dict[str, Any]:
|
|
151
|
+
headers = self._get_headers()
|
|
152
|
+
|
|
153
|
+
# Update headers with any provided in kwargs
|
|
154
|
+
request_kwargs = kwargs.copy()
|
|
155
|
+
if "headers" in request_kwargs:
|
|
156
|
+
headers.update(request_kwargs.pop("headers"))
|
|
157
|
+
|
|
158
|
+
# Log request details
|
|
159
|
+
logger.debug(
|
|
160
|
+
"Making %s request to %s with headers %s and kwargs %s",
|
|
177
161
|
method,
|
|
178
162
|
url,
|
|
179
|
-
headers
|
|
180
|
-
|
|
181
|
-
**kwargs,
|
|
163
|
+
headers,
|
|
164
|
+
request_kwargs,
|
|
182
165
|
)
|
|
183
166
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
167
|
+
try:
|
|
168
|
+
response = requests.request(
|
|
169
|
+
method,
|
|
170
|
+
url,
|
|
171
|
+
headers=headers,
|
|
172
|
+
timeout=self._config.timeout,
|
|
173
|
+
**request_kwargs,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Log response details
|
|
177
|
+
logger.debug("Received response with status %s", response.status_code)
|
|
178
|
+
logger.debug("Response headers: %s", response.headers)
|
|
179
|
+
logger.debug("Response content: %s", response.text[:1000])
|
|
180
|
+
|
|
181
|
+
return self._handle_response(response)
|
|
182
|
+
except requests.RequestException as e:
|
|
183
|
+
logger.error("Request failed: %s", str(e))
|
|
184
|
+
raise ServerError(str(e), 0, None) from e
|
|
185
|
+
|
|
186
|
+
# Create a retry object with config values
|
|
187
|
+
r = retry(
|
|
188
|
+
stop=stop_after_attempt(self._config.max_retries),
|
|
189
|
+
wait=wait_exponential(
|
|
190
|
+
multiplier=self._config.retry_backoff_factor,
|
|
191
|
+
min=self._config.retry_min_delay,
|
|
192
|
+
max=self._config.retry_max_delay,
|
|
193
|
+
),
|
|
194
|
+
retry=retry_if_exception_type(ServerError),
|
|
195
|
+
reraise=True,
|
|
196
|
+
)
|
|
190
197
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
raise ServerError(str(e), 0) from e
|
|
198
|
+
# Apply retry to our request function and call it
|
|
199
|
+
return r(_do_request)()
|
|
194
200
|
|
|
195
201
|
def _make_request(
|
|
196
202
|
self, method: str, endpoint: str, **kwargs: Any
|
|
@@ -210,6 +216,6 @@ class BaseClient:
|
|
|
210
216
|
kwargs["json"] = kwargs.pop("json_data")
|
|
211
217
|
|
|
212
218
|
url = self._get_endpoint_url(endpoint)
|
|
213
|
-
logger.
|
|
219
|
+
logger.debug("Making request to %s %s", method, url)
|
|
214
220
|
# Pass the full URL to the request method
|
|
215
221
|
return self._request_with_retry(method, url, **kwargs)
|
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
"""Configuration for the MontyCloud SDK."""
|
|
2
2
|
|
|
3
3
|
import configparser
|
|
4
|
-
from dataclasses import dataclass
|
|
4
|
+
from dataclasses import dataclass
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Any, Dict, Optional
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@dataclass
|
|
10
10
|
class Config:
|
|
11
|
-
"""Configuration for the MontyCloud SDK.
|
|
11
|
+
"""Configuration for the MontyCloud SDK.
|
|
12
|
+
|
|
13
|
+
Attributes:
|
|
14
|
+
base_url: Base URL for the MontyCloud API
|
|
15
|
+
api_version: API version to use
|
|
16
|
+
timeout: Request timeout in seconds
|
|
17
|
+
max_retries: Maximum number of retries for failed requests
|
|
18
|
+
retry_backoff_factor: Exponential backoff factor for retries
|
|
19
|
+
tenant_id: Default tenant ID for operations
|
|
20
|
+
retry_min_delay: Minimum delay between retries in seconds
|
|
21
|
+
retry_max_delay: Maximum delay between retries in seconds
|
|
22
|
+
output_format: Output format for CLI commands (table or json)
|
|
23
|
+
"""
|
|
12
24
|
|
|
13
25
|
# Base URL for the MontyCloud API
|
|
14
|
-
base_url: str = "https://
|
|
26
|
+
base_url: str = "https://api.montycloud.com/day2/api"
|
|
15
27
|
|
|
16
28
|
# API version
|
|
17
29
|
api_version: str = "v1"
|
|
@@ -34,12 +46,6 @@ class Config:
|
|
|
34
46
|
# Maximum retry delay in seconds
|
|
35
47
|
retry_max_delay: float = 10.0
|
|
36
48
|
|
|
37
|
-
# Status codes to retry on
|
|
38
|
-
retry_status_codes: tuple = (500, 502, 503, 504)
|
|
39
|
-
|
|
40
|
-
# Additional headers to include in all requests
|
|
41
|
-
additional_headers: Dict[str, str] = field(default_factory=dict)
|
|
42
|
-
|
|
43
49
|
# CLI output format (table or json)
|
|
44
50
|
output_format: str = "table"
|
|
45
51
|
|
|
@@ -67,10 +73,6 @@ class Config:
|
|
|
67
73
|
"tenant_id": self.tenant_id,
|
|
68
74
|
"retry_min_delay": self.retry_min_delay,
|
|
69
75
|
"retry_max_delay": self.retry_max_delay,
|
|
70
|
-
"retry_status_codes": ",".join(
|
|
71
|
-
str(code) for code in self.retry_status_codes
|
|
72
|
-
),
|
|
73
|
-
"additional_headers": self.additional_headers,
|
|
74
76
|
"output_format": self.output_format,
|
|
75
77
|
}
|
|
76
78
|
|
|
@@ -84,16 +86,6 @@ class Config:
|
|
|
84
86
|
Returns:
|
|
85
87
|
Config object initialized with values from dictionary.
|
|
86
88
|
"""
|
|
87
|
-
# Handle special case for retry_status_codes if it's a string
|
|
88
|
-
if "retry_status_codes" in config_dict and isinstance(
|
|
89
|
-
config_dict["retry_status_codes"], str
|
|
90
|
-
):
|
|
91
|
-
config_dict["retry_status_codes"] = tuple(
|
|
92
|
-
int(code)
|
|
93
|
-
for code in config_dict["retry_status_codes"].split(",")
|
|
94
|
-
if code.strip().isdigit()
|
|
95
|
-
)
|
|
96
|
-
|
|
97
89
|
return cls(**config_dict)
|
|
98
90
|
|
|
99
91
|
@classmethod
|
|
@@ -150,17 +142,6 @@ class Config:
|
|
|
150
142
|
fallback=str(cls.retry_max_delay),
|
|
151
143
|
)
|
|
152
144
|
),
|
|
153
|
-
# Status codes need special handling as they're a tuple in code
|
|
154
|
-
retry_status_codes=tuple(
|
|
155
|
-
int(code)
|
|
156
|
-
for code in config_parser.get(
|
|
157
|
-
"DEFAULT", "retry_status_codes", fallback=""
|
|
158
|
-
).split(",")
|
|
159
|
-
if code.strip().isdigit()
|
|
160
|
-
)
|
|
161
|
-
or cls.retry_status_codes,
|
|
162
|
-
# Headers would need to be serialized/deserialized specially
|
|
163
|
-
additional_headers={},
|
|
164
145
|
# Output format
|
|
165
146
|
output_format=config_parser.get(
|
|
166
147
|
"DEFAULT", "output_format", fallback=cls.output_format
|
|
@@ -7,6 +7,7 @@ from day2.models.assessment import (
|
|
|
7
7
|
GetAssessmentOutput,
|
|
8
8
|
ListAssessmentsOutput,
|
|
9
9
|
)
|
|
10
|
+
from day2.models.cost import GetCostByChargeTypeOutput
|
|
10
11
|
from day2.models.tenant import ListTenantsOutput, TenantDetails
|
|
11
12
|
|
|
12
13
|
__all__ = [
|
|
@@ -17,4 +18,5 @@ __all__ = [
|
|
|
17
18
|
"CreateAssessmentInput",
|
|
18
19
|
"CreateAssessmentOutput",
|
|
19
20
|
"GetAssessmentOutput",
|
|
21
|
+
"GetCostByChargeTypeOutput",
|
|
20
22
|
]
|
|
@@ -5,7 +5,20 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class Assessment(BaseModel):
|
|
8
|
-
"""Details of an assessment.
|
|
8
|
+
"""Details of an assessment.
|
|
9
|
+
|
|
10
|
+
Attributes:
|
|
11
|
+
id: Assessment identifier
|
|
12
|
+
name: Name of the assessment
|
|
13
|
+
description: Description of the assessment
|
|
14
|
+
status: Current status of the assessment
|
|
15
|
+
assessment_arn: AWS ARN of the assessment
|
|
16
|
+
lenses: List of lens identifiers used in the assessment
|
|
17
|
+
total_questions: Total number of questions in the assessment
|
|
18
|
+
answered_questions: Number of questions that have been answered
|
|
19
|
+
created_at: Timestamp when the assessment was created
|
|
20
|
+
updated_at: Timestamp when the assessment was last updated
|
|
21
|
+
"""
|
|
9
22
|
|
|
10
23
|
id: str = Field(alias="AssessmentId")
|
|
11
24
|
name: str = Field(alias="AssessmentName")
|
|
@@ -23,13 +36,15 @@ class Assessment(BaseModel):
|
|
|
23
36
|
|
|
24
37
|
|
|
25
38
|
class ListAssessmentsOutput(BaseModel):
|
|
26
|
-
"""Output of list_assessments operation.
|
|
39
|
+
"""Output of list_assessments operation.
|
|
40
|
+
|
|
41
|
+
Attributes:
|
|
42
|
+
assessments: List of assessment details
|
|
43
|
+
next_page_token: Token for fetching the next page of results
|
|
44
|
+
"""
|
|
27
45
|
|
|
28
46
|
assessments: List[Assessment] = Field(alias="Assessments")
|
|
29
47
|
next_page_token: Optional[str] = Field(None, alias="NextPageToken")
|
|
30
|
-
# Keep these fields for backward compatibility
|
|
31
|
-
page_number: Optional[int] = Field(None, alias="PageNumber")
|
|
32
|
-
has_more: Optional[bool] = Field(None, alias="HasMore")
|
|
33
48
|
|
|
34
49
|
# Allow extra fields
|
|
35
50
|
model_config = ConfigDict(extra="allow")
|
|
@@ -38,16 +53,48 @@ class ListAssessmentsOutput(BaseModel):
|
|
|
38
53
|
class CreateAssessmentInput(BaseModel):
|
|
39
54
|
"""Input for creating an assessment.
|
|
40
55
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
56
|
+
The scope parameter can be in one of two formats:
|
|
57
|
+
|
|
58
|
+
1. Account-based scope:
|
|
59
|
+
```python
|
|
60
|
+
{
|
|
61
|
+
"Project": {},
|
|
62
|
+
"Accounts": [
|
|
63
|
+
{"AccountNumber": "123456789012", "Regions": ["us-east-1", "us-west-2"]}
|
|
64
|
+
]
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
2. Project-based scope:
|
|
69
|
+
```python
|
|
70
|
+
{
|
|
71
|
+
"Project": {"ProjectId": "proj-123", "Applications": ["app1", "app2"]},
|
|
72
|
+
"Accounts": ["123456789012"]
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
name: Name of the assessment
|
|
78
|
+
description: Description of the assessment
|
|
79
|
+
review_owner: Email or identifier of the review owner
|
|
80
|
+
scope: Scope configuration (see formats above)
|
|
81
|
+
lenses: List of lens aliases to use (defaults to ["AWS Well-Architected Framework"] only "AWS Well-Architected Framework" is supported)
|
|
82
|
+
tags: Key-value pairs for tagging the assessment
|
|
83
|
+
region_code: AWS region code where the assessment is created
|
|
84
|
+
environment: Environment type (e.g., PRODUCTION, DEVELOPMENT)
|
|
85
|
+
hosted_account_number: Optional AWS account number for hosting
|
|
86
|
+
diagram_url: Optional URL to architecture diagram
|
|
87
|
+
industry_type: Optional industry type classification
|
|
88
|
+
industry: Optional industry specification
|
|
44
89
|
"""
|
|
45
90
|
|
|
46
91
|
name: str = Field(alias="AssessmentName")
|
|
47
92
|
description: str = Field(alias="Description")
|
|
48
93
|
review_owner: str = Field(alias="ReviewOwner")
|
|
49
94
|
scope: Dict[str, Any] = Field(alias="Scope")
|
|
50
|
-
lenses:
|
|
95
|
+
lenses: List[str] = Field(
|
|
96
|
+
default=["AWS Well-Architected Framework"], alias="Lenses"
|
|
97
|
+
)
|
|
51
98
|
tags: Optional[Dict[str, Any]] = Field(default={}, alias="Tags")
|
|
52
99
|
region_code: str = Field(alias="RegionCode")
|
|
53
100
|
environment: str = Field(alias="Environment")
|
|
@@ -63,7 +110,38 @@ class CreateAssessmentInput(BaseModel):
|
|
|
63
110
|
|
|
64
111
|
|
|
65
112
|
class GetAssessmentOutput(BaseModel):
|
|
66
|
-
"""Output of get_assessment operation.
|
|
113
|
+
"""Output of get_assessment operation.
|
|
114
|
+
|
|
115
|
+
Attributes:
|
|
116
|
+
id: Assessment identifier
|
|
117
|
+
name: Name of the assessment
|
|
118
|
+
description: Description of the assessment
|
|
119
|
+
status: Current status of the assessment
|
|
120
|
+
assessment_arn: AWS ARN of the assessment
|
|
121
|
+
created_at: Timestamp when the assessment was created
|
|
122
|
+
updated_at: Timestamp when the assessment was last updated
|
|
123
|
+
answered_questions: Number of questions that have been answered
|
|
124
|
+
total_questions: Total number of questions in the assessment
|
|
125
|
+
lenses: List of lens identifiers used in the assessment
|
|
126
|
+
owner: Owner of the assessment
|
|
127
|
+
diagram_url: URL to architecture diagram (if provided)
|
|
128
|
+
environment: Environment type
|
|
129
|
+
improvement_status: Status of improvement plan
|
|
130
|
+
in_sync: Synchronization status indicator
|
|
131
|
+
industry: Industry specification
|
|
132
|
+
industry_type: Industry type classification
|
|
133
|
+
region_code: AWS region code
|
|
134
|
+
scope: Scope configuration
|
|
135
|
+
risk_counts: Dictionary of risk counts by severity
|
|
136
|
+
lens_alias: Primary lens alias
|
|
137
|
+
lens_arn: AWS ARN of the lens
|
|
138
|
+
lens_version: Version of the lens
|
|
139
|
+
lens_name: Name of the lens
|
|
140
|
+
lens_status: Status of the lens
|
|
141
|
+
aws_updated_at: Timestamp of the last AWS update
|
|
142
|
+
last_run_at: Timestamp of the last execution run (if completed)
|
|
143
|
+
execution_id: ID of the last execution run (if completed)
|
|
144
|
+
"""
|
|
67
145
|
|
|
68
146
|
id: str = Field(alias="AssessmentId")
|
|
69
147
|
name: str = Field(alias="AssessmentName")
|
|
@@ -93,6 +171,12 @@ class GetAssessmentOutput(BaseModel):
|
|
|
93
171
|
aws_updated_at: str = Field(
|
|
94
172
|
alias="AWSUpdatedAt"
|
|
95
173
|
) # Changed to str to handle timezone format
|
|
174
|
+
last_run_at: Optional[datetime] = Field(
|
|
175
|
+
None, alias="LastRunAt"
|
|
176
|
+
) # Optional since it will be present only when the execution run is completed for the assessment
|
|
177
|
+
execution_id: Optional[str] = Field(
|
|
178
|
+
None, alias="ExecutionId"
|
|
179
|
+
) # Optional since it will be present only when the execution run is completed for the assessment
|
|
96
180
|
|
|
97
181
|
# Allow extra fields
|
|
98
182
|
model_config = ConfigDict(extra="allow")
|
|
@@ -101,7 +185,12 @@ class GetAssessmentOutput(BaseModel):
|
|
|
101
185
|
class CreateAssessmentOutput(BaseModel):
|
|
102
186
|
"""Output of create_assessment operation.
|
|
103
187
|
|
|
104
|
-
|
|
188
|
+
Note:
|
|
189
|
+
According to the OpenAPI specification, this only returns the AssessmentId and AssessmentArn.
|
|
190
|
+
|
|
191
|
+
Attributes:
|
|
192
|
+
id: Newly created assessment identifier
|
|
193
|
+
assessment_arn: AWS ARN of the newly created assessment
|
|
105
194
|
"""
|
|
106
195
|
|
|
107
196
|
# Fields from the OpenAPI spec
|
|
@@ -113,7 +202,21 @@ class CreateAssessmentOutput(BaseModel):
|
|
|
113
202
|
|
|
114
203
|
|
|
115
204
|
class Question(BaseModel):
|
|
116
|
-
"""Details of an assessment question.
|
|
205
|
+
"""Details of an assessment question.
|
|
206
|
+
|
|
207
|
+
Attributes:
|
|
208
|
+
id: Question identifier
|
|
209
|
+
title: Title of the question
|
|
210
|
+
pillar_id: ID of the pillar this question belongs to
|
|
211
|
+
risk: Current risk level (HIGH, MEDIUM, LOW, or UNANSWERED)
|
|
212
|
+
reason: Reason for the current answer (if applicable)
|
|
213
|
+
description: Detailed description of the question
|
|
214
|
+
pillar_name: Name of the pillar this question belongs to
|
|
215
|
+
is_answered: Whether the question has been answered
|
|
216
|
+
choices: Available choices for the question
|
|
217
|
+
selected_choices: Currently selected choice IDs
|
|
218
|
+
notes: Additional notes for the answer
|
|
219
|
+
"""
|
|
117
220
|
|
|
118
221
|
id: str = Field(alias="QuestionId")
|
|
119
222
|
title: str = Field(alias="QuestionTitle")
|
|
@@ -135,7 +238,15 @@ class Question(BaseModel):
|
|
|
135
238
|
|
|
136
239
|
|
|
137
240
|
class ListQuestionsOutput(BaseModel):
|
|
138
|
-
"""Output of list_questions operation.
|
|
241
|
+
"""Output of list_questions operation.
|
|
242
|
+
|
|
243
|
+
Attributes:
|
|
244
|
+
questions: List of question details
|
|
245
|
+
total_questions: Total number of questions
|
|
246
|
+
answered_questions: Number of questions that have been answered
|
|
247
|
+
pillar_id: ID of the pillar (if filtered by pillar)
|
|
248
|
+
pillar_name: Name of the pillar (if filtered by pillar)
|
|
249
|
+
"""
|
|
139
250
|
|
|
140
251
|
questions: List[Question] = Field(alias="Questions")
|
|
141
252
|
|
|
@@ -171,7 +282,25 @@ class ListQuestionsOutput(BaseModel):
|
|
|
171
282
|
|
|
172
283
|
|
|
173
284
|
class GetQuestionOutput(BaseModel):
|
|
174
|
-
"""Output of get_question operation.
|
|
285
|
+
"""Output of get_question operation.
|
|
286
|
+
|
|
287
|
+
Attributes:
|
|
288
|
+
title: Title of the question
|
|
289
|
+
description: Detailed description of the question
|
|
290
|
+
pillar_id: ID of the pillar this question belongs to
|
|
291
|
+
choices: Available choices for answering the question
|
|
292
|
+
is_applicable: Whether the question is applicable
|
|
293
|
+
risk: Current risk level (if answered)
|
|
294
|
+
reason: Reason for marking as not applicable
|
|
295
|
+
helpful_resource_url: URL to helpful resources
|
|
296
|
+
improvement_plan_url: URL to improvement plan
|
|
297
|
+
choice_answers: Currently selected choice answers
|
|
298
|
+
id: Question identifier
|
|
299
|
+
pillar_name: Name of the pillar
|
|
300
|
+
notes: Additional notes
|
|
301
|
+
is_answered: Whether the question has been answered
|
|
302
|
+
selected_choices: List of selected choice IDs
|
|
303
|
+
"""
|
|
175
304
|
|
|
176
305
|
# Fields directly from the API response
|
|
177
306
|
title: str = Field(alias="QuestionTitle")
|
|
@@ -228,7 +357,11 @@ class GetQuestionOutput(BaseModel):
|
|
|
228
357
|
|
|
229
358
|
|
|
230
359
|
class ChoiceStatus(BaseModel):
|
|
231
|
-
"""Status of a choice in an answer.
|
|
360
|
+
"""Status of a choice in an answer.
|
|
361
|
+
|
|
362
|
+
Attributes:
|
|
363
|
+
status: Status of the choice (SELECTED or UNSELECTED)
|
|
364
|
+
"""
|
|
232
365
|
|
|
233
366
|
status: str = Field("SELECTED", alias="Status")
|
|
234
367
|
|
|
@@ -236,7 +369,18 @@ class ChoiceStatus(BaseModel):
|
|
|
236
369
|
|
|
237
370
|
|
|
238
371
|
class AnswerQuestionInput(BaseModel):
|
|
239
|
-
"""Input for answering a question.
|
|
372
|
+
"""Input for answering a question.
|
|
373
|
+
|
|
374
|
+
Attributes:
|
|
375
|
+
lens_alias: Alias of the lens (defaults to 'wellarchitected')
|
|
376
|
+
choice_updates: Dictionary mapping choice IDs to their status
|
|
377
|
+
notes: Additional notes about the answer
|
|
378
|
+
is_applicable: Whether the question is applicable (defaults to True)
|
|
379
|
+
reason: Reason if not applicable (OUT_OF_SCOPE, BUSINESS_PRIORITIES,
|
|
380
|
+
ARCHITECTURE_CONSTRAINTS, OTHER, or NONE)
|
|
381
|
+
selected_choices: List of selected choice IDs (deprecated, use choice_updates)
|
|
382
|
+
risk: Risk level (deprecated, automatically calculated)
|
|
383
|
+
"""
|
|
240
384
|
|
|
241
385
|
lens_alias: str = Field("wellarchitected", alias="LensAlias")
|
|
242
386
|
choice_updates: Dict[str, ChoiceStatus] = Field(
|
|
@@ -275,7 +419,13 @@ class AnswerQuestionInput(BaseModel):
|
|
|
275
419
|
class AnswerQuestionOutput(BaseModel):
|
|
276
420
|
"""Output of answer_question operation.
|
|
277
421
|
|
|
278
|
-
|
|
422
|
+
Note:
|
|
423
|
+
The API returns a success message with status and message fields.
|
|
424
|
+
|
|
425
|
+
Attributes:
|
|
426
|
+
status: Status of the operation (e.g., 'success')
|
|
427
|
+
message: Descriptive message about the operation result
|
|
428
|
+
id: Question identifier (added for convenience)
|
|
279
429
|
"""
|
|
280
430
|
|
|
281
431
|
# Fields directly from the API response
|
|
@@ -289,7 +439,29 @@ class AnswerQuestionOutput(BaseModel):
|
|
|
289
439
|
|
|
290
440
|
|
|
291
441
|
class Finding(BaseModel):
|
|
292
|
-
"""Details of a finding.
|
|
442
|
+
"""Details of a finding.
|
|
443
|
+
|
|
444
|
+
Attributes:
|
|
445
|
+
finding_id: Unique identifier for the finding
|
|
446
|
+
resource_id: ID of the affected resource
|
|
447
|
+
resource_type: Type of the affected resource (e.g., AWS::EC2::Instance)
|
|
448
|
+
account_number: AWS account number where the resource exists
|
|
449
|
+
region_code: AWS region where the resource exists
|
|
450
|
+
check_id: ID of the check that generated this finding
|
|
451
|
+
recommendation: Recommended action to address the finding
|
|
452
|
+
remediation: Whether remediation is available
|
|
453
|
+
created_at: Timestamp when the finding was created
|
|
454
|
+
question_id: Related assessment question ID
|
|
455
|
+
question: Related assessment question text
|
|
456
|
+
pillar_id: ID of the related pillar
|
|
457
|
+
severity: Severity level (HIGH, MEDIUM, LOW)
|
|
458
|
+
status: Current status of the finding (OPEN, RESOLVED, SUPPRESSED)
|
|
459
|
+
title: Title of the finding
|
|
460
|
+
description: Detailed description of the finding
|
|
461
|
+
best_practice_id: ID of the related best practice
|
|
462
|
+
best_practice: Description of the best practice
|
|
463
|
+
best_practice_risk: Risk level associated with not following the best practice
|
|
464
|
+
"""
|
|
293
465
|
|
|
294
466
|
finding_id: str = Field(alias="FindingId")
|
|
295
467
|
resource_id: str = Field(alias="ResourceId")
|
|
@@ -315,7 +487,12 @@ class Finding(BaseModel):
|
|
|
315
487
|
|
|
316
488
|
|
|
317
489
|
class ListFindingsOutput(BaseModel):
|
|
318
|
-
"""Output of list_findings operation.
|
|
490
|
+
"""Output of list_findings operation.
|
|
491
|
+
|
|
492
|
+
Attributes:
|
|
493
|
+
records: List of finding details
|
|
494
|
+
next_page_token: Token for fetching the next page of results
|
|
495
|
+
"""
|
|
319
496
|
|
|
320
497
|
records: List[Finding] = Field(alias="Records", default=[])
|
|
321
498
|
next_page_token: Optional[str] = Field(None, alias="NextPageToken")
|