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.
- mms_client/services/base.py +6 -1
- mms_client/services/market.py +33 -0
- mms_client/types/reserve.py +71 -0
- mms_client/utils/auditing.py +9 -6
- mms_client/utils/errors.py +15 -0
- mms_client/utils/web.py +7 -1
- {mms_client-1.5.0.dist-info → mms_client-1.6.0.dist-info}/METADATA +2 -1
- {mms_client-1.5.0.dist-info → mms_client-1.6.0.dist-info}/RECORD +10 -9
- {mms_client-1.5.0.dist-info → mms_client-1.6.0.dist-info}/LICENSE +0 -0
- {mms_client-1.5.0.dist-info → mms_client-1.6.0.dist-info}/WHEEL +0 -0
mms_client/services/base.py
CHANGED
|
@@ -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
|
|
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.",
|
mms_client/services/market.py
CHANGED
|
@@ -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")
|
mms_client/utils/auditing.py
CHANGED
|
@@ -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
|
"""
|
mms_client/utils/errors.py
CHANGED
|
@@ -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.
|
|
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=
|
|
16
|
-
mms_client/services/market.py,sha256=
|
|
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=
|
|
32
|
-
mms_client/utils/errors.py,sha256=
|
|
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
|
|
35
|
-
mms_client-1.
|
|
36
|
-
mms_client-1.
|
|
37
|
-
mms_client-1.
|
|
38
|
-
mms_client-1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|