mms-client 1.7.0__tar.gz → 1.8.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 (42) hide show
  1. {mms_client-1.7.0 → mms_client-1.8.0}/PKG-INFO +6 -1
  2. {mms_client-1.7.0 → mms_client-1.8.0}/README.md +4 -0
  3. {mms_client-1.7.0 → mms_client-1.8.0}/pyproject.toml +2 -2
  4. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/services/base.py +81 -52
  5. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/services/market.py +28 -10
  6. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/services/registration.py +12 -9
  7. mms_client-1.8.0/src/mms_client/services/report.py +161 -0
  8. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/base.py +52 -9
  9. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/enums.py +34 -0
  10. mms_client-1.8.0/src/mms_client/types/report.py +474 -0
  11. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/resource.py +4 -34
  12. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/utils/serialization.py +111 -42
  13. mms_client-1.7.0/src/mms_client/services/report.py +0 -18
  14. {mms_client-1.7.0 → mms_client-1.8.0}/LICENSE +0 -0
  15. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/__init__.py +0 -0
  16. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/client.py +0 -0
  17. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/py.typed +0 -0
  18. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/schemas/wsdl/mi-web-service-jbms.wsdl +0 -0
  19. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/schemas/wsdl/omi-web-service.wsdl +0 -0
  20. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/schemas/xsd/mi-market.xsd +0 -0
  21. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/schemas/xsd/mi-outbnd-reports.xsd +0 -0
  22. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/schemas/xsd/mi-report.xsd +0 -0
  23. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/schemas/xsd/mpr.xsd +0 -0
  24. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/schemas/xsd/omi.xsd +0 -0
  25. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/security/__init__.py +0 -0
  26. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/security/certs.py +0 -0
  27. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/security/crypto.py +0 -0
  28. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/services/__init__.py +0 -0
  29. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/services/omi.py +0 -0
  30. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/__init__.py +0 -0
  31. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/award.py +0 -0
  32. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/fields.py +0 -0
  33. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/market.py +0 -0
  34. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/offer.py +0 -0
  35. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/registration.py +0 -0
  36. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/reserve.py +0 -0
  37. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/types/transport.py +0 -0
  38. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/utils/__init__.py +0 -0
  39. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/utils/auditing.py +0 -0
  40. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/utils/errors.py +0 -0
  41. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/utils/multipart_transport.py +0 -0
  42. {mms_client-1.7.0 → mms_client-1.8.0}/src/mms_client/utils/web.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mms-client
3
- Version: 1.7.0
3
+ Version: 1.8.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
@@ -24,6 +24,7 @@ Requires-Dist: lxml (>=5.1.0,<6.0.0)
24
24
  Requires-Dist: pendulum (>=3.0.0,<4.0.0)
25
25
  Requires-Dist: pycryptodomex (>=3.20.0,<4.0.0)
26
26
  Requires-Dist: pydantic (>=2.6.3,<3.0.0)
27
+ Requires-Dist: pydantic-extra-types (>=2.7.0,<3.0.0)
27
28
  Requires-Dist: pydantic-xml (>=2.9.0,<3.0.0)
28
29
  Requires-Dist: requests (>=2.31.0,<3.0.0)
29
30
  Requires-Dist: requests-pkcs12 (>=1.24,<2.0)
@@ -220,6 +221,10 @@ This client is not complete. Currently, it supports the following endpoints:
220
221
  - MarketQuery_AwardResultsQuery
221
222
  - RegistrationSubmit_Resource
222
223
  - RegistrationQuery_Resource
224
+ - ReportCreateRequest
225
+ - ReportListRequest
226
+ - ReportDownloadRequestTrnID
227
+ - BSP_ResourceList
223
228
 
224
229
  We can add support for additional endpoints as time goes on, and independent contribution is, of course, welcome. However, support for attachments is currently limited because none of the endpoints we support currently require them. We have implemented attachment support up to the client level, but we haven't developed an architecture for submitting them through an endpoint yet.
225
230
 
@@ -187,6 +187,10 @@ This client is not complete. Currently, it supports the following endpoints:
187
187
  - MarketQuery_AwardResultsQuery
188
188
  - RegistrationSubmit_Resource
189
189
  - RegistrationQuery_Resource
190
+ - ReportCreateRequest
191
+ - ReportListRequest
192
+ - ReportDownloadRequestTrnID
193
+ - BSP_ResourceList
190
194
 
