Skip to content

Commit bb9c9cf

Browse files
authored
Merge pull request #19 from serverless-operations/default-sdk-encoding
Set default encoding to the event payload of all SDK clients
2 parents 1772b95 + d161d4c commit bb9c9cf

32 files changed

+521
-164
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,5 +132,8 @@ pip-selfcheck.json
132132
# vscode
133133
.vscode
134134

135+
# sls
136+
node_modules
137+
.serverless
135138

136139
# End of https://www.gitignore.io/api/python

README.md

Lines changed: 81 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Mainly, Jeffy is focusing on three things.
2626
* 1.2. [Injecting additional attributes to logs](#Injectingadditionalattributestologs)
2727
* 1.3. [Change the attribute name of correlation id](#Changetheattributenameofcorrelationid)
2828
* 1.4. [Change the log lervel](#Changetheloglervel)
29-
* 2. [Event source specific decorators](#Eventsourcespecificdecorators)
29+
* 2. [Event handlers](#Eventhandlers)
3030
* 2.1. [common](#common)
3131
* 2.2. [rest_api](#rest_api)
3232
* 2.3. [sqs](#sqs)
@@ -35,14 +35,14 @@ Mainly, Jeffy is focusing on three things.
3535
* 2.6. [dynamodb_streams](#dynamodb_streams)
3636
* 2.7. [s3](#s3)
3737
* 2.8. [schedule](#schedule)
38-
* 3. [Encoding](#Encoding)
39-
* 4. [Validation](#Validation)
40-
* 4.1. [JSON Scheme Validator](#JSONSchemeValidator)
41-
* 5. [Tracing](#Tracing)
42-
* 5.1. [Kinesis Clinent](#KinesisClinent)
43-
* 5.2. [SNS Client](#SNSClient)
44-
* 5.3. [SQS Client](#SQSClient)
45-
* 5.4. [S3 Client](#S3Client)
38+
* 3. [SDK](#SDK)
39+
* 3.1. [Kinesis Clinent](#KinesisClinent)
40+
* 3.2. [SNS Client](#SNSClient)
41+
* 3.3. [SQS Client](#SQSClient)
42+
* 3.4. [S3 Client](#S3Client)
43+
* 4. [Encoding](#Encoding)
44+
* 5. [Validation](#Validation)
45+
* 5.1. [JSON Scheme Validator](#JSONSchemeValidator)
4646

4747
<!-- vscode-markdown-toc-config
4848
numbering=true
@@ -157,7 +157,7 @@ def handler(event, context):
157157
app.logger.info({'foo': 'bar'})
158158
```
159159

160-
## 2. <a name='Eventsourcespecificdecorators'></a>Event source specific decorators
160+
## 2. <a name='Eventhandlers'></a>Event handlers
161161
Decorators make simple to implement common lamdba tasks, such as parsing array from Kinesis, SNS, SQS events etc.
162162

163163
Here are provided decorators
@@ -210,7 +210,7 @@ Error output with auto_logging
210210
### 2.2. <a name='rest_api'></a>rest_api
211211
Decorator for API Gateway event. Automatically get the correlation id from request header and set the correlation id to response header.
212212

213-
Default event payload encoding is `jeffy.encoding.json.JsonEncoding`.
213+
Default encoding is `jeffy.encoding.json.JsonEncoding`.
214214

215215
```python
216216
from jeffy.framework import get_app
@@ -250,7 +250,7 @@ def handler(event, context):
250250
### 2.3. <a name='sqs'></a>sqs
251251
Decorator for sqs event. Automaticlly parse `"event.Records"` list from SQS event source to each items for making it easy to treat it inside main process of Lambda.
252252

253-
Default event payload encoding is `jeffy.encoding.json.JsonEncoding`.
253+
Default encoding is `jeffy.encoding.json.JsonEncoding`.
254254

255255
```python
256256
from jeffy.framework import get_app
@@ -274,7 +274,7 @@ def handler(event, context):
274274
### 2.4. <a name='sns'></a>sns
275275
Decorator for sns event. Automaticlly parse `event.Records` list from SNS event source to each items for making it easy to treat it inside main process of Lambda.
276276

277-
Default event payload encoding is `jeffy.encoding.json.JsonEncoding`.
277+
Default encoding is `jeffy.encoding.json.JsonEncoding`.
278278

279279
```python
280280
from jeffy.framework import get_app
@@ -298,7 +298,7 @@ def handler(event, context):
298298
### 2.5. <a name='kinesis_streams'></a>kinesis_streams
299299
Decorator for kinesis stream event. Automaticlly parse `event.Records` list from Kinesis event source to each items and decode it with base64 for making it easy to treat it inside main process of Lambda.
300300

301-
Default event payload encoding is `jeffy.encoding.json.JsonEncoding`.
301+
Default encoding is `jeffy.encoding.json.JsonEncoding`.
302302

303303
```python
304304
from jeffy.framework import get_app
@@ -344,7 +344,7 @@ def handler(event, context):
344344
### 2.7. <a name='s3'></a>s3
345345
Decorator for S3 event. Automatically parse body stream from triggered S3 object and S3 bucket and key name to Lambda.
346346

347-
Default event payload encoding is `jeffy.encoding.bytes.BytesEncoding`.
347+
Default encoding is `jeffy.encoding.bytes.BytesEncoding`.
348348

349349
```python
350350
from jeffy.framework import get_app
@@ -371,64 +371,12 @@ def handler(event, context):
371371
...
372372
```
373373

374-
## 3. <a name='Encoding'></a>Encoding
375-
Each handler has a a default encoding and automatically decode the data to python object. And you can change the encoding.
374+
## 3. <a name='SDK'></a>SDK
375+
Jeffy has the original wrapper clients of AWS SDK(boto3). The clients automatically inject `correlation_id` in the event payload and encode it to the specified(or default) encoding.
376376

377-
Currently, the encodings you can choose are:
378-
- `jeffy.encoding.bytes.BytesEncoding`
379-
- `jeffy.encoding.json.JsonEncoding`
377+
### 3.1. <a name='KinesisClinent'></a>Kinesis Clinent
380378

381-
Each encoding class also has `encode` methods to encode `bytes` data into own encoding.
382-
383-
```python
384-
from jeffy.framework import get_app
385-
from jeffy.encoding.bytes import BytesEncoding
386-
from jeffy.sdk.kinesis import Kinesis
387-
388-
app = get_app()
389-
bytes_encoding = BytesEncoding()
390-
391-
@app.handlers.kinesis_streams(encoding=bytes_encoding)
392-
def handler(event, context):
393-
Kinesis().put_record(
394-
stream_name=os.environ['STREAM_NAME'],
395-
data=bytes_encoding.encode('foo'.encode('utf-8)),
396-
partition_key='your-partition-key'
397-
)
398-
```
399-
400-
## 4. <a name='Validation'></a>Validation
401-
402-
### 4.1. <a name='JSONSchemeValidator'></a>JSON Scheme Validator
403-
`JsonSchemeValidator` is automatically validate event payload with following json scheme you define. raise `ValidationError` exception if the validation fails.
404-
405-
```python
406-
from jeffy.framework import get_app
407-
from jeffy.validator.jsonscheme import JsonSchemeValidator
408-
409-
app = get_app()
410-
411-
@app.handlers.rest_api(
412-
validator=JsonSchemeValidator(scheme={
413-
'type': 'object',
414-
'properties': {
415-
'message': {'type': 'string'}}}))
416-
def handler(event, context):
417-
return {
418-
'statusCode': 200,
419-
'headers': 'Content-Type':'application/json',
420-
'body': json.dumps({
421-
'message': 'ok.'
422-
})
423-
}
424-
```
425-
426-
## 5. <a name='Tracing'></a>Tracing
427-
`correlation_id` is to trace subsequent Lambda functions and services. Jeffy automatically extract correlation IDs and caputure logs from the invocation event.
428-
429-
And Jeffy provide boto3 wrapper client to create and automatically inject `correlation_id`.
430-
431-
### 5.1. <a name='KinesisClinent'></a>Kinesis Clinent
379+
Default encoding is `jeffy.encoding.json.JsonEncoding`.
432380

433381
```python
434382
from jeffy.framework import get_app
@@ -445,7 +393,9 @@ def handler(event, context):
445393
)
446394
```
447395

448-
### 5.2. <a name='SNSClient'></a>SNS Client
396+
### 3.2. <a name='SNSClient'></a>SNS Client
397+
398+
Default encoding is `jeffy.encoding.json.JsonEncoding`.
449399

450400
```python
451401
from jeffy.framework import get_app
@@ -462,7 +412,9 @@ def handler(event, context):
462412
)
463413
```
464414

465-
### 5.3. <a name='SQSClient'></a>SQS Client
415+
### 3.3. <a name='SQSClient'></a>SQS Client
416+
417+
Default encoding is `jeffy.encoding.json.JsonEncoding`.
466418

467419
```python
468420
from jeffy.framework import get_app
@@ -478,7 +430,9 @@ def handler(event, context):
478430
)
479431
```
480432

481-
### 5.4. <a name='S3Client'></a>S3 Client
433+
### 3.4. <a name='S3Client'></a>S3 Client
434+
435+
Default encoding is `jeffy.encoding.bytes.BytesEncoding`.
482436

483437
```python
484438
from jeffy.framework import get_app
@@ -495,6 +449,59 @@ def handler(event, context):
495449
)
496450
```
497451

452+
## 4. <a name='Encoding'></a>Encoding
453+
Each handler and SDK client has a default encoding and automatically encode/decode the data from/to python object. And you can change the encoding.
454+
455+
Currently, the encodings you can choose are:
456+
- `jeffy.encoding.bytes.BytesEncoding`
457+
- `jeffy.encoding.json.JsonEncoding`
458+
459+
Each encoding class also has `encode` methods to encode `bytes` data into own encoding.
460+
461+
```python
462+
from jeffy.framework import get_app
463+
from jeffy.encoding.bytes import BytesEncoding
464+
from jeffy.sdk.kinesis import Kinesis
465+
466+
app = get_app()
467+
bytes_encoding = BytesEncoding()
468+
469+
@app.handlers.kinesis_streams(encoding=bytes_encoding)
470+
def handler(event, context):
471+
kinesis = Kinesis(encoding=bytes_encoding)
472+
kinesis.put_record(
473+
stream_name=os.environ['STREAM_NAME'],
474+
data=bytes_encoding.encode('foo'.encode('utf-8)),
475+
partition_key='your-partition-key'
476+
)
477+
```
478+
479+
## 5. <a name='Validation'></a>Validation
480+
481+
### 5.1. <a name='JSONSchemeValidator'></a>JSON Scheme Validator
482+
`JsonSchemeValidator` is automatically validate event payload with following json scheme you define. raise `ValidationError` exception if the validation fails.
483+
484+
```python
485+
from jeffy.framework import get_app
486+
from jeffy.validator.jsonscheme import JsonSchemeValidator
487+
488+
app = get_app()
489+
490+
@app.handlers.rest_api(
491+
validator=JsonSchemeValidator(scheme={
492+
'type': 'object',
493+
'properties': {
494+
'message': {'type': 'string'}}}))
495+
def handler(event, context):
496+
return {
497+
'statusCode': 200,
498+
'headers': 'Content-Type':'application/json',
499+
'body': json.dumps({
500+
'message': 'ok.'
501+
})
502+
}
503+
```
504+
498505
# Requirements
499506

500507
- Python 3.6 or higher

handler.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import json
2+
import os
3+
import uuid
4+
5+
from jeffy.framework import get_app
6+
from jeffy.sdk.kinesis import Kinesis
7+
from jeffy.sdk.s3 import S3
8+
from jeffy.sdk.sns import Sns
9+
from jeffy.sdk.sqs import Sqs
10+
11+
import boto3
12+
import requests
13+
14+
app = get_app()
15+
16+
17+
@app.handlers.common()
18+
def start_test(event, context):
19+
requests.post(
20+
os.environ['API_URL'],
21+
data=json.dumps({'foo': 'bar'}),
22+
headers={'content-type': 'application/json'})
23+
Sns().publish(
24+
topic_arn=os.environ['TOPIC_ARN'],
25+
subject='foo',
26+
message='bar')
27+
Kinesis().put_record(
28+
stream_name=os.environ['STREAM_NAME'],
29+
data={'foo': 'bar'},
30+
partition_key='partition_key')
31+
Sqs().send_message(
32+
queue_url=os.environ['QUEUE_URL'],
33+
message='hello world')
34+
S3().upload_file(
35+
file_path='logo.png',
36+
bucket_name=os.environ['BUCKET_NAME'],
37+
key='logo.png')
38+
boto3.resource('dynamodb').Table(os.environ['TABLE_NAME']).put_item(Item={'id': str(uuid.uuid4())})
39+
return 'ok'
40+
41+
42+
@app.handlers.rest_api()
43+
def rest_api_test(event, context):
44+
return {
45+
'statusCode': 200,
46+
'headers': {'Content-Type': 'application/json'},
47+
'body': json.dumps({'result': 'ok.'})}
48+
49+
50+
@app.handlers.sqs()
51+
def sqs_test(event, context):
52+
return event
53+
54+
55+
@app.handlers.sns()
56+
def sns_test(event, context):
57+
return event
58+
59+
60+
@app.handlers.kinesis_streams()
61+
def kinesis_test(event, context):
62+
return event
63+
64+
65+
@app.handlers.dynamodb_streams()
66+
def dynamodb_test(event, context):
67+
return event
68+
69+
70+
@app.handlers.s3()
71+
def s3_test(event, context):
72+
del event['body']
73+
return event

jeffy/encoding/bytes.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,24 @@
1-
from typing import Any
2-
31
from jeffy.encoding import Encoding
42

53

64
class BytesEncoding(Encoding):
75
"""Bytes encoding class."""
86

9-
def encode(self, payload: Any) -> bytes:
7+
def encode(self, payload: bytes) -> bytes:
108
"""
119
Encode to bytes.
1210
1311
Parameters
1412
----------
15-
payload: Any
13+
payload: bytes
1614
1715
Returns
1816
-------
1917
payload : bytes
2018
"""
2119
return payload
2220

23-
def decode(self, payload: bytes) -> Any:
21+
def decode(self, payload: bytes) -> bytes:
2422
"""
2523
Decode from bytes.
2624

jeffy/handlers/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import uuid
21
from typing import Dict
32

43
from jeffy.handlers.common import CommonHandlerMixin
@@ -8,6 +7,7 @@
87
from jeffy.handlers.sns import SnsHandlerMixin
98
from jeffy.handlers.sqs import SqsHandlerMixin
109
from jeffy.handlers.streams import StreamsHandlerMixin
10+
from jeffy.logging import generate_correlation_id
1111

1212

1313
class Handlers(
@@ -42,7 +42,7 @@ def capture_correlation_id(self, payload: Dict = {}) -> str:
4242
elif self.app.correlation_id_header in payload.get('headers', {}): # type: ignore
4343
correlation_id = payload['headers'][self.app.correlation_id_header] # type: ignore
4444
else:
45-
correlation_id = str(uuid.uuid4())
45+
correlation_id = generate_correlation_id()
4646
self.app.logger.update_context({self.app.correlation_attr_name: correlation_id}) # type: ignore
4747
self.app.correlation_id = correlation_id # type: ignore
4848
return correlation_id

jeffy/handlers/common.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ def _common(func: Callable): # type: ignore
2727
@functools.wraps(func)
2828
def wrapper(event, context): # type: ignore
2929
self.capture_correlation_id(event)
30-
self.app.logger.info(event)
3130
try:
32-
result = func(event, context)
33-
self.app.logger.info(result)
34-
return result
31+
self.app.logger.info(event)
32+
ret = func(event, context)
33+
self.app.logger.info(ret)
34+
return ret
3535
except Exception as e:
3636
self.app.logger.exception(e)
3737
raise e

0 commit comments

Comments
 (0)