maleo-foundation 0.3.45__py3-none-any.whl → 0.3.47__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.
- maleo_foundation/authentication.py +24 -13
- maleo_foundation/authorization.py +2 -1
- maleo_foundation/client/manager.py +22 -21
- maleo_foundation/client/services/__init__.py +16 -7
- maleo_foundation/client/services/encryption/__init__.py +13 -4
- maleo_foundation/client/services/encryption/aes.py +41 -36
- maleo_foundation/client/services/encryption/rsa.py +50 -50
- maleo_foundation/client/services/hash/__init__.py +19 -6
- maleo_foundation/client/services/hash/bcrypt.py +20 -18
- maleo_foundation/client/services/hash/hmac.py +20 -17
- maleo_foundation/client/services/hash/sha256.py +18 -15
- maleo_foundation/client/services/key.py +50 -42
- maleo_foundation/client/services/signature.py +46 -42
- maleo_foundation/client/services/token.py +49 -58
- maleo_foundation/constants.py +13 -14
- maleo_foundation/enums.py +14 -13
- maleo_foundation/expanded_types/__init__.py +2 -3
- maleo_foundation/expanded_types/client.py +30 -34
- maleo_foundation/expanded_types/encryption/__init__.py +2 -1
- maleo_foundation/expanded_types/encryption/aes.py +7 -5
- maleo_foundation/expanded_types/encryption/rsa.py +7 -5
- maleo_foundation/expanded_types/general.py +13 -11
- maleo_foundation/expanded_types/hash.py +7 -5
- maleo_foundation/expanded_types/key.py +8 -6
- maleo_foundation/expanded_types/service.py +30 -34
- maleo_foundation/expanded_types/signature.py +7 -5
- maleo_foundation/expanded_types/token.py +7 -5
- maleo_foundation/extended_types.py +4 -3
- maleo_foundation/managers/cache.py +2 -1
- maleo_foundation/managers/client/base.py +25 -12
- maleo_foundation/managers/client/google/base.py +11 -4
- maleo_foundation/managers/client/google/parameter.py +9 -11
- maleo_foundation/managers/client/google/secret.py +53 -35
- maleo_foundation/managers/client/google/storage.py +52 -22
- maleo_foundation/managers/client/google/subscription.py +37 -39
- maleo_foundation/managers/client/maleo.py +18 -23
- maleo_foundation/managers/configuration.py +5 -9
- maleo_foundation/managers/credential.py +14 -17
- maleo_foundation/managers/db.py +51 -40
- maleo_foundation/managers/middleware.py +9 -9
- maleo_foundation/managers/service.py +47 -54
- maleo_foundation/middlewares/authentication.py +29 -54
- maleo_foundation/middlewares/base.py +83 -72
- maleo_foundation/middlewares/cors.py +8 -7
- maleo_foundation/models/__init__.py +2 -1
- maleo_foundation/models/responses.py +57 -29
- maleo_foundation/models/schemas/__init__.py +2 -1
- maleo_foundation/models/schemas/encryption.py +5 -2
- maleo_foundation/models/schemas/general.py +38 -18
- maleo_foundation/models/schemas/hash.py +2 -1
- maleo_foundation/models/schemas/key.py +5 -2
- maleo_foundation/models/schemas/parameter.py +45 -15
- maleo_foundation/models/schemas/result.py +35 -20
- maleo_foundation/models/schemas/signature.py +5 -2
- maleo_foundation/models/schemas/token.py +5 -2
- maleo_foundation/models/table.py +33 -27
- maleo_foundation/models/transfers/__init__.py +2 -1
- maleo_foundation/models/transfers/general/__init__.py +2 -1
- maleo_foundation/models/transfers/general/configurations/__init__.py +10 -4
- maleo_foundation/models/transfers/general/configurations/cache/__init__.py +3 -2
- maleo_foundation/models/transfers/general/configurations/cache/redis.py +13 -5
- maleo_foundation/models/transfers/general/configurations/client/__init__.py +5 -1
- maleo_foundation/models/transfers/general/configurations/client/maleo.py +38 -12
- maleo_foundation/models/transfers/general/configurations/database.py +5 -2
- maleo_foundation/models/transfers/general/configurations/middleware.py +22 -15
- maleo_foundation/models/transfers/general/configurations/service.py +2 -1
- maleo_foundation/models/transfers/general/credentials.py +2 -1
- maleo_foundation/models/transfers/general/database.py +11 -4
- maleo_foundation/models/transfers/general/key.py +13 -4
- maleo_foundation/models/transfers/general/request.py +28 -9
- maleo_foundation/models/transfers/general/settings.py +12 -22
- maleo_foundation/models/transfers/general/signature.py +4 -2
- maleo_foundation/models/transfers/general/token.py +34 -27
- maleo_foundation/models/transfers/parameters/__init__.py +2 -1
- maleo_foundation/models/transfers/parameters/client.py +15 -19
- maleo_foundation/models/transfers/parameters/encryption/__init__.py +2 -1
- maleo_foundation/models/transfers/parameters/encryption/aes.py +7 -5
- maleo_foundation/models/transfers/parameters/encryption/rsa.py +7 -5
- maleo_foundation/models/transfers/parameters/general.py +15 -13
- maleo_foundation/models/transfers/parameters/hash/__init__.py +2 -1
- maleo_foundation/models/transfers/parameters/hash/bcrypt.py +5 -5
- maleo_foundation/models/transfers/parameters/hash/hmac.py +6 -6
- maleo_foundation/models/transfers/parameters/hash/sha256.py +5 -5
- maleo_foundation/models/transfers/parameters/key.py +9 -8
- maleo_foundation/models/transfers/parameters/service.py +42 -48
- maleo_foundation/models/transfers/parameters/signature.py +7 -4
- maleo_foundation/models/transfers/parameters/token.py +10 -10
- maleo_foundation/models/transfers/results/__init__.py +2 -1
- maleo_foundation/models/transfers/results/client/__init__.py +2 -1
- maleo_foundation/models/transfers/results/client/controllers/__init__.py +2 -1
- maleo_foundation/models/transfers/results/client/controllers/http.py +10 -7
- maleo_foundation/models/transfers/results/client/service.py +12 -6
- maleo_foundation/models/transfers/results/encryption/__init__.py +2 -1
- maleo_foundation/models/transfers/results/encryption/aes.py +13 -5
- maleo_foundation/models/transfers/results/encryption/rsa.py +12 -4
- maleo_foundation/models/transfers/results/hash.py +7 -3
- maleo_foundation/models/transfers/results/key.py +18 -6
- maleo_foundation/models/transfers/results/service/__init__.py +2 -3
- maleo_foundation/models/transfers/results/service/controllers/__init__.py +2 -1
- maleo_foundation/models/transfers/results/service/controllers/rest.py +14 -11
- maleo_foundation/models/transfers/results/service/general.py +16 -10
- maleo_foundation/models/transfers/results/signature.py +12 -4
- maleo_foundation/models/transfers/results/token.py +10 -4
- maleo_foundation/rest_controller_result.py +23 -21
- maleo_foundation/types.py +15 -14
- maleo_foundation/utils/__init__.py +2 -1
- maleo_foundation/utils/cache.py +10 -13
- maleo_foundation/utils/client.py +25 -12
- maleo_foundation/utils/controller.py +59 -37
- maleo_foundation/utils/dependencies/__init__.py +2 -1
- maleo_foundation/utils/dependencies/auth.py +5 -12
- maleo_foundation/utils/dependencies/context.py +3 -4
- maleo_foundation/utils/exceptions.py +50 -28
- maleo_foundation/utils/extractor.py +18 -6
- maleo_foundation/utils/formatter/__init__.py +2 -1
- maleo_foundation/utils/formatter/case.py +5 -4
- maleo_foundation/utils/loaders/__init__.py +2 -1
- maleo_foundation/utils/loaders/credential/__init__.py +2 -1
- maleo_foundation/utils/loaders/credential/google.py +29 -15
- maleo_foundation/utils/loaders/json.py +3 -2
- maleo_foundation/utils/loaders/key/__init__.py +2 -1
- maleo_foundation/utils/loaders/key/rsa.py +26 -13
- maleo_foundation/utils/loaders/yaml.py +2 -1
- maleo_foundation/utils/logging.py +70 -46
- maleo_foundation/utils/merger.py +7 -9
- maleo_foundation/utils/query.py +41 -34
- maleo_foundation/utils/repository.py +28 -13
- maleo_foundation/utils/searcher.py +4 -6
- {maleo_foundation-0.3.45.dist-info → maleo_foundation-0.3.47.dist-info}/METADATA +14 -1
- maleo_foundation-0.3.47.dist-info/RECORD +137 -0
- maleo_foundation/expanded_types/repository.py +0 -68
- maleo_foundation/models/transfers/results/service/repository.py +0 -39
- maleo_foundation-0.3.45.dist-info/RECORD +0 -139
- {maleo_foundation-0.3.45.dist-info → maleo_foundation-0.3.47.dist-info}/WHEEL +0 -0
- {maleo_foundation-0.3.45.dist-info → maleo_foundation-0.3.47.dist-info}/top_level.txt +0 -0
@@ -6,6 +6,7 @@ from maleo_foundation.managers.client.base import ClientManager
|
|
6
6
|
from maleo_foundation.utils.loaders.credential.google import GoogleCredentialsLoader
|
7
7
|
from maleo_foundation.utils.logging import SimpleConfig
|
8
8
|
|
9
|
+
|
9
10
|
class GoogleClientManager(ClientManager):
|
10
11
|
def __init__(
|
11
12
|
self,
|
@@ -14,11 +15,15 @@ class GoogleClientManager(ClientManager):
|
|
14
15
|
log_config: SimpleConfig,
|
15
16
|
service_key: BaseTypes.OptionalString = None,
|
16
17
|
credentials: Optional[Credentials] = None,
|
17
|
-
credentials_path: Optional[Union[Path, str]] = None
|
18
|
+
credentials_path: Optional[Union[Path, str]] = None,
|
18
19
|
) -> None:
|
19
20
|
super().__init__(key, name, log_config, service_key)
|
20
|
-
if (credentials is not None and credentials_path is not None) or (
|
21
|
-
|
21
|
+
if (credentials is not None and credentials_path is not None) or (
|
22
|
+
credentials is None and credentials_path is None
|
23
|
+
):
|
24
|
+
raise ValueError(
|
25
|
+
"Only either 'credentials' or 'credentials_path' can be passed as parameter"
|
26
|
+
)
|
22
27
|
|
23
28
|
if credentials is not None:
|
24
29
|
self._credentials = credentials
|
@@ -33,4 +38,6 @@ class GoogleClientManager(ClientManager):
|
|
33
38
|
|
34
39
|
@property
|
35
40
|
def project_id(self) -> str:
|
36
|
-
|
41
|
+
if self._project_id is None:
|
42
|
+
raise ValueError("Project ID has not been initialized.")
|
43
|
+
return self._project_id
|
@@ -1,5 +1,3 @@
|
|
1
|
-
from google.api_core import retry
|
2
|
-
from google.api_core.exceptions import NotFound
|
3
1
|
from google.cloud import parametermanager
|
4
2
|
from google.oauth2.service_account import Credentials
|
5
3
|
from pathlib import Path
|
@@ -8,33 +6,33 @@ from maleo_foundation.types import BaseTypes
|
|
8
6
|
from maleo_foundation.utils.logging import SimpleConfig
|
9
7
|
from .base import GoogleClientManager
|
10
8
|
|
9
|
+
|
11
10
|
class GoogleParameterManager(GoogleClientManager):
|
12
11
|
def __init__(
|
13
12
|
self,
|
14
13
|
log_config: SimpleConfig,
|
15
14
|
service_key: BaseTypes.OptionalString = None,
|
16
15
|
credentials: Optional[Credentials] = None,
|
17
|
-
credentials_path: Optional[Union[Path, str]] = None
|
16
|
+
credentials_path: Optional[Union[Path, str]] = None,
|
18
17
|
) -> None:
|
19
18
|
key = "google-parameter-manager"
|
20
19
|
name = "GoogleParameterManager"
|
21
20
|
super().__init__(
|
22
|
-
key,
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
credentials,
|
27
|
-
credentials_path
|
21
|
+
key, name, log_config, service_key, credentials, credentials_path
|
22
|
+
)
|
23
|
+
self._client = parametermanager.ParameterManagerClient(
|
24
|
+
credentials=self._credentials
|
28
25
|
)
|
29
|
-
self._client = parametermanager.ParameterManagerClient(credentials=self._credentials)
|
30
26
|
self._logger.info("Client manager initialized successfully")
|
31
27
|
|
32
28
|
@property
|
33
29
|
def client(self) -> parametermanager.ParameterManagerClient:
|
30
|
+
if self._client is None:
|
31
|
+
raise ValueError("Client has not been initialized.")
|
34
32
|
return self._client
|
35
33
|
|
36
34
|
def dispose(self) -> None:
|
37
35
|
if self._client is not None:
|
38
36
|
self._logger.info("Disposing client manager")
|
39
37
|
self._client = None
|
40
|
-
self._logger.info("Client manager disposed successfully")
|
38
|
+
self._logger.info("Client manager disposed successfully")
|
@@ -8,44 +8,39 @@ from maleo_foundation.types import BaseTypes
|
|
8
8
|
from maleo_foundation.utils.logging import SimpleConfig
|
9
9
|
from .base import GoogleClientManager
|
10
10
|
|
11
|
+
|
11
12
|
class GoogleSecretManager(GoogleClientManager):
|
12
13
|
def __init__(
|
13
14
|
self,
|
14
15
|
log_config: SimpleConfig,
|
15
16
|
service_key: BaseTypes.OptionalString = None,
|
16
17
|
credentials: Optional[Credentials] = None,
|
17
|
-
credentials_path: Optional[Union[Path, str]] = None
|
18
|
+
credentials_path: Optional[Union[Path, str]] = None,
|
18
19
|
) -> None:
|
19
20
|
key = "google-secret-manager"
|
20
21
|
name = "GoogleSecretManager"
|
21
22
|
super().__init__(
|
22
|
-
key,
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
credentials,
|
27
|
-
credentials_path
|
23
|
+
key, name, log_config, service_key, credentials, credentials_path
|
24
|
+
)
|
25
|
+
self._client = secretmanager.SecretManagerServiceClient(
|
26
|
+
credentials=self._credentials
|
28
27
|
)
|
29
|
-
self._client = secretmanager.SecretManagerServiceClient(credentials=self._credentials)
|
30
28
|
self._logger.info("Client manager initialized successfully")
|
31
29
|
|
32
30
|
@property
|
33
31
|
def client(self) -> secretmanager.SecretManagerServiceClient:
|
32
|
+
if self._client is None:
|
33
|
+
raise ValueError("Client has not been initialized.")
|
34
34
|
return self._client
|
35
35
|
|
36
36
|
def dispose(self) -> None:
|
37
37
|
if self._client is not None:
|
38
38
|
self._logger.info("Disposing client manager")
|
39
|
-
self._client = None
|
40
39
|
self._logger.info("Client manager disposed successfully")
|
41
40
|
|
42
41
|
@retry.Retry(predicate=retry.if_exception_type(Exception), timeout=5)
|
43
|
-
def get(
|
44
|
-
|
45
|
-
name: str,
|
46
|
-
version: str = "latest"
|
47
|
-
) -> str:
|
48
|
-
#* Check if secret exists
|
42
|
+
def get(self, name: str, version: str = "latest") -> str:
|
43
|
+
# * Check if secret exists
|
49
44
|
secret_name = f"projects/{self._project_id}/secrets/{name}"
|
50
45
|
try:
|
51
46
|
request = secretmanager.GetSecretRequest(name=secret_name)
|
@@ -54,59 +49,82 @@ class GoogleSecretManager(GoogleClientManager):
|
|
54
49
|
self._logger.error("Secret '%s' did not exist", name, exc_info=True)
|
55
50
|
raise
|
56
51
|
except Exception:
|
57
|
-
self._logger.error(
|
52
|
+
self._logger.error(
|
53
|
+
"Exception raised while checking secret '%s' existence",
|
54
|
+
name,
|
55
|
+
exc_info=True,
|
56
|
+
)
|
58
57
|
raise
|
59
58
|
|
60
|
-
|
59
|
+
# * Check if secret's version exists
|
61
60
|
secret_version_name = f"{secret_name}/versions/{version}"
|
62
61
|
try:
|
63
62
|
request = secretmanager.GetSecretVersionRequest(name=secret_version_name)
|
64
63
|
self._client.get_secret_version(request=request)
|
65
64
|
except NotFound:
|
66
|
-
self._logger.error(
|
65
|
+
self._logger.error(
|
66
|
+
"Secret '%s' with version '%s' did not exist",
|
67
|
+
name,
|
68
|
+
version,
|
69
|
+
exc_info=True,
|
70
|
+
)
|
67
71
|
raise
|
68
72
|
except Exception:
|
69
|
-
self._logger.error(
|
73
|
+
self._logger.error(
|
74
|
+
"Exception raised while checking secret '%s' with version '%s' existence",
|
75
|
+
name,
|
76
|
+
version,
|
77
|
+
exc_info=True,
|
78
|
+
)
|
70
79
|
raise
|
71
80
|
|
72
|
-
|
81
|
+
# * Access secret's version
|
73
82
|
try:
|
74
83
|
request = secretmanager.AccessSecretVersionRequest(name=secret_version_name)
|
75
84
|
response = self._client.access_secret_version(request=request)
|
76
|
-
self._logger.info(
|
85
|
+
self._logger.info(
|
86
|
+
"Successfully retrieved secret '%s' with version '%s'", name, version
|
87
|
+
)
|
77
88
|
return response.payload.data.decode()
|
78
89
|
except Exception:
|
79
|
-
self._logger.error(
|
90
|
+
self._logger.error(
|
91
|
+
"Exception occured while retrieving secret '%s' with version '%s'",
|
92
|
+
name,
|
93
|
+
version,
|
94
|
+
exc_info=True,
|
95
|
+
)
|
80
96
|
raise
|
81
97
|
|
82
98
|
@retry.Retry(predicate=retry.if_exception_type(Exception), timeout=5)
|
83
|
-
def create(
|
84
|
-
self,
|
85
|
-
name: str,
|
86
|
-
data: str
|
87
|
-
) -> str:
|
99
|
+
def create(self, name: str, data: str) -> str:
|
88
100
|
parent = f"projects/{self._project_id}"
|
89
101
|
secret_path = f"{parent}/secrets/{name}"
|
90
102
|
try:
|
91
|
-
|
103
|
+
# * Check if the secret already exists
|
92
104
|
request = secretmanager.GetSecretRequest(name=secret_path)
|
93
105
|
self._client.get_secret(request=request)
|
94
106
|
except NotFound:
|
95
|
-
|
107
|
+
# * Secret does not exist, create it first
|
96
108
|
try:
|
97
109
|
secret = secretmanager.Secret(name=name, replication={"automatic": {}})
|
98
|
-
request = secretmanager.CreateSecretRequest(
|
110
|
+
request = secretmanager.CreateSecretRequest(
|
111
|
+
parent=parent, secret_id=name, secret=secret
|
112
|
+
)
|
99
113
|
self._client.create_secret(request=request)
|
100
114
|
except Exception:
|
101
115
|
self._logger.error("Exception occured while creating secret '%s'", name)
|
102
116
|
raise
|
103
117
|
|
104
|
-
|
118
|
+
# * Add a new secret version
|
105
119
|
try:
|
106
120
|
payload = secretmanager.SecretPayload(data=data.encode())
|
107
|
-
request = secretmanager.AddSecretVersionRequest(
|
108
|
-
|
121
|
+
request = secretmanager.AddSecretVersionRequest(
|
122
|
+
parent=secret_path, payload=payload
|
123
|
+
)
|
124
|
+
self._client.add_secret_version(request=request)
|
109
125
|
return data
|
110
126
|
except Exception:
|
111
|
-
self._logger.error(
|
112
|
-
|
127
|
+
self._logger.error(
|
128
|
+
"Exception occured while adding secret '%s' version", name
|
129
|
+
)
|
130
|
+
raise
|
@@ -10,6 +10,7 @@ from maleo_foundation.types import BaseTypes
|
|
10
10
|
from maleo_foundation.utils.logging import SimpleConfig
|
11
11
|
from .base import GoogleClientManager
|
12
12
|
|
13
|
+
|
13
14
|
class GoogleCloudStorage(GoogleClientManager):
|
14
15
|
def __init__(
|
15
16
|
self,
|
@@ -18,16 +19,20 @@ class GoogleCloudStorage(GoogleClientManager):
|
|
18
19
|
credentials: Optional[Credentials] = None,
|
19
20
|
credentials_path: Optional[Union[Path, str]] = None,
|
20
21
|
bucket_name: BaseTypes.OptionalString = None,
|
21
|
-
redis: Optional[Redis] = None
|
22
|
+
redis: Optional[Redis] = None,
|
22
23
|
) -> None:
|
23
24
|
key = "google-cloud-storage"
|
24
25
|
name = "GoogleCloudStorage"
|
25
|
-
super().__init__(
|
26
|
+
super().__init__(
|
27
|
+
key, name, log_config, service_key, credentials, credentials_path
|
28
|
+
)
|
26
29
|
self._client = Client(credentials=self._credentials)
|
27
30
|
self._bucket_name = bucket_name or os.getenv("GCS_BUCKET_NAME")
|
28
31
|
if self._bucket_name is None:
|
29
32
|
self._client.close()
|
30
|
-
raise ValueError(
|
33
|
+
raise ValueError(
|
34
|
+
"GCS_BUCKET_NAME environment variable must be set if 'bucket_name' is set to None"
|
35
|
+
)
|
31
36
|
self._bucket = self._client.lookup_bucket(bucket_name=self._bucket_name)
|
32
37
|
if self._bucket is None:
|
33
38
|
self._client.close()
|
@@ -38,10 +43,14 @@ class GoogleCloudStorage(GoogleClientManager):
|
|
38
43
|
|
39
44
|
@property
|
40
45
|
def bucket_name(self) -> str:
|
46
|
+
if self._bucket_name is None:
|
47
|
+
raise ValueError("Bucket name has not been initialized.")
|
41
48
|
return self._bucket_name
|
42
49
|
|
43
50
|
@property
|
44
51
|
def bucket(self) -> Bucket:
|
52
|
+
if self._bucket is None:
|
53
|
+
raise ValueError("Bucket has not been initialized.")
|
45
54
|
return self._bucket
|
46
55
|
|
47
56
|
def dispose(self) -> None:
|
@@ -55,11 +64,11 @@ class GoogleCloudStorage(GoogleClientManager):
|
|
55
64
|
self,
|
56
65
|
content: bytes,
|
57
66
|
location: str,
|
58
|
-
content_type: Optional[str]=None,
|
59
|
-
make_public: bool=False,
|
60
|
-
expiration: BaseEnums=BaseEnums.Expiration.EXP_15MN,
|
61
|
-
root_location_override: BaseTypes.OptionalString=None,
|
62
|
-
set_in_redis: bool=True
|
67
|
+
content_type: Optional[str] = None,
|
68
|
+
make_public: bool = False,
|
69
|
+
expiration: BaseEnums.Expiration = BaseEnums.Expiration.EXP_15MN,
|
70
|
+
root_location_override: BaseTypes.OptionalString = None,
|
71
|
+
set_in_redis: bool = True,
|
63
72
|
) -> str:
|
64
73
|
"""
|
65
74
|
Upload a file to Google Cloud Storage.
|
@@ -73,13 +82,20 @@ class GoogleCloudStorage(GoogleClientManager):
|
|
73
82
|
Returns:
|
74
83
|
str: The public URL or blob path depending on `make_public`.
|
75
84
|
"""
|
76
|
-
if root_location_override is None or (
|
85
|
+
if root_location_override is None or (
|
86
|
+
isinstance(root_location_override, str) and len(root_location_override) <= 0
|
87
|
+
):
|
77
88
|
blob_name = f"{self._root_location}/{location}"
|
78
89
|
else:
|
79
90
|
blob_name = f"{root_location_override}/{location}"
|
80
91
|
|
92
|
+
if self._bucket is None:
|
93
|
+
raise ValueError(
|
94
|
+
"Bucket is not initialized. Please check the bucket name and credentials."
|
95
|
+
)
|
96
|
+
|
81
97
|
blob = self._bucket.blob(blob_name=blob_name)
|
82
|
-
blob.upload_from_string(content, content_type=content_type)
|
98
|
+
blob.upload_from_string(content, content_type=content_type or "text/plain")
|
83
99
|
|
84
100
|
if make_public:
|
85
101
|
blob.make_public()
|
@@ -88,23 +104,27 @@ class GoogleCloudStorage(GoogleClientManager):
|
|
88
104
|
url = blob.generate_signed_url(
|
89
105
|
version="v4",
|
90
106
|
expiration=timedelta(seconds=int(expiration)),
|
91
|
-
method="GET"
|
107
|
+
method="GET",
|
92
108
|
)
|
93
109
|
|
94
110
|
if set_in_redis:
|
111
|
+
if self._redis is None:
|
112
|
+
raise ValueError("Can not use redis. Redis is not initialized")
|
95
113
|
if make_public:
|
96
114
|
await self._redis.set(f"{self.key}:{blob_name}", url)
|
97
115
|
else:
|
98
|
-
await self._redis.set(
|
116
|
+
await self._redis.set(
|
117
|
+
f"{self.key}:{blob_name}", url, ex=int(expiration)
|
118
|
+
)
|
99
119
|
|
100
120
|
return url
|
101
121
|
|
102
122
|
async def generate_signed_url(
|
103
123
|
self,
|
104
124
|
location: str,
|
105
|
-
expiration: BaseEnums=BaseEnums.Expiration.EXP_15MN,
|
125
|
+
expiration: BaseEnums.Expiration = BaseEnums.Expiration.EXP_15MN,
|
106
126
|
root_location_override: BaseTypes.OptionalString = None,
|
107
|
-
use_redis: bool = True
|
127
|
+
use_redis: bool = True,
|
108
128
|
) -> str:
|
109
129
|
"""
|
110
130
|
generate signed URL of a file in the bucket based on its location.
|
@@ -122,10 +142,17 @@ class GoogleCloudStorage(GoogleClientManager):
|
|
122
142
|
if use_redis and self._redis is None:
|
123
143
|
raise ValueError("Can not use redis. Redis is not initialized")
|
124
144
|
|
125
|
-
if root_location_override is None or (
|
126
|
-
|
145
|
+
if root_location_override is None or (
|
146
|
+
isinstance(root_location_override, str) and len(root_location_override) <= 0
|
147
|
+
):
|
148
|
+
blob_name = f"{self._root_location}/{location}"
|
127
149
|
else:
|
128
|
-
blob_name=f"{root_location_override}/{location}"
|
150
|
+
blob_name = f"{root_location_override}/{location}"
|
151
|
+
|
152
|
+
if self._bucket is None:
|
153
|
+
raise ValueError(
|
154
|
+
"Bucket is not initialized. Please check the bucket name and credentials."
|
155
|
+
)
|
129
156
|
|
130
157
|
blob = self._bucket.blob(blob_name=blob_name)
|
131
158
|
if not blob.exists():
|
@@ -139,9 +166,12 @@ class GoogleCloudStorage(GoogleClientManager):
|
|
139
166
|
return url
|
140
167
|
|
141
168
|
url = blob.generate_signed_url(
|
142
|
-
version="v4",
|
143
|
-
expiration=timedelta(seconds=int(expiration)),
|
144
|
-
method="GET"
|
169
|
+
version="v4", expiration=timedelta(seconds=int(expiration)), method="GET"
|
145
170
|
)
|
146
|
-
|
147
|
-
|
171
|
+
|
172
|
+
if use_redis:
|
173
|
+
if self._redis is None:
|
174
|
+
raise ValueError("Can not use redis. Redis is not initialized")
|
175
|
+
await self._redis.set(f"{self.key}:{blob_name}", url, ex=int(expiration))
|
176
|
+
|
177
|
+
return url
|
@@ -6,7 +6,7 @@ from google.cloud.pubsub_v1.subscriber.message import Message
|
|
6
6
|
from google.oauth2.service_account import Credentials
|
7
7
|
from pathlib import Path
|
8
8
|
from pydantic import BaseModel
|
9
|
-
from typing import Awaitable, Callable, Dict, List, Optional, Union
|
9
|
+
from typing import Awaitable, Callable, Dict, List, Optional, Union, cast
|
10
10
|
from maleo_foundation.managers.client.google.base import GoogleClientManager
|
11
11
|
|
12
12
|
SyncController = Callable[[str, Message], bool]
|
@@ -14,12 +14,14 @@ AsyncController = Callable[[str, Message], Awaitable[bool]]
|
|
14
14
|
Controller = Union[SyncController, AsyncController]
|
15
15
|
OptionalController = Optional[Controller]
|
16
16
|
|
17
|
+
|
17
18
|
class SubscriptionConfigurations(BaseModel):
|
18
19
|
subscription_name: str
|
19
20
|
max_messages: int = 10
|
20
21
|
ack_deadline: int = 10
|
21
22
|
controller: OptionalController = None
|
22
23
|
|
24
|
+
|
23
25
|
class SubscriptionManager(GoogleClientManager):
|
24
26
|
def __init__(
|
25
27
|
self,
|
@@ -31,35 +33,28 @@ class SubscriptionManager(GoogleClientManager):
|
|
31
33
|
):
|
32
34
|
key = "google-subscription-manager"
|
33
35
|
name = "GoogleSubscriptionManager"
|
34
|
-
super().__init__(
|
36
|
+
super().__init__(
|
37
|
+
key, name, log_config, service_key, credentials, credentials_path
|
38
|
+
)
|
35
39
|
self.subscriber = pubsub_v1.SubscriberClient(credentials=self._credentials)
|
36
40
|
self.subscriptions = subscriptions
|
37
41
|
self.active_listeners: Dict[str, StreamingPullFuture] = {}
|
38
42
|
self.loop: Optional[asyncio.AbstractEventLoop] = None
|
39
43
|
|
40
44
|
async def _handle_async_controller(
|
41
|
-
self,
|
42
|
-
controller: AsyncController,
|
43
|
-
subscription_name: str,
|
44
|
-
message: Message
|
45
|
+
self, controller: AsyncController, subscription_name: str, message: Message
|
45
46
|
) -> None:
|
46
47
|
success = await controller(subscription_name, message)
|
47
48
|
message.ack() if success else message.nack()
|
48
49
|
|
49
50
|
def _handle_sync_controller(
|
50
|
-
self,
|
51
|
-
controller: SyncController,
|
52
|
-
subscription_name: str,
|
53
|
-
message: Message
|
51
|
+
self, controller: SyncController, subscription_name: str, message: Message
|
54
52
|
) -> None:
|
55
53
|
success = controller(subscription_name, message)
|
56
54
|
message.ack() if success else message.nack()
|
57
55
|
|
58
56
|
def _message_callback(
|
59
|
-
self,
|
60
|
-
controller: OptionalController,
|
61
|
-
subscription_name: str,
|
62
|
-
message: Message
|
57
|
+
self, controller: OptionalController, subscription_name: str, message: Message
|
63
58
|
):
|
64
59
|
# If controller is not given, conduct default message processing
|
65
60
|
if controller is None:
|
@@ -73,58 +68,61 @@ class SubscriptionManager(GoogleClientManager):
|
|
73
68
|
raise RuntimeError("Event loop not set in SubscriptionManager")
|
74
69
|
asyncio.run_coroutine_threadsafe(
|
75
70
|
self._handle_async_controller(
|
76
|
-
controller,
|
77
|
-
subscription_name,
|
78
|
-
message
|
71
|
+
cast(AsyncController, controller), subscription_name, message
|
79
72
|
),
|
80
|
-
self.loop
|
73
|
+
self.loop,
|
81
74
|
)
|
82
75
|
else:
|
83
76
|
self._handle_sync_controller(
|
84
|
-
controller,
|
85
|
-
subscription_name,
|
86
|
-
message
|
77
|
+
cast(SyncController, controller), subscription_name, message
|
87
78
|
)
|
88
79
|
|
89
80
|
def _default_message_processing(
|
90
|
-
self,
|
91
|
-
subscription_name: str,
|
92
|
-
message: Message
|
81
|
+
self, subscription_name: str, message: Message
|
93
82
|
) -> None:
|
94
83
|
try:
|
95
|
-
self._logger.info(
|
84
|
+
self._logger.info(
|
85
|
+
"Default message processing for subscription '%s': %s",
|
86
|
+
subscription_name,
|
87
|
+
message.data.decode("utf-8"),
|
88
|
+
)
|
96
89
|
message.ack()
|
97
90
|
except Exception as e:
|
98
|
-
self._logger.error(
|
91
|
+
self._logger.error(
|
92
|
+
"Error handling message through default processor: %s", e, exc_info=True
|
93
|
+
)
|
99
94
|
message.nack()
|
100
95
|
|
101
96
|
async def _start_subscription_listener(
|
102
|
-
self,
|
103
|
-
config: SubscriptionConfigurations
|
97
|
+
self, config: SubscriptionConfigurations
|
104
98
|
) -> None:
|
105
|
-
|
99
|
+
if self.credentials.project_id is None:
|
100
|
+
raise ValueError("Project ID must be set in credentials")
|
101
|
+
subscription_path = self.subscriber.subscription_path(
|
102
|
+
self.credentials.project_id, config.subscription_name
|
103
|
+
)
|
106
104
|
flow_control = pubsub_v1.types.FlowControl(max_messages=config.max_messages)
|
107
105
|
future = self.subscriber.subscribe(
|
108
106
|
subscription_path,
|
109
107
|
callback=lambda message: self._message_callback(
|
110
|
-
config.controller,
|
111
|
-
config.subscription_name,
|
112
|
-
message
|
108
|
+
config.controller, config.subscription_name, message
|
113
109
|
),
|
114
110
|
flow_control=flow_control,
|
115
|
-
await_callbacks_on_shutdown=True
|
111
|
+
await_callbacks_on_shutdown=True,
|
116
112
|
)
|
117
113
|
self.active_listeners[subscription_path] = future
|
118
114
|
try:
|
119
115
|
await asyncio.get_event_loop().run_in_executor(None, future.result)
|
120
116
|
except Exception as e:
|
121
117
|
if not isinstance(e, asyncio.CancelledError):
|
122
|
-
self._logger.error(
|
118
|
+
self._logger.error(
|
119
|
+
"Listener error for subscription '%s': %s",
|
120
|
+
config.subscription_name,
|
121
|
+
e,
|
122
|
+
exc_info=True,
|
123
|
+
)
|
123
124
|
|
124
|
-
async def start_listeners(
|
125
|
-
self,
|
126
|
-
loop: asyncio.AbstractEventLoop
|
127
|
-
) -> None:
|
125
|
+
async def start_listeners(self, loop: asyncio.AbstractEventLoop) -> None:
|
128
126
|
self.loop = loop
|
129
127
|
for config in self.subscriptions:
|
130
128
|
asyncio.create_task(self._start_subscription_listener(config))
|
@@ -137,4 +135,4 @@ class SubscriptionManager(GoogleClientManager):
|
|
137
135
|
future.result()
|
138
136
|
except Exception:
|
139
137
|
pass
|
140
|
-
self.active_listeners.clear()
|
138
|
+
self.active_listeners.clear()
|
@@ -6,16 +6,15 @@ from maleo_foundation.managers.client.base import (
|
|
6
6
|
ClientHTTPController,
|
7
7
|
ClientServiceControllers,
|
8
8
|
ClientService,
|
9
|
-
ClientControllers
|
9
|
+
ClientControllers,
|
10
10
|
)
|
11
11
|
from maleo_foundation.managers.service import ServiceManager
|
12
12
|
from maleo_foundation.utils.logging import ClientLogger
|
13
13
|
|
14
|
+
|
14
15
|
class MaleoClientHTTPController(ClientHTTPController):
|
15
16
|
def __init__(
|
16
|
-
self,
|
17
|
-
service_manager: ServiceManager,
|
18
|
-
manager: ClientHTTPControllerManager
|
17
|
+
self, service_manager: ServiceManager, manager: ClientHTTPControllerManager
|
19
18
|
):
|
20
19
|
super().__init__(manager)
|
21
20
|
self._service_manager = service_manager
|
@@ -24,18 +23,17 @@ class MaleoClientHTTPController(ClientHTTPController):
|
|
24
23
|
def service_manager(self) -> ServiceManager:
|
25
24
|
return self._service_manager
|
26
25
|
|
26
|
+
|
27
27
|
class MaleoClientServiceControllers(ClientServiceControllers):
|
28
28
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
29
29
|
|
30
|
-
http:MaleoClientHTTPController = Field(
|
30
|
+
http: MaleoClientHTTPController = Field( # type: ignore
|
31
|
+
..., description="Maleo's HTTP Client Controller"
|
32
|
+
)
|
33
|
+
|
31
34
|
|
32
35
|
class MaleoClientService(ClientService):
|
33
|
-
def __init__(
|
34
|
-
self,
|
35
|
-
key: str,
|
36
|
-
logger: ClientLogger,
|
37
|
-
service_manager: ServiceManager
|
38
|
-
):
|
36
|
+
def __init__(self, key: str, logger: ClientLogger, service_manager: ServiceManager):
|
39
37
|
super().__init__(logger)
|
40
38
|
self._key = key
|
41
39
|
self._service_manager = service_manager
|
@@ -48,21 +46,16 @@ class MaleoClientService(ClientService):
|
|
48
46
|
def service_manager(self) -> ServiceManager:
|
49
47
|
return self._service_manager
|
50
48
|
|
49
|
+
|
51
50
|
class MaleoClientManager(ClientManager):
|
52
|
-
def __init__(
|
53
|
-
self,
|
54
|
-
key: str,
|
55
|
-
name: str,
|
56
|
-
url: str,
|
57
|
-
service_manager: ServiceManager
|
58
|
-
):
|
51
|
+
def __init__(self, key: str, name: str, url: str, service_manager: ServiceManager):
|
59
52
|
self._url = url
|
60
53
|
self._service_manager = service_manager
|
61
54
|
super().__init__(
|
62
55
|
key,
|
63
56
|
name,
|
64
57
|
service_manager.log_config,
|
65
|
-
service_manager.configurations.service.key
|
58
|
+
service_manager.configurations.service.key,
|
66
59
|
)
|
67
60
|
|
68
61
|
@property
|
@@ -70,10 +63,12 @@ class MaleoClientManager(ClientManager):
|
|
70
63
|
return self._service_manager
|
71
64
|
|
72
65
|
def _initialize_controllers(self) -> None:
|
73
|
-
|
66
|
+
# * Initialize managers
|
74
67
|
http_controller_manager = ClientHTTPControllerManager(url=self._url)
|
75
|
-
self._controller_managers = ClientControllerManagers(
|
76
|
-
|
68
|
+
self._controller_managers = ClientControllerManagers(
|
69
|
+
http=http_controller_manager
|
70
|
+
)
|
71
|
+
# * Initialize controllers
|
77
72
|
#! This initialied an empty controllers. Extend this function in the actual class to initialize all controllers.
|
78
73
|
self._controllers = ClientControllers()
|
79
74
|
|
@@ -84,4 +79,4 @@ class MaleoClientManager(ClientManager):
|
|
84
79
|
async def dispose(self) -> None:
|
85
80
|
self._logger.info("Disposing client manager")
|
86
81
|
await self._controller_managers.http.dispose()
|
87
|
-
self._logger.info("Client manager disposed successfully")
|
82
|
+
self._logger.info("Client manager disposed successfully")
|