mms-client 1.4.0__tar.gz → 1.5.0__tar.gz

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.
Files changed (38) hide show
  1. {mms_client-1.4.0 → mms_client-1.5.0}/PKG-INFO +5 -12
  2. {mms_client-1.4.0 → mms_client-1.5.0}/README.md +4 -11
  3. {mms_client-1.4.0 → mms_client-1.5.0}/pyproject.toml +1 -1
  4. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/client.py +5 -0
  5. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/services/base.py +51 -63
  6. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/services/market.py +7 -3
  7. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/services/omi.py +5 -0
  8. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/services/registration.py +6 -1
  9. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/services/report.py +5 -0
  10. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/utils/auditing.py +4 -0
  11. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/utils/errors.py +11 -2
  12. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/utils/serialization.py +4 -0
  13. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/utils/web.py +46 -42
  14. {mms_client-1.4.0 → mms_client-1.5.0}/LICENSE +0 -0
  15. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/__init__.py +0 -0
  16. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/py.typed +0 -0
  17. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/schemas/wsdl/mi-web-service-jbms.wsdl +0 -0
  18. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/schemas/wsdl/omi-web-service.wsdl +0 -0
  19. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/schemas/xsd/mi-market.xsd +0 -0
  20. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/schemas/xsd/mi-outbnd-reports.xsd +0 -0
  21. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/schemas/xsd/mi-report.xsd +0 -0
  22. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/schemas/xsd/mpr.xsd +0 -0
  23. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/schemas/xsd/omi.xsd +0 -0
  24. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/security/__init__.py +0 -0
  25. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/security/certs.py +0 -0
  26. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/security/crypto.py +0 -0
  27. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/services/__init__.py +0 -0
  28. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/__init__.py +0 -0
  29. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/award.py +0 -0
  30. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/base.py +0 -0
  31. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/enums.py +0 -0
  32. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/fields.py +0 -0
  33. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/market.py +0 -0
  34. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/offer.py +0 -0
  35. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/registration.py +0 -0
  36. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/resource.py +0 -0
  37. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/types/transport.py +0 -0
  38. {mms_client-1.4.0 → mms_client-1.5.0}/src/mms_client/utils/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mms-client
3
- Version: 1.4.0
3
+ Version: 1.5.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
@@ -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
 
@@ -184,15 +186,6 @@ If you're connecting as a market operator (MO), you can connect in admin mode:
184
186
  client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, is_admin=True)
185
187
  ```
186
188
 
187
- ## Log Settings
188
- The client also supports injection of a custom logger. The default logger is named "MMS Client".
189
-
190
- ```python
191
- client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, logger=my_logger)
192
- ```
193
-
194
- The client currently logs a number of informational, debug and error messages. You can freely change the logging level yourself.
195
-
196
189
  ## Auditing XML Requests & Responses
197
190
  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.
198
191
 
@@ -1,7 +1,7 @@
1
1
  [![Unit Tests Status](https://github.com/ElectroRoute-Japan/mms-client/actions/workflows/check.yml/badge.svg)](https://github.com/ElectroRoute-Japan/mms-client/actions)
2
2
 
3
3
  # Overview
4
- 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.
4
+ 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.
5
5
 
6
6
  # Communication
7
7
  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:
@@ -15,7 +15,9 @@ After the data has been converted and added to the outer request object, it is s
15
15
  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.
16
16
 
17
17
  ## Client Types
18
- 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.
18
+ 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.
19
+
20
+ 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.
19
21
 
20
22
  Should you request an endpoint which you are not authorized for, you will receive an `mms_client.utils.errors.AudienceError`.
21
23
 
@@ -152,15 +154,6 @@ If you're connecting as a market operator (MO), you can connect in admin mode:
152
154
  client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, is_admin=True)
153
155
  ```
154
156
 
155
- ## Log Settings
156
- The client also supports injection of a custom logger. The default logger is named "MMS Client".
157
-
158
- ```python
159
- client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, logger=my_logger)
160
- ```
161
-
162
- The client currently logs a number of informational, debug and error messages. You can freely change the logging level yourself.
163
-
164
157
  ## Auditing XML Requests & Responses
165
158
  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.
166
159
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "mms_client"
3
- version = "v1.4.0"
3
+ version = "v1.5.0"
4
4
  description = "API client for accessing the MMS"
5
5
  authors = ["Ryan Wood <ryan.wood@electroroute.co.jp>"]
6
6
  readme = "README.md"
@@ -1,11 +1,16 @@
1
1
  """Contains the client layer for communicating with the MMS server."""
2
2
 
3
+ from logging import getLogger
4
+
3
5
  from mms_client.services.base import BaseClient
4
6
  from mms_client.services.market import MarketClientMixin
