pygrestqlambda 0.0.2__py3-none-any.whl → 0.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pygrestqlambda/aws/lambda_function/json_transform.py +12 -5
- pygrestqlambda/aws/lambda_function/rest_api_gateway_proxy_integration.py +100 -5
- pygrestqlambda-0.0.4.dist-info/METADATA +124 -0
- {pygrestqlambda-0.0.2.dist-info → pygrestqlambda-0.0.4.dist-info}/RECORD +6 -6
- pygrestqlambda-0.0.2.dist-info/METADATA +0 -37
- {pygrestqlambda-0.0.2.dist-info → pygrestqlambda-0.0.4.dist-info}/WHEEL +0 -0
- {pygrestqlambda-0.0.2.dist-info → pygrestqlambda-0.0.4.dist-info}/licenses/LICENSE +0 -0
@@ -2,21 +2,28 @@
|
|
2
2
|
JSON output transformer for non-serialisable values
|
3
3
|
"""
|
4
4
|
|
5
|
+
from datetime import date, datetime
|
6
|
+
from decimal import Decimal
|
5
7
|
from uuid import UUID
|
6
|
-
from datetime import datetime
|
7
8
|
|
8
|
-
|
9
|
-
def json_output(value: object) -> str:
|
9
|
+
def to_string(value: object) -> str:
|
10
10
|
"""
|
11
|
-
Calculates the
|
11
|
+
Calculates the string version of an object to return in a JSON response
|
12
12
|
"""
|
13
13
|
|
14
14
|
# Handle UUIDs
|
15
15
|
if isinstance(value, UUID):
|
16
16
|
value = str(value)
|
17
17
|
|
18
|
-
# Handle timestamps
|
18
|
+
# Handle date/timestamps
|
19
19
|
if isinstance(value, datetime):
|
20
20
|
value = value.isoformat()
|
21
21
|
|
22
|
+
if isinstance(value, date):
|
23
|
+
value = value.isoformat()
|
24
|
+
|
25
|
+
# Handle decimals
|
26
|
+
if isinstance(value, Decimal):
|
27
|
+
value = float(value)
|
28
|
+
|
22
29
|
return value
|
@@ -6,11 +6,11 @@ Returns payload structure expected by REST API Gateway
|
|
6
6
|
https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format
|
7
7
|
"""
|
8
8
|
|
9
|
-
from base64 import b64encode
|
9
|
+
from base64 import b64encode, b64decode
|
10
10
|
from dataclasses import dataclass
|
11
11
|
import json
|
12
12
|
import logging
|
13
|
-
from pygrestqlambda.aws.lambda_function.json_transform import
|
13
|
+
from pygrestqlambda.aws.lambda_function.json_transform import to_string
|
14
14
|
|
15
15
|
|
16
16
|
@dataclass
|
@@ -22,25 +22,40 @@ class Response:
|
|
22
22
|
status_code: int | None = 401
|
23
23
|
headers: dict | None = None
|
24
24
|
multi_value_headers: dict | None = None
|
25
|
-
body: str | None = None
|
25
|
+
body: str | dict | None = None
|
26
26
|
|
27
27
|
def get_payload(self) -> dict:
|
28
28
|
"""
|
29
29
|
Gets payload to send to REST API Gateway
|
30
30
|
"""
|
31
31
|
|
32
|
+
is_json = False
|
33
|
+
if isinstance(self.body, dict):
|
34
|
+
is_json = True
|
35
|
+
|
32
36
|
# Set headers
|
33
37
|
if self.headers is None:
|
34
38
|
self.headers = {}
|
35
39
|
|
36
40
|
if "Content-Type" not in self.headers:
|
37
|
-
|
41
|
+
logging.debug("No content type header set")
|
42
|
+
if is_json:
|
43
|
+
logging.debug("Using application/json for content-type")
|
44
|
+
self.headers["Content-Type"] = "application/json"
|
45
|
+
else:
|
46
|
+
logging.debug("Using text/plain for content-type")
|
47
|
+
self.headers["Content-Type"] = "text/plain"
|
38
48
|
|
39
49
|
# Calculate body
|
40
50
|
if self.is_base64_encoded:
|
41
51
|
body = b64encode(self.body).decode("utf-8")
|
42
52
|
else:
|
43
|
-
|
53
|
+
if is_json:
|
54
|
+
logging.debug("Body is a JSON object")
|
55
|
+
body = json.dumps(self.body, default=to_string)
|
56
|
+
else:
|
57
|
+
logging.debug("Body is plain text")
|
58
|
+
body = self.body
|
44
59
|
|
45
60
|
logging.debug("Transforming dataclass dictionary to JSON")
|
46
61
|
data = {
|
@@ -52,3 +67,83 @@ class Response:
|
|
52
67
|
}
|
53
68
|
|
54
69
|
return data
|
70
|
+
|
71
|
+
# pylint: disable=too-many-instance-attributes
|
72
|
+
class Request:
|
73
|
+
"""
|
74
|
+
Lambda function proxy integration request
|
75
|
+
"""
|
76
|
+
|
77
|
+
def __init__(self, event: dict):
|
78
|
+
self.event = event
|
79
|
+
# Extract authorisation information
|
80
|
+
self.cognito_uid = self.get_cognito_uid()
|
81
|
+
# Extract headers needed for body and response
|
82
|
+
self.accept: str = self.get_header('accept')
|
83
|
+
self.content_type: str = self.get_header('content-type')
|
84
|
+
# Extract resource
|
85
|
+
self.resource = event.get('resource')
|
86
|
+
self.method = event.get('httpMethod')
|
87
|
+
# Extract parameters
|
88
|
+
self.query_params = event.get('multiValueQueryStringParameters')
|
89
|
+
self.path_params = event.get('pathParameters')
|
90
|
+
# Extract body
|
91
|
+
self.body = self.get_body()
|
92
|
+
|
93
|
+
|
94
|
+
def get_body(self):
|
95
|
+
"""
|
96
|
+
Returns body from request, decodes from base64 if necessary
|
97
|
+
"""
|
98
|
+
|
99
|
+
body = self.event.get('body')
|
100
|
+
content = body
|
101
|
+
if self.event.get('isBase64Encoded'):
|
102
|
+
if body:
|
103
|
+
content = b64decode(body)
|
104
|
+
|
105
|
+
# Handle no content type
|
106
|
+
if self.content_type is None:
|
107
|
+
return content
|
108
|
+
|
109
|
+
# Handle plain text
|
110
|
+
if self.content_type.lower() == 'text/plain':
|
111
|
+
return str(content)
|
112
|
+
|
113
|
+
# Handle JSON
|
114
|
+
if self.content_type.lower() == 'application/json':
|
115
|
+
return json.loads(content)
|
116
|
+
|
117
|
+
return content
|
118
|
+
|
119
|
+
|
120
|
+
def get_cognito_uid(self):
|
121
|
+
"""
|
122
|
+
Retrieve Cognito UID from supplied claim
|
123
|
+
"""
|
124
|
+
claims = self.event.get('requestContext', {}).get('authorizer', {}).get('claims')
|
125
|
+
|
126
|
+
if claims is None:
|
127
|
+
logging.info('No claims in event request context authoriser')
|
128
|
+
return None
|
129
|
+
|
130
|
+
cognito_uid = claims.get('sub')
|
131
|
+
|
132
|
+
return cognito_uid
|
133
|
+
|
134
|
+
|
135
|
+
def get_header(self, header_name: str):
|
136
|
+
"""
|
137
|
+
Retrieve Accept header
|
138
|
+
"""
|
139
|
+
|
140
|
+
headers = self.event.get('headers')
|
141
|
+
if headers is None:
|
142
|
+
return None
|
143
|
+
|
144
|
+
# Lowercase all the headers
|
145
|
+
headers_lower = {k.lower():v for k,v in headers.items()}
|
146
|
+
|
147
|
+
accept = headers_lower.get(header_name.lower())
|
148
|
+
|
149
|
+
return accept
|
@@ -0,0 +1,124 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: pygrestqlambda
|
3
|
+
Version: 0.0.4
|
4
|
+
Summary: PostgreSQL REST API framework for AWS Lambda functions
|
5
|
+
Project-URL: Homepage, https://github.com/mesogate/pygrestqlambda
|
6
|
+
Project-URL: Issues, https://github.com/mesogate/pygrestqlambda/issues
|
7
|
+
Author-email: Voquis Limited <opensource@voquis.com>
|
8
|
+
License-File: LICENSE
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
12
|
+
Requires-Python: >=3.11
|
13
|
+
Requires-Dist: aws-xray-sdk
|
14
|
+
Requires-Dist: boto3
|
15
|
+
Provides-Extra: dev
|
16
|
+
Requires-Dist: build; extra == 'dev'
|
17
|
+
Requires-Dist: docker; extra == 'dev'
|
18
|
+
Requires-Dist: httpx; extra == 'dev'
|
19
|
+
Requires-Dist: psycopg[binary]; extra == 'dev'
|
20
|
+
Requires-Dist: pylint; extra == 'dev'
|
21
|
+
Requires-Dist: pytest; extra == 'dev'
|
22
|
+
Requires-Dist: pytest-cov; extra == 'dev'
|
23
|
+
Requires-Dist: pytest-xdist; extra == 'dev'
|
24
|
+
Requires-Dist: ruff; extra == 'dev'
|
25
|
+
Requires-Dist: twine; extra == 'dev'
|
26
|
+
Description-Content-Type: text/markdown
|
27
|
+
|
28
|
+
# Python PostgreSQL REST API framework for AWS Lambda functions
|
29
|
+
> [!NOTE]
|
30
|
+
> Project status: `Alpha`
|
31
|
+
|
32
|
+
A REST API web framework for persisting records in a PostgreSQL database.
|
33
|
+
|
34
|
+
## Supported features
|
35
|
+
- Automatic creation of `uid` fields
|
36
|
+
- Automatic setting of `created_at` and `last_updated_at` timestamps
|
37
|
+
- Automatic setting of `creator_uid` and `last_updater_uid`
|
38
|
+
- RDS with IAM credentials
|
39
|
+
|
40
|
+
## Examples
|
41
|
+
See [Examples docs directory](./docs/examples/)
|
42
|
+
|
43
|
+
## Sequence diagrams
|
44
|
+
|
45
|
+
### High-level infrastructure
|
46
|
+
|
47
|
+
This sequence diagram shows how a lambda function running this library is intended to be deployed.
|
48
|
+
|
49
|
+
```mermaid
|
50
|
+
sequenceDiagram
|
51
|
+
# Set up actors and participants
|
52
|
+
actor User
|
53
|
+
participant APIGW as API Gateway
|
54
|
+
participant Cognito as Cognito
|
55
|
+
box Purple This library
|
56
|
+
participant Lambda as Lambda Function
|
57
|
+
end
|
58
|
+
participant RDS as RDS Database
|
59
|
+
|
60
|
+
# Set up Sequences
|
61
|
+
User ->> APIGW: HTTP /resource
|
62
|
+
activate APIGW
|
63
|
+
APIGW -->> Cognito: Authenticate
|
64
|
+
activate Cognito
|
65
|
+
Cognito ->> APIGW: Authenticated
|
66
|
+
deactivate Cognito
|
67
|
+
APIGW ->> Lambda: Send proxy integration request
|
68
|
+
activate Lambda
|
69
|
+
Lambda ->> RDS: Fetch/mutate
|
70
|
+
activate RDS
|
71
|
+
RDS -->> Lambda: Return records
|
72
|
+
deactivate RDS
|
73
|
+
Lambda -->> APIGW: Return response
|
74
|
+
deactivate Lambda
|
75
|
+
APIGW -->> User: Return response
|
76
|
+
deactivate APIGW
|
77
|
+
```
|
78
|
+
|
79
|
+
### Low-level architecture
|
80
|
+
|
81
|
+
This sequence diagram shows the layers within the library that handle request and response processing.
|
82
|
+
|
83
|
+
```mermaid
|
84
|
+
sequenceDiagram
|
85
|
+
|
86
|
+
Participant APIGW as API Gateway
|
87
|
+
box Purple This library as a deployed Lambda Function
|
88
|
+
Participant CONT as Controller
|
89
|
+
Participant REQM as Request Mapper
|
90
|
+
Participant RESOURCEM as Resource Mapper
|
91
|
+
Participant DBM as Database Mapper
|
92
|
+
Participant RESPONSEM as Response Mapper
|
93
|
+
end
|
94
|
+
Participant RDS
|
95
|
+
|
96
|
+
APIGW ->> CONT: Send `event` dict
|
97
|
+
activate CONT
|
98
|
+
CONT ->> REQM: Map request
|
99
|
+
activate REQM
|
100
|
+
REQM ->> CONT: Mapped request
|
101
|
+
deactivate REQM
|
102
|
+
|
103
|
+
CONT ->> RESOURCEM: Map resource
|
104
|
+
activate RESOURCEM
|
105
|
+
RESOURCEM -->> CONT: Mapped resource
|
106
|
+
deactivate RESOURCEM
|
107
|
+
|
108
|
+
CONT ->> DBM: Request resource operation
|
109
|
+
activate DBM
|
110
|
+
DBM ->> RDS: Perform resource operation
|
111
|
+
activate RDS
|
112
|
+
RDS -->> DBM: Return resources
|
113
|
+
deactivate RDS
|
114
|
+
DBM -->> CONT: Return resource
|
115
|
+
deactivate DBM
|
116
|
+
|
117
|
+
CONT ->> RESPONSEM: Map response
|
118
|
+
activate RESPONSEM
|
119
|
+
RESPONSEM -->> CONT: Mapped response
|
120
|
+
deactivate RESPONSEM
|
121
|
+
|
122
|
+
CONT -->> APIGW: Return response
|
123
|
+
deactivate CONT
|
124
|
+
```
|
@@ -1,11 +1,11 @@
|
|
1
1
|
pygrestqlambda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
pygrestqlambda/aws/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
3
|
pygrestqlambda/aws/lambda_function/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
pygrestqlambda/aws/lambda_function/json_transform.py,sha256=
|
5
|
-
pygrestqlambda/aws/lambda_function/rest_api_gateway_proxy_integration.py,sha256=
|
4
|
+
pygrestqlambda/aws/lambda_function/json_transform.py,sha256=Hb09h8n4tYS58vnP03QG46cV_ghCrVzfBctHdUURv6g,628
|
5
|
+
pygrestqlambda/aws/lambda_function/rest_api_gateway_proxy_integration.py,sha256=xE1w7dd-AUTQZB1Jc8DG_pqibWpasYB1rM1BQwuAJyI,4505
|
6
6
|
pygrestqlambda/db/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
7
7
|
pygrestqlambda/db/record.py,sha256=6HwvVIB8iJxxcoLeFgk51Gik_w7--w5fQzLN0ntB8iw,1527
|
8
|
-
pygrestqlambda-0.0.
|
9
|
-
pygrestqlambda-0.0.
|
10
|
-
pygrestqlambda-0.0.
|
11
|
-
pygrestqlambda-0.0.
|
8
|
+
pygrestqlambda-0.0.4.dist-info/METADATA,sha256=Bm0rNQxaQJ_LY5m9zF75BRO3pCzcGbhbZMRPvTnx47s,3796
|
9
|
+
pygrestqlambda-0.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
pygrestqlambda-0.0.4.dist-info/licenses/LICENSE,sha256=8QeS1c5uv4AYoEG3M80OSCBuC_Pk-6vjU1VBUnlX2m0,1071
|
11
|
+
pygrestqlambda-0.0.4.dist-info/RECORD,,
|
@@ -1,37 +0,0 @@
|
|
1
|
-
Metadata-Version: 2.4
|
2
|
-
Name: pygrestqlambda
|
3
|
-
Version: 0.0.2
|
4
|
-
Summary: PostgreSQL REST API framework for AWS Lambda functions
|
5
|
-
Project-URL: Homepage, https://github.com/mesogate/pygrestqlambda
|
6
|
-
Project-URL: Issues, https://github.com/mesogate/pygrestqlambda/issues
|
7
|
-
Author-email: Voquis Limited <opensource@voquis.com>
|
8
|
-
License-File: LICENSE
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
10
|
-
Classifier: Operating System :: OS Independent
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
12
|
-
Requires-Python: >=3.11
|
13
|
-
Requires-Dist: aws-xray-sdk
|
14
|
-
Requires-Dist: boto3
|
15
|
-
Provides-Extra: dev
|
16
|
-
Requires-Dist: build; extra == 'dev'
|
17
|
-
Requires-Dist: pylint; extra == 'dev'
|
18
|
-
Requires-Dist: pytest; extra == 'dev'
|
19
|
-
Requires-Dist: pytest-cov; extra == 'dev'
|
20
|
-
Requires-Dist: ruff; extra == 'dev'
|
21
|
-
Requires-Dist: twine; extra == 'dev'
|
22
|
-
Description-Content-Type: text/markdown
|
23
|
-
|
24
|
-
# Python PostgreSQL REST API framework for AWS Lambda functions
|
25
|
-
> [!NOTE]
|
26
|
-
> Project status: `Alpha`
|
27
|
-
|
28
|
-
A REST API web framework for persisting records in a PostgreSQL database.
|
29
|
-
|
30
|
-
## Supported features
|
31
|
-
- Automatic creation of `uid` fields
|
32
|
-
- Automatic setting of `created_at` and `last_updated_at` timestamps
|
33
|
-
- Automatic setting of `creator_uid` and `last_updater_uid`
|
34
|
-
- RDS with IAM credentials
|
35
|
-
|
36
|
-
## Examples
|
37
|
-
See [Examples docs directory](./docs/examples/)
|
File without changes
|
File without changes
|