day2 0.2.1__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.1 → day2-0.2.2}/PKG-INFO +3 -3
- {day2-0.2.1 → day2-0.2.2}/README.md +2 -2
- {day2-0.2.1 → day2-0.2.2}/day2/__init__.py +18 -1
- {day2-0.2.1 → day2-0.2.2}/day2/client/config.py +13 -1
- {day2-0.2.1 → day2-0.2.2}/day2/models/__init__.py +2 -0
- {day2-0.2.1 → day2-0.2.2}/day2/models/assessment.py +196 -19
- {day2-0.2.1 → day2-0.2.2}/day2/models/cost.py +30 -3
- {day2-0.2.1 → day2-0.2.2}/day2/models/tenant.py +44 -5
- {day2-0.2.1 → day2-0.2.2}/day2/resources/__init__.py +2 -1
- {day2-0.2.1 → day2-0.2.2}/day2/resources/assessment.py +8 -9
- {day2-0.2.1 → day2-0.2.2}/day2/resources/cost.py +1 -1
- {day2-0.2.1 → day2-0.2.2}/day2/resources/tenant.py +3 -3
- {day2-0.2.1 → day2-0.2.2}/day2/session.py +26 -5
- {day2-0.2.1 → day2-0.2.2}/day2.egg-info/PKG-INFO +3 -3
- {day2-0.2.1 → day2-0.2.2}/day2_cli/commands/assessment.py +25 -2
- {day2-0.2.1 → day2-0.2.2}/pyproject.toml +7 -1
- {day2-0.2.1 → day2-0.2.2}/tests/test_models_assessment.py +12 -11
- {day2-0.2.1 → day2-0.2.2}/tests/test_resources_assessment.py +2 -0
- {day2-0.2.1 → day2-0.2.2}/LICENSE +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2/_version.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2/auth/__init__.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2/auth/credentials.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2/client/__init__.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2/client/base.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2/exceptions.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2.egg-info/SOURCES.txt +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2.egg-info/dependency_links.txt +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2.egg-info/entry_points.txt +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2.egg-info/requires.txt +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2.egg-info/top_level.txt +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/__init__.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/cli.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/commands/__init__.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/commands/auth.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/commands/config.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/commands/cost.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/commands/tenant.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/utils/__init__.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/utils/config.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/utils/formatters.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/day2_cli/utils/output_formatter.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/setup.cfg +0 -0
- {day2-0.2.1 → day2-0.2.2}/tests/test_cli_auth.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/tests/test_cli_config.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/tests/test_client_base.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/tests/test_config.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/tests/test_models_cost.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/tests/test_models_tenant.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/tests/test_resources_cost.py +0 -0
- {day2-0.2.1 → day2-0.2.2}/tests/test_resources_tenant.py +0 -0
- {day2-0.2.1 → 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")
|
|
@@ -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")
|
|
@@ -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
|
+
]
|
|
@@ -8,7 +8,19 @@ from typing import Any, Dict, Optional
|
|
|
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
26
|
base_url: str = "https://api.montycloud.com/day2/api"
|
|
@@ -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")
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from typing import Any, Dict, Optional
|
|
3
3
|
|
|
4
|
-
from pydantic import BaseModel, ConfigDict, Field,
|
|
4
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
# Utility function to convert camelCase or PascalCase to snake_case
|
|
@@ -10,7 +10,34 @@ def to_snake_case(name: str) -> str:
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class GetCostByChargeTypeOutput(BaseModel):
|
|
13
|
-
"""Details of cost breakdown by charge type.
|
|
13
|
+
"""Details of cost breakdown by charge type.
|
|
14
|
+
|
|
15
|
+
This model provides a comprehensive breakdown of costs by various charge types.
|
|
16
|
+
It supports dynamic fields, automatically converting any additional charge types
|
|
17
|
+
from the API response to snake_case attributes.
|
|
18
|
+
|
|
19
|
+
Attributes:
|
|
20
|
+
total_cost: Total cost for the period
|
|
21
|
+
usage: Standard usage charges
|
|
22
|
+
bundled_discount: Bundled discount amount
|
|
23
|
+
credit: Credits applied
|
|
24
|
+
discount: General discounts applied
|
|
25
|
+
discounted_usage: Usage charges after discounts
|
|
26
|
+
fee: General fees
|
|
27
|
+
refund: Refund amounts
|
|
28
|
+
ri_fee: Reserved Instance fees
|
|
29
|
+
tax: Tax charges
|
|
30
|
+
savings_plan_upfront_fee: Upfront fee for Savings Plans
|
|
31
|
+
savings_plan_recurring_fee: Recurring fee for Savings Plans
|
|
32
|
+
savings_plan_covered_usage: Usage covered by Savings Plans
|
|
33
|
+
savings_plan_negation: Savings Plan negation amounts
|
|
34
|
+
spp_discount: SPP (Service Provider Program) discounts
|
|
35
|
+
distributor_discount: Distributor discounts
|
|
36
|
+
|
|
37
|
+
Note:
|
|
38
|
+
Additional charge types returned by the API are automatically added as
|
|
39
|
+
attributes with their names converted to snake_case format.
|
|
40
|
+
"""
|
|
14
41
|
|
|
15
42
|
total_cost: Optional[float] = Field(None, alias="TotalCost")
|
|
16
43
|
usage: Optional[float] = Field(None, alias="Usage")
|
|
@@ -38,7 +65,7 @@ class GetCostByChargeTypeOutput(BaseModel):
|
|
|
38
65
|
# Allow extra fields
|
|
39
66
|
model_config = ConfigDict(extra="allow")
|
|
40
67
|
|
|
41
|
-
@
|
|
68
|
+
@model_validator(mode="before")
|
|
42
69
|
@classmethod
|
|
43
70
|
def handle_dynamic_extra_fields(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
|
44
71
|
"""Handle dynamic extra fields by converting them to snake_case."""
|
|
@@ -7,7 +7,22 @@ from pydantic import BaseModel, ConfigDict, Field
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class TenantDetails(BaseModel):
|
|
10
|
-
"""Details of a tenant.
|
|
10
|
+
"""Details of a tenant.
|
|
11
|
+
|
|
12
|
+
Attributes:
|
|
13
|
+
id: Unique identifier of the tenant
|
|
14
|
+
name: Name of the tenant
|
|
15
|
+
description: Description of the tenant
|
|
16
|
+
parent_tenant_id: ID of the parent tenant if this is a sub-tenant
|
|
17
|
+
created_at: Timestamp when the tenant was created
|
|
18
|
+
created_by: User who created the tenant
|
|
19
|
+
modified_at: Timestamp of last modification
|
|
20
|
+
modified_by: User who last modified the tenant
|
|
21
|
+
owner: Owner of the tenant
|
|
22
|
+
document_url: URL to the tenant's documentation
|
|
23
|
+
feature: Feature set associated with the tenant
|
|
24
|
+
category_id: Category identifier for the tenant
|
|
25
|
+
"""
|
|
11
26
|
|
|
12
27
|
id: Optional[str] = Field(None, alias="ID")
|
|
13
28
|
name: str = Field(alias="Name")
|
|
@@ -27,7 +42,12 @@ class TenantDetails(BaseModel):
|
|
|
27
42
|
|
|
28
43
|
|
|
29
44
|
class ListTenantsOutput(BaseModel):
|
|
30
|
-
"""Output of list_tenants operation.
|
|
45
|
+
"""Output of list_tenants operation.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
tenants: List of tenant details
|
|
49
|
+
next_page_token: Token for fetching the next page of results
|
|
50
|
+
"""
|
|
31
51
|
|
|
32
52
|
tenants: List[TenantDetails] = Field(alias="Tenants", default=[])
|
|
33
53
|
next_page_token: Optional[str] = Field(None, alias="NextPageToken")
|
|
@@ -37,11 +57,24 @@ class ListTenantsOutput(BaseModel):
|
|
|
37
57
|
|
|
38
58
|
|
|
39
59
|
class GetTenantOutput(TenantDetails):
|
|
40
|
-
"""Output of get_tenant operation.
|
|
60
|
+
"""Output of get_tenant operation.
|
|
61
|
+
|
|
62
|
+
This class inherits all attributes from TenantDetails without modification.
|
|
63
|
+
See TenantDetails for the complete list of attributes.
|
|
64
|
+
"""
|
|
41
65
|
|
|
42
66
|
|
|
43
67
|
class Account(BaseModel):
|
|
44
|
-
"""Details of an account.
|
|
68
|
+
"""Details of an account.
|
|
69
|
+
|
|
70
|
+
Attributes:
|
|
71
|
+
number: AWS account number
|
|
72
|
+
name: Account name
|
|
73
|
+
status: Current status of the account
|
|
74
|
+
type: Type of the account
|
|
75
|
+
permission_model: Permission model used for the account
|
|
76
|
+
onboarded_date: Date when the account was onboarded
|
|
77
|
+
"""
|
|
45
78
|
|
|
46
79
|
number: str = Field(alias="Number")
|
|
47
80
|
name: str = Field(alias="Name")
|
|
@@ -55,7 +88,13 @@ class Account(BaseModel):
|
|
|
55
88
|
|
|
56
89
|
|
|
57
90
|
class ListAccountsOutput(BaseModel):
|
|
58
|
-
"""Output of list_accounts operation.
|
|
91
|
+
"""Output of list_accounts operation.
|
|
92
|
+
|
|
93
|
+
Attributes:
|
|
94
|
+
accounts: List of account details
|
|
95
|
+
has_more: Whether there are more pages of results
|
|
96
|
+
page_number: Current page number
|
|
97
|
+
"""
|
|
59
98
|
|
|
60
99
|
accounts: List[Account] = Field(alias="Accounts", default=[])
|
|
61
100
|
has_more: bool = Field(alias="HasMore", default=False)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Resource implementations for the MontyCloud DAY2 SDK."""
|
|
2
2
|
|
|
3
3
|
from day2.resources.assessment import AssessmentClient
|
|
4
|
+
from day2.resources.cost import CostClient
|
|
4
5
|
from day2.resources.tenant import TenantClient
|
|
5
6
|
|
|
6
|
-
__all__ = ["TenantClient", "AssessmentClient"]
|
|
7
|
+
__all__ = ["TenantClient", "AssessmentClient", "CostClient"]
|
|
@@ -43,8 +43,7 @@ class AssessmentClient(BaseClient):
|
|
|
43
43
|
status: Status to filter assessments by. Must be one of ["PENDING", "COMPLETED"].
|
|
44
44
|
keyword: Optional keyword to filter assessments by name or description.
|
|
45
45
|
page_size: The number of assessments per page (default: 10).
|
|
46
|
-
page_token: Token for pagination.
|
|
47
|
-
page_number: The page number to retrieve (deprecated, use page_token instead).
|
|
46
|
+
page_token: Token for pagination.
|
|
48
47
|
|
|
49
48
|
Returns:
|
|
50
49
|
ListAssessmentsOutput: Object containing list of assessments and pagination info.
|
|
@@ -55,7 +54,7 @@ class AssessmentClient(BaseClient):
|
|
|
55
54
|
AuthenticationError: If authentication fails.
|
|
56
55
|
|
|
57
56
|
Examples:
|
|
58
|
-
>>> session = Session(api_key="your-api-key")
|
|
57
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
59
58
|
>>> client = session.assessment
|
|
60
59
|
>>> response = client.list_assessments(tenant_id="tenant-123", status="PENDING", page_size=10)
|
|
61
60
|
>>> for assessment in response.assessments:
|
|
@@ -98,7 +97,7 @@ class AssessmentClient(BaseClient):
|
|
|
98
97
|
AuthenticationError: If authentication fails.
|
|
99
98
|
|
|
100
99
|
Examples:
|
|
101
|
-
>>> session = Session(api_key="your-api-key")
|
|
100
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
102
101
|
>>> client = session.assessment
|
|
103
102
|
>>> response = client.get_assessment("tenant-123", "assessment-123")
|
|
104
103
|
>>> print(f"Assessment name: {response.name}")
|
|
@@ -126,7 +125,7 @@ class AssessmentClient(BaseClient):
|
|
|
126
125
|
AuthenticationError: If authentication fails.
|
|
127
126
|
|
|
128
127
|
Examples:
|
|
129
|
-
>>> session = Session(api_key="your-api-key")
|
|
128
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
130
129
|
>>> client = session.assessment
|
|
131
130
|
>>> data = CreateAssessmentInput(
|
|
132
131
|
... name="My Assessment",
|
|
@@ -195,7 +194,7 @@ class AssessmentClient(BaseClient):
|
|
|
195
194
|
AuthenticationError: If authentication fails.
|
|
196
195
|
|
|
197
196
|
Examples:
|
|
198
|
-
>>> session = Session(api_key="your-api-key")
|
|
197
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
199
198
|
>>> client = session.assessment
|
|
200
199
|
>>> response = client.list_questions("tenant-123", "assessment-123", "pillar-123")
|
|
201
200
|
>>> for question in response.questions:
|
|
@@ -230,7 +229,7 @@ class AssessmentClient(BaseClient):
|
|
|
230
229
|
AuthenticationError: If authentication fails.
|
|
231
230
|
|
|
232
231
|
Examples:
|
|
233
|
-
>>> session = Session(api_key="your-api-key")
|
|
232
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
234
233
|
>>> client = session.assessment
|
|
235
234
|
>>> response = client.get_question("tenant-123", "assessment-123", "question-123")
|
|
236
235
|
>>> print(f"Question: {response.title}")
|
|
@@ -284,7 +283,7 @@ class AssessmentClient(BaseClient):
|
|
|
284
283
|
AuthenticationError: If authentication fails.
|
|
285
284
|
|
|
286
285
|
Examples:
|
|
287
|
-
>>> session = Session(api_key="your-api-key")
|
|
286
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
288
287
|
>>> client = session.assessment
|
|
289
288
|
>>> data = AnswerQuestionInput(
|
|
290
289
|
... Reason="ARCHITECTURE_CONSTRAINTS",
|
|
@@ -357,7 +356,7 @@ class AssessmentClient(BaseClient):
|
|
|
357
356
|
AuthenticationError: If authentication fails.
|
|
358
357
|
|
|
359
358
|
Examples:
|
|
360
|
-
>>> session = Session(api_key="your-api-key")
|
|
359
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
361
360
|
>>> client = session.assessment
|
|
362
361
|
>>> response = client.list_findings(
|
|
363
362
|
... tenant_id="tenant-123",
|
|
@@ -40,7 +40,7 @@ class CostClient(BaseClient):
|
|
|
40
40
|
AuthenticationError: If authentication fails.
|
|
41
41
|
|
|
42
42
|
Examples:
|
|
43
|
-
>>> session = Session(api_key="your-api-key")
|
|
43
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
44
44
|
>>> client = session.cost
|
|
45
45
|
>>> response = client.get_cost_by_charge_type(
|
|
46
46
|
... tenant_id="tenant-123",
|
|
@@ -36,7 +36,7 @@ class TenantClient(BaseClient):
|
|
|
36
36
|
AuthenticationError: If authentication fails.
|
|
37
37
|
|
|
38
38
|
Examples:
|
|
39
|
-
>>> session = Session(api_key="your-api-key")
|
|
39
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
40
40
|
>>> client = session.tenant
|
|
41
41
|
>>> response = client.list_tenants(page_size=10)
|
|
42
42
|
>>> for tenant in response.tenants:
|
|
@@ -71,7 +71,7 @@ class TenantClient(BaseClient):
|
|
|
71
71
|
AuthenticationError: If authentication fails.
|
|
72
72
|
|
|
73
73
|
Examples:
|
|
74
|
-
>>> session = Session(api_key="your-api-key")
|
|
74
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
75
75
|
>>> client = session.tenant
|
|
76
76
|
>>> response = client.get_tenant("tenant-123")
|
|
77
77
|
>>> print(f"Tenant name: {response.name}")
|
|
@@ -100,7 +100,7 @@ class TenantClient(BaseClient):
|
|
|
100
100
|
AuthenticationError: If authentication fails.
|
|
101
101
|
|
|
102
102
|
Examples:
|
|
103
|
-
>>> session = Session(api_key="your-api-key")
|
|
103
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
104
104
|
>>> client = session.tenant
|
|
105
105
|
>>> response = client.list_accounts("tenant-123", page_size=10)
|
|
106
106
|
>>> for account in response.accounts:
|
|
@@ -20,6 +20,22 @@ class Session:
|
|
|
20
20
|
"""Session for interacting with the MontyCloud API.
|
|
21
21
|
|
|
22
22
|
The Session manages authentication credentials, tenant context, and client creation.
|
|
23
|
+
It provides access to all DAY2 API resources through dedicated client properties.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
credentials: Authentication credentials for API access.
|
|
27
|
+
tenant_id: Current tenant ID context for API operations.
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
Create a session with API credentials:
|
|
31
|
+
|
|
32
|
+
>>> session = Session(api_key="your-api-key", api_secret_key="your-secret-key")
|
|
33
|
+
>>> tenants = session.tenant.list_tenants()
|
|
34
|
+
|
|
35
|
+
Create a session with custom configuration:
|
|
36
|
+
|
|
37
|
+
>>> config = Config(timeout=60, max_retries=5)
|
|
38
|
+
>>> session = Session(api_key="key", api_secret_key="secret", config=config)
|
|
23
39
|
"""
|
|
24
40
|
|
|
25
41
|
def __init__(
|
|
@@ -33,11 +49,16 @@ class Session:
|
|
|
33
49
|
"""Initialize a new session.
|
|
34
50
|
|
|
35
51
|
Args:
|
|
36
|
-
api_key: API key for authentication.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
52
|
+
api_key: API key for authentication. If not provided, will attempt to load
|
|
53
|
+
from environment variables or configuration file.
|
|
54
|
+
api_secret_key: API secret key for authentication. Required if api_key is provided.
|
|
55
|
+
credentials: Pre-configured Credentials object. Takes precedence over api_key/api_secret_key.
|
|
56
|
+
tenant_id: Default tenant ID for API operations. Can be changed later with set_tenant().
|
|
57
|
+
config: Configuration object with API settings. If not provided, loads from file or uses defaults.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValueError: If api_key is provided without api_secret_key.
|
|
61
|
+
AuthenticationError: If no valid credentials are found.
|
|
41
62
|
"""
|
|
42
63
|
# Initialize credentials
|
|
43
64
|
if credentials:
|
|
@@ -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")
|
|
@@ -194,6 +194,11 @@ def get_assessment(
|
|
|
194
194
|
if result.updated_at
|
|
195
195
|
else "N/A"
|
|
196
196
|
)
|
|
197
|
+
last_run_at = (
|
|
198
|
+
result.last_run_at.strftime("%Y-%m-%d %H:%M:%S")
|
|
199
|
+
if result.last_run_at
|
|
200
|
+
else "N/A"
|
|
201
|
+
)
|
|
197
202
|
|
|
198
203
|
# Convert assessment object to dictionary for the formatter
|
|
199
204
|
assessment_data = {
|
|
@@ -201,14 +206,30 @@ def get_assessment(
|
|
|
201
206
|
"Name": result.name,
|
|
202
207
|
"Description": result.description or "N/A",
|
|
203
208
|
"Status": result.status,
|
|
204
|
-
"
|
|
209
|
+
"Assessment ARN": result.assessment_arn,
|
|
210
|
+
"Owner": result.owner,
|
|
211
|
+
"Diagram URL": result.diagram_url or "N/A",
|
|
205
212
|
"Environment": result.environment or "N/A",
|
|
213
|
+
"Improvement Status": result.improvement_status,
|
|
214
|
+
"In Sync": result.in_sync,
|
|
215
|
+
"Industry": result.industry or "N/A",
|
|
216
|
+
"Industry Type": result.industry_type or "N/A",
|
|
206
217
|
"Region": result.region_code or "N/A",
|
|
218
|
+
"Scope": result.scope or "N/A",
|
|
219
|
+
"Risk Counts": result.risk_counts,
|
|
207
220
|
"Total Questions": result.total_questions,
|
|
208
221
|
"Answered Questions": result.answered_questions,
|
|
209
222
|
"Lenses": ", ".join(result.lenses) if result.lenses else "N/A",
|
|
223
|
+
"Lens Alias": result.lens_alias,
|
|
224
|
+
"Lens ARN": result.lens_arn,
|
|
225
|
+
"Lens Version": result.lens_version,
|
|
226
|
+
"Lens Name": result.lens_name,
|
|
227
|
+
"Lens Status": result.lens_status,
|
|
228
|
+
"AWS Updated At": result.aws_updated_at,
|
|
210
229
|
"Created At": created_at,
|
|
211
230
|
"Updated At": updated_at,
|
|
231
|
+
"Last Run At": last_run_at,
|
|
232
|
+
"Execution ID": result.execution_id or "N/A",
|
|
212
233
|
}
|
|
213
234
|
|
|
214
235
|
# Format and output the results
|
|
@@ -697,7 +718,9 @@ def answer_question(
|
|
|
697
718
|
if notes:
|
|
698
719
|
console.print(f"[bold]Notes:[/bold] {notes}")
|
|
699
720
|
if not applicable:
|
|
700
|
-
console.print(
|
|
721
|
+
console.print(
|
|
722
|
+
"[bold]Applicability:[/bold] Not applicable to this assessment"
|
|
723
|
+
)
|
|
701
724
|
|
|
702
725
|
console.print(
|
|
703
726
|
"\n[dim]To view the updated question details, use the 'question' command.[/dim]"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "day2"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.2"
|
|
4
4
|
description = "MontyCloud DAY2 SDK for Python"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.11"
|
|
@@ -50,6 +50,12 @@ cli = [
|
|
|
50
50
|
requires = ["setuptools>=61.0.0", "wheel"]
|
|
51
51
|
build-backend = "setuptools.build_meta"
|
|
52
52
|
|
|
53
|
+
[tool.uv]
|
|
54
|
+
# Note: This project requires uv to be installed globally
|
|
55
|
+
# See DEVELOPER.md for installation instructions
|
|
56
|
+
# Minimum version to ensure lock file compatibility
|
|
57
|
+
required-version = ">=0.7.0"
|
|
58
|
+
|
|
53
59
|
[tool.mypy]
|
|
54
60
|
python_version = "3.11"
|
|
55
61
|
disallow_untyped_defs = true
|
|
@@ -27,7 +27,7 @@ def test_assessment_parse():
|
|
|
27
27
|
"Description": "Test Description",
|
|
28
28
|
"Status": "COMPLETED",
|
|
29
29
|
"AssessmentArn": "arn:aws:wellarchitected:us-east-1:123456789012:workload/assessment-123",
|
|
30
|
-
"Lenses": ["
|
|
30
|
+
"Lenses": ["AWS Well-Architected Framework"],
|
|
31
31
|
"TotalQuestions": 50,
|
|
32
32
|
"AnsweredQuestions": 25,
|
|
33
33
|
}
|
|
@@ -42,7 +42,7 @@ def test_assessment_parse():
|
|
|
42
42
|
assessment.assessment_arn
|
|
43
43
|
== "arn:aws:wellarchitected:us-east-1:123456789012:workload/assessment-123"
|
|
44
44
|
)
|
|
45
|
-
assert assessment.lenses == ["
|
|
45
|
+
assert assessment.lenses == ["AWS Well-Architected Framework"]
|
|
46
46
|
assert assessment.total_questions == 50
|
|
47
47
|
assert assessment.answered_questions == 25
|
|
48
48
|
|
|
@@ -57,21 +57,18 @@ def test_list_assessments_output_parse():
|
|
|
57
57
|
"Description": "Test Description",
|
|
58
58
|
"Status": "COMPLETED",
|
|
59
59
|
"AssessmentArn": "arn:aws:wellarchitected:us-east-1:123456789012:workload/assessment-123",
|
|
60
|
-
"Lenses": ["
|
|
60
|
+
"Lenses": ["AWS Well-Architected Framework"],
|
|
61
61
|
"TotalQuestions": 50,
|
|
62
62
|
"AnsweredQuestions": 25,
|
|
63
63
|
}
|
|
64
64
|
],
|
|
65
|
-
"
|
|
66
|
-
"HasMore": False,
|
|
65
|
+
"NextPageToken": "next-page-token",
|
|
67
66
|
}
|
|
68
67
|
|
|
69
68
|
list_output = ListAssessmentsOutput.model_validate(data)
|
|
70
69
|
|
|
71
70
|
assert len(list_output.assessments) == 1
|
|
72
|
-
assert list_output.
|
|
73
|
-
assert list_output.has_more is False
|
|
74
|
-
|
|
71
|
+
assert list_output.next_page_token == "next-page-token"
|
|
75
72
|
assessment = list_output.assessments[0]
|
|
76
73
|
assert assessment.id == "assessment-123"
|
|
77
74
|
assert assessment.name == "Test Assessment"
|
|
@@ -91,7 +88,7 @@ def test_create_assessment_input_serialization():
|
|
|
91
88
|
{"AccountNumber": "111122223333", "Regions": ["us-east-1", "us-east-2"]}
|
|
92
89
|
],
|
|
93
90
|
},
|
|
94
|
-
Lenses=["
|
|
91
|
+
Lenses=["AWS Well-Architected Framework"],
|
|
95
92
|
RegionCode="us-east-1",
|
|
96
93
|
Environment="PRODUCTION",
|
|
97
94
|
)
|
|
@@ -105,7 +102,7 @@ def test_create_assessment_input_serialization():
|
|
|
105
102
|
assert len(data["Scope"]["Accounts"]) == 1
|
|
106
103
|
assert data["Scope"]["Accounts"][0]["AccountNumber"] == "111122223333"
|
|
107
104
|
assert "us-east-1" in data["Scope"]["Accounts"][0]["Regions"]
|
|
108
|
-
assert data["Lenses"] == ["
|
|
105
|
+
assert data["Lenses"] == ["AWS Well-Architected Framework"]
|
|
109
106
|
assert data["RegionCode"] == "us-east-1"
|
|
110
107
|
assert data["Environment"] == "PRODUCTION"
|
|
111
108
|
|
|
@@ -118,7 +115,7 @@ def test_create_assessment_output_parse():
|
|
|
118
115
|
"Description": "Test Description",
|
|
119
116
|
"Status": "PENDING",
|
|
120
117
|
"AssessmentArn": "arn:aws:wellarchitected:us-east-1:123456789012:workload/assessment-123",
|
|
121
|
-
"Lenses": ["
|
|
118
|
+
"Lenses": ["AWS Well-Architected Framework"],
|
|
122
119
|
"TotalQuestions": 50,
|
|
123
120
|
"AnsweredQuestions": 0,
|
|
124
121
|
}
|
|
@@ -167,6 +164,8 @@ def test_get_assessment_output_parse():
|
|
|
167
164
|
"LensName": "AWS Well-Architected Framework",
|
|
168
165
|
"LensStatus": "CURRENT",
|
|
169
166
|
"AWSUpdatedAt": "2025-04-09T11:54:33+05:30",
|
|
167
|
+
"LastRunAt": "2025-04-09T06:24:34",
|
|
168
|
+
"ExecutionId": "9575dde75568c808c46e525ed88d9978",
|
|
170
169
|
}
|
|
171
170
|
|
|
172
171
|
output = GetAssessmentOutput.model_validate(data)
|
|
@@ -202,6 +201,8 @@ def test_get_assessment_output_parse():
|
|
|
202
201
|
assert output.lens_name == "AWS Well-Architected Framework"
|
|
203
202
|
assert output.lens_status == "CURRENT"
|
|
204
203
|
assert output.aws_updated_at == "2025-04-09T11:54:33+05:30"
|
|
204
|
+
assert output.last_run_at == datetime.fromisoformat("2025-04-09T06:24:34")
|
|
205
|
+
assert output.execution_id == "9575dde75568c808c46e525ed88d9978"
|
|
205
206
|
|
|
206
207
|
|
|
207
208
|
def test_get_question_output_parse():
|
|
@@ -154,6 +154,8 @@ def test_get_assessment(mock_session):
|
|
|
154
154
|
"LensName": "AWS Well-Architected Framework",
|
|
155
155
|
"LensStatus": "CURRENT",
|
|
156
156
|
"AWSUpdatedAt": "2025-04-09T11:54:33+05:30",
|
|
157
|
+
"LastRunAt": "2025-04-09T11:54:33+05:30",
|
|
158
|
+
"ExecutionId": "9575dde75568c808c46e525ed88d9978",
|
|
157
159
|
}
|
|
158
160
|
|
|
159
161
|
# Set up the mock
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|