mms-client 1.2.0__py3-none-any.whl → 1.3.1__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.
@@ -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,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
45
47
 
46
48
  Returns: The offer that has been registered with the MMS server.
47
49
  """
48
- # Note: the return type does not match the method definition but the decorator will return the correct type
50
+ # NOTE: The return type does not match the method definition but the decorator will return the correct type
49
51
  return MarketSubmit( # type: ignore[return-value]
50
52
  date=date or Date.today(),
51
53
  participant=self.participant,
@@ -71,7 +73,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
71
73
 
72
74
  Returns: A list of offers that have been registered with the MMS server.
73
75
  """
74
- # Note: the return type does not match the method definition but the decorator will return the correct type
76
+ # NOTE: The return type does not match the method definition but the decorator will return the correct type
75
77
  return MarketSubmit( # type: ignore[return-value]
76
78
  date=date or Date.today(),
77
79
  participant=self.participant,
@@ -83,28 +85,24 @@ class MarketClientMixin: # pylint: disable=unused-argument
83
85
  @mms_multi_endpoint(
84
86
  "MarketQuery_OfferQuery", config, RequestType.INFO, resp_envelope_type=MarketSubmit, resp_data_type=OfferData
85
87
  )
86
- def query_offers(
87
- self: ClientProto, request: OfferQuery, market_type: MarketType, days: int, date: Optional[Date] = None
88
- ) -> List[OfferData]:
88
+ def query_offers(self: ClientProto, request: OfferQuery, days: int, date: Optional[Date] = None) -> List[OfferData]:
89
89
  """Query the MMS server for offers.
90
90
 
91
91
  This endpoint is accessible to all client types.
92
92
 
93
93
  Arguments:
94
94
  request (OfferQuery): The query to submit to the MMS server.
95
- market_type (MarketType): The type of market for which the offer was submitted.
96
95
  days (int): The number of days ahead for which the data is being queried.
97
96
  date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to the
98
97
  current date.
99
98
 
100
99
  Returns: A list of offers that match the query.
101
100
  """
102
- # Note: the return type does not match the method definition but the decorator will return the correct type
101
+ # NOTE: The return type does not match the method definition but the decorator will return the correct type
103
102
  return MarketQuery( # type: ignore[return-value]
104
103
  date=date or Date.today(),
105
104
  participant=self.participant,
106
105
  user=self.user,
107
- market_type=market_type,
108
106
  days=days,
109
107
  )
110
108
 
@@ -123,7 +121,7 @@ class MarketClientMixin: # pylint: disable=unused-argument
123
121
  date (Date): The date of the transaction in the format "YYYY-MM-DD". This value defaults to the
124
122
  current date.
125
123
  """
126
- # Note: the return type does not match the method definition but the decorator will return the correct type
124
+ # NOTE: The return type does not match the method definition but the decorator will return the correct type
127
125
  return MarketCancel( # type: ignore[return-value]
128
126
  date=date or Date.today(),
129
127
  participant=self.participant,
@@ -131,3 +129,30 @@ class MarketClientMixin: # pylint: disable=unused-argument
131
129
  market_type=market_type,
132
130
  days=days,
133
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/base.py CHANGED
@@ -115,7 +115,7 @@ class SchemaType(Enum):
115
115
  OMI = "omi.xsd"
116
116
 
117
117
 
118
- class PayloadBase(BaseXmlModel, nsmap={"xsi": "http://www.w3.org/2001/XMLSchema"}):
118
+ class PayloadBase(BaseXmlModel, nsmap={"xsi": "http://www.w3.org/2001/XMLSchema-instance"}):
119
119
  """Represents the base fields for an MMS request payload."""
120
120
 
121
121
  # The XML schema to use for validation
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"
@@ -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, lt=10000.00, decimal_places=2)
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
 
@@ -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)
@@ -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")
@@ -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
- Note that JSON is currently not supported and has been left in for future use.
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
- Note that JSON is currently not supported and has been left in for future use.
38
+ NOTE: JSON is currently not supported and has been left in for future use.
39
39
  """
40
40
 
41
41
  XML = "XML"
@@ -16,6 +16,7 @@ from lxml.etree import XMLSchema
16
16
  from lxml.etree import _Element as Element
17
17
  from lxml.etree import parse
18
18
  from pydantic_xml import element
19
+ from pydantic_xml.typedefs import EntityLocation
19
20
 
20
21
  from mms_client.types.base import E
21
22
  from mms_client.types.base import Messages
@@ -69,12 +70,12 @@ class Serializer:
69
70
  )