5
7
  from mms_client.services.omi import OMIClientMixin
6
8
  from mms_client.services.registration import RegistrationClientMixin
7
9
  from mms_client.services.report import ReportClientMixin
8
10
 
11
+ # Set the default logger for the MMS client
12
+ logger = getLogger(__name__)
13
+
9
14
 
10
15
  class MmsClient(BaseClient, MarketClientMixin, RegistrationClientMixin, ReportClientMixin, OMIClientMixin):
11
16
  """User client for the MMS server.
@@ -1,7 +1,6 @@
1
1
  """Contains the client layer for communicating with the MMS server."""
2
2
 
3
3
  from dataclasses import dataclass
4
- from logging import Logger
5
4
  from logging import getLogger
6
5
  from typing import Dict
7
6
  from typing import Generic
@@ -37,7 +36,7 @@ from mms_client.utils.web import Plugin
37
36
  from mms_client.utils.web import ZWrapper
38
37
 
39
38
  # Set the default logger for the MMS client
40
- default_logger = getLogger("MMS Client")
39
+ logger = getLogger(__name__)
41
40
 
42
41
 
43
42
  @dataclass
@@ -59,7 +58,7 @@ class EndpointConfiguration(Generic[E, P]):
59
58
  name: str
60
59
 
61
60
  # The allowed client types for the endpoint
62
- allowed_client: Optional[ClientType]
61
+ allowed_clients: Optional[List[ClientType]]
63
62
 
64
63
  # The service for the endpoint
65
64
  service: ServiceConfiguration
@@ -85,10 +84,6 @@ class ClientProto(Protocol):
85
84
  def user(self) -> str:
86
85
  """Return the user name of the person making the request."""
87
86
 
88
- @property
89
- def logger(self) -> Logger:
90
- """Return the logger for the client."""
91
-
92
87
  def verify_audience(self, config: EndpointConfiguration) -> None:
93
88
  """Verify that the client type is allowed.
94
89
 
@@ -139,7 +134,7 @@ def mms_endpoint(
139
134
  name: str,
140
135
  service: ServiceConfiguration,
141
136
  request_type: RequestType,
142
- allowed_client: Optional[ClientType] = None,
137
+ allowed_clients: Optional[List[ClientType]] = None,
143
138
  resp_envelope_type: Optional[Type[E]] = None,
144
139
  resp_data_type: Optional[Type[P]] = None,
145
140
  ):
