maleo-metadata-client 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of maleo-metadata-client might be problematic. Click here for more details.

File without changes
@@ -0,0 +1,137 @@
1
+ from Crypto.PublicKey.RSA import RsaKey
2
+ from redis.asyncio.client import Redis
3
+ from typing import Optional
4
+ from maleo.soma.dtos.configurations.cache.redis import RedisCacheNamespaces
5
+ from maleo.soma.dtos.configurations.client.maleo import MaleoClientConfigurationDTO
6
+ from maleo.soma.managers.client.maleo import MaleoClientManager
7
+ from maleo.soma.managers.credential import CredentialManager
8
+ from maleo.soma.schemas.service import ServiceContext
9
+ from maleo.soma.utils.logging import SimpleConfig
10
+ from .services.blood_type import BloodTypeClientService
11
+ from .services.gender import GenderClientService
12
+ from .services.medical_role import MedicalRoleClientService
13
+ from .services.organization_type import (
14
+ OrganizationTypeClientService,
15
+ )
16
+ from .services.service import ServiceClientService
17
+ from .services.system_role import SystemRoleClientService
18
+ from .services.user_type import UserTypeClientService
19
+
20
+
21
+ class ClientManager(MaleoClientManager):
22
+ def __init__(
23
+ self,
24
+ configurations: MaleoClientConfigurationDTO,
25
+ log_config: SimpleConfig,
26
+ credential_manager: CredentialManager,
27
+ private_key: RsaKey,
28
+ redis: Redis,
29
+ redis_namespaces: RedisCacheNamespaces,
30
+ service_context: Optional[ServiceContext] = None,
31
+ ):
32
+ assert configurations.key == "maleo-metadata"
33
+ assert configurations.name == "MaleoMetadata"
34
+ super().__init__(
35
+ configurations,
36
+ log_config,
37
+ credential_manager,
38
+ private_key,
39
+ redis,
40
+ redis_namespaces,
41
+ service_context,
42
+ )
43
+ self._initialize_services()
44
+ self._logger.info("Client manager initialized successfully")
45
+
46
+ def _initialize_services(self):
47
+ self.blood_type = BloodTypeClientService(
48
+ environment=self._environment,
49
+ key=self._key,
50
+ url=self._url,
51
+ operation_origin=self._operation_origin,
52
+ logger=self._logger,
53
+ credential_manager=self._credential_manager,
54
+ http_client_manager=self._http_client_manager,
55
+ private_key=self._private_key,
56
+ redis=self._redis,
57
+ redis_namespaces=self._redis_namespaces,
58
+ service_context=self._service_context,
59
+ )
60
+ self.gender = GenderClientService(
61
+ environment=self._environment,
62
+ key=self._key,
63
+ url=self._url,
64
+ operation_origin=self._operation_origin,
65
+ logger=self._logger,
66
+ credential_manager=self._credential_manager,
67
+ http_client_manager=self._http_client_manager,
68
+ private_key=self._private_key,
69
+ redis=self._redis,
70
+ redis_namespaces=self._redis_namespaces,
71
+ service_context=self._service_context,
72
+ )
73
+ self.medical_role = MedicalRoleClientService(
74
+ environment=self._environment,
75
+ key=self._key,
76
+ url=self._url,
77
+ operation_origin=self._operation_origin,
78
+ logger=self._logger,
79
+ credential_manager=self._credential_manager,
80
+ http_client_manager=self._http_client_manager,
81
+ private_key=self._private_key,
82
+ redis=self._redis,
83
+ redis_namespaces=self._redis_namespaces,
84
+ service_context=self._service_context,
85
+ )
86
+ self.organization_type = OrganizationTypeClientService(
87
+ environment=self._environment,
88
+ key=self._key,
89
+ url=self._url,
90
+ operation_origin=self._operation_origin,
91
+ logger=self._logger,
92
+ credential_manager=self._credential_manager,
93
+ http_client_manager=self._http_client_manager,
94
+ private_key=self._private_key,
95
+ redis=self._redis,
96
+ redis_namespaces=self._redis_namespaces,
97
+ service_context=self._service_context,
98
+ )
99
+ self.service = ServiceClientService(
100
+ environment=self._environment,
101
+ key=self._key,
102
+ url=self._url,
103
+ operation_origin=self._operation_origin,
104
+ logger=self._logger,
105
+ credential_manager=self._credential_manager,
106
+ http_client_manager=self._http_client_manager,
107
+ private_key=self._private_key,
108
+ redis=self._redis,
109
+ redis_namespaces=self._redis_namespaces,
110
+ service_context=self._service_context,
111
+ )
112
+ self.system_role = SystemRoleClientService(
113
+ environment=self._environment,
114
+ key=self._key,
115
+ url=self._url,
116
+ operation_origin=self._operation_origin,
117
+ logger=self._logger,
118
+ credential_manager=self._credential_manager,
119
+ http_client_manager=self._http_client_manager,
120
+ private_key=self._private_key,
121
+ redis=self._redis,
122
+ redis_namespaces=self._redis_namespaces,
123
+ service_context=self._service_context,
124
+ )
125
+ self.user_type = UserTypeClientService(
126
+ environment=self._environment,
127
+ key=self._key,
128
+ url=self._url,
129
+ operation_origin=self._operation_origin,
130
+ logger=self._logger,
131
+ credential_manager=self._credential_manager,
132
+ http_client_manager=self._http_client_manager,
133
+ private_key=self._private_key,
134
+ redis=self._redis,
135
+ redis_namespaces=self._redis_namespaces,
136
+ service_context=self._service_context,
137
+ )
File without changes
@@ -0,0 +1,379 @@
1
+ import json
2
+ from copy import deepcopy
3
+ from Crypto.PublicKey.RSA import RsaKey
4
+ from datetime import datetime, timezone
5
+ from redis.asyncio.client import Redis
6
+ from typing import Dict, List, Literal, Optional, Union, overload
7
+ from uuid import UUID
8
+ from maleo.soma.authorization import BearerAuth
9
+ from maleo.soma.dtos.configurations.cache.redis import RedisCacheNamespaces
10
+ from maleo.soma.enums.cardinality import Cardinality
11
+ from maleo.soma.enums.environment import Environment
12
+ from maleo.soma.enums.expiration import Expiration
13
+ from maleo.soma.enums.logging import LogLevel
14
+ from maleo.soma.enums.operation import OperationTarget
15
+ from maleo.soma.managers.client.maleo import MaleoClientService
16
+ from maleo.soma.managers.client.http import HTTPClientManager
17
+ from maleo.soma.managers.credential import CredentialManager
18
+ from maleo.soma.schemas.authentication import OptionalAuthentication
19
+ from maleo.soma.schemas.data import DataPair
20
+ from maleo.soma.schemas.operation.context import (
21
+ OperationContextSchema,
22
+ OperationOriginSchema,
23
+ OperationLayerSchema,
24
+ OperationTargetSchema,
25
+ )
26
+ from maleo.soma.schemas.operation.resource import (
27
+ ReadSingleResourceOperationSchema,
28
+ ReadMultipleResourceOperationSchema,
29
+ )
30
+ from maleo.soma.schemas.operation.resource.action import ReadResourceOperationAction
31
+ from maleo.soma.schemas.operation.resource.result import (
32
+ ReadSingleResourceOperationResult,
33
+ ReadMultipleResourceOperationResult,
34
+ )
35
+ from maleo.soma.schemas.operation.timestamp import OperationTimestamp
36
+ from maleo.soma.schemas.pagination import FlexiblePagination
37
+ from maleo.soma.schemas.parameter.general import ReadSingleQueryParameterSchema
38
+ from maleo.soma.schemas.request import RequestContext
39
+ from maleo.soma.schemas.response import (
40
+ SingleDataResponseSchema,
41
+ MultipleDataResponseSchema,
42
+ )
43
+ from maleo.soma.schemas.service import ServiceContext
44
+ from maleo.soma.utils.cache import build_key
45
+ from maleo.soma.utils.logging import ClientLogger
46
+ from maleo.soma.utils.merger import merge_dicts
47
+ from maleo.soma.utils.token import reencode
48
+ from maleo.metadata.constants.blood_type import RESOURCE
49
+ from maleo.metadata.schemas.data.blood_type import BloodTypeDataSchema
50
+ from maleo.metadata.schemas.parameter.client.blood_type import (
51
+ ReadMultipleParameter,
52
+ ReadMultipleQueryParameter,
53
+ )
54
+ from maleo.metadata.schemas.parameter.general.blood_type import ReadSingleParameter
55
+
56
+
57
+ class BloodTypeClientService(MaleoClientService):
58
+ def __init__(
59
+ self,
60
+ environment: Environment,
61
+ key: str,
62
+ url: str,
63
+ operation_origin: OperationOriginSchema,
64
+ logger: ClientLogger,
65
+ credential_manager: CredentialManager,
66
+ http_client_manager: HTTPClientManager,
67
+ private_key: RsaKey,
68
+ redis: Redis,
69
+ redis_namespaces: RedisCacheNamespaces,
70
+ service_context: ServiceContext,
71
+ ):
72
+ super().__init__(
73
+ environment,
74
+ key,
75
+ url,
76
+ operation_origin,
77
+ logger,
78
+ credential_manager,
79
+ http_client_manager,
80
+ private_key,
81
+ redis,
82
+ redis_namespaces,
83
+ service_context,
84
+ )
85
+ self._namespace = self._redis_namespaces.create(
86
+ self._key,
87
+ RESOURCE.aggregate(),
88
+ origin=self._CACHE_ORIGIN,
89
+ layer=self._CACHE_LAYER,
90
+ )
91
+ self._default_operation_context = OperationContextSchema(
92
+ origin=self._operation_origin,
93
+ layer=OperationLayerSchema(type=self._OPERATION_LAYER_TYPE, details=None),
94
+ target=OperationTargetSchema(
95
+ type=self._OPERATION_TARGET_TYPE, details=None
96
+ ),
97
+ )
98
+
99
+ @overload
100
+ async def read(
101
+ self,
102
+ cardinality: Literal[Cardinality.MULTIPLE],
103
+ *,
104
+ operation_id: UUID,
105
+ request_context: RequestContext,
106
+ authentication: OptionalAuthentication,
107
+ parameters: ReadMultipleParameter,
108
+ headers: Optional[Dict[str, str]] = None,
109
+ ) -> ReadMultipleResourceOperationResult[
110
+ BloodTypeDataSchema, FlexiblePagination, None
111
+ ]: ...
112
+ @overload
113
+ async def read(
114
+ self,
115
+ cardinality: Literal[Cardinality.SINGLE],
116
+ *,
117
+ operation_id: UUID,
118
+ request_context: RequestContext,
119
+ authentication: OptionalAuthentication,
120
+ parameters: ReadSingleParameter,
121
+ headers: Optional[Dict[str, str]] = None,
122
+ ) -> ReadSingleResourceOperationResult[BloodTypeDataSchema, None]: ...
123
+ async def read(
124
+ self,
125
+ cardinality: Cardinality,
126
+ *,
127
+ operation_id: UUID,
128
+ request_context: RequestContext,
129
+ authentication: OptionalAuthentication,
130
+ parameters: Union[ReadMultipleParameter, ReadSingleParameter],
131
+ headers: Optional[Dict[str, str]] = None,
132
+ ) -> Union[
133
+ ReadMultipleResourceOperationResult[
134
+ BloodTypeDataSchema, FlexiblePagination, None
135
+ ],
136
+ ReadSingleResourceOperationResult[BloodTypeDataSchema, None],
137
+ ]:
138
+ operation_action = ReadResourceOperationAction()
139
+ executed_at = datetime.now(tz=timezone.utc)
140
+
141
+ # Get function identifier
142
+ func = self.__class__
143
+ module, qualname = func.__module__, func.__qualname__
144
+
145
+ # Define arguments being used in this function
146
+ positional_arguments = [cardinality]
147
+ keyword_arguments = {
148
+ "authentication": authentication.model_dump(
149
+ mode="json",
150
+ exclude={
151
+ "credentials": {
152
+ "token": {
153
+ "payload": {
154
+ "iat_dt",
155
+ "iat",
156
+ "exp_dt",
157
+ "exp",
158
+ }
159
+ }
160
+ }
161
+ },
162
+ ),
163
+ "parameters": parameters.model_dump(mode="json"),
164
+ }
165
+
166
+ # Define full function string
167
+ function = f"{qualname}({json.dumps(positional_arguments)}|{json.dumps(keyword_arguments)})"
168
+
169
+ # Define full cache key
170
+ cache_key = build_key(module, function, namespace=self._namespace)
171
+
172
+ if parameters.use_cache:
173
+ operation_context = deepcopy(self._default_operation_context)
174
+ operation_context.target.type = OperationTarget.CACHE
175
+
176
+ # Check redis for data
177
+ result_str = await self._redis.get(cache_key)
178
+
179
+ if result_str is not None:
180
+ completed_at = datetime.now(tz=timezone.utc)
181
+
182
+ if isinstance(parameters, ReadMultipleParameter):
183
+ result = ReadMultipleResourceOperationResult[
184
+ BloodTypeDataSchema, FlexiblePagination, None
185
+ ].model_validate(json.loads(result_str))
186
+ ReadMultipleResourceOperationSchema[
187
+ OptionalAuthentication,
188
+ BloodTypeDataSchema,
189
+ FlexiblePagination,
190
+ None,
191
+ ](
192
+ service_context=self._service_context,
193
+ id=operation_id,
194
+ context=operation_context,
195
+ timestamp=OperationTimestamp(
196
+ executed_at=executed_at,
197
+ completed_at=completed_at,
198
+ duration=(completed_at - executed_at).total_seconds(),
199
+ ),
200
+ summary="Successfully retrieved multiple blood types from cache",
201
+ request_context=request_context,
202
+ authentication=authentication,
203
+ action=operation_action,
204
+ resource=RESOURCE,
205
+ result=result,
206
+ ).log(
207
+ self._logger, LogLevel.INFO
208
+ )
209
+ elif isinstance(parameters, ReadSingleParameter):
210
+ result = ReadSingleResourceOperationResult[
211
+ BloodTypeDataSchema, None
212
+ ].model_validate(json.loads(result_str))
213
+ ReadSingleResourceOperationSchema[
214
+ OptionalAuthentication, BloodTypeDataSchema, None
215
+ ](
216
+ service_context=self._service_context,
217
+ id=operation_id,
218
+ context=operation_context,
219
+ timestamp=OperationTimestamp(
220
+ executed_at=executed_at,
221
+ completed_at=completed_at,
222
+ duration=(completed_at - executed_at).total_seconds(),
223
+ ),
224
+ summary="Successfully retrieved single blood type from cache",
225
+ request_context=request_context,
226
+ authentication=authentication,
227
+ action=operation_action,
228
+ resource=RESOURCE,
229
+ result=result,
230
+ ).log(
231
+ self._logger, LogLevel.INFO
232
+ )
233
+ return result
234
+
235
+ operation_context = deepcopy(self._default_operation_context)
236
+ async with self._http_client_manager.get() as http_client:
237
+ # Create headers
238
+ base_headers = {
239
+ "content-type": "application/json",
240
+ "x-operation-id": str(operation_id),
241
+ }
242
+ if headers is not None:
243
+ headers = merge_dicts(base_headers, headers)
244
+ else:
245
+ headers = base_headers
246
+
247
+ # Create auth
248
+ token = None
249
+ if authentication.credentials.token is not None:
250
+ try:
251
+ token = reencode(
252
+ payload=authentication.credentials.token.payload,
253
+ key=self._private_key,
254
+ )
255
+ except Exception:
256
+ pass
257
+
258
+ auth = BearerAuth(token) if token is not None else None
259
+
260
+ if isinstance(parameters, ReadMultipleParameter):
261
+ # Define URL
262
+ url = f"{self._url}/v1/{RESOURCE.identifiers[0].url_slug}/"
263
+
264
+ # Parse parameters to query params
265
+ params = ReadMultipleQueryParameter.model_validate(
266
+ parameters.model_dump()
267
+ ).model_dump(
268
+ exclude={"sort_columns", "date_filters"}, exclude_none=True
269
+ )
270
+ elif isinstance(parameters, ReadSingleParameter):
271
+ # Define URL
272
+ url = f"{self._url}/v1/{RESOURCE.identifiers[0].url_slug}/{parameters.identifier}/{parameters.value}"
273
+
274
+ # Parse parameters to query params
275
+ params = ReadSingleQueryParameterSchema.model_validate(
276
+ parameters.model_dump()
277
+ ).model_dump(exclude_none=True)
278
+
279
+ # Send request and wait for response
280
+ response = await http_client.get(
281
+ url=url, params=params, headers=headers, auth=auth
282
+ )
283
+
284
+ if response.is_success:
285
+ completed_at = datetime.now(tz=timezone.utc)
286
+
287
+ if isinstance(parameters, ReadMultipleParameter):
288
+ validated_response = MultipleDataResponseSchema[
289
+ BloodTypeDataSchema, FlexiblePagination, None
290
+ ].model_validate(response.json())
291
+ data = DataPair[List[BloodTypeDataSchema], None](
292
+ old=validated_response.data,
293
+ new=None,
294
+ )
295
+ result = ReadMultipleResourceOperationResult[
296
+ BloodTypeDataSchema, FlexiblePagination, None
297
+ ](
298
+ data=data,
299
+ pagination=validated_response.pagination,
300
+ metadata=None,
301
+ other=None,
302
+ )
303
+ ReadMultipleResourceOperationSchema[
304
+ OptionalAuthentication,
305
+ BloodTypeDataSchema,
306
+ FlexiblePagination,
307
+ None,
308
+ ](
309
+ service_context=self._service_context,
310
+ id=operation_id,
311
+ context=operation_context,
312
+ timestamp=OperationTimestamp(
313
+ executed_at=executed_at,
314
+ completed_at=completed_at,
315
+ duration=(completed_at - executed_at).total_seconds(),
316
+ ),
317
+ summary="Successfully retrieved multiple blood types from http request",
318
+ request_context=request_context,
319
+ authentication=authentication,
320
+ action=operation_action,
321
+ resource=RESOURCE,
322
+ result=result,
323
+ ).log(
324
+ self._logger, level=LogLevel.INFO
325
+ )
326
+ elif isinstance(parameters, ReadSingleParameter):
327
+ validated_response = SingleDataResponseSchema[
328
+ BloodTypeDataSchema, None
329
+ ].model_validate(response.json())
330
+ data = DataPair[BloodTypeDataSchema, None](
331
+ old=validated_response.data,
332
+ new=None,
333
+ )
334
+ result = ReadSingleResourceOperationResult[
335
+ BloodTypeDataSchema, None
336
+ ](
337
+ data=data,
338
+ pagination=validated_response.pagination,
339
+ metadata=None,
340
+ other=None,
341
+ )
342
+ ReadSingleResourceOperationSchema[
343
+ OptionalAuthentication, BloodTypeDataSchema, None
344
+ ](
345
+ service_context=self._service_context,
346
+ id=operation_id,
347
+ context=operation_context,
348
+ timestamp=OperationTimestamp(
349
+ executed_at=executed_at,
350
+ completed_at=completed_at,
351
+ duration=(completed_at - executed_at).total_seconds(),
352
+ ),
353
+ summary="Successfully retrieved single blood type from http request",
354
+ request_context=request_context,
355
+ authentication=authentication,
356
+ action=operation_action,
357
+ resource=RESOURCE,
358
+ result=result,
359
+ ).log(
360
+ self._logger, level=LogLevel.INFO
361
+ )
362
+
363
+ if parameters.use_cache:
364
+ await self._redis.set(
365
+ cache_key, result.model_dump_json(), Expiration.EXP_1MO.value
366
+ )
367
+
368
+ return result
369
+
370
+ self._raise_resource_http_request_error(
371
+ response=response,
372
+ operation_id=operation_id,
373
+ operation_context=operation_context,
374
+ executed_at=executed_at,
375
+ operation_action=operation_action,
376
+ request_context=request_context,
377
+ authentication=authentication,
378
+ resource=RESOURCE,
379
+ )