mms-client 1.3.1__py3-none-any.whl → 1.4.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.
@@ -33,6 +33,7 @@ from mms_client.utils.errors import MMSValidationError
33
33
  from mms_client.utils.serialization import Serializer
34
34
  from mms_client.utils.web import ClientType
35
35
  from mms_client.utils.web import Interface
36
+ from mms_client.utils.web import Plugin
36
37
  from mms_client.utils.web import ZWrapper
37
38
 
38
39
  # Set the default logger for the MMS client
@@ -58,7 +59,7 @@ class EndpointConfiguration(Generic[E, P]):
58
59
  name: str
59
60
 
60
61
  # The allowed client types for the endpoint
61
- allowed_client: Optional[ClientType]
62
+ allowed_clients: Optional[List[ClientType]]
62
63
 
63
64
  # The service for the endpoint
64
65
  service: ServiceConfiguration
@@ -138,7 +139,7 @@ def mms_endpoint(
138
139
  name: str,
139
140
  service: ServiceConfiguration,
140
141
  request_type: RequestType,
141
- allowed_client: Optional[ClientType] = None,
142
+ allowed_clients: Optional[List[ClientType]] = None,
142
143
  resp_envelope_type: Optional[Type[E]] = None,
143
144
  resp_data_type: Optional[Type[P]] = None,
144
145
  ):
@@ -149,18 +150,18 @@ def mms_endpoint(
149
150
  Decorated functions will only be responsible for creating the payload envelope to submit to the MMS server.
150
151
 
151
152
  Arguments:
152
- name (str): The name of the endpoint.
153
- service (ServiceConfiguration): The configuration for the service.
154
- request_type (RequestType): The type of request to submit to the MMS server.
155
- allowed_client (ClientType): The type of client that is allowed to access the endpoint. If this is not provided,
156
- then any client type is allowed.
157
- resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
158
- the response envelope will be assumed to have the same type as the request envelope.
159
- resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
160
- response data will be assumed to have the same type as the request data.
153
+ name (str): The name of the endpoint.
154
+ service (ServiceConfiguration): The configuration for the service.
155
+ request_type (RequestType): The type of request to submit to the MMS server.
156
+ allowed_clients (List[ClientType]): The types of clients that are allowed to access the endpoint. If this is not
157
+ provided, then any client will be allowed.
158
+ resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
159
+ response envelope will be assumed to have the same type as the request envelope.
160
+ resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
161
+ response data will be assumed to have the same type as the request data.
161
162
  """
162
163
  # First, create the endpoint configuration from the given parameters
163
- config = EndpointConfiguration(name, allowed_client, service, request_type, resp_envelope_type, resp_data_type)
164
+ config = EndpointConfiguration(name, allowed_clients, service, request_type, resp_envelope_type, resp_data_type)
164
165
 
165
166
  # Next, create a decorator that will add the endpoint configuration to the function
166
167
  def decorator(func):
@@ -188,7 +189,7 @@ def mms_multi_endpoint(
188
189
  name: str,
189
190
  service: ServiceConfiguration,
190
191
  request_type: RequestType,
191
- allowed_client: Optional[ClientType] = None,
192
+ allowed_clients: Optional[List[ClientType]] = None,
192
193
  resp_envelope_type: Optional[Type[E]] = None,
193
194
  resp_data_type: Optional[Type[P]] = None,
194
195
  ):
@@ -200,21 +201,22 @@ def mms_multi_endpoint(
200
201
  the MMS server.
201
202
 
202
203
  Arguments:
203
- name (str): The name of the endpoint.
204
- service (ServiceConfiguration): The configuration for the service.
205
- request_type (RequestType): The type of request to submit to the MMS server.
206
- allowed_client (ClientType): The type of client that is allowed to access the endpoint. If this is not provided,
207
- then any client type is allowed.
208
- resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
209
- the response envelope will be assumed to have the same type as the request envelope.
210
- resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
211
- response data will be assumed to have the same type as the request data. Note, that
212
- this is not intended to account for the expected sequence type of the response data.
213
- That is already handled in the wrapped function, so this should only be set if the
214
- inner data type being returned differs from what was sent.
204
+ name (str): The name of the endpoint.
205
+ service (ServiceConfiguration): The configuration for the service.
206
+ request_type (RequestType): The type of request to submit to the MMS server.
207
+ allowed_clients (List[ClientType]): The types of clients that are allowed to access the endpoint. If this is not
208
+ provided, then any client will be allowed.
209
+ resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then
210
+ the response envelope will be assumed to have the same type as the request
211
+ envelope.
212
+ resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
213
+ response data will be assumed to have the same type as the request data. Note,
214
+ that this is not intended to account for the expected sequence type of the
215
+ response data. That is already handled in the wrapped function, so this should
216
+ only be set if the inner data type being returned differs from what was sent.
215
217
  """
216
218
  # First, create the endpoint configuration from the given parameters
217
- config = EndpointConfiguration(name, allowed_client, service, request_type, resp_envelope_type, resp_data_type)
219
+ config = EndpointConfiguration(name, allowed_clients, service, request_type, resp_envelope_type, resp_data_type)
218
220
 
219
221
  # Next, create a decorator that will add the endpoint configuration to the function
220
222
  def decorator(func):
@@ -253,6 +255,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
253
255
  client_type: ClientType,
254
256
  cert: Certificate,
255
257
  logger: Optional[Logger] = None,
258
+ plugins: Optional[List[Plugin]] = None,
256
259
  is_admin: bool = False,
257
260
  test: bool = False,
258
261
  ):