@@ -150,22 +145,23 @@ def mms_endpoint(
150
145
  Decorated functions will only be responsible for creating the payload envelope to submit to the MMS server.
151
146
 
152
147
  Arguments:
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_client (ClientType): The type of client that is allowed to access the endpoint. If this is not provided,
157
- then any client type is allowed.
158
- resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
159
- the 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.
148
+ name (str): The name of the endpoint.
149
+ service (ServiceConfiguration): The configuration for the service.
150
+ request_type (RequestType): The type of request to submit to the MMS server.
151
+ allowed_clients (List[ClientType]): The types of clients that are allowed to access the endpoint. If this is not
152
+ provided, then any client will be allowed.
153
+ resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
154
+ response envelope will be assumed to have the same type as the request envelope.
155
+ resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
156
+ response data will be assumed to have the same type as the request data.
162
157
  """
163
158
  # First, create the endpoint configuration from the given parameters
164
- config = EndpointConfiguration(name, allowed_client, service, request_type, resp_envelope_type, resp_data_type)
159
+ config = EndpointConfiguration(name, allowed_clients, service, request_type, resp_envelope_type, resp_data_type)
165
160
 
166
161
  # Next, create a decorator that will add the endpoint configuration to the function
167
162
  def decorator(func):
168
163
  def wrapper(self: ClientProto, *args, **kwargs) -> Optional[P]:
164
+ logger.info(f"{config.name}: Called with args: {args[1:]}...")
169
165
 
170
166
  # First, verify that the client type is allowed
171
167
  self.verify_audience(config)
@@ -177,6 +173,7 @@ def mms_endpoint(
177
173
  resp, _ = self.request_one(envelope, args[0], config)
178
174
 
179
175
  # Finally, extract the data from the response and return it
176
+ logger.info(f"{config.name}: Returning {type(resp.data).__name__} data.")
180
177
  return resp.data
181
178
 
182
179
  return wrapper
@@ -189,7 +186,7 @@ def mms_multi_endpoint(
189
186
  name: str,
190
187
  service: ServiceConfiguration,
191
188
  request_type: RequestType,
192
- allowed_client: Optional[ClientType] = None,
189
+ allowed_clients: Optional[List[ClientType]] = None,
193
190
  resp_envelope_type: Optional[Type[E]] = None,
194
191
  resp_data_type: Optional[Type[P]] = None,
195
192
  ):
@@ -201,26 +198,27 @@ def mms_multi_endpoint(
201
198
  the MMS server.
202
199
 
203
200
  Arguments:
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_client (ClientType): The type of client that is allowed to access the endpoint. If this is not provided,
208
- then any client type is allowed.
209
- resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
210
- the response envelope will be assumed to have the same type as the request envelope.
211
- resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
212
- response data will be assumed to have the same type as the request data. Note, that
213
- this is not intended to account for the expected sequence type of the response data.
214
- That is already handled in the wrapped function, so this should only be set if the
215
- inner data type being returned differs from what was sent.
201
+ name (str): The name of the endpoint.
202
+ service (ServiceConfiguration): The configuration for the service.
203
+ request_type (RequestType): The type of request to submit to the MMS server.
204
+ allowed_clients (List[ClientType]): The types of clients that are allowed to access the endpoint. If this is not
205
+ provided, then any client will be allowed.
206
+ resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then
207
+ the response envelope will be assumed to have the same type as the request
208
+ 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,
211
+ that this is not intended to account for the expected sequence type of the
212
+ response data. That is already handled in the wrapped function, so this should
213
+ only be set if the inner data type being returned differs from what was sent.
216
214
  """
217
215
  # First, create the endpoint configuration from the given parameters
218
- config = EndpointConfiguration(name, allowed_client, service, request_type, resp_envelope_type, resp_data_type)
216
+ config = EndpointConfiguration(name, allowed_clients, service, request_type, resp_envelope_type, resp_data_type)
219
217
 
220
218
  # Next, create a decorator that will add the endpoint configuration to the function
221
219
  def decorator(func):
222
220
  def wrapper(self: ClientProto, *args, **kwargs) -> List[P]:
223
- self.logger.info(f"{config.name}: Called with args: {args[1:]}...")
221
+ logger.info(f"{config.name}: Called with args: {args[1:]}...")
224
222
 
225
223
  # First, verify that the client type is allowed
226
224
  self.verify_audience(config)
@@ -232,7 +230,7 @@ def mms_multi_endpoint(
232
230
  resp, _ = self.request_many(envelope, args[0], config)
233
231
 
234
232
  # Finally, extract the data from the response and return it
235
- self.logger.info(f"{config.name}: Returning {len(resp.data)} item(s).")
233
+ logger.info(f"{config.name}: Returning {len(resp.data)} item(s).")
236
234
  return resp.data
237
235
 
238
236
  return wrapper
@@ -253,7 +251,6 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
253
251
  user: str,
254
252
  client_type: ClientType,
255
253
  cert: Certificate,
256
- logger: Optional[Logger] = None,
257
254
  plugins: Optional[List[Plugin]] = None,
258
255
  is_admin: bool = False,
259
256
  test: bool = False,
@@ -265,8 +262,6 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
265
262
  user (str): The user name of the person making the request.
266
263
  client_type (ClientType): The type of client to use for making requests to the MMS server.
267
264
  cert (Certificate): The certificate to use for signing requests.
268
- logger (Logger): The logger to use for instrumentation. If this is not provided, then the default
269
- logger will be used.
270
265
  plugins (List[Plugin]): A list of plugins to add to the Zeep client. This can be useful for auditing or
271
266
  other purposes.
272
267
  is_admin (bool): Whether the user is an admin (i.e. is a market operator).
@@ -283,8 +278,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
283
278
  self._cert = cert
284
279
  self._signer = CryptoWrapper(cert)
285
280
 
286
- # Now, set our logger to either the provided logger or the default logger
287
- self._logger = logger or default_logger
281
+ # Now, set the plugins we'll inject into the Zeep client
288
282
  self._plugins = plugins or []
289
283
 
290
284
  # Finally, create a list of wrappers for the different interfaces
@@ -300,11 +294,6 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
300
294
  """Return the user name of the person making the request."""
301
295
  return self._user
302
296
 
303
- @property
304
- def logger(self) -> Logger:
305
- """Return the logger for the client."""
306
- return self._logger
307
-
308
297
  def verify_audience(self, config: EndpointConfiguration) -> None:
309
298
  """Verify that the client type is allowed.
310
299
 
@@ -317,12 +306,12 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
317
306
  Raises:
318
307
  ValueError: If the client type is not allowed.
319
308
  """
320
- self._logger.debug(
321
- f"{config.name}: Verifying audience. Allowed client: "
322
- f"{config.allowed_client.name if config.allowed_client else 'Any'}."
309
+ logger.debug(
310
+ f"{config.name}: Verifying audience. Allowed clients: "
311
+ f"{config.allowed_clients if config.allowed_clients else 'Any'}."
323
312
  )
324
- if config.allowed_client and self._client_type != config.allowed_client:
325
- raise AudienceError(config.name, config.allowed_client, self._client_type)
313
+ if config.allowed_clients and (self._client_type not in config.allowed_clients):
314
+ raise AudienceError(config.name, config.allowed_clients, self._client_type)
326
315
 
327
316
  def request_one(
328
317
  self,
@@ -340,7 +329,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
340
329
  Returns: The response from the MMS server.
341
330
  """
342
331
  # First, create the MMS request from the payload and data.
343
- self._logger.debug(
332
+ logger.debug(
344
333
  f"{config.name}: Starting request. Envelope: {type(envelope).__name__}, Data: {type(payload).__name__}",
345
334
  )
346
335
  request = self._to_mms_request(config.request_type, config.service.serializer.serialize(envelope, payload))
@@ -359,7 +348,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
359
348
  self._verify_response(data, config)
360
349
 
361
350
  # Return the response data and any attachments
362
- self._logger.debug(
351
+ logger.debug(
363
352
  f"{config.name}: Returning response. Envelope: {envelope_type.__name__}, Data: {data_type.__name__}",
364
353
  )
365
354
  return data, attachments
@@ -382,7 +371,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
382
371
  # First, create the MMS request from the payload and data.
383
372
  is_list = isinstance(payload, list)
384
373
  data_type = type(payload[0]) if is_list else type(payload) # type: ignore[index]
385
- self._logger.debug(
374
+ logger.debug(
386
375
  (
387
376
  f"{config.name}: Starting multi-request. Envelope: {type(envelope).__name__}, "
388
377
  f"Data: {data_type.__name__}"
@@ -413,7 +402,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
413
402
  self._verify_multi_response(data, config)
414
403
 
415
404
  # Return the response data and any attachments
416
- self._logger.debug(
405
+ logger.debug(
417
406
  f"{config.name}: Returning multi-response. Envelope: {envelope_type.__name__}, Data: {data_type.__name__}",
418
407
  )
419
408
  return data, attachments
@@ -446,7 +435,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
446
435
  )
447
436
 
448
437
  # Embed the data and the attachments in the MMS request and return it
449
- self._logger.debug(
438
+ logger.debug(
450
439
  f"Creating MMS request of type {req_type.name} to send {len(data)} bytes of data and "
451
440
  f"{len(attachment_data)} attachments."
452
441
  )
@@ -480,9 +469,9 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
480
469
 
481
470
  # Check the response status flags and log any warnings or errors
482
471
  if resp.warnings:
483
- self._logger.warning(f"{config.name}: MMS response contained warnings.")
472
+ logger.warning(f"{config.name}: MMS response contained warnings.")
484
473
  if not resp.success:
485
- self._logger.error(f"{config.name}: MMS response was unsuccessful.")
474
+ logger.error(f"{config.name}: MMS response was unsuccessful.")
486
475
 
487
476
  def _verify_response(self, resp: Response[E, P], config: EndpointConfiguration) -> None:
488
477
  """Verify that the given response is valid.
@@ -543,7 +532,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
543
532
  Returns: True to indicate that the response is valid, False otherwise.
544
533
  """
545
534
  # Log the request's processing statistics
546
- self._logger.info(
535
+ logger.info(
547
536
  f"{config.name} ({resp.statistics.timestamp_xml}): Recieved {resp.statistics.received}, "
548
537
  f"Valid: {resp.statistics.valid}, Invalid: {resp.statistics.invalid}, "
549
538
  f"Successful: {resp.statistics.successful}, Unsuccessful: {resp.statistics.unsuccessful} "
@@ -565,11 +554,11 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
565
554
  """
566
555
  for path, messages in resp.messages.items():
567
556
  for info in messages.information:
568
- self._logger.info(f"{config.name} - {path}: {info.code}")
557
+ logger.info(f"{config.name} - {path}: {info.code}")
569
558
  for warning in messages.warnings:
570
- self._logger.warning(f"{config.name} - {path}: {warning.code}")
559
+ logger.warning(f"{config.name} - {path}: {warning.code}")
571
560
  for error in messages.errors:
572
- self._logger.error(f"{config.name} - {path}: {error.code}")
561
+ logger.error(f"{config.name} - {path}: {error.code}")
573
562
 
574
563
  def _verify_response_common(
575
564
  self, config: EndpointConfiguration, payload_type: type, resp: ResponseCommon, index: Optional[int] = None
@@ -586,7 +575,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
586
575
  Returns: True to indicate that the response is valid, False otherwise.
587
576
  """
588
577
  # Log the status of the response validation
589
- self._logger.info(
578
+ logger.info(
590
579
  f"{config.name}: {payload_type.__name__}{f'[{index}]' if index is not None else ''} was valid? "
591
580
  f"{resp.success} ({resp.validation.value})",
592
581
  )
@@ -601,12 +590,11 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
601
590
  service (ServiceConfiguration): The service for which to get the wrapper.
602
591
  """
603
592
  if service.interface not in self._wrappers:
604
- self._logger.debug(f"Creating wrapper for {service.interface.name} interface.")
593
+ logger.debug(f"Creating wrapper for {service.interface.name} interface.")
605
594
  self._wrappers[service.interface] = ZWrapper(
606
595
  self._client_type,
607
596
  service.interface,
608
597
  self._cert.to_adapter(),
609
- self._logger,
610
598
  self._plugins,
611
599
  True,
612
600
  self._test,
@@ -1,6 +1,7 @@
1
1
  """Contains the client layer for making market requests to the MMS server."""
2
2
 
3
3
  from datetime import date as Date
4
+ from logging import getLogger
4
5
  from typing import List
5
6
  from typing import Optional
6
7
 
@@ -23,6 +24,9 @@ from mms_client.utils.serialization import Serializer
23
24
  from mms_client.utils.web import ClientType
24
25
  from mms_client.utils.web import Interface
25
26
 
27
+ # Set the default logger for the MMS client
28
+ logger = getLogger(__name__)
29
+
26
30
 
27
31
  class MarketClientMixin: # pylint: disable=unused-argument
28
32
  """Market client for the MMS server."""
@@ -30,7 +34,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
30
34
  # The configuration for the market service
31
35
  config = ServiceConfiguration(Interface.MI, Serializer(SchemaType.MARKET, "MarketData"))
32
36
 
33
- @mms_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, ClientType.BSP)
37
+ @mms_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, [ClientType.BSP])
34
38
  def put_offer(
35
39
  self: ClientProto, request: OfferData, market_type: MarketType, days: int, date: Optional[Date] = None
36
40
  ) -> OfferData:
@@ -56,7 +60,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
56
60
  days=days,
57
61
  )
58
62
 
59
- @mms_multi_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, ClientType.BSP)
63
+ @mms_multi_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, [ClientType.BSP])
60
64
  def put_offers(
61
65
  self: ClientProto, requests: List[OfferData], market_type: MarketType, days: int, date: Optional[Date] = None
62
66
  ) -> List[OfferData]:
@@ -106,7 +110,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
106
110
  days=days,
107
111
  )