70
71
 
71
72
  # Next, inject the payload and data into the payload class
72
- # Note: this returns a type that inherits from PayloadBase and the arguments provided to the initializer
73
+ # NOTE: this returns a type that inherits from PayloadBase and the arguments provided to the initializer
73
74
  # here are correct, but mypy thinks they are incorrect because it doesn't understand the the inherited type
74
75
  payload = payload_cls(request_envelope, request_data, self._xsd.value) # type: ignore[call-arg, misc]
75
76
 
76
77
  # Finally, convert the payload to XML and return it
77
- # Note: we provided the encoding here so this will return bytes, not a string
78
+ # NOTE: we provided the encoding here so this will return bytes, not a string
78
79
  return payload.to_xml(skip_empty=True, encoding="utf-8", xml_declaration=True) # type: ignore[return-value]
79
80
 
80
81
  def serialize_multi(self, request_envelope: E, request_data: List[P], request_type: Type[P]) -> bytes:
@@ -93,12 +94,12 @@ class Serializer:
93
94
  )
94
95
 
95
96
  # Next, inject the payload and data into the payload class
96
- # Note: this returns a type that inherits from PayloadBase and the arguments provided to the initializer
97
+ # NOTE: this returns a type that inherits from PayloadBase and the arguments provided to the initializer
97
98
  # here are correct, but mypy thinks they are incorrect because it doesn't understand the the inherited type
98
99
  payload = payload_cls(request_envelope, request_data, self._xsd.value) # type: ignore[call-arg, misc]
99
100
 
100
101
  # Finally, convert the payload to XML and return it
101
- # Note: we provided the encoding here so this will return bytes, not a string
102
+ # NOTE: we provided the encoding here so this will return bytes, not a string
102
103
  return payload.to_xml(skip_empty=True, encoding="utf-8", xml_declaration=True) # type: ignore[return-value]
103
104
 
104
105
  def deserialize(self, data: bytes, envelope_type: Type[E], data_type: Type[P]) -> Response[E, P]:
@@ -190,7 +191,7 @@ class Serializer:
190
191
 
191
192
  # Now, verify that the response doesn't contain an unexpected data type and then retrieve the payload data
192
193
  # from within the envelope
193
- # Note: apparently, mypy doesn't know about setter-getter properties either...
194
+ # NOTE: apparently, mypy doesn't know about setter-getter properties either...
194
195
  self._verify_tree_data_tag(env_node, data_type)
195
196
  resp.payload = [
196
197
  self._from_tree_data(item, data_type) for item in env_node.findall(get_tag(data_type)) # type: ignore[misc]
@@ -271,7 +272,13 @@ class Serializer:
271
272
  )
272
273
 
273
274
  def _from_tree_messages(
274
- self, raw: Element, envelope_type: Type[E], current_type: Type[P], root: str, multi: bool
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,
275
282
  ) -> Dict[str, Messages]:
276
283
  """Attempt to extract the messages from within the payload.
277
284
 
@@ -287,6 +294,7 @@ class Serializer:
287
294
  multi (bool): Whether we're processing a list of nodes or a single node. If called with the
288
295
  payload root, this value will determine whether we're processing a multi-
289
296
  response or a single response.
297
+ wrapped (bool): Whether or not this type is referenced from a wrapped field.
290
298
  """
291
299
  # First, find the Messages node in the raw data
292
300
  message_node = raw.find("Messages")
@@ -309,40 +317,41 @@ class Serializer:
309
317
  current_type,
310
318
  f"{root}.{envelope_type.__name__}",
311
319
  multi,
320
+ False,
312
321
  )
313
322
  )
