PyS3Uploader 0.2.2__py3-none-any.whl → 0.2.3__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.
- {pys3uploader-0.2.2.dist-info → pys3uploader-0.2.3.dist-info}/METADATA +8 -4
- pys3uploader-0.2.3.dist-info/RECORD +11 -0
- s3/__init__.py +1 -1
- s3/uploader.py +57 -16
- pys3uploader-0.2.2.dist-info/RECORD +0 -11
- {pys3uploader-0.2.2.dist-info → pys3uploader-0.2.3.dist-info}/LICENSE +0 -0
- {pys3uploader-0.2.2.dist-info → pys3uploader-0.2.3.dist-info}/WHEEL +0 -0
- {pys3uploader-0.2.2.dist-info → pys3uploader-0.2.3.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: PyS3Uploader
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Python module to upload objects to an S3 bucket.
|
|
5
5
|
Author-email: Vignesh Rao <svignesh1793@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -40,6 +40,7 @@ Requires-Python: >=3.11
|
|
|
40
40
|
Description-Content-Type: text/markdown
|
|
41
41
|
License-File: LICENSE
|
|
42
42
|
Requires-Dist: boto3==1.40.*
|
|
43
|
+
Requires-Dist: python-dotenv==1.1.*
|
|
43
44
|
Requires-Dist: tqdm==4.67.*
|
|
44
45
|
Provides-Extra: dev
|
|
45
46
|
Requires-Dist: sphinx==5.1.1; extra == "dev"
|
|
@@ -90,7 +91,7 @@ if __name__ == '__main__':
|
|
|
90
91
|
wrapper = s3.Uploader(
|
|
91
92
|
bucket_name="BUCKET_NAME",
|
|
92
93
|
upload_dir="FULL_PATH_TO_UPLOAD",
|
|
93
|
-
|
|
94
|
+
exclude_prefix="PART_OF_UPLOAD_DIR_TO_EXCLUDE"
|
|
94
95
|
)
|
|
95
96
|
wrapper.run_in_parallel()
|
|
96
97
|
```
|
|
@@ -103,7 +104,7 @@ if __name__ == '__main__':
|
|
|
103
104
|
wrapper = s3.Uploader(
|
|
104
105
|
bucket_name="BUCKET_NAME",
|
|
105
106
|
upload_dir="FULL_PATH_TO_UPLOAD",
|
|
106
|
-
|
|
107
|
+
exclude_prefix="PART_OF_UPLOAD_DIR_TO_EXCLUDE"
|
|
107
108
|
)
|
|
108
109
|
wrapper.run()
|
|
109
110
|
```
|
|
@@ -114,10 +115,13 @@ if __name__ == '__main__':
|
|
|
114
115
|
|
|
115
116
|
#### Optional kwargs
|
|
116
117
|
- **s3_prefix** - S3 object prefix for each file. Defaults to ``None``
|
|
117
|
-
- **
|
|
118
|
+
- **exclude_prefix** - Path in ``upload_dir`` that has to be excluded in object keys. Defaults to `None`
|
|
118
119
|
- **skip_dot_files** - Boolean flag to skip dot files. Defaults to ``True``
|
|
119
120
|
- **overwrite** - Boolean flag to overwrite files present in S3. Defaults to ``False``
|
|
121
|
+
- **file_exclusion** - Sequence of files to exclude during upload. Defaults to ``None``
|
|
122
|
+
- **folder_exclusion** - Sequence of directories to exclude during upload. Defaults to ``None``
|
|
120
123
|
- **logger** - Bring your own custom pre-configured logger. Defaults to on-screen logging.
|
|
124
|
+
- **env_file** – Path to a `.env` file for loading environment variables. Defaults to scanning the current directory.
|
|
121
125
|
<br><br>
|
|
122
126
|
- **region_name** - AWS region name. Defaults to the env var `AWS_DEFAULT_REGION`
|
|
123
127
|
- **profile_name** - AWS profile name. Defaults to the env var `PROFILE_NAME`
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
s3/__init__.py,sha256=aZ2woJ8TD2tgqXi0ElG-wWwJWoQLIdqTdm50FLaxL8w,66
|
|
2
|
+
s3/exceptions.py,sha256=hH3jlMOe8yjBatQK9EdndWZz4QESU74KSY_iDhQ37SY,2585
|
|
3
|
+
s3/logger.py,sha256=oH540oq8jY723jA4lDWlgfFPLbNgGXTkDwFpB7TLO_o,1196
|
|
4
|
+
s3/tree.py,sha256=DiQ2ekMMaj2m_P3-iKkEqSuJCJZ_UZxcAwHtAoPVa5c,1824
|
|
5
|
+
s3/uploader.py,sha256=KxrWbIInXxXQszP_uJLf_dBI5rUNjNnhco3gr9Vdrto,13767
|
|
6
|
+
s3/utils.py,sha256=NbF28CYviK_St5qd1EOumMVyus9BvQON7clUFeR_SEQ,4473
|
|
7
|
+
pys3uploader-0.2.3.dist-info/LICENSE,sha256=8k-hEraOzyum0GvmmK65YxNRTFXK7eIFHJ0OshJXeTk,1068
|
|
8
|
+
pys3uploader-0.2.3.dist-info/METADATA,sha256=ae2lA8b7dsGWZSMfB8w4joDiAlaE6Wk1f3p1Fxywkc4,7795
|
|
9
|
+
pys3uploader-0.2.3.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
10
|
+
pys3uploader-0.2.3.dist-info/top_level.txt,sha256=iQp4y1P58Q633gj8M08kHE4mqqT0hixuDWcniDk_RJ4,3
|
|
11
|
+
pys3uploader-0.2.3.dist-info/RECORD,,
|
s3/__init__.py
CHANGED
s3/uploader.py
CHANGED
|
@@ -2,9 +2,10 @@ import logging
|
|
|
2
2
|
import os
|
|
3
3
|
import time
|
|
4
4
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
5
|
-
from typing import Dict
|
|
5
|
+
from typing import Dict, Iterable
|
|
6
6
|
|
|
7
7
|
import boto3.resources.factory
|
|
8
|
+
import dotenv
|
|
8
9
|
from botocore.config import Config
|
|
9
10
|
from botocore.exceptions import ClientError
|
|
10
11
|
from tqdm import tqdm
|
|
@@ -33,15 +34,18 @@ class Uploader:
|
|
|
33
34
|
bucket_name: str,
|
|
34
35
|
upload_dir: str,
|
|
35
36
|
s3_prefix: str = None,
|
|
36
|
-
|
|
37
|
+
exclude_prefix: str = None,
|
|
37
38
|
skip_dot_files: bool = True,
|
|
38
39
|
overwrite: bool = False,
|
|
40
|
+
file_exclusion: Iterable[str] = None,
|
|
41
|
+
folder_exclusion: Iterable[str] = None,
|
|
39
42
|
region_name: str = None,
|
|
40
43
|
profile_name: str = None,
|
|
41
44
|
aws_access_key_id: str = None,
|
|
42
45
|
aws_secret_access_key: str = None,
|
|
43
46
|
retry_config: Config = RETRY_CONFIG,
|
|
44
47
|
logger: logging.Logger = None,
|
|
48
|
+
env_file: str = None,
|
|
45
49
|
):
|
|
46
50
|
"""Initiates all the necessary args and creates a boto3 session with retry logic.
|
|
47
51
|
|
|
@@ -49,14 +53,17 @@ class Uploader:
|
|
|
49
53
|
bucket_name: Name of the bucket.
|
|
50
54
|
upload_dir: Full path of the directory to be uploaded.
|
|
51
55
|
s3_prefix: Particular bucket prefix within which the upload should happen.
|
|
52
|
-
|
|
56
|
+
exclude_prefix: Full directory path to exclude from S3 object prefix.
|
|
53
57
|
skip_dot_files: Boolean flag to skip dot files.
|
|
54
58
|
overwrite: Boolean flag to overwrite files in S3.
|
|
59
|
+
file_exclusion: Sequence of files to exclude during upload.
|
|
60
|
+
folder_exclusion: Sequence of directories to exclude during upload.
|
|
55
61
|
region_name: Name of the AWS region.
|
|
56
62
|
profile_name: AWS profile name.
|
|
57
63
|
aws_access_key_id: AWS access key ID.
|
|
58
64
|
aws_secret_access_key: AWS secret access key.
|
|
59
65
|
logger: Bring your own logger.
|
|
66
|
+
env_file: Dotenv file (.env) filepath to load environment variables.
|
|
60
67
|
|
|
61
68
|
See Also:
|
|
62
69
|
s3_prefix:
|
|
@@ -65,29 +72,56 @@ class Uploader:
|
|
|
65
72
|
If ``s3_prefix`` is set to: ``2025``, then the file path
|
|
66
73
|
``/home/ubuntu/Desktop/S3Upload/sub/photo.jpg`` will be uploaded as ``2025/S3Upload/sub/photo.jpg``
|
|
67
74
|
|
|
68
|
-
|
|
75
|
+
exclude_prefix:
|
|
69
76
|
When upload directory is "/home/ubuntu/Desktop/S3Upload", each file will naturally have the full prefix.
|
|
70
|
-
However, this behavior can be avoided by specifying the ``
|
|
77
|
+
However, this behavior can be avoided by specifying the ``exclude_prefix`` parameter.
|
|
71
78
|
|
|
72
|
-
If
|
|
79
|
+
If exclude_prefix is set to: ``/home/ubuntu/Desktop``, then the file path
|
|
73
80
|
``/home/ubuntu/Desktop/S3Upload/sub-dir/photo.jpg`` will be uploaded as ``S3Upload/sub-dir/photo.jpg``
|
|
81
|
+
|
|
82
|
+
env_file:
|
|
83
|
+
Environment variables can be loaded from a .env file.
|
|
84
|
+
The filepath can be set as ``env_file`` during object instantiation or as an environment variable.
|
|
85
|
+
If a filepath is provided, PyS3Uploader loads it directly or searches the root directory for the file.
|
|
86
|
+
If no filepath is provided, PyS3Uploader searches the current directory for a .env file.
|
|
74
87
|
"""
|
|
88
|
+
self.logger = logger or default_logger()
|
|
89
|
+
self.env_file = env_file or getenv("ENV_FILE", default=".env")
|
|
90
|
+
|
|
91
|
+
# Check for env_file in current working directory
|
|
92
|
+
if os.path.isfile(self.env_file):
|
|
93
|
+
self.logger.debug("Loading env file: %s", self.env_file)
|
|
94
|
+
dotenv.load_dotenv(dotenv_path=self.env_file, override=True)
|
|
95
|
+
# Find the env_file from root
|
|
96
|
+
elif env_file := dotenv.find_dotenv(self.env_file, raise_error_if_not_found=False):
|
|
97
|
+
self.logger.debug("Loading env file: %s", env_file)
|
|
98
|
+
dotenv.load_dotenv(dotenv_path=env_file, override=True)
|
|
99
|
+
else:
|
|
100
|
+
# Scan current working directory for any .env files
|
|
101
|
+
for file in os.listdir():
|
|
102
|
+
if file.endswith(".env"):
|
|
103
|
+
self.logger.debug("Loading env file: %s", file)
|
|
104
|
+
dotenv.load_dotenv(dotenv_path=file, override=True)
|
|
105
|
+
break
|
|
106
|
+
else:
|
|
107
|
+
self.logger.debug("No .env files found to load")
|
|
108
|
+
|
|
75
109
|
self.session = boto3.Session(
|
|
76
|
-
profile_name=profile_name or getenv("PROFILE_NAME"),
|
|
110
|
+
profile_name=profile_name or getenv("PROFILE_NAME", "AWS_PROFILE_NAME"),
|
|
77
111
|
region_name=region_name or getenv("AWS_DEFAULT_REGION"),
|
|
78
112
|
aws_access_key_id=aws_access_key_id or getenv("AWS_ACCESS_KEY_ID"),
|
|
79
113
|
aws_secret_access_key=aws_secret_access_key or getenv("AWS_SECRET_ACCESS_KEY"),
|
|
80
114
|
)
|
|
81
115
|
self.s3 = self.session.resource(service_name="s3", config=retry_config)
|
|
82
116
|
|
|
83
|
-
self.logger = logger or default_logger()
|
|
84
|
-
|
|
85
117
|
self.bucket_name = bucket_name
|
|
86
|
-
self.upload_dir = upload_dir
|
|
118
|
+
self.upload_dir = upload_dir
|
|
87
119
|
self.s3_prefix = s3_prefix
|
|
88
|
-
self.
|
|
120
|
+
self.exclude_prefix = exclude_prefix
|
|
89
121
|
self.skip_dot_files = skip_dot_files
|
|
90
122
|
self.overwrite = overwrite
|
|
123
|
+
self.file_exclusion = file_exclusion or []
|
|
124
|
+
self.folder_exclusion = folder_exclusion or []
|
|
91
125
|
|
|
92
126
|
self.results = UploadResults()
|
|
93
127
|
self.start = time.time()
|
|
@@ -106,9 +140,9 @@ class Uploader:
|
|
|
106
140
|
BucketNotFound: If bucket name was not found.
|
|
107
141
|
"""
|
|
108
142
|
self.start = time.time()
|
|
109
|
-
if self.
|
|
143
|
+
if self.exclude_prefix and self.exclude_prefix not in self.upload_dir:
|
|
110
144
|
raise ValueError(
|
|
111
|
-
f"\n\n\tStart folder {self.
|
|
145
|
+
f"\n\n\tStart folder {self.exclude_prefix!r} is not a part of upload directory {self.upload_dir!r}"
|
|
112
146
|
)
|
|
113
147
|
if not self.upload_dir:
|
|
114
148
|
raise ValueError("\n\n\tCannot proceed without an upload directory.")
|
|
@@ -184,13 +218,20 @@ class Uploader:
|
|
|
184
218
|
"""
|
|
185
219
|
files_to_upload = {}
|
|
186
220
|
for __path, __directory, __files in os.walk(self.upload_dir):
|
|
221
|
+
scan_dir = os.path.split(__path)[-1]
|
|
222
|
+
if scan_dir in self.folder_exclusion:
|
|
223
|
+
self.logger.info("Skipping '%s' honoring folder exclusion", scan_dir)
|
|
224
|
+
continue
|
|
187
225
|
for file_ in __files:
|
|
226
|
+
if file_ in self.file_exclusion:
|
|
227
|
+
self.logger.info("Skipping '%s' honoring file exclusion", file_)
|
|
228
|
+
continue
|
|
188
229
|
if self.skip_dot_files and file_.startswith("."):
|
|
189
230
|
self.logger.info("Skipping dot file: %s", file_)
|
|
190
231
|
continue
|
|
191
232
|
file_path = os.path.join(__path, file_)
|
|
192
|
-
if self.
|
|
193
|
-
relative_path = file_path.replace(self.
|
|
233
|
+
if self.exclude_prefix:
|
|
234
|
+
relative_path = file_path.replace(self.exclude_prefix, "")
|
|
194
235
|
else:
|
|
195
236
|
relative_path = file_path
|
|
196
237
|
# Lists in python are ordered, so s3 prefix will get loaded first when provided
|
|
@@ -213,7 +254,7 @@ class Uploader:
|
|
|
213
254
|
self.logger.debug(keys)
|
|
214
255
|
self.logger.info("%d files from '%s' will be uploaded to '%s'", len(keys), self.upload_dir, self.bucket_name)
|
|
215
256
|
self.logger.info("Initiating upload process.")
|
|
216
|
-
for
|
|
257
|
+
for filepath, objectpath in tqdm(
|
|
217
258
|
keys.items(), total=len(keys), unit="file", leave=True, desc=f"Uploading files from {self.upload_dir}"
|
|
218
259
|
):
|
|
219
260
|
try:
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
s3/__init__.py,sha256=L12aFBb0plj8WISe0_He1vvQ55aOKd8VCyMlj_2LqrQ,66
|
|
2
|
-
s3/exceptions.py,sha256=hH3jlMOe8yjBatQK9EdndWZz4QESU74KSY_iDhQ37SY,2585
|
|
3
|
-
s3/logger.py,sha256=oH540oq8jY723jA4lDWlgfFPLbNgGXTkDwFpB7TLO_o,1196
|
|
4
|
-
s3/tree.py,sha256=DiQ2ekMMaj2m_P3-iKkEqSuJCJZ_UZxcAwHtAoPVa5c,1824
|
|
5
|
-
s3/uploader.py,sha256=I2An6Ix0rFMlvDLtLaDQ6F-YrN70IDCNFgh9E32cXHA,11489
|
|
6
|
-
s3/utils.py,sha256=NbF28CYviK_St5qd1EOumMVyus9BvQON7clUFeR_SEQ,4473
|
|
7
|
-
pys3uploader-0.2.2.dist-info/LICENSE,sha256=8k-hEraOzyum0GvmmK65YxNRTFXK7eIFHJ0OshJXeTk,1068
|
|
8
|
-
pys3uploader-0.2.2.dist-info/METADATA,sha256=fIlyxO6dFHYH3uhFg4yZa7RtGGBKqaOynAHlrbuffoY,7449
|
|
9
|
-
pys3uploader-0.2.2.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
|
|
10
|
-
pys3uploader-0.2.2.dist-info/top_level.txt,sha256=iQp4y1P58Q633gj8M08kHE4mqqT0hixuDWcniDk_RJ4,3
|
|
11
|
-
pys3uploader-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|