108
112
 
109
- @mms_endpoint("MarketCancel_OfferCancel", config, RequestType.INFO, ClientType.BSP)
113
+ @mms_endpoint("MarketCancel_OfferCancel", config, RequestType.INFO, [ClientType.BSP])
110
114
  def cancel_offer(
111
115
  self: ClientProto, request: OfferCancel, market_type: MarketType, days: int, date: Optional[Date] = None
112
116
  ) -> OfferCancel:
@@ -1,10 +1,15 @@
1
1
  """Contains the client layer for making OMI requests to the MMS server."""
2
2
 
3
+ from logging import getLogger
4
+
3
5
  from mms_client.services.base import ServiceConfiguration
4
6
  from mms_client.utils.serialization import SchemaType
5
7
  from mms_client.utils.serialization import Serializer
6
8
  from mms_client.utils.web import Interface
7
9
 
10
+ # Set the default logger for the MMS client
11
+ logger = getLogger(__name__)
12
+
8
13
 
9
14
  class OMIClientMixin: # pylint: disable=unused-argument
10
15
  """OMI client for the MMS server."""
@@ -1,6 +1,7 @@
1
1
  """Contains the client layer for making registration requests to the MMS server."""
2
2
 
3
3
  from datetime import date as Date
4
+ from logging import getLogger
4
5
  from typing import List
