okdata-aws 2.2.0__tar.gz → 3.0.1__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.
Files changed (23) hide show
  1. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/PKG-INFO +1 -1
  2. okdata-aws-3.0.1/okdata/aws/status/model.py +98 -0
  3. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/status/wrapper.py +14 -9
  4. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/PKG-INFO +1 -1
  5. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/SOURCES.txt +1 -4
  6. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/requires.txt +0 -1
  7. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/setup.py +1 -2
  8. okdata-aws-2.2.0/okdata/aws/status/model.py +0 -72
  9. okdata-aws-2.2.0/tests/test_logging.py +0 -318
  10. okdata-aws-2.2.0/tests/test_model.py +0 -29
  11. okdata-aws-2.2.0/tests/test_status.py +0 -131
  12. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/README.md +0 -0
  13. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/__init__.py +0 -0
  14. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/__init__.py +0 -0
  15. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/logging.py +0 -0
  16. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/ssm.py +0 -0
  17. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/status/__init__.py +0 -0
  18. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata/aws/status/sdk.py +0 -0
  19. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/dependency_links.txt +0 -0
  20. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/namespace_packages.txt +0 -0
  21. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/okdata_aws.egg-info/top_level.txt +0 -0
  22. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/pyproject.toml +0 -0
  23. {okdata-aws-2.2.0 → okdata-aws-3.0.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: okdata-aws
3
- Version: 2.2.0
3
+ Version: 3.0.1
4
4
  Summary: Collection of helpers for working with AWS
5
5
  Home-page: https://github.com/oslokommune/okdata-aws
6
6
  Author: Oslo Origo
@@ -0,0 +1,98 @@
1
+ from dataclasses import asdict, dataclass, field
2
+ from datetime import datetime, timezone
3
+ from enum import Enum
4
+ from json import dumps, JSONEncoder
5
+ from typing import Dict, Optional, List
6
+
7
+
8
+ class TraceStatus(str, Enum):
9
+ STARTED = "STARTED"
10
+ CONTINUE = "CONTINUE"
11
+ FINISHED = "FINISHED"
12
+
13
+
14
+ class TraceEventStatus(str, Enum):
15
+ OK = "OK"
16
+ FAILED = "FAILED"
17
+
18
+
19
+ class StatusJSONEncoder(JSONEncoder):
20
+ def default(self, obj, *args, **kwargs):
21
+ if isinstance(obj, Exception):
22
+ return str(obj)
23
+ if isinstance(obj, datetime):
24
+ return obj.isoformat()
25
+ return super().default(obj)
26
+
27
+
28
+ class BaseModel:
29
+ def dict(self, exclude_none=False):
30
+ if exclude_none:
31
+ return asdict(
32
+ self,
33
+ dict_factory=lambda d: {k: v for (k, v) in d if v is not None},
34
+ )
35
+ return asdict(self)
36
+
37
+ def json(self, exclude_none=False, **kwargs):
38
+ return dumps(
39
+ self.dict(exclude_none=exclude_none),
40
+ cls=StatusJSONEncoder,
41
+ **kwargs,
42
+ )
43
+
44
+ @classmethod
45
+ def parse_obj(cls, obj):
46
+ return cls(**obj)
47
+
48
+
49
+ @dataclass
50
+ class StatusMeta(BaseModel):
51
+ function_name: Optional[str] = None
52
+ function_version: Optional[str] = None
53
+ function_stage: Optional[str] = None
54
+ function_api_id: Optional[str] = None
55
+ git_rev: Optional[str] = None
56
+ git_branch: Optional[str] = None
57
+
58
+
59
+ # TODO: Rework optional vs required and defaults when currents users are updated
60
+ # and if to be used as a basis for the status api. Both trace_id (for new traces)
61
+ # and trace_event_id are currently generated in status-api.
62
+ @dataclass
63
+ class StatusData(BaseModel):
64
+ trace_id: Optional[str] = None # TODO: Generate here as default?
65
+ # trace_event_id: UUID = None # = Field(default_factory=uuid4)
66
+ domain: str = "N/A" # TODO: Temporary default (required)
67
+ domain_id: Optional[str] = None
68
+ start_time: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
69
+ end_time: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
70
+ trace_status: TraceStatus = TraceStatus.CONTINUE
71
+ trace_event_status: TraceEventStatus = TraceEventStatus.OK
72
+ user: Optional[str] = None
73
+ component: str = "N/A" # TODO: Temporary default (required)
74
+ operation: Optional[str] = None
75
+ status_body: Optional[Dict] = None
76
+ meta: Optional[StatusMeta] = None
77
+ s3_path: Optional[str] = None
78
+ duration: Optional[int] = None
79
+ exception: Optional[str] = None
80
+ errors: Optional[List] = None
81
+
82
+ def __setattr__(self, name, value):
83
+ # Validate and ensure format of errors
84
+ if name == "errors" and value is not None:
85
+ if not isinstance(value, list):
86
+ raise TypeError("`errors` must be provided as a list.")
87
+
88
+ for error in value:
89
+ if not isinstance(error, dict):
90
+ raise TypeError(f"{error} is not a dict.")
91
+ if "message" not in error:
92
+ raise ValueError("Missing key 'message'.")
93
+ if not isinstance(error["message"], dict):
94
+ raise TypeError("error['message'] is not a dict.")
95
+ if "nb" not in error["message"]:
96
+ raise ValueError("Missing key 'nb' in error['message'].")
97
+
98
+ super().__setattr__(name, value)
@@ -5,7 +5,12 @@ from functools import wraps
5
5
 
6
6
  from requests.exceptions import HTTPError
7
7
 
8
- from .model import StatusData, TraceStatus, TraceEventStatus
8
+ from .model import (
9
+ StatusData,
10
+ StatusMeta,
11
+ TraceEventStatus,
12
+ TraceStatus,
13
+ )
9
14
  from .sdk import Status
10
15
 
11
16
  _status_logger = None
@@ -57,13 +62,13 @@ def _status_from_lambda_context(event, context):
57
62
  "trace_id": event.get("execution_name"),
58
63
  "user": authorizer.get("principalId"),
59
64
  "component": os.getenv("SERVICE_NAME"),
60
- "meta": {
61
- "function_name": getattr(context, "function_name", None),
62
- "function_version": getattr(context, "function_version", None),
63
- "function_stage": request_context.get("stage"),
64
- "function_api_id": request_context.get("apiId"),
65
- "git_rev": os.getenv("GIT_REV"),
66
- "git_branch": os.getenv("GIT_BRANCH"),
67
- },
65
+ "meta": StatusMeta(
66
+ function_name=getattr(context, "function_name", None),
67
+ function_version=getattr(context, "function_version", None),
68
+ function_stage=request_context.get("stage"),
69
+ function_api_id=request_context.get("apiId"),
70
+ git_rev=os.getenv("GIT_REV"),
71
+ git_branch=os.getenv("GIT_BRANCH"),
72
+ ),
68
73
  }
