maleo-identity-client 0.6.2__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.
@@ -0,0 +1,282 @@
1
+ import json
2
+ from copy import deepcopy
3
+ from datetime import datetime, timezone
4
+ from typing import ClassVar, Literal, overload
5
+ from uuid import UUID
6
+ from nexo.client.service import ClientService
7
+ from nexo.database.enums import Connection
8
+ from nexo.database.utils import build_cache_key
9
+ from nexo.enums.cardinality import Cardinality
10
+ from nexo.enums.connection import Header
11
+ from nexo.logging.enums import LogLevel
12
+ from nexo.schemas.connection import ConnectionContext
13
+ from nexo.schemas.exception.factory import MaleoExceptionFactory
14
+ from nexo.schemas.operation.action.resource import ReadResourceOperationAction
15
+ from nexo.schemas.operation.enums import OperationType, Target
16
+ from nexo.schemas.operation.mixins import Timestamp
17
+ from nexo.schemas.operation.resource import (
18
+ ReadMultipleResourceOperation,
19
+ ReadSingleResourceOperation,
20
+ )
21
+ from nexo.schemas.pagination import StrictPagination
22
+ from nexo.schemas.resource import Resource, AggregateField
23
+ from nexo.schemas.response import (
24
+ MultipleDataResponse,
25
+ ReadMultipleDataResponse,
26
+ SingleDataResponse,
27
+ ReadSingleDataResponse,
28
+ )
29
+ from nexo.schemas.security.authorization import (
30
+ AnyAuthorization,
31
+ AuthorizationFactory,
32
+ )
33
+ from nexo.schemas.security.impersonation import OptImpersonation
34
+ from nexo.types.dict import OptStrToStrDict
35
+ from nexo.utils.merger import merge_dicts
36
+ from maleo.identity.constants.user_organization import USER_ORGANIZATION_RESOURCE
37
+ from maleo.identity.mixins.user_organization import (
38
+ is_id_identifier,
39
+ is_composite_identifier,
40
+ )
41
+ from maleo.identity.schemas.common import UserOrganizationSchema
42
+ from maleo.identity.schemas.user_organization import (
43
+ ReadMultipleParameter,
44
+ ReadSingleParameter,
45
+ )
46
+ from ..config import ClientConfig
47
+
48
+
49
+ class UserOrganizationClientService(ClientService[ClientConfig]):
50
+ _resource: ClassVar[Resource] = USER_ORGANIZATION_RESOURCE
51
+
52
+ @overload
53
+ async def read(
54
+ self,
55
+ cardinality: Literal[Cardinality.MULTIPLE],
56
+ *,
57
+ operation_id: UUID,
58
+ connection_context: ConnectionContext,
59
+ authorization: AnyAuthorization,
60
+ impersonation: OptImpersonation = None,
61
+ parameters: ReadMultipleParameter,
62
+ headers: OptStrToStrDict = None,
63
+ ) -> ReadMultipleDataResponse[UserOrganizationSchema, StrictPagination, None]: ...
64
+ @overload
65
+ async def read(
66
+ self,
67
+ cardinality: Literal[Cardinality.SINGLE],
68
+ *,
69
+ operation_id: UUID,
70
+ connection_context: ConnectionContext,
71
+ authorization: AnyAuthorization,
72
+ impersonation: OptImpersonation = None,
73
+ parameters: ReadSingleParameter,
74
+ headers: OptStrToStrDict = None,
75
+ ) -> ReadSingleDataResponse[UserOrganizationSchema, None]: ...
76
+ async def read(
77
+ self,
78
+ cardinality: Cardinality,
79
+ *,
80
+ operation_id: UUID,
81
+ connection_context: ConnectionContext,
82
+ authorization: AnyAuthorization,
83
+ impersonation: OptImpersonation = None,
84
+ parameters: ReadMultipleParameter | ReadSingleParameter,
85
+ headers: OptStrToStrDict = None,
86
+ ) -> (
87
+ ReadMultipleDataResponse[UserOrganizationSchema, StrictPagination, None]
88
+ | ReadSingleDataResponse[UserOrganizationSchema, None]
89
+ ):
90
+ redis_client = self._redis.manager.client.get(Connection.ASYNC)
91
+
92
+ executed_at = datetime.now(tz=timezone.utc)
93
+
94
+ # Define arguments being used in this function
95
+ positional_arguments = [cardinality]
96
+ keyword_arguments = {
97
+ "authorization": (
98
+ authorization.model_dump(mode="json")
99
+ if authorization is not None
100
+ else None
101
+ ),
102
+ "parameters": parameters.model_dump(mode="json"),
103
+ }
104
+
105
+ # Define full function string
106
+ ext = f"({json.dumps(positional_arguments)}|{json.dumps(keyword_arguments)})"
107
+
108
+ # Define full cache_key
109
+ cache_key = build_cache_key(ext, namespace=self._namespace)
110
+
111
+ if parameters.use_cache:
112
+ # Initialize cache operation context
113
+ operation_context = deepcopy(self._operation_context)
114
+ operation_context.target.type = Target.CACHE
115
+
116
+ redis_response_str = await redis_client.get(cache_key)
117
+
118
+ if redis_response_str is not None:
119
+ operation_timestamp = Timestamp.completed_now(executed_at)
120
+ if cardinality is Cardinality.MULTIPLE:
121
+ response = ReadMultipleDataResponse[
122
+ UserOrganizationSchema, StrictPagination, None
123
+ ].model_validate_json(redis_response_str)
124
+ operation = ReadMultipleResourceOperation[
125
+ UserOrganizationSchema, StrictPagination, None
126
+ ](
127
+ application_context=self._application_context,
128
+ id=operation_id,
129
+ context=operation_context,
130
+ resource=self._resource,
131
+ timestamp=operation_timestamp,
132
+ summary=f"Successfully read multiple {self._resource.aggregate(AggregateField.NAME, sep=" ").lower()} from cache",
133
+ connection_context=connection_context,
134
+ authentication=None,
135
+ authorization=authorization,
136
+ impersonation=impersonation,
137
+ response=response,
138
+ )
139
+ operation.log(self._logger, LogLevel.INFO)
140
+ operation.publish(self._logger, self._publishers)
141
+ elif cardinality is Cardinality.SINGLE:
142
+ response = ReadSingleDataResponse[
143
+ UserOrganizationSchema, None
144
+ ].model_validate_json(redis_response_str)
145
+ operation = ReadSingleResourceOperation[
146
+ UserOrganizationSchema, None
147
+ ](
148
+ application_context=self._application_context,
149
+ id=operation_id,
150
+ context=operation_context,
151
+ resource=self._resource,
152
+ timestamp=operation_timestamp,
153
+ summary=f"Successfully read single {self._resource.aggregate(AggregateField.NAME, sep=" ").lower()} from cache",
154
+ connection_context=connection_context,
155
+ authentication=None,
156
+ authorization=authorization,
157
+ impersonation=impersonation,
158
+ response=response,
159
+ )
160
+ operation.log(self._logger, LogLevel.INFO)
161
+ operation.publish(self._logger, self._publishers)
162
+
163
+ return response # type: ignore
164
+
165
+ operation_context = deepcopy(self._operation_context)
166
+ operation_context.target.type = Target.MICROSERVICE
167
+
168
+ async with self._http_client_manager.get() as http_client:
169
+ base_headers = {
170
+ Header.CONTENT_TYPE.value: "application/json",
171
+ Header.X_OPERATION_ID.value: str(operation_id),
172
+ }
173
+ if impersonation is not None:
174
+ base_headers[Header.X_USER_ID.value] = str(impersonation.user_id)
175
+ if impersonation.organization_id is not None:
176
+ base_headers[Header.X_ORGANIZATION_ID.value] = str(
177
+ impersonation.organization_id
178
+ )
179
+
180
+ if headers is not None:
181
+ headers = merge_dicts(base_headers, headers)
182
+ else:
183
+ headers = base_headers
184
+
185
+ auth = AuthorizationFactory.httpx_auth(
186
+ scheme=authorization.scheme, authorization=authorization.credentials
187
+ )
188
+
189
+ base_url = f"{self._config.url}/v1/{self._resource.identifiers[-1].slug}"
190
+ if isinstance(parameters, ReadMultipleParameter):
191
+ url = base_url
192
+ elif isinstance(parameters, ReadSingleParameter):
193
+ if is_composite_identifier(parameters.identifier):
194
+ value = parameters.identifier.value
195
+ url = f"{self._config.url}/v1/users/{value[0]}/organizations/{value[1]}"
196
+ elif is_id_identifier(parameters.identifier):
197
+ url = base_url + f"/{parameters.identifier.value}"
198
+ else:
199
+ url = (
200
+ base_url
201
+ + f"/{parameters.identifier.type}/{parameters.identifier.value}"
202
+ )
203
+
204
+ params = parameters.to_query_params()
205
+
206
+ response = await http_client.get(
207
+ url, params=params, headers=headers, auth=auth
208
+ )
209
+
210
+ operation_timestamp = Timestamp.completed_now(executed_at)
211
+
212
+ if response.is_error:
213
+ exc = MaleoExceptionFactory.from_httpx(
214
+ response,
215
+ operation_type=OperationType.REQUEST,
216
+ application_context=self._application_context,
217
+ operation_id=operation_id,
218
+ operation_context=operation_context,
219
+ operation_action=ReadResourceOperationAction(),
220
+ operation_timestamp=operation_timestamp,
221
+ connection_context=connection_context,
222
+ authentication=None,
223
+ authorization=authorization,
224
+ impersonation=impersonation,
225
+ logger=self._logger,
226
+ )
227
+ exc.log_and_publish_operation(self._logger, self._publishers)
228
+ raise exc
229
+
230
+ if isinstance(parameters, ReadMultipleParameter):
231
+ validated_response = MultipleDataResponse[
232
+ UserOrganizationSchema, StrictPagination, None
233
+ ].model_validate(response.json())
234
+ service_response = ReadMultipleDataResponse[
235
+ UserOrganizationSchema, StrictPagination, None
236
+ ].new(
237
+ data=validated_response.data,
238
+ pagination=validated_response.pagination,
239
+ )
240
+ operation = ReadMultipleResourceOperation[
241
+ UserOrganizationSchema, StrictPagination, None
242
+ ](
243
+ application_context=self._application_context,
244
+ id=operation_id,
245
+ context=operation_context,
246
+ resource=self._resource,
247
+ timestamp=operation_timestamp,
248
+ summary=f"Successfully read multiple {self._resource.aggregate(AggregateField.NAME, sep=" ").lower()} from microservice",
249
+ connection_context=connection_context,
250
+ authentication=None,
251
+ authorization=authorization,
252
+ impersonation=impersonation,
253
+ response=service_response,
254
+ )
255
+ operation.log(self._logger, LogLevel.INFO)
256
+ operation.publish(self._logger, self._publishers)
257
+ elif isinstance(parameters, ReadSingleParameter):
258
+ validated_response = SingleDataResponse[
259
+ UserOrganizationSchema, None
260
+ ].model_validate(response.json())
261
+ service_response = ReadSingleDataResponse[
262
+ UserOrganizationSchema, None
263
+ ].new(
264
+ data=validated_response.data,
265
+ )
266
+ operation = ReadSingleResourceOperation[UserOrganizationSchema, None](
267
+ application_context=self._application_context,
268
+ id=operation_id,
269
+ context=operation_context,
270
+ resource=self._resource,
271
+ timestamp=operation_timestamp,
272
+ summary=f"Successfully read single {self._resource.aggregate(AggregateField.NAME, sep=" ").lower()} from microservice",
273
+ connection_context=connection_context,
274
+ authentication=None,
275
+ authorization=authorization,
276
+ impersonation=impersonation,
277
+ response=service_response,
278
+ )
279
+ operation.log(self._logger, LogLevel.INFO)
280
+ operation.publish(self._logger, self._publishers)
281
+
282
+ return service_response # type: ignore
@@ -0,0 +1,288 @@
1
+ import json
2
+ from copy import deepcopy
3
+ from datetime import datetime, timezone
4
+ from typing import ClassVar, Literal, overload
5
+ from uuid import UUID
6
+ from nexo.client.service import ClientService
7
+ from nexo.database.enums import Connection
8
+ from nexo.database.utils import build_cache_key
9
+ from nexo.enums.cardinality import Cardinality
10
+ from nexo.enums.connection import Header
11
+ from nexo.logging.enums import LogLevel
12
+ from nexo.schemas.connection import ConnectionContext
13
+ from nexo.schemas.exception.factory import MaleoExceptionFactory
14
+ from nexo.schemas.operation.action.resource import ReadResourceOperationAction
15
+ from nexo.schemas.operation.enums import OperationType, Target
16
+ from nexo.schemas.operation.mixins import Timestamp
17
+ from nexo.schemas.operation.resource import (
18
+ ReadMultipleResourceOperation,
19
+ ReadSingleResourceOperation,
20
+ )
21
+ from nexo.schemas.pagination import StrictPagination
22
+ from nexo.schemas.resource import Resource, AggregateField
23
+ from nexo.schemas.response import (
24
+ MultipleDataResponse,
25
+ ReadMultipleDataResponse,
26
+ SingleDataResponse,
27
+ ReadSingleDataResponse,
28
+ )
29
+ from nexo.schemas.security.authorization import (
30
+ AnyAuthorization,
31
+ AuthorizationFactory,
32
+ )
33
+ from nexo.schemas.security.impersonation import OptImpersonation
34
+ from nexo.types.dict import OptStrToStrDict
35
+ from nexo.utils.merger import merge_dicts
36
+ from maleo.identity.constants.user_organization_role import (
37
+ USER_ORGANIZATION_ROLE_RESOURCE,
38
+ )
39
+ from maleo.identity.mixins.user_organization_role import (
40
+ is_id_identifier,
41
+ is_composite_identifier,
42
+ )
43
+ from maleo.identity.schemas.common import UserOrganizationRoleSchema
44
+ from maleo.identity.schemas.user_organization_role import (
45
+ ReadMultipleParameter,
46
+ ReadSingleParameter,
47
+ )
48
+ from ..config import ClientConfig
49
+
50
+
51
+ class UserOrganizationRoleClientService(ClientService[ClientConfig]):
52
+ _resource: ClassVar[Resource] = USER_ORGANIZATION_ROLE_RESOURCE
53
+
54
+ @overload
55
+ async def read(
56
+ self,
57
+ cardinality: Literal[Cardinality.MULTIPLE],
58
+ *,
59
+ operation_id: UUID,
60
+ connection_context: ConnectionContext,
61
+ authorization: AnyAuthorization,
62
+ impersonation: OptImpersonation = None,
63
+ parameters: ReadMultipleParameter,
64
+ headers: OptStrToStrDict = None,
65
+ ) -> ReadMultipleDataResponse[
66
+ UserOrganizationRoleSchema, StrictPagination, None
67
+ ]: ...
68
+ @overload
69
+ async def read(
70
+ self,
71
+ cardinality: Literal[Cardinality.SINGLE],
72
+ *,
73
+ operation_id: UUID,
74
+ connection_context: ConnectionContext,
75
+ authorization: AnyAuthorization,
76
+ impersonation: OptImpersonation = None,
77
+ parameters: ReadSingleParameter,
78
+ headers: OptStrToStrDict = None,
79
+ ) -> ReadSingleDataResponse[UserOrganizationRoleSchema, None]: ...
80
+ async def read(
81
+ self,
82
+ cardinality: Cardinality,
83
+ *,
84
+ operation_id: UUID,
85
+ connection_context: ConnectionContext,
86
+ authorization: AnyAuthorization,
87
+ impersonation: OptImpersonation = None,
88
+ parameters: ReadMultipleParameter | ReadSingleParameter,
89
+ headers: OptStrToStrDict = None,
90
+ ) -> (
91
+ ReadMultipleDataResponse[UserOrganizationRoleSchema, StrictPagination, None]
92
+ | ReadSingleDataResponse[UserOrganizationRoleSchema, None]
93
+ ):
94
+ redis_client = self._redis.manager.client.get(Connection.ASYNC)
95
+
96
+ executed_at = datetime.now(tz=timezone.utc)
97
+
98
+ # Define arguments being used in this function
99
+ positional_arguments = [cardinality]
100
+ keyword_arguments = {
101
+ "authorization": (
102
+ authorization.model_dump(mode="json")
103
+ if authorization is not None
104
+ else None
105
+ ),
106
+ "parameters": parameters.model_dump(mode="json"),
107
+ }
108
+
109
+ # Define full function string
110
+ ext = f"({json.dumps(positional_arguments)}|{json.dumps(keyword_arguments)})"
111
+
112
+ # Define full cache_key
113
+ cache_key = build_cache_key(ext, namespace=self._namespace)
114
+
115
+ if parameters.use_cache:
116
+ # Initialize cache operation context
117
+ operation_context = deepcopy(self._operation_context)
118
+ operation_context.target.type = Target.CACHE
119
+
120
+ redis_response_str = await redis_client.get(cache_key)
121
+
122
+ if redis_response_str is not None:
123
+ operation_timestamp = Timestamp.completed_now(executed_at)
124
+ if cardinality is Cardinality.MULTIPLE:
125
+ response = ReadMultipleDataResponse[
126
+ UserOrganizationRoleSchema, StrictPagination, None
127
+ ].model_validate_json(redis_response_str)
128
+ operation = ReadMultipleResourceOperation[
129
+ UserOrganizationRoleSchema, StrictPagination, None
130
+ ](
131
+ application_context=self._application_context,
132
+ id=operation_id,
133
+ context=operation_context,
134
+ resource=self._resource,
135
+ timestamp=operation_timestamp,
136
+ summary=f"Successfully read multiple {self._resource.aggregate(AggregateField.NAME, sep=" ").lower()} from cache",
137
+ connection_context=connection_context,
138
+ authentication=None,
139
+ authorization=authorization,
140
+ impersonation=impersonation,
141
+ response=response,
142
+ )
143
+ operation.log(self._logger, LogLevel.INFO)
144
+ operation.publish(self._logger, self._publishers)
145
+ elif cardinality is Cardinality.SINGLE:
146
+ response = ReadSingleDataResponse[
147
+ UserOrganizationRoleSchema, None
148
+ ].model_validate_json(redis_response_str)
149
+ operation = ReadSingleResourceOperation[
150
+ UserOrganizationRoleSchema, None
151
+ ](
152
+ application_context=self._application_context,
153
+ id=operation_id,
154
+ context=operation_context,
155
+ resource=self._resource,
156
+ timestamp=operation_timestamp,
157
+ summary=f"Successfully read single {self._resource.aggregate(AggregateField.NAME, sep=" ").lower()} from cache",
158
+ connection_context=connection_context,
159
+ authentication=None,
160
+ authorization=authorization,
161
+ impersonation=impersonation,
162
+ response=response,
163
+ )
164
+ operation.log(self._logger, LogLevel.INFO)
165
+ operation.publish(self._logger, self._publishers)
166
+
167
+ return response # type: ignore
168
+
169
+ operation_context = deepcopy(self._operation_context)
170
+ operation_context.target.type = Target.MICROSERVICE
171
+
172
+ async with self._http_client_manager.get() as http_client:
173
+ base_headers = {
174
+ Header.CONTENT_TYPE.value: "application/json",
175
+ Header.X_OPERATION_ID.value: str(operation_id),
176
+ }
177
+ if impersonation is not None:
178
+ base_headers[Header.X_USER_ID.value] = str(impersonation.user_id)
179
+ if impersonation.organization_id is not None:
180
+ base_headers[Header.X_ORGANIZATION_ID.value] = str(
181
+ impersonation.organization_id
182
+ )
183
+
184
+ if headers is not None:
185
+ headers = merge_dicts(base_headers, headers)
186
+ else:
187
+ headers = base_headers
188
+
189
+ auth = AuthorizationFactory.httpx_auth(
190
+ scheme=authorization.scheme, authorization=authorization.credentials
191
+ )
192
+
193
+ base_url = f"{self._config.url}/v1/{self._resource.identifiers[-1].slug}"
194
+ if isinstance(parameters, ReadMultipleParameter):
195
+ url = base_url
196
+ elif isinstance(parameters, ReadSingleParameter):
197
+ if is_composite_identifier(parameters.identifier):
198
+ value = parameters.identifier.value
199
+ url = f"{self._config.url}/v1/users/{value[0]}/organizations/{value[1]}/medical-roles/{value[2]}"
200
+ elif is_id_identifier(parameters.identifier):
201
+ url = base_url + f"/{parameters.identifier.value}"
202
+ else:
203
+ url = (
204
+ base_url
205
+ + f"/{parameters.identifier.type}/{parameters.identifier.value}"
206
+ )
207
+
208
+ params = parameters.to_query_params()
209
+
210
+ response = await http_client.get(
211
+ url, params=params, headers=headers, auth=auth
212
+ )
213
+
214
+ operation_timestamp = Timestamp.completed_now(executed_at)
215
+
216
+ if response.is_error:
217
+ exc = MaleoExceptionFactory.from_httpx(
218
+ response,
219
+ operation_type=OperationType.REQUEST,
220
+ application_context=self._application_context,
221
+ operation_id=operation_id,
222
+ operation_context=operation_context,
223
+ operation_action=ReadResourceOperationAction(),
224
+ operation_timestamp=operation_timestamp,
225
+ connection_context=connection_context,
226
+ authentication=None,
227
+ authorization=authorization,
228
+ impersonation=impersonation,
229
+ logger=self._logger,
230
+ )
231
+ exc.log_and_publish_operation(self._logger, self._publishers)
232
+ raise exc
233
+
234
+ if isinstance(parameters, ReadMultipleParameter):
235
+ validated_response = MultipleDataResponse[
236
+ UserOrganizationRoleSchema, StrictPagination, None
237
+ ].model_validate(response.json())
238
+ service_response = ReadMultipleDataResponse[
239
+ UserOrganizationRoleSchema, StrictPagination, None
240
+ ].new(
241
+ data=validated_response.data,
242
+ pagination=validated_response.pagination,
243
+ )
244
+ operation = ReadMultipleResourceOperation[
245
+ UserOrganizationRoleSchema, StrictPagination, None
246
+ ](
247
+ application_context=self._application_context,
248
+ id=operation_id,
249
+ context=operation_context,
250
+ resource=self._resource,
251
+ timestamp=operation_timestamp,
252
+ summary=f"Successfully read multiple {self._resource.aggregate(AggregateField.NAME, sep=" ").lower()} from microservice",
253
+ connection_context=connection_context,
254
+ authentication=None,
255
+ authorization=authorization,
256
+ impersonation=impersonation,
257
+ response=service_response,
258
+ )
259
+ operation.log(self._logger, LogLevel.INFO)
260
+ operation.publish(self._logger, self._publishers)
261
+ elif isinstance(parameters, ReadSingleParameter):
262
+ validated_response = SingleDataResponse[
263
+ UserOrganizationRoleSchema, None
264
+ ].model_validate(response.json())
265
+ service_response = ReadSingleDataResponse[
266
+ UserOrganizationRoleSchema, None
267
+ ].new(
268
+ data=validated_response.data,
269
+ )
270
+ operation = ReadSingleResourceOperation[
271
+ UserOrganizationRoleSchema, None
272
+ ](
273
+ application_context=self._application_context,
274
+ id=operation_id,
275
+ context=operation_context,
276
+ resource=self._resource,
277
+ timestamp=operation_timestamp,
278
+ summary=f"Successfully read single {self._resource.aggregate(AggregateField.NAME, sep=" ").lower()} from microservice",
279
+ connection_context=connection_context,
280
+ authentication=None,
281
+ authorization=authorization,
282
+ impersonation=impersonation,
283
+ response=service_response,
284
+ )
285
+ operation.log(self._logger, LogLevel.INFO)
286
+ operation.publish(self._logger, self._publishers)
287
+
288
+ return service_response # type: ignore