mms-client 1.6.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.
@@ -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
  )
@@ -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(Interface.MI, Serializer(SchemaType.REPORT, "MarketReport"))
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
- # A list of information messages returned with a payload
41
- information: List[Message] = element(default=[], tag="Information", nillable=True)
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
- # A list of warning messages returned with a payload
44
- warnings: List[Message] = element(default=[], tag="Warning", nillable=True)
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
- # A list of error messages returned with a payload
47
- errors: List[Message] = element(default=[], tag="Error", nillable=True)
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
- validation: ValidationStatus = attr(default=ValidationStatus.NOT_DONE, name="Validation")
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,23 +153,21 @@ 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
 
117
160
 
118
- class PayloadBase(BaseXmlModel, nsmap={"xsi": "http://www.w3.org/2001/XMLSchema-instance"}):
161
+ class PayloadBase(BaseXmlModel):
119
162
  """Represents the base fields for an MMS request payload."""
120
163
 
121
- # The XML schema to use for validation
122
- location: SchemaType = attr(name="noNamespaceSchemaLocation", ns="xsi")
123
-
124
164
 
125
165
  class BaseResponse(BaseXmlModel, Generic[E], tag="BaseResponse"):
126
166
  """Contains the base data extracted from the MMS response in a format we can use."""
127
167
 
128
168
  # The processing statistics returned with the payload. This will not be present in requests, and will be populated
129
169
  # in responses.
130
- statistics: ProcessingStatistics = element(tag="ProcessingStatistics")
170
+ statistics: Optional[ProcessingStatistics] = element(default=None, tag="ProcessingStatistics")
131
171
 
132
172
  # The request payload, containing the request data
133
173
  _envelope: E = PrivateAttr()
mms_client/types/enums.py CHANGED
@@ -53,6 +53,26 @@ class ResourceType(Enum):
53
53
  VPP_DEM = "07"
54
54
 
55
55
 
56
+ class RemainingReserveAvailability(Enum):
57
+ """Describes the availability of remaining reserves for a power generation unit."""
58
+
59
+ NOT_AVAILABLE = "0"
60
+ AVAILABLE_FOR_UP_ONLY = "1"
61
+ AVAILABLE_FOR_DOWN_ONLY = "2"
62
+ AVAILABLE_FOR_UP_AND_DOWN = "3"
63
+
64
+
65
+ class ContractType(Enum):
66
+ """Describes the type of contract for a power generation unit."""
67
+
68
+ MARKET = "1"
69
+ MARKET_AND_POWER_SUPPLY_2 = "2"
70
+ POWER_SUPPLY_2 = "3"
71
+ ONLY_POWER_SUPPLY_1 = "4"
72
+ MARKET_AND_REMAINING_RESERVE_UTILIZATION = "5"
73
+ REMAINING_RESERVE_UTILIZATION = "6"
74
+
75
+
56
76
  class CommandMonitorMethod(Enum):
57
77
  """Describes how the power generation unit is monitored and commanded."""
58
78
 
@@ -61,6 +81,20 @@ class CommandMonitorMethod(Enum):
61
81
  OFFLINE = "3"
62
82
 
63
83
 
84
+ class BaseLineSettingMethod(Enum):
85
+ """Describe how the baseline is set for a power generation unit."""
86
+
87
+ PREDICTION_BASE = "1"
88
+ MEASUREMENT_BASE = "2"
89
+
90
+
91
+ class SignalType(Enum):
92
+ """Describes the type of signal used to monitor and command a power generation unit."""
93
+
94
+ ACTUAL_OUTPUT_ORDER = "1"
95
+ DIFFERENTIAL_OUTPUT_ORDER = "2"
96
+
97
+
64
98
  class BooleanFlag(Enum):
65
99
  """Represents a Boolean value as an enumeration.
66
100