191
195
  We can add support for additional endpoints as time goes on, and independent contribution is, of course, welcome. However, support for attachments is currently limited because none of the endpoints we support currently require them. We have implemented attachment support up to the client level, but we haven't developed an architecture for submitting them through an endpoint yet.
192
196
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "mms_client"
3
- version = "v1.7.0"
3
+ version = "v1.8.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"
@@ -33,6 +33,7 @@ pydantic-xml = "^2.9.0"
33
33
  lxml = "^5.1.0"
34
34
  backoff = "^2.2.1"
35
35
  pycryptodomex = "^3.20.0"
36
+ pydantic-extra-types = "^2.7.0"
36
37
 
37
38
  [tool.poetry.group.dev.dependencies]
38
39
  black = "^24.2.0"
@@ -49,7 +50,6 @@ pytest-mock = "^3.12.0"
49
50
  mock = "^5.1.0"
50
51
  lxml-stubs = "^0.5.1"
51
52
  pyfakefs = "^5.3.5"
52
- pydantic-extra-types = "^2.6.0"
53
53
  responses = "^0.25.0"
54
54
  types-urllib3 = "^1.26.25.14"
55
55
 
@@ -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
- resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then the
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
- resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
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(name, allowed_clients, service, request_type, resp_envelope_type, resp_data_type)
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
- envelope = func(self, *args, **kwargs)
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, _ = self.request_one(envelope, args[0], config)
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
- resp_envelope_type (Type[E]): The type of payload to expect in the response. If this is not provided, then
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
- resp_data_type (Type[P]): The type of data to expect in the response. If this is not provided, then the
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(name, allowed_clients, service, request_type, resp_envelope_type, resp_data_type)
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
- envelope = func(self, *args, **kwargs)
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, _ = self.request_many(envelope, args[0], config)
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
- data: Response[E, P] = config.service.serializer.deserialize(resp.payload, envelope_type, data_type)
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(envelope, payload, data_type) # type: ignore[arg-type]
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
- data: MultiResponse[E, P] = config.service.serializer.deserialize_multi(
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
- logger.info(
578
- f"{config.name} ({resp.statistics.timestamp_xml}): Recieved {resp.statistics.received}, "
579
- f"Valid: {resp.statistics.valid}, Invalid: {resp.statistics.invalid}, "
580
- f"Successful: {resp.statistics.successful}, Unsuccessful: {resp.statistics.unsuccessful} "
581
- f"in {resp.statistics.time_ms}ms"
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
- return resp.statistics.invalid == 0 and self._verify_response_common(
587
- config, type(resp.envelope), resp.envelope_validation
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.code}")
600
- for warning in messages.warnings:
601
- logger.warning(f"{config.name} - {path}: {warning.code}")
602
- for error in messages.errors:
603
- logger.error(f"{config.name} - {path}: {error.code}")
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
@@ -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
- resp_envelope_type=MarketSubmit,
44
- resp_data_type=ReserveRequirement,
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("MarketSubmit_OfferData", config, RequestType.INFO, [ClientType.BSP])
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("MarketSubmit_OfferData", config, RequestType.INFO, [ClientType.BSP])
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", config, RequestType.INFO, resp_envelope_type=MarketSubmit, resp_data_type=OfferData
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("MarketCancel_OfferCancel", config, RequestType.INFO, [ClientType.BSP])
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("MarketQuery_AwardResultsQuery", config, RequestType.MARKET, resp_data_type=AwardResponse)
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("RegistrationSubmit_Resource", config, RequestType.REGISTRATION, [ClientType.BSP])
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
- resp_envelope_type=RegistrationSubmit,
59
- resp_data_type=ResourceData,
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
- query_type=QueryType.TRADE,
83
- date=date or Date.today(),
86
+ date=date,
84
87
  )
@@ -0,0 +1,161 @@
1
+ """Contains the client layer for making report requests to the MMS server."""
2
+
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
8
+
9
+ from mms_client.services.base import ClientProto
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
25
+ from mms_client.utils.serialization import SchemaType
26
+ from mms_client.utils.serialization import Serializer
27
+ from mms_client.utils.web import ClientType
28
+ from mms_client.utils.web import Interface
29
+
30
+ # Set the default logger for the MMS client
31
+ logger = getLogger(__name__)
32
+
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
+
96
+ class ReportClientMixin: # pylint: disable=unused-argument
97
+ """Report client for the MMS server."""
98
+
99
+ # The configuration for the report service
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])