pypomes-s3 0.3.1__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.
- pypomes_s3-0.3.1/.gitignore +16 -0
- pypomes_s3-0.3.1/LICENSE +21 -0
- pypomes_s3-0.3.1/PKG-INFO +19 -0
- pypomes_s3-0.3.1/README.md +0 -0
- pypomes_s3-0.3.1/pyproject.toml +33 -0
- pypomes_s3-0.3.1/src/__init__.py +0 -0
- pypomes_s3-0.3.1/src/pypomes_s3/__init__.py +24 -0
- pypomes_s3-0.3.1/src/pypomes_s3/aws_pomes.py +42 -0
- pypomes_s3-0.3.1/src/pypomes_s3/minio_client.py +67 -0
- pypomes_s3-0.3.1/src/pypomes_s3/minio_pomes.py +578 -0
- pypomes_s3-0.3.1/src/pypomes_s3/s3_common.py +145 -0
- pypomes_s3-0.3.1/src/pypomes_s3/s3_pomes.py +89 -0
pypomes_s3-0.3.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 GT Nunes
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: pypomes_s3
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: A collection of Python pomes, pennyeach (S3 storage modules)
|
|
5
|
+
Project-URL: Homepage, https://github.com/TheWiseCoder/PyPomes-S3
|
|
6
|
+
Project-URL: Bug Tracker, https://github.com/TheWiseCoder/PyPomes-S3/issues
|
|
7
|
+
Author-email: GT Nunes <wisecoder01@gmail.com>
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: boto3>=1.34.108
|
|
14
|
+
Requires-Dist: minio>=7.2.5
|
|
15
|
+
Requires-Dist: pip>=24.0
|
|
16
|
+
Requires-Dist: pypomes-core>=1.0.4
|
|
17
|
+
Requires-Dist: setuptools>=68.0.0
|
|
18
|
+
Requires-Dist: unidecode>=1.3.6
|
|
19
|
+
Requires-Dist: wheel>=0.42.0
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [
|
|
3
|
+
"hatchling>=1.22.2"
|
|
4
|
+
]
|
|
5
|
+
build-backend = "hatchling.build"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "pypomes_s3"
|
|
9
|
+
version = "0.3.1"
|
|
10
|
+
authors = [
|
|
11
|
+
{ name="GT Nunes", email="wisecoder01@gmail.com" }
|
|
12
|
+
]
|
|
13
|
+
description = "A collection of Python pomes, pennyeach (S3 storage modules)"
|
|
14
|
+
readme = "README.md"
|
|
15
|
+
requires-python = ">=3.10"
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent"
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"boto3>=1.34.108",
|
|
23
|
+
"minio>=7.2.5",
|
|
24
|
+
"pip>=24.0",
|
|
25
|
+
"pypomes_core>=1.0.4",
|
|
26
|
+
"setuptools>=68.0.0",
|
|
27
|
+
"Unidecode>=1.3.6",
|
|
28
|
+
"wheel>=0.42.0"
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
"Homepage" = "https://github.com/TheWiseCoder/PyPomes-S3"
|
|
33
|
+
"Bug Tracker" = "https://github.com/TheWiseCoder/PyPomes-S3/issues"
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from .minio_client import (
|
|
2
|
+
MinioCM
|
|
3
|
+
)
|
|
4
|
+
from .minio_pomes import (
|
|
5
|
+
MINIO_BUCKET_NAME, MINIO_ENDPOINT_URL, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_SECURE_ACCESS, MINIO_TEMP_PATH,
|
|
6
|
+
minio_access, minio_file_store, minio_object_store, minio_object_stat,
|
|
7
|
+
minio_object_delete, minio_objects_list, minio_object_retrieve, minio_object_exists,
|
|
8
|
+
minio_object_tags_retrieve, minio_file_retrieve, minio_setup,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
# minio_client
|
|
13
|
+
"MinioCM",
|
|
14
|
+
# minio_pomes
|
|
15
|
+
"MINIO_BUCKET_NAME", "MINIO_ENDPOINT_URL", "MINIO_ACCESS_KEY",
|
|
16
|
+
"MINIO_SECRET_KEY", "MINIO_SECURE_ACCESS", "MINIO_TEMP_PATH",
|
|
17
|
+
"minio_access", "minio_file_store", "minio_object_store", "minio_object_stat",
|
|
18
|
+
"minio_object_delete", "minio_objects_list", "minio_object_retrieve", "minio_object_exists",
|
|
19
|
+
"minio_object_tags_retrieve", "minio_file_retrieve", "minio_setup",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
from importlib.metadata import version
|
|
23
|
+
__version__ = version("pypomes_s3")
|
|
24
|
+
__version_info__ = tuple(int(i) for i in __version__.split(".") if i.isdigit())
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# import pickle
|
|
2
|
+
# import uuid
|
|
3
|
+
from boto3.session import Session
|
|
4
|
+
from botocore.client import BaseClient
|
|
5
|
+
from logging import Logger
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from pypomes_core import (
|
|
8
|
+
APP_PREFIX, TEMP_DIR,
|
|
9
|
+
env_get_str, env_get_path
|
|
10
|
+
)
|
|
11
|
+
from typing import Final
|
|
12
|
+
# from unidecode import unidecode
|
|
13
|
+
|
|
14
|
+
S3_BUCKET_NAME: Final[str] = env_get_str(f"{APP_PREFIX}_S3_BUCKET_NAME")
|
|
15
|
+
S3_ACCESS_KEY: Final[str] = env_get_str(f"{APP_PREFIX}_S3_ACCESS_KEY")
|
|
16
|
+
S3_SECRET_KEY: Final[str] = env_get_str(f"{APP_PREFIX}_S3_SECRET_KEY")
|
|
17
|
+
S3_ENDPOINT_URL: Final[str] = env_get_str(f"{APP_PREFIX}_S3_ENDPOINT_URL")
|
|
18
|
+
S3_TEMP_PATH: Final[Path] = env_get_path(f"{APP_PREFIX}_S3_TEMP_PATH", TEMP_DIR)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def s3_access(errors: list[str],
|
|
22
|
+
logger: Logger = None) -> BaseClient:
|
|
23
|
+
"""
|
|
24
|
+
Obtain and return a *S3* client object.
|
|
25
|
+
|
|
26
|
+
:param errors: incidental error messages
|
|
27
|
+
:param logger: optional logger
|
|
28
|
+
:return: the S3 client object
|
|
29
|
+
"""
|
|
30
|
+
# initialize the return variable
|
|
31
|
+
result: BaseClient | None = None
|
|
32
|
+
|
|
33
|
+
try:
|
|
34
|
+
result = Session().client(service_name="s3",
|
|
35
|
+
aws_access_key_id=S3_ACCESS_KEY,
|
|
36
|
+
aws_secret_access_key=S3_SECRET_KEY,
|
|
37
|
+
endpoint_url=S3_ENDPOINT_URL)
|
|
38
|
+
except Exception as e:
|
|
39
|
+
errors.append(str(e))
|
|
40
|
+
logger.debug(e)
|
|
41
|
+
|
|
42
|
+
return result
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from minio import Minio
|
|
2
|
+
from minio.credentials.providers import Provider
|
|
3
|
+
from types import TracebackType
|
|
4
|
+
from urllib3 import PoolManager
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MinioCM:
|
|
8
|
+
"""
|
|
9
|
+
Simple Storage Service (*S3*) client, extended to provide context-managed operations.
|
|
10
|
+
|
|
11
|
+
Please, refer to the *MinIO* package documentation for details on its implementation.
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self,
|
|
14
|
+
endpoint: str,
|
|
15
|
+
access_key: str | None = None,
|
|
16
|
+
secret_key: str | None = None,
|
|
17
|
+
session_token: str | None = None,
|
|
18
|
+
secure: bool = True,
|
|
19
|
+
region: str | None = None,
|
|
20
|
+
http_client: PoolManager | None = None,
|
|
21
|
+
credentials: Provider | None = None,
|
|
22
|
+
cert_check: bool = True) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Extend *MinIO* package's implementation of a Simple Storage Service (*S3*) client, .
|
|
25
|
+
|
|
26
|
+
The aim is to provide context-managed *MinIO* clients.
|
|
27
|
+
Please, refer to the *MinIO* package documentation for details on its implementation.
|
|
28
|
+
|
|
29
|
+
:param endpoint: Hostname of a S3 service
|
|
30
|
+
:param access_key: Access key (aka user ID) of your account in S3 service
|
|
31
|
+
:param secret_key: Secret Key (aka password) of your account in S3 service
|
|
32
|
+
:param session_token: Session token of your account in S3 service
|
|
33
|
+
:param secure: Flag to indicate to use secure (TLS) connection to S3 service or not
|
|
34
|
+
:param region: Region name of buckets in S3 service
|
|
35
|
+
:param http_client: Customized HTTP client
|
|
36
|
+
:param credentials: Credentials provider of your account in S3 service
|
|
37
|
+
:param cert_check: Flag to indicate to verify SSL certificate or not
|
|
38
|
+
"""
|
|
39
|
+
self.minio_client: Minio = Minio(endpoint=endpoint,
|
|
40
|
+
access_key=access_key,
|
|
41
|
+
secret_key=secret_key,
|
|
42
|
+
session_token=session_token,
|
|
43
|
+
secure=secure,
|
|
44
|
+
region=region,
|
|
45
|
+
http_client=http_client,
|
|
46
|
+
credentials=credentials,
|
|
47
|
+
cert_check=cert_check)
|
|
48
|
+
|
|
49
|
+
def __enter__(self) -> Minio:
|
|
50
|
+
"""
|
|
51
|
+
First step in providing a context-managed *MinIO* client.
|
|
52
|
+
|
|
53
|
+
:return: a Minio client object
|
|
54
|
+
"""
|
|
55
|
+
return self.minio_client
|
|
56
|
+
|
|
57
|
+
def __exit__(self,
|
|
58
|
+
exception_type: type[BaseException],
|
|
59
|
+
exception_value: BaseException,
|
|
60
|
+
traceback: TracebackType) -> bool:
|
|
61
|
+
"""
|
|
62
|
+
Last step in providing a context-managed *MinIO* client.
|
|
63
|
+
"""
|
|
64
|
+
self.minio_client = None
|
|
65
|
+
|
|
66
|
+
# make sure an eventual exception is propagated
|
|
67
|
+
return exception_type is None
|
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
import uuid
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from logging import Logger
|
|
5
|
+
from minio import Minio
|
|
6
|
+
from minio.datatypes import Object as MinioObject
|
|
7
|
+
from minio.commonconfig import Tags
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from pypomes_core import (
|
|
10
|
+
APP_PREFIX, TEMP_DIR,
|
|
11
|
+
env_get_bool, env_get_str, env_get_path
|
|
12
|
+
)
|
|
13
|
+
from typing import Final
|
|
14
|
+
from unidecode import unidecode
|
|
15
|
+
|
|
16
|
+
MINIO_BUCKET_NAME: Final[str] = env_get_str(f"{APP_PREFIX}_MINIO_BUCKET_URL")
|
|
17
|
+
MINIO_ENDPOINT_URL: Final[str] = env_get_str(f"{APP_PREFIX}_MINIO_ENDPOINT_URL")
|
|
18
|
+
MINIO_ACCESS_KEY: Final[str] = env_get_str(f"{APP_PREFIX}_MINIO_ACCESS_KEY")
|
|
19
|
+
MINIO_SECRET_KEY: Final[str] = env_get_str(f"{APP_PREFIX}_MINIO_SECRET_KEY")
|
|
20
|
+
MINIO_SECURE_ACCESS: Final[bool] = env_get_bool(f"{APP_PREFIX}_MINIO_SECURE_ACCESS")
|
|
21
|
+
MINIO_TEMP_PATH: Final[Path] = env_get_path(f"{APP_PREFIX}_MINIO_TEMP_PATH", TEMP_DIR)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def minio_access(errors: list[str],
|
|
25
|
+
logger: Logger = None) -> Minio:
|
|
26
|
+
"""
|
|
27
|
+
Obtain and return a *MinIO* client object.
|
|
28
|
+
|
|
29
|
+
:param errors: incidental error messages
|
|
30
|
+
:param logger: optional logger
|
|
31
|
+
:return: the MinIO client object
|
|
32
|
+
"""
|
|
33
|
+
# initialize the return variable
|
|
34
|
+
result: Minio | None = None
|
|
35
|
+
|
|
36
|
+
# obtain the MinIO client
|
|
37
|
+
try:
|
|
38
|
+
result = Minio(endpoint=MINIO_ENDPOINT_URL,
|
|
39
|
+
access_key=MINIO_ACCESS_KEY,
|
|
40
|
+
secret_key=MINIO_SECRET_KEY,
|
|
41
|
+
secure=MINIO_SECURE_ACCESS)
|
|
42
|
+
if logger:
|
|
43
|
+
logger.debug("Minio client created")
|
|
44
|
+
|
|
45
|
+
except Exception as e:
|
|
46
|
+
__minio_except_msg(errors=errors,
|
|
47
|
+
exception=e,
|
|
48
|
+
logger=logger)
|
|
49
|
+
|
|
50
|
+
return result
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def minio_setup(errors: list[str],
|
|
54
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
55
|
+
client: Minio = None,
|
|
56
|
+
logger: Logger = None) -> bool:
|
|
57
|
+
"""
|
|
58
|
+
Prepare the *MinIO* client for operations.
|
|
59
|
+
|
|
60
|
+
This function should be called just once, at startup,
|
|
61
|
+
to make sure the interaction with the MinIo service is fully functional.
|
|
62
|
+
|
|
63
|
+
:param errors: incidental error messages
|
|
64
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
65
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
66
|
+
:param logger: optional logger
|
|
67
|
+
:return: True if service is fully functional
|
|
68
|
+
"""
|
|
69
|
+
# initialize the return variable
|
|
70
|
+
result: bool = False
|
|
71
|
+
|
|
72
|
+
# make sure to have a MinIO client
|
|
73
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
74
|
+
logger=logger)
|
|
75
|
+
# was the MinIO client obtained ?
|
|
76
|
+
if curr_client:
|
|
77
|
+
# yes, proceed
|
|
78
|
+
try:
|
|
79
|
+
if not curr_client.bucket_exists(bucket_name=bucket):
|
|
80
|
+
curr_client.make_bucket(bucket_name=bucket)
|
|
81
|
+
result = True
|
|
82
|
+
if logger:
|
|
83
|
+
logger.debug(f"Setup MinIO, endpoint={MINIO_ENDPOINT_URL}, bucket={bucket}, "
|
|
84
|
+
f"access key={MINIO_ACCESS_KEY}, secure={MINIO_SECURE_ACCESS}")
|
|
85
|
+
except Exception as e:
|
|
86
|
+
__minio_except_msg(errors=errors,
|
|
87
|
+
exception=e,
|
|
88
|
+
logger=logger)
|
|
89
|
+
|
|
90
|
+
return result
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def minio_file_store(errors: list[str],
|
|
94
|
+
basepath: Path | str,
|
|
95
|
+
identifier: str,
|
|
96
|
+
filepath: Path | str,
|
|
97
|
+
mimetype: str,
|
|
98
|
+
tags: dict = None,
|
|
99
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
100
|
+
client: Minio = None,
|
|
101
|
+
logger: Logger = None) -> None:
|
|
102
|
+
"""
|
|
103
|
+
Store a file at the *MinIO* store.
|
|
104
|
+
|
|
105
|
+
:param errors: incidental error messages
|
|
106
|
+
:param basepath: the path specifying the location to store the file at
|
|
107
|
+
:param identifier: the file identifier, tipically a file name
|
|
108
|
+
:param filepath: the path specifying where the file is
|
|
109
|
+
:param mimetype: the file mimetype
|
|
110
|
+
:param tags: optional metadata describing the file
|
|
111
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
112
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
113
|
+
:param logger: optional logger
|
|
114
|
+
"""
|
|
115
|
+
# make sure to have a MinIO client
|
|
116
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
117
|
+
logger=logger)
|
|
118
|
+
# was the MinIO client obtained ?
|
|
119
|
+
if curr_client:
|
|
120
|
+
# yes, proceed
|
|
121
|
+
remotepath: Path = Path(basepath) / identifier
|
|
122
|
+
# have tags been defined ?
|
|
123
|
+
if tags is None or len(tags) == 0:
|
|
124
|
+
# no
|
|
125
|
+
doc_tags = None
|
|
126
|
+
else:
|
|
127
|
+
# sim, store them
|
|
128
|
+
doc_tags = Tags(for_object=True)
|
|
129
|
+
for key, value in tags.items():
|
|
130
|
+
# normalize text, by removing all diacritics
|
|
131
|
+
doc_tags[key] = unidecode(value)
|
|
132
|
+
# store the file
|
|
133
|
+
try:
|
|
134
|
+
curr_client.fput_object(bucket_name=bucket,
|
|
135
|
+
object_name=f"{remotepath}",
|
|
136
|
+
file_path=filepath,
|
|
137
|
+
content_type=mimetype,
|
|
138
|
+
tags=doc_tags)
|
|
139
|
+
if logger:
|
|
140
|
+
logger.debug(f"Stored {remotepath}, "
|
|
141
|
+
f"content type {mimetype}, tags {tags}, bucket {bucket}")
|
|
142
|
+
except Exception as e:
|
|
143
|
+
__minio_except_msg(errors=errors,
|
|
144
|
+
exception=e,
|
|
145
|
+
logger=logger)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def minio_file_retrieve(errors: list[str],
|
|
149
|
+
basepath: Path | str,
|
|
150
|
+
identifier: str,
|
|
151
|
+
filepath: Path | str,
|
|
152
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
153
|
+
client: Minio = None,
|
|
154
|
+
logger: Logger = None) -> any:
|
|
155
|
+
"""
|
|
156
|
+
Retrieve a file from the *MinIO* store.
|
|
157
|
+
|
|
158
|
+
:param errors: incidental error messages
|
|
159
|
+
:param basepath: the path specifying the location to retrieve the file from
|
|
160
|
+
:param identifier: the file identifier, tipically a file name
|
|
161
|
+
:param filepath: the path to save the retrieved file at
|
|
162
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
163
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
164
|
+
:param logger: optional logger
|
|
165
|
+
:return: information about the file retrieved
|
|
166
|
+
"""
|
|
167
|
+
# initialize the return variable
|
|
168
|
+
result: any = None
|
|
169
|
+
|
|
170
|
+
# make sure to have a MinIO client
|
|
171
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
172
|
+
logger=logger)
|
|
173
|
+
# was the MinIO client obtained ?
|
|
174
|
+
if curr_client:
|
|
175
|
+
# yes, proceed
|
|
176
|
+
remotepath: Path = Path(basepath) / identifier
|
|
177
|
+
try:
|
|
178
|
+
result = curr_client.fget_object(bucket_name=bucket,
|
|
179
|
+
object_name=f"{remotepath}",
|
|
180
|
+
file_path=filepath)
|
|
181
|
+
if logger:
|
|
182
|
+
logger.debug(f"Retrieved {remotepath}, bucket {bucket}")
|
|
183
|
+
except Exception as e:
|
|
184
|
+
if not hasattr(e, "code") or e.code != "NoSuchKey":
|
|
185
|
+
__minio_except_msg(errors=errors,
|
|
186
|
+
exception=e,
|
|
187
|
+
logger=logger)
|
|
188
|
+
|
|
189
|
+
return result
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def minio_object_exists(errors: list[str],
|
|
193
|
+
basepath: Path | str,
|
|
194
|
+
identifier: str = None,
|
|
195
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
196
|
+
client: Minio = None,
|
|
197
|
+
logger: Logger = None) -> bool:
|
|
198
|
+
"""
|
|
199
|
+
Determine if a given object exists in the *MinIO* store.
|
|
200
|
+
|
|
201
|
+
:param errors: incidental error messages
|
|
202
|
+
:param basepath: the path specifying the location to locate the object at
|
|
203
|
+
:param identifier: the object identifier
|
|
204
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
205
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
206
|
+
:param logger: optional logger
|
|
207
|
+
:return: True if the object was found
|
|
208
|
+
"""
|
|
209
|
+
# initialize the return variable
|
|
210
|
+
result: bool = False
|
|
211
|
+
|
|
212
|
+
# make sure to have a MinIO client
|
|
213
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
214
|
+
logger=logger)
|
|
215
|
+
# proceed, if the MinIO client eas obtained
|
|
216
|
+
if curr_client:
|
|
217
|
+
# was the identifier provided ?
|
|
218
|
+
if identifier is None:
|
|
219
|
+
# no, object is a folder
|
|
220
|
+
objs: Iterator = minio_objects_list(errors=errors,
|
|
221
|
+
basepath=basepath,
|
|
222
|
+
recursive=False,
|
|
223
|
+
bucket=bucket,
|
|
224
|
+
client=curr_client,
|
|
225
|
+
logger=logger)
|
|
226
|
+
for _ in objs:
|
|
227
|
+
result = True
|
|
228
|
+
break
|
|
229
|
+
# verify the status of the object
|
|
230
|
+
elif minio_object_stat(errors=errors,
|
|
231
|
+
basepath=basepath,
|
|
232
|
+
identifier=identifier,
|
|
233
|
+
bucket=bucket,
|
|
234
|
+
client=curr_client,
|
|
235
|
+
logger=logger):
|
|
236
|
+
result = True
|
|
237
|
+
if logger:
|
|
238
|
+
remotepath: Path = Path(basepath) / identifier
|
|
239
|
+
existence: str = "exists" if result else "do not exist"
|
|
240
|
+
logger.debug(f"Object {remotepath}, bucket {bucket}, {existence}")
|
|
241
|
+
|
|
242
|
+
return result
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def minio_object_stat(errors: list[str],
|
|
246
|
+
basepath: Path | str,
|
|
247
|
+
identifier: str,
|
|
248
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
249
|
+
client: Minio = None,
|
|
250
|
+
logger: Logger = None) -> MinioObject:
|
|
251
|
+
"""
|
|
252
|
+
Retrieve and return the information about an object in the *MinIO* store.
|
|
253
|
+
|
|
254
|
+
:param errors: incidental error messages
|
|
255
|
+
:param basepath: the path specifying where to locate the object
|
|
256
|
+
:param identifier: the object identifier
|
|
257
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
258
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
259
|
+
:param logger: optional logger
|
|
260
|
+
:return: metadata and information about the object
|
|
261
|
+
"""
|
|
262
|
+
# initialize the return variable
|
|
263
|
+
result: MinioObject | None = None
|
|
264
|
+
|
|
265
|
+
# make sure to have a MinIO client
|
|
266
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
267
|
+
logger=logger)
|
|
268
|
+
# was the MinIO client obtained ?
|
|
269
|
+
if curr_client:
|
|
270
|
+
# yes, proceed
|
|
271
|
+
remotepath: Path = Path(basepath) / identifier
|
|
272
|
+
try:
|
|
273
|
+
result = curr_client.stat_object(bucket_name=bucket,
|
|
274
|
+
object_name=f"{remotepath}")
|
|
275
|
+
if logger:
|
|
276
|
+
logger.debug(f"Stat'ed {remotepath}, bucket {bucket}")
|
|
277
|
+
except Exception as e:
|
|
278
|
+
if not hasattr(e, "code") or e.code != "NoSuchKey":
|
|
279
|
+
__minio_except_msg(errors=errors,
|
|
280
|
+
exception=e,
|
|
281
|
+
logger=logger)
|
|
282
|
+
|
|
283
|
+
return result
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def minio_object_store(errors: list[str],
|
|
287
|
+
basepath: Path | str,
|
|
288
|
+
identifier: str,
|
|
289
|
+
obj: any,
|
|
290
|
+
tags: dict = None,
|
|
291
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
292
|
+
client: Minio = None,
|
|
293
|
+
logger: Logger = None) -> None:
|
|
294
|
+
"""
|
|
295
|
+
Store an object at the *MinIO* store.
|
|
296
|
+
|
|
297
|
+
:param errors: incidental error messages
|
|
298
|
+
:param basepath: the path specifying the location to store the object at
|
|
299
|
+
:param identifier: the object identifier
|
|
300
|
+
:param obj: object to be stored
|
|
301
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
302
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
303
|
+
:param logger: optional logger
|
|
304
|
+
:param tags: optional metadata describing the object
|
|
305
|
+
"""
|
|
306
|
+
# make sure to have a MinIO client
|
|
307
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
308
|
+
logger=logger)
|
|
309
|
+
# proceed, if the MinIO client was obtained
|
|
310
|
+
if curr_client:
|
|
311
|
+
# serialize the object into a file
|
|
312
|
+
filepath: Path = Path(MINIO_TEMP_PATH) / f"{uuid.uuid4()}.pickle"
|
|
313
|
+
with filepath.open("wb") as f:
|
|
314
|
+
pickle.dump(obj, f)
|
|
315
|
+
|
|
316
|
+
# store the file
|
|
317
|
+
op_errors: list[str] = []
|
|
318
|
+
minio_file_store(errors=op_errors,
|
|
319
|
+
basepath=basepath,
|
|
320
|
+
identifier=identifier,
|
|
321
|
+
filepath=filepath,
|
|
322
|
+
mimetype="application/octet-stream",
|
|
323
|
+
tags=tags,
|
|
324
|
+
bucket=bucket,
|
|
325
|
+
client=curr_client,
|
|
326
|
+
logger=logger)
|
|
327
|
+
|
|
328
|
+
# errors ?
|
|
329
|
+
if op_errors:
|
|
330
|
+
# yes, report them
|
|
331
|
+
errors.extend(op_errors)
|
|
332
|
+
storage: str = "Unable to store"
|
|
333
|
+
else:
|
|
334
|
+
# no, remove the file from the file system
|
|
335
|
+
filepath.unlink()
|
|
336
|
+
storage: str = "Stored "
|
|
337
|
+
|
|
338
|
+
if logger:
|
|
339
|
+
remotepath: Path = Path(basepath) / identifier
|
|
340
|
+
logger.debug(f"{storage} {remotepath}, bucket {bucket}")
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def minio_object_retrieve(errors: list[str],
|
|
344
|
+
basepath: Path,
|
|
345
|
+
identifier: str,
|
|
346
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
347
|
+
client: Minio = None,
|
|
348
|
+
logger: Logger = None) -> any:
|
|
349
|
+
"""
|
|
350
|
+
Retrieve an object from the *MinIO* store.
|
|
351
|
+
|
|
352
|
+
:param errors: incidental error messages
|
|
353
|
+
:param basepath: the path specifying the location to retrieve the object from
|
|
354
|
+
:param identifier: the object identifier
|
|
355
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
356
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
357
|
+
:param logger: optional logger
|
|
358
|
+
:return: the object retrieved
|
|
359
|
+
"""
|
|
360
|
+
# initialize the return variable
|
|
361
|
+
result: any = None
|
|
362
|
+
|
|
363
|
+
# make sure to have a MinIO client
|
|
364
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
365
|
+
logger=logger)
|
|
366
|
+
# proceed, if the MinIO client was obtained
|
|
367
|
+
if curr_client:
|
|
368
|
+
# retrieve the file containg the serialized object
|
|
369
|
+
filepath: Path = Path(MINIO_TEMP_PATH) / f"{uuid.uuid4()}.pickle"
|
|
370
|
+
stat: any = minio_file_retrieve(errors=errors,
|
|
371
|
+
basepath=basepath,
|
|
372
|
+
identifier=identifier,
|
|
373
|
+
filepath=filepath,
|
|
374
|
+
bucket=bucket,
|
|
375
|
+
client=curr_client,
|
|
376
|
+
logger=logger)
|
|
377
|
+
|
|
378
|
+
# was the file retrieved ?
|
|
379
|
+
if stat:
|
|
380
|
+
# yes, umarshall the corresponding object
|
|
381
|
+
with filepath.open("rb") as f:
|
|
382
|
+
result = pickle.load(f)
|
|
383
|
+
filepath.unlink()
|
|
384
|
+
|
|
385
|
+
if logger:
|
|
386
|
+
retrieval: str = "Retrieved" if result else "Unable to retrieve"
|
|
387
|
+
remotepath: Path = Path(basepath) / identifier
|
|
388
|
+
logger.debug(f"{retrieval} {remotepath}, bucket {bucket}")
|
|
389
|
+
|
|
390
|
+
return result
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def minio_object_delete(errors: list[str],
|
|
394
|
+
basepath: str,
|
|
395
|
+
identifier: str = None,
|
|
396
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
397
|
+
client: Minio = None,
|
|
398
|
+
logger: Logger = None) -> None:
|
|
399
|
+
"""
|
|
400
|
+
Remove an object from the *MinIO* store.
|
|
401
|
+
|
|
402
|
+
:param errors: incidental error messages
|
|
403
|
+
:param basepath: the path specifying the location to retrieve the object from
|
|
404
|
+
:param identifier: the object identifier
|
|
405
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
406
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
407
|
+
:param logger: optional logger
|
|
408
|
+
"""
|
|
409
|
+
# make sure to have a MinIO client
|
|
410
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
411
|
+
logger=logger)
|
|
412
|
+
# proceed, if the MinIO client was obtained
|
|
413
|
+
if curr_client:
|
|
414
|
+
# was the identifier provided ?
|
|
415
|
+
if identifier is None:
|
|
416
|
+
# no, remove the folder
|
|
417
|
+
__minio_folder_delete(errors=errors,
|
|
418
|
+
client=curr_client,
|
|
419
|
+
basepath=basepath,
|
|
420
|
+
bucket=bucket,
|
|
421
|
+
logger=logger)
|
|
422
|
+
else:
|
|
423
|
+
# yes, remove the object
|
|
424
|
+
remotepath: Path = Path(basepath) / identifier
|
|
425
|
+
try:
|
|
426
|
+
curr_client.remove_object(bucket_name=bucket,
|
|
427
|
+
object_name=f"{remotepath}")
|
|
428
|
+
if logger:
|
|
429
|
+
logger.debug(f"Deleted {remotepath}, bucket {bucket}")
|
|
430
|
+
except Exception as e:
|
|
431
|
+
if not hasattr(e, "code") or e.code != "NoSuchKey":
|
|
432
|
+
__minio_except_msg(errors=errors,
|
|
433
|
+
exception=e,
|
|
434
|
+
logger=logger)
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
# recupera as tags do objeto
|
|
438
|
+
def minio_object_tags_retrieve(errors: list[str],
|
|
439
|
+
basepath: str,
|
|
440
|
+
identifier: str,
|
|
441
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
442
|
+
client: Minio = None,
|
|
443
|
+
logger: Logger = None) -> dict:
|
|
444
|
+
"""
|
|
445
|
+
Retrieve and return the metadata information for an object in the *MinIO* store.
|
|
446
|
+
|
|
447
|
+
:param errors: incidental error messages
|
|
448
|
+
:param basepath: the path specifying the location to retrieve the object from
|
|
449
|
+
:param identifier: the object identifier
|
|
450
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
451
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
452
|
+
:param logger: optional logger
|
|
453
|
+
:return: the metadata about the object
|
|
454
|
+
"""
|
|
455
|
+
# initialize the return variable
|
|
456
|
+
result: dict | None = None
|
|
457
|
+
|
|
458
|
+
# make sure to have a MinIO client
|
|
459
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
460
|
+
logger=logger)
|
|
461
|
+
# was the MinIO client obtained ?
|
|
462
|
+
if curr_client:
|
|
463
|
+
# yes, proceed
|
|
464
|
+
remotepath: Path = Path(basepath) / identifier
|
|
465
|
+
try:
|
|
466
|
+
tags: Tags = curr_client.get_object_tags(bucket_name=bucket,
|
|
467
|
+
object_name=f"{remotepath}")
|
|
468
|
+
if tags is not None and len(tags) > 0:
|
|
469
|
+
result = {}
|
|
470
|
+
for key, value in tags.items():
|
|
471
|
+
result[key] = value
|
|
472
|
+
if logger:
|
|
473
|
+
logger.debug(f"Retrieved {remotepath}, bucket {bucket}, tags {result}")
|
|
474
|
+
except Exception as e:
|
|
475
|
+
if not hasattr(e, "code") or e.code != "NoSuchKey":
|
|
476
|
+
__minio_except_msg(errors=errors,
|
|
477
|
+
exception=e,
|
|
478
|
+
logger=logger)
|
|
479
|
+
|
|
480
|
+
return result
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def minio_objects_list(errors: list[str],
|
|
484
|
+
basepath: str,
|
|
485
|
+
recursive: bool = False,
|
|
486
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
487
|
+
client: Minio = None,
|
|
488
|
+
logger: Logger = None) -> Iterator:
|
|
489
|
+
"""
|
|
490
|
+
Retrieve and return an iterator into the list of objects at *basepath*, in the *MinIO* store.
|
|
491
|
+
|
|
492
|
+
:param errors: incidental error messages
|
|
493
|
+
:param basepath: the path specifying the location to iterate from
|
|
494
|
+
:param recursive: whether the location is iterated recursively
|
|
495
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
496
|
+
:param client: optional MinIO client (obtains a new one, if not provided)
|
|
497
|
+
:param logger: optional logger
|
|
498
|
+
:return: the iterator into the list of objects, 'None' if the folder does not exist
|
|
499
|
+
"""
|
|
500
|
+
# initialize the return variable
|
|
501
|
+
result: Iterator | None = None
|
|
502
|
+
|
|
503
|
+
# make sure to have a MinIO client
|
|
504
|
+
curr_client: Minio = client or minio_access(errors=errors,
|
|
505
|
+
logger=logger)
|
|
506
|
+
# was the MinIO client obtained ?
|
|
507
|
+
if curr_client:
|
|
508
|
+
# yes, proceed
|
|
509
|
+
try:
|
|
510
|
+
result = curr_client.list_objects(bucket_name=bucket,
|
|
511
|
+
prefix=basepath,
|
|
512
|
+
recursive=recursive)
|
|
513
|
+
if logger:
|
|
514
|
+
logger.debug(f"Listed {basepath}, bucket {bucket}")
|
|
515
|
+
except Exception as e:
|
|
516
|
+
__minio_except_msg(errors=errors,
|
|
517
|
+
exception=e,
|
|
518
|
+
logger=logger)
|
|
519
|
+
|
|
520
|
+
return result
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def __minio_folder_delete(errors: list[str],
|
|
524
|
+
client: Minio,
|
|
525
|
+
basepath: str,
|
|
526
|
+
bucket: str = MINIO_BUCKET_NAME,
|
|
527
|
+
logger: Logger = None) -> None:
|
|
528
|
+
"""
|
|
529
|
+
Traverse the folders recursively, removing its objects.
|
|
530
|
+
|
|
531
|
+
:param errors: incidental error messages
|
|
532
|
+
:param client: the MinIO client object
|
|
533
|
+
:param basepath: the path specifying the location to delete the objects at
|
|
534
|
+
:param bucket: the bucket to use (defaults to MINIO_BUCKET)
|
|
535
|
+
:param logger: optional logger
|
|
536
|
+
"""
|
|
537
|
+
# obtain the list of entries in the given folder
|
|
538
|
+
objs: Iterator = minio_objects_list(errors=errors,
|
|
539
|
+
basepath=basepath,
|
|
540
|
+
recursive=True,
|
|
541
|
+
bucket=bucket,
|
|
542
|
+
logger=logger)
|
|
543
|
+
# was the list obtained ?
|
|
544
|
+
if objs:
|
|
545
|
+
# yes, proceed
|
|
546
|
+
for obj in objs:
|
|
547
|
+
try:
|
|
548
|
+
client.remove_object(bucket_name=bucket,
|
|
549
|
+
object_name=obj.object_name)
|
|
550
|
+
except Exception as e:
|
|
551
|
+
# SANITY CHECK: in case of concurrent exclusion
|
|
552
|
+
if not hasattr(e, "code") or e.code != "NoSuchKey":
|
|
553
|
+
__minio_except_msg(errors=errors,
|
|
554
|
+
exception=e,
|
|
555
|
+
logger=logger)
|
|
556
|
+
if logger:
|
|
557
|
+
logger.debug(msg=f"Removed folder {basepath}, bucket {bucket}")
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def __minio_except_msg(errors: list[str],
|
|
561
|
+
exception: Exception,
|
|
562
|
+
logger: Logger) -> None:
|
|
563
|
+
"""
|
|
564
|
+
Format and return an error message from *exception*.
|
|
565
|
+
|
|
566
|
+
:param errors: incidental error messages
|
|
567
|
+
:param exception: the reference exception
|
|
568
|
+
:param logger: optional logger
|
|
569
|
+
:return: the error message
|
|
570
|
+
"""
|
|
571
|
+
# interaction with MinIO raised the exception "<class 'exception_class'>"
|
|
572
|
+
cls: str = str(exception.__class__)
|
|
573
|
+
exc_msg: str = f"{cls[7:-1]} - {exception}"
|
|
574
|
+
err_msg: str = f"Error accessing the object storer: {exc_msg}"
|
|
575
|
+
if errors:
|
|
576
|
+
errors.append(err_msg)
|
|
577
|
+
if logger:
|
|
578
|
+
logger.error(msg=err_msg)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from logging import DEBUG, Logger
|
|
2
|
+
from pypomes_core import (
|
|
3
|
+
APP_PREFIX,
|
|
4
|
+
env_get_str, str_sanitize, str_get_positional
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
# the preferred way to specify S3 storage parameters is dynamically with 's3_setup_params'
|
|
8
|
+
# specifying S3 storage parameters with environment variables can be done in two ways:
|
|
9
|
+
# 1. specify the set
|
|
10
|
+
# {APP_PREFIX}_S3_ENGINE (one of 'aws', 'minio')
|
|
11
|
+
# {APP_PREFIX}_S3_ACCESS_KEY
|
|
12
|
+
# {APP_PREFIX}_S3_SECRET_KEY
|
|
13
|
+
# {APP_PREFIX}_S3_BUCKET_NAME
|
|
14
|
+
# {APP_PREFIX}_S3_TEMP_PATH
|
|
15
|
+
# {APP_PREFIX}_S3_REGION_NAME (for aws)
|
|
16
|
+
# {APP_PREFIX}_S3_ENDPOINT_URL (for minio)
|
|
17
|
+
# {APP_PREFIX}_S3_SECURE_ACCESS (for minio)
|
|
18
|
+
# 2. alternatively, specify a comma-separated list of servers in
|
|
19
|
+
# {APP_PREFIX}_S3_ENGINES
|
|
20
|
+
# and for each engine, specify the set above, replacing 'S3' with
|
|
21
|
+
# 'AWS' and 'MINIO', respectively for the engines listed above
|
|
22
|
+
|
|
23
|
+
_S3_ACCESS_DATA: dict = {}
|
|
24
|
+
_S3_ENGINES: list[str] = []
|
|
25
|
+
if env_get_str(f"{APP_PREFIX}_S3_ENGINE", None):
|
|
26
|
+
_default_setup: bool = True
|
|
27
|
+
_S3_ENGINES.append(env_get_str(f"{APP_PREFIX}_S3_ENGINE"))
|
|
28
|
+
else:
|
|
29
|
+
_default_setup: bool = False
|
|
30
|
+
_engines: str = env_get_str(f"{APP_PREFIX}_S3_ENGINES", None)
|
|
31
|
+
if _engines:
|
|
32
|
+
_S3_ENGINES.extend(_engines.split(sep=","))
|
|
33
|
+
for engine in _S3_ENGINES:
|
|
34
|
+
if _default_setup:
|
|
35
|
+
_tag = "S3"
|
|
36
|
+
_default_setup = False
|
|
37
|
+
else:
|
|
38
|
+
_tag: str = str_get_positional(source=engine,
|
|
39
|
+
list_origin=["aws", "minio"],
|
|
40
|
+
list_dest=["AWS", "MINIO"])
|
|
41
|
+
_s3_data = {
|
|
42
|
+
"access-key": env_get_str(f"{APP_PREFIX}_{_tag}_ACESS_KEY"),
|
|
43
|
+
"secret-key": env_get_str(f"{APP_PREFIX}_{_tag}_SECRET_KEY"),
|
|
44
|
+
"bucket-name": env_get_str(f"{APP_PREFIX}_{_tag}_BUCKET_NAME"),
|
|
45
|
+
"temp-path": env_get_str(f"{APP_PREFIX}_{_tag}_TEMP_PATH")
|
|
46
|
+
}
|
|
47
|
+
if engine == "aws":
|
|
48
|
+
_s3_data["client"] = env_get_str(f"{APP_PREFIX}_{_tag}_REGION_NAME")
|
|
49
|
+
elif engine == "minio":
|
|
50
|
+
_s3_data["endpoit-url"] = env_get_str(f"{APP_PREFIX}_{_tag}_ENDPOINT_URL")
|
|
51
|
+
_s3_data["secure-access"] = env_get_str(f"{APP_PREFIX}_{_tag}_SECURE_ACCESS")
|
|
52
|
+
_S3_ACCESS_DATA[engine] = _s3_data
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _assert_engine(errors: list[str],
|
|
56
|
+
engine: str) -> str:
|
|
57
|
+
"""
|
|
58
|
+
Verify if *engine* is in the list of supported engines.
|
|
59
|
+
|
|
60
|
+
If *engine* is a supported engine, it is returned. If its value is 'None',
|
|
61
|
+
the first engine in the list of supported engines (the default engine) is returned.
|
|
62
|
+
|
|
63
|
+
:param errors: incidental errors
|
|
64
|
+
:param engine: the reference database engine
|
|
65
|
+
:return: the validated or default engine
|
|
66
|
+
"""
|
|
67
|
+
# initialize the return valiable
|
|
68
|
+
result: str | None = None
|
|
69
|
+
|
|
70
|
+
if not engine and _S3_ENGINES:
|
|
71
|
+
result = _S3_ENGINES[0]
|
|
72
|
+
elif engine in _S3_ENGINES:
|
|
73
|
+
result = engine
|
|
74
|
+
else:
|
|
75
|
+
err_msg = f"S3 engine '{engine}' unknown or not configured"
|
|
76
|
+
errors.append(err_msg)
|
|
77
|
+
|
|
78
|
+
return result
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _s3_get_params(engine: str) -> tuple:
|
|
82
|
+
"""
|
|
83
|
+
Return the current parameters being used for *engine*.
|
|
84
|
+
|
|
85
|
+
The parameters are returned as a *tuple*, with the elements
|
|
86
|
+
*access-key*, *secret-key*, *bucket-name*, *temp-path*.
|
|
87
|
+
For *aws* engines, the extra element *region-name* is returned.
|
|
88
|
+
for *minio* engines, the elements *endpoint-url* and *secure-access are returned.
|
|
89
|
+
The meaning of some parameters may vary between different S3 engines.
|
|
90
|
+
|
|
91
|
+
:param engine: the reference database engine
|
|
92
|
+
:return: the current parameters for the engine
|
|
93
|
+
"""
|
|
94
|
+
access_key: str = _S3_ACCESS_DATA[engine]["access-key"]
|
|
95
|
+
secret_key: str = _S3_ACCESS_DATA[engine]["secret-key"]
|
|
96
|
+
bucket_name: str = _S3_ACCESS_DATA[engine]["bucket-name"]
|
|
97
|
+
temp_path = _S3_ACCESS_DATA[engine]["temp-path"]
|
|
98
|
+
|
|
99
|
+
result: tuple | None = None
|
|
100
|
+
if engine == "aws":
|
|
101
|
+
region_name: str = _S3_ACCESS_DATA[engine]["region-name"]
|
|
102
|
+
result = (access_key, secret_key, bucket_name, temp_path, region_name)
|
|
103
|
+
elif engine == "minio":
|
|
104
|
+
endpoint_url: str = _S3_ACCESS_DATA[engine]["endpoint-url"]
|
|
105
|
+
secure_access: bool = _S3_ACCESS_DATA[engine]["secure-access"]
|
|
106
|
+
result = (access_key, secret_key, bucket_name, temp_path, endpoint_url, secure_access)
|
|
107
|
+
|
|
108
|
+
return result
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _db_except_msg(exception: Exception,
|
|
112
|
+
engine: str) -> str:
|
|
113
|
+
"""
|
|
114
|
+
Format and return the error message corresponding to the exception raised while accessing the database.
|
|
115
|
+
|
|
116
|
+
:param exception: the exception raised
|
|
117
|
+
:param engine: the reference database engine
|
|
118
|
+
:return: the formatted error message
|
|
119
|
+
"""
|
|
120
|
+
endpoint: str = _S3_ACCESS_DATA[engine].get("region-name") if engine == "aws" else \
|
|
121
|
+
_S3_ACCESS_DATA[engine].get("endpoint-url")
|
|
122
|
+
return f"Error accessing '{engine}' at '{endpoint}': {str_sanitize(f'{exception}')}"
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _s3_log(logger: Logger,
|
|
126
|
+
err_msg: str = None,
|
|
127
|
+
level: int = DEBUG,
|
|
128
|
+
errors: list[str] = None,
|
|
129
|
+
stmt: str = None) -> None:
|
|
130
|
+
"""
|
|
131
|
+
Log *err_msg* and add it to *errors*, or else log *stmt*, whichever is applicable.
|
|
132
|
+
|
|
133
|
+
:param logger: the logger object
|
|
134
|
+
:param err_msg: the error message to log
|
|
135
|
+
:param level: log level (defaults to DEBUG)
|
|
136
|
+
:param errors: optional incidental errors
|
|
137
|
+
:param stmt: optional statement
|
|
138
|
+
"""
|
|
139
|
+
if err_msg:
|
|
140
|
+
if logger:
|
|
141
|
+
logger.log(level, err_msg)
|
|
142
|
+
if isinstance(errors, list):
|
|
143
|
+
errors.append(err_msg)
|
|
144
|
+
if logger and stmt:
|
|
145
|
+
logger.log(level, stmt)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from logging import Logger
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .s3_common import (
|
|
6
|
+
_S3_ENGINES, _S3_ACCESS_DATA, _assert_engine
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def db_setup(engine: str,
|
|
11
|
+
access_key: str,
|
|
12
|
+
access_secret: str,
|
|
13
|
+
bucket_name: str,
|
|
14
|
+
temp_path: str | Path,
|
|
15
|
+
region_name: str = None,
|
|
16
|
+
endpoint_url: str = None,
|
|
17
|
+
secure_access: bool = None) -> bool:
|
|
18
|
+
"""
|
|
19
|
+
Establish the provided parameters for access to *engine*.
|
|
20
|
+
|
|
21
|
+
The meaning of some parameters may vary between different database engines.
|
|
22
|
+
All parameters, with the exception of *db_client* and *db_driver*, are required.
|
|
23
|
+
*db_client* may be provided for *oracle*, but not for the other engines.
|
|
24
|
+
*db_driver* is required for *sqlserver*, but is not accepted for the other engines.
|
|
25
|
+
|
|
26
|
+
:param engine: the S3 engine (one of [aws, minio])
|
|
27
|
+
:param access_key: the access key for the service
|
|
28
|
+
:param access_secret: the access secret code
|
|
29
|
+
:param bucket_name: the name of the default bucket
|
|
30
|
+
:param temp_path: path for temporary files
|
|
31
|
+
:param region_name: the name of the region where the engine is located (AWS only)
|
|
32
|
+
:param endpoint_url: the access URL for the service (MinIO only)
|
|
33
|
+
:param secure_access: whether or not to use Transport Security Layer (MinIO only)
|
|
34
|
+
:return: True if the data was accepted, False otherwise
|
|
35
|
+
"""
|
|
36
|
+
# initialize the return variable
|
|
37
|
+
result: bool = False
|
|
38
|
+
|
|
39
|
+
# are the parameters compliant ?
|
|
40
|
+
if (engine in ["aws", "minio"] and
|
|
41
|
+
access_key and access_secret and bucket_name and temp_path and
|
|
42
|
+
not (engine != "aws" and region_name) and
|
|
43
|
+
not (engine == "aws" and not region_name) and
|
|
44
|
+
not (engine != "minio" and endpoint_url) and
|
|
45
|
+
not (engine == "minio" and not endpoint_url) and
|
|
46
|
+
not (engine != "minio" and secure_access is not None) and
|
|
47
|
+
not (engine == "minio" and secure_access is None)):
|
|
48
|
+
_S3_ACCESS_DATA[engine] = {
|
|
49
|
+
"access-key": access_key,
|
|
50
|
+
"access-secret": access_secret,
|
|
51
|
+
"bucket-name": bucket_name,
|
|
52
|
+
"temp-path": temp_path
|
|
53
|
+
}
|
|
54
|
+
if engine == "aws":
|
|
55
|
+
_S3_ACCESS_DATA[engine]["region-name"] = region_name
|
|
56
|
+
elif engine == "minio":
|
|
57
|
+
_S3_ACCESS_DATA[engine]["endpoint-url"] = endpoint_url
|
|
58
|
+
_S3_ACCESS_DATA[engine]["secure-access"] = secure_access
|
|
59
|
+
if engine not in _S3_ENGINES:
|
|
60
|
+
_S3_ENGINES.append(engine)
|
|
61
|
+
result = True
|
|
62
|
+
|
|
63
|
+
return result
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def s3_get_engines() -> list[str]:
|
|
67
|
+
"""
|
|
68
|
+
Retrieve and return the list of configured engines.
|
|
69
|
+
|
|
70
|
+
This list may include any of the supported engines:
|
|
71
|
+
*aws*, *minio*.
|
|
72
|
+
|
|
73
|
+
:return: the list of configured engines
|
|
74
|
+
"""
|
|
75
|
+
return _S3_ENGINES
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def db_get_params(engine: str = None) -> dict:
|
|
79
|
+
"""
|
|
80
|
+
Return the connection parameters a *dict*.
|
|
81
|
+
|
|
82
|
+
The returned *dict* contains the keys *name*, *user*, *pwd*, *host*, *port*.
|
|
83
|
+
The meaning of these parameters may vary between different database engines.
|
|
84
|
+
|
|
85
|
+
:param engine: the database engine
|
|
86
|
+
:return: the current connection parameters for the engine
|
|
87
|
+
"""
|
|
88
|
+
curr_engine: str = _S3_ENGINES[0] if not engine and _S3_ENGINES else engine
|
|
89
|
+
return _S3_ACCESS_DATA.get(engine or curr_engine)
|