69
74
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: okdata-aws
3
- Version: 2.2.0
3
+ Version: 3.0.1
4
4
  Summary: Collection of helpers for working with AWS
5
5
  Home-page: https://github.com/oslokommune/okdata-aws
6
6
  Author: Oslo Origo
@@ -14,7 +14,4 @@ okdata_aws.egg-info/SOURCES.txt
14
14
  okdata_aws.egg-info/dependency_links.txt
15
15
  okdata_aws.egg-info/namespace_packages.txt
16
16
  okdata_aws.egg-info/requires.txt
17
- okdata_aws.egg-info/top_level.txt
18
- tests/test_logging.py
19
- tests/test_model.py
20
- tests/test_status.py
17
+ okdata_aws.egg-info/top_level.txt
@@ -1,6 +1,5 @@
1
1
  boto3
2
2
  okdata-sdk<4,>=3
3
- pydantic<2
4
3
  requests
5
4
  starlette<1.0.0,>=0.25.0
6
5
  structlog
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setuptools.setup(
7
7
  name="okdata-aws",
8
- version="2.2.0",
8
+ version="3.0.1",
9
9
  author="Oslo Origo",
10
10
  author_email="dataplattform@oslo.kommune.no",
11
11
  description="Collection of helpers for working with AWS",
@@ -30,7 +30,6 @@ setuptools.setup(
30
30
  install_requires=[
31
31
  "boto3",
32
32
  "okdata-sdk>=3,<4",
33
- "pydantic<2",
34
33
  "requests",
35
34
  "starlette>=0.25.0,<1.0.0",
36
35
  "structlog",
@@ -1,72 +0,0 @@
1
- from enum import Enum
2
- from typing import Dict, Optional, List
3
- from datetime import datetime, timezone
4
- from pydantic import BaseModel, Field, validator
5
-
6
-
7
- class TraceStatus(str, Enum):
8
- STARTED = "STARTED"
9
- CONTINUE = "CONTINUE"
10
- FINISHED = "FINISHED"
11
-
12
-
13
- class TraceEventStatus(str, Enum):
14
- OK = "OK"
15
- FAILED = "FAILED"
16
-
17
-
18
- class StatusMeta(BaseModel):
19
- function_name: Optional[str] = None
20
- function_version: Optional[str] = None
21
- function_stage: Optional[str] = None
22
- function_api_id: Optional[str] = None
23
- git_rev: Optional[str] = None
24
- git_branch: Optional[str] = None
25
-
26
-
27
- # TODO: Rework optional vs required and defaults when currents users are updated
28
- # and if to be used as a basis for the status api. Both trace_id (for new traces)
29
- # and trace_event_id are currently generated in status-api.
30
- class StatusData(BaseModel):
31
- trace_id: Optional[str] = None # TODO: Generate here as default?
32
- # trace_event_id: UUID = None # = Field(default_factory=uuid4)
33
- domain: str = "N/A" # TODO: Temporary default (required)
34
- domain_id: Optional[str] = None
35
- start_time: datetime = Field(
36
- default_factory=lambda: datetime.now(timezone.utc), str=datetime.isoformat
37
- )
38
- end_time: datetime = Field(
39
- default_factory=lambda: datetime.now(timezone.utc), str=datetime.isoformat
40
- )
41
- trace_status: TraceStatus = TraceStatus.CONTINUE
42
- trace_event_status: TraceEventStatus = TraceEventStatus.OK
43
- user: Optional[str] = None
44
- component: str = "N/A" # TODO: Temporary default (required)
45
- operation: Optional[str] = None
46
- status_body: Optional[Dict] = None
47
- meta: Optional[StatusMeta] = None
48
- s3_path: Optional[str] = None
49
- duration: Optional[int] = None
50
- exception: Optional[str] = None
51
- errors: Optional[List] = None
52
-
53
- class Config:
54
- validate_assignment = True
55
-
56
- @validator("exception", pre=True)
57
- def ensure_exception_data_is_string(cls, v):
58
- if isinstance(v, Exception):
59
- return str(v)
60
- return v
61
-
62
- @validator("errors", each_item=True)
63
- def ensure_format_of_errors(cls, v):
64
- if not isinstance(v, dict):
65
- raise TypeError(f"{v} is not a dict.")
66
- if "message" not in v:
67
- raise ValueError("Missing key 'message'.")
68
- if not isinstance(v["message"], dict):
69
- raise TypeError("error['message'] is not a dict.")
70
- if "nb" not in v["message"]:
71
- raise ValueError("Missing key 'nb' in error['message'].")
72
- return v
@@ -1,318 +0,0 @@
1
- import json
2
- import os
3
- from time import sleep
4
-
5
- from okdata.aws.logging import hide_suffix, log_duration, log_dynamodb, logging_wrapper
6
-
7
- empty_event = {}
8
- empty_context = None
9
-
10
-
11
- def empty_handler(event, context):
12
- return {}
13
-
14
-
15
- def ok_handler(event, context):
16
- return {"statusCode": 200, "body": "OK"}
17
-
18
-
19
- def user_error_handler(event, context):
20
- return {"statusCode": 400, "body": "Bad Request"}
21
-
22
-
23
- def server_error_handler(event, context):
24
- return {"statusCode": 500, "body": "Internal Server Error"}
25
-
26
-
27
- def throwing_handler(event, context):
28
- raise Exception("fail!")
29
-
30
-
31
- def timed_operation(raise_exception=False):
32
- sleep(0.01)
33
- if raise_exception:
34
- raise Exception("fail!")
35
- else:
36
- return 201
37
-
38
-
39
- def timing_handler(event, context):
40
- result = log_duration(timed_operation, "my_timer")
41
- return {"statusCode": result}
42
-
43
-
44
- def throwing_timing_handler(event, context):
45
- log_duration(lambda: timed_operation(raise_exception=True), "my_timer")
46
-
47
-
48
- def non_rest_handler(event, context):
49
- return None
50
-
51
-
52
- class RequestContext:
53
- def __init__(
54
- self, function_name, function_version, aws_request_id, memory_limit_in_mb
55
- ):
56
- self.function_name = function_name
57
- self.function_version = function_version
58
- self.aws_request_id = aws_request_id
59
- self.memory_limit_in_mb = memory_limit_in_mb
60
-
61
-
62
- def test_log_service_name_from_env(capsys):
63
- os.environ["SERVICE_NAME"] = "my_other_service"
64
-
65
- wrapper = logging_wrapper(empty_handler)
66
- wrapper(empty_event, empty_context)
67
-
68
- log = json.loads(capsys.readouterr().out)
69
-
70
- assert log["service_name"] == "my_other_service"
71
- assert log["handler_method"] == "empty_handler"
72
- assert log["function_name"] == ""
73
-
74
-
75
- def test_log_empty_event_and_context(capsys):
76
- decorator = logging_wrapper(service_name="my_service")
77
- wrapper = decorator(empty_handler)
78
- wrapper(empty_event, empty_context)
79
-
80
- log = json.loads(capsys.readouterr().out)
81
-
82
- assert log["service_name"] == "my_service"
83
- assert log["handler_method"] == "empty_handler"
84
- assert log["function_name"] == ""
85
-
86
-
87
- def test_log_none_headers(capsys):
88
- decorator = logging_wrapper(service_name="my_service")
89
- wrapper = decorator(empty_handler)
90
- wrapper({"headers": None}, empty_context)
91
-
92
-
93
- def test_legacy_wrapper(capsys):
94
- decorator = logging_wrapper("my_old_service")
95
- wrapper = decorator(empty_handler)
96
- wrapper(empty_event, empty_context)
97
-
98
- log = json.loads(capsys.readouterr().out)
99
-
100
- assert log["service_name"] == "my_old_service"
101
- assert log["handler_method"] == "empty_handler"
102
- assert log["function_name"] == ""
103
-
104
-
105
- def test_handler_name(capsys):
106
- wrapper = logging_wrapper(empty_handler)
107
-
108
- assert wrapper.__name__ == empty_handler.__name__
109
-
110
-
111
- def test_log_event_data(capsys):
112
- wrapper = logging_wrapper(empty_handler)
113
- event = {
114
- "path": "my_path",
115
- "pathParameters": {"my_path_param": "my_value"},
116
- "requestContext": {
117
- "accountId": "1234567890",
118
- "stage": "my_stage",
119
- "apiId": "my_api_id",
120
- "domainName": "my_domain",
121
- "identity": {"sourceIp": "1.2.3.4"},
122
- },
123
- "resource": "my_resource",
124
- "httpMethod": "my_method",
125
- "queryStringParameters": {
126
- "my_query_param": "my_query_value",
127
- "token": "secret-stuff",
128
- "secretToken": "secret-stuff",
129
- },
130
- }
131
- wrapper(event, empty_context)
132
-
133
- log = json.loads(capsys.readouterr().out)
134
-
135
- assert log["function_stage"] == "my_stage"
136
- assert log["function_api_id"] == "my_api_id"
137
- assert log["aws_account_id"] == "1234567890"
138
- assert log["source_ip"] == "1.2.3.x"
139
- assert log["request_domain_name"] == "my_domain"
140
- assert log["request_resource"] == "my_resource"
141
- assert log["request_path"] == "my_path"
142
- assert log["request_method"] == "my_method"
143
- assert log["request_path_parameters"] == {"my_path_param": "my_value"}
144
- assert log["request_query_string_parameters"] == {
145
- "my_query_param": "my_query_value"
146
- }
147
- assert event["queryStringParameters"] == {
148
- "my_query_param": "my_query_value",
149
- "token": "secret-stuff",
150
- "secretToken": "secret-stuff",
151
- }
152
-
153
-
154
- def test_log_headers(capsys):
155
- wrapper = logging_wrapper(empty_handler)
156
-
157
- event = {"headers": {"X-Amzn-Trace-Id": "my-trace-id"}}
158
- wrapper(event, empty_context)
159
-
160
- log = json.loads(capsys.readouterr().out)
161
-
162
- assert log["aws_trace_id"] == "my-trace-id"
163
-
164
-
165
- def test_log_context(capsys):
166
- wrapper = logging_wrapper(empty_handler)
167
-
168
- context = RequestContext(
169
- function_name="my_function",
170
- function_version="my_function_version",
171
- aws_request_id="my_request_id",
172
- memory_limit_in_mb=1024,
173
- )
174
- wrapper(empty_event, context)
175
-
176
- log = json.loads(capsys.readouterr().out)
177
-
178
- assert log["function_name"] == "my_function"
179
- assert log["function_version"] == "my_function_version"
180
- assert log["aws_request_id"] == "my_request_id"
181
- assert log["memory_limit_in_mb"] == 1024
182
-
183
-
184
- def test_log_authenticated(capsys):
185
- event = {"requestContext": {"authorizer": {"principalId": "abc123456"}}}
186
- wrapper = logging_wrapper(ok_handler)
187
- wrapper(event, empty_context)
188
-
189
- log = json.loads(capsys.readouterr().out)
190
-
191
- assert log["principal_id"] == "abc123xxx"
192
- assert log["logged_in"] is True
193
-
194
-
195
- def test_log_unauthenticated(capsys):
196
- wrapper = logging_wrapper(ok_handler)
197
- wrapper(empty_event, empty_context)
198
-
199
- log = json.loads(capsys.readouterr().out)
200
-
201
- assert log["logged_in"] is False
202
-
203
-
204
- def test_log_response_ok(capsys):
205
- wrapper = logging_wrapper(ok_handler)
206
- wrapper(empty_event, empty_context)
207
-
208
- log = json.loads(capsys.readouterr().out)
209
-
210
- assert log["response_status_code"] == 200
211
- assert "response_body" not in log
212
- assert log["level"] == "info"
213
-
214
-
215
- def test_log_user_error(capsys):
216
- wrapper = logging_wrapper(user_error_handler)
217
- wrapper(empty_event, empty_context)
218
-
219
- log = json.loads(capsys.readouterr().out)
220
-
221
- assert log["response_status_code"] == 400
222
- assert log["response_body"] == "Bad Request"
223
- assert log["level"] == "info"
224
-
225
-
226
- def test_log_server_error(capsys):
227
- wrapper = logging_wrapper(server_error_handler)
228
- wrapper(empty_event, empty_context)
229
-
230
- log = json.loads(capsys.readouterr().out)
231
-
232
- assert log["response_status_code"] == 500
233
- assert log["response_body"] == "Internal Server Error"
234
- assert log["level"] == "error"
235
-
236
-
237
- def test_log_exception(capsys):
238
- wrapper = logging_wrapper(throwing_handler)
239
- try:
240
- wrapper(empty_event, empty_context)
241
- assert False
242
- except Exception:
243
- pass
244
-
245
- log = json.loads(capsys.readouterr().out)
246
-
247
- assert log["level"] == "error"
248
- assert "Exception: fail!" in log["exception"]
249
-
250
-
251
- def test_log_duration(capsys):
252
- wrapper = logging_wrapper(timing_handler)
253
- wrapper(empty_event, empty_context)
254
-
255
- log = json.loads(capsys.readouterr().out)
256
-
257
- assert log["my_timer"] > 10.0
258
- assert log["response_status_code"] == 201
259
-
260
-
261
- def test_log_duration_exception(capsys):
262
- wrapper = logging_wrapper(throwing_timing_handler)
263
- try:
264
- wrapper(empty_event, empty_context)
265
- assert False
266
- except Exception:
267
- pass
268
-
269
- log = json.loads(capsys.readouterr().out)
270
-
271
- assert log["my_timer"] > 10.0
272
- assert log["level"] == "error"
273
- assert "Exception: fail!" in log["exception"]
274
-
275
-
276
- def test_log_non_rest_response(capsys):
277
- wrapper = logging_wrapper(non_rest_handler)
278
- wrapper(empty_event, empty_context)
279
-
280
- log = json.loads(capsys.readouterr().out)
281
-
282
- assert log["level"] == "info"
283
-
284
-
285
- def test_hide_suffix():
286
- username = "jon-blund"
287
-
288
- assert hide_suffix(username) == "jon-blxxx"
289
- assert hide_suffix(username, 5) == "jon-xxxxx"
290
- assert hide_suffix(username, 20) == "xxxxxxxxx"
291
-
292
-
293
- def dynamodb_handler(event, context):
294
- dynamodb_response = {"ResponseMetadata": {"HTTPStatusCode": 200}}
295
- if "Count" in event:
296
- dynamodb_response["Count"] = event["Count"]
297
-
298
- log_dynamodb(lambda: dynamodb_response)
299
-
300
-
301
- def test_log_dynamodb(capsys):
302
- wrapper = logging_wrapper(dynamodb_handler)
303
- wrapper(empty_event, empty_context)
304
-
305
- log = json.loads(capsys.readouterr().out)
306
-
307
- assert "dynamodb_duration_ms" in log
308
- assert log["dynamodb_status_code"] == 200
309
-
310
-
311
- def test_log_dynamodb_item_count(capsys):
312
- event = {"Count": 123}
313
- wrapper = logging_wrapper(dynamodb_handler)
314
- wrapper(event, empty_context)
315
-
316
- log = json.loads(capsys.readouterr().out)
317
-
318
- assert log["dynamodb_item_count"] == 123
@@ -1,29 +0,0 @@
1
- import pytest
2
- from pydantic.error_wrappers import ValidationError
3
-
4
- from okdata.aws.status.model import StatusData
5
-
6
- OK_ERROR = {"message": {"nb": "Det er et problem", "en": "There is a problem"}}
7
-
8
-
9
- class TestStatusData:
10
- def test_errors_entry_valid(self):
11
- params = {"errors": [OK_ERROR, OK_ERROR]}
12
- result = StatusData(**params)
13
- assert isinstance(result, StatusData)
14
- assert result.errors[0] == OK_ERROR
15
-
16
- def test_errors_entry_not_dict(self):
17
- params = {"errors": [OK_ERROR, "This is string, not a dict."]}
18
- with pytest.raises(ValidationError):
19
- StatusData(**params)
20
-
21
- def test_errors_entry_no_message(self):
22
- params = {"errors": [OK_ERROR, {"Bad key": {}}]}
23
- with pytest.raises(ValueError):
24
- StatusData(**params)
25
-
26
- def test_errors_entry_no_nb(self):
27
- params = {"errors": [OK_ERROR, {"message": {"Bad key": "foo", "en": "bar"}}]}
28
- with pytest.raises(ValueError):
29
- StatusData(**params)
@@ -1,131 +0,0 @@
1
- import re
2
- from copy import deepcopy
3
- from unittest.mock import patch
4
-
5
- import pytest
6
- from freezegun import freeze_time
7
- from okdata.sdk.config import Config
8
-
9
- from okdata.aws.status.model import StatusData, TraceStatus, TraceEventStatus
10
- from okdata.aws.status.sdk import Status
11
- from okdata.aws.status.wrapper import _status_from_lambda_context
12
-
13
-
14
- utc_now = "2020-10-10T08:55:01+00:00"
15
- trace_id = "my-trace-id"
16
- mock_token_response = {
17
- "access_token": "access",
18
- "refresh_token": "refresh",
19
- "token_type": "bearer",
20
- }
21
- mock_status_data = {
22
- "trace_id": trace_id,
23
- "trace_status": "CONTINUE",
24
- "trace_event_status": "OK",
25
- "domain": "dataset",
26
- "domain_id": "my-dataset/1",
27
- "status_body": None,
28
- "user": "someuser",
29
- "component": "system32",
30
- }
31
- mock_status_response = {"trace_id": trace_id}
32
-
33
-
34
- class MockLambdaContext:
35
- function_name = "my_lambda_function"
36
- function_version = 123
37
-
38
-
39
- @pytest.fixture(scope="function")
40
- def mock_openid(requests_mock):
41
- openid_matcher = re.compile("openid-connect")
42
- requests_mock.register_uri(
43
- "POST",
44
- openid_matcher,
45
- json=mock_token_response,
46
- status_code=200,
47
- )
48
-
49
-
50
- @pytest.fixture(scope="function")
51
- def mock_status_api(requests_mock):
52
- matcher = re.compile("mock-status-api")
53
- requests_mock.register_uri(
54
- "POST", matcher, json=mock_status_response, status_code=200
55
- )
56
-
57
-
58
- class TestStatusClass:
59
- def test_status_data_from_trace_id(self):
60
- s = Status(trace_id)
61
- assert s.status_data.trace_id == trace_id
62
-
63
- def test_status_data_from_dict(self):
64
- s = Status(mock_status_data)
65
- assert s.status_data.trace_id == trace_id
66
-
67
- def test_status_data_from_object(self):
68
- status_data = StatusData.parse_obj(mock_status_data)
69
- s = Status(status_data)
70
- assert s.status_data.trace_id == trace_id
71
-
72
- def test_status_with_sdk_config(self):
73
- config = Config(config={"foo": "bar"})
74
- # Mock out the `Authenticate` class so that the SDK doesn't try to set
75
- # up authentication with a malformed config.
76
- with patch("okdata.sdk.sdk.Authenticate"):
77
- s = Status(trace_id, config)
78
- assert s._sdk.config.config == {"foo": "bar"}
79
-
80
- def test_status_data_from_lambda(self):
81
- lambda_context = MockLambdaContext()
82
- s = _status_from_lambda_context(
83
- event={
84
- "requestContext": {"authorizer": {"principalId": "someuser"}},
85
- "execution_name": trace_id,
86
- },
87
- context=lambda_context,
88
- )
89
- assert s.trace_id == trace_id
90
- assert s.meta.function_name == lambda_context.function_name
91
- assert s.user == "someuser"
92
-
93
- @freeze_time(utc_now)
94
- def test_status_done(self, requests_mock, mock_openid, mock_status_api):
95
- requests_mock.register_uri("POST", f"/status-api/status/{trace_id}", json={})
96
- s = Status(StatusData.parse_obj(mock_status_data))
97
- s.done()
98
-
99
- last_request = requests_mock.last_request
100
- assert last_request.method == "POST"
101
- assert last_request.url.endswith(f"/status/{trace_id}")
102
-
103
- payload = last_request.json()
104
- assert payload["trace_id"] == trace_id
105
- assert payload["start_time"] == utc_now
106
- assert payload["end_time"] == utc_now
107
- assert payload["domain"] == "dataset"
108
- assert payload["domain_id"] == "my-dataset/1"
109
- assert payload["user"] == "someuser"
110
- assert payload["component"] == "system32"
111
- assert payload["trace_status"] == TraceStatus.CONTINUE
112
- assert payload["trace_event_status"] == TraceEventStatus.OK
113
-
114
- def test_status_optional_fields(self):
115
- status_data = deepcopy(mock_status_data)
116
- status_data["domain_id"] = None
117
- status_data["user"] = None
118
- s = StatusData.parse_obj(status_data)
119
- assert s.domain_id is None
120
- assert s.user is None
121
-
122
- status_data.pop("domain_id")
123
- status_data.pop("user")
124
- s = StatusData.parse_obj(status_data)
125
- assert s.domain_id is None
126
- assert s.user is None
127
-
128
- def test_add_status_data_payload(self, mock_openid, mock_status_api):
129
- s = Status(mock_status_data)
130
- s.add(domain_id="my-domain-id")
131
- assert s.status_data.domain_id == "my-domain-id"
File without changes
File without changes
File without changes
File without changes