@@ -265,6 +268,8 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
265
268
  cert (Certificate): The certificate to use for signing requests.
266
269
  logger (Logger): The logger to use for instrumentation. If this is not provided, then the default
267
270
  logger will be used.
271
+ plugins (List[Plugin]): A list of plugins to add to the Zeep client. This can be useful for auditing or
272
+ other purposes.
268
273
  is_admin (bool): Whether the user is an admin (i.e. is a market operator).
269
274
  test (bool): Whether to use the test server.
270
275
  """
@@ -281,6 +286,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
281
286
 
282
287
  # Now, set our logger to either the provided logger or the default logger
283
288
  self._logger = logger or default_logger
289
+ self._plugins = plugins or []
284
290
 
285
291
  # Finally, create a list of wrappers for the different interfaces
286
292
  self._wrappers: Dict[Interface, ZWrapper] = {}
@@ -313,11 +319,11 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
313
319
  ValueError: If the client type is not allowed.
314
320
  """
315
321
  self._logger.debug(
316
- f"{config.name}: Verifying audience. Allowed client: "
317
- f"{config.allowed_client.name if config.allowed_client else 'Any'}."
322
+ f"{config.name}: Verifying audience. Allowed clients: "
323
+ f"{config.allowed_clients if config.allowed_clients else 'Any'}."
318
324
  )
319
- if config.allowed_client and self._client_type != config.allowed_client:
320
- raise AudienceError(config.name, config.allowed_client, self._client_type)
325
+ if config.allowed_clients and (self._client_type not in config.allowed_clients):
326
+ raise AudienceError(config.name, config.allowed_clients, self._client_type)
321
327
 
322
328
  def request_one(
323
329
  self,
@@ -598,6 +604,12 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
598
604
  if service.interface not in self._wrappers:
599
605
  self._logger.debug(f"Creating wrapper for {service.interface.name} interface.")
600
606
  self._wrappers[service.interface] = ZWrapper(
601
- self._client_type, service.interface, self._cert.to_adapter(), self._logger, True, self._test
607
+ self._client_type,
608
+ service.interface,
609
+ self._cert.to_adapter(),
610
+ self._logger,
611
+ self._plugins,
612
+ True,
613
+ self._test,
602
614
  )
603
615
  return self._wrappers[service.interface]
@@ -30,7 +30,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
30
30
  # The configuration for the market service
31
31
  config = ServiceConfiguration(Interface.MI, Serializer(SchemaType.MARKET, "MarketData"))
32
32
 
33
- @mms_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, ClientType.BSP)
33
+ @mms_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, [ClientType.BSP])
34
34
  def put_offer(
35
35
  self: ClientProto, request: OfferData, market_type: MarketType, days: int, date: Optional[Date] = None
36
36
  ) -> OfferData:
@@ -56,7 +56,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
56
56
  days=days,
57
57
  )
58
58
 
59
- @mms_multi_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, ClientType.BSP)
59
+ @mms_multi_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, [ClientType.BSP])
60
60
  def put_offers(
61
61
  self: ClientProto, requests: List[OfferData], market_type: MarketType, days: int, date: Optional[Date] = None
62
62
  ) -> List[OfferData]:
@@ -106,7 +106,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
106
106
  days=days,
107
107
  )
108
108
 
109
- @mms_endpoint("MarketCancel_OfferCancel", config, RequestType.INFO, ClientType.BSP)
109
+ @mms_endpoint("MarketCancel_OfferCancel", config, RequestType.INFO, [ClientType.BSP])
110
110
  def cancel_offer(
111
111
  self: ClientProto, request: OfferCancel, market_type: MarketType, days: int, date: Optional[Date] = None
112
112
  ) -> OfferCancel:
@@ -27,7 +27,7 @@ class RegistrationClientMixin: # pylint: disable=unused-argument
27
27
  # The configuration for the registration service
28
28
  config = ServiceConfiguration(Interface.MI, Serializer(SchemaType.REGISTRATION, "RegistrationData"))
29
29
 
30
- @mms_endpoint("RegistrationSubmit_Resource", config, RequestType.REGISTRATION, ClientType.BSP)
30
+ @mms_endpoint("RegistrationSubmit_Resource", config, RequestType.REGISTRATION, [ClientType.BSP])
31
31
  def put_resource(self: ClientProto, request: ResourceData) -> ResourceData:
32
32
  """Submit a new resource to the MMS server.
33
33
 
@@ -50,6 +50,7 @@ class RegistrationClientMixin: # pylint: disable=unused-argument
50
50
  "RegistrationQuery_Resource",
51
51
  config,
52
52
  RequestType.REGISTRATION,
53
+ allowed_clients=[ClientType.BSP, ClientType.TSO],
53
54
  resp_envelope_type=RegistrationSubmit,
54
55
  resp_data_type=ResourceData,
55
56
  )
@@ -0,0 +1,54 @@
1
+ """Contains functions for auditability plugins."""
2
+
3
+ from abc import ABC
4
+ from abc import abstractmethod
5
+
6
+ from lxml.etree import _Element as Element
7
+ from lxml.etree import tostring
8
+ from zeep import Plugin
9
+
10
+
11
+ class AuditPlugin(ABC, Plugin):
12
+ """Base class for audit plugins."""
13
+
14
+ def egress(self, envelope: Element, http_headers: dict, operation, binding_options):
15
+ """Handle the MMS request before it is sent.
16
+
17
+ Arguments are the same as in the egress method of the Plugin class.
18
+
19
+ Returns:
20
+ lxml.etree.Element: The XML message to send.
21
+ dict: The HTTP headers to send.
22
+ """
23
+ data = tostring(envelope, encoding="UTF-8", xml_declaration=True)
24
+ self.audit_request(data)
25
+ return envelope, http_headers
26
+
27
+ def ingress(self, envelope: Element, http_headers: dict, operation):
28
+ """Handle the MMS response before it is processed.
29
+
30
+ Arguments are the same as in the ingress method of the Plugin class.
31
+
32
+ Returns:
33
+ lxml.etree.Element: The XML message to process.
34
+ dict: The HTTP headers to process.
35
+ """
36
+ data = tostring(envelope, encoding="UTF-8", xml_declaration=True)
37
+ self.audit_response(data)
38
+ return envelope, http_headers
39
+
40
+ @abstractmethod
41
+ def audit_request(self, mms_request: bytes) -> None:
42
+ """Audit an MMS request.
43
+
44
+ Arguments:
45
+ mms_request (bytes): The MMS request XML to audit.
46
+ """
47
+
48
+ @abstractmethod
49
+ def audit_response(self, mms_response: bytes) -> None:
50
+ """Audit an MMS response.
51
+
52
+ Arguments:
53
+ mms_response (bytes): The MMS response XML to audit.
54
+ """
@@ -14,7 +14,7 @@ from mms_client.utils.web import ClientType
14
14
  class AudienceError(ValueError):
15
15
  """Error raised when an invalid audience is provided."""
16
16
 
17
- def __init__(self, method: str, allowed: ClientType, audience: ClientType):
17
+ def __init__(self, method: str, allowed: List[ClientType], audience: ClientType):
18
18
  """Initialize the error.
