mms-client 1.5.0__py3-none-any.whl → 1.6.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.
@@ -28,6 +28,7 @@ from mms_client.types.transport import RequestType
28
28
  from mms_client.types.transport import ResponseDataType
29
29
  from mms_client.utils.errors import AudienceError
30
30
  from mms_client.utils.errors import MMSClientError
31
+ from mms_client.utils.errors import MMSServerError
31
32
  from mms_client.utils.errors import MMSValidationError
32
33
  from mms_client.utils.serialization import Serializer
33
34
  from mms_client.utils.web import ClientType
@@ -459,7 +460,11 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
459
460
  MMSClientError: If the response is not valid.
460
461
  """
461
462
  # Verify that the response is in the correct format. If it's not, raise an error.
462
- if resp.data_type != ResponseDataType.XML:
463
+ # NOTE: We're disabling the no-else-raise rule here because both comparisons are on the same enum so if one is
464
+ # removed then the other will raise an error. This is a false positive.
465
+ if resp.data_type == ResponseDataType.TXT: # pylint: disable=no-else-raise
466
+ raise MMSServerError(config.name, resp.payload.decode("UTF-8"))
467
+ elif resp.data_type != ResponseDataType.XML:
463
468
  raise MMSClientError(
464
469
  config.name,
465
470
  f"Invalid MMS response data type: {resp.data_type.name}. Only XML is supported.",
@@ -18,6 +18,8 @@ from mms_client.types.market import MarketType
18
18
  from mms_client.types.offer import OfferCancel
19
19
  from mms_client.types.offer import OfferData
20
20
  from mms_client.types.offer import OfferQuery
21
+ from mms_client.types.reserve import ReserveRequirement
22
+ from mms_client.types.reserve import ReserveRequirementQuery
21
23
  from mms_client.types.transport import RequestType
22
24
  from mms_client.utils.serialization import SchemaType
23
25
  from mms_client.utils.serialization import Serializer
@@ -34,6 +36,35 @@ class MarketClientMixin: # pylint: disable=unused-argument
34
36
  # The configuration for the market service
35
37
  config = ServiceConfiguration(Interface.MI, Serializer(SchemaType.MARKET, "MarketData"))
36
38
 
39
+ @mms_endpoint(
40
+ "MarketQuery_ReserveRequirementQuery",
41
+ config,
42
+ RequestType.INFO,
43
+ resp_envelope_type=MarketSubmit,
44
+ resp_data_type=ReserveRequirement,
45
+ )
46
+ def query_reserve_requirements(
47
+ self: ClientProto, request: ReserveRequirementQuery, date: Optional[Date] = None
48
+ ) -> List[ReserveRequirement]:
49
+ """Query the MMS server for reserve requirements.
50
+
51
+ This endpoint is accessible to all client types.
52
+
53
+ Arguments:
54
+ request (ReserveRequirementQuery): The query to submit to the MMS server.
55
+ date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults
56
+ to the current date.
57
+
58
+ Returns: A list of reserve requirements that match the query.
59
+ """
60
+ # NOTE: The return type does not match the method definition but the decorator will return the correct type
61
+ return MarketQuery( # type: ignore[return-value]
62
+ date=date or Date.today(),
63
+ participant=self.participant,
64
+ user=self.user,
65
+ days=1,
66
+ )
67
+
37
68
  @mms_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, [ClientType.BSP])
38
69
  def put_offer(
39
70
  self: ClientProto, request: OfferData, market_type: MarketType, days: int, date: Optional[Date] = None
@@ -124,6 +155,8 @@ class MarketClientMixin: # pylint: disable=unused-argument
124
155
  days (int): The number of days ahead for which the data is being cancelled.
125
156
  date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to the
126
157
  current date.
158
+
159
+ Returns: Data identifying the offer that was cancelled.
127
160
  """
128
161
  # NOTE: The return type does not match the method definition but the decorator will return the correct type