5
6
  from typing import Optional
6
7
 
@@ -20,6 +21,9 @@ from mms_client.utils.serialization import Serializer
20
21
  from mms_client.utils.web import ClientType
21
22
  from mms_client.utils.web import Interface
22
23
 
24
+ # Set the default logger for the MMS client
25
+ logger = getLogger(__name__)
26
+
23
27
 
24
28
  class RegistrationClientMixin: # pylint: disable=unused-argument
25
29
  """Registration client for the MMS server."""
@@ -27,7 +31,7 @@ class RegistrationClientMixin: # pylint: disable=unused-argument
27
31
  # The configuration for the registration service
28
32
  config = ServiceConfiguration(Interface.MI, Serializer(SchemaType.REGISTRATION, "RegistrationData"))
29
33
 
30
- @mms_endpoint("RegistrationSubmit_Resource", config, RequestType.REGISTRATION, ClientType.BSP)
34
+ @mms_endpoint("RegistrationSubmit_Resource", config, RequestType.REGISTRATION, [ClientType.BSP])
31
35
  def put_resource(self: ClientProto, request: ResourceData) -> ResourceData:
32
36
  """Submit a new resource to the MMS server.
33
37
 
@@ -50,6 +54,7 @@ class RegistrationClientMixin: # pylint: disable=unused-argument
50
54
  "RegistrationQuery_Resource",
51
55
  config,
52
56
  RequestType.REGISTRATION,
57
+ allowed_clients=[ClientType.BSP, ClientType.TSO],
53
58
  resp_envelope_type=RegistrationSubmit,
54
59
  resp_data_type=ResourceData,
55
60
  )
@@ -1,10 +1,15 @@
1
1
  """Contains the client layer for making report requests to the MMS server."""
2
2
 
3
+ from logging import getLogger
4
+
3
5
  from mms_client.services.base import ServiceConfiguration
4
6
  from mms_client.utils.serialization import SchemaType
5
7
  from mms_client.utils.serialization import Serializer
6
8
  from mms_client.utils.web import Interface
7
9
 
10
+ # Set the default logger for the MMS client
11
+ logger = getLogger(__name__)
12
+
8
13
 
9
14
  class ReportClientMixin: # pylint: disable=unused-argument
10
15
  """Report client for the MMS server."""
@@ -2,11 +2,15 @@
2
2
 
3
3
  from abc import ABC
4
4
  from abc import abstractmethod
5
+ from logging import getLogger
5
6
 
6
7
  from lxml.etree import _Element as Element
7
8
  from lxml.etree import tostring
8
9
  from zeep import Plugin
9
10
 
11
+ # Set the default logger for the MMS client
12
+ logger = getLogger(__name__)
13
+
10
14
 
11
15
  class AuditPlugin(ABC, Plugin):
12
16
  """Base class for audit plugins."""
@@ -1,5 +1,6 @@
1
1
  """Contains error classes for the MMS client."""
2
2
 
3
+ from logging import getLogger
3
4
  from typing import Dict
4
5
  from typing import List
5
6
  from typing import Optional
@@ -10,11 +11,14 @@ from mms_client.types.base import Messages
10
11
  from mms_client.types.base import P
11
12
  from mms_client.utils.web import ClientType
12
13
 
14
+ # Set the default logger for the MMS client
15
+ logger = getLogger(__name__)
16
+
13
17
 
14
18
  class AudienceError(ValueError):
15
19
  """Error raised when an invalid audience is provided."""
16
20
 
17
- def __init__(self, method: str, allowed: ClientType, audience: ClientType):
21
+ def __init__(self, method: str, allowed: List[ClientType], audience: ClientType):
18
22
  """Initialize the error.