19
19
 
20
20
  Arguments:
@@ -22,8 +22,13 @@ class AudienceError(ValueError):
22
22
  allowed (str): The allowed audience.
23
23
  audience (str): The invalid audience.
24
24
  """
25
+ inner = (
26
+ f"'{allowed[0].name}' is"
27
+ if len(allowed) == 1
28
+ else f"""{" or ".join([f"'{a.name}'" for a in allowed])} are"""
29
+ )
25
30
  self.method = method
26
- self.message = f"{method}: Invalid client type, '{audience.name}' provided. Only '{allowed.name}' is supported."
31
+ self.message = f"{method}: Invalid client type, '{audience.name}' provided. Only {inner} supported."
27
32
  self.allowed = allowed
28
33
  self.audience = audience
29
34
  super().__init__(self.message)
mms_client/utils/web.py CHANGED
@@ -1,14 +1,17 @@
1
1
  """Contains the HTTP/web layer for communicating with the MMS server."""
2
2
 
3
- from enum import Enum
3
+ from enum import StrEnum
4
4
  from logging import Logger
5
5
  from pathlib import Path
6
+ from typing import List
7
+ from typing import Optional
6
8
 
7
9
  from backoff import expo
8
10
  from backoff import on_exception
9
11
  from requests import Session
10
12
  from requests_pkcs12 import Pkcs12Adapter
11
13
  from zeep import Client
14
+ from zeep import Plugin
12
15
  from zeep import Transport
13
16
  from zeep.cache import SqliteCache
14
17
  from zeep.exceptions import TransportError
@@ -18,17 +21,19 @@ from mms_client.types.transport import MmsRequest
18
21
  from mms_client.types.transport import MmsResponse
19
22
 
20
23
 
21
- class ClientType(Enum):
24
+ class ClientType(StrEnum):
22
25
  """Identifies the type of client to use.
23
26
 
24
- The client can be either "bsp" (Balancing Service Provider) or "tso" (Transmission System Operator).
27
+ The client can be either "bsp" (Balancing Service Provider), "mo" (Market Operator), or "tso" (Transmission System
28
+ Operator).
25
29
  """
26
30
 
27
31
  BSP = "bsp"
32
+ MO = "mo"
28
33
  TSO = "tso"
29
34
 
30
35
 
31
- class Interface(Enum):
36
+ class Interface(StrEnum):
32
37
  """Identifies the type of interface to use.
33
38
 
34
39
  The interface can be either "omi" (Other Market Initiator) or "mi" (Market Initiator).
@@ -73,32 +78,30 @@ class ServiceEndpoint:
73
78
  self._selected = self.main
74
79
 
75
80
 
