sharedkernel 2.2.6__py3-none-any.whl → 2.3.0__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.
- sharedkernel/multipart_upload.py +69 -0
- sharedkernel/s3_uploader.py +50 -26
- {sharedkernel-2.2.6.dist-info → sharedkernel-2.3.0.dist-info}/METADATA +5 -1
- {sharedkernel-2.2.6.dist-info → sharedkernel-2.3.0.dist-info}/RECORD +6 -5
- {sharedkernel-2.2.6.dist-info → sharedkernel-2.3.0.dist-info}/WHEEL +0 -0
- {sharedkernel-2.2.6.dist-info → sharedkernel-2.3.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
class MultipartUploadSession:
|
|
2
|
+
MIN_PART_SIZE = 5 * 1024 * 1024 # 5MB
|
|
3
|
+
|
|
4
|
+
def __init__(self, s3_client, bucket, object_key, acl="private"):
|
|
5
|
+
self.s3 = s3_client
|
|
6
|
+
self.bucket = bucket
|
|
7
|
+
self.key = object_key
|
|
8
|
+
self.acl = acl
|
|
9
|
+
|
|
10
|
+
self.upload_id = None
|
|
11
|
+
self.parts = []
|
|
12
|
+
self.part_number = 1
|
|
13
|
+
self.buffer = bytearray()
|
|
14
|
+
|
|
15
|
+
self._start()
|
|
16
|
+
|
|
17
|
+
def _start(self):
|
|
18
|
+
resp = self.s3.create_multipart_upload(
|
|
19
|
+
Bucket=self.bucket,
|
|
20
|
+
Key=self.key,
|
|
21
|
+
ACL=self.acl,
|
|
22
|
+
)
|
|
23
|
+
self.upload_id = resp["UploadId"]
|
|
24
|
+
|
|
25
|
+
def upload_chunk(self, chunk: bytes):
|
|
26
|
+
"""
|
|
27
|
+
Receives small chunks (KB) and buffers them
|
|
28
|
+
"""
|
|
29
|
+
self.buffer.extend(chunk)
|
|
30
|
+
|
|
31
|
+
if len(self.buffer) >= self.MIN_PART_SIZE:
|
|
32
|
+
self._upload_part(bytes(self.buffer))
|
|
33
|
+
self.buffer.clear()
|
|
34
|
+
|
|
35
|
+
def _upload_part(self, data: bytes):
|
|
36
|
+
resp = self.s3.upload_part(
|
|
37
|
+
Bucket=self.bucket,
|
|
38
|
+
Key=self.key,
|
|
39
|
+
UploadId=self.upload_id,
|
|
40
|
+
PartNumber=self.part_number,
|
|
41
|
+
Body=data,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
self.parts.append({
|
|
45
|
+
"ETag": resp["ETag"],
|
|
46
|
+
"PartNumber": self.part_number
|
|
47
|
+
})
|
|
48
|
+
self.part_number += 1
|
|
49
|
+
|
|
50
|
+
def complete(self):
|
|
51
|
+
# upload remaining buffer (can be < 5MB)
|
|
52
|
+
if self.buffer:
|
|
53
|
+
self._upload_part(bytes(self.buffer))
|
|
54
|
+
self.buffer.clear()
|
|
55
|
+
|
|
56
|
+
self.s3.complete_multipart_upload(
|
|
57
|
+
Bucket=self.bucket,
|
|
58
|
+
Key=self.key,
|
|
59
|
+
UploadId=self.upload_id,
|
|
60
|
+
MultipartUpload={"Parts": self.parts},
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def abort(self):
|
|
64
|
+
if self.upload_id:
|
|
65
|
+
self.s3.abort_multipart_upload(
|
|
66
|
+
Bucket=self.bucket,
|
|
67
|
+
Key=self.key,
|
|
68
|
+
UploadId=self.upload_id,
|
|
69
|
+
)
|
sharedkernel/s3_uploader.py
CHANGED
|
@@ -1,62 +1,64 @@
|
|
|
1
|
+
|
|
1
2
|
import requests
|
|
2
3
|
import boto3
|
|
3
4
|
from io import BytesIO
|
|
4
5
|
import uuid
|
|
5
6
|
import os
|
|
7
|
+
from sharedkernel.multipart_upload import MultipartUploadSession
|
|
6
8
|
|
|
7
9
|
class S3Uploader:
|
|
8
10
|
def __init__(self, endpoint_url, bucket, access_key, secret_key):
|
|
9
|
-
"""
|
|
10
|
-
Initializes the S3Uploader with credentials and optional endpoint/region.
|
|
11
|
-
|
|
12
|
-
:param endpoint_url: Custom endpoint URL (for S3-compatible services like MinIO)
|
|
13
|
-
:param bucket: Name of S3 bucket
|
|
14
|
-
:param access_key: Access key
|
|
15
|
-
:param secret_key: Secret key
|
|
16
|
-
"""
|
|
17
11
|
self.endpoint_url = endpoint_url
|
|
18
12
|
self.bucket = bucket
|
|
19
13
|
self.access_key = access_key
|
|
20
14
|
self.secret_key = secret_key
|
|
21
15
|
|
|
22
|
-
# Initialize the S3 client
|
|
23
16
|
self.s3 = boto3.client(
|
|
24
17
|
's3',
|
|
25
18
|
endpoint_url=self.endpoint_url,
|
|
26
19
|
aws_access_key_id=self.access_key,
|
|
27
20
|
aws_secret_access_key=self.secret_key,
|
|
28
21
|
)
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
Uploads a file object to an S3 bucket and returns its URL.
|
|
33
|
-
|
|
34
|
-
:param file_obj: File object to upload
|
|
35
|
-
:param object_name: S3 file name to save as (optional)
|
|
36
|
-
:param file_extension: File extension to append to object name (optional)
|
|
37
|
-
:param folder_name: Optional folder name to save the file in
|
|
38
|
-
:return: URL of the uploaded file if successful, else False
|
|
39
|
-
"""
|
|
40
|
-
# Use uuid4 to generate a unique object_name if not provided
|
|
22
|
+
|
|
23
|
+
@staticmethod
|
|
24
|
+
def __generate_object_name(object_name=None, file_extension=None, folder_name=None):
|
|
41
25
|
if object_name is None:
|
|
42
26
|
object_name = str(uuid.uuid4())
|
|
43
27
|
|
|
44
|
-
# Append the file extension to the object_name
|
|
45
28
|
if file_extension:
|
|
46
29
|
object_name += file_extension
|
|
47
30
|
|
|
48
|
-
# Prefix the object name with the folder if provided
|
|
49
31
|
if folder_name:
|
|
50
32
|
object_name = f"{folder_name}/{object_name}"
|
|
33
|
+
|
|
34
|
+
return object_name
|
|
35
|
+
|
|
51
36
|
|
|
52
|
-
|
|
53
|
-
self.
|
|
37
|
+
def upload_file_object(self, file_obj, object_name=None, file_extension=None, folder_name=None, public_read=True):
|
|
38
|
+
object_name = self.__generate_object_name(object_name, file_extension, folder_name)
|
|
39
|
+
|
|
40
|
+
acl = "public-read" if public_read else "private"
|
|
41
|
+
|
|
42
|
+
self.s3.upload_fileobj(file_obj, self.bucket, object_name, ExtraArgs={'ACL': acl})
|
|
54
43
|
|
|
55
|
-
# Construct the URL of the uploaded file
|
|
56
44
|
file_url = f"{self.endpoint_url}/{self.bucket}/{object_name}"
|
|
57
45
|
|
|
58
46
|
return file_url
|
|
59
47
|
|
|
48
|
+
def generate_presigned_url(self, object_name=None, file_extension=None, folder_name=None, expire_time: int = 3600):
|
|
49
|
+
object_name = self.__generate_object_name(object_name, file_extension, folder_name)
|
|
50
|
+
|
|
51
|
+
temp_url = self.s3.generate_presigned_url(
|
|
52
|
+
'get_object',
|
|
53
|
+
Params={
|
|
54
|
+
'Bucket': self.bucket,
|
|
55
|
+
'Key': object_name
|
|
56
|
+
},
|
|
57
|
+
ExpiresIn=expire_time
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return temp_url
|
|
61
|
+
|
|
60
62
|
def upload_file_from_url(self, file_url, object_name=None, folder_name=None):
|
|
61
63
|
"""
|
|
62
64
|
Downloads a file from a URL and uploads it to an S3 bucket, returning its URL.
|
|
@@ -80,3 +82,25 @@ class S3Uploader:
|
|
|
80
82
|
# Step 2: Upload the file to S3
|
|
81
83
|
file_obj = BytesIO(response.content)
|
|
82
84
|
return self.upload_file_object(file_obj=file_obj, object_name=object_name, file_extension=file_extension, folder_name=folder_name)
|
|
85
|
+
|
|
86
|
+
def create_multipart_session(
|
|
87
|
+
self,
|
|
88
|
+
object_name=None,
|
|
89
|
+
file_extension=None,
|
|
90
|
+
folder_name=None,
|
|
91
|
+
public_read=True,
|
|
92
|
+
):
|
|
93
|
+
object_key = self.__generate_object_name(
|
|
94
|
+
object_name, file_extension, folder_name
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
acl = "public-read" if public_read else "private"
|
|
98
|
+
|
|
99
|
+
return MultipartUploadSession(
|
|
100
|
+
s3_client=self.s3,
|
|
101
|
+
bucket=self.bucket,
|
|
102
|
+
object_key=object_key,
|
|
103
|
+
acl=acl,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sharedkernel
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: sharekernel is a shared package between all python projects
|
|
5
5
|
Author: Smilinno
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -29,6 +29,10 @@ Dynamic: summary
|
|
|
29
29
|
this is a shared kernel package
|
|
30
30
|
|
|
31
31
|
# Change Log
|
|
32
|
+
### Version 2.3.0
|
|
33
|
+
- Implement multipart uploader for S3
|
|
34
|
+
### Version 2.2.7
|
|
35
|
+
- add presigned url in s3 uploader
|
|
32
36
|
### Version 2.2.6
|
|
33
37
|
- implement api key
|
|
34
38
|
### Version 2.2.5
|
|
@@ -3,8 +3,9 @@ sharedkernel/data_format_converter.py,sha256=GWGbfhKJBifkz-cfnqKAFjJM43WC0qdq9KS
|
|
|
3
3
|
sharedkernel/date_converter.py,sha256=Cjd4ewm0pIfQzv7nlgAAB_EYrr-VvXxQGehJCNphgXc,4491
|
|
4
4
|
sharedkernel/diff_utils.py,sha256=mtwJmc05GAXUOB0ZLtqAhfBT1kGoSQ7qmP5N44P73ho,2564
|
|
5
5
|
sharedkernel/jwt_service.py,sha256=KSkrpXVqmKMGdaoDg0DqhOfzR9CIGVTg7HfOlAaz1Zo,1611
|
|
6
|
+
sharedkernel/multipart_upload.py,sha256=JVlCBlznB9dWh2_spjAqzLOqQT1CHUTvrR4m7ug8qaM,1877
|
|
6
7
|
sharedkernel/regex_masking.py,sha256=zQrgteP8Cuq1EC9B7QUJqAXUxK9ISD9kWMYK2AbRfw0,3288
|
|
7
|
-
sharedkernel/s3_uploader.py,sha256=
|
|
8
|
+
sharedkernel/s3_uploader.py,sha256=VWgN-RVHmLXMDuxgZux3M-iFwWk5zhRnUIECKS0auW4,3637
|
|
8
9
|
sharedkernel/string_extentions.py,sha256=ld02W06gd0Ql80GQU6nlqPAeUSfOe2Yr8cCzf3lJgQY,98
|
|
9
10
|
sharedkernel/database/__init__.py,sha256=AtIbU7pDKwY6YCg_J8PX62WcYld1lAvBcVFW62MdiFg,241
|
|
10
11
|
sharedkernel/database/audit_model.py,sha256=SMAYrvMb7XvPi4076TDgkOLaxh_3Jg_tfE12qKOy8RA,364
|
|
@@ -27,7 +28,7 @@ sharedkernel/objects/json_string_model.py,sha256=j63tnoqiok0EmBP6T-ChYuQYKPw7mLq
|
|
|
27
28
|
sharedkernel/objects/jwt_model.py,sha256=XQHQhTbg7PT8XiUh5fd9MwRH4ldPsesI_hfbjaSqdKg,134
|
|
28
29
|
sharedkernel/objects/result.py,sha256=I_9hX5TPEO1oStzuFLjFh1rtimXorz7ml-OaW_2BMvc,680
|
|
29
30
|
sharedkernel/objects/user_info.py,sha256=51WyspRxlIWzK7Lfxgqg4D6mylXeHe9ZSenf-RhYTdA,286
|
|
30
|
-
sharedkernel-2.
|
|
31
|
-
sharedkernel-2.
|
|
32
|
-
sharedkernel-2.
|
|
33
|
-
sharedkernel-2.
|
|
31
|
+
sharedkernel-2.3.0.dist-info/METADATA,sha256=sJZef9JTB6R7oQ5kGWKCQPuV6d7vIztvimh2VWN6Uh4,3339
|
|
32
|
+
sharedkernel-2.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
33
|
+
sharedkernel-2.3.0.dist-info/top_level.txt,sha256=TVTOnV1MItSSlpSjqkiijuHkoVsGHS4CArpsM-lylkE,13
|
|
34
|
+
sharedkernel-2.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|