19
23
 
20
24
  Arguments:
@@ -22,8 +26,13 @@ class AudienceError(ValueError):
22
26
  allowed (str): The allowed audience.
23
27
  audience (str): The invalid audience.
24
28
  """
29
+ inner = (
30
+ f"'{allowed[0].name}' is"
31
+ if len(allowed) == 1
32
+ else f"""{" or ".join([f"'{a.name}'" for a in allowed])} are"""
33
+ )
25
34
  self.method = method
26
- self.message = f"{method}: Invalid client type, '{audience.name}' provided. Only '{allowed.name}' is supported."
35
+ self.message = f"{method}: Invalid client type, '{audience.name}' provided. Only {inner} supported."
27
36
  self.allowed = allowed
28
37
  self.audience = audience
29
38
  super().__init__(self.message)
@@ -2,6 +2,7 @@
2
2
 
3
3
  from functools import lru_cache
4
4
  from io import BytesIO
5
+ from logging import getLogger
5
6
  from pathlib import Path
6
7
  from typing import Dict
7
8
  from typing import List
@@ -32,6 +33,9 @@ from mms_client.types.base import SchemaType
32
33
  # Directory containing all our XML schemas
33
34
  XSD_DIR = Path(__file__).parent.parent / "schemas" / "xsd"
34
35
 
36
+ # Set the default logger for the MMS client
37
+ logger = getLogger(__name__)
38
+
35
39
 
36
40
  class Serializer:
37
41
  """Contains methods for serializing and deserializing MMS data."""
@@ -1,7 +1,7 @@
1
1
  """Contains the HTTP/web layer for communicating with the MMS server."""
2
2
 
3
- from enum import Enum
4
- from logging import Logger
3
+ from enum import StrEnum
4
+ from logging import getLogger
5
5
  from pathlib import Path
6
6
  from typing import List
7
7
  from typing import Optional
@@ -20,18 +20,23 @@ from zeep.xsd.valueobjects import CompoundValue
20
20
  from mms_client.types.transport import MmsRequest
21
21
  from mms_client.types.transport import MmsResponse
22
22
 
23
+ # Set the default logger for the MMS client
24
+ logger = getLogger(__name__)
23
25
 
24
- class ClientType(Enum):
26
+
27
+ class ClientType(StrEnum):
25
28
  """Identifies the type of client to use.