76
- # Defines the service endpoints for the BSP and TSO clients for the OMI and MI web services, respectively.
77
- URLS = {
78
- ClientType.BSP: {
79
- Interface.OMI: ServiceEndpoint(
80
- main="https://www5.tdgc.jp/axis2/services/OmiWebService",
81
- backup="https://www6.tdgc.jp/axis2/services/OmiWebService",
82
- test="https://www7.tdgc.jp/axis2/services/OmiWebService",
83
- ),
84
- Interface.MI: ServiceEndpoint(
85
- main="https://www2.tdgc.jp/axis2/services/MiWebService",
86
- backup="https://www3.tdgc.jp/axis2/services/MiWebService",
87
- test="https://www4.tdgc.jp/axis2/services/MiWebService",
88
- ),
89
- },
90
- ClientType.TSO: {
91
- Interface.OMI: ServiceEndpoint(
92
- main="https://maiwlba103v07.tdgc.jp/axis2/services/OmiWebService",
93
- backup="https://mbiwlba103v07.tdgc.jp/axis2/services/OmiWebService",
94
- test="https://mbiwlba103v08.tdgc.jp/axis2/services/OmiWebService",
95
- ),
96
- Interface.MI: ServiceEndpoint(
97
- main="https://maiwlba103v03.tdgc.jp/axis2/services/MiWebService",
98
- backup="https://mbiwlba103v03.tdgc.jp/axis2/services/MiWebService",
99
- test="https://mbiwlba103v06.tdgc.jp/axis2/services/MiWebService",
100
- ),
101
- },
81
+ # Defines the service endpoints for the BSP, MO and TSO clients for the OMI and MI web services, respectively.
82
+ BSP_MO_URLS = {
83
+ Interface.OMI: ServiceEndpoint(
84
+ main="https://www5.tdgc.jp/axis2/services/OmiWebService",
85
+ backup="https://www6.tdgc.jp/axis2/services/OmiWebService",
86
+ test="https://www7.tdgc.jp/axis2/services/OmiWebService",
87
+ ),
88
+ Interface.MI: ServiceEndpoint(
89
+ main="https://www2.tdgc.jp/axis2/services/MiWebService",
90
+ backup="https://www3.tdgc.jp/axis2/services/MiWebService",
91
+ test="https://www4.tdgc.jp/axis2/services/MiWebService",
92
+ ),
93
+ }
94
+ TSO_URLS = {
95
+ Interface.OMI: ServiceEndpoint(
96
+ main="https://maiwlba103v07.tdgc.jp/axis2/services/OmiWebService",
97
+ backup="https://mbiwlba103v07.tdgc.jp/axis2/services/OmiWebService",
98
+ test="https://mbiwlba103v08.tdgc.jp/axis2/services/OmiWebService",
99
+ ),
100
+ Interface.MI: ServiceEndpoint(
101
+ main="https://maiwlba103v03.tdgc.jp/axis2/services/MiWebService",
102
+ backup="https://mbiwlba103v03.tdgc.jp/axis2/services/MiWebService",
103
+ test="https://mbiwlba103v06.tdgc.jp/axis2/services/MiWebService",
104
+ ),
102
105
  }
103
106
 
104
107
 
@@ -132,6 +135,7 @@ class ZWrapper:
132
135
  interface: Interface,
133
136
  adapter: Pkcs12Adapter,
134
137
  logger: Logger,
138
+ plugins: Optional[List[Plugin]] = None,
135
139
  cache: bool = True,
136
140
  test: bool = False,
137
141
  ):
@@ -147,13 +151,19 @@ class ZWrapper:
147
151
  adapter (Pkcs12Adapter): The PKCS12 adapter containing the certificate and private key to use for
148
152
  authenticating with the MMS server.
149
153
  logger (Logger): The logger to use for instrumentation.
154
+ plugins (List[Plugin]): A list of Zeep plugins to use with the client. This is useful for adding additional
155
+ functionality to the client, such as auditing or logging.
150
156
  cache (bool): If True, use a cache for the Zeep client. This is useful for avoiding repeated
151
157
  lookups of the WSDL file, which should result in lower latency.
152
158
  test (bool): If True, use the test service endpoint. This is useful for testing the client.