129
162
  return MarketCancel( # type: ignore[return-value]
@@ -0,0 +1,71 @@
1
+ """Contains objects for MMS reserve requirements."""
2
+
3
+ from typing import List
4
+ from typing import Optional
5
+
6
+ from pydantic_extra_types.pendulum_dt import DateTime
7
+ from pydantic_xml import attr
8
+ from pydantic_xml import element
9
+
10
+ from mms_client.types.base import Payload
11
+ from mms_client.types.enums import AreaCode
12
+ from mms_client.types.enums import Direction
13
+ from mms_client.types.fields import power_positive
14
+ from mms_client.types.market import MarketType
15
+
16
+
17
+ class Requirement(Payload):
18
+ """Represents a reserve requirement."""
19
+
20
+ # The start block of the requirement
21
+ start: DateTime = attr(name="StartTime")
22
+
23
+ # The end block of the requirement
24
+ end: DateTime = attr(name="EndTime")
25
+
26
+ # The direction of the requirement
27
+ direction: Direction = attr(name="Direction")
28
+
29
+ # The primary reserve quantity in kW
30
+ primary_qty_kw: Optional[int] = power_positive("PrimaryReserveQuantityInKw", True)
31
+
32
+ # The first secondary reserve quantity in kW
33
+ secondary_1_qty_kw: Optional[int] = power_positive("Secondary1ReserveQuantityInKw", True)
34
+
35
+ # The second secondary reserve quantity in kW
36
+ secondary_2_qty_kw: Optional[int] = power_positive("Secondary2ReserveQuantityInKw", True)
37
+
38
+ # The first tertiary reserve quantity in kW
39
+ tertiary_1_qty_kw: Optional[int] = power_positive("Tertiary1ReserveQuantityInKw", True)
40
+
41
+ # The second tertiary reserve quantity in kW
42
+ tertiary_2_qty_kw: Optional[int] = power_positive("Tertiary2ReserveQuantityInKw", True)
43
+
44
+ # The minimum reserve of compound primary and secondary 1 in kW
45
+ primary_secondary_1_qty_kw: Optional[int] = power_positive("CompoundPriSec1ReserveQuantityInKw", True)
46
+
47
+ # The minimum reserve of compound primary and secondary 2 in kW
48
+ primary_secondary_2_qty_kw: Optional[int] = power_positive("CompoundPriSec2ReserveQuantityInKw", True)
49
+
50
+ # The minimum reserve of compound primary and tertiary 1 in kW
51
+ primary_tertiary_1_qty_kw: Optional[int] = power_positive("CompoundPriTer1ReserveQuantityInKw", True)
52
+
53
+
54
+ class ReserveRequirement(Payload):
55
+ """Represents a set of reserve requirements."""
56
+
57
+ # The area for which the reserve requirement applies
58
+ area: AreaCode = attr(name="Area")
59
+
60
+ # The requirements associated with the area
61
+ requirements: List[Requirement] = element(tag="Requirement", min_length=1)
62
+
63
+
64
+ class ReserveRequirementQuery(Payload):
65
+ """Represents a request to query reserve requirements."""
66
+
67
+ # The market type for which to query reserve requirements
68
+ market_type: MarketType = attr(name="MarketType")
69
+
70
+ # The area for which to query reserve requirements
71
+ area: Optional[AreaCode] = attr(default=None, name="Area")
@@ -7,6 +7,7 @@ from logging import getLogger
7
7
  from lxml.etree import _Element as Element
8
8
  from lxml.etree import tostring
9
9
  from zeep import Plugin
10
+ from zeep.wsdl.definitions import Operation
10
11
 
11
12
  # Set the default logger for the MMS client
12
13
  logger = getLogger(__name__)
@@ -15,7 +16,7 @@ logger = getLogger(__name__)
15
16
  class AuditPlugin(ABC, Plugin):
16
17
  """Base class for audit plugins."""
17
18
 
18
- def egress(self, envelope: Element, http_headers: dict, operation, binding_options):
19
+ def egress(self, envelope: Element, http_headers: dict, operation: Operation, binding_options):
19
20
  """Handle the MMS request before it is sent.
20
21
 
21
22
  Arguments are the same as in the egress method of the Plugin class.
@@ -25,10 +26,10 @@ class AuditPlugin(ABC, Plugin):
25
26
  dict: The HTTP headers to send.
26
27
  """
27
28
  data = tostring(envelope, encoding="UTF-8", xml_declaration=True)
28
- self.audit_request(data)
29
+ self.audit_request(operation.name, data)
29
30
  return envelope, http_headers
30
31
 
31
- def ingress(self, envelope: Element, http_headers: dict, operation):
32
+ def ingress(self, envelope: Element, http_headers: dict, operation: Operation):
32
33
  """Handle the MMS response before it is processed.
33
34
 
34
35
  Arguments are the same as in the ingress method of the Plugin class.
@@ -38,21 +39,23 @@ class AuditPlugin(ABC, Plugin):
38
39
  dict: The HTTP headers to process.
39
40
  """
40
41
  data = tostring(envelope, encoding="UTF-8", xml_declaration=True)
41
- self.audit_response(data)
42
+ self.audit_response(operation.name, data)
42
43
  return envelope, http_headers
43
44
 
44
45
  @abstractmethod
45
- def audit_request(self, mms_request: bytes) -> None:
46
+ def audit_request(self, operation: str, mms_request: bytes) -> None:
46
47
  """Audit an MMS request.
47
48
 
48
49
  Arguments:
50
+ operation (str): The SOAP operation being called.
49
51
  mms_request (bytes): The MMS request XML to audit.
50
52
  """
51
53
 
52
54
  @abstractmethod
53
- def audit_response(self, mms_response: bytes) -> None:
55
+ def audit_response(self, operation: str, mms_response: bytes) -> None:
54
56
  """Audit an MMS response.
55
57
 
56
58
  Arguments:
59
+ operation (str): The SOAP operation being called.
57
60
  mms_response (bytes): The MMS response XML to audit.
58
61
  """
@@ -38,6 +38,21 @@ class AudienceError(ValueError):
38
38
  super().__init__(self.message)
39
39
 
40
40
 
41
+ class MMSServerError(RuntimeError):
42
+ """Error raised when the MMS server returns an error."""
43
+
44
+ def __init__(self, method: str, message: str):
45
+ """Initialize the error.
46
+
47
+ Arguments:
48
+ method (str): The method that caused the error.
49
+ message (str): The error message.
50
+ """
51
+ super().__init__(f"{method}: {message}")
52
+ self.message = message
53
+ self.method = method
54
+
55
+
41
56
  class MMSClientError(RuntimeError):
42
57
  """Base class for MMS client errors."""
43
58
 
mms_client/utils/web.py CHANGED
@@ -200,7 +200,13 @@ class ZWrapper:
200
200
 
201
201
  @on_exception(expo, TransportError, max_tries=3, giveup=fatal_code, logger=logger) # type: ignore[arg-type]
202
202
  def submit(self, req: MmsRequest) -> MmsResponse:
203
- """Submit the given request to the MMS server and return the response."""
203
+ """Submit the given request to the MMS server and return the response.
204
+
205
+ Arguments:
206
+ req (MmsRequest): The MMS request to submit.
207
+
208
+ Returns: The MMS response.
209
+ """
204
210
  try:
205
211
  logger.debug(f"Submitting MMS request request to {self._interface.name} service")
206
212
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mms-client
3
- Version: 1.5.0
3
+ Version: 1.6.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
@@ -209,6 +209,7 @@ This same input allows for the user to create their own plugins and add them to
209
209
 
210
210
  # Completeness
211
211
  This client is not complete. Currently, it supports the following endpoints:
212
+ - MarketQuery_ReserveRequirementQuery
212
213
  - MarketSubmit_OfferData
213
214
  - MarketQuery_OfferQuery
214
215
  - MarketCancel_OfferCancel
@@ -12,8 +12,8 @@ mms_client/security/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSu
12
12
  mms_client/security/certs.py,sha256=kNCUFmy18YIxkWKu3mdMmlxmHdft4a6BvtyJ46rA9I4,1489
13
13
  mms_client/security/crypto.py,sha256=M7aIllM3_ZwZgm9nH6QQ6Ig14XCAd6e6WGwqqUbbI1Q,2149
14
14
  mms_client/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- mms_client/services/base.py,sha256=kI--NGcxYP9-cF5PHolSGOT-XtBFcjmlsRLxpd48GVE,25897
16
- mms_client/services/market.py,sha256=XCD49lILwf7_hpF54MpQFsO-NBK4PwmNznsoMyg23mo,7684
15
+ mms_client/services/base.py,sha256=DavXAfWqSqcxfgbAFDj-9nj2DYL5YTMTPWHUM8aaL9s,26309
16
+ mms_client/services/market.py,sha256=7eVqbgkfSip-GAAFnjetFbtmFILOGI6Gb8YISTiMh6w,9031
17
17
  mms_client/services/omi.py,sha256=UG1zYkFz0sFsEbhE6P0CLoAOZZOyEshkZ_b7D_e3CjQ,626
18
18
  mms_client/services/registration.py,sha256=ryj2WKJoBzRU1r6Svbl2MyGOG0H5aEYAK6HQWL9kYaA,3815
19
19
  mms_client/services/report.py,sha256=ZXYDaknPCnSYZI857QHbkzst1Pez_PrKFudVF9bQB7Q,642
@@ -25,14 +25,15 @@ mms_client/types/fields.py,sha256=pa5qvQVwEr8dh44IGHyYqgJYTYyTIeAjBW6CylXrkP0,14
25
25
  mms_client/types/market.py,sha256=IbXsH4Q5MJI-CEvGvZlzv2S36mX_Ea02U11Ik-NwSxQ,2706
26
26
  mms_client/types/offer.py,sha256=KosFiKRMnt7XwlLBUfjHUGHiWzrMJUPPhGQMxgdeepM,6791
27
27
  mms_client/types/registration.py,sha256=Nir73S3ffpk0O_fnTD2alFaqV1k67_8dcyyduXvPBI4,1381
28
+ mms_client/types/reserve.py,sha256=pLV47w_749EIVhj0tUuJdWdHBBEl0-v10oVioccgxnU,2667
28
29
  mms_client/types/resource.py,sha256=TQnY3JLHRgQhQrG6ISquw-BQgKSr8TGuqn9ItWxWz_w,65974
29
30
  mms_client/types/transport.py,sha256=DPjWs34UW915GkUCJWKuDZmsjS6mRdRXgcGISduN_Bc,4399
30
31
  mms_client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
31
- mms_client/utils/auditing.py,sha256=yb_8cdUeZkTCb2W48E0LLuJXv4m4SKjDH8jMX7rjNLk,1797
32
- mms_client/utils/errors.py,sha256=ovoJjrvoVkbMRhHcq3HR9p1uH_65QicvaGy_7wNv7JI,2579
32
+ mms_client/utils/auditing.py,sha256=JDcvNo4ul66xPtDeeocn568yIe8fhh-jM11MWP-Kfes,2057
33
+ mms_client/utils/errors.py,sha256=6k-NOjGZyTbTUISzN7B4JrmU2P8cwjpFFmFC7kJOQFQ,3005
33
34
  mms_client/utils/serialization.py,sha256=k0_fBm-yoRZV2AMiickSyauoDyA8i7uIPU6JjfQWx4Q,29638
34
- mms_client/utils/web.py,sha256=kguQ4uDFpfw2dly89WXukkvJ8RMKTDIafWGUIpmPPJo,10033
35
- mms_client-1.5.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
36
- mms_client-1.5.0.dist-info/METADATA,sha256=RrGO-fZkJP0aHFEYOdT74lvi3Syzp1x9OeyhqdSsabA,15987
37
- mms_client-1.5.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
38
- mms_client-1.5.0.dist-info/RECORD,,
35
+ mms_client/utils/web.py,sha256=-abdVxSi7c6xQYsZObbj0yXwGI5VWthr5KtWDyLBCM4,10156
36
+ mms_client-1.6.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
37
+ mms_client-1.6.0.dist-info/METADATA,sha256=FSz-AvooX7RCU5vZ8BC1nhXINBlF3EFhitTc3ZngIh8,16025
38
+ mms_client-1.6.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
39
+ mms_client-1.6.0.dist-info/RECORD,,