mms-client 1.7.0__py3-none-any.whl → 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mms_client/services/base.py +81 -52
- mms_client/services/market.py +28 -10
- mms_client/services/registration.py +12 -9
- mms_client/services/report.py +144 -1
- mms_client/types/base.py +52 -9
- mms_client/types/enums.py +34 -0
- mms_client/types/report.py +474 -0
- mms_client/types/resource.py +4 -34
- mms_client/utils/serialization.py +111 -42
- {mms_client-1.7.0.dist-info → mms_client-1.8.0.dist-info}/METADATA +6 -1
- {mms_client-1.7.0.dist-info → mms_client-1.8.0.dist-info}/RECORD +13 -12
- {mms_client-1.7.0.dist-info → mms_client-1.8.0.dist-info}/LICENSE +0 -0
- {mms_client-1.7.0.dist-info → mms_client-1.8.0.dist-info}/WHEEL +0 -0
mms_client/services/base.py
CHANGED
|
@@ -53,26 +53,33 @@ class ServiceConfiguration:
|
|
|
53
53
|
|
|
54
54
|
|
|
55
55
|
@dataclass
|
|
56
|
-
class EndpointConfiguration(Generic[E, P]):
|
|
56
|
+
class EndpointConfiguration(Generic[E, P]): # pylint: disable=too-many-instance-attributes
|
|
57
57
|
"""Configuration for an endpoint on the MMS server."""
|
|
58
58
|
|
|
59
59
|
# The name of the endpoint
|
|
60
60
|
name: str
|
|
61
61
|
|
|
62
|
-
# The allowed client types for the endpoint
|
|
63
|
-
allowed_clients: Optional[List[ClientType]]
|
|
64
|
-
|
|
65
62
|
# The service for the endpoint
|
|
66
63
|
service: ServiceConfiguration
|
|
67
64
|
|
|
68
65
|
# The type of request to submit to the MMS server
|
|
69
66
|
request_type: RequestType
|
|
70
67
|
|
|
68
|
+
# The allowed client types for the endpoint
|
|
69
|
+
allowed_clients: Optional[List[ClientType]] = None
|
|
70
|
+
|
|
71
71
|
# The type of payload to expect in the response
|
|
72
|
-
response_envelope_type: Optional[Type[E]]
|
|
72
|
+
response_envelope_type: Optional[Type[E]] = None
|
|
73
73
|
|
|
74
74
|
# The type of data to expect in the response
|
|
75
|
-
response_data_type: Optional[Type[P]]
|
|
75
|
+
response_data_type: Optional[Type[P]] = None
|
|
76
|
+
|
|
77
|
+
# Whether the endpoint is for a report request
|
|
78
|
+
for_report: bool = False
|
|
79
|
+
|
|
80
|
+
# An optional serializer used to deserialize the response data for the service. This is only used for report
|
|
81
|
+
# requests because they have a separate XSD file for responses.
|
|
82
|
+
serializer: Optional[Serializer] = None
|
|
76
83
|
|
|
77
84
|
|
|
78
85
|
class ClientProto(Protocol):
|
|
@@ -86,6 +93,10 @@ class ClientProto(Protocol):
|
|
|
86
93
|
def user(self) -> str:
|
|
87
94
|
"""Return the user name of the person making the request."""
|
|
88
95
|
|
|
96
|
+
@property
|
|
97
|
+
def client_type(self) -> ClientType:
|
|
98
|
+
"""Return the type of client to use for making requests to the MMS server."""
|
|
99
|
+
|
|
89
100
|
def verify_audience(self, config: EndpointConfiguration) -> None:
|
|
90
101
|
"""Verify that the client type is allowed.
|
|
91
102
|
|
|
@@ -132,14 +143,7 @@ class ClientProto(Protocol):
|
|
|
132
143
|
"""
|
|
133
144
|
|
|
134
145
|
|
|
135
|
-
def mms_endpoint(
|
|
136
|
-
name: str,
|
|
137
|
-
service: ServiceConfiguration,
|
|
138
|
-
request_type: RequestType,
|
|
139
|
-
allowed_clients: Optional[List[ClientType]] = None,
|
|
140
|
-
resp_envelope_type: Optional[Type[E]] = None,
|
|
141
|
-
resp_data_type: Optional[Type[P]] = None,
|
|
142
|
-
):
|
|
146
|
+
def mms_endpoint(**kwargs):
|
|
143
147
|
"""Create a decorator for an MMS endpoint.
|
|
144
148
|
|
|
145
149
|
This decorator is used to mark a method as an MMS endpoint. It will add the endpoint configuration to the function
|
|
@@ -152,13 +156,16 @@ def mms_endpoint(
|
|
|
152
156
|
request_type (RequestType): The type of request to submit to the MMS server.
|
|
153
157
|
allowed_clients (List[ClientType]): The types of clients that are allowed to access the endpoint. If this is not
|
|
154
158
|
provided, then any client will be allowed.
|
|
155
|
-
|
|
159
|
+
response_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
|
|
156
160
|
response envelope will be assumed to have the same type as the request envelope.
|
|
157
|
-
|
|
161
|
+
response_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
|
|
158
162
|
response data will be assumed to have the same type as the request data.
|
|
163
|
+
for_report (bool): If True, the endpoint is for a report request.
|
|
164
|
+
serializer (Serializer): The serializer to use for responses from the endpoint. Overrides the default
|
|
165
|
+
serializer for the service.
|
|
159
166
|
"""
|
|
160
167
|
# First, create the endpoint configuration from the given parameters
|
|
161
|
-
config = EndpointConfiguration(
|
|
168
|
+
config = EndpointConfiguration(**kwargs)
|
|
162
169
|
|
|
163
170
|
# Next, create a decorator that will add the endpoint configuration to the function
|
|
164
171
|
def decorator(func):
|
|
@@ -169,10 +176,18 @@ def mms_endpoint(
|
|
|
169
176
|
self.verify_audience(config)
|
|
170
177
|
|
|
171
178
|
# Next, call the wrapped function to get the envelope
|
|
172
|
-
|
|
179
|
+
result = func(self, *args, **kwargs)
|
|
180
|
+
if isinstance(result, tuple):
|
|
181
|
+
envelope, callback = result
|
|
182
|
+
else:
|
|
183
|
+
envelope, callback = result, None
|
|
173
184
|
|
|
174
185
|
# Now, submit the request to the MMS server and get the response
|
|
175
|
-
resp,
|
|
186
|
+
resp, attachments = self.request_one(envelope, args[0], config)
|
|
187
|
+
|
|
188
|
+
# Call the callback function if it was provided
|
|
189
|
+
if callback:
|
|
190
|
+
callback(resp, attachments)
|
|
176
191
|
|
|
177
192
|
# Finally, extract the data from the response and return it
|
|
178
193
|
logger.info(f"{config.name}: Returning {type(resp.data).__name__} data.")
|
|
@@ -184,14 +199,7 @@ def mms_endpoint(
|
|
|
184
199
|
return decorator
|
|
185
200
|
|
|
186
201
|
|
|
187
|
-
def mms_multi_endpoint(
|
|
188
|
-
name: str,
|
|
189
|
-
service: ServiceConfiguration,
|
|
190
|
-
request_type: RequestType,
|
|
191
|
-
allowed_clients: Optional[List[ClientType]] = None,
|
|
192
|
-
resp_envelope_type: Optional[Type[E]] = None,
|
|
193
|
-
resp_data_type: Optional[Type[P]] = None,
|
|
194
|
-
):
|
|
202
|
+
def mms_multi_endpoint(**kwargs):
|
|
195
203
|
"""Create a decorator for an MMS multi-response endpoint.
|
|
196
204
|
|
|
197
205
|
This decorator is used to mark a method as an MMS multi-response endpoint. It will add the endpoint configuration to
|
|
@@ -205,17 +213,20 @@ def mms_multi_endpoint(
|
|
|
205
213
|
request_type (RequestType): The type of request to submit to the MMS server.
|
|
206
214
|
allowed_clients (List[ClientType]): The types of clients that are allowed to access the endpoint. If this is not
|
|
207
215
|
provided, then any client will be allowed.
|
|
208
|
-
|
|
216
|
+
response_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then
|
|
209
217
|
the response envelope will be assumed to have the same type as the request
|
|
210
218
|
envelope.
|
|
211
|
-
|
|
219
|
+
response_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
|
|
212
220
|
response data will be assumed to have the same type as the request data. Note,
|
|
213
221
|
that this is not intended to account for the expected sequence type of the
|
|
214
222
|
response data. That is already handled in the wrapped function, so this should
|
|
215
223
|
only be set if the inner data type being returned differs from what was sent.
|
|
224
|
+
for_report (bool): If True, the endpoint is for a report request.
|
|
225
|
+
serializer (Serializer): The serializer to use for responses from the endpoint. Overrides the default
|
|
226
|
+
serializer for the service.
|
|
216
227
|
"""
|
|
217
228
|
# First, create the endpoint configuration from the given parameters
|
|
218
|
-
config = EndpointConfiguration(
|
|
229
|
+
config = EndpointConfiguration(**kwargs)
|
|
219
230
|
|
|
220
231
|
# Next, create a decorator that will add the endpoint configuration to the function
|
|
221
232
|
def decorator(func):
|
|
@@ -226,10 +237,18 @@ def mms_multi_endpoint(
|
|
|
226
237
|
self.verify_audience(config)
|
|
227
238
|
|
|
228
239
|
# Next, call the wrapped function to get the envelope
|
|
229
|
-
|
|
240
|
+
result = func(self, *args, **kwargs)
|
|
241
|
+
if isinstance(result, tuple):
|
|
242
|
+
envelope, callback = result # pragma: no cover
|
|
243
|
+
else:
|
|
244
|
+
envelope, callback = result, None
|
|
230
245
|
|
|
231
246
|
# Now, submit the request to the MMS server and get the response
|
|
232
|
-
resp,
|
|
247
|
+
resp, attachments = self.request_many(envelope, args[0], config)
|
|
248
|
+
|
|
249
|
+
# Call the callback function if it was provided
|
|
250
|
+
if callback:
|
|
251
|
+
callback(resp, attachments) # pragma: no cover
|
|
233
252
|
|
|
234
253
|
# Finally, extract the data from the response and return it
|
|
235
254
|
logger.info(f"{config.name}: Returning {len(resp.data)} item(s).")
|
|
@@ -299,6 +318,11 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
299
318
|
"""Return the user name of the person making the request."""
|
|
300
319
|
return self._user
|
|
301
320
|
|
|
321
|
+
@property
|
|
322
|
+
def client_type(self) -> ClientType:
|
|
323
|
+
"""Return the type of client to use for making requests to the MMS server."""
|
|
324
|
+
return self._client_type
|
|
325
|
+
|
|
302
326
|
def verify_audience(self, config: EndpointConfiguration) -> None:
|
|
303
327
|
"""Verify that the client type is allowed.
|
|
304
328
|
|
|
@@ -341,7 +365,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
341
365
|
f"{config.name}: Starting request. Envelope: {type(envelope).__name__}, Data: {type(payload).__name__}",
|
|
342
366
|
)
|
|
343
367
|
request = self._to_mms_request(
|
|
344
|
-
wrapper, config.request_type, config.service.serializer.serialize(envelope, payload)
|
|
368
|
+
wrapper, config.request_type, config.service.serializer.serialize(envelope, payload, config.for_report)
|
|
345
369
|
)
|
|
346
370
|
|
|
347
371
|
# Next, submit the request to the MMS server and get and verify the response.
|
|
@@ -354,7 +378,8 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
354
378
|
# Finally, deserialize and verify the response
|
|
355
379
|
envelope_type = config.response_envelope_type or type(envelope)
|
|
356
380
|
data_type = config.response_data_type or type(payload)
|
|
357
|
-
|
|
381
|
+
deserializer = config.serializer or config.service.serializer
|
|
382
|
+
data: Response[E, P] = deserializer.deserialize(resp.payload, envelope_type, data_type, config.for_report)
|
|
358
383
|
self._verify_response(data, config)
|
|
359
384
|
|
|
360
385
|
# Return the response data and any attachments
|
|
@@ -391,9 +416,11 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
391
416
|
),
|
|
392
417
|
)
|
|
393
418
|
serialized = (
|
|
394
|
-
config.service.serializer.serialize_multi(
|
|
419
|
+
config.service.serializer.serialize_multi(
|
|
420
|
+
envelope, payload, data_type, config.for_report # type: ignore[arg-type]
|
|
421
|
+
)
|
|
395
422
|
if is_list
|
|
396
|
-
else config.service.serializer.serialize(envelope, payload) # type: ignore[type-var]
|
|
423
|
+
else config.service.serializer.serialize(envelope, payload, config.for_report) # type: ignore[type-var]
|
|
397
424
|
)
|
|
398
425
|
request = self._to_mms_request(wrapper, config.request_type, serialized)
|
|
399
426
|
|
|
@@ -407,10 +434,12 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
407
434
|
# Finally, deserialize and verify the response
|
|
408
435
|
envelope_type = config.response_envelope_type or type(envelope)
|
|
409
436
|
data_type = config.response_data_type or data_type
|
|
410
|
-
|
|
437
|
+
deserializer = config.serializer or config.service.serializer
|
|
438
|
+
data: MultiResponse[E, P] = deserializer.deserialize_multi(
|
|
411
439
|
resp.payload,
|
|
412
440
|
envelope_type,
|
|
413
441
|
data_type, # type: ignore[arg-type]
|
|
442
|
+
config.for_report,
|
|
414
443
|
)
|
|
415
444
|
self._verify_multi_response(data, config)
|
|
416
445
|
|
|
@@ -574,18 +603,18 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
574
603
|
Returns: True to indicate that the response is valid, False otherwise.
|
|
575
604
|
"""
|
|
576
605
|
# Log the request's processing statistics
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
606
|
+
if resp.statistics is not None:
|
|
607
|
+
logger.info(
|
|
608
|
+
f"{config.name} ({resp.statistics.timestamp_xml}): Recieved {resp.statistics.received}, "
|
|
609
|
+
f"Valid: {resp.statistics.valid}, Invalid: {resp.statistics.invalid}, "
|
|
610
|
+
f"Successful: {resp.statistics.successful}, Unsuccessful: {resp.statistics.unsuccessful} "
|
|
611
|
+
f"in {resp.statistics.time_ms}ms"
|
|
612
|
+
)
|
|
583
613
|
|
|
584
614
|
# Check if the response is invalid and if the envelope had any validation issues. If not, then we have a
|
|
585
615
|
# valid base response so return True. Otherwise, return False.
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
)
|
|
616
|
+
is_invalid = resp.statistics is not None and resp.statistics.invalid
|
|
617
|
+
return not is_invalid and self._verify_response_common(config, type(resp.envelope), resp.envelope_validation)
|
|
589
618
|
|
|
590
619
|
def _verify_messages(self, config: EndpointConfiguration, resp: BaseResponse[E]) -> None:
|
|
591
620
|
"""Verify the messages in the given response.
|
|
@@ -595,12 +624,12 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
595
624
|
resp (BaseResponse): The response to verify.
|
|
596
625
|
"""
|
|
597
626
|
for path, messages in resp.messages.items():
|
|
598
|
-
for info in messages.information:
|
|
599
|
-
logger.info(f"{config.name} - {path}: {info
|
|
600
|
-
for warning in messages.warnings:
|
|
601
|
-
logger.warning(f"{config.name} - {path}: {warning
|
|
602
|
-
for error in messages.errors:
|
|
603
|
-
logger.error(f"{config.name} - {path}: {error
|
|
627
|
+
for info in messages.information: # type: ignore[union-attr]
|
|
628
|
+
logger.info(f"{config.name} - {path}: {info}")
|
|
629
|
+
for warning in messages.warnings: # type: ignore[union-attr]
|
|
630
|
+
logger.warning(f"{config.name} - {path}: {warning}")
|
|
631
|
+
for error in messages.errors: # type: ignore[union-attr]
|
|
632
|
+
logger.error(f"{config.name} - {path}: {error}")
|
|
604
633
|
|
|
605
634
|
def _verify_response_common(
|
|
606
635
|
self, config: EndpointConfiguration, payload_type: type, resp: ResponseCommon, index: Optional[int] = None
|
mms_client/services/market.py
CHANGED
|
@@ -37,11 +37,11 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
37
37
|
config = ServiceConfiguration(Interface.MI, Serializer(SchemaType.MARKET, "MarketData"))
|
|
38
38
|
|
|
39
39
|
@mms_endpoint(
|
|
40
|
-
"MarketQuery_ReserveRequirementQuery",
|
|
41
|
-
config,
|
|
42
|
-
RequestType.INFO,
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
name="MarketQuery_ReserveRequirementQuery",
|
|
41
|
+
service=config,
|
|
42
|
+
request_type=RequestType.INFO,
|
|
43
|
+
response_envelope_type=MarketSubmit,
|
|
44
|
+
response_data_type=ReserveRequirement,
|
|
45
45
|
)
|
|
46
46
|
def query_reserve_requirements(
|
|
47
47
|
self: ClientProto, request: ReserveRequirementQuery, days: int, date: Optional[Date] = None
|
|
@@ -66,7 +66,9 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
66
66
|
days=days,
|
|
67
67
|
)
|
|
68
68
|
|
|
69
|
-
@mms_endpoint(
|
|
69
|
+
@mms_endpoint(
|
|
70
|
+
name="MarketSubmit_OfferData", service=config, request_type=RequestType.MARKET, allowed_clients=[ClientType.BSP]
|
|
71
|
+
)
|
|
70
72
|
def put_offer(
|
|
71
73
|
self: ClientProto, request: OfferData, market_type: MarketType, days: int, date: Optional[Date] = None
|
|
72
74
|
) -> OfferData:
|
|
@@ -92,7 +94,9 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
92
94
|
days=days,
|
|
93
95
|
)
|
|
94
96
|
|
|
95
|
-
@mms_multi_endpoint(
|
|
97
|
+
@mms_multi_endpoint(
|
|
98
|
+
name="MarketSubmit_OfferData", service=config, request_type=RequestType.MARKET, allowed_clients=[ClientType.BSP]
|
|
99
|
+
)
|
|
96
100
|
def put_offers(
|
|
97
101
|
self: ClientProto, requests: List[OfferData], market_type: MarketType, days: int, date: Optional[Date] = None
|
|
98
102
|
) -> List[OfferData]:
|
|
@@ -119,7 +123,11 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
119
123
|
)
|
|
120
124
|
|
|
121
125
|
@mms_multi_endpoint(
|
|
122
|
-
"MarketQuery_OfferQuery",
|
|
126
|
+
name="MarketQuery_OfferQuery",
|
|
127
|
+
service=config,
|
|
128
|
+
request_type=RequestType.MARKET,
|
|
129
|
+
response_envelope_type=MarketSubmit,
|
|
130
|
+
response_data_type=OfferData,
|
|
123
131
|
)
|
|
124
132
|
def query_offers(self: ClientProto, request: OfferQuery, days: int, date: Optional[Date] = None) -> List[OfferData]:
|
|
125
133
|
"""Query the MMS server for offers.
|
|
@@ -142,7 +150,12 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
142
150
|
days=days,
|
|
143
151
|
)
|
|
144
152
|
|
|
145
|
-
@mms_endpoint(
|
|
153
|
+
@mms_endpoint(
|
|
154
|
+
name="MarketCancel_OfferCancel",
|
|
155
|
+
service=config,
|
|
156
|
+
request_type=RequestType.MARKET,
|
|
157
|
+
allowed_clients=[ClientType.BSP],
|
|
158
|
+
)
|
|
146
159
|
def cancel_offer(
|
|
147
160
|
self: ClientProto, request: OfferCancel, market_type: MarketType, days: int, date: Optional[Date] = None
|
|
148
161
|
) -> OfferCancel:
|
|
@@ -168,7 +181,12 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
168
181
|
days=days,
|
|
169
182
|
)
|
|
170
183
|
|
|
171
|
-
@mms_endpoint(
|
|
184
|
+
@mms_endpoint(
|
|
185
|
+
name="MarketQuery_AwardResultsQuery",
|
|
186
|
+
service=config,
|
|
187
|
+
request_type=RequestType.MARKET,
|
|
188
|
+
response_data_type=AwardResponse,
|
|
189
|
+
)
|
|
172
190
|
def query_awards(self: ClientProto, request: AwardQuery, days: int, date: Optional[Date] = None) -> AwardResponse:
|
|
173
191
|
"""Query the MMS server for award results.
|
|
174
192
|
|
|
@@ -10,7 +10,6 @@ from mms_client.services.base import ServiceConfiguration
|
|
|
10
10
|
from mms_client.services.base import mms_endpoint
|
|
11
11
|
from mms_client.services.base import mms_multi_endpoint
|
|
12
12
|
from mms_client.types.registration import QueryAction
|
|
13
|
-
from mms_client.types.registration import QueryType
|
|
14
13
|
from mms_client.types.registration import RegistrationQuery
|
|
15
14
|
from mms_client.types.registration import RegistrationSubmit
|
|
16
15
|
from mms_client.types.resource import ResourceData
|
|
@@ -31,7 +30,12 @@ class RegistrationClientMixin: # pylint: disable=unused-argument
|
|
|
31
30
|
# The configuration for the registration service
|
|
32
31
|
config = ServiceConfiguration(Interface.MI, Serializer(SchemaType.REGISTRATION, "RegistrationData"))
|
|
33
32
|
|
|
34
|
-
@mms_endpoint(
|
|
33
|
+
@mms_endpoint(
|
|
34
|
+
name="RegistrationSubmit_Resource",
|
|
35
|
+
service=config,
|
|
36
|
+
request_type=RequestType.REGISTRATION,
|
|
37
|
+
allowed_clients=[ClientType.BSP],
|
|
38
|
+
)
|
|
35
39
|
def put_resource(self: ClientProto, request: ResourceData) -> ResourceData:
|
|
36
40
|
"""Submit a new resource to the MMS server.
|
|
37
41
|
|
|
@@ -51,12 +55,12 @@ class RegistrationClientMixin: # pylint: disable=unused-argument
|
|
|
51
55
|
return RegistrationSubmit() # type: ignore[return-value]
|
|
52
56
|
|
|
53
57
|
@mms_multi_endpoint(
|
|
54
|
-
"RegistrationQuery_Resource",
|
|
55
|
-
config,
|
|
56
|
-
RequestType.REGISTRATION,
|
|
58
|
+
name="RegistrationQuery_Resource",
|
|
59
|
+
service=config,
|
|
60
|
+
request_type=RequestType.REGISTRATION,
|
|
57
61
|
allowed_clients=[ClientType.BSP, ClientType.TSO],
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
response_envelope_type=RegistrationSubmit,
|
|
63
|
+
response_data_type=ResourceData,
|
|
60
64
|
)
|
|
61
65
|
def query_resources(
|
|
62
66
|
self: ClientProto, request: ResourceQuery, action: QueryAction, date: Optional[Date] = None
|
|
@@ -79,6 +83,5 @@ class RegistrationClientMixin: # pylint: disable=unused-argument
|
|
|
79
83
|
# Inject our parameters into the query and return it.
|
|
80
84
|
return RegistrationQuery( # type: ignore[return-value]
|
|
81
85
|
action=action,
|
|
82
|
-
|
|
83
|
-
date=date or Date.today(),
|
|
86
|
+
date=date,
|
|
84
87
|
)
|
mms_client/services/report.py
CHANGED
|
@@ -1,18 +1,161 @@
|
|
|
1
1
|
"""Contains the client layer for making report requests to the MMS server."""
|
|
2
2
|
|
|
3
3
|
from logging import getLogger
|
|
4
|
+
from typing import Dict
|
|
5
|
+
from typing import List
|
|
6
|
+
from typing import Type
|
|
7
|
+
from typing import TypeVar
|
|
4
8
|
|
|
9
|
+
from mms_client.services.base import ClientProto
|
|
5
10
|
from mms_client.services.base import ServiceConfiguration
|
|
11
|
+
from mms_client.services.base import mms_endpoint
|
|
12
|
+
from mms_client.services.base import mms_multi_endpoint
|
|
13
|
+
from mms_client.types.base import Response
|
|
14
|
+
from mms_client.types.report import ApplicationType
|
|
15
|
+
from mms_client.types.report import BSPResourceListItem
|
|
16
|
+
from mms_client.types.report import ListReportRequest
|
|
17
|
+
from mms_client.types.report import ListReportResponse
|
|
18
|
+
from mms_client.types.report import NewReportRequest
|
|
19
|
+
from mms_client.types.report import NewReportResponse
|
|
20
|
+
from mms_client.types.report import OutboundData
|
|
21
|
+
from mms_client.types.report import ReportBase
|
|
22
|
+
from mms_client.types.report import ReportDownloadRequestTrnID
|
|
23
|
+
from mms_client.types.report import ReportLineBase
|
|
24
|
+
from mms_client.types.transport import RequestType
|
|
6
25
|
from mms_client.utils.serialization import SchemaType
|
|
7
26
|
from mms_client.utils.serialization import Serializer
|
|
27
|
+
from mms_client.utils.web import ClientType
|
|
8
28
|
from mms_client.utils.web import Interface
|
|
9
29
|
|
|
10
30
|
# Set the default logger for the MMS client
|
|
11
31
|
logger = getLogger(__name__)
|
|
12
32
|
|
|
13
33
|
|
|
34
|
+
# The type variable we'll use to represent report line items
|
|
35
|
+
R = TypeVar("R", bound=ReportLineBase)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def attach_transaction_id(
|
|
39
|
+
resp: Response[ReportBase, NewReportResponse],
|
|
40
|
+
attachments: Dict[str, bytes], # pylint: disable=unused-argument
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Attach the transaction ID to the response.
|
|
43
|
+
|
|
44
|
+
Arguments:
|
|
45
|
+
resp (Response[ReportBase, NewReportResponse]): The MMS response.
|
|
46
|
+
attachments (Dict[str, bytes]): Attachements to the response.
|
|
47
|
+
"""
|
|
48
|
+
if resp.data and resp.statistics is not None:
|
|
49
|
+
resp.data.transaction_id = resp.statistics.transaction_id or ""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def report_getter_factory(config: ServiceConfiguration, resp_data_type: Type[R], allowed_clients: List[ClientType]):
|
|
53
|
+
"""Create a function for getting a report with a transaction ID.
|
|
54
|
+
|
|
55
|
+
Arguments:
|
|
56
|
+
config (ServiceConfiguration): The configuration for the report service.
|
|
57
|
+
resp_data_type (Type[R]): The type of report data to return.
|
|
58
|
+
allowed_clients (List[ClientType]): The allowed client types for the report service.
|
|
59
|
+
|
|
60
|
+
Returns: A function for getting a report with a transaction ID.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
@mms_multi_endpoint(
|
|
64
|
+
name="ReportDownloadRequestTrnID",
|
|
65
|
+
service=config,
|
|
66
|
+
request_type=RequestType.REPORT,
|
|
67
|
+
allowed_clients=allowed_clients,
|
|
68
|
+
response_envelope_type=OutboundData,
|
|
69
|
+
response_data_type=resp_data_type,
|
|
70
|
+
serializer=Serializer(SchemaType.REPORT_RESPONSE, "OutboundData"),
|
|
71
|
+
for_report=True,
|
|
72
|
+
)
|
|
73
|
+
def get_report_with_transaction_id(
|
|
74
|
+
self: ClientProto,
|
|
75
|
+
request: ReportDownloadRequestTrnID, # pylint: disable=unused-argument
|
|
76
|
+
) -> List[resp_data_type]: # type: ignore[valid-type]
|
|
77
|
+
"""Request download of a report with a transaction ID.
|
|
78
|
+
|
|
79
|
+
This endpoint is accessible to any client, but may be rejected depending on the type of report being requested.
|
|
80
|
+
|
|
81
|
+
Arguments:
|
|
82
|
+
self (ClientProto): The client to use for making the request.
|
|
83
|
+
request (ReportDownloadRequestTrnID): The request to download the report.
|
|
84
|
+
|
|
85
|
+
Returns: The request to download the report.
|
|
86
|
+
"""
|
|
87
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
88
|
+
return ReportBase( # type: ignore[return-value]
|
|
89
|
+
application_type=ApplicationType.MARKET_REPORT,
|
|
90
|
+
participant=self.participant,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
return get_report_with_transaction_id
|
|
94
|
+
|
|
95
|
+
|
|
14
96
|
class ReportClientMixin: # pylint: disable=unused-argument
|
|
15
97
|
"""Report client for the MMS server."""
|
|
16
98
|
|
|
17
99
|
# The configuration for the report service
|
|
18
|
-
config = ServiceConfiguration(
|
|
100
|
+
config = ServiceConfiguration(
|
|
101
|
+
Interface.MI,
|
|
102
|
+
Serializer(SchemaType.REPORT, "MarketReport"),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
@mms_endpoint(
|
|
106
|
+
name="ReportCreateRequest",
|
|
107
|
+
service=config,
|
|
108
|
+
request_type=RequestType.REPORT,
|
|
109
|
+
response_envelope_type=ReportBase,
|
|
110
|
+
response_data_type=NewReportResponse,
|
|
111
|
+
for_report=True,
|
|
112
|
+
)
|
|
113
|
+
def create_report(self: ClientProto, request: NewReportRequest) -> NewReportResponse:
|
|
114
|
+
"""Request creation of a new report.
|
|
115
|
+
|
|
116
|
+
This endpoint is only accessible to BSPs.
|
|
117
|
+
|
|
118
|
+
Arguments:
|
|
119
|
+
request (NewReportRequest): The request to create the report.
|
|
120
|
+
|
|
121
|
+
Returns: The request to create the report.
|
|
122
|
+
"""
|
|
123
|
+
# If the client type is BSP, then we can't set the BSP name
|
|
124
|
+
if self.client_type == ClientType.BSP:
|
|
125
|
+
request.bsp_name = None
|
|
126
|
+
|
|
127
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
128
|
+
# Return the envelope and the callback function
|
|
129
|
+
return (
|
|
130
|
+
ReportBase( # type: ignore[return-value]
|
|
131
|
+
application_type=ApplicationType.MARKET_REPORT,
|
|
132
|
+
participant=self.participant,
|
|
133
|
+
),
|
|
134
|
+
attach_transaction_id,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
@mms_endpoint(
|
|
138
|
+
name="ReportListRequest",
|
|
139
|
+
service=config,
|
|
140
|
+
request_type=RequestType.REPORT,
|
|
141
|
+
response_envelope_type=ReportBase,
|
|
142
|
+
response_data_type=ListReportResponse,
|
|
143
|
+
for_report=True,
|
|
144
|
+
)
|
|
145
|
+
def list_reports(self: ClientProto, request: ListReportRequest) -> ListReportResponse:
|
|
146
|
+
"""Query the existing reports.
|
|
147
|
+
|
|
148
|
+
Arguments:
|
|
149
|
+
request (ListReportRequest): The request to query the reports.
|
|
150
|
+
|
|
151
|
+
Returns: A list of reports that match the query.
|
|
152
|
+
"""
|
|
153
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
154
|
+
# Return the envelope and the callback function
|
|
155
|
+
return ReportBase( # type: ignore[return-value]
|
|
156
|
+
application_type=ApplicationType.MARKET_REPORT,
|
|
157
|
+
participant=self.participant,
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Define the individual report getter functions here
|
|
161
|
+
list_bsp_resources = report_getter_factory(config, BSPResourceListItem, [ClientType.BSP])
|
mms_client/types/base.py
CHANGED
|
@@ -8,10 +8,12 @@ from typing import List
|
|
|
8
8
|
from typing import Optional
|
|
9
9
|
from typing import TypeVar
|
|
10
10
|
|
|
11
|
+
from lxml.etree import _Element as Element
|
|
11
12
|
from pydantic import PrivateAttr
|
|
12
13
|
from pydantic_extra_types.pendulum_dt import DateTime
|
|
13
14
|
from pydantic_xml import BaseXmlModel
|
|
14
15
|
from pydantic_xml import attr
|
|
16
|
+
from pydantic_xml import computed_element
|
|
15
17
|
from pydantic_xml import element
|
|
16
18
|
|
|
17
19
|
from mms_client.types.fields import transaction_id
|
|
@@ -34,17 +36,48 @@ class Message(BaseXmlModel):
|
|
|
34
36
|
code: str = attr(default="", name="Code", min_length=2, max_length=50, pattern=r"^[a-zA-Z_0-9\-]*$")
|
|
35
37
|
|
|
36
38
|
|
|
37
|
-
class Messages(BaseXmlModel, search_mode="unordered"):
|
|
39
|
+
class Messages(BaseXmlModel, search_mode="unordered", arbitrary_types_allowed=True):
|
|
38
40
|
"""Represents a collection of messages returned with a payload."""
|
|
39
41
|
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
+
# The raw information messages returned with a payload
|
|
43
|
+
information_raw: List[Element] = element(default=[], tag="Information", nillable=True, exclude=True)
|
|
42
44
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
+
# The raw warning messages returned with a payload
|
|
46
|
+
warnings_raw: List[Element] = element(default=[], tag="Warning", nillable=True, exclude=True)
|
|
45
47
|
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
+
# The raw error messages returned with a payload
|
|
49
|
+
errors_raw: List[Element] = element(default=[], tag="Error", nillable=True, exclude=True)
|
|
50
|
+
|
|
51
|
+
@computed_element
|
|
52
|
+
def information(self) -> List[str]:
|
|
53
|
+
"""Return the information messages."""
|
|
54
|
+
return self._parse_messages(self.information_raw)
|
|
55
|
+
|
|
56
|
+
@computed_element
|
|
57
|
+
def warnings(self) -> List[str]:
|
|
58
|
+
"""Return the warning messages."""
|
|
59
|
+
return self._parse_messages(self.warnings_raw)
|
|
60
|
+
|
|
61
|
+
@computed_element
|
|
62
|
+
def errors(self) -> List[str]:
|
|
63
|
+
"""Return the error messages."""
|
|
64
|
+
return self._parse_messages(self.errors_raw)
|
|
65
|
+
|
|
66
|
+
def _parse_messages(self, raw: List[Element]) -> List[str]:
|
|
67
|
+
"""Parse the messages from the XML tree.
|
|
68
|
+
|
|
69
|
+
Arguments:
|
|
70
|
+
raw (List[Element]): The raw XML tree to parse.
|
|
71
|
+
|
|
72
|
+
Returns: A list of message codes.
|
|
73
|
+
"""
|
|
74
|
+
messages = []
|
|
75
|
+
for item in raw:
|
|
76
|
+
if message := item.attrib.get("Code"):
|
|
77
|
+
messages.append(message)
|
|
78
|
+
else:
|
|
79
|
+
messages.append(item.text or "")
|
|
80
|
+
return messages
|
|
48
81
|
|
|
49
82
|
|
|
50
83
|
class ProcessingStatistics(BaseXmlModel):
|
|
@@ -87,7 +120,16 @@ class ResponseCommon(BaseXmlModel, search_mode="unordered"):
|
|
|
87
120
|
|
|
88
121
|
# The status of the validation check done on the element. This field is not required for requests, and will be
|
|
89
122
|
# populated in responses. For responses, the default value is "NOT_DONE".
|
|
90
|
-
|
|
123
|
+
base_validation: Optional[ValidationStatus] = attr(default=None, name="Validation")
|
|
124
|
+
|
|
125
|
+
# The status of the validation check done on the element, specifically for reports. This field is not required for
|
|
126
|
+
# requests, and will be populated in responses. For responses, the default value is "NOT_DONE".
|
|
127
|
+
report_validation: Optional[ValidationStatus] = attr(default=None, name="ValidationStatus")
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def validation(self) -> ValidationStatus:
|
|
131
|
+
"""Return the validation status of the element."""
|
|
132
|
+
return self.base_validation or self.report_validation or ValidationStatus.NOT_DONE
|
|
91
133
|
|
|
92
134
|
|
|
93
135
|
class Payload(BaseXmlModel, search_mode="unordered"):
|
|
@@ -111,6 +153,7 @@ class SchemaType(Enum):
|
|
|
111
153
|
|
|
112
154
|
MARKET = "mi-market.xsd"
|
|
113
155
|
REPORT = "mi-report.xsd"
|
|
156
|
+
REPORT_RESPONSE = "mi-outbnd-reports.xsd"
|
|
114
157
|
REGISTRATION = "mpr.xsd"
|
|
115
158
|
OMI = "omi.xsd"
|
|
116
159
|
|
|
@@ -124,7 +167,7 @@ class BaseResponse(BaseXmlModel, Generic[E], tag="BaseResponse"):
|
|
|
124
167
|
|
|
125
168
|
# The processing statistics returned with the payload. This will not be present in requests, and will be populated
|
|
126
169
|
# in responses.
|
|
127
|
-
statistics: ProcessingStatistics = element(tag="ProcessingStatistics")
|
|
170
|
+
statistics: Optional[ProcessingStatistics] = element(default=None, tag="ProcessingStatistics")
|
|
128
171
|
|
|
129
172
|
# The request payload, containing the request data
|
|
130
173
|
_envelope: E = PrivateAttr()
|