153
159
  """
154
160
  # First, we'll check that the client is valid. If it's not, we'll raise a ValueError.
155
- if client not in URLS:
156
- raise ValueError(f"Invalid client, '{client}'. Only 'bsp' and 'tso' are supported.")
161
+ if client in [ClientType.BSP, ClientType.MO]:
162
+ urls = BSP_MO_URLS
163
+ elif client == ClientType.TSO:
164
+ urls = TSO_URLS
165
+ else:
166
+ raise ValueError(f"Invalid client, '{client}'. Only 'bsp', 'mo', and 'tso' are supported.")
157
167
 
158
168
  # We need to determine the service port and location of the WSDL file based on the given interface. If the
159
169
  # interface is neither "omi" nor "mi", we raise a ValueError.
@@ -166,7 +176,7 @@ class ZWrapper:
166
176
  raise ValueError(f"Invalid interface, '{self._interface}'. Only 'mi' and 'omi' are supported.")
167
177
 
168
178
  # Next, we need to select the correct service endpoint based on the given client and interface.
169
- self._endpoint = URLS[client][self._interface]
179
+ self._endpoint = urls[self._interface]
170
180
  self._endpoint.select(test=test)
171
181
 
172
182
  # Now, we need to create a new session and mount the PKCS12 adapter to it. This is necessary for
@@ -184,6 +194,7 @@ class ZWrapper:
184
194
  self._client = Client(
185
195
  wsdl=str(location.resolve()),
186
196
  transport=Transport(cache=SqliteCache() if cache else None, session=sess),
197
+ plugins=plugins,
187
198
  )
188
199
  self._create_service()
189
200
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mms-client
3
- Version: 1.3.1
3
+ Version: 1.4.1
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
@@ -33,7 +33,7 @@ Description-Content-Type: text/markdown
33
33
  [![Unit Tests Status](https://github.com/ElectroRoute-Japan/mms-client/actions/workflows/check.yml/badge.svg)](https://github.com/ElectroRoute-Japan/mms-client/actions)
34
34
 
35
35
  # Overview
36
- This repository contains a Python client that is capable of communication with the Market Management System (MMS), which handles requests related to Flex or Virtual Power Plant (VPP) trading. The underlying library is relies on SOAP communication, which is a pain to work with at the best of times. This particular SOAP API adds its own special layer of obnoxiousness, however. As such, it was deemed useful to have a client which would obfuscate most, if not all of this away from the user.
36
+ This repository contains a Python client that is capable of communication with the Market Management System (MMS), which handles requests related to Flex or Virtual Power Plant (VPP) trading. The underlying library relies on SOAP communication, which is a pain to work with at the best of times. This particular SOAP API adds its own special layer of obnoxiousness, however. As such, it was deemed useful to have a client which would obfuscate most, if not all of this away from the user.
37
37
 
38
38
  # Communication
39
39
  The underlying API sends and receives XML documents. Each of these request or responses, which we will hereafter refer to as *outer* requests and responses, contains metadata about the request/response as well as three fields which are extremely important to successful communication with the API:
@@ -47,7 +47,9 @@ After the data has been converted and added to the outer request object, it is s
47
47
  This library relies on Pydantic 2 and the pydantic-xml library for serialization/deserialization. As such, any type in this library can be converted to not only XML, but to JSON as well. This is extremely useful if you're trying to build a pass-through API service or something similar.
48
48
 
49
49
  ## Client Types
50
- Clients cannot call any and all endpoints in this API, willy-nilly. Some endpoints are restricted to particular clients. At the moment, there are two clients: Balance Service Providers (BSPs) and Transmission Service Operators (TSOs). Most likely you're operating as a BSP, in which case you'll have access to all endpoints. However, it makes little sense for a TSO to be able to submit bids on their own power, so they are restricted to a read-only role in most cases.
50
+ Clients cannot call any and all endpoints in this API, willy-nilly. Some endpoints are restricted to particular clients. At the moment, there are three clients: Balance Service Providers (BSPs), Market Operators (MOs), and Transmission Service Operators (TSOs). Most likely you're operating as a BSP or MO, in which case you'll have access to most or all endpoints. However, it makes little sense for a TSO to be able to submit bids on their own power, so they are restricted to a read-only role in most cases.
51
+
52
+ Note that MOs and BSPs access the MMS service using the same endpoints, but have distinct permissions. As such, both client types are supported explicitly here.
51
53
 
52
54
  Should you request an endpoint which you are not authorized for, you will receive an `mms_client.utils.errors.AudienceError`.
53
55
 
@@ -193,6 +195,27 @@ client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.B
193
195
 
194
196
  The client currently logs a number of informational, debug and error messages. You can freely change the logging level yourself.
195
197
 
198
+ ## Auditing XML Requests & Responses
199
+ 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.
200
+
201
+ ```python
202
+ class TestAuditPlugin(AuditPlugin):
203
+
204
+ def __init__(self):
205
+ self.request = None
206
+ self.response = None
207
+
208
+ def audit_request(self, mms_request: bytes) -> None:
209
+ self.request = mms_request
210
+
211
+ def audit_response(self, mms_response: bytes) -> None:
212
+ self.response = mms_response
213
+
214
+ client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, plugins=[TestAuditPlugin()])
215
+ ```
216
+
217
+ This same input allows for the user to create their own plugins and add them to the Zeep client, allowing for a certain amount of extensibility.
218
+
196
219
  # Completeness
197
220
  This client is not complete. Currently, it supports the following endpoints:
198
221
  - MarketSubmit_OfferData
@@ -12,10 +12,10 @@ mms_client/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
12
12
  mms_client/security/certs.py,sha256=kNCUFmy18YIxkWKu3mdMmlxmHdft4a6BvtyJ46rA9I4,1489
13
13
  mms_client/security/crypto.py,sha256=M7aIllM3_ZwZgm9nH6QQ6Ig14XCAd6e6WGwqqUbbI1Q,2149
14
14
  mms_client/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- mms_client/services/base.py,sha256=mcUL-AZpaJETVkzAOoazmKfI5O3cJ0KWzIYQ5bjpNuE,25839
16
- mms_client/services/market.py,sha256=aIWi52BFvDmtuipKnSZNprwbFYGYBcV6sZCQqMR_D2g,7574
15
+ mms_client/services/base.py,sha256=dKyMoulyyh5p7L7eBtiY10dLx5HAqn5icUjrheYgv4I,26397
16
+ mms_client/services/market.py,sha256=cxfBrqHeHLHdXWFsS4UFJYMsUBW-m66QYlM0WQcdwEc,7580
17
17
  mms_client/services/omi.py,sha256=h6cM5U3-iSm0YiIaJwqYTZeI5uhLbA7FPxh_qy_3qww,521
18
- mms_client/services/registration.py,sha256=46Scntwlc9CtCO-tV6uEnr4NrVGJiUqhvksII13CGAE,3651
18
+ mms_client/services/registration.py,sha256=lq5eC3M5BX74WD8oAPGOu4ExxwDSled4iGC9aJzzFPI,3711
19
19
  mms_client/services/report.py,sha256=HYVJNwEHo6ZC6497UqO5y1IqZ2ga3kVH5AepdxhYfug,537
20
20
  mms_client/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
21
  mms_client/types/award.py,sha256=BWE9V_KHXpg_cW1LZsetVrPs2hZDOklvRpNnoZtmR3k,14139
@@ -28,10 +28,11 @@ mms_client/types/registration.py,sha256=Nir73S3ffpk0O_fnTD2alFaqV1k67_8dcyyduXvP
28
28
  mms_client/types/resource.py,sha256=TQnY3JLHRgQhQrG6ISquw-BQgKSr8TGuqn9ItWxWz_w,65974
29
29
  mms_client/types/transport.py,sha256=DPjWs34UW915GkUCJWKuDZmsjS6mRdRXgcGISduN_Bc,4399
30
30
  mms_client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- mms_client/utils/errors.py,sha256=jYdlG4OPI82s0fJcXCRNlKeEixDUSSAxjs_7C16qVL4,2306
31
+ mms_client/utils/auditing.py,sha256=nRa2SI21B3N3ZhbGhgooHAPZ81J1NZ9e4JU1ucEKM-g,1693
32
+ mms_client/utils/errors.py,sha256=ZiAmCCp8ntUyPzaBST4pAItAHYW7vYcNeSWftLf2uII,2475
32
33
  mms_client/utils/serialization.py,sha256=vTJZSjhfTph2tDAYbLAhnz89i0qXJ8FINGdve67cJOU,29534
33
- mms_client/utils/web.py,sha256=fcdCtdDrHBPyhIlTcyiuAk3D3TlW8HmUw-wGfpG4KTA,9653
34
- mms_client-1.3.1.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
35
- mms_client-1.3.1.dist-info/METADATA,sha256=oSdqfUgW01Tr2ZRcCUd4SedQzQZrvNLY5fWmNmlMviY,14774
36
- mms_client-1.3.1.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
37
- mms_client-1.3.1.dist-info/RECORD,,
34
+ mms_client/utils/web.py,sha256=sS7CriPh-5lAEKJh4YkAjXKhRr9SzwfosUOWXFFhq-w,10094
35
+ mms_client-1.4.1.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
36
+ mms_client-1.4.1.dist-info/METADATA,sha256=eXxAN45UXZTb1bX5_YJetGscOPOtF2nLwUsYuUtnrn0,16354
37
+ mms_client-1.4.1.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
38
+ mms_client-1.4.1.dist-info/RECORD,,