26
29
 
27
- The client can be either "bsp" (Balancing Service Provider) or "tso" (Transmission System Operator).
30
+ The client can be either "bsp" (Balancing Service Provider), "mo" (Market Operator), or "tso" (Transmission System
31
+ Operator).
28
32
  """
29
33
 
30
34
  BSP = "bsp"
35
+ MO = "mo"
31
36
  TSO = "tso"
32
37
 
33
38
 
34
- class Interface(Enum):
39
+ class Interface(StrEnum):
35
40
  """Identifies the type of interface to use.
36
41
 
37
42
  The interface can be either "omi" (Other Market Initiator) or "mi" (Market Initiator).
@@ -76,32 +81,30 @@ class ServiceEndpoint:
76
81
  self._selected = self.main
77
82
 
78
83
 
79
- # Defines the service endpoints for the BSP and TSO clients for the OMI and MI web services, respectively.
80
- URLS = {
81
- ClientType.BSP: {
82
- Interface.OMI: ServiceEndpoint(
83
- main="https://www5.tdgc.jp/axis2/services/OmiWebService",
84
- backup="https://www6.tdgc.jp/axis2/services/OmiWebService",
85
- test="https://www7.tdgc.jp/axis2/services/OmiWebService",
86
- ),
87
- Interface.MI: ServiceEndpoint(
88
- main="https://www2.tdgc.jp/axis2/services/MiWebService",
89
- backup="https://www3.tdgc.jp/axis2/services/MiWebService",
90
- test="https://www4.tdgc.jp/axis2/services/MiWebService",
91
- ),
92
- },
93
- ClientType.TSO: {
94
- Interface.OMI: ServiceEndpoint(
95
- main="https://maiwlba103v07.tdgc.jp/axis2/services/OmiWebService",
96
- backup="https://mbiwlba103v07.tdgc.jp/axis2/services/OmiWebService",
97
- test="https://mbiwlba103v08.tdgc.jp/axis2/services/OmiWebService",
98
- ),
99
- Interface.MI: ServiceEndpoint(
100
- main="https://maiwlba103v03.tdgc.jp/axis2/services/MiWebService",
101
- backup="https://mbiwlba103v03.tdgc.jp/axis2/services/MiWebService",
102
- test="https://mbiwlba103v06.tdgc.jp/axis2/services/MiWebService",
103
- ),
104
- },
84
+ # Defines the service endpoints for the BSP, MO and TSO clients for the OMI and MI web services, respectively.
85
+ BSP_MO_URLS = {
86
+ Interface.OMI: ServiceEndpoint(
87
+ main="https://www5.tdgc.jp/axis2/services/OmiWebService",
88
+ backup="https://www6.tdgc.jp/axis2/services/OmiWebService",
89
+ test="https://www7.tdgc.jp/axis2/services/OmiWebService",
90
+ ),
91
+ Interface.MI: ServiceEndpoint(
92
+ main="https://www2.tdgc.jp/axis2/services/MiWebService",
93
+ backup="https://www3.tdgc.jp/axis2/services/MiWebService",
94
+ test="https://www4.tdgc.jp/axis2/services/MiWebService",
95
+ ),
96
+ }
97
+ TSO_URLS = {
98
+ Interface.OMI: ServiceEndpoint(
99
+ main="https://maiwlba103v07.tdgc.jp/axis2/services/OmiWebService",
100
+ backup="https://mbiwlba103v07.tdgc.jp/axis2/services/OmiWebService",
101
+ test="https://mbiwlba103v08.tdgc.jp/axis2/services/OmiWebService",
102
+ ),
103
+ Interface.MI: ServiceEndpoint(
104
+ main="https://maiwlba103v03.tdgc.jp/axis2/services/MiWebService",
105
+ backup="https://mbiwlba103v03.tdgc.jp/axis2/services/MiWebService",
106
+ test="https://mbiwlba103v06.tdgc.jp/axis2/services/MiWebService",
107
+ ),
105
108
  }
106
109
 
107
110
 
@@ -134,7 +137,6 @@ class ZWrapper:
134
137
  client: ClientType,
135
138
  interface: Interface,
136
139
  adapter: Pkcs12Adapter,
137
- logger: Logger,
138
140
  plugins: Optional[List[Plugin]] = None,
139
141
  cache: bool = True,
140
142
  test: bool = False,
@@ -150,7 +152,6 @@ class ZWrapper:
150
152
  as the service and port to use.
151
153
  adapter (Pkcs12Adapter): The PKCS12 adapter containing the certificate and private key to use for
152
154
  authenticating with the MMS server.
153
- logger (Logger): The logger to use for instrumentation.
154
155
  plugins (List[Plugin]): A list of Zeep plugins to use with the client. This is useful for adding additional
155
156
  functionality to the client, such as auditing or logging.
156
157
  cache (bool): If True, use a cache for the Zeep client. This is useful for avoiding repeated
@@ -158,8 +159,12 @@ class ZWrapper:
158
159
  test (bool): If True, use the test service endpoint. This is useful for testing the client.
159
160
  """
160
161
  # First, we'll check that the client is valid. If it's not, we'll raise a ValueError.
161
- if client not in URLS:
162
- raise ValueError(f"Invalid client, '{client}'. Only 'bsp' and 'tso' are supported.")
162
+ if client in [ClientType.BSP, ClientType.MO]:
163
+ urls = BSP_MO_URLS
164
+ elif client == ClientType.TSO:
165
+ urls = TSO_URLS
166
+ else:
167
+ raise ValueError(f"Invalid client, '{client}'. Only 'bsp', 'mo', and 'tso' are supported.")
163
168
 
164
169
  # We need to determine the service port and location of the WSDL file based on the given interface. If the
165
170
  # interface is neither "omi" nor "mi", we raise a ValueError.
@@ -172,7 +177,7 @@ class ZWrapper:
172
177
  raise ValueError(f"Invalid interface, '{self._interface}'. Only 'mi' and 'omi' are supported.")
173
178
 
174
179
  # Next, we need to select the correct service endpoint based on the given client and interface.
175
- self._endpoint = URLS[client][self._interface]
180
+ self._endpoint = urls[self._interface]
176
181
  self._endpoint.select(test=test)
177
182
 
178
183
  # Now, we need to create a new session and mount the PKCS12 adapter to it. This is necessary for
@@ -186,7 +191,6 @@ class ZWrapper:
186
191
 
187
192
  # Finally, we create the Zeep client with the given WSDL file location, session, and cache settings and then,
188
193
  # from that client, we create the SOAP service with the given service binding and selected endpoint.
189
- self._logger = logger
190
194
  self._client = Client(
191
195
  wsdl=str(location.resolve()),
192
196
  transport=Transport(cache=SqliteCache() if cache else None, session=sess),
@@ -194,11 +198,11 @@ class ZWrapper:
194
198
  )
195
199
  self._create_service()
196
200
 
197
- @on_exception(expo, TransportError, max_tries=3, giveup=fatal_code) # type: ignore[arg-type]
201
+ @on_exception(expo, TransportError, max_tries=3, giveup=fatal_code, logger=logger) # type: ignore[arg-type]
198
202
  def submit(self, req: MmsRequest) -> MmsResponse:
199
203
  """Submit the given request to the MMS server and return the response."""
200
204
  try:
201
- self._logger.debug(f"Submitting MMS request request to {self._interface.name} service")
205
+ logger.debug(f"Submitting MMS request request to {self._interface.name} service")
202
206
 
203
207
  # Submit the request to the MMS server and retrieve the response
204
208
  resp: CompoundValue = self._service["submitAttachment"](**req.to_arguments())
@@ -208,12 +212,12 @@ class ZWrapper:
208
212
  except TransportError as e:
209
213
  # If we got a server fault error, then we'll switch to the backup endpoint. In any case, we'll raise the
210
214
  # exception so that our back-off can handle it or pass the exception up the stack.
211
- self._logger.error(
215
+ logger.error(
212
216
  f"MMS request to {self._interface.name} service failed with status code: {e.status_code}",
213
217
  exc_info=e,
214
218
  )
215
219
  if e.status_code >= 500:
216
- self._logger.warning(f"MMS server error, switching to backup endpoint: {self._endpoint.backup}")
220
+ logger.warning(f"MMS server error, switching to backup endpoint: {self._endpoint.backup}")
217
221
  self._endpoint.select(error=True)
218
222
  self._create_service()
219
223
  raise
@@ -223,5 +227,5 @@ class ZWrapper:
223
227
 
224
228
  This is useful for switching between the main and backup endpoints in case of an error.
225
229
  """
226
- self._logger.debug(f"Creating new {self._interface.name} service with endpoint: {self._endpoint.selected}")
230
+ logger.debug(f"Creating new {self._interface.name} service with endpoint: {self._endpoint.selected}")
227
231
  self._service = self._client.create_service(SERVICE_BINDINGS[self._interface], self._endpoint.selected)
File without changes