mms-client 1.1.0__py3-none-any.whl → 1.3.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 +18 -6
- mms_client/services/market.py +59 -8
- mms_client/services/registration.py +2 -0
- mms_client/types/award.py +310 -0
- mms_client/types/enums.py +47 -0
- mms_client/types/fields.py +73 -2
- mms_client/types/market.py +6 -3
- mms_client/types/offer.py +3 -31
- mms_client/types/resource.py +4 -32
- mms_client/types/transport.py +2 -2
- mms_client/utils/serialization.py +128 -29
- {mms_client-1.1.0.dist-info → mms_client-1.3.0.dist-info}/METADATA +2 -1
- {mms_client-1.1.0.dist-info → mms_client-1.3.0.dist-info}/RECORD +15 -14
- {mms_client-1.1.0.dist-info → mms_client-1.3.0.dist-info}/LICENSE +0 -0
- {mms_client-1.1.0.dist-info → mms_client-1.3.0.dist-info}/WHEEL +0 -0
mms_client/services/base.py
CHANGED
|
@@ -10,6 +10,7 @@ from typing import Optional
|
|
|
10
10
|
from typing import Protocol
|
|
11
11
|
from typing import Tuple
|
|
12
12
|
from typing import Type
|
|
13
|
+
from typing import Union
|
|
13
14
|
|
|
14
15
|
from mms_client.security.crypto import Certificate
|
|
15
16
|
from mms_client.security.crypto import CryptoWrapper
|
|
@@ -119,7 +120,7 @@ class ClientProto(Protocol):
|
|
|
119
120
|
def request_many(
|
|
120
121
|
self,
|
|
121
122
|
envelope: E,
|
|
122
|
-
data: P,
|
|
123
|
+
data: Union[P, List[P]],
|
|
123
124
|
config: EndpointConfiguration,
|
|
124
125
|
) -> Tuple[MultiResponse[E, P], Dict[str, bytes]]:
|
|
125
126
|
"""Submit a request to the MMS server and return the multi-response.
|
|
@@ -361,7 +362,7 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
361
362
|
def request_many(
|
|
362
363
|
self,
|
|
363
364
|
envelope: E,
|
|
364
|
-
payload: P,
|
|
365
|
+
payload: Union[P, List[P]],
|
|
365
366
|
config: EndpointConfiguration[E, P],
|
|
366
367
|
) -> Tuple[MultiResponse[E, P], Dict[str, bytes]]:
|
|
367
368
|
"""Submit a request to the MMS server and return the multi-response.
|
|
@@ -374,13 +375,20 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
374
375
|
Returns: The multi-response from the MMS server.
|
|
375
376
|
"""
|
|
376
377
|
# First, create the MMS request from the payload and data.
|
|
378
|
+
is_list = isinstance(payload, list)
|
|
379
|
+
data_type = type(payload[0]) if is_list else type(payload) # type: ignore[index]
|
|
377
380
|
self._logger.debug(
|
|
378
381
|
(
|
|
379
382
|
f"{config.name}: Starting multi-request. Envelope: {type(envelope).__name__}, "
|
|
380
|
-
f"Data: {
|
|
383
|
+
f"Data: {data_type.__name__}"
|
|
381
384
|
),
|
|
382
385
|
)
|
|
383
|
-
|
|
386
|
+
serialized = (
|
|
387
|
+
config.service.serializer.serialize_multi(envelope, payload, data_type) # type: ignore[arg-type]
|
|
388
|
+
if is_list
|
|
389
|
+
else config.service.serializer.serialize(envelope, payload) # type: ignore[type-var]
|
|
390
|
+
)
|
|
391
|
+
request = self._to_mms_request(config.request_type, serialized)
|
|
384
392
|
|
|
385
393
|
# Next, submit the request to the MMS server and get and verify the response.
|
|
386
394
|
resp = self._get_wrapper(config.service).submit(request)
|
|
@@ -391,8 +399,12 @@ class BaseClient: # pylint: disable=too-many-instance-attributes
|
|
|
391
399
|
|
|
392
400
|
# Finally, deserialize and verify the response
|
|
393
401
|
envelope_type = config.response_envelope_type or type(envelope)
|
|
394
|
-
data_type = config.response_data_type or
|
|
395
|
-
data: MultiResponse[E, P] = config.service.serializer.deserialize_multi(
|
|
402
|
+
data_type = config.response_data_type or data_type
|
|
403
|
+
data: MultiResponse[E, P] = config.service.serializer.deserialize_multi(
|
|
404
|
+
resp.payload,
|
|
405
|
+
envelope_type,
|
|
406
|
+
data_type, # type: ignore[arg-type]
|
|
407
|
+
)
|
|
396
408
|
self._verify_multi_response(data, config)
|
|
397
409
|
|
|
398
410
|
# Return the response data and any attachments
|
mms_client/services/market.py
CHANGED
|
@@ -8,6 +8,8 @@ from mms_client.services.base import ClientProto
|
|
|
8
8
|
from mms_client.services.base import ServiceConfiguration
|
|
9
9
|
from mms_client.services.base import mms_endpoint
|
|
10
10
|
from mms_client.services.base import mms_multi_endpoint
|
|
11
|
+
from mms_client.types.award import AwardQuery
|
|
12
|
+
from mms_client.types.award import AwardResponse
|
|
11
13
|
from mms_client.types.market import MarketCancel
|
|
12
14
|
from mms_client.types.market import MarketQuery
|
|
13
15
|
from mms_client.types.market import MarketSubmit
|
|
@@ -45,7 +47,33 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
45
47
|
|
|
46
48
|
Returns: The offer that has been registered with the MMS server.
|
|
47
49
|
"""
|
|
48
|
-
#
|
|
50
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
51
|
+
return MarketSubmit( # type: ignore[return-value]
|
|
52
|
+
date=date or Date.today(),
|
|
53
|
+
participant=self.participant,
|
|
54
|
+
user=self.user,
|
|
55
|
+
market_type=market_type,
|
|
56
|
+
days=days,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
@mms_multi_endpoint("MarketSubmit_OfferData", config, RequestType.INFO, ClientType.BSP)
|
|
60
|
+
def put_offers(
|
|
61
|
+
self: ClientProto, requests: List[OfferData], market_type: MarketType, days: int, date: Optional[Date] = None
|
|
62
|
+
) -> List[OfferData]:
|
|
63
|
+
"""Submit multiple offers to the MMS server.
|
|
64
|
+
|
|
65
|
+
This endpoint is only accessible to BSPs.
|
|
66
|
+
|
|
67
|
+
Arguments:
|
|
68
|
+
requests (List[OfferData]): The offers to submit to the MMS server.
|
|
69
|
+
market_type (MarketType): The type of market for which the offers are being submitted.
|
|
70
|
+
days (int): The number of days ahead for which the offers are being submitted.
|
|
71
|
+
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to the
|
|
72
|
+
current date.
|
|
73
|
+
|
|
74
|
+
Returns: A list of offers that have been registered with the MMS server.
|
|
75
|
+
"""
|
|
76
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
49
77
|
return MarketSubmit( # type: ignore[return-value]
|
|
50
78
|
date=date or Date.today(),
|
|
51
79
|
participant=self.participant,
|
|
@@ -57,28 +85,24 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
57
85
|
@mms_multi_endpoint(
|
|
58
86
|
"MarketQuery_OfferQuery", config, RequestType.INFO, resp_envelope_type=MarketSubmit, resp_data_type=OfferData
|
|
59
87
|
)
|
|
60
|
-
def query_offers(
|
|
61
|
-
self: ClientProto, request: OfferQuery, market_type: MarketType, days: int, date: Optional[Date] = None
|
|
62
|
-
) -> List[OfferData]:
|
|
88
|
+
def query_offers(self: ClientProto, request: OfferQuery, days: int, date: Optional[Date] = None) -> List[OfferData]:
|
|
63
89
|
"""Query the MMS server for offers.
|
|
64
90
|
|
|
65
91
|
This endpoint is accessible to all client types.
|
|
66
92
|
|
|
67
93
|
Arguments:
|
|
68
94
|
request (OfferQuery): The query to submit to the MMS server.
|
|
69
|
-
market_type (MarketType): The type of market for which the offer was submitted.
|
|
70
95
|
days (int): The number of days ahead for which the data is being queried.
|
|
71
96
|
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to the
|
|
72
97
|
current date.
|
|
73
98
|
|
|
74
99
|
Returns: A list of offers that match the query.
|
|
75
100
|
"""
|
|
76
|
-
#
|
|
101
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
77
102
|
return MarketQuery( # type: ignore[return-value]
|
|
78
103
|
date=date or Date.today(),
|
|
79
104
|
participant=self.participant,
|
|
80
105
|
user=self.user,
|
|
81
|
-
market_type=market_type,
|
|
82
106
|
days=days,
|
|
83
107
|
)
|
|
84
108
|
|
|
@@ -97,7 +121,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
97
121
|
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to the
|
|
98
122
|
current date.
|
|
99
123
|
"""
|
|
100
|
-
#
|
|
124
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
101
125
|
return MarketCancel( # type: ignore[return-value]
|
|
102
126
|
date=date or Date.today(),
|
|
103
127
|
participant=self.participant,
|
|
@@ -105,3 +129,30 @@ class MarketClientMixin: # pylint: disable=unused-argument
|
|
|
105
129
|
market_type=market_type,
|
|
106
130
|
days=days,
|
|
107
131
|
)
|
|
132
|
+
|
|
133
|
+
@mms_endpoint("MarketQuery_AwardResultsQuery", config, RequestType.INFO, resp_data_type=AwardResponse)
|
|
134
|
+
def query_awards(self: ClientProto, request: AwardQuery, days: int, date: Optional[Date] = None) -> AwardResponse:
|
|
135
|
+
"""Query the MMS server for award results.
|
|
136
|
+
|
|
137
|
+
This endpoint is accessible to all client types.
|
|
138
|
+
|
|
139
|
+
If no values are specified for Area, Associated Area, Power Generation Unit Code, or GC Registration Flag,
|
|
140
|
+
the results for all areas will be retrieved. If one or more of these criteria are specified, the results will
|
|
141
|
+
be filtered according to the specified criteria. If no value is specified for the retrieval period, the default
|
|
142
|
+
value for this field is 1.
|
|
143
|
+
|
|
144
|
+
Arguments:
|
|
145
|
+
request (AwardQuery): The query to submit to the MMS server.
|
|
146
|
+
days (int): The number of days ahead for which the data is being queried.
|
|
147
|
+
date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to the
|
|
148
|
+
current date.
|
|
149
|
+
|
|
150
|
+
Returns: The award results that match the query.
|
|
151
|
+
"""
|
|
152
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
153
|
+
return MarketQuery( # type: ignore[return-value]
|
|
154
|
+
date=date or Date.today(),
|
|
155
|
+
participant=self.participant,
|
|
156
|
+
user=self.user,
|
|
157
|
+
days=days,
|
|
158
|
+
)
|
|
@@ -38,6 +38,7 @@ class RegistrationClientMixin: # pylint: disable=unused-argument
|
|
|
38
38
|
|
|
39
39
|
Returns: The resource that has been registered with the MMS server.
|
|
40
40
|
"""
|
|
41
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
41
42
|
# For some reason, the registration DTOs require that the participant ID exist on the payload rather than on
|
|
42
43
|
# the envelope so we need to set it before we return the envelope.
|
|
43
44
|
request.participant = self.participant
|
|
@@ -65,6 +66,7 @@ class RegistrationClientMixin: # pylint: disable=unused-argument
|
|
|
65
66
|
|
|
66
67
|
Returns: A list of resources that match the query.
|
|
67
68
|
"""
|
|
69
|
+
# NOTE: The return type does not match the method definition but the decorator will return the correct type
|
|
68
70
|
# For some reason, the registration DTOs require that the participant ID exist on the payload rather than on
|
|
69
71
|
# the envelope so we need to set it before we return the envelope.
|
|
70
72
|
request.participant = self.participant
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"""Contains objects for MMS award results."""
|
|
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
|
+
from pydantic_xml import wrapped
|
|
13
|
+
|
|
14
|
+
from mms_client.types.base import Payload
|
|
15
|
+
from mms_client.types.enums import AreaCode
|
|
16
|
+
from mms_client.types.enums import BooleanFlag
|
|
17
|
+
from mms_client.types.enums import CommandMonitorMethod
|
|
18
|
+
from mms_client.types.enums import ContractResult
|
|
19
|
+
from mms_client.types.enums import Direction
|
|
20
|
+
from mms_client.types.enums import ResourceType
|
|
21
|
+
from mms_client.types.fields import company_short_name
|
|
22
|
+
from mms_client.types.fields import contract_id
|
|
23
|
+
from mms_client.types.fields import dr_patter_number
|
|
24
|
+
from mms_client.types.fields import dr_pattern_name
|
|
25
|
+
from mms_client.types.fields import jbms_id
|
|
26
|
+
from mms_client.types.fields import offer_id
|
|
27
|
+
from mms_client.types.fields import operator_code
|
|
28
|
+
from mms_client.types.fields import participant
|
|
29
|
+
from mms_client.types.fields import power_positive
|
|
30
|
+
from mms_client.types.fields import price
|
|
31
|
+
from mms_client.types.fields import resource_name
|
|
32
|
+
from mms_client.types.fields import resource_short_name
|
|
33
|
+
from mms_client.types.fields import system_code
|
|
34
|
+
from mms_client.types.market import MarketType
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def baseload_file_name(alias: str, optional: bool = False):
|
|
38
|
+
"""Create a field for a baseload file name.
|
|
39
|
+
|
|
40
|
+
Arguments:
|
|
41
|
+
alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
|
|
42
|
+
to the JSON/XML key.
|
|
43
|
+
optional (bool): If True, the field will be optional with a default of None. If False, the field will be
|
|
44
|
+
required, with no default.
|
|
45
|
+
|
|
46
|
+
Returns: A Pydantic Field object for the baseload file name.
|
|
47
|
+
"""
|
|
48
|
+
return attr(
|
|
49
|
+
default=None if optional else PydanticUndefined,
|
|
50
|
+
name=alias,
|
|
51
|
+
min_length=31,
|
|
52
|
+
max_length=40,
|
|
53
|
+
pattern=r"^W9_[0-9]{4}_[0-9]{8}_[0-9]{2}_[A-Z0-9]{5}_[A-Z0-9_\-]{1,10}\.xml$",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class SubRequirement(Enum):
|
|
58
|
+
"""Commodity combination categories."""
|
|
59
|
+
|
|
60
|
+
PRIMARY = "PRI"
|
|
61
|
+
SECONDARY_1 = "SEC1"
|
|
62
|
+
SECONDARY_2 = "SEC2"
|
|
63
|
+
TERTIARY_1 = "TER1"
|
|
64
|
+
PRIMARY_SECONDARY_1 = "PRI-SEC1"
|
|
65
|
+
PRIMARY_SECONDARY_2 = "PRI-SEC2"
|
|
66
|
+
PRIMARY_TERTIARY_1 = "PRI-TER1"
|
|
67
|
+
SECONDARY = "SEC1-SEC2"
|
|
68
|
+
SECONDARY_1_TERTIARY_1 = "SEC1-TER1"
|
|
69
|
+
SECONDARY_2_TERTIARY_1 = "SEC2-TER1"
|
|
70
|
+
PRIAMRY_SECONDARY = "PRI-SEC1-SEC2"
|
|
71
|
+
PRIMARY_SECONDARY_1_TERTIARY_1 = "PRI-SEC1-TER1"
|
|
72
|
+
PRIMARY_SECONDARY_2_TERTIARY_1 = "PRI-SEC2-TER1"
|
|
73
|
+
SECONDARY_TERTIARY_1 = "SEC1-SEC2-TER1"
|
|
74
|
+
PRIMARY_SECONDARY_TERTIARY_1 = "PRI-SEC1-SEC2-TER1"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class ContractSource(Enum):
|
|
78
|
+
"""Describes the source of the contract."""
|
|
79
|
+
|
|
80
|
+
MA = "1"
|
|
81
|
+
SWITCHING = "2"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class AwardQuery(Payload, tag="AwardResultsQuery"):
|
|
85
|
+
"""Query object for bid awards."""
|
|
86
|
+
|
|
87
|
+
# The market for which results should be retrieved
|
|
88
|
+
market_type: MarketType = attr(name="MarketType")
|
|
89
|
+
|
|
90
|
+
# The area for which results should be retrieved. If this isn't provided, then all results will be retrieved
|
|
91
|
+
area: Optional[AreaCode] = attr(default=None, name="Area")
|
|
92
|
+
|
|
93
|
+
# The associated area. If the API user is a BSP, this field cannot be set
|
|
94
|
+
linked_area: Optional[AreaCode] = attr(default=None, name="LinkedArea")
|
|
95
|
+
|
|
96
|
+
# The name of the resource for which results should be retrieved. If this isn't provided, then all results will be
|
|
97
|
+
# retrieved for all resources
|
|
98
|
+
resource: Optional[str] = resource_name("ResourceName", True)
|
|
99
|
+
|
|
100
|
+
# The start date and time for which results should be retrieved. Should conform with the start of a block
|
|
101
|
+
start: DateTime = attr(name="StartTime")
|
|
102
|
+
|
|
103
|
+
# The end date and time for which results should be retrieved. Should conform with the end of a block. You can
|
|
104
|
+
# specify the end date and time of any block within the period from the transaction date to the number of days to
|
|
105
|
+
# get multiple blocks.
|
|
106
|
+
end: DateTime = attr(name="EndTime")
|
|
107
|
+
|
|
108
|
+
# Whether we are before gate close or after gate close. If this isn't provided, then all results will be retrieved
|
|
109
|
+
# regardless of gate closure.
|
|
110
|
+
gate_closed: Optional[BooleanFlag] = attr(default=None, name="AfterGC")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Award(Payload):
|
|
114
|
+
"""Represents the details of a bid award."""
|
|
115
|
+
|
|
116
|
+
# Unique identifier assigned to the contract
|
|
117
|
+
contract_id: str = contract_id("ContractId")
|
|
118
|
+
|
|
119
|
+
# Unique identifier assigned to the contract by the JBMS
|
|
120
|
+
jbms_id: int = jbms_id("JbmsId")
|
|
121
|
+
|
|
122
|
+
# The area of the power generation unit generating the electricity
|
|
123
|
+
area: AreaCode = attr(name="Area")
|
|
124
|
+
|
|
125
|
+
# The associated area. If the API user is a BSP, it is not set.
|
|
126
|
+
linked_area: Optional[AreaCode] = attr(default=None, name="LinkedArea")
|
|
127
|
+
|
|
128
|
+
# A code identifying the power generation unit for which this bid was awarded
|
|
129
|
+
resource: str = resource_name("ResourceName")
|
|
130
|
+
|
|
131
|
+
# An abbreviated name for the power generation unit for which this bid was awarded
|
|
132
|
+
resource_short_name: str = resource_short_name("ResourceShortName")
|
|
133
|
+
|
|
134
|
+
# The grid code of the resource being traded
|
|
135
|
+
system_code: str = system_code("SystemCode")
|
|
136
|
+
|
|
137
|
+
# How the power generation unit produces electricity
|
|
138
|
+
resource_type: ResourceType = attr(name="ResourceType")
|
|
139
|
+
|
|
140
|
+
# The type of market for which the offer is being submitted. Must be a valid pattern number for the submission date
|
|
141
|
+
# Required for VPP resources. Ensure there are no duplicate pattern numbers for the same resource and start time.
|
|
142
|
+
pattern_number: Optional[int] = dr_patter_number("DrPatternNumber", True)
|
|
143
|
+
|
|
144
|
+
# The name of the list pattern with which this award is associated
|
|
145
|
+
pattern_name: Optional[str] = dr_pattern_name("DrPatternName", True)
|
|
146
|
+
|
|
147
|
+
# The participant ID of the BSP associated with this bid award
|
|
148
|
+
bsp_participant: str = participant("BspParticipantName")
|
|
149
|
+
|
|
150
|
+
# The abbreviated name of the counterparty
|
|
151
|
+
company_short_name: str = company_short_name("CompanyShortName")
|
|
152
|
+
|
|
153
|
+
# A code identifying the TSO or MO
|
|
154
|
+
operator: str = operator_code("OperatorCode")
|
|
155
|
+
|
|
156
|
+
# Primary-Secondary 1 command-and-control and monitoring method. Refers to the methodology or system utilized for
|
|
157
|
+
# regulating and overseeing power generation units' operations in response to external commands or signals. This
|
|
158
|
+
# method encompasses the process by which instructions are transmitted to power generation units, and their
|
|
159
|
+
# performance is monitored to ensure compliance with these instructions. If has_secondary_1 is set to True, then
|
|
160
|
+
# it must be set to DEDICATED_LINE. At least one of the following combinations must be set:
|
|
161
|
+
# primary_secondary_1_control_method
|
|
162
|
+
# secondary_2_tertiary_control_method
|
|
163
|
+
primary_secondary_1_control_method: Optional[CommandMonitorMethod] = attr(
|
|
164
|
+
default=None, name="CommandOperationMethodPriSec1"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Secondary 2-Tertiary command-and-control and monitoring method. Refers to the methodology or system utilized for
|
|
168
|
+
# regulating and overseeing power generation units' operations in response to external commands or signals. This
|
|
169
|
+
# method encompasses the process by which instructions are transmitted to power generation units, and their
|
|
170
|
+
# performance is monitored to ensure compliance with these instructions.
|
|
171
|
+
secondary_2_tertiary_control_method: Optional[CommandMonitorMethod] = attr(
|
|
172
|
+
default=None, name="CommandOperationMethodSec2Ter1Ter2"
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Bid unit price, in JPY/kW/segment
|
|
176
|
+
offer_price: Decimal = price("OfferPrice", 10000.00)
|
|
177
|
+
|
|
178
|
+
# The contract price, in JPY/kW/segment
|
|
179
|
+
contract_price: Decimal = price("ContractPrice", 10000.00)
|
|
180
|
+
|
|
181
|
+
# The performance evaluation coefficient, alpha
|
|
182
|
+
performance_evaluation_coefficient: Decimal = attr(name="PerfEvalCoeff", ge=0.00, le=100.0, decimal_places=2)
|
|
183
|
+
|
|
184
|
+
# The corrected unit price, in JPY/kW/segment
|
|
185
|
+
corrected_unit_price: Decimal = price("CorrectedUnitPrice", 1000000.00)
|
|
186
|
+
|
|
187
|
+
# The commodity combination category. This is only set for non-BSP users.
|
|
188
|
+
sub_requirement: Optional[SubRequirement] = attr(default=None, name="SubRequirementType")
|
|
189
|
+
|
|
190
|
+
# The primary offer quantity in kW. If the API user is a BSP, this field is not set for the contract results
|
|
191
|
+
# generated by the TSO's power source exchange.
|
|
192
|
+
primary_offer_qty: Optional[int] = power_positive("PrimaryOfferQuantityInKw", True)
|
|
193
|
+
|
|
194
|
+
# The secondary 1 offer quantity, in kW. If the API user is a BSP, this field is not set for the contract results
|
|
195
|
+
# generated by the TSO's power source exchange.
|
|
196
|
+
secondary_1_offer_qty: Optional[int] = power_positive("Secondary1OfferQuantityInKw", True)
|
|
197
|
+
|
|
198
|
+
# The secondary 2 offer quantity, in kW. If the API user is a BSP, this field is not set for the contract results
|
|
199
|
+
# generated by the TSO's power source exchange.
|
|
200
|
+
secondary_2_offer_qty: Optional[int] = power_positive("Secondary2OfferQuantityInKw", True)
|
|
201
|
+
|
|
202
|
+
# The tertiary 1 offer quantity, in kW. If the API user is a BSP, this field is not set for the contract results
|
|
203
|
+
# generated by the TSO's power source exchange.
|
|
204
|
+
tertiary_1_offer_qty: Optional[int] = power_positive("Tertiary1OfferQuantityInKw", True)
|
|
205
|
+
|
|
206
|
+
# The tertiary 2 offer quantity, in kW. If the API user is a BSP, this field is not set for the contract results
|
|
207
|
+
# generated by the TSO's power source exchange.
|
|
208
|
+
tertiary_2_offer_qty: Optional[int] = power_positive("Tertiary2OfferQuantityInKw", True)
|
|
209
|
+
|
|
210
|
+
# The primary award quantity, in kW
|
|
211
|
+
primary_award_qty: Optional[int] = power_positive("PrimaryAwardQuantityInKw", True)
|
|
212
|
+
|
|
213
|
+
# The secondary 1 award quantity, in kW
|
|
214
|
+
secondary_1_award_qty: Optional[int] = power_positive("Secondary1AwardQuantityInKw", True)
|
|
215
|
+
|
|
216
|
+
# The secondary 2 award quantity, in kW
|
|
217
|
+
secondary_2_award_qty: Optional[int] = power_positive("Secondary2AwardQuantityInKw", True)
|
|
218
|
+
|
|
219
|
+
# The tertiary 1 award quantity, in kW
|
|
220
|
+
tertiary_1_award_qty: Optional[int] = power_positive("Tertiary1AwardQuantityInKw", True)
|
|
221
|
+
|
|
222
|
+
# The tertiary 2 award quantity, in kW
|
|
223
|
+
tertiary_2_award_qty: Optional[int] = power_positive("Tertiary2AwardQuantityInKw", True)
|
|
224
|
+
|
|
225
|
+
# The primary contract quantity, in kW
|
|
226
|
+
primary_contract_qty: Optional[int] = power_positive("PrimaryContractQuantityInKw", True)
|
|
227
|
+
|
|
228
|
+
# The secondary 1 contract quantity, in kW
|
|
229
|
+
secondary_1_contract_qty: Optional[int] = power_positive("Secondary1ContractQuantityInKw", True)
|
|
230
|
+
|
|
231
|
+
# The secondary 2 contract quantity, in kW
|
|
232
|
+
secondary_2_contract_qty: Optional[int] = power_positive("Secondary2ContractQuantityInKw", True)
|
|
233
|
+
|
|
234
|
+
# The tertiary 1 contract quantity, in kW
|
|
235
|
+
tertiary_1_contract_qty: Optional[int] = power_positive("Tertiary1ContractQuantityInKw", True)
|
|
236
|
+
|
|
237
|
+
# The tertiary 2 contract quantity, in kW
|
|
238
|
+
tertiary_2_contract_qty: Optional[int] = power_positive("Tertiary2ContractQuantityInKw", True)
|
|
239
|
+
|
|
240
|
+
# The primary effective contracted quantity, in kW
|
|
241
|
+
primary_valid_qty: Optional[int] = power_positive("PrimaryValidQuantityInKw", True)
|
|
242
|
+
|
|
243
|
+
# The secondary 1 effective contracted quantity, in kW
|
|
244
|
+
secondary_1_valid_qty: Optional[int] = power_positive("Secondary1ValidQuantityInKw", True)
|
|
245
|
+
|
|
246
|
+
# The secondary 2 effective contracted quantity, in kW
|
|
247
|
+
secondary_2_valid_qty: Optional[int] = power_positive("Secondary2ValidQuantityInKw", True)
|
|
248
|
+
|
|
249
|
+
# The tertiary 1 effective contracted quantity, in kW
|
|
250
|
+
tertiary_1_valid_qty: Optional[int] = power_positive("Tertiary1ValidQuantityInKw", True)
|
|
251
|
+
|
|
252
|
+
# The compound fulfillment quantity, in kW
|
|
253
|
+
compound_valid_qty: Optional[int] = power_positive("CompoundValidQuantityInKw", True)
|
|
254
|
+
|
|
255
|
+
# The primary invalid contract quantity, in kW
|
|
256
|
+
primary_invalid_qty: Optional[int] = power_positive("PrimaryInvalidQuantityInKw", True)
|
|
257
|
+
|
|
258
|
+
# The secondary 1 invalid contract quantity, in kW
|
|
259
|
+
secondary_1_invalid_qty: Optional[int] = power_positive("Secondary1InvalidQuantityInKw", True)
|
|
260
|
+
|
|
261
|
+
# The secondary 2 invalid contract quantity, in kW
|
|
262
|
+
secondary_2_invalid_qty: Optional[int] = power_positive("Secondary2InvalidQuantityInKw", True)
|
|
263
|
+
|
|
264
|
+
# The tertiary 1 invalid contract quantity, in kW
|
|
265
|
+
tertiary_1_invalid_qty: Optional[int] = power_positive("Tertiary1InvalidQuantityInKw", True)
|
|
266
|
+
|
|
267
|
+
# Name of the file containing the negative baseload data
|
|
268
|
+
negative_baseload_file: Optional[str] = baseload_file_name("BaselineLoadFileNameNeg", True)
|
|
269
|
+
|
|
270
|
+
# Name of the file containing the positive baseload data
|
|
271
|
+
positive_baseload_file: Optional[str] = baseload_file_name("BaselineLoadFileNamePos", True)
|
|
272
|
+
|
|
273
|
+
# The date and time when the offer was submitted. If the API user is a BSP, this attribute is not set for the
|
|
274
|
+
# contract results generated by the TSO's power source exchange.
|
|
275
|
+
submission_time: Optional[DateTime] = attr(default=None, name="SubmissionTime")
|
|
276
|
+
|
|
277
|
+
# Contract result (full, partial)
|
|
278
|
+
offer_award_level: ContractResult = attr(name="OfferAwardedLevel")
|
|
279
|
+
|
|
280
|
+
# The ID of the offer to which this stack belongs
|
|
281
|
+
offer_id: Optional[str] = offer_id("OfferId", True)
|
|
282
|
+
|
|
283
|
+
# The source of the contract
|
|
284
|
+
contract_source: ContractSource = attr(name="ContractSource")
|
|
285
|
+
|
|
286
|
+
# Whether we are before gate close or after gate close
|
|
287
|
+
gate_closed: BooleanFlag = attr(name="AfterGC")
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
class AwardResult(Payload, tag="AwardResults"):
|
|
291
|
+
"""Contains a number of bid rewards associated with a block of time and trade direction."""
|
|
292
|
+
|
|
293
|
+
# The start date and time of the block associated with the awards
|
|
294
|
+
start: DateTime = attr(name="StartTime")
|
|
295
|
+
|
|
296
|
+
# The end date and time of the block associated with the awards
|
|
297
|
+
end: DateTime = attr(name="EndTime")
|
|
298
|
+
|
|
299
|
+
# The direction of the associated trades
|
|
300
|
+
direction: Direction = attr(name="Direction")
|
|
301
|
+
|
|
302
|
+
# The bid awards associated with these parameters
|
|
303
|
+
data: List[Award] = element(tag="AwardResultsData", min_length=1)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
class AwardResponse(AwardQuery, tag="AwardResultsQuery"):
|
|
307
|
+
"""Contains the results of a bid award query."""
|
|
308
|
+
|
|
309
|
+
# The bid awards associated with the query
|
|
310
|
+
results: Optional[List[AwardResult]] = wrapped(default=None, path="AwardResultsQueryResponse")
|
mms_client/types/enums.py
CHANGED
|
@@ -19,8 +19,55 @@ class AreaCode(Enum):
|
|
|
19
19
|
OKINAWA = "10"
|
|
20
20
|
|
|
21
21
|
|
|
22
|
+
class Direction(Enum):
|
|
23
|
+
"""Represents the reserve direction of the offer."""
|
|
24
|
+
|
|
25
|
+
SELL = "1" # Increasing the reserves (sell)
|
|
26
|
+
# Note: Support for the BUY direction was removed from the MMS API
|
|
27
|
+
# BUY = "2" # Decreasing the reserves (buy)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ContractResult(Enum):
|
|
31
|
+
"""Represents the result of a contract."""
|
|
32
|
+
|
|
33
|
+
FULL = "1"
|
|
34
|
+
PARTIAL = "2"
|
|
35
|
+
|
|
36
|
+
|
|
22
37
|
class Frequency(IntEnum):
|
|
23
38
|
"""Represents the frequency of power sources."""
|
|
24
39
|
|
|
25
40
|
EAST = 50
|
|
26
41
|
WEST = 60
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ResourceType(Enum):
|
|
45
|
+
"""How the power generation unit produces electricity."""
|
|
46
|
+
|
|
47
|
+
THERMAL = "01"
|
|
48
|
+
HYDRO = "02"
|
|
49
|
+
PUMP = "03"
|
|
50
|
+
BATTERY = "04"
|
|
51
|
+
VPP_GEN = "05"
|
|
52
|
+
VPP_GEN_AND_DEM = "06"
|
|
53
|
+
VPP_DEM = "07"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class CommandMonitorMethod(Enum):
|
|
57
|
+
"""Describes how the power generation unit is monitored and commanded."""
|
|
58
|
+
|
|
59
|
+
DEDICATED_LINE = "1"
|
|
60
|
+
SIMPLE_COMMAND = "2"
|
|
61
|
+
OFFLINE = "3"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class BooleanFlag(Enum):
|
|
65
|
+
"""Represents a Boolean value as an enumeration.
|
|
66
|
+
|
|
67
|
+
There are many places throughout the MMS documenation where Boolean values are treated as enums for reasons that are
|
|
68
|
+
not clear. This is a common pattern and this class is provided to make it easier to handle these cases without
|
|
69
|
+
having many different enum classes
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
YES = "1"
|
|
73
|
+
NO = "0"
|
mms_client/types/fields.py
CHANGED
|
@@ -69,6 +69,26 @@ def transaction_id(alias: str, optional: bool = False):
|
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
|
|
72
|
+
def offer_id(alias: str, optional: bool = False):
|
|
73
|
+
"""Create a field for an offer ID.
|
|
74
|
+
|
|
75
|
+
Arguments:
|
|
76
|
+
alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
|
|
77
|
+
to the JSON/XML key.
|
|
78
|
+
optional (bool): If True, the field will be optional with a default of None. If False, the field will be
|
|
79
|
+
required, with no default.
|
|
80
|
+
|
|
81
|
+
Returns: A Pydantic Field object for the offer ID.
|
|
82
|
+
"""
|
|
83
|
+
return attr(
|
|
84
|
+
default=None if optional else PydanticUndefined,
|
|
85
|
+
name=alias,
|
|
86
|
+
min_length=1,
|
|
87
|
+
max_length=30,
|
|
88
|
+
pattern=r"^[a-zA-Z0-9_-]*$",
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
72
92
|
def capacity(alias: str, minimum: int, optional: bool = False):
|
|
73
93
|
"""Create a field for a capacity value.
|
|
74
94
|
|
|
@@ -98,18 +118,19 @@ def power_positive(alias: str, optional: bool = False):
|
|
|
98
118
|
return attr(default=None if optional else PydanticUndefined, name=alias, gt=0, le=10000000)
|
|
99
119
|
|
|
100
120
|
|
|
101
|
-
def price(alias: str, optional: bool = False):
|
|
121
|
+
def price(alias: str, limit: float, optional: bool = False):
|
|
102
122
|
"""Create a field for a price value.
|
|
103
123
|
|
|
104
124
|
Arguments:
|
|
105
125
|
alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
|
|
106
126
|
to the JSON/XML key.
|
|
127
|
+
limit (int): The maximum value for the price field.
|
|
107
128
|
optional (bool): If True, the field will be optional with a default of None. If False, the field will be
|
|
108
129
|
required, with no default.
|
|
109
130
|
|
|
110
131
|
Returns: A Pydantic Field object for the price value.
|
|
111
132
|
"""
|
|
112
|
-
return attr(default=None if optional else PydanticUndefined, name=alias, ge=0.00,
|
|
133
|
+
return attr(default=None if optional else PydanticUndefined, name=alias, ge=0.00, le=limit, decimal_places=2)
|
|
113
134
|
|
|
114
135
|
|
|
115
136
|
def percentage(alias: str, optional: bool = False):
|
|
@@ -140,6 +161,22 @@ def dr_patter_number(alias: str, optional: bool = False):
|
|
|
140
161
|
return attr(default=None if optional else PydanticUndefined, name=alias, ge=1, le=20)
|
|
141
162
|
|
|
142
163
|
|
|
164
|
+
def dr_pattern_name(alias: str, optional: bool = False):
|
|
165
|
+
"""Create a field for a DR pattern name.
|
|
166
|
+
|
|
167
|
+
Arguments:
|
|
168
|
+
alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
|
|
169
|
+
to the JSON/XML key.
|
|
170
|
+
optional (bool): If True, the field will be optional with a default of None. If False, the field will be
|
|
171
|
+
required, with no default.
|
|
172
|
+
|
|
173
|
+
Returns: A Pydantic Field object for the DR pattern name.
|
|
174
|
+
"""
|
|
175
|
+
return attr(
|
|
176
|
+
default=None if optional else PydanticUndefined, name=alias, min_length=1, max_length=20, pattern=JAPANESE_TEXT
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
143
180
|
def pattern_name(alias: str, optional: bool = False):
|
|
144
181
|
"""Create a field for a pattern name.
|
|
145
182
|
|
|
@@ -246,6 +283,40 @@ def resource_short_name(alias: str, optional: bool = False):
|
|
|
246
283
|
)
|
|
247
284
|
|
|
248
285
|
|
|
286
|
+
def contract_id(alias: str, optional: bool = False):
|
|
287
|
+
"""Create a field for a contract ID.
|
|
288
|
+
|
|
289
|
+
Arguments:
|
|
290
|
+
alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
|
|
291
|
+
to the JSON/XML key.
|
|
292
|
+
optional (bool): If True, the field will be optional with a default of None. If False, the field will be
|
|
293
|
+
required, with no default.
|
|
294
|
+
|
|
295
|
+
Returns: A Pydantic Field object for the contract ID.
|
|
296
|
+
"""
|
|
297
|
+
return attr(
|
|
298
|
+
default=None if optional else PydanticUndefined,
|
|
299
|
+
name=alias,
|
|
300
|
+
min_length=19,
|
|
301
|
+
max_length=19,
|
|
302
|
+
pattern=r"^[a-zA-Z0-9]**$",
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def jbms_id(alias: str, optional: bool = False):
|
|
307
|
+
"""Create a field for a JBMS ID.
|
|
308
|
+
|
|
309
|
+
Arguments:
|
|
310
|
+
alias (str): The name of the alias to assign to the Pydanitc field. This value will be used to map the field
|
|
311
|
+
to the JSON/XML key.
|
|
312
|
+
optional (bool): If True, the field will be optional with a default of None. If False, the field will be
|
|
313
|
+
required, with no default.
|
|
314
|
+
|
|
315
|
+
Returns: A Pydantic Field object for the JBMS ID.
|
|
316
|
+
"""
|
|
317
|
+
return attr(default=None if optional else PydanticUndefined, name=alias, ge=1, lt=1000000000000000000)
|
|
318
|
+
|
|
319
|
+
|
|
249
320
|
def system_code(alias: str, optional: bool = False):
|
|
250
321
|
"""Create a field for a system code.
|
|
251
322
|
|
mms_client/types/market.py
CHANGED
|
@@ -33,9 +33,6 @@ class BaseMarketRequest(Envelope):
|
|
|
33
33
|
# will be checked against the certificate used to make the request.
|
|
34
34
|
user: str = attr(name="UserName", min_length=1, max_length=12, pattern=r"^[A-Z0-9]*$")
|
|
35
35
|
|
|
36
|
-
# The type of market for which the data is being submitted
|
|
37
|
-
market_type: Optional[MarketType] = attr(default=None, name="MarketType")
|
|
38
|
-
|
|
39
36
|
|
|
40
37
|
class MarketQuery(BaseMarketRequest):
|
|
41
38
|
"""Represents the base fields for a market query."""
|
|
@@ -48,6 +45,9 @@ class MarketQuery(BaseMarketRequest):
|
|
|
48
45
|
class MarketSubmit(BaseMarketRequest):
|
|
49
46
|
"""Represents the base fields for a market registration request."""
|
|
50
47
|
|
|
48
|
+
# The type of market for which the data is being submitted
|
|
49
|
+
market_type: Optional[MarketType] = attr(default=None, name="MarketType")
|
|
50
|
+
|
|
51
51
|
# If the market type is specified as "DAM" (day-ahead market), the number of days should be specified as "1".
|
|
52
52
|
# Otherwise, this field indicates the number of days ahead for which the data is being submitted.
|
|
53
53
|
days: int = attr(default=1, name="NumOfDays", ge=1, le=31)
|
|
@@ -56,6 +56,9 @@ class MarketSubmit(BaseMarketRequest):
|
|
|
56
56
|
class MarketCancel(BaseMarketRequest):
|
|
57
57
|
"""Represents the base fields for a market cancellation request."""
|
|
58
58
|
|
|
59
|
+
# The type of market for which the data is being submitted
|
|
60
|
+
market_type: MarketType = attr(name="MarketType")
|
|
61
|
+
|
|
59
62
|
# If the market type is specified as "DAM" (day-ahead market), the number of days should be specified as "1".
|
|
60
63
|
# Otherwise, this field indicates the number of days ahead for which the data is being cancelled.
|
|
61
64
|
days: int = attr(default=1, name="NumOfDays", ge=1, le=31)
|
mms_client/types/offer.py
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
"""Contains objects for MMS offers."""
|
|
2
2
|
|
|
3
3
|
from decimal import Decimal
|
|
4
|
-
from enum import Enum
|
|
5
4
|
from typing import List
|
|
6
5
|
from typing import Optional
|
|
7
6
|
|
|
8
|
-
from pydantic_core import PydanticUndefined
|
|
9
7
|
from pydantic_extra_types.pendulum_dt import DateTime
|
|
10
8
|
from pydantic_xml import attr
|
|
11
9
|
from pydantic_xml import element
|
|
12
10
|
|
|
13
11
|
from mms_client.types.base import Payload
|
|
14
12
|
from mms_client.types.enums import AreaCode
|
|
13
|
+
from mms_client.types.enums import Direction
|
|
15
14
|
from mms_client.types.fields import company_short_name
|
|
16
15
|
from mms_client.types.fields import dr_patter_number
|
|
16
|
+
from mms_client.types.fields import offer_id
|
|
17
17
|
from mms_client.types.fields import operator_code
|
|
18
18
|
from mms_client.types.fields import participant
|
|
19
19
|
from mms_client.types.fields import power_positive
|
|
@@ -24,34 +24,6 @@ from mms_client.types.fields import system_code
|
|
|
24
24
|
from mms_client.types.market import MarketType
|
|
25
25
|
|
|
26
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
27
|
class OfferStack(Payload):
|
|
56
28
|
"""Represents a single price-quantity pair in an offer request.
|
|
57
29
|
|
|
@@ -94,7 +66,7 @@ class OfferStack(Payload):
|
|
|
94
66
|
tertiary_2_qty_kw: Optional[int] = power_positive("Tertiary2OfferQuantityInKw", True)
|
|
95
67
|
|
|
96
68
|
# The unit price of the power, in JPY/kW/segment
|
|
97
|
-
unit_price: Decimal = price("OfferUnitPrice")
|
|
69
|
+
unit_price: Decimal = price("OfferUnitPrice", 10000.00)
|
|
98
70
|
|
|
99
71
|
# The ID of the offer to which this stack belongs
|
|
100
72
|
id: Optional[str] = offer_id("OfferId", True)
|
mms_client/types/resource.py
CHANGED
|
@@ -16,7 +16,10 @@ from pydantic_xml import wrapped
|
|
|
16
16
|
|
|
17
17
|
from mms_client.types.base import Payload
|
|
18
18
|
from mms_client.types.enums import AreaCode
|
|
19
|
+
from mms_client.types.enums import BooleanFlag
|
|
20
|
+
from mms_client.types.enums import CommandMonitorMethod
|
|
19
21
|
from mms_client.types.enums import Frequency
|
|
22
|
+
from mms_client.types.enums import ResourceType
|
|
20
23
|
from mms_client.types.fields import ASCII_TEXT
|
|
21
24
|
from mms_client.types.fields import JAPANESE_ASCII_TEXT
|
|
22
25
|
from mms_client.types.fields import JAPANESE_TEXT
|
|
@@ -180,18 +183,6 @@ class ContractType(Enum):
|
|
|
180
183
|
REMAINING_RESERVE_UTILIZATION = "6"
|
|
181
184
|
|
|
182
185
|
|
|
183
|
-
class ResourceType(Enum):
|
|
184
|
-
"""How the power generation unit produces electricity."""
|
|
185
|
-
|
|
186
|
-
THERMAL = "01"
|
|
187
|
-
HYDRO = "02"
|
|
188
|
-
PUMP = "03"
|
|
189
|
-
BATTERY = "04"
|
|
190
|
-
VPP_GEN = "05"
|
|
191
|
-
VPP_GEN_AND_DEM = "06"
|
|
192
|
-
VPP_DEM = "07"
|
|
193
|
-
|
|
194
|
-
|
|
195
186
|
class RemainingReserveAvailability(Enum):
|
|
196
187
|
"""Describes the availability of remaining reserves for a power generation unit."""
|
|
197
188
|
|
|
@@ -201,14 +192,6 @@ class RemainingReserveAvailability(Enum):
|
|
|
201
192
|
AVAILABLE_FOR_UP_AND_DOWN = "3"
|
|
202
193
|
|
|
203
194
|
|
|
204
|
-
class CommandMonitorMethod(Enum):
|
|
205
|
-
"""Describes how the power generation unit is monitored and commanded."""
|
|
206
|
-
|
|
207
|
-
DEDICATED_LINE = "1"
|
|
208
|
-
SIMPLE_COMMAND = "2"
|
|
209
|
-
OFFLINE = "3"
|
|
210
|
-
|
|
211
|
-
|
|
212
195
|
class SignalType(Enum):
|
|
213
196
|
"""Describes the type of signal used to monitor and command a power generation unit."""
|
|
214
197
|
|
|
@@ -231,17 +214,6 @@ class ThermalType(Enum):
|
|
|
231
214
|
OTHER = "9"
|
|
232
215
|
|
|
233
216
|
|
|
234
|
-
class BooleanFlag(Enum):
|
|
235
|
-
"""Describes a boolean flag.
|
|
236
|
-
|
|
237
|
-
This could literally be a Boolean value but it's coded in the reference as an enum and I don't want to create a
|
|
238
|
-
custom serializer for it.
|
|
239
|
-
"""
|
|
240
|
-
|
|
241
|
-
YES = "1"
|
|
242
|
-
NO = "0"
|
|
243
|
-
|
|
244
|
-
|
|
245
217
|
class OverrideOption(Enum):
|
|
246
218
|
"""Describes the override option for a power generation unit."""
|
|
247
219
|
|
|
@@ -761,7 +733,7 @@ class ResourceData(Payload, tag="Resource"):
|
|
|
761
733
|
has_contract: Optional[BooleanFlag] = attr(default=None, name="ContractExistence")
|
|
762
734
|
|
|
763
735
|
# The maximum bid price for POWER_SUPPLY_1 power, in JPY/kW/hr
|
|
764
|
-
declared_maximum_unit_price_kWh: Annotated[Decimal, price("DeclaredMaximumUnitPrice", True)]
|
|
736
|
+
declared_maximum_unit_price_kWh: Annotated[Decimal, price("DeclaredMaximumUnitPrice", 10000.00, True)]
|
|
765
737
|
|
|
766
738
|
# Presence of voltage regulation function.
|
|
767
739
|
voltage_adjustable: Optional[BooleanFlag] = attr(default=None, name="VoltageAdjustment")
|
mms_client/types/transport.py
CHANGED
|
@@ -25,7 +25,7 @@ class RequestType(Enum):
|
|
|
25
25
|
class RequestDataType(Enum):
|
|
26
26
|
"""Represents the type of data to be sent to the MMS server.
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
NOTE: JSON is currently not supported and has been left in for future use.
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
31
|
JSON = "JSON"
|
|
@@ -35,7 +35,7 @@ class RequestDataType(Enum):
|
|
|
35
35
|
class ResponseDataType(Enum):
|
|
36
36
|
"""Represents the type of data to be received from the MMS server.
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
NOTE: JSON is currently not supported and has been left in for future use.
|
|
39
39
|
"""
|
|
40
40
|
|
|
41
41
|
XML = "XML"
|
|
@@ -4,6 +4,7 @@ from functools import lru_cache
|
|
|
4
4
|
from io import BytesIO
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
from typing import Dict
|
|
7
|
+
from typing import List
|
|
7
8
|
from typing import Optional
|
|
8
9
|
from typing import Tuple
|
|
9
10
|
from typing import Type
|
|
@@ -15,6 +16,7 @@ from lxml.etree import XMLSchema
|
|
|
15
16
|
from lxml.etree import _Element as Element
|
|
16
17
|
from lxml.etree import parse
|
|
17
18
|
from pydantic_xml import element
|
|
19
|
+
from pydantic_xml.typedefs import EntityLocation
|
|
18
20
|
|
|
19
21
|
from mms_client.types.base import E
|
|
20
22
|
from mms_client.types.base import Messages
|
|
@@ -62,17 +64,42 @@ class Serializer:
|
|
|
62
64
|
# First, create our payload class from the payload and data types
|
|
63
65
|
payload_cls = _create_request_payload_type(
|
|
64
66
|
self._payload_key,
|
|
65
|
-
type(request_envelope),
|
|
66
|
-
type(request_data),
|
|
67
|
+
type(request_envelope),
|
|
68
|
+
type(request_data),
|
|
69
|
+
False, # type: ignore[arg-type]
|
|
67
70
|
)
|
|
68
71
|
|
|
69
72
|
# Next, inject the payload and data into the payload class
|
|
70
|
-
#
|
|
73
|
+
# NOTE: this returns a type that inherits from PayloadBase and the arguments provided to the initializer
|
|
71
74
|
# here are correct, but mypy thinks they are incorrect because it doesn't understand the the inherited type
|
|
72
75
|
payload = payload_cls(request_envelope, request_data, self._xsd.value) # type: ignore[call-arg, misc]
|
|
73
76
|
|
|
74
77
|
# Finally, convert the payload to XML and return it
|
|
75
|
-
#
|
|
78
|
+
# NOTE: we provided the encoding here so this will return bytes, not a string
|
|
79
|
+
return payload.to_xml(skip_empty=True, encoding="utf-8", xml_declaration=True) # type: ignore[return-value]
|
|
80
|
+
|
|
81
|
+
def serialize_multi(self, request_envelope: E, request_data: List[P], request_type: Type[P]) -> bytes:
|
|
82
|
+
"""Serialize the envelope and data to a byte string for sending to the MMS server.
|
|
83
|
+
|
|
84
|
+
Arguments:
|
|
85
|
+
request_envelope (Envelope): The envelope to be serialized.
|
|
86
|
+
request_data (List[Payload]): The data to be serialized.
|
|
87
|
+
request_type (Type[Payload]): The type of data to be serialized.
|
|
88
|
+
|
|
89
|
+
Returns: A byte string containing the XML-formatted data to be sent to the MMS server.
|
|
90
|
+
"""
|
|
91
|
+
# First, create our payload class from the payload and data types
|
|
92
|
+
payload_cls = _create_request_payload_type(
|
|
93
|
+
self._payload_key, type(request_envelope), request_type, True # type: ignore[arg-type]
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Next, inject the payload and data into the payload class
|
|
97
|
+
# NOTE: this returns a type that inherits from PayloadBase and the arguments provided to the initializer
|
|
98
|
+
# here are correct, but mypy thinks they are incorrect because it doesn't understand the the inherited type
|
|
99
|
+
payload = payload_cls(request_envelope, request_data, self._xsd.value) # type: ignore[call-arg, misc]
|
|
100
|
+
|
|
101
|
+
# Finally, convert the payload to XML and return it
|
|
102
|
+
# NOTE: we provided the encoding here so this will return bytes, not a string
|
|
76
103
|
return payload.to_xml(skip_empty=True, encoding="utf-8", xml_declaration=True) # type: ignore[return-value]
|
|
77
104
|
|
|
78
105
|
def deserialize(self, data: bytes, envelope_type: Type[E], data_type: Type[P]) -> Response[E, P]:
|
|
@@ -164,7 +191,7 @@ class Serializer:
|
|
|
164
191
|
|
|
165
192
|
# Now, verify that the response doesn't contain an unexpected data type and then retrieve the payload data
|
|
166
193
|
# from within the envelope
|
|
167
|
-
#
|
|
194
|
+
# NOTE: apparently, mypy doesn't know about setter-getter properties either...
|
|
168
195
|
self._verify_tree_data_tag(env_node, data_type)
|
|
169
196
|
resp.payload = [
|
|
170
197
|
self._from_tree_data(item, data_type) for item in env_node.findall(get_tag(data_type)) # type: ignore[misc]
|
|
@@ -245,7 +272,13 @@ class Serializer:
|
|
|
245
272
|
)
|
|
246
273
|
|
|
247
274
|
def _from_tree_messages(
|
|
248
|
-
self,
|
|
275
|
+
self,
|
|
276
|
+
raw: Element,
|
|
277
|
+
envelope_type: Type[E],
|
|
278
|
+
current_type: Type[P],
|
|
279
|
+
root: str,
|
|
280
|
+
multi: bool,
|
|
281
|
+
wrapped: bool = False,
|
|
249
282
|
) -> Dict[str, Messages]:
|
|
250
283
|
"""Attempt to extract the messages from within the payload.
|
|
251
284
|
|
|
@@ -261,6 +294,7 @@ class Serializer:
|
|
|
261
294
|
multi (bool): Whether we're processing a list of nodes or a single node. If called with the
|
|
262
295
|
payload root, this value will determine whether we're processing a multi-
|
|
263
296
|
response or a single response.
|
|
297
|
+
wrapped (bool): Whether or not this type is referenced from a wrapped field.
|
|
264
298
|
"""
|
|
265
299
|
# First, find the Messages node in the raw data
|
|
266
300
|
message_node = raw.find("Messages")
|
|
@@ -283,40 +317,41 @@ class Serializer:
|
|
|
283
317
|
current_type,
|
|
284
318
|
f"{root}.{envelope_type.__name__}",
|
|
285
319
|
multi,
|
|
320
|
+
False,
|
|
286
321
|
)
|
|
287
322
|
)
|
|
288
|
-
elif root.endswith(envelope_type.__name__):
|
|
323
|
+
elif root.endswith(envelope_type.__name__) or wrapped:
|
|
289
324
|
messages.update(
|
|
290
|
-
self._from_tree_messages_inner(
|
|
325
|
+
self._from_tree_messages_inner(
|
|
326
|
+
raw, envelope_type, current_type, root, get_tag(current_type), multi, False
|
|
327
|
+
)
|
|
291
328
|
)
|
|
292
329
|
else:
|
|
293
330
|
# Iterate over each field on the current type...
|
|
294
331
|
for field in current_type.model_fields.values():
|
|
295
332
|
|
|
296
|
-
# First, get the arguments and origin of the field's annotation
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
has_args = len(args) > 0
|
|
333
|
+
# First, get the arguments and origin of the field's annotation. Occaisionally, we'll have an optional
|
|
334
|
+
# list. In this case, we'll have to do get_args twice to traverse the type tree.
|
|
335
|
+
arg, multi = _get_field_typing(field.annotation) # type: ignore[arg-type]
|
|
300
336
|
|
|
301
337
|
# Next, check if the annotation is a subclass of Payload or else if it's a collection of Payload. If
|
|
302
338
|
# neither of these is the case, we can skip this field.
|
|
303
|
-
#
|
|
304
|
-
if not (
|
|
305
|
-
(has_args and issubclass(args[0], Payload))
|
|
306
|
-
or (not has_args and issubclass(field.annotation, Payload)) # type: ignore[arg-type]
|
|
307
|
-
):
|
|
339
|
+
# NOTE: All our fields are annotated so there's no need to check if they're not
|
|
340
|
+
if not issubclass(arg, Payload):
|
|
308
341
|
continue
|
|
309
342
|
|
|
310
343
|
# Finally, call this method recursively for the field and update the messages with the results
|
|
311
|
-
#
|
|
344
|
+
# NOTE: All our fields are annotated as XmlEntityInfo, so they have the "path" and "location" attributes
|
|
345
|
+
print(field)
|
|
312
346
|
messages.update(
|
|
313
347
|
self._from_tree_messages_inner(
|
|
314
348
|
raw,
|
|
315
349
|
envelope_type,
|
|
316
|
-
|
|
350
|
+
arg,
|
|
317
351
|
root,
|
|
318
352
|
field.path, # type: ignore[attr-defined]
|
|
319
|
-
|
|
353
|
+
multi,
|
|
354
|
+
field.location == EntityLocation.WRAPPED, # type: ignore[attr-defined]
|
|
320
355
|
)
|
|
321
356
|
)
|
|
322
357
|
|
|
@@ -324,7 +359,14 @@ class Serializer:
|
|
|
324
359
|
return messages
|
|
325
360
|
|
|
326
361
|
def _from_tree_messages_inner(
|
|
327
|
-
self,
|
|
362
|
+
self,
|
|
363
|
+
raw: Element,
|
|
364
|
+
envelope_type: Type[E],
|
|
365
|
+
current_type: Type[P],
|
|
366
|
+
root: str,
|
|
367
|
+
tag: str,
|
|
368
|
+
multi: bool,
|
|
369
|
+
wrapped: bool,
|
|
328
370
|
) -> Dict[str, Messages]:
|
|
329
371
|
"""Attempt to extract the messages from within the payload at the current level.
|
|
330
372
|
|
|
@@ -336,6 +378,7 @@ class Serializer:
|
|
|
336
378
|
tag (str): The tag of the current node being processed.
|
|
337
379
|
multi (bool): If True, the payload will be a multi-response; otherwise, it will be a single
|
|
338
380
|
response.
|
|
381
|
+
wrapped (bool): Whether or not this field is a wrapped field.
|
|
339
382
|
|
|
340
383
|
Returns: A dictionary mapping messages to where they were found in the response.
|
|
341
384
|
"""
|
|
@@ -353,12 +396,20 @@ class Serializer:
|
|
|
353
396
|
# Otherwise, we'll call this method recursively for each node and update the messages with the results.
|
|
354
397
|
messages = {}
|
|
355
398
|
for i, node in enumerate(nodes):
|
|
356
|
-
messages.update(
|
|
399
|
+
messages.update(
|
|
400
|
+
self._from_tree_messages(
|
|
401
|
+
node, envelope_type, current_type, path_base if wrapped else f"{path_base}[{i}]", True, wrapped
|
|
402
|
+
)
|
|
403
|
+
)
|
|
357
404
|
return messages
|
|
358
405
|
|
|
359
406
|
# If we reached this point then we are processing a single item so find the associated
|
|
360
407
|
child = raw.find(tag)
|
|
361
|
-
return
|
|
408
|
+
return (
|
|
409
|
+
{}
|
|
410
|
+
if child is None
|
|
411
|
+
else self._from_tree_messages(child, envelope_type, current_type, path_base, False, wrapped)
|
|
412
|
+
)
|
|
362
413
|
|
|
363
414
|
def _from_xml(self, data: bytes) -> Element:
|
|
364
415
|
"""Parse the XML file, returning the resulting XML tree.
|
|
@@ -439,7 +490,12 @@ def _create_response_common_type(tag_type: Type[Union[E, P]]) -> Type[ResponseCo
|
|
|
439
490
|
|
|
440
491
|
|
|
441
492
|
@lru_cache(maxsize=None)
|
|
442
|
-
def _create_request_payload_type(
|
|
493
|
+
def _create_request_payload_type(
|
|
494
|
+
key: str,
|
|
495
|
+
envelope_type: Type[E],
|
|
496
|
+
data_type: Type[Union[P, List[P]]],
|
|
497
|
+
multi: bool,
|
|
498
|
+
) -> Type[PayloadBase]:
|
|
443
499
|
"""Create a new payload type for the given payload and data types.
|
|
444
500
|
|
|
445
501
|
This method is intended to save us the overhead of writing a new class for each payload type. Instead, we can
|
|
@@ -453,17 +509,21 @@ def _create_request_payload_type(key: str, envelope_type: Type[E], data_type: Ty
|
|
|
453
509
|
key: str The tag to use for the parent element of the payload.
|
|
454
510
|
envelope_type (Type[Envelope]): The type of payload to be constructed.
|
|
455
511
|
data_type (Type[Payload]): The type of data to be constructed.
|
|
512
|
+
multi (bool): If True, the payload will be a list; otherwise, it will be a singleton.
|
|
456
513
|
|
|
457
514
|
Returns: A new payload type that can be used for serialization.
|
|
458
515
|
""" # fmt: skip
|
|
459
|
-
# First, create
|
|
516
|
+
# First, create our data type
|
|
517
|
+
payload_type = List[data_type] if multi else data_type # type: ignore[valid-type]
|
|
518
|
+
|
|
519
|
+
# Next, create a wrapper for our data type that will be used to store the data in the payload
|
|
460
520
|
class Envelope(envelope_type): # type: ignore[valid-type, misc]
|
|
461
521
|
"""Wrapper for the data type that will be used to store the data in the payload."""
|
|
462
522
|
|
|
463
523
|
# The data to be stored in the payload
|
|
464
|
-
data:
|
|
524
|
+
data: payload_type = element(tag=get_tag(data_type)) # type: ignore[valid-type, type-var]
|
|
465
525
|
|
|
466
|
-
def __init__(self, envelope: envelope_type, data:
|
|
526
|
+
def __init__(self, envelope: envelope_type, data: payload_type): # type: ignore[valid-type]
|
|
467
527
|
"""Create a new envelope to store payload data.
|
|
468
528
|
|
|
469
529
|
Arguments:
|
|
@@ -474,14 +534,14 @@ def _create_request_payload_type(key: str, envelope_type: Type[E], data_type: Ty
|
|
|
474
534
|
obj["data"] = data
|
|
475
535
|
super().__init__(**obj)
|
|
476
536
|
|
|
477
|
-
#
|
|
537
|
+
# Now, create our payload type that actually contains all the XML data
|
|
478
538
|
class RQPayload(PayloadBase, tag=key): # type: ignore[call-arg]
|
|
479
539
|
"""The payload type that will be used for serialization."""
|
|
480
540
|
|
|
481
541
|
# The payload containing our request object and any data
|
|
482
542
|
envelope: Envelope = element(tag=envelope_type.__name__)
|
|
483
543
|
|
|
484
|
-
def __init__(self, envelope: envelope_type, data:
|
|
544
|
+
def __init__(self, envelope: envelope_type, data: payload_type, schema: str): # type: ignore[valid-type]
|
|
485
545
|
"""Create a new payload containing the request object and any data.
|
|
486
546
|
|
|
487
547
|
Arguments:
|
|
@@ -512,6 +572,45 @@ def _find_or_fail(node: Element, tag: str) -> Element:
|
|
|
512
572
|
return found
|
|
513
573
|
|
|
514
574
|
|
|
575
|
+
def _get_field_typing(typ: Type) -> Tuple[Type, bool]:
|
|
576
|
+
"""Retrieve the field's actual type and whether or not the field is a collection.
|
|
577
|
+
|
|
578
|
+
This method is designed to find the inner type of fields in the following cases:
|
|
579
|
+
1. Fundamental types and classes (e.g. int, str, Award, Offer, etc.)
|
|
580
|
+
2. Nullable fundamental types and classes
|
|
581
|
+
3. Collections of fundamental types and classes
|
|
582
|
+
4. Nullable collections of fundamental types and classes
|
|
583
|
+
|
|
584
|
+
Arguments:
|
|
585
|
+
typ (Type): The type of the field to retrieve the inner type for.
|
|
586
|
+
|
|
587
|
+
Returns:
|
|
588
|
+
Type: The inner type of the field.
|
|
589
|
+
bool: Whether or not the field is a collection.
|
|
590
|
+
"""
|
|
591
|
+
# First, check for the case where we have a fundamental type. If we do then we can return the type and False.
|
|
592
|
+
init = get_args(typ)
|
|
593
|
+
if len(init) == 0:
|
|
594
|
+
return typ, False
|
|
595
|
+
|
|
596
|
+
# Next, iterate over the type hierarchy and repeat the operation until we find the leaf type.
|
|
597
|
+
args = [get_args(typ)]
|
|
598
|
+
while len(args[-1]) > 1:
|
|
599
|
+
temp = get_args(args[-1][0])
|
|
600
|
+
if len(temp) == 0:
|
|
601
|
+
break
|
|
602
|
+
args.append(temp)
|
|
603
|
+
|
|
604
|
+
# Now, find the origin type of the field. This will be the lowest type in the hierarchy that isn't a multi-type.
|
|
605
|
+
# If there aren't any of these, then we'll just use the original type.
|
|
606
|
+
origin_type = next(
|
|
607
|
+
filter(lambda x: x is not None, map(lambda arg: arg[0] if len(arg) > 1 else None, reversed(args))), typ
|
|
608
|
+
)
|
|
609
|
+
|
|
610
|
+
# Finally, return the inner type and whether or not the origin type is a list
|
|
611
|
+
return args[-1][0] if len(args) > 0 else typ, get_origin(origin_type) is list # typing: ignore[return-value]
|
|
612
|
+
|
|
613
|
+
|
|
515
614
|
def get_tag(data_type: Type[P]) -> str:
|
|
516
615
|
"""Get the tag for the given data type.
|
|
517
616
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mms-client
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.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
|
|
@@ -198,6 +198,7 @@ This client is not complete. Currently, it supports the following endpoints:
|
|
|
198
198
|
- MarketSubmit_OfferData
|
|
199
199
|
- MarketQuery_OfferQuery
|
|
200
200
|
- MarketCancel_OfferCancel
|
|
201
|
+
- MarketQuery_AwardResultsQuery
|
|
201
202
|
- RegistrationSubmit_Resource
|
|
202
203
|
- RegistrationQuery_Resource
|
|
203
204
|
|
|
@@ -12,25 +12,26 @@ 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=mcUL-AZpaJETVkzAOoazmKfI5O3cJ0KWzIYQ5bjpNuE,25839
|
|
16
|
+
mms_client/services/market.py,sha256=aIWi52BFvDmtuipKnSZNprwbFYGYBcV6sZCQqMR_D2g,7574
|
|
17
17
|
mms_client/services/omi.py,sha256=h6cM5U3-iSm0YiIaJwqYTZeI5uhLbA7FPxh_qy_3qww,521
|
|
18
|
-
mms_client/services/registration.py,sha256=
|
|
18
|
+
mms_client/services/registration.py,sha256=46Scntwlc9CtCO-tV6uEnr4NrVGJiUqhvksII13CGAE,3651
|
|
19
19
|
mms_client/services/report.py,sha256=HYVJNwEHo6ZC6497UqO5y1IqZ2ga3kVH5AepdxhYfug,537
|
|
20
20
|
mms_client/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
mms_client/types/award.py,sha256=BWE9V_KHXpg_cW1LZsetVrPs2hZDOklvRpNnoZtmR3k,14139
|
|
21
22
|
mms_client/types/base.py,sha256=wrDPn9io30in_w2qKa4A503gX_gAaMT7a1MpZ3HcmIc,9701
|
|
22
|
-
mms_client/types/enums.py,sha256=
|
|
23
|
-
mms_client/types/fields.py,sha256=
|
|
24
|
-
mms_client/types/market.py,sha256=
|
|
25
|
-
mms_client/types/offer.py,sha256=
|
|
23
|
+
mms_client/types/enums.py,sha256=YJ58FbhyQ0TVlf8Z-Dg1UfVu8CurY5b21Cy5-WYkJ0I,1629
|
|
24
|
+
mms_client/types/fields.py,sha256=pa5qvQVwEr8dh44IGHyYqgJYTYyTIeAjBW6CylXrkP0,14785
|
|
25
|
+
mms_client/types/market.py,sha256=IbXsH4Q5MJI-CEvGvZlzv2S36mX_Ea02U11Ik-NwSxQ,2706
|
|
26
|
+
mms_client/types/offer.py,sha256=KosFiKRMnt7XwlLBUfjHUGHiWzrMJUPPhGQMxgdeepM,6791
|
|
26
27
|
mms_client/types/registration.py,sha256=Nir73S3ffpk0O_fnTD2alFaqV1k67_8dcyyduXvPBI4,1381
|
|
27
|
-
mms_client/types/resource.py,sha256=
|
|
28
|
-
mms_client/types/transport.py,sha256=
|
|
28
|
+
mms_client/types/resource.py,sha256=TQnY3JLHRgQhQrG6ISquw-BQgKSr8TGuqn9ItWxWz_w,65974
|
|
29
|
+
mms_client/types/transport.py,sha256=DPjWs34UW915GkUCJWKuDZmsjS6mRdRXgcGISduN_Bc,4399
|
|
29
30
|
mms_client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
31
|
mms_client/utils/errors.py,sha256=jYdlG4OPI82s0fJcXCRNlKeEixDUSSAxjs_7C16qVL4,2306
|
|
31
|
-
mms_client/utils/serialization.py,sha256=
|
|
32
|
+
mms_client/utils/serialization.py,sha256=vTJZSjhfTph2tDAYbLAhnz89i0qXJ8FINGdve67cJOU,29534
|
|
32
33
|
mms_client/utils/web.py,sha256=fcdCtdDrHBPyhIlTcyiuAk3D3TlW8HmUw-wGfpG4KTA,9653
|
|
33
|
-
mms_client-1.
|
|
34
|
-
mms_client-1.
|
|
35
|
-
mms_client-1.
|
|
36
|
-
mms_client-1.
|
|
34
|
+
mms_client-1.3.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
|
|
35
|
+
mms_client-1.3.0.dist-info/METADATA,sha256=7yCFpQBubFp8jx3Nd90fj0wfXwSiNIywKd61wV798qE,14774
|
|
36
|
+
mms_client-1.3.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
|
37
|
+
mms_client-1.3.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|