universal-mcp-applications 0.1.1__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.
- universal_mcp/applications/ahrefs/README.md +51 -0
- universal_mcp/applications/ahrefs/__init__.py +1 -0
- universal_mcp/applications/ahrefs/app.py +2291 -0
- universal_mcp/applications/airtable/README.md +22 -0
- universal_mcp/applications/airtable/__init__.py +1 -0
- universal_mcp/applications/airtable/app.py +479 -0
- universal_mcp/applications/apollo/README.md +44 -0
- universal_mcp/applications/apollo/__init__.py +1 -0
- universal_mcp/applications/apollo/app.py +1847 -0
- universal_mcp/applications/asana/README.md +199 -0
- universal_mcp/applications/asana/__init__.py +1 -0
- universal_mcp/applications/asana/app.py +9509 -0
- universal_mcp/applications/aws-s3/README.md +0 -0
- universal_mcp/applications/aws-s3/__init__.py +1 -0
- universal_mcp/applications/aws-s3/app.py +552 -0
- universal_mcp/applications/bill/README.md +0 -0
- universal_mcp/applications/bill/__init__.py +1 -0
- universal_mcp/applications/bill/app.py +8705 -0
- universal_mcp/applications/box/README.md +307 -0
- universal_mcp/applications/box/__init__.py +1 -0
- universal_mcp/applications/box/app.py +15987 -0
- universal_mcp/applications/braze/README.md +106 -0
- universal_mcp/applications/braze/__init__.py +1 -0
- universal_mcp/applications/braze/app.py +4754 -0
- universal_mcp/applications/cal-com-v2/README.md +150 -0
- universal_mcp/applications/cal-com-v2/__init__.py +1 -0
- universal_mcp/applications/cal-com-v2/app.py +5541 -0
- universal_mcp/applications/calendly/README.md +53 -0
- universal_mcp/applications/calendly/__init__.py +1 -0
- universal_mcp/applications/calendly/app.py +1436 -0
- universal_mcp/applications/canva/README.md +43 -0
- universal_mcp/applications/canva/__init__.py +1 -0
- universal_mcp/applications/canva/app.py +941 -0
- universal_mcp/applications/clickup/README.md +135 -0
- universal_mcp/applications/clickup/__init__.py +1 -0
- universal_mcp/applications/clickup/app.py +5009 -0
- universal_mcp/applications/coda/README.md +108 -0
- universal_mcp/applications/coda/__init__.py +1 -0
- universal_mcp/applications/coda/app.py +3671 -0
- universal_mcp/applications/confluence/README.md +198 -0
- universal_mcp/applications/confluence/__init__.py +1 -0
- universal_mcp/applications/confluence/app.py +6273 -0
- universal_mcp/applications/contentful/README.md +17 -0
- universal_mcp/applications/contentful/__init__.py +1 -0
- universal_mcp/applications/contentful/app.py +364 -0
- universal_mcp/applications/crustdata/README.md +25 -0
- universal_mcp/applications/crustdata/__init__.py +1 -0
- universal_mcp/applications/crustdata/app.py +586 -0
- universal_mcp/applications/dialpad/README.md +202 -0
- universal_mcp/applications/dialpad/__init__.py +1 -0
- universal_mcp/applications/dialpad/app.py +5949 -0
- universal_mcp/applications/digitalocean/README.md +463 -0
- universal_mcp/applications/digitalocean/__init__.py +1 -0
- universal_mcp/applications/digitalocean/app.py +20835 -0
- universal_mcp/applications/domain-checker/README.md +13 -0
- universal_mcp/applications/domain-checker/__init__.py +1 -0
- universal_mcp/applications/domain-checker/app.py +265 -0
- universal_mcp/applications/e2b/README.md +12 -0
- universal_mcp/applications/e2b/__init__.py +1 -0
- universal_mcp/applications/e2b/app.py +187 -0
- universal_mcp/applications/elevenlabs/README.md +88 -0
- universal_mcp/applications/elevenlabs/__init__.py +1 -0
- universal_mcp/applications/elevenlabs/app.py +3235 -0
- universal_mcp/applications/exa/README.md +15 -0
- universal_mcp/applications/exa/__init__.py +1 -0
- universal_mcp/applications/exa/app.py +221 -0
- universal_mcp/applications/falai/README.md +17 -0
- universal_mcp/applications/falai/__init__.py +1 -0
- universal_mcp/applications/falai/app.py +331 -0
- universal_mcp/applications/figma/README.md +49 -0
- universal_mcp/applications/figma/__init__.py +1 -0
- universal_mcp/applications/figma/app.py +1090 -0
- universal_mcp/applications/firecrawl/README.md +20 -0
- universal_mcp/applications/firecrawl/__init__.py +1 -0
- universal_mcp/applications/firecrawl/app.py +514 -0
- universal_mcp/applications/fireflies/README.md +25 -0
- universal_mcp/applications/fireflies/__init__.py +1 -0
- universal_mcp/applications/fireflies/app.py +506 -0
- universal_mcp/applications/fpl/README.md +23 -0
- universal_mcp/applications/fpl/__init__.py +1 -0
- universal_mcp/applications/fpl/app.py +1327 -0
- universal_mcp/applications/fpl/utils/api.py +142 -0
- universal_mcp/applications/fpl/utils/fixtures.py +629 -0
- universal_mcp/applications/fpl/utils/helper.py +982 -0
- universal_mcp/applications/fpl/utils/league_utils.py +546 -0
- universal_mcp/applications/fpl/utils/position_utils.py +68 -0
- universal_mcp/applications/ghost-content/README.md +25 -0
- universal_mcp/applications/ghost-content/__init__.py +1 -0
- universal_mcp/applications/ghost-content/app.py +654 -0
- universal_mcp/applications/github/README.md +1049 -0
- universal_mcp/applications/github/__init__.py +1 -0
- universal_mcp/applications/github/app.py +50600 -0
- universal_mcp/applications/gong/README.md +63 -0
- universal_mcp/applications/gong/__init__.py +1 -0
- universal_mcp/applications/gong/app.py +2297 -0
- universal_mcp/applications/google-ads/README.md +0 -0
- universal_mcp/applications/google-ads/__init__.py +1 -0
- universal_mcp/applications/google-ads/app.py +23 -0
- universal_mcp/applications/google-calendar/README.md +21 -0
- universal_mcp/applications/google-calendar/__init__.py +1 -0
- universal_mcp/applications/google-calendar/app.py +574 -0
- universal_mcp/applications/google-docs/README.md +25 -0
- universal_mcp/applications/google-docs/__init__.py +1 -0
- universal_mcp/applications/google-docs/app.py +760 -0
- universal_mcp/applications/google-drive/README.md +68 -0
- universal_mcp/applications/google-drive/__init__.py +1 -0
- universal_mcp/applications/google-drive/app.py +4936 -0
- universal_mcp/applications/google-gemini/README.md +25 -0
- universal_mcp/applications/google-gemini/__init__.py +1 -0
- universal_mcp/applications/google-gemini/app.py +663 -0
- universal_mcp/applications/google-mail/README.md +31 -0
- universal_mcp/applications/google-mail/__init__.py +1 -0
- universal_mcp/applications/google-mail/app.py +1354 -0
- universal_mcp/applications/google-searchconsole/README.md +21 -0
- universal_mcp/applications/google-searchconsole/__init__.py +1 -0
- universal_mcp/applications/google-searchconsole/app.py +320 -0
- universal_mcp/applications/google-sheet/README.md +36 -0
- universal_mcp/applications/google-sheet/__init__.py +1 -0
- universal_mcp/applications/google-sheet/app.py +1941 -0
- universal_mcp/applications/hashnode/README.md +20 -0
- universal_mcp/applications/hashnode/__init__.py +1 -0
- universal_mcp/applications/hashnode/app.py +455 -0
- universal_mcp/applications/heygen/README.md +44 -0
- universal_mcp/applications/heygen/__init__.py +1 -0
- universal_mcp/applications/heygen/app.py +961 -0
- universal_mcp/applications/http-tools/README.md +16 -0
- universal_mcp/applications/http-tools/__init__.py +1 -0
- universal_mcp/applications/http-tools/app.py +153 -0
- universal_mcp/applications/hubspot/README.md +239 -0
- universal_mcp/applications/hubspot/__init__.py +1 -0
- universal_mcp/applications/hubspot/app.py +416 -0
- universal_mcp/applications/jira/README.md +600 -0
- universal_mcp/applications/jira/__init__.py +1 -0
- universal_mcp/applications/jira/app.py +28804 -0
- universal_mcp/applications/klaviyo/README.md +313 -0
- universal_mcp/applications/klaviyo/__init__.py +1 -0
- universal_mcp/applications/klaviyo/app.py +11236 -0
- universal_mcp/applications/linkedin/README.md +15 -0
- universal_mcp/applications/linkedin/__init__.py +1 -0
- universal_mcp/applications/linkedin/app.py +243 -0
- universal_mcp/applications/mailchimp/README.md +281 -0
- universal_mcp/applications/mailchimp/__init__.py +1 -0
- universal_mcp/applications/mailchimp/app.py +10937 -0
- universal_mcp/applications/markitdown/README.md +12 -0
- universal_mcp/applications/markitdown/__init__.py +1 -0
- universal_mcp/applications/markitdown/app.py +63 -0
- universal_mcp/applications/miro/README.md +151 -0
- universal_mcp/applications/miro/__init__.py +1 -0
- universal_mcp/applications/miro/app.py +5429 -0
- universal_mcp/applications/ms-teams/README.md +42 -0
- universal_mcp/applications/ms-teams/__init__.py +1 -0
- universal_mcp/applications/ms-teams/app.py +1823 -0
- universal_mcp/applications/neon/README.md +74 -0
- universal_mcp/applications/neon/__init__.py +1 -0
- universal_mcp/applications/neon/app.py +2018 -0
- universal_mcp/applications/notion/README.md +30 -0
- universal_mcp/applications/notion/__init__.py +1 -0
- universal_mcp/applications/notion/app.py +527 -0
- universal_mcp/applications/openai/README.md +22 -0
- universal_mcp/applications/openai/__init__.py +1 -0
- universal_mcp/applications/openai/app.py +759 -0
- universal_mcp/applications/outlook/README.md +20 -0
- universal_mcp/applications/outlook/__init__.py +1 -0
- universal_mcp/applications/outlook/app.py +444 -0
- universal_mcp/applications/perplexity/README.md +12 -0
- universal_mcp/applications/perplexity/__init__.py +1 -0
- universal_mcp/applications/perplexity/app.py +65 -0
- universal_mcp/applications/pipedrive/README.md +284 -0
- universal_mcp/applications/pipedrive/__init__.py +1 -0
- universal_mcp/applications/pipedrive/app.py +12924 -0
- universal_mcp/applications/posthog/README.md +132 -0
- universal_mcp/applications/posthog/__init__.py +1 -0
- universal_mcp/applications/posthog/app.py +7125 -0
- universal_mcp/applications/reddit/README.md +135 -0
- universal_mcp/applications/reddit/__init__.py +1 -0
- universal_mcp/applications/reddit/app.py +4652 -0
- universal_mcp/applications/replicate/README.md +18 -0
- universal_mcp/applications/replicate/__init__.py +1 -0
- universal_mcp/applications/replicate/app.py +495 -0
- universal_mcp/applications/resend/README.md +40 -0
- universal_mcp/applications/resend/__init__.py +1 -0
- universal_mcp/applications/resend/app.py +881 -0
- universal_mcp/applications/retell/README.md +21 -0
- universal_mcp/applications/retell/__init__.py +1 -0
- universal_mcp/applications/retell/app.py +333 -0
- universal_mcp/applications/rocketlane/README.md +70 -0
- universal_mcp/applications/rocketlane/__init__.py +1 -0
- universal_mcp/applications/rocketlane/app.py +4346 -0
- universal_mcp/applications/semanticscholar/README.md +25 -0
- universal_mcp/applications/semanticscholar/__init__.py +1 -0
- universal_mcp/applications/semanticscholar/app.py +482 -0
- universal_mcp/applications/semrush/README.md +44 -0
- universal_mcp/applications/semrush/__init__.py +1 -0
- universal_mcp/applications/semrush/app.py +2081 -0
- universal_mcp/applications/sendgrid/README.md +362 -0
- universal_mcp/applications/sendgrid/__init__.py +1 -0
- universal_mcp/applications/sendgrid/app.py +9752 -0
- universal_mcp/applications/sentry/README.md +186 -0
- universal_mcp/applications/sentry/__init__.py +1 -0
- universal_mcp/applications/sentry/app.py +7471 -0
- universal_mcp/applications/serpapi/README.md +14 -0
- universal_mcp/applications/serpapi/__init__.py +1 -0
- universal_mcp/applications/serpapi/app.py +293 -0
- universal_mcp/applications/sharepoint/README.md +0 -0
- universal_mcp/applications/sharepoint/__init__.py +1 -0
- universal_mcp/applications/sharepoint/app.py +215 -0
- universal_mcp/applications/shopify/README.md +321 -0
- universal_mcp/applications/shopify/__init__.py +1 -0
- universal_mcp/applications/shopify/app.py +15392 -0
- universal_mcp/applications/shortcut/README.md +128 -0
- universal_mcp/applications/shortcut/__init__.py +1 -0
- universal_mcp/applications/shortcut/app.py +4478 -0
- universal_mcp/applications/slack/README.md +0 -0
- universal_mcp/applications/slack/__init__.py +1 -0
- universal_mcp/applications/slack/app.py +570 -0
- universal_mcp/applications/spotify/README.md +91 -0
- universal_mcp/applications/spotify/__init__.py +1 -0
- universal_mcp/applications/spotify/app.py +2526 -0
- universal_mcp/applications/supabase/README.md +87 -0
- universal_mcp/applications/supabase/__init__.py +1 -0
- universal_mcp/applications/supabase/app.py +2970 -0
- universal_mcp/applications/tavily/README.md +12 -0
- universal_mcp/applications/tavily/__init__.py +1 -0
- universal_mcp/applications/tavily/app.py +51 -0
- universal_mcp/applications/trello/README.md +266 -0
- universal_mcp/applications/trello/__init__.py +1 -0
- universal_mcp/applications/trello/app.py +10875 -0
- universal_mcp/applications/twillo/README.md +0 -0
- universal_mcp/applications/twillo/__init__.py +1 -0
- universal_mcp/applications/twillo/app.py +269 -0
- universal_mcp/applications/twitter/README.md +100 -0
- universal_mcp/applications/twitter/__init__.py +1 -0
- universal_mcp/applications/twitter/api_segments/__init__.py +0 -0
- universal_mcp/applications/twitter/api_segments/api_segment_base.py +51 -0
- universal_mcp/applications/twitter/api_segments/compliance_api.py +122 -0
- universal_mcp/applications/twitter/api_segments/dm_conversations_api.py +255 -0
- universal_mcp/applications/twitter/api_segments/dm_events_api.py +140 -0
- universal_mcp/applications/twitter/api_segments/likes_api.py +159 -0
- universal_mcp/applications/twitter/api_segments/lists_api.py +395 -0
- universal_mcp/applications/twitter/api_segments/openapi_json_api.py +34 -0
- universal_mcp/applications/twitter/api_segments/spaces_api.py +309 -0
- universal_mcp/applications/twitter/api_segments/trends_api.py +40 -0
- universal_mcp/applications/twitter/api_segments/tweets_api.py +1403 -0
- universal_mcp/applications/twitter/api_segments/usage_api.py +40 -0
- universal_mcp/applications/twitter/api_segments/users_api.py +1498 -0
- universal_mcp/applications/twitter/app.py +46 -0
- universal_mcp/applications/unipile/README.md +28 -0
- universal_mcp/applications/unipile/__init__.py +1 -0
- universal_mcp/applications/unipile/app.py +829 -0
- universal_mcp/applications/whatsapp/README.md +23 -0
- universal_mcp/applications/whatsapp/__init__.py +1 -0
- universal_mcp/applications/whatsapp/app.py +595 -0
- universal_mcp/applications/whatsapp-business/README.md +34 -0
- universal_mcp/applications/whatsapp-business/__init__.py +1 -0
- universal_mcp/applications/whatsapp-business/app.py +1065 -0
- universal_mcp/applications/wrike/README.md +46 -0
- universal_mcp/applications/wrike/__init__.py +1 -0
- universal_mcp/applications/wrike/app.py +1583 -0
- universal_mcp/applications/youtube/README.md +57 -0
- universal_mcp/applications/youtube/__init__.py +1 -0
- universal_mcp/applications/youtube/app.py +1696 -0
- universal_mcp/applications/zenquotes/README.md +12 -0
- universal_mcp/applications/zenquotes/__init__.py +1 -0
- universal_mcp/applications/zenquotes/app.py +31 -0
- universal_mcp_applications-0.1.1.dist-info/METADATA +172 -0
- universal_mcp_applications-0.1.1.dist-info/RECORD +268 -0
- universal_mcp_applications-0.1.1.dist-info/WHEEL +4 -0
- universal_mcp_applications-0.1.1.dist-info/licenses/LICENSE +21 -0
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .app import AwsS3App
|
|
@@ -0,0 +1,552 @@
|
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import boto3
|
|
6
|
+
from botocore.exceptions import ClientError
|
|
7
|
+
from universal_mcp.applications import BaseApplication
|
|
8
|
+
from universal_mcp.integrations import Integration
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AwsS3App(BaseApplication):
|
|
12
|
+
"""
|
|
13
|
+
A class to interact with Amazon S3.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, integration: Integration | None = None, client=None, **kwargs):
|
|
17
|
+
"""
|
|
18
|
+
Initializes the AmazonS3App.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
aws_access_key_id (str, optional): AWS access key ID.
|
|
22
|
+
aws_secret_access_key (str, optional): AWS secret access key.
|
|
23
|
+
region_name (str, optional): AWS region name.
|
|
24
|
+
"""
|
|
25
|
+
super().__init__(name="aws-s3", integration=integration, **kwargs)
|
|
26
|
+
self._client = client
|
|
27
|
+
self.integration = integration
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def client(self):
|
|
31
|
+
"""
|
|
32
|
+
Gets the S3 client.
|
|
33
|
+
"""
|
|
34
|
+
if not self.integration:
|
|
35
|
+
raise ValueError("Integration not initialized")
|
|
36
|
+
if not self._client:
|
|
37
|
+
credentials = self.integration.get_credentials()
|
|
38
|
+
credentials = {
|
|
39
|
+
"aws_access_key_id": credentials.get("access_key_id")
|
|
40
|
+
or credentials.get("username"),
|
|
41
|
+
"aws_secret_access_key": credentials.get("secret_access_key")
|
|
42
|
+
or credentials.get("password"),
|
|
43
|
+
"region_name": credentials.get("region"),
|
|
44
|
+
}
|
|
45
|
+
self._client = boto3.client("s3", **credentials)
|
|
46
|
+
return self._client
|
|
47
|
+
|
|
48
|
+
def list_buckets(self) -> list[str]:
|
|
49
|
+
"""
|
|
50
|
+
Lists all S3 buckets.
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
List[str]: A list of bucket names.
|
|
54
|
+
"""
|
|
55
|
+
response = self.client.list_buckets()
|
|
56
|
+
return [bucket["Name"] for bucket in response["Buckets"]]
|
|
57
|
+
|
|
58
|
+
def create_bucket(self, bucket_name: str, region: str | None = None) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
Creates a new S3 bucket.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
bucket_name (str): The name of the bucket to create.
|
|
64
|
+
region (str, optional): The region to create the bucket in.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
bool: True if the bucket was created successfully.
|
|
68
|
+
Tags:
|
|
69
|
+
important
|
|
70
|
+
"""
|
|
71
|
+
try:
|
|
72
|
+
if region:
|
|
73
|
+
self.client.create_bucket(
|
|
74
|
+
Bucket=bucket_name,
|
|
75
|
+
CreateBucketConfiguration={"LocationConstraint": region},
|
|
76
|
+
)
|
|
77
|
+
else:
|
|
78
|
+
self.client.create_bucket(Bucket=bucket_name)
|
|
79
|
+
return True
|
|
80
|
+
except ClientError:
|
|
81
|
+
return False
|
|
82
|
+
|
|
83
|
+
def delete_bucket(self, bucket_name: str) -> bool:
|
|
84
|
+
"""
|
|
85
|
+
Deletes an S3 bucket (must be empty).
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
bucket_name (str): The name of the bucket to delete.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
bool: True if the bucket was deleted successfully.
|
|
92
|
+
Tags:
|
|
93
|
+
important
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
self.client.delete_bucket(Bucket=bucket_name)
|
|
97
|
+
return True
|
|
98
|
+
except ClientError:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
def get_bucket_policy(self, bucket_name: str) -> dict[str, Any]:
|
|
102
|
+
"""
|
|
103
|
+
Gets the bucket policy for the specified bucket.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
bucket_name (str): The name of the S3 bucket.
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Dict[str, Any]: The bucket policy as a dictionary.
|
|
110
|
+
Tags:
|
|
111
|
+
important
|
|
112
|
+
"""
|
|
113
|
+
try:
|
|
114
|
+
response = self.client.get_bucket_policy(Bucket=bucket_name)
|
|
115
|
+
return json.loads(response["Policy"])
|
|
116
|
+
except ClientError as e:
|
|
117
|
+
return {"error": str(e)}
|
|
118
|
+
|
|
119
|
+
def put_bucket_policy(self, bucket_name: str, policy: dict[str, Any]) -> bool:
|
|
120
|
+
"""
|
|
121
|
+
Sets the bucket policy for the specified bucket.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
bucket_name (str): The name of the S3 bucket.
|
|
125
|
+
policy (Dict[str, Any]): The bucket policy as a dictionary.
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
bool: True if the policy was set successfully.
|
|
129
|
+
Tags:
|
|
130
|
+
important
|
|
131
|
+
"""
|
|
132
|
+
try:
|
|
133
|
+
self.client.put_bucket_policy(Bucket=bucket_name, Policy=json.dumps(policy))
|
|
134
|
+
return True
|
|
135
|
+
except ClientError:
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
def list_prefixes(
|
|
139
|
+
self, bucket_name: str, prefix: str | None = None
|
|
140
|
+
) -> list[str]:
|
|
141
|
+
"""
|
|
142
|
+
Lists common prefixes ("folders") in the specified S3 bucket and prefix.
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
bucket_name (str): The name of the S3 bucket.
|
|
146
|
+
prefix (str, optional): The prefix to list folders under.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
List[str]: A list of folder prefixes.
|
|
150
|
+
Tags:
|
|
151
|
+
important
|
|
152
|
+
"""
|
|
153
|
+
paginator = self.client.get_paginator("list_objects_v2")
|
|
154
|
+
operation_parameters = {"Bucket": bucket_name}
|
|
155
|
+
if prefix:
|
|
156
|
+
operation_parameters["Prefix"] = prefix
|
|
157
|
+
operation_parameters["Delimiter"] = "/"
|
|
158
|
+
else:
|
|
159
|
+
operation_parameters["Delimiter"] = "/"
|
|
160
|
+
|
|
161
|
+
prefixes = []
|
|
162
|
+
for page in paginator.paginate(**operation_parameters):
|
|
163
|
+
for cp in page.get("CommonPrefixes", []):
|
|
164
|
+
prefixes.append(cp.get("Prefix"))
|
|
165
|
+
return prefixes
|
|
166
|
+
|
|
167
|
+
def put_prefix(
|
|
168
|
+
self, bucket_name: str, prefix_name: str, parent_prefix: str | None = None
|
|
169
|
+
) -> bool:
|
|
170
|
+
"""
|
|
171
|
+
Creates a prefix ("folder") in the specified S3 bucket.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
bucket_name (str): The name of the S3 bucket.
|
|
175
|
+
prefix_name (str): The name of the prefix to create.
|
|
176
|
+
parent_prefix (str, optional): The parent prefix (folder path).
|
|
177
|
+
|
|
178
|
+
Returns:
|
|
179
|
+
bool: True if the prefix was created successfully.
|
|
180
|
+
Tags:
|
|
181
|
+
important
|
|
182
|
+
"""
|
|
183
|
+
if parent_prefix:
|
|
184
|
+
key = f"{parent_prefix.rstrip('/')}/{prefix_name}/"
|
|
185
|
+
else:
|
|
186
|
+
key = f"{prefix_name}/"
|
|
187
|
+
self.client.put_object(Bucket=bucket_name, Key=key)
|
|
188
|
+
return True
|
|
189
|
+
|
|
190
|
+
def list_objects(self, bucket_name: str, prefix: str) -> list[dict[str, Any]]:
|
|
191
|
+
"""
|
|
192
|
+
Lists all objects in a specified S3 prefix.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
bucket_name (str): The name of the S3 bucket.
|
|
196
|
+
prefix (str): The prefix (folder path) to list objects under.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
List[Dict[str, Any]]: A list of dictionaries containing object metadata.
|
|
200
|
+
Tags:
|
|
201
|
+
important
|
|
202
|
+
"""
|
|
203
|
+
paginator = self.client.get_paginator("list_objects_v2")
|
|
204
|
+
operation_parameters = {"Bucket": bucket_name, "Prefix": prefix}
|
|
205
|
+
objects = []
|
|
206
|
+
for page in paginator.paginate(**operation_parameters):
|
|
207
|
+
for obj in page.get("Contents", []):
|
|
208
|
+
if not obj["Key"].endswith("/"):
|
|
209
|
+
objects.append(
|
|
210
|
+
{
|
|
211
|
+
"key": obj["Key"],
|
|
212
|
+
"name": obj["Key"].split("/")[-1],
|
|
213
|
+
"size": obj["Size"],
|
|
214
|
+
"last_modified": obj["LastModified"].isoformat()
|
|
215
|
+
if hasattr(obj["LastModified"], "isoformat")
|
|
216
|
+
else str(obj["LastModified"]),
|
|
217
|
+
}
|
|
218
|
+
)
|
|
219
|
+
return objects
|
|
220
|
+
|
|
221
|
+
def put_object(
|
|
222
|
+
self, bucket_name: str, prefix: str, object_name: str, content: str
|
|
223
|
+
) -> bool:
|
|
224
|
+
"""
|
|
225
|
+
Uploads an object to the specified S3 prefix.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
bucket_name (str): The name of the S3 bucket.
|
|
229
|
+
prefix (str): The prefix (folder path) where the object will be created.
|
|
230
|
+
object_name (str): The name of the object to create.
|
|
231
|
+
content (str): The content to write into the object.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
bool: True if the object was created successfully.
|
|
235
|
+
Tags:
|
|
236
|
+
important
|
|
237
|
+
"""
|
|
238
|
+
key = f"{prefix.rstrip('/')}/{object_name}" if prefix else object_name
|
|
239
|
+
self.client.put_object(
|
|
240
|
+
Bucket=bucket_name, Key=key, Body=content.encode("utf-8")
|
|
241
|
+
)
|
|
242
|
+
return True
|
|
243
|
+
|
|
244
|
+
def put_object_from_base64(
|
|
245
|
+
self, bucket_name: str, prefix: str, object_name: str, base64_content: str
|
|
246
|
+
) -> bool:
|
|
247
|
+
"""
|
|
248
|
+
Uploads a binary object from base64 content to the specified S3 prefix.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
bucket_name (str): The name of the S3 bucket.
|
|
252
|
+
prefix (str): The prefix (folder path) where the object will be created.
|
|
253
|
+
object_name (str): The name of the object to create.
|
|
254
|
+
base64_content (str): The base64-encoded content to upload.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
bool: True if the object was created successfully.
|
|
258
|
+
Tags:
|
|
259
|
+
important
|
|
260
|
+
"""
|
|
261
|
+
try:
|
|
262
|
+
key = f"{prefix.rstrip('/')}/{object_name}" if prefix else object_name
|
|
263
|
+
content = base64.b64decode(base64_content)
|
|
264
|
+
self.client.put_object(Bucket=bucket_name, Key=key, Body=content)
|
|
265
|
+
return True
|
|
266
|
+
except Exception:
|
|
267
|
+
return False
|
|
268
|
+
|
|
269
|
+
def get_object_content(self, bucket_name: str, key: str) -> dict[str, Any]:
|
|
270
|
+
"""
|
|
271
|
+
Gets the content of a specified object.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
bucket_name (str): The name of the S3 bucket.
|
|
275
|
+
key (str): The key (path) to the object.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
Dict[str, Any]: A dictionary containing the object's name, content type, content (as text or base64), and size.
|
|
279
|
+
Tags:
|
|
280
|
+
important
|
|
281
|
+
"""
|
|
282
|
+
try:
|
|
283
|
+
obj = self.client.get_object(Bucket=bucket_name, Key=key)
|
|
284
|
+
content = obj["Body"].read()
|
|
285
|
+
is_text_file = key.lower().endswith(
|
|
286
|
+
(".txt", ".csv", ".json", ".xml", ".html", ".md", ".js", ".css", ".py")
|
|
287
|
+
)
|
|
288
|
+
content_dict = (
|
|
289
|
+
{"content": content.decode("utf-8")}
|
|
290
|
+
if is_text_file
|
|
291
|
+
else {"content_base64": base64.b64encode(content).decode("ascii")}
|
|
292
|
+
)
|
|
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
|
+
}
|
|
299
|
+
except ClientError as e:
|
|
300
|
+
return {"error": str(e)}
|
|
301
|
+
|
|
302
|
+
def get_object_metadata(self, bucket_name: str, key: str) -> dict[str, Any]:
|
|
303
|
+
"""
|
|
304
|
+
Gets metadata for a specified object without downloading the content.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
bucket_name (str): The name of the S3 bucket.
|
|
308
|
+
key (str): The key (path) to the object.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Dict[str, Any]: A dictionary containing the object's metadata.
|
|
312
|
+
Tags:
|
|
313
|
+
important
|
|
314
|
+
"""
|
|
315
|
+
try:
|
|
316
|
+
response = self.client.head_object(Bucket=bucket_name, Key=key)
|
|
317
|
+
return {
|
|
318
|
+
"key": key,
|
|
319
|
+
"name": key.split("/")[-1],
|
|
320
|
+
"size": response.get("ContentLength", 0),
|
|
321
|
+
"last_modified": response.get("LastModified", "").isoformat()
|
|
322
|
+
if response.get("LastModified")
|
|
323
|
+
else "",
|
|
324
|
+
"content_type": response.get("ContentType", ""),
|
|
325
|
+
"etag": response.get("ETag", ""),
|
|
326
|
+
"metadata": response.get("Metadata", {}),
|
|
327
|
+
}
|
|
328
|
+
except ClientError as e:
|
|
329
|
+
return {"error": str(e)}
|
|
330
|
+
|
|
331
|
+
def copy_object(
|
|
332
|
+
self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str
|
|
333
|
+
) -> bool:
|
|
334
|
+
"""
|
|
335
|
+
Copies an object from one location to another.
|
|
336
|
+
|
|
337
|
+
Args:
|
|
338
|
+
source_bucket (str): The source bucket name.
|
|
339
|
+
source_key (str): The source object key.
|
|
340
|
+
dest_bucket (str): The destination bucket name.
|
|
341
|
+
dest_key (str): The destination object key.
|
|
342
|
+
|
|
343
|
+
Returns:
|
|
344
|
+
bool: True if the object was copied successfully.
|
|
345
|
+
Tags:
|
|
346
|
+
important
|
|
347
|
+
"""
|
|
348
|
+
try:
|
|
349
|
+
copy_source = {"Bucket": source_bucket, "Key": source_key}
|
|
350
|
+
self.client.copy_object(
|
|
351
|
+
CopySource=copy_source, Bucket=dest_bucket, Key=dest_key
|
|
352
|
+
)
|
|
353
|
+
return True
|
|
354
|
+
except ClientError:
|
|
355
|
+
return False
|
|
356
|
+
|
|
357
|
+
def move_object(
|
|
358
|
+
self, source_bucket: str, source_key: str, dest_bucket: str, dest_key: str
|
|
359
|
+
) -> bool:
|
|
360
|
+
"""
|
|
361
|
+
Moves an object from one location to another (copy then delete).
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
source_bucket (str): The source bucket name.
|
|
365
|
+
source_key (str): The source object key.
|
|
366
|
+
dest_bucket (str): The destination bucket name.
|
|
367
|
+
dest_key (str): The destination object key.
|
|
368
|
+
|
|
369
|
+
Returns:
|
|
370
|
+
bool: True if the object was moved successfully.
|
|
371
|
+
Tags:
|
|
372
|
+
important
|
|
373
|
+
"""
|
|
374
|
+
if self.copy_object(source_bucket, source_key, dest_bucket, dest_key):
|
|
375
|
+
return self.delete_object(source_bucket, source_key)
|
|
376
|
+
return False
|
|
377
|
+
|
|
378
|
+
def delete_object(self, bucket_name: str, key: str) -> bool:
|
|
379
|
+
"""
|
|
380
|
+
Deletes an object from S3.
|
|
381
|
+
|
|
382
|
+
Args:
|
|
383
|
+
bucket_name (str): The name of the S3 bucket.
|
|
384
|
+
key (str): The key (path) to the object to delete.
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
bool: True if the object was deleted successfully.
|
|
388
|
+
Tags:
|
|
389
|
+
important
|
|
390
|
+
"""
|
|
391
|
+
try:
|
|
392
|
+
self.client.delete_object(Bucket=bucket_name, Key=key)
|
|
393
|
+
return True
|
|
394
|
+
except ClientError:
|
|
395
|
+
return False
|
|
396
|
+
|
|
397
|
+
def delete_objects(self, bucket_name: str, keys: list[str]) -> dict[str, Any]:
|
|
398
|
+
"""
|
|
399
|
+
Deletes multiple objects from S3.
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
bucket_name (str): The name of the S3 bucket.
|
|
403
|
+
keys (List[str]): List of object keys to delete.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Dict[str, Any]: Results of the deletion operation.
|
|
407
|
+
Tags:
|
|
408
|
+
important
|
|
409
|
+
"""
|
|
410
|
+
try:
|
|
411
|
+
delete_dict = {"Objects": [{"Key": key} for key in keys]}
|
|
412
|
+
response = self.client.delete_objects(
|
|
413
|
+
Bucket=bucket_name, Delete=delete_dict
|
|
414
|
+
)
|
|
415
|
+
return {
|
|
416
|
+
"deleted": [obj.get("Key") for obj in response.get("Deleted", [])],
|
|
417
|
+
"errors": [obj for obj in response.get("Errors", [])],
|
|
418
|
+
}
|
|
419
|
+
except ClientError as e:
|
|
420
|
+
return {"error": str(e)}
|
|
421
|
+
|
|
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:
|
|
429
|
+
"""
|
|
430
|
+
Generates a presigned URL for accessing an S3 object.
|
|
431
|
+
|
|
432
|
+
Args:
|
|
433
|
+
bucket_name (str): The name of the S3 bucket.
|
|
434
|
+
key (str): The key (path) to the object.
|
|
435
|
+
expiration (int): Time in seconds for the presigned URL to remain valid (default: 3600).
|
|
436
|
+
http_method (str): HTTP method for the presigned URL (default: 'GET').
|
|
437
|
+
|
|
438
|
+
Returns:
|
|
439
|
+
str: The presigned URL or error message.
|
|
440
|
+
Tags:
|
|
441
|
+
important
|
|
442
|
+
"""
|
|
443
|
+
try:
|
|
444
|
+
method_map = {
|
|
445
|
+
"GET": "get_object",
|
|
446
|
+
"PUT": "put_object",
|
|
447
|
+
"DELETE": "delete_object",
|
|
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,
|
|
454
|
+
)
|
|
455
|
+
return response
|
|
456
|
+
except ClientError as e:
|
|
457
|
+
return f"Error: {str(e)}"
|
|
458
|
+
|
|
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,
|
|
466
|
+
) -> list[dict[str, Any]]:
|
|
467
|
+
"""
|
|
468
|
+
Searches for objects in S3 based on various criteria.
|
|
469
|
+
|
|
470
|
+
Args:
|
|
471
|
+
bucket_name (str): The name of the S3 bucket.
|
|
472
|
+
prefix (str): The prefix to search within.
|
|
473
|
+
name_pattern (str): Pattern to match in object names (case-insensitive).
|
|
474
|
+
min_size (int, optional): Minimum object size in bytes.
|
|
475
|
+
max_size (int, optional): Maximum object size in bytes.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
List[Dict[str, Any]]: List of matching objects with metadata.
|
|
479
|
+
Tags:
|
|
480
|
+
important
|
|
481
|
+
"""
|
|
482
|
+
all_objects = self.list_objects(bucket_name, prefix)
|
|
483
|
+
filtered_objects = []
|
|
484
|
+
|
|
485
|
+
for obj in all_objects:
|
|
486
|
+
# Filter by name pattern
|
|
487
|
+
if name_pattern and name_pattern.lower() not in obj["name"].lower():
|
|
488
|
+
continue
|
|
489
|
+
|
|
490
|
+
# Filter by size
|
|
491
|
+
if min_size and obj["size"] < min_size:
|
|
492
|
+
continue
|
|
493
|
+
if max_size and obj["size"] > max_size:
|
|
494
|
+
continue
|
|
495
|
+
|
|
496
|
+
filtered_objects.append(obj)
|
|
497
|
+
|
|
498
|
+
return filtered_objects
|
|
499
|
+
|
|
500
|
+
def get_bucket_size(self, bucket_name: str, prefix: str = "") -> dict[str, Any]:
|
|
501
|
+
"""
|
|
502
|
+
Calculates the total size and object count for a bucket or prefix.
|
|
503
|
+
|
|
504
|
+
Args:
|
|
505
|
+
bucket_name (str): The name of the S3 bucket.
|
|
506
|
+
prefix (str): The prefix to calculate size for (default: entire bucket).
|
|
507
|
+
|
|
508
|
+
Returns:
|
|
509
|
+
Dict[str, Any]: Dictionary containing total size, object count, and human-readable size.
|
|
510
|
+
Tags:
|
|
511
|
+
important
|
|
512
|
+
"""
|
|
513
|
+
objects = self.list_objects(bucket_name, prefix)
|
|
514
|
+
total_size = sum(obj["size"] for obj in objects)
|
|
515
|
+
object_count = len(objects)
|
|
516
|
+
|
|
517
|
+
# Convert to human-readable format
|
|
518
|
+
for unit in ["B", "KB", "MB", "GB", "TB"]:
|
|
519
|
+
if total_size < 1024.0:
|
|
520
|
+
human_size = f"{total_size:.2f} {unit}"
|
|
521
|
+
break
|
|
522
|
+
total_size /= 1024.0
|
|
523
|
+
else:
|
|
524
|
+
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
|
+
}
|
|
531
|
+
|
|
532
|
+
def list_tools(self):
|
|
533
|
+
return [
|
|
534
|
+
self.create_bucket,
|
|
535
|
+
self.delete_bucket,
|
|
536
|
+
self.get_bucket_policy,
|
|
537
|
+
self.put_bucket_policy,
|
|
538
|
+
self.list_prefixes,
|
|
539
|
+
self.put_prefix,
|
|
540
|
+
self.list_objects,
|
|
541
|
+
self.put_object,
|
|
542
|
+
self.put_object_from_base64,
|
|
543
|
+
self.get_object_content,
|
|
544
|
+
self.get_object_metadata,
|
|
545
|
+
self.copy_object,
|
|
546
|
+
self.move_object,
|
|
547
|
+
self.delete_object,
|
|
548
|
+
self.delete_objects,
|
|
549
|
+
self.generate_presigned_url,
|
|
550
|
+
self.search_objects,
|
|
551
|
+
self.get_bucket_size,
|
|
552
|
+
]
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .app import BillApp
|