universal-mcp-applications 0.1.33__py3-none-any.whl → 0.1.39rc16__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.
Potentially problematic release.
This version of universal-mcp-applications might be problematic. Click here for more details.
- universal_mcp/applications/BEST_PRACTICES.md +1 -1
- universal_mcp/applications/ahrefs/app.py +92 -238
- universal_mcp/applications/airtable/app.py +36 -135
- universal_mcp/applications/apollo/app.py +124 -477
- universal_mcp/applications/asana/app.py +605 -1755
- universal_mcp/applications/aws_s3/app.py +63 -119
- universal_mcp/applications/bill/app.py +644 -2055
- universal_mcp/applications/box/app.py +1246 -4159
- universal_mcp/applications/braze/app.py +410 -1476
- universal_mcp/applications/browser_use/README.md +15 -1
- universal_mcp/applications/browser_use/__init__.py +1 -0
- universal_mcp/applications/browser_use/app.py +91 -26
- universal_mcp/applications/cal_com_v2/app.py +207 -625
- universal_mcp/applications/calendly/app.py +103 -242
- universal_mcp/applications/canva/app.py +75 -140
- universal_mcp/applications/clickup/app.py +331 -798
- universal_mcp/applications/coda/app.py +240 -520
- universal_mcp/applications/confluence/app.py +497 -1285
- universal_mcp/applications/contentful/app.py +40 -155
- universal_mcp/applications/crustdata/app.py +44 -123
- universal_mcp/applications/dialpad/app.py +451 -924
- universal_mcp/applications/digitalocean/app.py +2071 -6082
- universal_mcp/applications/domain_checker/app.py +3 -54
- universal_mcp/applications/e2b/app.py +17 -68
- universal_mcp/applications/elevenlabs/README.md +27 -3
- universal_mcp/applications/elevenlabs/app.py +741 -74
- universal_mcp/applications/exa/README.md +8 -4
- universal_mcp/applications/exa/app.py +415 -186
- universal_mcp/applications/falai/README.md +5 -7
- universal_mcp/applications/falai/app.py +156 -232
- universal_mcp/applications/figma/app.py +91 -175
- universal_mcp/applications/file_system/app.py +2 -13
- universal_mcp/applications/firecrawl/app.py +198 -176
- universal_mcp/applications/fireflies/app.py +59 -281
- universal_mcp/applications/fpl/app.py +92 -529
- universal_mcp/applications/fpl/utils/fixtures.py +15 -49
- universal_mcp/applications/fpl/utils/helper.py +25 -89
- universal_mcp/applications/fpl/utils/league_utils.py +20 -64
- universal_mcp/applications/ghost_content/app.py +70 -179
- universal_mcp/applications/github/app.py +30 -67
- universal_mcp/applications/gong/app.py +142 -302
- universal_mcp/applications/google_calendar/app.py +26 -78
- universal_mcp/applications/google_docs/README.md +15 -14
- universal_mcp/applications/google_docs/app.py +103 -206
- universal_mcp/applications/google_drive/app.py +194 -793
- universal_mcp/applications/google_gemini/app.py +68 -59
- universal_mcp/applications/google_mail/README.md +1 -0
- universal_mcp/applications/google_mail/app.py +93 -214
- universal_mcp/applications/google_searchconsole/app.py +25 -58
- universal_mcp/applications/google_sheet/README.md +2 -1
- universal_mcp/applications/google_sheet/app.py +226 -624
- universal_mcp/applications/google_sheet/helper.py +26 -53
- universal_mcp/applications/hashnode/app.py +57 -269
- universal_mcp/applications/heygen/README.md +10 -32
- universal_mcp/applications/heygen/app.py +339 -811
- universal_mcp/applications/http_tools/app.py +10 -32
- universal_mcp/applications/hubspot/README.md +1 -1
- universal_mcp/applications/hubspot/app.py +7508 -99
- universal_mcp/applications/jira/app.py +2419 -8334
- universal_mcp/applications/klaviyo/app.py +739 -1621
- universal_mcp/applications/linkedin/README.md +18 -1
- universal_mcp/applications/linkedin/app.py +729 -251
- universal_mcp/applications/mailchimp/app.py +696 -1851
- universal_mcp/applications/markitdown/app.py +8 -20
- universal_mcp/applications/miro/app.py +333 -815
- universal_mcp/applications/ms_teams/app.py +420 -1407
- universal_mcp/applications/neon/app.py +144 -250
- universal_mcp/applications/notion/app.py +38 -53
- universal_mcp/applications/onedrive/app.py +26 -48
- universal_mcp/applications/openai/app.py +43 -166
- universal_mcp/applications/outlook/README.md +22 -9
- universal_mcp/applications/outlook/app.py +403 -141
- universal_mcp/applications/perplexity/README.md +2 -1
- universal_mcp/applications/perplexity/app.py +161 -20
- universal_mcp/applications/pipedrive/app.py +1021 -3331
- universal_mcp/applications/posthog/app.py +272 -541
- universal_mcp/applications/reddit/app.py +65 -164
- universal_mcp/applications/resend/app.py +72 -139
- universal_mcp/applications/retell/app.py +23 -50
- universal_mcp/applications/rocketlane/app.py +252 -965
- universal_mcp/applications/scraper/app.py +114 -142
- universal_mcp/applications/semanticscholar/app.py +36 -78
- universal_mcp/applications/semrush/app.py +44 -78
- universal_mcp/applications/sendgrid/app.py +826 -1576
- universal_mcp/applications/sentry/app.py +444 -1079
- universal_mcp/applications/serpapi/app.py +44 -146
- universal_mcp/applications/sharepoint/app.py +27 -49
- universal_mcp/applications/shopify/app.py +1748 -4486
- universal_mcp/applications/shortcut/app.py +275 -536
- universal_mcp/applications/slack/app.py +43 -125
- universal_mcp/applications/spotify/app.py +206 -405
- universal_mcp/applications/supabase/app.py +174 -283
- universal_mcp/applications/tavily/app.py +2 -2
- universal_mcp/applications/trello/app.py +853 -2816
- universal_mcp/applications/twilio/app.py +27 -62
- universal_mcp/applications/twitter/api_segments/compliance_api.py +4 -14
- universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +6 -18
- universal_mcp/applications/twitter/api_segments/likes_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/lists_api.py +5 -15
- universal_mcp/applications/twitter/api_segments/trends_api.py +1 -3
- universal_mcp/applications/twitter/api_segments/tweets_api.py +9 -31
- universal_mcp/applications/twitter/api_segments/usage_api.py +1 -5
- universal_mcp/applications/twitter/api_segments/users_api.py +14 -42
- universal_mcp/applications/whatsapp/app.py +35 -186
- universal_mcp/applications/whatsapp/audio.py +2 -6
- universal_mcp/applications/whatsapp/whatsapp.py +17 -51
- universal_mcp/applications/whatsapp_business/app.py +86 -299
- universal_mcp/applications/wrike/app.py +80 -153
- universal_mcp/applications/yahoo_finance/app.py +19 -65
- universal_mcp/applications/youtube/app.py +120 -306
- universal_mcp/applications/zenquotes/app.py +3 -3
- {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/METADATA +4 -2
- {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/RECORD +115 -119
- {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/WHEEL +1 -1
- universal_mcp/applications/hubspot/api_segments/__init__.py +0 -0
- universal_mcp/applications/hubspot/api_segments/api_segment_base.py +0 -54
- universal_mcp/applications/hubspot/api_segments/crm_api.py +0 -7337
- universal_mcp/applications/hubspot/api_segments/marketing_api.py +0 -1467
- {universal_mcp_applications-0.1.33.dist-info → universal_mcp_applications-0.1.39rc16.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import json
|
|
3
3
|
from typing import Any
|
|
4
|
-
|
|
5
4
|
import boto3
|
|
6
5
|
from botocore.exceptions import ClientError
|
|
7
6
|
from universal_mcp.applications.application import BaseApplication
|
|
@@ -26,36 +25,34 @@ class AwsS3App(BaseApplication):
|
|
|
26
25
|
self._client = client
|
|
27
26
|
self.integration = integration
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
def client(self):
|
|
28
|
+
async def get_client(self):
|
|
31
29
|
"""
|
|
32
30
|
Lazily initializes and returns a cached Boto3 S3 client instance. It retrieves authentication credentials from the associated `integration` object. This property is the core mechanism used by all other methods in the class to interact with AWS S3, raising an error if the integration is not set.
|
|
33
31
|
"""
|
|
34
32
|
if not self.integration:
|
|
35
33
|
raise ValueError("Integration not initialized")
|
|
36
34
|
if not self._client:
|
|
37
|
-
credentials = self.integration.
|
|
35
|
+
credentials = await self.integration.get_credentials_async()
|
|
38
36
|
credentials = {
|
|
39
|
-
"aws_access_key_id": credentials.get("access_key_id")
|
|
40
|
-
or credentials.get("
|
|
41
|
-
"aws_secret_access_key": credentials.get("secret_access_key")
|
|
42
|
-
or credentials.get("password"),
|
|
37
|
+
"aws_access_key_id": credentials.get("access_key_id") or credentials.get("username"),
|
|
38
|
+
"aws_secret_access_key": credentials.get("secret_access_key") or credentials.get("password"),
|
|
43
39
|
"region_name": credentials.get("region"),
|
|
44
40
|
}
|
|
45
41
|
self._client = boto3.client("s3", **credentials)
|
|
46
42
|
return self._client
|
|
47
43
|
|
|
48
|
-
def list_buckets(self) -> list[str]:
|
|
44
|
+
async def list_buckets(self) -> list[str]:
|
|
49
45
|
"""
|
|
50
46
|
Retrieves all S3 buckets accessible by the configured AWS credentials. It calls the S3 API's list_buckets operation and processes the response to return a simple list containing just the names of the buckets.
|
|
51
47
|
|
|
52
48
|
Returns:
|
|
53
49
|
List[str]: A list of bucket names.
|
|
54
50
|
"""
|
|
55
|
-
|
|
51
|
+
client = await self.get_client()
|
|
52
|
+
response = await client.list_buckets()
|
|
56
53
|
return [bucket["Name"] for bucket in response["Buckets"]]
|
|
57
54
|
|
|
58
|
-
def create_bucket(self, bucket_name: str, region: str | None = None) -> bool:
|
|
55
|
+
async def create_bucket(self, bucket_name: str, region: str | None = None) -> bool:
|
|
59
56
|
"""
|
|
60
57
|
Creates a new Amazon S3 bucket with a specified name and optional region. Returns `True` upon successful creation, or `False` if an AWS client error, such as a naming conflict or permission issue, occurs.
|
|
61
58
|
|
|
@@ -69,18 +66,16 @@ class AwsS3App(BaseApplication):
|
|
|
69
66
|
important
|
|
70
67
|
"""
|
|
71
68
|
try:
|
|
69
|
+
client = await self.get_client()
|
|
72
70
|
if region:
|
|
73
|
-
|
|
74
|
-
Bucket=bucket_name,
|
|
75
|
-
CreateBucketConfiguration={"LocationConstraint": region},
|
|
76
|
-
)
|
|
71
|
+
await client.create_bucket(Bucket=bucket_name, CreateBucketConfiguration={"LocationConstraint": region})
|
|
77
72
|
else:
|
|
78
|
-
|
|
73
|
+
await client.create_bucket(Bucket=bucket_name)
|
|
79
74
|
return True
|
|
80
75
|
except ClientError:
|
|
81
76
|
return False
|
|
82
77
|
|
|
83
|
-
def delete_bucket(self, bucket_name: str) -> bool:
|
|
78
|
+
async def delete_bucket(self, bucket_name: str) -> bool:
|
|
84
79
|
"""
|
|
85
80
|
Deletes a specified S3 bucket. The operation requires the bucket to be empty to succeed. It returns `True` if the bucket is successfully deleted and `False` if an error occurs, such as the bucket not being found or containing objects.
|
|
86
81
|
|
|
@@ -93,12 +88,13 @@ class AwsS3App(BaseApplication):
|
|
|
93
88
|
important
|
|
94
89
|
"""
|
|
95
90
|
try:
|
|
96
|
-
self.
|
|
91
|
+
client = await self.get_client()
|
|
92
|
+
await client.delete_bucket(Bucket=bucket_name)
|
|
97
93
|
return True
|
|
98
94
|
except ClientError:
|
|
99
95
|
return False
|
|
100
96
|
|
|
101
|
-
def get_bucket_policy(self, bucket_name: str) -> dict[str, Any]:
|
|
97
|
+
async def get_bucket_policy(self, bucket_name: str) -> dict[str, Any]:
|
|
102
98
|
"""
|
|
103
99
|
Retrieves the IAM resource policy for a specified S3 bucket, parsing the JSON string into a dictionary. If the operation fails due to permissions or a non-existent policy, it returns an error dictionary. This complements `put_bucket_policy`, which applies a new policy.
|
|
104
100
|
|
|
@@ -111,12 +107,13 @@ class AwsS3App(BaseApplication):
|
|
|
111
107
|
important
|
|
112
108
|
"""
|
|
113
109
|
try:
|
|
114
|
-
|
|
110
|
+
client = await self.get_client()
|
|
111
|
+
response = await client.get_bucket_policy(Bucket=bucket_name)
|
|
115
112
|
return json.loads(response["Policy"])
|
|
116
113
|
except ClientError as e:
|
|
117
114
|
return {"error": str(e)}
|
|
118
115
|
|
|
119
|
-
def set_bucket_policy(self, bucket_name: str, policy: dict[str, Any]) -> bool:
|
|
116
|
+
async def set_bucket_policy(self, bucket_name: str, policy: dict[str, Any]) -> bool:
|
|
120
117
|
"""
|
|
121
118
|
Applies or replaces the access policy for a specified S3 bucket. The function accepts the policy as a dictionary, converts it to JSON, and assigns it to the bucket. This write operation is the counterpart to `get_bucket_policy` and returns `True` on success.
|
|
122
119
|
|
|
@@ -130,14 +127,13 @@ class AwsS3App(BaseApplication):
|
|
|
130
127
|
important
|
|
131
128
|
"""
|
|
132
129
|
try:
|
|
133
|
-
|
|
130
|
+
client = await self.get_client()
|
|
131
|
+
await client.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy))
|
|
134
132
|
return True
|
|
135
133
|
except ClientError:
|
|
136
134
|
return False
|
|
137
135
|
|
|
138
|
-
def list_subdirectories(
|
|
139
|
-
self, bucket_name: str, prefix: str | None = None
|
|
140
|
-
) -> list[str]:
|
|
136
|
+
async def list_subdirectories(self, bucket_name: str, prefix: str | None = None) -> list[str]:
|
|
141
137
|
"""
|
|
142
138
|
Lists immediate subdirectories (common prefixes) within an S3 bucket. If a prefix is provided, it returns subdirectories under that path; otherwise, it lists top-level directories. This function specifically lists folders, distinguishing it from `list_objects`, which lists files.
|
|
143
139
|
|
|
@@ -150,23 +146,20 @@ class AwsS3App(BaseApplication):
|
|
|
150
146
|
Tags:
|
|
151
147
|
important
|
|
152
148
|
"""
|
|
153
|
-
paginator = self.
|
|
149
|
+
paginator = (await self.get_client()).get_paginator("list_objects_v2")
|
|
154
150
|
operation_parameters = {"Bucket": bucket_name}
|
|
155
151
|
if prefix:
|
|
156
152
|
operation_parameters["Prefix"] = prefix
|
|
157
153
|
operation_parameters["Delimiter"] = "/"
|
|
158
154
|
else:
|
|
159
155
|
operation_parameters["Delimiter"] = "/"
|
|
160
|
-
|
|
161
156
|
prefixes = []
|
|
162
157
|
for page in paginator.paginate(**operation_parameters):
|
|
163
158
|
for cp in page.get("CommonPrefixes", []):
|
|
164
159
|
prefixes.append(cp.get("Prefix"))
|
|
165
160
|
return prefixes
|
|
166
161
|
|
|
167
|
-
def create_prefix(
|
|
168
|
-
self, bucket_name: str, prefix_name: str, parent_prefix: str | None = None
|
|
169
|
-
) -> bool:
|
|
162
|
+
async def create_prefix(self, bucket_name: str, prefix_name: str, parent_prefix: str | None = None) -> bool:
|
|
170
163
|
"""
|
|
171
164
|
Creates a prefix (folder) in an S3 bucket, optionally nested under a parent prefix. It simulates a directory by creating a zero-byte object with a key ending in a slash ('/'), distinguishing it from put_object, which uploads content.
|
|
172
165
|
|
|
@@ -184,10 +177,10 @@ class AwsS3App(BaseApplication):
|
|
|
184
177
|
key = f"{parent_prefix.rstrip('/')}/{prefix_name}/"
|
|
185
178
|
else:
|
|
186
179
|
key = f"{prefix_name}/"
|
|
187
|
-
self.
|
|
180
|
+
await (await self.get_client()).put_object(Bucket=bucket_name, Key=key)
|
|
188
181
|
return True
|
|
189
182
|
|
|
190
|
-
def list_objects(self, bucket_name: str, prefix: str) -> list[dict[str, Any]]:
|
|
183
|
+
async def list_objects(self, bucket_name: str, prefix: str) -> list[dict[str, Any]]:
|
|
191
184
|
"""
|
|
192
185
|
Paginates through and lists all objects within a specified S3 bucket prefix. It returns a curated list of metadata for each object (key, name, size, last modified), excluding folder placeholders. This function specifically lists files, distinguishing it from `list_prefixes` which lists folders.
|
|
193
186
|
|
|
@@ -200,7 +193,8 @@ class AwsS3App(BaseApplication):
|
|
|
200
193
|
Tags:
|
|
201
194
|
important
|
|
202
195
|
"""
|
|
203
|
-
|
|
196
|
+
client = await self.get_client()
|
|
197
|
+
paginator = client.get_paginator("list_objects_v2")
|
|
204
198
|
operation_parameters = {"Bucket": bucket_name, "Prefix": prefix}
|
|
205
199
|
objects = []
|
|
206
200
|
for page in paginator.paginate(**operation_parameters):
|
|
@@ -218,9 +212,7 @@ class AwsS3App(BaseApplication):
|
|
|
218
212
|
)
|
|
219
213
|
return objects
|
|
220
214
|
|
|
221
|
-
def put_text_object(
|
|
222
|
-
self, bucket_name: str, prefix: str, object_name: str, content: str
|
|
223
|
-
) -> bool:
|
|
215
|
+
async def put_text_object(self, bucket_name: str, prefix: str, object_name: str, content: str) -> bool:
|
|
224
216
|
"""
|
|
225
217
|
Uploads string content to create an object in a specified S3 bucket and prefix. The content is UTF-8 encoded before being written. This method is for text, distinguishing it from `put_object_from_base64` which handles binary data.
|
|
226
218
|
|
|
@@ -236,14 +228,11 @@ class AwsS3App(BaseApplication):
|
|
|
236
228
|
important
|
|
237
229
|
"""
|
|
238
230
|
key = f"{prefix.rstrip('/')}/{object_name}" if prefix else object_name
|
|
239
|
-
self.
|
|
240
|
-
|
|
241
|
-
)
|
|
231
|
+
client = await self.get_client()
|
|
232
|
+
await client.put_object(Bucket=bucket_name, Key=key, Body=content.encode("utf-8"))
|
|
242
233
|
return True
|
|
243
234
|
|
|
244
|
-
def put_object_from_base64(
|
|
245
|
-
self, bucket_name: str, prefix: str, object_name: str, base64_content: str
|
|
246
|
-
) -> bool:
|
|
235
|
+
async def put_object_from_base64(self, bucket_name: str, prefix: str, object_name: str, base64_content: str) -> bool:
|
|
247
236
|
"""
|
|
248
237
|
Decodes a base64 string into binary data and uploads it as an object to a specified S3 location. This method is designed for binary files, differentiating it from `put_object` which handles plain text content. Returns true on success.
|
|
249
238
|
|
|
@@ -261,12 +250,13 @@ class AwsS3App(BaseApplication):
|
|
|
261
250
|
try:
|
|
262
251
|
key = f"{prefix.rstrip('/')}/{object_name}" if prefix else object_name
|
|
263
252
|
content = base64.b64decode(base64_content)
|
|
264
|
-
|
|
253
|
+
client = await self.get_client()
|
|
254
|
+
await client.put_object(Bucket=bucket_name, Key=key, Body=content)
|
|
265
255
|
return True
|
|
266
256
|
except Exception:
|
|
267
257
|
return False
|
|
268
258
|
|
|
269
|
-
def get_object_with_content(self, bucket_name: str, key: str) -> dict[str, Any]:
|
|
259
|
+
async def get_object_with_content(self, bucket_name: str, key: str) -> dict[str, Any]:
|
|
270
260
|
"""
|
|
271
261
|
Retrieves an S3 object's content. It decodes text files as UTF-8 or encodes binary files as base64 based on the object's key. This function downloads the full object body, unlike `get_object_metadata` which only fetches metadata without content, returning the content, name, size, and type.
|
|
272
262
|
|
|
@@ -280,26 +270,18 @@ class AwsS3App(BaseApplication):
|
|
|
280
270
|
important
|
|
281
271
|
"""
|
|
282
272
|
try:
|
|
283
|
-
|
|
273
|
+
client = await self.get_client()
|
|
274
|
+
obj = await client.get_object(Bucket=bucket_name, Key=key)
|
|
284
275
|
content = obj["Body"].read()
|
|
285
|
-
is_text_file = key.lower().endswith(
|
|
286
|
-
(".txt", ".csv", ".json", ".xml", ".html", ".md", ".js", ".css", ".py")
|
|
287
|
-
)
|
|
276
|
+
is_text_file = key.lower().endswith((".txt", ".csv", ".json", ".xml", ".html", ".md", ".js", ".css", ".py"))
|
|
288
277
|
content_dict = (
|
|
289
|
-
{"content": content.decode("utf-8")}
|
|
290
|
-
if is_text_file
|
|
291
|
-
else {"content_base64": base64.b64encode(content).decode("ascii")}
|
|
278
|
+
{"content": content.decode("utf-8")} if is_text_file else {"content_base64": base64.b64encode(content).decode("ascii")}
|
|
292
279
|
)
|
|
293
|
-
return {
|
|
294
|
-
"name": key.split("/")[-1],
|
|
295
|
-
"content_type": "text" if is_text_file else "binary",
|
|
296
|
-
**content_dict,
|
|
297
|
-
"size": len(content),
|
|
298
|
-
}
|
|
280
|
+
return {"name": key.split("/")[-1], "content_type": "text" if is_text_file else "binary", **content_dict, "size": len(content)}
|
|
299
281
|
except ClientError as e:
|
|
300
282
|
return {"error": str(e)}
|
|
301
283
|
|
|
302
|
-
def get_object_metadata(self, bucket_name: str, key: str) -> dict[str, Any]:
|
|
284
|
+
async def get_object_metadata(self, bucket_name: str, key: str) -> dict[str, Any]:
|
|
303
285
|
"""
|
|
304
286
|
Efficiently retrieves metadata for a specified S3 object, such as size and last modified date, without downloading its content. This function uses a HEAD request, making it faster than `get_object_content` for accessing object properties. Returns a dictionary of metadata or an error message on failure.
|
|
305
287
|
|
|
@@ -313,14 +295,13 @@ class AwsS3App(BaseApplication):
|
|
|
313
295
|
important
|
|
314
296
|
"""
|
|
315
297
|
try:
|
|
316
|
-
|
|
298
|
+
client = await self.get_client()
|
|
299
|
+
response = await client.head_object(Bucket=bucket_name, Key=key)
|
|
317
300
|
return {
|
|
318
301
|
"key": key,
|
|
319
302
|
"name": key.split("/")[-1],
|
|
320
303
|
"size": response.get("ContentLength", 0),
|
|
321
|
-
"last_modified": response.get("LastModified", "").isoformat()
|
|
322
|
-
if response.get("LastModified")
|
|
323
|
-
else "",
|
|
304
|
+
"last_modified": response.get("LastModified", "").isoformat() if response.get("LastModified") else "",
|
|
324
305
|
"content_type": response.get("ContentType", ""),
|
|
325
306
|
"etag": response.get("ETag", ""),
|
|
326
307
|
"metadata": response.get("Metadata", {}),
|
|
@@ -328,9 +309,7 @@ class AwsS3App(BaseApplication):
|
|
|
328
309
|
except ClientError as e:
|
|
329
310
|
return {"error": str(e)}
|
|
330
311
|
|
|
331
|
-
def copy_object(
|
|
332
|
-
self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str
|
|
333
|
-
) -> bool:
|
|
312
|
+
async def copy_object(self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str) -> bool:
|
|
334
313
|
"""
|
|
335
314
|
Copies an S3 object from a specified source location to a destination, which can be in the same or a different bucket. Unlike `move_object`, the original object remains at the source after the copy operation. It returns `True` for a successful operation.
|
|
336
315
|
|
|
@@ -347,16 +326,12 @@ class AwsS3App(BaseApplication):
|
|
|
347
326
|
"""
|
|
348
327
|
try:
|
|
349
328
|
copy_source = {"Bucket": source_bucket, "Key": source_key}
|
|
350
|
-
self.client.copy_object(
|
|
351
|
-
CopySource=copy_source, Bucket=dest_bucket, Key=dest_key
|
|
352
|
-
)
|
|
329
|
+
self.client.copy_object(CopySource=copy_source, Bucket=dest_bucket, Key=dest_key)
|
|
353
330
|
return True
|
|
354
331
|
except ClientError:
|
|
355
332
|
return False
|
|
356
333
|
|
|
357
|
-
def move_object(
|
|
358
|
-
self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str
|
|
359
|
-
) -> bool:
|
|
334
|
+
async def move_object(self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str) -> bool:
|
|
360
335
|
"""
|
|
361
336
|
Moves an S3 object from a source to a destination. This is achieved by first copying the object to the new location and subsequently deleting the original. The move can occur within the same bucket or between different ones, returning `True` on success.
|
|
362
337
|
|
|
@@ -371,11 +346,11 @@ class AwsS3App(BaseApplication):
|
|
|
371
346
|
Tags:
|
|
372
347
|
important
|
|
373
348
|
"""
|
|
374
|
-
if self.copy_object(source_bucket, source_key, dest_bucket, dest_key):
|
|
349
|
+
if await self.copy_object(source_bucket, source_key, dest_bucket, dest_key):
|
|
375
350
|
return self.delete_object(source_bucket, source_key)
|
|
376
351
|
return False
|
|
377
352
|
|
|
378
|
-
def delete_single_object(self, bucket_name: str, key: str) -> bool:
|
|
353
|
+
async def delete_single_object(self, bucket_name: str, key: str) -> bool:
|
|
379
354
|
"""
|
|
380
355
|
Deletes a single, specified object from an S3 bucket using its key. Returns `True` if successful, otherwise `False`. For bulk deletions in a single request, the `delete_objects` function should be used instead.
|
|
381
356
|
|
|
@@ -394,7 +369,7 @@ class AwsS3App(BaseApplication):
|
|
|
394
369
|
except ClientError:
|
|
395
370
|
return False
|
|
396
371
|
|
|
397
|
-
def delete_objects(self, bucket_name: str, keys: list[str]) -> dict[str, Any]:
|
|
372
|
+
async def delete_objects(self, bucket_name: str, keys: list[str]) -> dict[str, Any]:
|
|
398
373
|
"""
|
|
399
374
|
Performs a bulk deletion of objects from a specified S3 bucket in a single request. Given a list of keys, it returns a dictionary detailing successful deletions and any errors. This method is the batch-processing counterpart to the singular `delete_object` function, designed for efficiency.
|
|
400
375
|
|
|
@@ -409,9 +384,8 @@ class AwsS3App(BaseApplication):
|
|
|
409
384
|
"""
|
|
410
385
|
try:
|
|
411
386
|
delete_dict = {"Objects": [{"Key": key} for key in keys]}
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
)
|
|
387
|
+
client = await self.get_client()
|
|
388
|
+
response = await client.delete_objects(Bucket=bucket_name, Delete=delete_dict)
|
|
415
389
|
return {
|
|
416
390
|
"deleted": [obj.get("Key") for obj in response.get("Deleted", [])],
|
|
417
391
|
"errors": [obj for obj in response.get("Errors", [])],
|
|
@@ -419,13 +393,7 @@ class AwsS3App(BaseApplication):
|
|
|
419
393
|
except ClientError as e:
|
|
420
394
|
return {"error": str(e)}
|
|
421
395
|
|
|
422
|
-
def generate_presigned_url(
|
|
423
|
-
self,
|
|
424
|
-
bucket_name: str,
|
|
425
|
-
key: str,
|
|
426
|
-
expiration: int = 3600,
|
|
427
|
-
http_method: str = "GET",
|
|
428
|
-
) -> str:
|
|
396
|
+
async def generate_presigned_url(self, bucket_name: str, key: str, expiration: int = 3600, http_method: str = "GET") -> str:
|
|
429
397
|
"""
|
|
430
398
|
Generates a temporary, secure URL for a specific S3 object. This URL grants time-limited permissions for actions like GET, PUT, or DELETE, expiring after a defined period. It allows object access without sharing permanent AWS credentials.
|
|
431
399
|
|
|
@@ -441,28 +409,17 @@ class AwsS3App(BaseApplication):
|
|
|
441
409
|
important
|
|
442
410
|
"""
|
|
443
411
|
try:
|
|
444
|
-
method_map = {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
"
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
response = self.client.generate_presigned_url(
|
|
451
|
-
method_map.get(http_method.upper(), "get_object"),
|
|
452
|
-
Params={"Bucket": bucket_name, "Key": key},
|
|
453
|
-
ExpiresIn=expiration,
|
|
412
|
+
method_map = {"GET": "get_object", "PUT": "put_object", "DELETE": "delete_object"}
|
|
413
|
+
client = await self.get_client()
|
|
414
|
+
response = await client.generate_presigned_url(
|
|
415
|
+
method_map.get(http_method.upper(), "get_object"), Params={"Bucket": bucket_name, "Key": key}, ExpiresIn=expiration
|
|
454
416
|
)
|
|
455
417
|
return response
|
|
456
418
|
except ClientError as e:
|
|
457
419
|
return f"Error: {str(e)}"
|
|
458
420
|
|
|
459
|
-
def search_objects(
|
|
460
|
-
self,
|
|
461
|
-
bucket_name: str,
|
|
462
|
-
prefix: str = "",
|
|
463
|
-
name_pattern: str = "",
|
|
464
|
-
min_size: int | None = None,
|
|
465
|
-
max_size: int | None = None,
|
|
421
|
+
async def search_objects(
|
|
422
|
+
self, bucket_name: str, prefix: str = "", name_pattern: str = "", min_size: int | None = None, max_size: int | None = None
|
|
466
423
|
) -> list[dict[str, Any]]:
|
|
467
424
|
"""
|
|
468
425
|
Filters objects within an S3 bucket and prefix based on a name pattern and size range. It retrieves all objects via `list_objects` and then applies the specified criteria client-side, returning a refined list of matching objects and their metadata.
|
|
@@ -479,25 +436,19 @@ class AwsS3App(BaseApplication):
|
|
|
479
436
|
Tags:
|
|
480
437
|
important
|
|
481
438
|
"""
|
|
482
|
-
all_objects = self.list_objects(bucket_name, prefix)
|
|
439
|
+
all_objects = await self.list_objects(bucket_name, prefix)
|
|
483
440
|
filtered_objects = []
|
|
484
|
-
|
|
485
441
|
for obj in all_objects:
|
|
486
|
-
# Filter by name pattern
|
|
487
442
|
if name_pattern and name_pattern.lower() not in obj["name"].lower():
|
|
488
443
|
continue
|
|
489
|
-
|
|
490
|
-
# Filter by size
|
|
491
444
|
if min_size and obj["size"] < min_size:
|
|
492
445
|
continue
|
|
493
446
|
if max_size and obj["size"] > max_size:
|
|
494
447
|
continue
|
|
495
|
-
|
|
496
448
|
filtered_objects.append(obj)
|
|
497
|
-
|
|
498
449
|
return filtered_objects
|
|
499
450
|
|
|
500
|
-
def get_storage_summary(self, bucket_name: str, prefix: str = "") -> dict[str, Any]:
|
|
451
|
+
async def get_storage_summary(self, bucket_name: str, prefix: str = "") -> dict[str, Any]:
|
|
501
452
|
"""
|
|
502
453
|
Calculates and returns statistics for an S3 bucket or prefix. The result includes the total number of objects, their combined size in bytes, and a human-readable string representation of the size (e.g., '15.2 MB').
|
|
503
454
|
|
|
@@ -510,11 +461,9 @@ class AwsS3App(BaseApplication):
|
|
|
510
461
|
Tags:
|
|
511
462
|
important
|
|
512
463
|
"""
|
|
513
|
-
objects = self.list_objects(bucket_name, prefix)
|
|
514
|
-
total_size = sum(obj["size"] for obj in objects)
|
|
464
|
+
objects = await self.list_objects(bucket_name, prefix)
|
|
465
|
+
total_size = sum((obj["size"] for obj in objects))
|
|
515
466
|
object_count = len(objects)
|
|
516
|
-
|
|
517
|
-
# Convert to human-readable format
|
|
518
467
|
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
|
519
468
|
if total_size < 1024.0:
|
|
520
469
|
human_size = f"{total_size:.2f} {unit}"
|
|
@@ -522,12 +471,7 @@ class AwsS3App(BaseApplication):
|
|
|
522
471
|
total_size /= 1024.0
|
|
523
472
|
else:
|
|
524
473
|
human_size = f"{total_size:.2f} PB"
|
|
525
|
-
|
|
526
|
-
return {
|
|
527
|
-
"total_size_bytes": sum(obj["size"] for obj in objects),
|
|
528
|
-
"human_readable_size": human_size,
|
|
529
|
-
"object_count": object_count,
|
|
530
|
-
}
|
|
474
|
+
return {"total_size_bytes": sum((obj["size"] for obj in objects)), "human_readable_size": human_size, "object_count": object_count}
|
|
531
475
|
|
|
532
476
|
def list_tools(self):
|
|
533
477
|
return [
|