nimble-aws 0.1.0__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.
- nimble_aws-0.1.0/PKG-INFO +18 -0
- nimble_aws-0.1.0/README.md +0 -0
- nimble_aws-0.1.0/nimble_aws/__init__.py +0 -0
- nimble_aws-0.1.0/nimble_aws/adapter/api_gateway.py +38 -0
- nimble_aws-0.1.0/nimble_aws/adapter/appsync.py +51 -0
- nimble_aws-0.1.0/nimble_aws/adapter/cognito.py +19 -0
- nimble_aws-0.1.0/nimble_aws/adapter/config.py +10 -0
- nimble_aws-0.1.0/nimble_aws/adapter/dynamodb.py +193 -0
- nimble_aws-0.1.0/nimble_aws/adapter/event_bridge.py +26 -0
- nimble_aws-0.1.0/nimble_aws/adapter/glue.py +51 -0
- nimble_aws-0.1.0/nimble_aws/adapter/lambda_serverless.py +26 -0
- nimble_aws-0.1.0/nimble_aws/adapter/s3.py +167 -0
- nimble_aws-0.1.0/nimble_aws/adapter/secrets_manager.py +20 -0
- nimble_aws-0.1.0/nimble_aws/adapter/session.py +22 -0
- nimble_aws-0.1.0/nimble_aws/adapter/sfn.py +38 -0
- nimble_aws-0.1.0/nimble_aws/adapter/sns.py +35 -0
- nimble_aws-0.1.0/nimble_aws/adapter/sqs.py +86 -0
- nimble_aws-0.1.0/nimble_aws/adapter/utils.py +9 -0
- nimble_aws-0.1.0/nimble_aws/middleware/api_gateway.py +101 -0
- nimble_aws-0.1.0/pyproject.toml +18 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nimble-aws
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Lucas Tiense Blazzi
|
|
6
|
+
Author-email: lucast.blazzi@gmail.com
|
|
7
|
+
Requires-Python: >=3.11,<4.0
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: aiobotocore (==2.13.1)
|
|
13
|
+
Requires-Dist: boto3 (>=1.17.47,<2.0.0)
|
|
14
|
+
Requires-Dist: pydantic (>=2.4.2,<3.0.0)
|
|
15
|
+
Requires-Dist: pydantic-settings (>=2.0.3,<3.0.0)
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
|
|
18
|
+
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from .config import env
|
|
4
|
+
from .session import Session
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ApiGateway(Session):
|
|
8
|
+
|
|
9
|
+
async def post_to_connection(self, connection_id, data, region=env.region):
|
|
10
|
+
"""
|
|
11
|
+
Send a message to a client's WebSocket connection.
|
|
12
|
+
:param connection_id: The ID of the connection.
|
|
13
|
+
:param data: The message to send.
|
|
14
|
+
:param region: The region of the connection (default: us-east-1).
|
|
15
|
+
:return: The response from the API Gateway Management API.
|
|
16
|
+
"""
|
|
17
|
+
session = await self.session
|
|
18
|
+
async with session.create_client("apigatewaymanagementapi", region_name=region) as client:
|
|
19
|
+
response = await client.post_to_connection(
|
|
20
|
+
ConnectionId=connection_id,
|
|
21
|
+
Data=json.dumps(data).encode("utf-8")
|
|
22
|
+
)
|
|
23
|
+
return response
|
|
24
|
+
|
|
25
|
+
async def get_api_key(self, api_key_id, region=env.region):
|
|
26
|
+
"""
|
|
27
|
+
Get an API key.
|
|
28
|
+
:param api_key_id: The ID of the API key
|
|
29
|
+
:param region: The region of the API key (default: us-east-1).
|
|
30
|
+
:return: The response from the API Gateway API.
|
|
31
|
+
"""
|
|
32
|
+
session = await self.session
|
|
33
|
+
async with session.create_client("apigateway", region_name=region) as client:
|
|
34
|
+
response = await client.get_api_key(
|
|
35
|
+
apiKey=api_key_id,
|
|
36
|
+
includeValue=True
|
|
37
|
+
)
|
|
38
|
+
return response
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from .config import env
|
|
2
|
+
from .session import Session
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AppSync(Session):
|
|
6
|
+
|
|
7
|
+
async def list_keys(self, api_id, max_results=10, region=env.region):
|
|
8
|
+
"""
|
|
9
|
+
List API keys.
|
|
10
|
+
:param api_id: The ID of the API.
|
|
11
|
+
:param max_results: The maximum number of results to return (default: 10).
|
|
12
|
+
:param region: The region of the API (default: us-east-1).
|
|
13
|
+
:return: The response from the AppSync API.
|
|
14
|
+
"""
|
|
15
|
+
session = await self.session
|
|
16
|
+
async with session.create_client(service_name="appsync", region_name=region) as client:
|
|
17
|
+
response = await client.list_api_keys(
|
|
18
|
+
apiId=api_id,
|
|
19
|
+
maxResults=max_results
|
|
20
|
+
)
|
|
21
|
+
return response
|
|
22
|
+
|
|
23
|
+
async def get_key(self, api_id, key_id, region=env.region):
|
|
24
|
+
"""
|
|
25
|
+
Get an API key.service_name=
|
|
26
|
+
:param api_id: The ID of the API.
|
|
27
|
+
:param key_id: The ID of the key.
|
|
28
|
+
:param region: The region of the API (default: us-east-1).
|
|
29
|
+
:return: The value of the provided API key id.
|
|
30
|
+
"""
|
|
31
|
+
session = await self.session
|
|
32
|
+
async with session.create_client(service_name="appsync", region_name=region) as client:
|
|
33
|
+
response = await client.list_api_keys(
|
|
34
|
+
apiId=api_id,
|
|
35
|
+
maxResults=100
|
|
36
|
+
)
|
|
37
|
+
api_key = next((key["id"] for key in response["apiKeys"] if key["id"] == key_id), response["apiKeys"][0]["id"])
|
|
38
|
+
return api_key
|
|
39
|
+
|
|
40
|
+
async def get_domain(self, api_id, region=env.region):
|
|
41
|
+
"""
|
|
42
|
+
Get the domain name of an API.
|
|
43
|
+
:param api_id: The ID of the API.
|
|
44
|
+
:param region: The region of the API (default: us-east-1).
|
|
45
|
+
:return: The domain name of the API.
|
|
46
|
+
"""
|
|
47
|
+
session = await self.session
|
|
48
|
+
async with session.create_client(service_name="appsync", region_name=region) as client:
|
|
49
|
+
response = await client.list_graphql_apis()
|
|
50
|
+
domain_name = next((domain["uris"]["GRAPHQL"] for domain in response["graphqlApis"] if domain["apiId"] == api_id), None)
|
|
51
|
+
return domain_name
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from .config import env
|
|
2
|
+
from .session import Session
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class Cognito(Session):
|
|
6
|
+
|
|
7
|
+
async def get_user(self, auth, region=env.region):
|
|
8
|
+
"""
|
|
9
|
+
Get a user.
|
|
10
|
+
:param auth: The user's authentication token.
|
|
11
|
+
:param region: The region of the user (default: us-east-1).
|
|
12
|
+
:return: The response from the Cognito Identity Provider API.
|
|
13
|
+
"""
|
|
14
|
+
session = await self.session
|
|
15
|
+
async with session.create_client(service_name="cognito-idp", region_name=region) as client:
|
|
16
|
+
response = await client.get_user(
|
|
17
|
+
AccessToken=auth
|
|
18
|
+
)
|
|
19
|
+
return response
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import asyncio
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from functional import seq
|
|
6
|
+
from boto3.dynamodb.types import TypeDeserializer, TypeSerializer
|
|
7
|
+
|
|
8
|
+
from .config import env
|
|
9
|
+
from .session import Session
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
serializer = TypeSerializer()
|
|
13
|
+
deserializer = TypeDeserializer()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DynamoDB(Session):
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def preprocess_item(item):
|
|
20
|
+
item = json.loads(json.dumps(item), parse_float=Decimal)
|
|
21
|
+
try:
|
|
22
|
+
item = {k: serializer.serialize(v) for k, v in item.items()}
|
|
23
|
+
except AttributeError:
|
|
24
|
+
return serializer.serialize(item)
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def process_item(item):
|
|
28
|
+
return {k: serializer.serialize(v) for k, v in item.items()}
|
|
29
|
+
|
|
30
|
+
async def describe_table(self, table_name, region=env.region):
|
|
31
|
+
"""
|
|
32
|
+
Describe a DynamoDB table.
|
|
33
|
+
:param table_name: The name of the table.
|
|
34
|
+
:param region: The region of the table (default: us-east-1).
|
|
35
|
+
:return: The response from the DynamoDB API.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
session = await self.session
|
|
39
|
+
async with session.create_client(service_name="dynamodb", region_name=region) as client:
|
|
40
|
+
response = await client.describe_table(
|
|
41
|
+
TableName=table_name
|
|
42
|
+
)
|
|
43
|
+
return response
|
|
44
|
+
|
|
45
|
+
async def put_items(self, table_name, items, region=env.region):
|
|
46
|
+
"""
|
|
47
|
+
Put items into a DynamoDB table.
|
|
48
|
+
:param table_name: The name of the table.
|
|
49
|
+
:param items: The items to put.
|
|
50
|
+
:param region: The region of the table (default: us-east-1).
|
|
51
|
+
:return: The response from the DynamoDB API.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
session = await self.session
|
|
55
|
+
async with session.create_client(service_name="dynamodb", region_name=region) as client:
|
|
56
|
+
tasks = [client.put_item(
|
|
57
|
+
TableName=table_name,
|
|
58
|
+
Item=self.preprocess_item(item)
|
|
59
|
+
) for item in items]
|
|
60
|
+
result = await asyncio.gather(*tasks)
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
async def delete_items(self, table_name, id_list, region=env.region):
|
|
64
|
+
"""
|
|
65
|
+
Delete items from a DynamoDB table.
|
|
66
|
+
:param table_name: The name of the table.
|
|
67
|
+
:param id_list: The IDs of the items to delete.
|
|
68
|
+
:param region: The region of the table (default: us-east-1).
|
|
69
|
+
:return: The response from the DynamoDB API.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
session = await self.session
|
|
73
|
+
async with session.create_client(service_name="dynamodb", region_name=region) as client:
|
|
74
|
+
tasks = [client.delete_item(
|
|
75
|
+
TableName=table_name,
|
|
76
|
+
Key=self.preprocess_item(id)
|
|
77
|
+
) for id in id_list]
|
|
78
|
+
result = await asyncio.gather(*tasks)
|
|
79
|
+
return result
|
|
80
|
+
|
|
81
|
+
async def get_item(self, table_name, id, region=env.region):
|
|
82
|
+
"""
|
|
83
|
+
Get an item from a DynamoDB table.
|
|
84
|
+
:param table_name: The name of the table.
|
|
85
|
+
:param id: The ID of the item to get.
|
|
86
|
+
:param region: The region of the table (default: us-east-1).
|
|
87
|
+
:return: The serialized item from the DynamoDB API.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
session = await self.session
|
|
91
|
+
async with session.create_client(service_name="dynamodb", region_name=region) as client:
|
|
92
|
+
response = await client.get_item(
|
|
93
|
+
TableName=table_name,
|
|
94
|
+
Key=self.preprocess_item(id)
|
|
95
|
+
)
|
|
96
|
+
result = self.process_item(response.get("Item", {}))
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
async def get_items(self, table_name, id_list, region=env.region):
|
|
100
|
+
"""
|
|
101
|
+
Get many items from a DynamoDB table.
|
|
102
|
+
:param table_name: The name of the table.
|
|
103
|
+
:param id_list: The IDs of the items to get.
|
|
104
|
+
:param region: The region of the table (default: us-east-1).
|
|
105
|
+
:return: The serialized items from the DynamoDB API.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
session = await self.session
|
|
109
|
+
async with session.create_client(service_name="dynamodb", region_name=region) as client:
|
|
110
|
+
tasks = [client.get_item(
|
|
111
|
+
TableName=table_name,
|
|
112
|
+
Key=self.preprocess_item(id)
|
|
113
|
+
) for id in id_list]
|
|
114
|
+
response = await asyncio.gather(*tasks)
|
|
115
|
+
result = [self.process_item(item.get("Item", {})) for item in response]
|
|
116
|
+
return result
|
|
117
|
+
|
|
118
|
+
async def index_query(self, table_mname, index, filters, region=env.region, **kwargs):
|
|
119
|
+
"""
|
|
120
|
+
Get many items from a DynamoDB table using an index.
|
|
121
|
+
:param table_mname: The name of the table.
|
|
122
|
+
:param index: The index definition to query (example: {"name": "metadata-index", "field": "metadata", "value": "audio"}).
|
|
123
|
+
:param filters: The filters to apply (example: {"deleted": false}).
|
|
124
|
+
:param region: The region of the table (default: us-east-1).
|
|
125
|
+
:param kwargs: Additional arguments to pass to the DynamoDB API.
|
|
126
|
+
:return: The serialized items from the DynamoDB API.
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
filters.update({index["field"]: index["value"]})
|
|
130
|
+
expression_attr = self.preprocess_item({f":{k}": filters[k] for k in filters.keys()})
|
|
131
|
+
|
|
132
|
+
session = await self.session
|
|
133
|
+
async with session.create_client(service_name="dynamodb", region_name=region) as client:
|
|
134
|
+
response = await client.query(
|
|
135
|
+
TableName=table_mname,
|
|
136
|
+
IndexName=index["name"],
|
|
137
|
+
KeyConditionExpression=f"#{index['field']} = :{index['field']}",
|
|
138
|
+
ExpressionAttributeNames={f"#{index['field']}": index["field"]},
|
|
139
|
+
ExpressionAttributeValues=expression_attr,
|
|
140
|
+
**kwargs
|
|
141
|
+
)
|
|
142
|
+
result = [self.process_item(item) for item in response.get("Items", [])]
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
async def scan_dynamo(self, table_name, region=env.region):
|
|
146
|
+
"""
|
|
147
|
+
Scan a DynamoDB table.
|
|
148
|
+
:param table_name: The name of the table.
|
|
149
|
+
:param region: The region of the table (default: us-east-1).
|
|
150
|
+
:return: The serialized items from the DynamoDB API
|
|
151
|
+
"""
|
|
152
|
+
|
|
153
|
+
session = await self.session
|
|
154
|
+
async with session.create_client(service_name="dynamodb", region_name=region) as client:
|
|
155
|
+
response = await client.scan(
|
|
156
|
+
TableName=table_name
|
|
157
|
+
)
|
|
158
|
+
result = list(seq(response.get("Items", [])).map(self.process_item))
|
|
159
|
+
while "LastEvaluatedKey" in response:
|
|
160
|
+
response = await client.scan(
|
|
161
|
+
TableName=table_name,
|
|
162
|
+
ExclusiveStartKey=response["LastEvaluatedKey"]
|
|
163
|
+
)
|
|
164
|
+
result.extend(list(seq(response.get("Items", [])).map(self.process_item)))
|
|
165
|
+
return result
|
|
166
|
+
|
|
167
|
+
async def update_items(self, table_name, items, key_fields, region=env.region):
|
|
168
|
+
"""
|
|
169
|
+
Update items in a DynamoDB table.
|
|
170
|
+
:param table_name: The name of the table.
|
|
171
|
+
:param items: The items to update.
|
|
172
|
+
:param key_fields: The fields to use as keys.
|
|
173
|
+
:param region: The region of the table (default: us-east-1).
|
|
174
|
+
:return: The serialized new items from the DynamoDB API.
|
|
175
|
+
"""
|
|
176
|
+
|
|
177
|
+
items = [self.preprocess_item(item) for item in items]
|
|
178
|
+
|
|
179
|
+
session = await self.session
|
|
180
|
+
async with session.create_client(service_name="dynamodb", region_name=region) as client:
|
|
181
|
+
tasks = [client.update_item(
|
|
182
|
+
TableName=table_name,
|
|
183
|
+
Key={k: item.pop(k) for k in key_fields},
|
|
184
|
+
ReturnValues="ALL_NEW",
|
|
185
|
+
AttributeValues={
|
|
186
|
+
**{k: {
|
|
187
|
+
"Action": "PUT",
|
|
188
|
+
"Value": v,
|
|
189
|
+
} for k, v in item.items()}}
|
|
190
|
+
) for item in items]
|
|
191
|
+
response = await asyncio.gather(*tasks)
|
|
192
|
+
result = [self.process_item(item.get("Attributes", {})) for item in response]
|
|
193
|
+
return result
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from .config import env
|
|
4
|
+
from .session import Session
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EventBridge(Session):
|
|
8
|
+
|
|
9
|
+
async def publish_events(self, messages, batch_size=10, region=env.region):
|
|
10
|
+
"""
|
|
11
|
+
Publish events to an EventBridge bus.
|
|
12
|
+
:param messages: The messages to publish.
|
|
13
|
+
:param batch_size: The number of messages to publish at a time (default: 10).
|
|
14
|
+
:param region: The region of the bus (default: us-east-1).
|
|
15
|
+
:return: The response from the EventBridge API.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
chunks = [messages[i:i + batch_size] for i in range(0, len(messages), batch_size)]
|
|
19
|
+
|
|
20
|
+
session = await self.session
|
|
21
|
+
async with session.create_client(service_name="events", region_name=region) as client:
|
|
22
|
+
tasks = [client.put_events(
|
|
23
|
+
Entries=chunk
|
|
24
|
+
) for chunk in chunks]
|
|
25
|
+
result = await asyncio.gather(*tasks)
|
|
26
|
+
return result
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from .config import env
|
|
4
|
+
from .session import Session
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Glue(Session):
|
|
8
|
+
|
|
9
|
+
async def get_jobs(self, region=env.region):
|
|
10
|
+
"""
|
|
11
|
+
Get a list of jobs.
|
|
12
|
+
:param region: The region of the jobs (default: us-east-1).
|
|
13
|
+
:return: The response from the Glue API.
|
|
14
|
+
"""
|
|
15
|
+
session = await self.session
|
|
16
|
+
async with session.create_client(service_name="glue", region_name=region) as client:
|
|
17
|
+
response = await client.get_jobs()
|
|
18
|
+
return response
|
|
19
|
+
|
|
20
|
+
async def get_job_runs(self, job_name, max_results, region=env.region):
|
|
21
|
+
"""
|
|
22
|
+
Get a list of job runs.
|
|
23
|
+
:param job_name: The name of the job.
|
|
24
|
+
:param max_results: The maximum number of results to return.
|
|
25
|
+
:param region: The region of the job (default: us-east-1).
|
|
26
|
+
:return: The response from the Glue API.
|
|
27
|
+
"""
|
|
28
|
+
session = await self.session
|
|
29
|
+
async with session.create_client(service_name="glue", region_name=region) as client:
|
|
30
|
+
response = await client.get_job_runs(
|
|
31
|
+
JobName=job_name,
|
|
32
|
+
MaxResults=max_results
|
|
33
|
+
)
|
|
34
|
+
return response
|
|
35
|
+
|
|
36
|
+
async def get_jobs_runs(self, job_names, max_results, region=env.region):
|
|
37
|
+
"""
|
|
38
|
+
Get a list of job runs for multiple jobs.
|
|
39
|
+
:param job_names: The names of the jobs.
|
|
40
|
+
:param max_results: The maximum number of results to return.
|
|
41
|
+
:param region: The region of the jobs (default: us-east-1).
|
|
42
|
+
:return: The response from the Glue API.
|
|
43
|
+
"""
|
|
44
|
+
session = await self.session
|
|
45
|
+
async with session.create_client(service_name="glue", region_name=region) as client:
|
|
46
|
+
tasks = [client.get_job_runs(
|
|
47
|
+
JobName=job_name,
|
|
48
|
+
MaxResults=max_results
|
|
49
|
+
) for job_name in job_names]
|
|
50
|
+
response = await asyncio.gather(*tasks)
|
|
51
|
+
return response
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from .config import env
|
|
4
|
+
from .session import Session
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Lambda(Session):
|
|
8
|
+
|
|
9
|
+
async def invoke(self, function_name, payload, invocation_type="RequestResponse", region=env.region):
|
|
10
|
+
"""
|
|
11
|
+
Invoke a Lambda function.
|
|
12
|
+
:param function_name: The name of the function.
|
|
13
|
+
:param payload: The payload to send to the function.
|
|
14
|
+
:param invocation_type: The invocation type, valid values are Event | RequestResponse | DryRun (default: RequestResponse).
|
|
15
|
+
:param region: The region of the function (default: us-east-1).
|
|
16
|
+
:return: The response payload from the Lambda API.
|
|
17
|
+
"""
|
|
18
|
+
session = await self.session
|
|
19
|
+
async with session.create_client("lambda", region_name=region) as client:
|
|
20
|
+
response = await client.invoke(
|
|
21
|
+
FunctionName=function_name,
|
|
22
|
+
InvocationType=invocation_type,
|
|
23
|
+
Payload=json.dumps(payload).encode("utf-8")
|
|
24
|
+
)
|
|
25
|
+
return json.loads(response["Payload"].read().decode("utf-8")) if invocation_type == "RequestResponse" else response["Payload"].read()
|
|
26
|
+
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from botocore.client import Config
|
|
3
|
+
|
|
4
|
+
from .config import env
|
|
5
|
+
from .session import Session
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class S3(Session):
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def extract_filename_from_files_list(files_list):
|
|
12
|
+
return [f"{file['Name']}/{content['Key']}" for file in files_list for content in file["Contents"]]
|
|
13
|
+
|
|
14
|
+
async def get_files(self, bucket, paths, decode=True, region=env.region):
|
|
15
|
+
"""
|
|
16
|
+
Get a list of files from an S3 bucket.
|
|
17
|
+
:param bucket: The name of the bucket.
|
|
18
|
+
:param paths: The paths to the files.
|
|
19
|
+
:param decode: Whether to decode the files (default: True).
|
|
20
|
+
:param region: The region of the bucket (default: us-east-1).
|
|
21
|
+
:return: The files.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
session = await self.session
|
|
25
|
+
async with session.create_client(service_name="s3", region_name=region) as client:
|
|
26
|
+
tasks = [client.get_object(Bucket=bucket, Key=path) for path in paths]
|
|
27
|
+
results = await asyncio.gather(*tasks)
|
|
28
|
+
files = [await result["Body"].read() for result in results]
|
|
29
|
+
return [file.decode("utf-8") for file in files] if decode else files
|
|
30
|
+
|
|
31
|
+
async def list_files(self, bucket, prefix_list, region=env.region):
|
|
32
|
+
"""
|
|
33
|
+
List files in an S3 bucket.
|
|
34
|
+
:param bucket: The name of the bucket.
|
|
35
|
+
:param prefix_list: The prefixes to list.
|
|
36
|
+
:param region: The region of the bucket (default: us-east-1).
|
|
37
|
+
:return: The response from the S3 API.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
session = await self.session
|
|
41
|
+
async with session.create_client(service_name="s3", region_name=region) as client:
|
|
42
|
+
tasks = [client.list_objects_v2(Bucket=bucket, Prefix=prefix, Delimiter="/") for prefix in prefix_list]
|
|
43
|
+
results = await asyncio.gather(*tasks)
|
|
44
|
+
return results
|
|
45
|
+
|
|
46
|
+
async def put_file(self, bucket, key, body, region=env.region):
|
|
47
|
+
"""
|
|
48
|
+
Put a file into an S3 bucket.
|
|
49
|
+
:param bucket: The name of the bucket.
|
|
50
|
+
:param key: The key of the file.
|
|
51
|
+
:param body: The body of the file.
|
|
52
|
+
:param region: The region of the bucket (default: us-east-1).
|
|
53
|
+
:return: The response from the S3 API.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
session = await self.session
|
|
57
|
+
async with session.create_client(service_name="s3", region_name=region) as client:
|
|
58
|
+
response = await client.put_object(Bucket=bucket, Key=key, Body=body)
|
|
59
|
+
return response
|
|
60
|
+
|
|
61
|
+
async def put_files(self, bucket, objects, region=env.region):
|
|
62
|
+
"""
|
|
63
|
+
Put files into an S3 bucket.
|
|
64
|
+
:param bucket: The name of the bucket.
|
|
65
|
+
:param objects: The objects to put.
|
|
66
|
+
:param region: The region of the bucket (default: us-east-1).
|
|
67
|
+
:return: The response from the S3 API.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
session = await self.session
|
|
71
|
+
async with session.create_client(service_name="s3", region_name=region) as client:
|
|
72
|
+
tasks = [client.put_object(Bucket=bucket, Key=object["key"], Body=object["body"]) for object in objects]
|
|
73
|
+
results = await asyncio.gather(*tasks)
|
|
74
|
+
return results
|
|
75
|
+
|
|
76
|
+
async def put_files_with_content_type(self, bucket, objects, region=env.region):
|
|
77
|
+
"""
|
|
78
|
+
Put files into an S3 bucket with content type.
|
|
79
|
+
:param bucket: The name of the bucket.
|
|
80
|
+
:param objects: The objects to put.
|
|
81
|
+
:param region: The region of the bucket (default: us-east-1).
|
|
82
|
+
:return: The response from the S3 API.
|
|
83
|
+
"""
|
|
84
|
+
|
|
85
|
+
session = await self.session
|
|
86
|
+
async with session.create_client(service_name="s3", region_name=region) as client:
|
|
87
|
+
tasks = [client.put_object(Bucket=bucket, Key=object["key"], Body=object["body"], ContentType=object["type"]) for object in objects]
|
|
88
|
+
results = await asyncio.gather(*tasks)
|
|
89
|
+
return results
|
|
90
|
+
|
|
91
|
+
async def put_files_with_metadata(self, bucket, objects, region=env.region):
|
|
92
|
+
"""
|
|
93
|
+
Put files into an S3 bucket with metadata.
|
|
94
|
+
:param bucket: The name of the bucket.
|
|
95
|
+
:param objects: The objects to put.
|
|
96
|
+
:param region: The region of the bucket (default: us-east-1).
|
|
97
|
+
:return: The response from the S3 API.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
session = await self.session
|
|
101
|
+
async with session.create_client(service_name="s3", region_name=region) as client:
|
|
102
|
+
tasks = [client.put_object(Bucket=bucket, Key=object["key"], Body=object["body"], Metadata=object["metadata"]) for object in objects]
|
|
103
|
+
results = await asyncio.gather(*tasks)
|
|
104
|
+
return results
|
|
105
|
+
|
|
106
|
+
async def head_files(self, bucket, keys, region=env.region):
|
|
107
|
+
"""
|
|
108
|
+
Get the metadata of files in an S3 bucket.
|
|
109
|
+
:param bucket: The name of the bucket.
|
|
110
|
+
:param keys: The keys of the files.
|
|
111
|
+
:param region: The region of the bucket (default: us-east-1).
|
|
112
|
+
:return: The response from the S3 API.
|
|
113
|
+
"""
|
|
114
|
+
|
|
115
|
+
session = await self.session
|
|
116
|
+
async with session.create_client(service_name="s3", region_name=region) as client:
|
|
117
|
+
tasks = [client.head_object(Bucket=bucket, Key=key) for key in keys]
|
|
118
|
+
results = await asyncio.gather(*tasks)
|
|
119
|
+
return results
|
|
120
|
+
|
|
121
|
+
async def generate_pre_signed_url(self, bucket, item, expiration, region=env.region):
|
|
122
|
+
"""
|
|
123
|
+
Generate a pre-signed URL for a file in an S3 bucket.
|
|
124
|
+
:param bucket: The name of the bucket.
|
|
125
|
+
:param item: The item to generate a URL for.
|
|
126
|
+
:param expiration: The expiration time of the URL.
|
|
127
|
+
:param region: The region of the bucket (default: us-east-1).
|
|
128
|
+
:return: The pre-signed URL.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
session = await self.session
|
|
132
|
+
async with session.create_client(service_name="s3", region_name=region, config=Config(signature_version="s3v4")) as client:
|
|
133
|
+
response = await client.generate_presigned_url(
|
|
134
|
+
ClientMethod="get_object",
|
|
135
|
+
Params={
|
|
136
|
+
"Bucket": bucket,
|
|
137
|
+
"Key": item["key"],
|
|
138
|
+
"ReponseContentDisposition": f"attachment; filename={item['name']}"
|
|
139
|
+
},
|
|
140
|
+
ExpiresIn=expiration
|
|
141
|
+
)
|
|
142
|
+
return response
|
|
143
|
+
|
|
144
|
+
async def generate_pre_signed_urls(self, bucket, data, expiration, region=env.region):
|
|
145
|
+
"""
|
|
146
|
+
Generate pre-signed URLs for files in an S3 bucket.
|
|
147
|
+
:param bucket: The name of the bucket.
|
|
148
|
+
:param data: The data to generate URLs for.
|
|
149
|
+
:param expiration: The expiration time of the URLs.
|
|
150
|
+
:param region: The region of the bucket (default: us-east-1).
|
|
151
|
+
:return: The pre-signed URLs.
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
session = await self.session
|
|
155
|
+
async with session.create_client(service_name="s3", region_name=region, config=Config(signature_version="s3v4")) as client:
|
|
156
|
+
tasks = [
|
|
157
|
+
client.generate_presigned_url(
|
|
158
|
+
ClientMethod="get_object",
|
|
159
|
+
Params={
|
|
160
|
+
"Bucket": bucket,
|
|
161
|
+
"Key": item["key"],
|
|
162
|
+
"ReponseContentDisposition": f"attachment; filename={item['name']}"
|
|
163
|
+
},
|
|
164
|
+
ExpiresIn=expiration
|
|
165
|
+
) for item in data]
|
|
166
|
+
results = await asyncio.gather(*tasks)
|
|
167
|
+
return results
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from .config import env
|
|
4
|
+
from .session import Session
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SecretsManager(Session):
|
|
8
|
+
|
|
9
|
+
async def get_secret(self, secret_id, region=env.region):
|
|
10
|
+
"""
|
|
11
|
+
Get a secret.
|
|
12
|
+
:param secret_id: The ID of the secret.
|
|
13
|
+
:param region: The region of the secret (default: us-east-1).
|
|
14
|
+
:return: The requested secret id value.
|
|
15
|
+
"""
|
|
16
|
+
session = await self.session
|
|
17
|
+
async with session.create_client(service_name="secretsmanager", region_name=region) as client:
|
|
18
|
+
response = await client.get_secret_value(SecretId=secret_id)
|
|
19
|
+
secrets = json.loads(response["SecretString"])
|
|
20
|
+
return secrets
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from aiobotocore.session import get_session
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Singleton(type):
|
|
5
|
+
_instances = {}
|
|
6
|
+
|
|
7
|
+
def __call__(cls, *args, **kwargs):
|
|
8
|
+
if cls not in cls._instances:
|
|
9
|
+
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
|
10
|
+
return cls._instances[cls]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Session(metaclass=Singleton):
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
self._session = None
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def session(self):
|
|
20
|
+
if self._session is None:
|
|
21
|
+
self._session = get_session()
|
|
22
|
+
return self._session
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
from .config import env
|
|
5
|
+
from .utils import flatten
|
|
6
|
+
from .session import Session
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SFN(Session):
|
|
10
|
+
|
|
11
|
+
async def run_sync_execution(self, state_machine_arn, payload, region=env.region):
|
|
12
|
+
"""
|
|
13
|
+
Start an execution.
|
|
14
|
+
:param state_machine_arn: The ARN of the state machine.
|
|
15
|
+
:param input: The input to the execution.
|
|
16
|
+
:param region: The region of the state machine (default: us-east-1).
|
|
17
|
+
:return: The output from the Step Functions API.
|
|
18
|
+
"""
|
|
19
|
+
session = await self.session
|
|
20
|
+
async with session.create_client(service_name="stepfunctions", region_name=region) as client:
|
|
21
|
+
response = await client.start_sync_execution(
|
|
22
|
+
stateMachineArn=state_machine_arn,
|
|
23
|
+
input=json.dumps(payload)
|
|
24
|
+
)
|
|
25
|
+
return json.loads(response.get("output") or response.get("cause"))
|
|
26
|
+
|
|
27
|
+
async def run_sync_executions(self, data, region=env.region):
|
|
28
|
+
"""
|
|
29
|
+
Start multiple executions.
|
|
30
|
+
:param data: The data to execute.
|
|
31
|
+
:param region: The region of the state machine (default: us-east-1).
|
|
32
|
+
:return: The output from the Step Functions API.
|
|
33
|
+
"""
|
|
34
|
+
session = await self.session
|
|
35
|
+
async with session.create_client(service_name="stepfunctions", region_name=region) as client:
|
|
36
|
+
tasks = [client.start_sync_execution(stateMachineArn=item["arn"], input=json.dumps(item["input"])) for item in data]
|
|
37
|
+
responses = await asyncio.gather(*tasks)
|
|
38
|
+
return flatten([json.loads(response.get("output") or response.get("cause")) for response in responses])
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
|
|
3
|
+
from .config import env
|
|
4
|
+
from .session import Session
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SNS(Session):
|
|
8
|
+
|
|
9
|
+
async def publish_messages_to_topic(self, topic_arn, messages, region=env.region):
|
|
10
|
+
"""
|
|
11
|
+
Publish messages to a topic.
|
|
12
|
+
:param topic_arn: The ARN of the topic.
|
|
13
|
+
:param messages: The messages to publish.
|
|
14
|
+
:param region: The region of the topic (default: us-east-1).
|
|
15
|
+
:return: The response from the SNS API.
|
|
16
|
+
"""
|
|
17
|
+
session = await self.session
|
|
18
|
+
async with session.create_client(service_name="sns", region_name=region) as client:
|
|
19
|
+
tasks = [client.publish(TopicArn=topic_arn, Message=message) for message in messages]
|
|
20
|
+
responses = await asyncio.gather(*tasks)
|
|
21
|
+
return responses
|
|
22
|
+
|
|
23
|
+
async def publish_messages_with_attributes_to_topic(self, topic_arn, messages, region=env.region):
|
|
24
|
+
"""
|
|
25
|
+
Publish messages with attributes to a topic.
|
|
26
|
+
:param topic_arn: The ARN of the topic.
|
|
27
|
+
:param messages: The messages to publish.
|
|
28
|
+
:param region: The region of the topic (default: us-east-1).
|
|
29
|
+
:return: The response from the SNS API.
|
|
30
|
+
"""
|
|
31
|
+
session = await self.session
|
|
32
|
+
async with session.create_client(service_name="sns", region_name=region) as client:
|
|
33
|
+
tasks = [client.publish(TopicArn=topic_arn, Message=message["message"], MessageAttributes=message["attributes"]) for message in messages]
|
|
34
|
+
responses = await asyncio.gather(*tasks)
|
|
35
|
+
return responses
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import asyncio
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from .config import env
|
|
6
|
+
from .session import Session
|
|
7
|
+
from .utils import chunk_array
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SQS(Session):
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def get_messages(result):
|
|
14
|
+
return [json.loads(message["Body"]) for message in result.get("Messages", [])]
|
|
15
|
+
|
|
16
|
+
async def publish_messages_to_queue(self, queue_url, messages, batch_size=10, region=env.region):
|
|
17
|
+
"""
|
|
18
|
+
Publish messages to a queue.
|
|
19
|
+
:param queue_url: The URL of the queue.
|
|
20
|
+
:param messages: The messages to publish.
|
|
21
|
+
:param batch_size: The number of messages to publish in a batch (default: 10).
|
|
22
|
+
:param region: The region of the queue (default: us-east-1).
|
|
23
|
+
:return: The response from the SQS API.
|
|
24
|
+
"""
|
|
25
|
+
chunks = chunk_array(messages, batch_size)
|
|
26
|
+
|
|
27
|
+
session = await self.session
|
|
28
|
+
async with session.create_client(service_name="sqs", region_name=region) as client:
|
|
29
|
+
tasks = [
|
|
30
|
+
client.send_message_batch(
|
|
31
|
+
QueueUrl=queue_url,
|
|
32
|
+
Entries=[{"Id": str(uuid4()), "MessageBody": json.dumps(message)} for message in chunk]
|
|
33
|
+
) for chunk in chunks]
|
|
34
|
+
responses = await asyncio.gather(*tasks)
|
|
35
|
+
return responses
|
|
36
|
+
|
|
37
|
+
async def read_ten_messages(self, queue_url, region=env.region, **kwargs):
|
|
38
|
+
"""
|
|
39
|
+
Read ten messages from a queue.
|
|
40
|
+
:param queue_url: The URL of the queue.
|
|
41
|
+
:param region: The region of the queue (default: us-east-1).
|
|
42
|
+
:return: The messages from the SQS API.
|
|
43
|
+
"""
|
|
44
|
+
session = await self.session
|
|
45
|
+
async with session.create_client(service_name="sqs", region_name=region) as client:
|
|
46
|
+
response = await client.receive_message(
|
|
47
|
+
QueueUrl=queue_url,
|
|
48
|
+
MaxNumberOfMessages=10,
|
|
49
|
+
**kwargs
|
|
50
|
+
)
|
|
51
|
+
return self.get_messages(response)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def read_messages(self, queue_url, num_messages=10, region=env.region, **kwargs):
|
|
55
|
+
"""
|
|
56
|
+
Read messages from a queue.
|
|
57
|
+
:param queue_url: The URL of the queue.
|
|
58
|
+
:param num_messages: The number of messages to read (default: 10).
|
|
59
|
+
:param region: The region of the queue (default: us-east-1).
|
|
60
|
+
:return: The messages from the SQS API.
|
|
61
|
+
"""
|
|
62
|
+
messages_read = 0
|
|
63
|
+
all_messages = list()
|
|
64
|
+
messages = await self.read_ten_messages(queue_url, region, **kwargs)
|
|
65
|
+
all_messages.extend(messages)
|
|
66
|
+
messages_read += len(messages)
|
|
67
|
+
while messages and messages_read < num_messages:
|
|
68
|
+
messages = await self.read_ten_messages(queue_url, region, **kwargs)
|
|
69
|
+
all_messages.extend(messages)
|
|
70
|
+
messages_read += len(messages)
|
|
71
|
+
return all_messages
|
|
72
|
+
|
|
73
|
+
async def read_all_messages(self, queue_url, region=env.region, **kwargs):
|
|
74
|
+
"""
|
|
75
|
+
Read all messages from a queue.
|
|
76
|
+
:param queue_url: The URL of the queue.
|
|
77
|
+
:param region: The region of the queue (default: us-east-1).
|
|
78
|
+
:return: The messages from the SQS API.
|
|
79
|
+
"""
|
|
80
|
+
all_messages = list()
|
|
81
|
+
messages = await self.read_ten_messages(queue_url, region, **kwargs)
|
|
82
|
+
all_messages.extend(messages)
|
|
83
|
+
while messages:
|
|
84
|
+
messages = await self.read_ten_messages(queue_url, region, **kwargs)
|
|
85
|
+
all_messages.extend(messages)
|
|
86
|
+
return all_messages
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from decimal import Decimal
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Encoder(json.JSONEncoder):
|
|
7
|
+
|
|
8
|
+
def defaul(self, obj):
|
|
9
|
+
if isinstance(obj, Decimal):
|
|
10
|
+
if obj % 1 == 0:
|
|
11
|
+
return int(obj)
|
|
12
|
+
return float(obj)
|
|
13
|
+
elif isinstance(obj, datetime):
|
|
14
|
+
return obj.strftime("%Y-%m-%d")
|
|
15
|
+
return str(obj)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class API:
|
|
19
|
+
|
|
20
|
+
def __init__(self, event, **args):
|
|
21
|
+
self.event = event
|
|
22
|
+
self.extra_params = args
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def get_default(data, field, default):
|
|
26
|
+
return data[field] if data.get(field) else default
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def body(self):
|
|
30
|
+
raw_body = self.get_default(self.event, field="body", default={})
|
|
31
|
+
return json.loads(raw_body)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def headers(self):
|
|
35
|
+
raw_headers = self.get_default(self.event, field="headers", default={})
|
|
36
|
+
return {k.lower(): v for k, v in raw_headers.items()}
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def path_parameters(self):
|
|
40
|
+
return self.get_default(self.event, field="pathParameters", default={})
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def path(self):
|
|
44
|
+
return self.event["requestContext"]["resourcePath"] if self.event["requestContext"].get("resourcePath") else self.event["requestContext"]["routeKey"].split(" ")[-1]
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def query_string_parameters(self):
|
|
48
|
+
return self.get_default(self.event, field="queryStringParameters", default={})
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def request(self):
|
|
52
|
+
return {
|
|
53
|
+
"headers": self.headers,
|
|
54
|
+
"path": self.path,
|
|
55
|
+
"path_parameters": self.path_parameters,
|
|
56
|
+
"query_string_parameters": self.query_string_parameters,
|
|
57
|
+
"body": self.body
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def response_headers(self):
|
|
62
|
+
cors_headers = {}
|
|
63
|
+
origin = self.get_default(self.headers, field="origin", default="")
|
|
64
|
+
allowed = "*" in self.extra_params["cors-allowed-origin"]
|
|
65
|
+
if origin is self.extra_params["cors-allowed-origin"] or allowed:
|
|
66
|
+
cors_headers = {
|
|
67
|
+
"Access-Control-Allow-Origin": origin,
|
|
68
|
+
"Access-Control-Allow-Credentials": "true",
|
|
69
|
+
"Access-Control-Allow-Methods": self.extra_params["cors-allowed-methods"],
|
|
70
|
+
"Access-Control-Allow-Headers": self.extra_params["cors-allowed-headers"]
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
**cors_headers,
|
|
74
|
+
"X-XSS-Protection": "1; mode=block",
|
|
75
|
+
"X-Frame-Options": "DENY",
|
|
76
|
+
"X-Content-Type-Options": "nosniff",
|
|
77
|
+
"Referrer-Policy": "strict-origin-when-cross-origin",
|
|
78
|
+
"Strict-Transport-Security": "max-age=31536000; includeSubDomains"
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def error_response(self, exception):
|
|
83
|
+
if exception.__class__.__name__ != "":
|
|
84
|
+
exception.args = ({"pt": "Erro", "en": "Error"}, 500)
|
|
85
|
+
return {
|
|
86
|
+
"statusCode": exception.args[1],
|
|
87
|
+
"headers": self.response_headers,
|
|
88
|
+
"body": {
|
|
89
|
+
"error": {
|
|
90
|
+
"type": type(exception).__name__,
|
|
91
|
+
"description": exception.args[0]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
def success_response(self, data):
|
|
97
|
+
return {
|
|
98
|
+
"statusCode": 200,
|
|
99
|
+
"headers": self.response_headers,
|
|
100
|
+
"body": json.dumps(data, cls=Encoder, ensure_ascii=False)
|
|
101
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[tool.poetry]
|
|
2
|
+
name = "nimble-aws"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = ""
|
|
5
|
+
authors = ["Lucas Tiense Blazzi <lucast.blazzi@gmail.com>"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
|
|
8
|
+
[tool.poetry.dependencies]
|
|
9
|
+
python = "^3.11"
|
|
10
|
+
pydantic = "^2.4.2"
|
|
11
|
+
pydantic-settings = "^2.0.3"
|
|
12
|
+
aiobotocore = "2.13.1"
|
|
13
|
+
boto3 = "^1.17.47"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["poetry-core"]
|
|
18
|
+
build-backend = "poetry.core.masonry.api"
|