314
- elif root.endswith(envelope_type.__name__):
323
+ elif root.endswith(envelope_type.__name__) or wrapped:
315
324
  messages.update(
316
- self._from_tree_messages_inner(raw, envelope_type, current_type, root, current_type.__name__, multi)
325
+ self._from_tree_messages_inner(
326
+ raw, envelope_type, current_type, root, get_tag(current_type), multi, False
327
+ )
317
328
  )
318
329
  else:
319
330
  # Iterate over each field on the current type...
320
331
  for field in current_type.model_fields.values():
321
332
 
322
- # First, get the arguments and origin of the field's annotation
323
- args = get_args(field.annotation)
324
- origin = get_origin(field.annotation)
325
- 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]
326
336
 
327
337
  # Next, check if the annotation is a subclass of Payload or else if it's a collection of Payload. If
328
338
  # neither of these is the case, we can skip this field.
329
- # Note: all our fields are annotated so there's no need to check if they're not
330
- if not (
331
- (has_args and issubclass(args[0], Payload))
332
- or (not has_args and issubclass(field.annotation, Payload)) # type: ignore[arg-type]
333
- ):
339
+ # NOTE: All our fields are annotated so there's no need to check if they're not
340
+ if not issubclass(arg, Payload):
334
341
  continue
335
342
 
336
343
  # Finally, call this method recursively for the field and update the messages with the results
337
- # Note: All our fields are annotated as XmlEntityInfo, so they have the "path" attribute
344
+ # NOTE: All our fields are annotated as XmlEntityInfo, so they have the "path" and "location" attributes
345
+ print(field)
338
346
  messages.update(
339
347
  self._from_tree_messages_inner(
340
348
  raw,
341
349
  envelope_type,
342
- args[0],
350
+ arg,
343
351
  root,
344
352
  field.path, # type: ignore[attr-defined]
345
- origin is list,
353
+ multi,
354
+ field.location == EntityLocation.WRAPPED, # type: ignore[attr-defined]
346
355
  )
347
356
  )
348
357
 
@@ -350,7 +359,14 @@ class Serializer:
350
359
  return messages
351
360
 
352
361
  def _from_tree_messages_inner(
353
- self, raw: Element, envelope_type: Type[E], current_type: Type[P], root: str, tag: str, multi: bool
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,
354
370
  ) -> Dict[str, Messages]:
355
371
  """Attempt to extract the messages from within the payload at the current level.
356
372
 
@@ -362,6 +378,7 @@ class Serializer:
362
378
  tag (str): The tag of the current node being processed.
363
379
  multi (bool): If True, the payload will be a multi-response; otherwise, it will be a single
364
380
  response.
381
+ wrapped (bool): Whether or not this field is a wrapped field.
365
382
 
366
383
  Returns: A dictionary mapping messages to where they were found in the response.
367
384
  """
@@ -379,12 +396,20 @@ class Serializer:
379
396
  # Otherwise, we'll call this method recursively for each node and update the messages with the results.
380
397
  messages = {}
381
398
  for i, node in enumerate(nodes):
382
- messages.update(self._from_tree_messages(node, envelope_type, current_type, f"{path_base}[{i}]", True))
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
+ )
383
404
  return messages
384
405
 
385
406
  # If we reached this point then we are processing a single item so find the associated
386
407
  child = raw.find(tag)
387
- return {} if child is None else self._from_tree_messages(child, envelope_type, current_type, path_base, False)
408
+ return (
409
+ {}
410
+ if child is None
411
+ else self._from_tree_messages(child, envelope_type, current_type, path_base, False, wrapped)
412
+ )
388
413
 
389
414
  def _from_xml(self, data: bytes) -> Element:
390
415
  """Parse the XML file, returning the resulting XML tree.
@@ -547,6 +572,45 @@ def _find_or_fail(node: Element, tag: str) -> Element:
547
572
  return found
548
573
 
549
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
+
550
614
  def get_tag(data_type: Type[P]) -> str:
551
615
  """Get the tag for the given data type.
552
616
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mms-client
3
- Version: 1.2.0
3
+ Version: 1.3.1
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
@@ -174,14 +174,14 @@ There's a lot of code here but it's not terribly difficult to understand. All th
174
174
  If you want to test your MMS connection, you can try using the test server:
