mms-client 1.8.0__py3-none-any.whl → 1.9.0__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.
@@ -365,7 +365,10 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
365
365
  f"{config.name}: Starting request. Envelope: {type(envelope).__name__}, Data: {type(payload).__name__}",
366
366
  )
367
367
  request = self._to_mms_request(
368
- wrapper, config.request_type, config.service.serializer.serialize(envelope, payload, config.for_report)
368
+ wrapper,
369
+ config.name,
370
+ config.request_type,
371
+ config.service.serializer.serialize(envelope, payload, config.for_report),
369
372
  )
370
373
 
371
374
  # Next, submit the request to the MMS server and get and verify the response.
@@ -422,7 +425,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
422
425
  if is_list
423
426
  else config.service.serializer.serialize(envelope, payload, config.for_report) # type: ignore[type-var]
424
427
  )
425
- request = self._to_mms_request(wrapper, config.request_type, serialized)
428
+ request = self._to_mms_request(wrapper, config.name, config.request_type, serialized)
426
429
 
427
430
  # Next, submit the request to the MMS server and get and verify the response.
428
431
  resp = wrapper.submit(request)
@@ -452,6 +455,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
452
455
  def _to_mms_request(
453
456
  self,
454
457
  client: ZWrapper,
458
+ operation: str,
455
459
  req_type: RequestType,
456
460
  data: bytes,
457
461
  return_req: bool = False,
@@ -461,6 +465,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
461
465
 
462
466
  Arguments:
463
467
  client (ZWrapper): The Zeep client to use for submitting the request.
468
+ operation (str): The name of the MMS function being called.
464
469
  req_type (RequestType): The type of request to submit to the MMS server.
465
470
  data (bytes): The data to submit to the MMS server.
466
471
  return_req (bool): Whether to return the request data in the response. This is False by default.
@@ -470,11 +475,13 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
470
475
  """
471
476
  # First, convert the attachments to the correct the MMS format
472
477
  attachment_data = (
473
- [self._to_mms_attachment(client, name, data) for name, data in attachments.items()] if attachments else []
478
+ [self._to_mms_attachment(client, operation, name, data) for name, data in attachments.items()]
479
+ if attachments
480
+ else []
474
481
  )
475
482
 
476
483
  # Next, convert the payload to a base-64 string
477
- tag, signature = self._register_and_sign(client, "payload", data)
484
+ tag, signature = self._register_and_sign(client, operation, "payload", data)
478
485
 
479
486
  # Embed the data and the attachments in the MMS request and return it
480
487
  logger.debug(
@@ -491,24 +498,38 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
491
498
  attachmentData=attachment_data,
492
499
  )
493
500
 
494
- def _to_mms_attachment(self, client: ZWrapper, name: str, data: bytes) -> Attachment: # pragma: no cover
501
+ def _to_mms_attachment(
502
+ self, client: ZWrapper, operation: str, name: str, data: bytes
503
+ ) -> Attachment: # pragma: no cover
495
504
  """Convert the given data to an MMS attachment.
496
505
 
497
506
  Arguments:
498
507
  client (ZWrapper): The Zeep client to use for submitting the request.
508
+ operation (str): The name of the operation.
499
509
  name (str): The name of the attachment.
500
510
  data (bytes): The data to be attached.
501
511
 
502
512
  Returns: The MMS attachment.
503
513
  """
504
514
  # Convert the data to a base-64 string
505
- tag, signature = self._register_and_sign(client, name, data)
515
+ tag, signature = self._register_and_sign(client, operation, name, data)
506
516
 
507
517
  # Create the MMS attachment and return it
508
518
  return Attachment(signature=signature, name=name, binaryData=tag)
509
519
 
510
- def _register_and_sign(self, client: ZWrapper, name: str, data: bytes) -> Tuple[str, str]:
511
- tag = client.register_attachment(name, data)
520
+ def _register_and_sign(self, client: ZWrapper, operation: str, name: str, data: bytes) -> Tuple[str, str]:
521
+ """Register an attachment and sign the data.
522
+
523
+ Arguments:
524
+ client (ZWrapper): The Zeep client to use for submitting the request.
525
+ operation (str): The name of the operation.
526
+ name (str): The name of the attachment.
527
+ data (bytes): The data to be attached.
528
+
529
+ Returns: The content ID of the attachment, which should be used in place of the attachment data.
530
+ """
531
+ # First, register the attachment
532
+ tag = client.register_attachment(operation, name, data)
512
533
 
513
534
  # Next, sign the data
514
535
  signature = self._signer.sign(data)
@@ -17,7 +17,7 @@ class RequestType(Enum):
17
17
 
18
18
  INFO = "mp.info"
19
19
  MARKET = "mp.market"
20
- REGISTRATION = "mpr"
20
+ REGISTRATION = "mp.registration"
21
21
  REPORT = "mp.report"
22
22
  OMI = "mp.omi"
23
23
 
@@ -9,16 +9,21 @@ from email.mime.multipart import MIMEMultipart
9
9
  from logging import getLogger
10
10
  from random import SystemRandom
11
11
  from typing import Dict
12
+ from typing import List
12
13
  from typing import Optional
14
+ from typing import Tuple
13
15
 
14
16
  from lxml import etree
15
17
  from lxml.etree import _Element as Element
16
18
  from pendulum import now
19
+ from requests import Response
17
20
  from requests import Session
18
21
  from zeep.cache import VersionedCacheBase
19
22
  from zeep.transports import Transport
20
23
  from zeep.wsdl.utils import etree_to_string
21
24
 
25
+ from mms_client.utils.plugin import Plugin
26
+
22
27
  # Set the default logger for the MMS client
23
28
  logger = getLogger(__name__)
24
29
 
@@ -136,6 +141,7 @@ class MultipartTransport(Transport):
136
141
  timeout: int = 300,
137
142
  operation_timeout: Optional[int] = None,
138
143
  session: Optional[Session] = None,
144
+ plugins: Optional[List[Plugin]] = None,
139
145
  ):
140
146
  """Create a new MTOMS transport.
141
147
 
@@ -145,12 +151,17 @@ class MultipartTransport(Transport):
145
151
  timeout (int): The timeout for the transport.
146
152
  operation_timeout (int, optional): The operation timeout for the transport.
147
153
  session (Session, optional): The session to be used for the transport.
154
+ plugins (List[Plugin], optional): The plugins to be used for the transport.
148
155
  """
149
156
  # Save the domain for later use
150
157
  self._domain = domain
151
158
 
152
- # Setup a dictionary to store the attachments after they're registered
159
+ # Setup a dictionary to store the attachments and operations after they're registered
153
160
  self._attachments: Dict[str, Attachment] = {}
161
+ self._operations: Dict[str, str] = {}
162
+
163
+ # Save our list of plugins
164
+ self._plugins = plugins or []
154
165
 
155
166
  # Call the parent constructor
156
167
  super().__init__(
@@ -160,23 +171,25 @@ class MultipartTransport(Transport):
160
171
  session=session,
161
172
  )
162
173
 
163
- def register_attachment(self, name: str, data: bytes) -> str:
174
+ def register_attachment(self, operation: str, name: str, data: bytes) -> str:
164
175
  """Register an attachment.
165
176
 
166
177
  Registered attachments will be sent with the request as MTOMS attachments. The content ID of the attachment
167
178
  will be returned so that it can be used in the request.
168
179
 
169
180
  Arguments:
170
- name (str): The name of the attachment.
171
- data (bytes): The data to be attached.
181
+ operation (str): The name of the operation.
182
+ name (str): The name of the attachment.
183
+ data (bytes): The data to be attached.
172
184
 
173
185
  Returns: The content ID of the attachment, which should be used in place of the attachment data.
174
186
  """
175
187
  attachment = Attachment(name, data, self._domain)
176
188
  self._attachments[attachment.cid] = attachment
189
+ self._operations[attachment.cid] = operation
177
190
  return attachment.tag
178
191
 
179
- def post_xml(self, address: str, envelope: Element, headers: dict):
192
+ def post_xml(self, address: str, envelope: Element, headers: dict) -> Response:
180
193
  """Post the XML envelope and attachments.
181
194
 
182
195
  Arguments:
@@ -186,19 +199,30 @@ class MultipartTransport(Transport):
186
199
 
187
200
  Returns: The response from the server.
188
201
  """
189
- # Search for values that start with our FILETAG
202
+ # First, search for values that start with our FILETAG
190
203
  filetags = envelope.xpath(f"//*[starts-with(text(), '{FILETAG}')]")
191
204
 
192
- # if there are any attached files, we will set the attachments. Otherwise, just the envelope
205
+ # Next, if there are any attached files, we will set the attachments. Otherwise, just the envelope
193
206
  if filetags:
194
- message = self.create_mtom_request(filetags, envelope, headers).encode("UTF-8")
207
+ message, operation = self.create_mtom_request(filetags, envelope, headers)
195
208
  else:
196
- message = etree_to_string(envelope)
209
+ message, operation = etree_to_string(envelope), address
210
+
211
+ # Iterate over all the plugins and call their egress methods
212
+ for plugin in self._plugins:
213
+ message, headers = plugin.egress(message, headers, operation)
214
+
215
+ # Now, post the request and get the response
216
+ resp = self.post(address, message, headers)
217
+
218
+ # Iterate over all the plugins and call their ingress methods
219
+ for plugin in self._plugins:
220
+ resp._content, resp.headers = plugin.ingress(resp.content, resp.headers, operation)
197
221
 
198
- # Post the request and return the response
199
- return self.post(address, message, headers)
222
+ # Finally, return the response
223
+ return resp
200
224
 
201
- def create_mtom_request(self, filetags, envelope: Element, headers: dict) -> str:
225
+ def create_mtom_request(self, filetags, envelope: Element, headers: dict) -> Tuple[bytes, str]:
202
226
  """Set MTOM attachments and return the right envelope.
203
227
 
204
228
  Arguments:
@@ -221,6 +245,7 @@ class MultipartTransport(Transport):
221
245
 
222
246
  # Attach each file to the multipart request
223
247
  for cid in files:
248
+ operation = self._operations.pop(cid)
224
249
  mtom_part.attach(self.create_attachment(cid))
225
250
 
226
251
  # Finally, create the final multipart request string
@@ -235,7 +260,7 @@ class MultipartTransport(Transport):
235
260
  # Decode the XML and return the request
236
261
  message = mtom_part.as_string().split("\n\n", 1)[1]
237
262
  message = message.replace("\n", "\r\n", 5)
238
- return message
263
+ return message.encode("UTF-8"), operation
239
264
 
240
265
  def create_attachment(self, cid):
241
266
  """Create an attachment for the multipart request.
@@ -246,7 +271,7 @@ class MultipartTransport(Transport):
246
271
  Returns: The attachment.
247
272
  """
248
273
  # First, get the attachment from the cache
249
- attach = self._attachments[cid]
274
+ attach = self._attachments.pop(cid)
250
275
 
251
276
  # Next, create the attachment
252
277
  part = MIMEBase("application", "octet-stream")
@@ -3,44 +3,44 @@
3
3
  from abc import ABC
4
4
  from abc import abstractmethod
5
5
  from logging import getLogger
6
-
7
- from lxml.etree import _Element as Element
8
- from lxml.etree import tostring
9
- from zeep import Plugin
10
- from zeep.wsdl.definitions import Operation
6
+ from typing import Tuple
11
7
 
12
8
  # Set the default logger for the MMS client
13
9
  logger = getLogger(__name__)
14
10
 
15
11
 
16
- class AuditPlugin(ABC, Plugin):
12
+ class Plugin(ABC):
17
13
  """Base class for audit plugins."""
18
14
 
19
- def egress(self, envelope: Element, http_headers: dict, operation: Operation, binding_options):
15
+ def egress(self, message: bytes, http_headers: dict, operation: str) -> Tuple[bytes, dict]:
20
16
  """Handle the MMS request before it is sent.
21
17
 
22
- Arguments are the same as in the egress method of the Plugin class.
18
+ Arguments:
19
+ message (bytes): The message to send.
20
+ http_headers (dict): The HTTP headers to send.
21
+ operation (str): The operation being called.
23
22
 
24
23
  Returns:
25
24
  lxml.etree.Element: The XML message to send.
26
25
  dict: The HTTP headers to send.
27
26
  """
28
- data = tostring(envelope, encoding="UTF-8", xml_declaration=True)
29
- self.audit_request(operation.name, data)
30
- return envelope, http_headers
27
+ self.audit_request(operation, message)
28
+ return message, http_headers
31
29
 
32
- def ingress(self, envelope: Element, http_headers: dict, operation: Operation):
30
+ def ingress(self, message: bytes, http_headers: dict, operation: str) -> Tuple[bytes, dict]:
33
31
  """Handle the MMS response before it is processed.
34
32
 
35
- Arguments are the same as in the ingress method of the Plugin class.
33
+ Arguments:
34
+ message (bytes): The message to process.
35
+ http_headers (dict): The HTTP headers to process.
36
+ operation (str): The operation being called.
36
37
 
37
38
  Returns:
38
39
  lxml.etree.Element: The XML message to process.
39
40
  dict: The HTTP headers to process.
40
41
  """
41
- data = tostring(envelope, encoding="UTF-8", xml_declaration=True)
42
- self.audit_response(operation.name, data)
43
- return envelope, http_headers
42
+ self.audit_response(operation, message)
43
+ return message, http_headers
44
44
 
45
45
  @abstractmethod
46
46
  def audit_request(self, operation: str, mms_request: bytes) -> None:
mms_client/utils/web.py CHANGED
@@ -11,7 +11,6 @@ from backoff import on_exception
11
11
  from requests import Session
12
12
  from requests_pkcs12 import Pkcs12Adapter
13
13
  from zeep import Client
14
- from zeep import Plugin
15
14
  from zeep.cache import SqliteCache
16
15
  from zeep.exceptions import TransportError
17
16
  from zeep.xsd.valueobjects import CompoundValue
@@ -19,6 +18,7 @@ from zeep.xsd.valueobjects import CompoundValue
19
18
  from mms_client.types.transport import MmsRequest
20
19
  from mms_client.types.transport import MmsResponse
21
20
  from mms_client.utils.multipart_transport import MultipartTransport
21
+ from mms_client.utils.plugin import Plugin
22
22
 
23
23
  # Set the default logger for the MMS client
24
24
  logger = getLogger(__name__)
@@ -193,24 +193,26 @@ class ZWrapper:
193
193
 
194
194
  # Finally, we create the Zeep client with the given WSDL file location, session, and cache settings and then,
195
195
  # from that client, we create the SOAP service with the given service binding and selected endpoint.
196
- self._transport = MultipartTransport(domain, cache=SqliteCache() if cache else None, session=sess)
196
+ self._transport = MultipartTransport(
197
+ domain, cache=SqliteCache() if cache else None, session=sess, plugins=plugins
198
+ )
197
199
  self._client = Client(
198
200
  wsdl=str(location.resolve()),
199
201
  transport=self._transport,
200
- plugins=plugins,
201
202
  )
202
203
  self._create_service()
203
204
 
204
- def register_attachment(self, name: str, attachment: bytes) -> str:
205
+ def register_attachment(self, operation: str, name: str, attachment: bytes) -> str:
205
206
  """Register a multipart attachment.
206
207
 
207
208
  Arguments:
209
+ operation (str): The name of the operation.
208
210
  name (str): The name of the attachment.
209
211
  attachment (bytes): The data to be attached.
210
212
 
211
213
  Returns: The content ID of the attachment, which should be used in place of the attachment data.
212
214
  """
213
- return self._transport.register_attachment(name, attachment)
215
+ return self._transport.register_attachment(operation, name, attachment)
214
216
 
215
217
  @on_exception(expo, TransportError, max_tries=3, giveup=fatal_code, logger=logger) # type: ignore[arg-type]
216
218
  def submit(self, req: MmsRequest) -> MmsResponse:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mms-client
3
- Version: 1.8.0
3
+ Version: 1.9.0
4
4
  Summary: API client for accessing the MMS
5
5
  Home-page: https://github.com/ElectroRoute-Japan/mms-client
6
6
  Author: Ryan Wood
@@ -192,10 +192,10 @@ client = MmsClient(domain="mydomain.com", participant="F100", user="FAKEUSER", c
192
192
  ```
193
193
 
194
194
  ## Auditing XML Requests & Responses
195
- A common requirement for this sort of library is recording or saving the raw XML requests and responses for audit/logging purposes. This library supports this workflow through the `mms_client.utils.auditing.AuditPlugin` object. This object intercepts the XML request at the Zeep client level right before it is sent to the MMS and, similarly, intercepts the XML response immediately after it is received from the MMS. Before passing these objects on, without modifying them, it records the XML data as a byte string and passes it to two methods: `audit_request` and `audit_response`. These can be overridden by any object that inherits from this class, allowing the user to direct this data to whatever store they prefer to use for auditing or logging.
195
+ A common requirement for this sort of library is recording or saving the raw XML requests and responses for audit/logging purposes. This library supports this workflow through the `mms_client.utils.plugin.Plugin` object. This object intercepts the XML request at the Zeep client level right before it is sent to the MMS and, similarly, intercepts the XML response immediately after it is received from the MMS. Before passing these objects on, without modifying them, it records the XML data as a byte string and passes it to two methods: `audit_request` and `audit_response`. These can be overridden by any object that inherits from this class, allowing the user to direct this data to whatever store they prefer to use for auditing or logging.
196
196
 
197
197
  ```python
198
- class TestAuditPlugin(AuditPlugin):
198
+ class TestAuditPlugin(Plugin):
199
199
 
200
200
  def __init__(self):
201
201
  self.request = None
@@ -12,7 +12,7 @@ mms_client/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
12
12
  mms_client/security/certs.py,sha256=Gy-CuSsdLPFeoPH_sEYhY67dI5sy6yJ8iTwlysRKT1s,3018
13
13
  mms_client/security/crypto.py,sha256=u9Z6nkAW6LbBqUzjIEbZ-CcqdkMJ9fqvdX7IXTTh1EI,2345
14
14
  mms_client/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- mms_client/services/base.py,sha256=4G8Ie5B56JGxDPL-T5t1ytC0c0kpSkQq3PJC5O-hEco,29604
15
+ mms_client/services/base.py,sha256=4r_hsmSDmoutu9A-fVayBKqoe6FOAkm10qzNs47cFUI,30421
16
16
  mms_client/services/market.py,sha256=DUtnzWRLmTrTgZ4jo93B0S5Pq9K7dEtAGWYWbTbe5rA,9524
17
17
  mms_client/services/omi.py,sha256=UG1zYkFz0sFsEbhE6P0CLoAOZZOyEshkZ_b7D_e3CjQ,626
18
18
  mms_client/services/registration.py,sha256=tPJo-cBYURH5KkpxMQqWvZW6IqelzoBm5x6jzkJy-C0,3822
@@ -28,14 +28,14 @@ mms_client/types/registration.py,sha256=Nir73S3ffpk0O_fnTD2alFaqV1k67_8dcyyduXvP
28
28
  mms_client/types/report.py,sha256=ogfzIMvQeGBR_21JVHqQo1fqBZY7ExuRlpj6nNcKRcU,23044
29
29
  mms_client/types/reserve.py,sha256=pLV47w_749EIVhj0tUuJdWdHBBEl0-v10oVioccgxnU,2667
30
30
  mms_client/types/resource.py,sha256=TNQM51SLxnkjSlyJ2Sh-8Ph1-rNgkz3JKrAgI6qSHEg,65284
31
- mms_client/types/transport.py,sha256=PZ7mDKeH8rGOVONk0ZH5herft71PFF-MUpp3uB57WXo,4395
31
+ mms_client/types/transport.py,sha256=-hRmhv1VdMiby6zQJjqNRO7TkatRU-ZEqoCdtQMpgdg,4407
32
32
  mms_client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
- mms_client/utils/auditing.py,sha256=JDcvNo4ul66xPtDeeocn568yIe8fhh-jM11MWP-Kfes,2057
34
33
  mms_client/utils/errors.py,sha256=6k-NOjGZyTbTUISzN7B4JrmU2P8cwjpFFmFC7kJOQFQ,3005
35
- mms_client/utils/multipart_transport.py,sha256=O374vPh2j29_CSjkWnIaPJfiabRSGNpQvNQcUODdkDM,8871
34
+ mms_client/utils/multipart_transport.py,sha256=GJvjdlmXituiT78f5XuhpPkcHdDHFtVELa-F3eqUvbk,9981
35
+ mms_client/utils/plugin.py,sha256=_Jymcny5ta9uV4CMLGDX7O5xSQIhuu76rb-A6uhtFSY,2013
36
36
  mms_client/utils/serialization.py,sha256=weXZQOqAiQ4ga-vAVz8PQ1JR_iX2iN0lyMimqqC3mio,33783
37
- mms_client/utils/web.py,sha256=7c6Ghs3Y52cm2ge-9svR39uQjr2Pm2LhX9Wz-S1APa4,10816
38
- mms_client-1.8.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
39
- mms_client-1.8.0.dist-info/METADATA,sha256=eyLCuzX2tua2TvhouQRP5CBRPs45bLEJboNf22rhkE0,16590
40
- mms_client-1.8.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
41
- mms_client-1.8.0.dist-info/RECORD,,
37
+ mms_client/utils/web.py,sha256=Qk8azZpxAIEtI9suOikxBNtFQFNuWh-92DaUBU1qX8s,10927
38
+ mms_client-1.9.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
39
+ mms_client-1.9.0.dist-info/METADATA,sha256=sMf-OwVTryynN6TL4f_XykJj-GDpV1u0zPqMp_QKQkE,16578
40
+ mms_client-1.9.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
41
+ mms_client-1.9.0.dist-info/RECORD,,