mms-client 1.0.5__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/__init__.py +0 -0
- mms_client/client.py +14 -0
- mms_client/py.typed +0 -0
- mms_client/schemas/wsdl/mi-web-service-jbms.wsdl +276 -0
- mms_client/schemas/wsdl/omi-web-service.wsdl +262 -0
- mms_client/schemas/xsd/mi-market.xsd +2395 -0
- mms_client/schemas/xsd/mi-outbnd-reports.xsd +1489 -0
- mms_client/schemas/xsd/mi-report.xsd +379 -0
- mms_client/schemas/xsd/mpr.xsd +1817 -0
- mms_client/schemas/xsd/omi.xsd +793 -0
- mms_client/security/__init__.py +0 -0
- mms_client/security/certs.py +44 -0
- mms_client/security/crypto.py +57 -0
- mms_client/services/__init__.py +0 -0
- mms_client/services/base.py +591 -0
- mms_client/services/market.py +107 -0
- mms_client/services/omi.py +13 -0
- mms_client/services/registration.py +13 -0
- mms_client/services/report.py +13 -0
- mms_client/types/__init__.py +0 -0
- mms_client/types/base.py +272 -0
- mms_client/types/enums.py +18 -0
- mms_client/types/fields.py +153 -0
- mms_client/types/market.py +61 -0
- mms_client/types/offer.py +163 -0
- mms_client/types/transport.py +130 -0
- mms_client/utils/__init__.py +0 -0
- mms_client/utils/errors.py +66 -0
- mms_client/utils/serialization.py +513 -0
- mms_client/utils/web.py +220 -0
- mms_client-1.0.5.dist-info/LICENSE +24 -0
- mms_client-1.0.5.dist-info/METADATA +202 -0
- mms_client-1.0.5.dist-info/RECORD +34 -0
- mms_client-1.0.5.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Contains objects for MMS offers."""
|
|
2
|
+
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import List
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from pydantic_core import PydanticUndefined
|
|
9
|
+
from pydantic_extra_types.pendulum_dt import DateTime
|
|
10
|
+
from pydantic_xml import attr
|
|
11
|
+
from pydantic_xml import element
|
|
12
|
+
|
|
13
|
+
from mms_client.types.base import Payload
|
|
14
|
+
from mms_client.types.enums import AreaCode
|
|
15
|
+
from mms_client.types.fields import company_short_name
|
|
16
|
+
from mms_client.types.fields import dr_patter_number
|
|
17
|
+
from mms_client.types.fields import operator_code
|
|
18
|
+
from mms_client.types.fields import participant
|
|
19
|
+
from mms_client.types.fields import power_positive
|
|
20
|
+
from mms_client.types.fields import price
|
|
21
|
+
from mms_client.types.fields import resource_name
|
|
22
|
+
from mms_client.types.fields import resource_short_name
|
|
23
|
+
from mms_client.types.fields import system_code
|
|
24
|
+
from mms_client.types.market import MarketType
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def offer_id(alias: str, optional: bool = False):
|
|
28
|
+
"""Create a field for an offer ID.
|
|
29
|
+
|
|
30
|
+
Arguments:
|
|
31
|
+
alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
|
|
32
|
+
to the JSON/XML key.
|
|
33
|
+
optional (bool): If True, the field will be optional with a default of None. If False, the field will be
|
|
34
|
+
required, with no default.
|
|
35
|
+
|
|
36
|
+
Returns: A Pydantic Field object for the offer ID.
|
|
37
|
+
"""
|
|
38
|
+
return attr(
|
|
39
|
+
default=None if optional else PydanticUndefined,
|
|
40
|
+
name=alias,
|
|
41
|
+
min_length=1,
|
|
42
|
+
max_length=30,
|
|
43
|
+
pattern=r"^[a-zA-Z0-9_-]*$",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class Direction(Enum):
|
|
48
|
+
"""Represents the reserve direction of the offer."""
|
|
49
|
+
|
|
50
|
+
SELL = "1" # Increasing the reserves (sell)
|
|
51
|
+
# Note: Support for the BUY direction was removed from the MMS API
|
|
52
|
+
# BUY = "2" # Decreasing the reserves (buy)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class OfferStack(Payload):
|
|
56
|
+
"""Represents a single price-quantity pair in an offer request.
|
|
57
|
+
|
|
58
|
+
Notes:
|
|
59
|
+
For the Day-ahead Market, the tertiary 2 sell bidding volume is mandatory.
|
|
60
|
+
For the Week-ahead Market, one of primary, secondary 1, secondary 2, or tertiary 1 sell bidding volumes are
|
|
61
|
+
mandatory.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
# A number used to identify this PQ pair within the offer
|
|
65
|
+
number: int = attr(name="StackNumber", ge=1, le=20)
|
|
66
|
+
|
|
67
|
+
# The minimum quantity that must be provided before the offer can be awarded
|
|
68
|
+
minimum_quantity_kw: int = power_positive("MinimumQuantityInKw")
|
|
69
|
+
|
|
70
|
+
# The primary bid quantity in kW
|
|
71
|
+
primary_qty_kw: Optional[int] = power_positive("PrimaryOfferQuantityInKw", True)
|
|
72
|
+
|
|
73
|
+
# The first secondary bid quantity in kW
|
|
74
|
+
secondary_1_qty_kw: Optional[int] = power_positive("Secondary1OfferQuantityInKw", True)
|
|
75
|
+
|
|
76
|
+
# The second secondary bid quantity in kW
|
|
77
|
+
secondary_2_qty_kw: Optional[int] = power_positive("Secondary2OfferQuantityInKw", True)
|
|
78
|
+
|
|
79
|
+
# The first tertiary bid quantity in kW
|
|
80
|
+
tertiary_1_qty_kw: Optional[int] = power_positive("Tertiary1OfferQuantityInKw", True)
|
|
81
|
+
|
|
82
|
+
# The second tertiary bid quantity in kW.
|
|
83
|
+
tertiary_2_qty_kw: Optional[int] = power_positive("Tertiary2OfferQuantityInKw", True)
|
|
84
|
+
|
|
85
|
+
# The unit price of the power, in JPY/kW/segment
|
|
86
|
+
unit_price: Decimal = price("OfferUnitPrice")
|
|
87
|
+
|
|
88
|
+
# The ID of the offer to which this stack belongs
|
|
89
|
+
id: Optional[str] = offer_id("OfferId", True)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class OfferData(Payload):
|
|
93
|
+
"""Describes the data common to both offer requests and responses."""
|
|
94
|
+
|
|
95
|
+
# The separate offers that make up the offer stack
|
|
96
|
+
stack: List[OfferStack] = element(tag="OfferStack", min_length=1, max_length=20)
|
|
97
|
+
|
|
98
|
+
# The identifier for the power resource being traded
|
|
99
|
+
resource: str = resource_name("ResourceName")
|
|
100
|
+
|
|
101
|
+
# Date and time of the starting block associated with the offer
|
|
102
|
+
start: DateTime = attr(name="StartTime")
|
|
103
|
+
|
|
104
|
+
# Date and time of the ending block associated with the offer
|
|
105
|
+
end: DateTime = attr(name="EndTime")
|
|
106
|
+
|
|
107
|
+
# The direction of the offer (buy, sell)
|
|
108
|
+
direction: Direction = attr(name="Direction")
|
|
109
|
+
|
|
110
|
+
# The type of market for which the offer is being submitted
|
|
111
|
+
pattern_number: Optional[int] = dr_patter_number("DrPatternNumber", True)
|
|
112
|
+
|
|
113
|
+
# The name of the BSP participant submitting the offer
|
|
114
|
+
bsp_participant: Optional[str] = participant("BspParticipantName", True)
|
|
115
|
+
|
|
116
|
+
# The abbreviated name of the counterparty
|
|
117
|
+
company_short_name: Optional[str] = company_short_name("CompanyShortName", True)
|
|
118
|
+
|
|
119
|
+
# A code identifying the TSO or MO
|
|
120
|
+
operator: Optional[str] = operator_code("OperatorCode", True)
|
|
121
|
+
|
|
122
|
+
# The area associated with the offer
|
|
123
|
+
area: Optional[AreaCode] = attr(default=None, name="Area")
|
|
124
|
+
|
|
125
|
+
# The abbreviated name of the resource being traded
|
|
126
|
+
resource_short_name: Optional[str] = resource_short_name("ResourceShortName", True)
|
|
127
|
+
|
|
128
|
+
# The grid code of the resource being traded
|
|
129
|
+
system_code: Optional[str] = system_code("SystemCode", True)
|
|
130
|
+
|
|
131
|
+
# The date and time when the offer was submitted
|
|
132
|
+
submission_time: Optional[DateTime] = attr(default=None, name="SubmissionTime")
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class OfferCancel(Payload):
|
|
136
|
+
"""Describes the data necessary to cancel an offer in the MMS."""
|
|
137
|
+
|
|
138
|
+
# The identifier for the power resource this offer is trading on
|
|
139
|
+
resource: str = resource_name("ResourceName")
|
|
140
|
+
|
|
141
|
+
# The date and time of the starting block associated with the offer
|
|
142
|
+
start: DateTime = attr(name="StartTime")
|
|
143
|
+
|
|
144
|
+
# The date and time of the ending block associated with the offer. You can cancel multiple blocks by specifying the
|
|
145
|
+
# end date and time for any block within the period from the trading day to the specified number of days.
|
|
146
|
+
end: DateTime = attr(name="EndTime")
|
|
147
|
+
|
|
148
|
+
# The type of market for the offer was submitted on
|
|
149
|
+
market_type: MarketType = attr(name="MarketType")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
class OfferQuery(Payload):
|
|
153
|
+
"""Describes the data necessary to query for offers in the MMS."""
|
|
154
|
+
|
|
155
|
+
# The type of market for the offer was submitted on
|
|
156
|
+
market_type: MarketType = attr(name="MarketType")
|
|
157
|
+
|
|
158
|
+
# The identifier for the power resource being requested. If this isn't provided, then all offers for the specified
|
|
159
|
+
# region will be returned
|
|
160
|
+
resource: Optional[str] = resource_name("ResourceName", True)
|
|
161
|
+
|
|
162
|
+
# The area associated with the offer. For TSOs and MOs, this field is mandatory
|
|
163
|
+
area: Optional[AreaCode] = attr(default=None, name="Area")
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Contains the base types necessary for communication with the MMS server."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import List
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel
|
|
8
|
+
from pydantic import Field
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class RequestType(Enum):
|
|
12
|
+
"""Represents the type of request to be sent to the MMS server.
|
|
13
|
+
|
|
14
|
+
Note that the first four items are valid for the Market Initiator (MI) interface, while the last item is valid for
|
|
15
|
+
the Other Market Initiator (OMI) interface.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
INFO = "mp.info"
|
|
19
|
+
MARKET = "mp.market"
|
|
20
|
+
REGISTRATION = "mp.registration"
|
|
21
|
+
REPORT = "mp.report"
|
|
22
|
+
OMI = "mp.omi"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RequestDataType(Enum):
|
|
26
|
+
"""Represents the type of data to be sent to the MMS server.
|
|
27
|
+
|
|
28
|
+
Note that JSON is currently not supported and has been left in for future use.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
JSON = "JSON"
|
|
32
|
+
XML = "XML"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ResponseDataType(Enum):
|
|
36
|
+
"""Represents the type of data to be received from the MMS server.
|
|
37
|
+
|
|
38
|
+
Note that JSON is currently not supported and has been left in for future use.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
XML = "XML"
|
|
42
|
+
HTML = "HTML"
|
|
43
|
+
CSV = "CSV"
|
|
44
|
+
JSON = "JSON"
|
|
45
|
+
TXT = "TXT"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class Attachment(BaseModel):
|
|
49
|
+
"""Represents a file attachment to be sent with a request or response."""
|
|
50
|
+
|
|
51
|
+
# The signature used to encrypt the attachment
|
|
52
|
+
signature: str = Field(alias="signature")
|
|
53
|
+
|
|
54
|
+
# The name of the attachment file
|
|
55
|
+
name: str = Field(alias="name")
|
|
56
|
+
|
|
57
|
+
# The attachment file data
|
|
58
|
+
data: bytes = Field(alias="binaryData")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class MmsRequest(BaseModel):
|
|
62
|
+
"""Base class for all MMS requests."""
|
|
63
|
+
|
|
64
|
+
# The API subsystem to which the request is sent.
|
|
65
|
+
subsystem: RequestType = Field(alias="requestType")
|
|
66
|
+
|
|
67
|
+
# Whether the request is being made as the market operator
|
|
68
|
+
as_admin: bool = Field(default=False, alias="adminRole")
|
|
69
|
+
|
|
70
|
+
# Whether the request data is compressed (for future use only)
|
|
71
|
+
compressed: bool = Field(default=False, alias="requestDataCompressed")
|
|
72
|
+
|
|
73
|
+
# The type of data to be sent to the MMS server.
|
|
74
|
+
data_type: RequestDataType = Field(alias="requestDataType")
|
|
75
|
+
|
|
76
|
+
# Whether to send the request data in the response, on a successful request
|
|
77
|
+
respond_with_request: bool = Field(default=True, alias="sendRequestDataOnSuccess")
|
|
78
|
+
|
|
79
|
+
# Wether the response data should be compressed (for future use only)
|
|
80
|
+
response_compressed: bool = Field(default=False, alias="sendResponseDataCompressed")
|
|
81
|
+
|
|
82
|
+
# The signature used to encrypt the request payload
|
|
83
|
+
signature: str = Field(alias="requestSignature")
|
|
84
|
+
|
|
85
|
+
# The base-64 encoded payload of the request
|
|
86
|
+
payload: bytes = Field(alias="requestData")
|
|
87
|
+
|
|
88
|
+
# Any attached files to be sent with the request. Only 20 of these are allowed for OMI requests. For MI requests,
|
|
89
|
+
# the limit is 40.
|
|
90
|
+
attachments: List[Attachment] = Field(default=[], alias="attachmentData")
|
|
91
|
+
|
|
92
|
+
def to_arguments(self) -> dict:
|
|
93
|
+
"""Convert the request to a dictionary of arguments for use in the MMS client."""
|
|
94
|
+
# First, convert the type to a dictionary format
|
|
95
|
+
converted = self.model_dump(by_alias=True)
|
|
96
|
+
|
|
97
|
+
# Next, convert the enum types to their string representations
|
|
98
|
+
converted["requestType"] = converted["requestType"].value
|
|
99
|
+
converted["requestDataType"] = converted["requestDataType"].value
|
|
100
|
+
|
|
101
|
+
# Finally, return the converted dictionary
|
|
102
|
+
return converted
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class MmsResponse(BaseModel):
|
|
106
|
+
"""Base class for all MMS responses."""
|
|
107
|
+
|
|
108
|
+
# Whether the request was successful
|
|
109
|
+
success: bool = Field(alias="success")
|
|
110
|
+
|
|
111
|
+
# Whether there were any warnings. This field will only be present if the request was successful.
|
|
112
|
+
warnings: bool = Field(default=False, alias="warnings")
|
|
113
|
+
|
|
114
|
+
# Whether the response is binary data.
|
|
115
|
+
is_binary: bool = Field(default=False, alias="responseBinary")
|
|
116
|
+
|
|
117
|
+
# Whether the response data is compressed (for future use only)
|
|
118
|
+
compressed: bool = Field(default=False, alias="responseCompressed")
|
|
119
|
+
|
|
120
|
+
# The type of data to be received from the MMS server.
|
|
121
|
+
data_type: ResponseDataType = Field(alias="responseDataType")
|
|
122
|
+
|
|
123
|
+
# The filename assigned to the response (for pre-generated reports)
|
|
124
|
+
report_filename: Optional[str] = Field(default=None, alias="responseFilename")
|
|
125
|
+
|
|
126
|
+
# The base-64 encoded payload of the response
|
|
127
|
+
payload: bytes = Field(alias="responseData")
|
|
128
|
+
|
|
129
|
+
# Any attached files to be sent with the response
|
|
130
|
+
attachments: List[Attachment] = Field(default=[], alias="attachmentData")
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Contains error classes for the MMS client."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from typing import List
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
from mms_client.types.base import E
|
|
9
|
+
from mms_client.types.base import Messages
|
|
10
|
+
from mms_client.types.base import P
|
|
11
|
+
from mms_client.utils.web import ClientType
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AudienceError(ValueError):
|
|
15
|
+
"""Error raised when an invalid audience is provided."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, method: str, allowed: ClientType, audience: ClientType):
|
|
18
|
+
"""Initialize the error.
|
|
19
|
+
|
|
20
|
+
Arguments:
|
|
21
|
+
method (str): The method that caused the error.
|
|
22
|
+
allowed (str): The allowed audience.
|
|
23
|
+
audience (str): The invalid audience.
|
|
24
|
+
"""
|
|
25
|
+
self.method = method
|
|
26
|
+
self.message = f"{method}: Invalid client type, '{audience.name}' provided. Only '{allowed.name}' is supported."
|
|
27
|
+
self.allowed = allowed
|
|
28
|
+
self.audience = audience
|
|
29
|
+
super().__init__(self.message)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class MMSClientError(RuntimeError):
|
|
33
|
+
"""Base class for MMS client errors."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, method: str, message: str):
|
|
36
|
+
"""Initialize the error.
|
|
37
|
+
|
|
38
|
+
Arguments:
|
|
39
|
+
method (str): The method that caused the error.
|
|
40
|
+
message (str): The error message.
|
|
41
|
+
"""
|
|
42
|
+
super().__init__(f"{method}: {message}")
|
|
43
|
+
self.message = message
|
|
44
|
+
self.method = method
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class MMSValidationError(RuntimeError):
|
|
48
|
+
"""Error raised when a request fails validation."""
|
|
49
|
+
|
|
50
|
+
def __init__(self, method: str, envelope: E, request: Optional[Union[P, List[P]]], messages: Dict[str, Messages]):
|
|
51
|
+
"""Initialize the validation error.
|
|
52
|
+
|
|
53
|
+
Arguments:
|
|
54
|
+
method (str): The method that caused the error.
|
|
55
|
+
message (str): The error message.
|
|
56
|
+
envelope (E): The request envelope.
|
|
57
|
+
request (P): The request data.
|
|
58
|
+
messages (Dict[str, Messages]): The messages returned with the payload.
|
|
59
|
+
"""
|
|
60
|
+
self.message = (
|
|
61
|
+
f"{method}: Request of type {type(envelope).__name__} containing data {type(request).__name__} "
|
|
62
|
+
f"failed validation. See the logs for more information."
|
|
63
|
+
)
|
|
64
|
+
self.method = method
|
|
65
|
+
self.messages = messages
|
|
66
|
+
super().__init__(self.message)
|