175
175
 
176
176
  ```python
177
- client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, is_admin=True)
177
+ client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, test=True)
178
178
  ```
179
179
 
180
180
  ## Connecting as a Market Admin
181
181
  If you're connecting as a market operator (MO), you can connect in admin mode:
182
182
 
183
183
  ```python
184
- client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, test=True)
184
+ client = MmsClient(participant="F100", user="FAKEUSER", client_type=ClientType.BSP, cert, is_admin=True)
185
185
  ```
186
186
 
187
187
  ## Log Settings
@@ -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
 
@@ -13,24 +13,25 @@ mms_client/security/certs.py,sha256=kNCUFmy18YIxkWKu3mdMmlxmHdft4a6BvtyJ46rA9I4,
13
13
  mms_client/security/crypto.py,sha256=M7aIllM3_ZwZgm9nH6QQ6Ig14XCAd6e6WGwqqUbbI1Q,2149
14
14
  mms_client/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  mms_client/services/base.py,sha256=mcUL-AZpaJETVkzAOoazmKfI5O3cJ0KWzIYQ5bjpNuE,25839
16
- mms_client/services/market.py,sha256=juYIZuSsJ10srzDK4N6s8guoGqdDuQXGohecF43WQh8,6169
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=9pNZdgwRbJJCnmsVgESNnc3ywkn-wdQryumUhLn6Xvg,3419
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/base.py,sha256=wrDPn9io30in_w2qKa4A503gX_gAaMT7a1MpZ3HcmIc,9701
22
- mms_client/types/enums.py,sha256=wXlXvifWXWm9dowfG5iQlkax_6OX6z2nHid94WkhQwE,464
23
- mms_client/types/fields.py,sha256=SAvrDJGSvOVtbGvaaqLFBx2mCr0AxzqVFUTJgPSEDOY,12065
24
- mms_client/types/market.py,sha256=OKjBIx9aIgaLUci6b5WoB4NZbFncRbbd2FxeAvW1iWw,2588
25
- mms_client/types/offer.py,sha256=orlohxAWZlW2rwPJpGAoiy1knoCT2x2sXJyzThkXYVY,7693
21
+ mms_client/types/award.py,sha256=BWE9V_KHXpg_cW1LZsetVrPs2hZDOklvRpNnoZtmR3k,14139
22
+ mms_client/types/base.py,sha256=VQCr50CL1SnnPcO1EYGHa4rrkHBtXs-J8psWLJWoHJY,9710
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=_pRNAqOX8A0lUXmU8qO_8QwRtRx9BwZDdrN-w_Fnu38,66466
28
- mms_client/types/transport.py,sha256=vyosoeGSdYthqlmiyDAkZusYa8yVHUwOzfTqwZne2Ik,4407
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=TqkucFL1rvMu49sxsWDwCZSvWMDd75Gj9kZmm1FYUzc,27203
32
+ mms_client/utils/serialization.py,sha256=vTJZSjhfTph2tDAYbLAhnz89i0qXJ8FINGdve67cJOU,29534
32
33
  mms_client/utils/web.py,sha256=fcdCtdDrHBPyhIlTcyiuAk3D3TlW8HmUw-wGfpG4KTA,9653
33
- mms_client-1.2.0.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
34
- mms_client-1.2.0.dist-info/METADATA,sha256=xTwJZuKbEW7HKg_C5zw-uVYVXjnI112CiYxEPxGmoWA,14742
35
- mms_client-1.2.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
36
- mms_client-1.2.0.dist-info/RECORD,,
34
+ mms_client-1.3.1.dist-info/LICENSE,sha256=awOCsWJ58m_2kBQwBUGWejVqZm6wuRtCL2hi9rfa0X4,1211
35
+ mms_client-1.3.1.dist-info/METADATA,sha256=oSdqfUgW01Tr2ZRcCUd4SedQzQZrvNLY5fWmNmlMviY,14774
36
+ mms_client-1.3.1.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
37
+ mms_client-1.3.1.dist-info/RECORD,,