mms-client 1.0.5__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.
- mms_client/__init__.py +0 -0
- mms_client/client.py +14 -0
- mms_client/py.typed +0 -0
- mms_client/schemas/wsdl/mi-web-service-jbms.wsdl +276 -0
- mms_client/schemas/wsdl/omi-web-service.wsdl +262 -0
- mms_client/schemas/xsd/mi-market.xsd +2395 -0
- mms_client/schemas/xsd/mi-outbnd-reports.xsd +1489 -0
- mms_client/schemas/xsd/mi-report.xsd +379 -0
- mms_client/schemas/xsd/mpr.xsd +1817 -0
- mms_client/schemas/xsd/omi.xsd +793 -0
- mms_client/security/__init__.py +0 -0
- mms_client/security/certs.py +44 -0
- mms_client/security/crypto.py +57 -0
- mms_client/services/__init__.py +0 -0
- mms_client/services/base.py +591 -0
- mms_client/services/market.py +107 -0
- mms_client/services/omi.py +13 -0
- mms_client/services/registration.py +13 -0
- mms_client/services/report.py +13 -0
- mms_client/types/__init__.py +0 -0
- mms_client/types/base.py +272 -0
- mms_client/types/enums.py +18 -0
- mms_client/types/fields.py +153 -0
- mms_client/types/market.py +61 -0
- mms_client/types/offer.py +163 -0
- mms_client/types/transport.py +130 -0
- mms_client/utils/__init__.py +0 -0
- mms_client/utils/errors.py +66 -0
- mms_client/utils/serialization.py +513 -0
- mms_client/utils/web.py +220 -0
- mms_client-1.0.5.dist-info/LICENSE +24 -0
- mms_client-1.0.5.dist-info/METADATA +202 -0
- mms_client-1.0.5.dist-info/RECORD +34 -0
- mms_client-1.0.5.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
"""Contains the client layer for communicating with the MMS server."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from logging import Logger
|
|
5
|
+
from logging import getLogger
|
|
6
|
+
from typing import Dict
|
|
7
|
+
from typing import Generic
|
|
8
|
+
from typing import List
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from typing import Protocol
|
|
11
|
+
from typing import Tuple
|
|
12
|
+
from typing import Type
|
|
13
|
+
|
|
14
|
+
from mms_client.security.crypto import Certificate
|
|
15
|
+
from mms_client.security.crypto import CryptoWrapper
|
|
16
|
+
from mms_client.types.base import BaseResponse
|
|
17
|
+
from mms_client.types.base import E
|
|
18
|
+
from mms_client.types.base import MultiResponse
|
|
19
|
+
from mms_client.types.base import P
|
|
20
|
+
from mms_client.types.base import Response
|
|
21
|
+
from mms_client.types.base import ResponseCommon
|
|
22
|
+
from mms_client.types.base import ValidationStatus
|
|
23
|
+
from mms_client.types.transport import Attachment
|
|
24
|
+
from mms_client.types.transport import MmsRequest
|
|
25
|
+
from mms_client.types.transport import MmsResponse
|
|
26
|
+
from mms_client.types.transport import RequestDataType
|
|
27
|
+
from mms_client.types.transport import RequestType
|
|
28
|
+
from mms_client.types.transport import ResponseDataType
|
|
29
|
+
from mms_client.utils.errors import AudienceError
|
|
30
|
+
from mms_client.utils.errors import MMSClientError
|
|
31
|
+
from mms_client.utils.errors import MMSValidationError
|
|
32
|
+
from mms_client.utils.serialization import Serializer
|
|
33
|
+
from mms_client.utils.web import ClientType
|
|
34
|
+
from mms_client.utils.web import Interface
|
|
35
|
+
from mms_client.utils.web import ZWrapper
|
|
36
|
+
|
|
37
|
+
# Set the default logger for the MMS client
|
|
38
|
+
default_logger = getLogger("MMS Client")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ServiceConfiguration:
|
|
43
|
+
"""Configuration for a service on the MMS server."""
|
|
44
|
+
|
|
45
|
+
# The interface for the service
|
|
46
|
+
interface: Interface
|
|
47
|
+
|
|
48
|
+
# A serializer used to serialize and deserialize the data for the service
|
|
49
|
+
serializer: Serializer
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class EndpointConfiguration(Generic[E, P]):
|
|
54
|
+
"""Configuration for an endpoint on the MMS server."""
|
|
55
|
+
|
|
56
|
+
# The name of the endpoint
|
|
57
|
+
name: str
|
|
58
|
+
|
|
59
|
+
# The allowed client types for the endpoint
|
|
60
|
+
allowed_client: Optional[ClientType]
|
|
61
|
+
|
|
62
|
+
# The service for the endpoint
|
|
63
|
+
service: ServiceConfiguration
|
|
64
|
+
|
|
65
|
+
# The type of request to submit to the MMS server
|
|
66
|
+
request_type: RequestType
|
|
67
|
+
|
|
68
|
+
# The type of payload to expect in the response
|
|
69
|
+
response_envelope_type: Optional[Type[E]]
|
|
70
|
+
|
|
71
|
+
# The type of data to expect in the response
|
|
72
|
+
response_data_type: Optional[Type[P]]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ClientProto(Protocol):
|
|
76
|
+
"""Protocol for the MMS client, allowing for proper typing of the mixins."""
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def participant(self) -> str:
|
|
80
|
+
"""Return the MMS code of the business entity to which the requesting user belongs."""
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def user(self) -> str:
|
|
84
|
+
"""Return the user name of the person making the request."""
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def logger(self) -> Logger:
|
|
88
|
+
"""Return the logger for the client."""
|
|
89
|
+
|
|
90
|
+
def verify_audience(self, config: EndpointConfiguration) -> None:
|
|
91
|
+
"""Verify that the client type is allowed.
|
|
92
|
+
|
|
93
|
+
Some MMS endpoints are only accessible to certain client types. This method is used to verify that the client
|
|
94
|
+
type is allowed to access the endpoint.
|
|
95
|
+
|
|
96
|
+
Arguments:
|
|
97
|
+
config (EndpointConfiguration): The configuration for the endpoint.
|
|
98
|
+
|
|
99
|
+
Raises:
|
|
100
|
+
ValueError: If the client type is not allowed.
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def request_one(
|
|
104
|
+
self,
|
|
105
|
+
envelope: E,
|
|
106
|
+
data: P,
|
|
107
|
+
config: EndpointConfiguration,
|
|
108
|
+
) -> Tuple[Response[E, P], Dict[str, bytes]]:
|
|
109
|
+
"""Submit a request to the MMS server and return the response.
|
|
110
|
+
|
|
111
|
+
Arguments:
|
|
112
|
+
envelope (Envelope): The payload envelope to submit to the MMS server.
|
|
113
|
+
data (Payload): The data to submit to the MMS server.
|
|
114
|
+
config (EndpointConfiguration): The configuration for the endpoint.
|
|
115
|
+
|
|
116
|
+
Returns: The response from the MMS server.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def request_many(
|
|
120
|
+
self,
|
|
121
|
+
envelope: E,
|
|
122
|
+
data: P,
|
|
123
|
+
config: EndpointConfiguration,
|
|
124
|
+
) -> Tuple[MultiResponse[E, P], Dict[str, bytes]]:
|
|
125
|
+
"""Submit a request to the MMS server and return the multi-response.
|
|
126
|
+
|
|
127
|
+
Arguments:
|
|
128
|
+
envelope (Envelope): The payload envelope to submit to the MMS server.
|
|
129
|
+
data (Payload): The data to submit to the MMS server.
|
|
130
|
+
config (EndpointConfiguration): The configuration for the endpoint.
|
|
131
|
+
|
|
132
|
+
Returns: The multi-response from the MMS server.
|
|
133
|
+
"""
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def mms_endpoint(
|
|
137
|
+
name: str,
|
|
138
|
+
service: ServiceConfiguration,
|
|
139
|
+
request_type: RequestType,
|
|
140
|
+
allowed_client: Optional[ClientType] = None,
|
|
141
|
+
resp_envelope_type: Optional[Type[E]] = None,
|
|
142
|
+
resp_data_type: Optional[Type[P]] = None,
|
|
143
|
+
):
|
|
144
|
+
"""Create a decorator for an MMS endpoint.
|
|
145
|
+
|
|
146
|
+
This decorator is used to mark a method as an MMS endpoint. It will add the endpoint configuration to the function
|
|
147
|
+
and submit the request to the MMS server when the function is called. The response will be extracted and returned.
|
|
148
|
+
Decorated functions will only be responsible for creating the payload envelope to submit to the MMS server.
|
|
149
|
+
|
|
150
|
+
Arguments:
|
|
151
|
+
name (str): The name of the endpoint.
|
|
152
|
+
service (ServiceConfiguration): The configuration for the service.
|
|
153
|
+
request_type (RequestType): The type of request to submit to the MMS server.
|
|
154
|
+
allowed_client (ClientType): The type of client that is allowed to access the endpoint. If this is not provided,
|
|
155
|
+
then any client type is allowed.
|
|
156
|
+
resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
|
|
157
|
+
the response envelope will be assumed to have the same type as the request envelope.
|
|
158
|
+
resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
|
|
159
|
+
response data will be assumed to have the same type as the request data.
|
|
160
|
+
"""
|
|
161
|
+
# First, create the endpoint configuration from the given parameters
|
|
162
|
+
config = EndpointConfiguration(name, allowed_client, service, request_type, resp_envelope_type, resp_data_type)
|
|
163
|
+
|
|
164
|
+
# Next, create a decorator that will add the endpoint configuration to the function
|
|
165
|
+
def decorator(func):
|
|
166
|
+
def wrapper(self: ClientProto, *args, **kwargs) -> Optional[P]:
|
|
167
|
+
|
|
168
|
+
# First, verify that the client type is allowed
|
|
169
|
+
self.verify_audience(config)
|
|
170
|
+
|
|
171
|
+
# Next, call the wrapped function to get the envelope
|
|
172
|
+
envelope = func(self, *args, **kwargs)
|
|
173
|
+
|
|
174
|
+
# Now, submit the request to the MMS server and get the response
|
|
175
|
+
resp, _ = self.request_one(envelope, args[0], config)
|
|
176
|
+
|
|
177
|
+
# Finally, extract the data from the response and return it
|
|
178
|
+
return resp.data
|
|
179
|
+
|
|
180
|
+
return wrapper
|
|
181
|
+
|
|
182
|
+
# Finally, return the decorator
|
|
183
|
+
return decorator
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def mms_multi_endpoint(
|
|
187
|
+
name: str,
|
|
188
|
+
service: ServiceConfiguration,
|
|
189
|
+
request_type: RequestType,
|
|
190
|
+
allowed_client: Optional[ClientType] = None,
|
|
191
|
+
resp_envelope_type: Optional[Type[E]] = None,
|
|
192
|
+
resp_data_type: Optional[Type[P]] = None,
|
|
193
|
+
):
|
|
194
|
+
"""Create a decorator for an MMS multi-response endpoint.
|
|
195
|
+
|
|
196
|
+
This decorator is used to mark a method as an MMS multi-response endpoint. It will add the endpoint configuration to
|
|
197
|
+
the function and submit the request to the MMS server when the function is called. The multi-response will be
|
|
198
|
+
extracted and returned. Decorated functions will only be responsible for creating the payload envelope to submit to
|
|
199
|
+
the MMS server.
|
|
200
|
+
|
|
201
|
+
Arguments:
|
|
202
|
+
name (str): The name of the endpoint.
|
|
203
|
+
service (ServiceConfiguration): The configuration for the service.
|
|
204
|
+
request_type (RequestType): The type of request to submit to the MMS server.
|
|
205
|
+
allowed_client (ClientType): The type of client that is allowed to access the endpoint. If this is not provided,
|
|
206
|
+
then any client type is allowed.
|
|
207
|
+
resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
|
|
208
|
+
the response envelope will be assumed to have the same type as the request envelope.
|
|
209
|
+
resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
|
|
210
|
+
response data will be assumed to have the same type as the request data. Note, that
|
|
211
|
+
this is not intended to account for the expected sequence type of the response data.
|
|
212
|
+
That is already handled in the wrapped function, so this should only be set if the
|
|
213
|
+
inner data type being returned differs from what was sent.
|
|
214
|
+
"""
|
|
215
|
+
# First, create the endpoint configuration from the given parameters
|
|
216
|
+
config = EndpointConfiguration(name, allowed_client, service, request_type, resp_envelope_type, resp_data_type)
|
|
217
|
+
|
|
218
|
+
# Next, create a decorator that will add the endpoint configuration to the function
|
|
219
|
+
def decorator(func):
|
|
220
|
+
def wrapper(self: ClientProto, *args, **kwargs) -> List[P]:
|
|
221
|
+
self.logger.info(f"{config.name}: Called with args: {args[1:]}...")
|
|
222
|
+
|
|
223
|
+
# First, verify that the client type is allowed
|
|
224
|
+
self.verify_audience(config)
|
|
225
|
+
|
|
226
|
+
# Next, call the wrapped function to get the envelope
|
|
227
|
+
envelope = func(self, *args, **kwargs)
|
|
228
|
+
|
|
229
|
+
# Now, submit the request to the MMS server and get the response
|
|
230
|
+
resp, _ = self.request_many(envelope, args[0], config)
|
|
231
|
+
|
|
232
|
+
# Finally, extract the data from the response and return it
|
|
233
|
+
self.logger.info(f"{config.name}: Returning {len(resp.data)} item(s).")
|
|
234
|
+
return resp.data
|
|
235
|
+
|
|
236
|
+
return wrapper
|
|
237
|
+
|
|
238
|
+
# Finally, return the decorator
|
|
239
|
+
return decorator
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
243
|
+
"""Base end-client for the MMS server.
|
|
244
|
+
|
|
245
|
+
This class is used to communicate with the MMS server.
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
def __init__(
|
|
249
|
+
self,
|
|
250
|
+
participant: str,
|
|
251
|
+
user: str,
|
|
252
|
+
client_type: ClientType,
|
|
253
|
+
cert: Certificate,
|
|
254
|
+
logger: Optional[Logger] = None,
|
|
255
|
+
is_admin: bool = False,
|
|
256
|
+
test: bool = False,
|
|
257
|
+
):
|
|
258
|
+
"""Create a new MMS client with the given participant, user, client type, and authentication.
|
|
259
|
+
|
|
260
|
+
Arguments:
|
|
261
|
+
participant (str): The MMS code of the business entity to which the requesting user belongs.
|
|
262
|
+
user (str): The user name of the person making the request.
|
|
263
|
+
client_type (ClientType): The type of client to use for making requests to the MMS server.
|
|
264
|
+
cert (Certificate): The certificate to use for signing requests.
|
|
265
|
+
logger (Logger): The logger to use for instrumentation. If this is not provided, then the default
|
|
266
|
+
logger will be used.
|
|
267
|
+
is_admin (bool): Whether the user is an admin (i.e. is a market operator).
|
|
268
|
+
test (bool): Whether to use the test server.
|
|
269
|
+
"""
|
|
270
|
+
# First, save the base field associated with the client
|
|
271
|
+
self._participant = participant
|
|
272
|
+
self._user = user
|
|
273
|
+
self._client_type = client_type
|
|
274
|
+
self._is_admin = is_admin
|
|
275
|
+
self._test = test
|
|
276
|
+
|
|
277
|
+
# Next, save the security-related fields associated with the client
|
|
278
|
+
self._cert = cert
|
|
279
|
+
self._signer = CryptoWrapper(cert)
|
|
280
|
+
|
|
281
|
+
# Now, set our logger to either the provided logger or the default logger
|
|
282
|
+
self._logger = logger or default_logger
|
|
283
|
+
|
|
284
|
+
# Finally, create a list of wrappers for the different interfaces
|
|
285
|
+
self._wrappers: Dict[Interface, ZWrapper] = {}
|
|
286
|
+
|
|
287
|
+
@property
|
|
288
|
+
def participant(self) -> str:
|
|
289
|
+
"""Return the MMS code of the business entity to which the requesting user belongs."""
|
|
290
|
+
return self._participant
|
|
291
|
+
|
|
292
|
+
@property
|
|
293
|
+
def user(self) -> str:
|
|
294
|
+
"""Return the user name of the person making the request."""
|
|
295
|
+
return self._user
|
|
296
|
+
|
|
297
|
+
@property
|
|
298
|
+
def logger(self) -> Logger:
|
|
299
|
+
"""Return the logger for the client."""
|
|
300
|
+
return self._logger
|
|
301
|
+
|
|
302
|
+
def verify_audience(self, config: EndpointConfiguration) -> None:
|
|
303
|
+
"""Verify that the client type is allowed.
|
|
304
|
+
|
|
305
|
+
Some MMS endpoints are only accessible to certain client types. This method is used to verify that the client
|
|
306
|
+
type is allowed to access the endpoint.
|
|
307
|
+
|
|
308
|
+
Arguments:
|
|
309
|
+
config (EndpointConfiguration): The configuration for the endpoint.
|
|
310
|
+
|
|
311
|
+
Raises:
|
|
312
|
+
ValueError: If the client type is not allowed.
|
|
313
|
+
"""
|
|
314
|
+
self._logger.debug(
|
|
315
|
+
f"{config.name}: Verifying audience. Allowed client: "
|
|
316
|
+
f"{config.allowed_client.name if config.allowed_client else 'Any'}."
|
|
317
|
+
)
|
|
318
|
+
if config.allowed_client and self._client_type != config.allowed_client:
|
|
319
|
+
raise AudienceError(config.name, config.allowed_client, self._client_type)
|
|
320
|
+
|
|
321
|
+
def request_one(
|
|
322
|
+
self,
|
|
323
|
+
envelope: E,
|
|
324
|
+
payload: P,
|
|
325
|
+
config: EndpointConfiguration[E, P],
|
|
326
|
+
) -> Tuple[Response[E, P], Dict[str, bytes]]:
|
|
327
|
+
"""Submit a request to the MMS server and return the response.
|
|
328
|
+
|
|
329
|
+
Arguments:
|
|
330
|
+
envelope (Envelope): The payload envelope to submit to the MMS server.
|
|
331
|
+
payload (Payload): The data to submit to the MMS server.
|
|
332
|
+
config (EndpointConfiguration): The configuration for the endpoint.
|
|
333
|
+
|
|
334
|
+
Returns: The response from the MMS server.
|
|
335
|
+
"""
|
|
336
|
+
# First, create the MMS request from the payload and data.
|
|
337
|
+
self._logger.debug(
|
|
338
|
+
f"{config.name}: Starting request. Envelope: {type(envelope).__name__}, Data: {type(payload).__name__}",
|
|
339
|
+
)
|
|
340
|
+
request = self._to_mms_request(config.request_type, config.service.serializer.serialize(envelope, payload))
|
|
341
|
+
|
|
342
|
+
# Next, submit the request to the MMS server and get and verify the response.
|
|
343
|
+
resp = self._get_wrapper(config.service).submit(request)
|
|
344
|
+
self._verify_mms_response(resp, config)
|
|
345
|
+
|
|
346
|
+
# Now, extract the attachments from the response
|
|
347
|
+
attachments = {a.name: a.data for a in resp.attachments}
|
|
348
|
+
|
|
349
|
+
# Finally, deserialize and verify the response
|
|
350
|
+
envelope_type = config.response_envelope_type or type(envelope)
|
|
351
|
+
data_type = config.response_data_type or type(payload)
|
|
352
|
+
data: Response[E, P] = config.service.serializer.deserialize(resp.payload, envelope_type, data_type)
|
|
353
|
+
self._verify_response(data, config)
|
|
354
|
+
|
|
355
|
+
# Return the response data and any attachments
|
|
356
|
+
self._logger.debug(
|
|
357
|
+
f"{config.name}: Returning response. Envelope: {envelope_type.__name__}, Data: {data_type.__name__}",
|
|
358
|
+
)
|
|
359
|
+
return data, attachments
|
|
360
|
+
|
|
361
|
+
def request_many(
|
|
362
|
+
self,
|
|
363
|
+
envelope: E,
|
|
364
|
+
payload: P,
|
|
365
|
+
config: EndpointConfiguration[E, P],
|
|
366
|
+
) -> Tuple[MultiResponse[E, P], Dict[str, bytes]]:
|
|
367
|
+
"""Submit a request to the MMS server and return the multi-response.
|
|
368
|
+
|
|
369
|
+
Arguments:
|
|
370
|
+
envelope (Envelope): The payload envelope to submit to the MMS server.
|
|
371
|
+
payload (Payload): The data to submit to the MMS server.
|
|
372
|
+
config (EndpointConfiguration): The configuration for the endpoint.
|
|
373
|
+
|
|
374
|
+
Returns: The multi-response from the MMS server.
|
|
375
|
+
"""
|
|
376
|
+
# First, create the MMS request from the payload and data.
|
|
377
|
+
self._logger.debug(
|
|
378
|
+
(
|
|
379
|
+
f"{config.name}: Starting multi-request. Envelope: {type(envelope).__name__}, "
|
|
380
|
+
f"Data: {type(payload).__name__}"
|
|
381
|
+
),
|
|
382
|
+
)
|
|
383
|
+
request = self._to_mms_request(config.request_type, config.service.serializer.serialize(envelope, payload))
|
|
384
|
+
|
|
385
|
+
# Next, submit the request to the MMS server and get and verify the response.
|
|
386
|
+
resp = self._get_wrapper(config.service).submit(request)
|
|
387
|
+
self._verify_mms_response(resp, config)
|
|
388
|
+
|
|
389
|
+
# Now, extract the attachments from the response
|
|
390
|
+
attachments = {a.name: a.data for a in resp.attachments}
|
|
391
|
+
|
|
392
|
+
# Finally, deserialize and verify the response
|
|
393
|
+
envelope_type = config.response_envelope_type or type(envelope)
|
|
394
|
+
data_type = config.response_data_type or type(payload)
|
|
395
|
+
data: MultiResponse[E, P] = config.service.serializer.deserialize_multi(resp.payload, envelope_type, data_type)
|
|
396
|
+
self._verify_multi_response(data, config)
|
|
397
|
+
|
|
398
|
+
# Return the response data and any attachments
|
|
399
|
+
self._logger.debug(
|
|
400
|
+
f"{config.name}: Returning multi-response. Envelope: {envelope_type.__name__}, Data: {data_type.__name__}",
|
|
401
|
+
)
|
|
402
|
+
return data, attachments
|
|
403
|
+
|
|
404
|
+
def _to_mms_request(
|
|
405
|
+
self,
|
|
406
|
+
req_type: RequestType,
|
|
407
|
+
data: bytes,
|
|
408
|
+
return_req: bool = False,
|
|
409
|
+
attachments: Optional[Dict[str, bytes]] = None,
|
|
410
|
+
) -> MmsRequest:
|
|
411
|
+
"""Convert the given data to an MMS request.
|
|
412
|
+
|
|
413
|
+
Arguments:
|
|
414
|
+
req_type (RequestType): The type of request to submit to the MMS server.
|
|
415
|
+
data (bytes): The data to submit to the MMS server.
|
|
416
|
+
return_req (bool): Whether to return the request data in the response. This is False by default.
|
|
417
|
+
attachments (Dict[str, bytes]): The attachments to send with the request.
|
|
418
|
+
|
|
419
|
+
Arguments: The MMS request to submit to the MMS server.
|
|
420
|
+
"""
|
|
421
|
+
# Convert the attachments to the correct the MMS format
|
|
422
|
+
attachment_data = (
|
|
423
|
+
[
|
|
424
|
+
Attachment(signature=self._signer.sign(data), name=name, binaryData=data)
|
|
425
|
+
for name, data in attachments.items()
|
|
426
|
+
]
|
|
427
|
+
if attachments
|
|
428
|
+
else []
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
# Embed the data and the attachments in the MMS request and return it
|
|
432
|
+
self._logger.debug(
|
|
433
|
+
f"Creating MMS request of type {req_type.name} to send {len(data)} bytes of data and "
|
|
434
|
+
f"{len(attachment_data)} attachments."
|
|
435
|
+
)
|
|
436
|
+
return MmsRequest(
|
|
437
|
+
requestType=req_type,
|
|
438
|
+
adminRole=self._is_admin,
|
|
439
|
+
requestDataType=RequestDataType.XML,
|
|
440
|
+
sendRequestDataOnSuccess=return_req,
|
|
441
|
+
requestSignature=self._signer.sign(data),
|
|
442
|
+
requestData=data,
|
|
443
|
+
attachmentData=attachment_data,
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
def _verify_mms_response(self, resp: MmsResponse, config: EndpointConfiguration) -> None:
|
|
447
|
+
"""Verify that the given MMS response is valid.
|
|
448
|
+
|
|
449
|
+
Arguments:
|
|
450
|
+
resp (MmsResponse): The MMS response to verify.
|
|
451
|
+
|
|
452
|
+
Raises:
|
|
453
|
+
MMSClientError: If the response is not valid.
|
|
454
|
+
"""
|
|
455
|
+
# Verify that the response is in the correct format. If it's not, raise an error.
|
|
456
|
+
if resp.data_type != ResponseDataType.XML:
|
|
457
|
+
raise MMSClientError(
|
|
458
|
+
config.name,
|
|
459
|
+
f"Invalid MMS response data type: {resp.data_type.name}. Only XML is supported.",
|
|
460
|
+
)
|
|
461
|
+
if resp.compressed:
|
|
462
|
+
raise MMSClientError(config.name, "Invalid MMS response. Compressed responses are not supported.")
|
|
463
|
+
|
|
464
|
+
# Check the response status flags and log any warnings or errors
|
|
465
|
+
if resp.warnings:
|
|
466
|
+
self._logger.warning(f"{config.name}: MMS response contained warnings.")
|
|
467
|
+
if not resp.success:
|
|
468
|
+
self._logger.error(f"{config.name}: MMS response was unsuccessful.")
|
|
469
|
+
|
|
470
|
+
def _verify_response(self, resp: Response[E, P], config: EndpointConfiguration) -> None:
|
|
471
|
+
"""Verify that the given response is valid.
|
|
472
|
+
|
|
473
|
+
Arguments:
|
|
474
|
+
resp (Response): The response to verify.
|
|
475
|
+
|
|
476
|
+
Raises:
|
|
477
|
+
MMSValidationError: If the response is not valid.
|
|
478
|
+
"""
|
|
479
|
+
# First, verify the base response data to make sure we haven't missed some request details
|
|
480
|
+
valid = self._verify_base_response(resp, config)
|
|
481
|
+
|
|
482
|
+
# Next, if we received data back with the request then verify that it's valid
|
|
483
|
+
if resp.payload:
|
|
484
|
+
valid = valid and self._verify_response_common(config, type(resp.data), resp.payload.data_validation)
|
|
485
|
+
|
|
486
|
+
# Now, log any messages that were returned with the response
|
|
487
|
+
self._verify_messages(config, resp)
|
|
488
|
+
|
|
489
|
+
# Finally, if the response is not valid, raise an error
|
|
490
|
+
if not valid:
|
|
491
|
+
raise MMSValidationError(config.name, resp.envelope, resp.data, resp.messages)
|
|
492
|
+
|
|
493
|
+
def _verify_multi_response(self, resp: MultiResponse[E, P], config: EndpointConfiguration) -> None:
|
|
494
|
+
"""Verify that the given multi-response is valid.
|
|
495
|
+
|
|
496
|
+
Arguments:
|
|
497
|
+
resp (MultiResponse): The multi-response to verify.
|
|
498
|
+
|
|
499
|
+
Raises:
|
|
500
|
+
MMSValidationError: If the response is not valid.
|
|
501
|
+
"""
|
|
502
|
+
# First, verify the base response data to make sure we haven't missed some request details
|
|
503
|
+
valid = self._verify_base_response(resp, config)
|
|
504
|
+
|
|
505
|
+
# Next, if we received data back with the request then verify that it's valid
|
|
506
|
+
if resp.payload:
|
|
507
|
+
valid = valid and all(
|
|
508
|
+
self._verify_response_common(config, type(data), resp.payload[i].data_validation, i)
|
|
509
|
+
for i, data in enumerate(resp.data)
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
# Now, log any messages that were returned with the response
|
|
513
|
+
self._verify_messages(config, resp)
|
|
514
|
+
|
|
515
|
+
# Finally, if the response is not valid, raise an error
|
|
516
|
+
if not valid:
|
|
517
|
+
raise MMSValidationError(config.name, resp.envelope, resp.data, resp.messages)
|
|
518
|
+
|
|
519
|
+
def _verify_base_response(self, resp: BaseResponse[E], config: EndpointConfiguration) -> bool:
|
|
520
|
+
"""Verify that the given base response is valid.
|
|
521
|
+
|
|
522
|
+
Arguments:
|
|
523
|
+
resp (BaseResponse): The base response to verify.
|
|
524
|
+
config (EndpointConfiguration): The configuration for the endpoint.
|
|
525
|
+
|
|
526
|
+
Returns: True to indicate that the response is valid, False otherwise.
|
|
527
|
+
"""
|
|
528
|
+
# Log the request's processing statistics
|
|
529
|
+
self._logger.info(
|
|
530
|
+
f"{config.name} ({resp.statistics.timestamp_xml}): Recieved {resp.statistics.received}, "
|
|
531
|
+
f"Valid: {resp.statistics.valid}, Invalid: {resp.statistics.invalid}, "
|
|
532
|
+
f"Successful: {resp.statistics.successful}, Unsuccessful: {resp.statistics.unsuccessful} "
|
|
533
|
+
f"in {resp.statistics.time_ms}ms"
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
# Check if the response is invalid and if the envelope had any validation issues. If not, then we have a
|
|
537
|
+
# valid base response so return True. Otherwise, return False.
|
|
538
|
+
return resp.statistics.invalid == 0 and self._verify_response_common(
|
|
539
|
+
config, type(resp.envelope), resp.envelope_validation
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
def _verify_messages(self, config: EndpointConfiguration, resp: BaseResponse[E]) -> None:
|
|
543
|
+
"""Verify the messages in the given response.
|
|
544
|
+
|
|
545
|
+
Arguments:
|
|
546
|
+
config (EndpointConfiguration): The configuration for the endpoint.
|
|
547
|
+
resp (BaseResponse): The response to verify.
|
|
548
|
+
"""
|
|
549
|
+
for path, messages in resp.messages.items():
|
|
550
|
+
for info in messages.information:
|
|
551
|
+
self._logger.info(f"{config.name} - {path}: {info.code}")
|
|
552
|
+
for warning in messages.warnings:
|
|
553
|
+
self._logger.warning(f"{config.name} - {path}: {warning.code}")
|
|
554
|
+
for error in messages.errors:
|
|
555
|
+
self._logger.error(f"{config.name} - {path}: {error.code}")
|
|
556
|
+
|
|
557
|
+
def _verify_response_common(
|
|
558
|
+
self, config: EndpointConfiguration, payload_type: type, resp: ResponseCommon, index: Optional[int] = None
|
|
559
|
+
) -> bool:
|
|
560
|
+
"""Verify the common response data in the given response.
|
|
561
|
+
|
|
562
|
+
Arguments:
|
|
563
|
+
config (EndpointConfiguration): The configuration for the endpoint.
|
|
564
|
+
payload_type (type): The type of payload that was sent with the request.
|
|
565
|
+
resp (ResponseCommon): The common response data to verify.
|
|
566
|
+
index (int): The index of the response in the multi-response. This is None for single
|
|
567
|
+
responses.
|
|
568
|
+
|
|
569
|
+
Returns: True to indicate that the response is valid, False otherwise.
|
|
570
|
+
"""
|
|
571
|
+
# Log the status of the response validation
|
|
572
|
+
self._logger.info(
|
|
573
|
+
f"{config.name}: {payload_type.__name__}{f'[{index}]' if index is not None else ''} was valid? "
|
|
574
|
+
f"{resp.success} ({resp.validation.value})",
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
# Verify that the response was successful and that the validation status is not a failed status
|
|
578
|
+
return resp.success and (resp.validation not in (ValidationStatus.FAILED, ValidationStatus.PASSED_PARTIAL))
|
|
579
|
+
|
|
580
|
+
def _get_wrapper(self, service: ServiceConfiguration) -> ZWrapper:
|
|
581
|
+
"""Get the wrapper for the given service.
|
|
582
|
+
|
|
583
|
+
Arguments:
|
|
584
|
+
service (ServiceConfiguration): The service for which to get the wrapper.
|
|
585
|
+
"""
|
|
586
|
+
if service.interface not in self._wrappers:
|
|
587
|
+
self._logger.debug(f"Creating wrapper for {service.interface.name} interface.")
|
|
588
|
+
self._wrappers[service.interface] = ZWrapper(
|
|
589
|
+
self._client_type, service.interface, self._cert.to_adapter(), self._logger, True, self._test
|
|
590
|
+
)
|
|
591
|
+
return self._wrappers[service.interface]
|