mms-client 1.3.1__tar.gz → 1.4.1__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.
- {mms_client-1.3.1 → mms_client-1.4.1}/PKG-INFO +26 -3
- {mms_client-1.3.1 → mms_client-1.4.1}/README.md +25 -2
- {mms_client-1.3.1 → mms_client-1.4.1}/pyproject.toml +1 -1
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/services/base.py +43 -31
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/services/market.py +3 -3
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/services/registration.py +2 -1
- mms_client-1.4.1/src/mms_client/utils/auditing.py +54 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/utils/errors.py +7 -2
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/utils/web.py +44 -33
- {mms_client-1.3.1 → mms_client-1.4.1}/LICENSE +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/__init__.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/client.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/py.typed +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/schemas/wsdl/mi-web-service-jbms.wsdl +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/schemas/wsdl/omi-web-service.wsdl +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/schemas/xsd/mi-market.xsd +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/schemas/xsd/mi-outbnd-reports.xsd +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/schemas/xsd/mi-report.xsd +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/schemas/xsd/mpr.xsd +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/schemas/xsd/omi.xsd +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/security/__init__.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/security/certs.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/security/crypto.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/services/__init__.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/services/omi.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/services/report.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/__init__.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/award.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/base.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/enums.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/fields.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/market.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/offer.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/registration.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/resource.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/types/transport.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/utils/__init__.py +0 -0
- {mms_client-1.3.1 → mms_client-1.4.1}/src/mms_client/utils/serialization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mms-client
|
|
3
|
-
Version: 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
|
[](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
|
|
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
|
|
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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[](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
|
|
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
|
|
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
|
|
|
@@ -161,6 +163,27 @@ client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.B
|
|
|
161
163
|
|
|
162
164
|
The client currently logs a number of informational, debug and error messages. You can freely change the logging level yourself.
|
|
163
165
|
|
|
166
|
+
## Auditing XML Requests & Responses
|
|
167
|
+
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.
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
class TestAuditPlugin(AuditPlugin):
|
|
171
|
+
|
|
172
|
+
def __init__(self):
|
|
173
|
+
self.request = None
|
|
174
|
+
self.response = None
|
|
175
|
+
|
|
176
|
+
def audit_request(self, mms_request: bytes) -> None:
|
|
177
|
+
self.request = mms_request
|
|
178
|
+
|
|
179
|
+
def audit_response(self, mms_response: bytes) -> None:
|
|
180
|
+
self.response = mms_response
|
|
181
|
+
|
|
182
|
+
client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, plugins=[TestAuditPlugin()])
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
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.
|
|
186
|
+
|
|
164
187
|
# Completeness
|
|
165
188
|
This client is not complete. Currently, it supports the following endpoints:
|
|
166
189
|
- MarketSubmit_OfferData
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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):
|
|
153
|
-
service (ServiceConfiguration):
|
|
154
|
-
request_type (RequestType):
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
resp_envelope_type (Type[E]):
|
|
158
|
-
|
|
159
|
-
resp_data_type (Type[P]):
|
|
160
|
-
|
|
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,
|
|
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
|
-
|
|
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):
|
|
204
|
-
service (ServiceConfiguration):
|
|
205
|
-
request_type (RequestType):
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
resp_envelope_type (Type[E]):
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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,
|
|
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
|
|
317
|
-
f"{config.
|
|
322
|
+
f"{config.name}: Verifying audience. Allowed clients: "
|
|
323
|
+
f"{config.allowed_clients if config.allowed_clients else 'Any'}."
|
|
318
324
|
)
|
|
319
|
-
if config.
|
|
320
|
-
raise AudienceError(config.name, config.
|
|
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,
|
|
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
|
|
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)
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
"""Contains the HTTP/web layer for communicating with the MMS server."""
|
|
2
2
|
|
|
3
|
-
from enum import
|
|
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(
|
|
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
|
|
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(
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
156
|
-
|
|
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 =
|
|
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
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|