mss-python 0.0.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.
mss_python/__init__.py ADDED
File without changes
mss_python/client.py ADDED
@@ -0,0 +1,111 @@
1
+ import requests
2
+ import xmltodict
3
+ import xml.etree.ElementTree as ET
4
+
5
+ from .request.methods import Method
6
+ from .request.types_mss import Root, BaseType, Credentials, Search, MethodName, Literal
7
+
8
+
9
+ # maybe recursion should be used instead of doing if checks?
10
+ def change_structure(root: Root, child: BaseType):
11
+ if child.tag == "root":
12
+ return child
13
+
14
+ if child.tag == "request":
15
+ root.request = child
16
+
17
+ elif child.tag == "search":
18
+ root.request.search = child
19
+
20
+ elif child.tag == "order":
21
+ root.request.order = child
22
+
23
+ elif child.tag == "options":
24
+ root.request.options = child
25
+
26
+ elif child.tag == "logging":
27
+ root.request.logging = child
28
+
29
+ elif child.tag == "header":
30
+ root.header = child
31
+
32
+ return root
33
+
34
+
35
+ class Client:
36
+ def __init__(
37
+ self, cred: Credentials, lang: list, elements: list | None = None
38
+ ) -> None:
39
+ self.cred = cred
40
+ self.lang = Search(lang)
41
+
42
+ def request(
43
+ self,
44
+ url: str,
45
+ method_name: MethodName,
46
+ data: BaseType | None = None,
47
+ _print: Literal[True, False] = False,
48
+ ) -> dict:
49
+ """
50
+ Makes a post request to the given URL with the specified data and method name.
51
+
52
+ data: ElementTree with the data required in the XML
53
+ url: Service URL
54
+ method: Method to be executed by the service
55
+ """
56
+
57
+ method = Method(method_name)
58
+
59
+ root = method.get_base_xml(self.cred, self.lang)
60
+
61
+ if data is None:
62
+ root = change_structure(root, data)
63
+
64
+ if _print is True:
65
+ print(ET.tostring(root.request.to_xml(), encoding="unicode"))
66
+
67
+ response = requests.post(
68
+ url,
69
+ data=ET.tostring(root.to_xml(), encoding="unicode"),
70
+ headers={"Content-Type": "application/xml"},
71
+ )
72
+
73
+ responseRoot = xmltodict.parse(response.content)
74
+ error = responseRoot["root"]["header"]["error"]
75
+ if int(error["code"]) > 0:
76
+ raise Exception(
77
+ f'MSS returned an error: code {error["code"]}, message: "{error["message"]}"'
78
+ )
79
+
80
+ return refine_response(responseRoot["root"], method_name)
81
+
82
+
83
+ def refine_response(resp: dict, meth: MethodName) -> dict:
84
+ if meth == MethodName.GetHotelList:
85
+ return refine_getHotelList(resp)
86
+
87
+ return resp
88
+
89
+
90
+ def refine_getHotelList(resp: dict) -> dict:
91
+ if resp["result"] is None:
92
+ return resp
93
+ elif "hotel" not in resp["result"] or resp["result"]["hotel"] is None:
94
+ return resp
95
+ elif resp["result"]["hotel"] is list:
96
+ resp["result"]["hotel"] = [resp["result"]["hotel"]]
97
+
98
+ for hotel in resp["result"]["hotel"]:
99
+ ensure_list_value(hotel, "pictures", "picture")
100
+ ensure_list_value(hotel, "gallery", "picture")
101
+ ensure_list_value(hotel, "feature_view", "feature")
102
+ ensure_list_value(hotel, "pos", "id_pos")
103
+
104
+ return resp
105
+
106
+
107
+ def ensure_list_value(hotel: dict, parent_fld: str, child_fld: str) -> None:
108
+ if parent_fld not in hotel:
109
+ hotel[parent_fld] = {child_fld: []}
110
+ elif hotel[parent_fld][child_fld] is list:
111
+ hotel[parent_fld][child_fld] = [hotel[parent_fld][child_fld]]
@@ -0,0 +1,28 @@
1
+ from enum import Enum
2
+
3
+
4
+ class MethodName(Enum):
5
+ GetHotelList = "getHotelList"
6
+ GetHotelListByFilter = "getHotelListByFilter"
7
+ GetSpecialList = "getSpecialList"
8
+ GetRoomList = "getRoomList"
9
+ GetWidgetConfig = "getWidgetConfig"
10
+ GetPriceList = "getPriceList"
11
+ GetAvailability = "getAvailability"
12
+ GetRoomAvailability = "getRoomAvailability"
13
+ GetDayAvailability = "getDayAvailability"
14
+ PrepareBooking = "prepareBooking"
15
+ GetBooking = "getBooking"
16
+ CancelBooking = "cancelBooking"
17
+ CreateInquiry = "createInquiry"
18
+ GetInquiry = "getInquiry"
19
+ GetUserSources = "getUserSources"
20
+ GetLocationList = "getLocationList"
21
+ GetMasterpackagesList = "getMasterpackagesList"
22
+ NotifyMasterpackages = "notifyMasterpackages"
23
+ GetThemeList = "getThemeList"
24
+ GetLastminuteQuotations = "getLastminuteQuotations"
25
+ GetHotelPictures = "getHotelPictures"
26
+ GetHotelPictureGroups = "getHotelPictureGroups"
27
+ GetOptionalServices = "getOptionalServices"
28
+ ValidateCoupon = "validateCoupon"
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from .method_names import MethodName
4
+ from .types_mss import Credentials, Search, Root, Header, Request
5
+
6
+
7
+ class Method:
8
+ def __init__(self, name: MethodName):
9
+ self.name = name
10
+
11
+ def get_base_xml(self, cred: Credentials, lang: Search) -> Root:
12
+ """
13
+ Returns an XML (ElementTree) with the mandatory elements needed for the request.
14
+
15
+ cred: XML Credentials Element (username, password, source)
16
+
17
+ lang: XML Search Element with the language element defined
18
+ """
19
+
20
+ header = Header(cred, self.name)
21
+ req = Request(lang)
22
+ root = Root(header, req)
23
+ return root
@@ -0,0 +1,835 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from enum import Enum, IntFlag
4
+ from typing import Literal
5
+ import xml.etree.ElementTree as ET
6
+ from abc import ABC
7
+
8
+ from .method_names import MethodName
9
+
10
+
11
+ def get_child_xml(value, field, root):
12
+ field = field.replace("___", "-")
13
+ match value:
14
+ case None:
15
+ return
16
+ case int() | float():
17
+ ET.SubElement(root, field).text = str(value)
18
+ case str():
19
+ if field == "tag":
20
+ return
21
+ ET.SubElement(root, field).text = value
22
+ case list():
23
+ for x in value:
24
+ get_child_xml(x, field, root)
25
+ case Enum():
26
+ ET.SubElement(root, field).text = str(value.value)
27
+ case _:
28
+ root.append(value.to_xml())
29
+
30
+
31
+ class BaseType(ABC):
32
+ def __init__(self, tag):
33
+ self.tag = tag
34
+
35
+ def to_xml(self) -> ET.Element:
36
+ root = ET.Element(self.tag)
37
+ # root.append(self.search.to_xml())
38
+
39
+ for field_, value in vars(self).items():
40
+ get_child_xml(value, field_, root)
41
+
42
+ return root
43
+
44
+
45
+ @dataclass()
46
+ class Root(BaseType):
47
+ header: Header
48
+ request: Request
49
+ version: str = "2.0"
50
+
51
+ def __post_init__(self):
52
+ super().__init__("root")
53
+
54
+
55
+ @dataclass()
56
+ class Header(BaseType):
57
+ credentials: Credentials
58
+ method: MethodName
59
+ # method: str
60
+ paging: Paging | None = field(default=None)
61
+
62
+ def __post_init__(self):
63
+ super().__init__("header")
64
+
65
+
66
+ @dataclass()
67
+ class Credentials(BaseType):
68
+ user: str
69
+ password: str
70
+ source: str
71
+
72
+ def __post_init__(self):
73
+ super().__init__("credentials")
74
+
75
+
76
+ @dataclass()
77
+ class Paging:
78
+ start: int
79
+ limit: int
80
+
81
+
82
+ @dataclass
83
+ class Request(BaseType):
84
+ search: Search
85
+ options: Options | None = field(default=None)
86
+
87
+ order: Order | None = field(default=None)
88
+
89
+ data: Data | None = field(default=None)
90
+
91
+ logging: Logging | None = field(default=None)
92
+
93
+ def __post_init__(self):
94
+ super().__init__("request")
95
+
96
+
97
+ @dataclass
98
+ class Search(BaseType):
99
+ lang: list # Literal["de", "it", "en", "es", "fr", "ru", "da"]
100
+ result_id: str | None = field(default=None)
101
+
102
+ agent: str | None = field(default=None)
103
+
104
+ id: list | None = field(default=None) # number[]
105
+
106
+ search_hotel: SearchHotel | None = field(default=None)
107
+
108
+ search_location: SearchLocation | None = field(default=None)
109
+
110
+ search_distance: SearchDistance | None = field(default=None)
111
+
112
+ search_offer: SearchOffer | None = field(default=None)
113
+
114
+ search_lts: SearchLts | None = field(default=None)
115
+
116
+ search_special: SearchSpecial | None = field(default=None)
117
+
118
+ search_availability: SearchAvailability | None = field(default=None)
119
+
120
+ search_pricelist: SearchPriceList | None = field(default=None)
121
+
122
+ in_: int | None = field(default=None) # number[]
123
+
124
+ id_ofchannel: Literal["hgv", "lts", "esy"] | None = field(default=None)
125
+
126
+ transaction_id: str | None = field(default=None)
127
+
128
+ booking_id: str | None = field(default=None)
129
+
130
+ guest_email: str | None = field(default=None)
131
+
132
+ pic_group_id: int | None = field(default=None)
133
+
134
+ id_apt: int | None = field(default=None)
135
+
136
+ root_id: list | None = field(default=None) # list of ints
137
+
138
+ pic_type: PictureType | None = field(default=None)
139
+
140
+ select___avail: HotelFilters | None = field(default=None)
141
+ select___rooms: HotelFilters | None = field(default=None)
142
+ select___price: HotelFilters | None = field(default=None)
143
+ avail_alert: Literal[0, 1] | None = field(default=None)
144
+ rooms_alert: Literal[0, 1] | None = field(default=None)
145
+ price_alert: Literal[0, 1] | None = field(default=None)
146
+
147
+ coupon_code: str | None = field(default=None)
148
+ total_price: int | None = field(default=None)
149
+ arrival: str | None = field(default=None)
150
+ departure: str | None = field(default=None)
151
+ coupon_type: Literal["promotion", "voucher"] | None = field(default=None)
152
+
153
+ def __post_init__(self):
154
+ super().__init__("search")
155
+
156
+
157
+ class HotelFilters(Enum):
158
+ OTA = "ota"
159
+ EASI_CHANNEL = "easiChannel"
160
+ ROTER_HAHN = "10ec953a9d700b9598580a52d3dcef6c"
161
+ HOTEL_PARTNER = "2d9038f6a34c23280669f73ff531683c"
162
+ FIVE_STELLE = "30fb54af1d0d231b01b778647cfd967c"
163
+ ASA_LIGHT = "51c761e7b7b87dbeafe25a9b"
164
+ ASA_HOTEL = "51c761e7b7b87dc3afe15afa"
165
+ ROOM_CLOUD = "65ee9db4c37baedc1032cbd72c8caaaf"
166
+ GS_DATAPROVIDER = "66461e9d7c481e44e632051c3d040a2b"
167
+ HGV_TEST_CLIENT = "95f6a058-45ca-49df-874f-86f41e0bb29f"
168
+ HGV_CLIENT_FOR_TRENTINO = "a47a9dd543e55f8b3ee7ce4cc88b6d57"
169
+ HOTEL_NET_SOLUTIONS = "a57f0615b226609ae409c421ff118a8a"
170
+ BEDDY = "a8513684c7683fd457f82818f5f15bbf"
171
+ PCS_PHOENIX_XENUS = "AE1D2D8776B3FC1C;XENUS"
172
+ AIDA = "AIbD6vqHlYHA17DA"
173
+ AE_SMO = "ba098f2c86b942e58b6e7a89448f4063"
174
+ MTS_ONLINE = "bdcec66dfe4e8ef9f5c7e71e4835204c"
175
+ LTS_BOZEN = "be6bc64c94bbc062bcebfb40b4f93304"
176
+ BOOKING_EXPERT = "BookingE47acfb09170e8"
177
+ CASABLANCA = "CasaifeDZQQDaqvlblanca"
178
+ APARTMENT4HOLIDAY = "ceaeed03a0711eec46582b689d16dc0b"
179
+ CIAOBNB = "CiaoK2VAvAkFlCTcbnb"
180
+ EASI_SUITE = "Easy68e9nMxChannel"
181
+ EASI_SUITE_2 = "Easy68e9nMxChannel2"
182
+ SCIDOO = "f0bdfb926c2a8d066a9d2ea3e632471c"
183
+ ERICSOFT = "fcfb97bb1a8e608e5971928ac2032a68"
184
+ HOTEL_SPIDER = "HoteltihPrvo8kMpbSpider"
185
+ IPER_BOOKING = "iperbooking"
186
+ KROSS_BOOKING = "Kross2XklTsfhbBooking"
187
+ MM_ONE = "MMwRNWBeSQiABuONE"
188
+ PASSEPARTOUT = "passPGkPyHnw3IbApartout"
189
+ SEEKDA = "SEEK8XLyALKEzmpyp2DA"
190
+ SIMPLE_BOOKING = "Simple89sKiOdS9PUcbooking"
191
+ ANEXIS = "SiPaCbW2I921EpMedia"
192
+ SMART_PRICING = "SmartqpukLfH5Kl1price"
193
+ TEST_YANOVIS = "travelsuite"
194
+ VERTICAL_BOOKING = "VertiexDH0x8XV7Booking"
195
+ ALPINE_BITS_CERTIFICATION = "VwnIjABCertification78czDK6"
196
+ WUBOOK = "Wub0Na0CxWovxdVook"
197
+
198
+
199
+ class PictureType(Enum):
200
+ Hotel = "hotel"
201
+ Gallery = "gallery"
202
+ Profile = "profile"
203
+ Roomgroup = "roomgroup"
204
+ Pricelist = "pricelist"
205
+ Special = "special"
206
+
207
+
208
+ @dataclass
209
+ class Options(BaseType):
210
+ hotel_details: HotelDetails | None = field(default=None)
211
+ offer_details: OfferDetails | None = field(default=None)
212
+
213
+ picture_date: str | None = field(default=None) # Date
214
+ lts_bookable: Literal[0, 1, 2] | None = field(default=None) # number, 0|1|2
215
+
216
+ base_price: int | None = field(default=None) # number, 0|1
217
+
218
+ # field is in easychannel but not in nodejs
219
+ only_available: Literal[0, 1] | None = field(default=None) # 0 | 1
220
+
221
+ # below fields are in nodejs but not in easychannel.it?
222
+ room_details: RoomDetails | None = field(default=None)
223
+ special_details: SpecialDetails | None = field(default=None)
224
+ # pricelist_details: PriceListDetails | None = field(default=None)
225
+
226
+ get_availability: Literal[0, 1] | None = field(default=None) # 0 | 1
227
+ get_restrictions: Literal[0, 1] | None = field(default=None) # 0 | 1
228
+ get_roomdetails: int | None = field(default=None) # 0 | 1
229
+ get_masterpackages: Literal[0, 1] | None = field(default=None)
230
+
231
+ location_details: Literal[0, 1] | None = field(default=None)
232
+ theme_details: Literal[0, 1] | None = field(default=None)
233
+
234
+ service_details: ServiceDetails | None = field(default=None)
235
+
236
+ def __post_init__(self):
237
+ super().__init__("options")
238
+
239
+
240
+ class ServiceDetails(IntFlag):
241
+ Headlines = 8
242
+ Pictures = 32
243
+ Descriptions = 256
244
+
245
+
246
+ class HotelDetails(IntFlag):
247
+ BasicInfo = 1
248
+ Themes = 2
249
+ HotelFacilities = 4
250
+ ShortDescription = 8
251
+ FullDescription = 16
252
+ GeographicInformation = 32
253
+ Coordinates = 64
254
+ Address = 128
255
+ Contacts = 256
256
+ PaymentOptionsForOnlineBooking = 512
257
+ PaymentOptionsAtHotel = 1024 #
258
+ Logo = 2048
259
+ HeaderImages = 4096
260
+ Gallery = 8192
261
+ HotelMatching = 16384
262
+ GeographicalInformationAsText = 32768
263
+ HotelReviews = 65536 # different name in GO
264
+ DetailedHotelFacilities = 131072
265
+ SalesPoint = 524288
266
+ LtsSpecificParameters = 262144
267
+ CouponServiceData = 16777216
268
+ CheckInOut = 1048576
269
+ SourceData = 2097152
270
+ AccountData = 67108864
271
+ RoomTypes = 134217728
272
+ # Payment provider missing?
273
+
274
+
275
+ class OfferDetails(IntFlag):
276
+ BasicInfo = 1
277
+ RoomCode = 4
278
+ RoomTitle = 8
279
+ PriceDetails = 16
280
+ RoomImages = 32
281
+ RoomFacilitiesFilter = 64
282
+ RoomDescription = 256
283
+ IncludedServices = 1024
284
+ AdditionalServices = 2048
285
+ RoomFacilitiesDetails = 4096
286
+ PriceImages = 8192
287
+ Themes = 16384
288
+ RoomFeatures = 32768
289
+ CancelPolicies = 262144
290
+ PaymentTerms = 1048576
291
+
292
+
293
+ class RoomDetails(IntFlag):
294
+ BasicInfo = 4
295
+ Title = 8
296
+ RoomImages = 32
297
+ RoomFacilitiesFilter = 64
298
+ RoomDescription = 256
299
+ RoomFacilitiesDetails = 4096
300
+ RoomFeatures = 32768
301
+ RoomNumbers = 65536
302
+
303
+
304
+ class SpecialDetails(IntFlag):
305
+ BasicInfo = 1
306
+ Title = 2
307
+ Descriptions = 4
308
+ Seasons = 8
309
+ Images = 16
310
+ Themes = 32
311
+ IncludedServices = 64
312
+
313
+
314
+ @dataclass
315
+ class Order(BaseType):
316
+ dir: Direction | None = field(default=None)
317
+ field: Field | None = field(default=None)
318
+
319
+ def __post_init__(self):
320
+ super().__init__("order")
321
+
322
+
323
+ class Field(Enum):
324
+ ValidityStart = "valid_start"
325
+ ValidityEnd = "valid_end"
326
+ Random = "rand"
327
+ Stars = "stars"
328
+ Name = "name"
329
+
330
+
331
+ class Direction(Enum):
332
+ Ascending = "asc"
333
+ Descending = "desc"
334
+
335
+
336
+ @dataclass
337
+ class Data(BaseType):
338
+ guest: Guest | None = field(default=None)
339
+ company: Company | None = field(default=None)
340
+ payment: Payment | None = field(default=None)
341
+ note: str | None = field(default=None)
342
+ details: Details | None = field(default=None)
343
+ form: Form | None = field(default=None)
344
+ tracking: Tracking | None = field(default=None)
345
+ # insurance: Ins
346
+ storno_reason: StornoReason | None = field(default=None)
347
+ storno_reason_text: str | None = field(default=None)
348
+
349
+ special: list | None = field(default=None) # list of specials
350
+
351
+ def __post_init__(self):
352
+ super().__init__("data")
353
+
354
+
355
+ @dataclass
356
+ class Special(BaseType):
357
+ offer_id: int
358
+ action: Literal["update", "delete"]
359
+ time_modified: str # ISO 8601 Date
360
+ reset_participants: Literal[0, 1] | None = field(default=None)
361
+
362
+ def __post_init__(self):
363
+ super().__init__("special")
364
+
365
+
366
+ class StornoReason(Enum):
367
+ Unknown = 0
368
+ GuestNotAvailable = 1
369
+ AccomodationRequestedToCancel = 2
370
+ GuestAnotherDestination = 3
371
+ GuestAnotherAccomodation = 4
372
+ Other = 99
373
+
374
+
375
+ @dataclass
376
+ class Logging(BaseType):
377
+ step: str | None = field(default=None)
378
+
379
+ def __post_init__(self):
380
+ super().__init__("logging")
381
+
382
+
383
+ class Step(Enum):
384
+ Search = "search"
385
+ SearchOffer = "search_offer"
386
+ SearchDetails = "search_details"
387
+ View = "view"
388
+ ViewOffer = "view_offer"
389
+ Step1 = "step_1"
390
+ Step2 = "step_2"
391
+ Step3 = "step_3"
392
+ Step4 = "step_4"
393
+
394
+
395
+ @dataclass
396
+ class SearchHotel(BaseType):
397
+ name: str | None = field(default=None)
398
+ type: HotelType | None = field(default=None)
399
+ stars: Stars | None = field(default=None)
400
+ feature: HotelFeature | None = field(default=None)
401
+ theme: HotelTheme | None = field(default=None)
402
+
403
+ def __post_init__(self):
404
+ super().__init__("search_hotel")
405
+
406
+
407
+ class HotelType(Enum):
408
+ Hotel = "1"
409
+ SkiSchool = "2"
410
+ Residence = "4"
411
+ Appartment = "16"
412
+ FarmVacation = "32"
413
+ MountainInn = "64"
414
+ CampingSite = "128"
415
+ HolidayHome = "256"
416
+ YouthHostel = "512"
417
+ Guesthouse = "1024"
418
+ Refuge = "2048"
419
+ Garni = "4096"
420
+ Inn = "8192"
421
+
422
+
423
+ class HotelFeature(Enum):
424
+ Garage = "1"
425
+ Elevator = "2"
426
+ Restaurant = "4"
427
+ Gym = "8"
428
+ Wellness = "16"
429
+ Spa = "32"
430
+ Breakfast = "64"
431
+ Buffet = "128"
432
+ OutdoorPool = "256"
433
+ IndoorPool = "512"
434
+ Bar = "1024"
435
+ BarrierFree = "2048"
436
+ Wlan = "4096"
437
+ ShuttleService = "8192"
438
+ Childcare = "16384"
439
+ SmallPetsAllowed = "32768"
440
+ BeautyFarm = "65536"
441
+ CentralLocation = "262144"
442
+ CoveredParking = "524288"
443
+ OpenParking = "1048576"
444
+ Massages = "2097152"
445
+ Sauna = "4194304"
446
+ SteamBath = "8388608"
447
+ PublicBar = "16777216"
448
+ DogsAllowed = "33554432"
449
+
450
+
451
+ class HotelTheme(Enum):
452
+ Family = "1"
453
+ Wellness = "2"
454
+ Hiking = "4"
455
+ Motorcycle = "8"
456
+ Bike = "16"
457
+ Golf = "32"
458
+ Riding = "64"
459
+ Romantic = "128"
460
+ Ski = "256"
461
+ Meeting = "512"
462
+ CrossCountrySkiing = "1024"
463
+ Culture = "2048"
464
+ Snowshoeing = "4096"
465
+
466
+
467
+ @dataclass
468
+ class SearchLocation(BaseType):
469
+ location: list | None = field(default=None) # number[], Location ID
470
+ location_lts: list | None = field(default=None) # string[], LTS Location RID
471
+
472
+ def __post_init__(self):
473
+ super().__init__("search_location")
474
+
475
+
476
+ @dataclass
477
+ class SearchDistance:
478
+ latitude: float # number
479
+ longitude: float # number
480
+ radius: int # number
481
+
482
+
483
+ @dataclass
484
+ class SearchOffer(BaseType):
485
+ arrival: str | None = field(default=None) # Date YYYY-MM-DD
486
+ departure: str | None = field(default=None) # Date YYYY-MM-DD
487
+ service: Board | None = field(default=None)
488
+ room: list | None = field(default=None) # Room[]
489
+ feature: RoomFeature | None = field(default=None)
490
+ channel_id: list | None = field(default=None) # string[]
491
+ typ: SearchOfferType | None = field(default=None)
492
+ rateplan: Rateplan | None = field(default=None)
493
+
494
+ # features from easychange missing?
495
+
496
+ def __post_init__(self):
497
+ super().__init__("search_offer")
498
+
499
+
500
+ class SearchOfferType(Enum):
501
+ DefaultPricelist = "10"
502
+ PeopleAge = "20"
503
+ PeopleNumber = "21"
504
+ Staying = "22"
505
+ BookingDate = "23"
506
+ Weekday = "24"
507
+ NoReference = "25"
508
+ SpecialPeopleAge = "50"
509
+ SpecialPeopleNumber = "51"
510
+ SpecialStaying = "52"
511
+ SpecialBookingDate = "53"
512
+ SpecialWeekday = "54"
513
+ SpecialNoReference = "55"
514
+
515
+
516
+ class Board(Enum):
517
+ Without = 1
518
+ Breakfast = 2
519
+ HalfBoard = 3
520
+ FullBoard = 4
521
+ AllInclusive = 5
522
+
523
+
524
+ class RoomFeature(IntFlag):
525
+ Balcony = 1
526
+ Terrace = 2
527
+ MiniBar = 4
528
+ Safe = 8
529
+ TV = 16
530
+ Satellite = 32
531
+ Wlan = 64
532
+ Internet = 128
533
+ BarrierFree = 512
534
+
535
+
536
+ @dataclass
537
+ class SearchLts(BaseType):
538
+ A0Ene: int | None = field(default=None) # number
539
+ A0MTV: int | None = field(default=None) # number
540
+ A0Rep: int | None = field(default=None) # number
541
+
542
+ def __post_init__(self):
543
+ super().__init__("search_lts")
544
+
545
+
546
+ @dataclass
547
+ class SearchSpecial(BaseType):
548
+ offer_id: list | None = field(default=None) # number[]
549
+ date_from: str | None = field(default=None) # Date YYYY-MM-DD
550
+ date_to: str | None = field(default=None) # Date YYYY-MM-DD
551
+ theme: list | None = field(default=None) # array of SpecialTheme
552
+ validity: Validity | None = field(default=None)
553
+ typ: SearchSpecialType | None = field(default=None)
554
+ premium: SearchSpecialPremium | None = field(default=None)
555
+
556
+ poi_id: list | None = field(default=None)
557
+ poi_cat: list | None = field(default=None)
558
+ status: Literal[0, 1] | None = field(default=None)
559
+
560
+ def __post_init__(self):
561
+ super().__init__("search_special")
562
+
563
+
564
+ class SearchSpecialType(Enum):
565
+ PriceLists = 0
566
+ Packages = 1
567
+ Specials = 2
568
+ ShortLongStays = 4
569
+
570
+
571
+ class SearchSpecialPremium(IntFlag):
572
+ FamilyHotelsPremium = 2
573
+ VinumHotelsPremium = 4
574
+ SüdtirolBalancePremium = 8
575
+ VitalpinaDurchatmen = 16
576
+ VitalpinaWohlfühlen = 32
577
+ VitalpinaErnährung = 64
578
+ VitalpinaAktiv = 128
579
+ VitalpinaPremium = 256
580
+ BikehotelsMountainbike = 512
581
+ BikehotelsBikeTouringEBike = 1024
582
+ BikehotelsRoadbike = 2048
583
+ BikehotelsPremium = 4096
584
+ ArchitectureDays = 8192
585
+ VinumHotels = 16384
586
+ FamilyHotels = 32768
587
+ FamilyHotelsNatureDetective = 65536
588
+ FamilyHotelsNatureDetectiveWinter = 131072
589
+
590
+
591
+ class SpecialTheme(Enum):
592
+ ThemeIDHiking = 1
593
+ ThemeIDCyclingMountainbike = 2
594
+ ThemeIDFamily = 3
595
+ ThemeIDWellnessHealth = 4
596
+ ThemeIDFoodAndDrink = 5
597
+ ThemeIDGolf = 6
598
+ ThemeIDCulture = 7
599
+ ThemeIDMotorsport = 8
600
+ ThemeIDCarFreeHolidays = 9
601
+ ThemeIDSkiSnowboard = 10
602
+ ThemeIDSummerActivities = 11
603
+ ThemeIDEvents = 12
604
+ ThemeIDChristmasMarkets = 13
605
+ ThemeIDActiveWinter = 14
606
+ ThemeIDVitalpina = 15
607
+ ThemeIDVitalpinaBreathe = 16
608
+ ThemeIDBikeHotelsEBike = 17
609
+ ThemeIDBikeHotelsFreeride = 18
610
+ ThemeIDBikeHotelsMountainbike = 20
611
+ ThemeIDBikeHotelsBikeTours = 21
612
+ ThemeIDBikeHotelsRacingBike = 22
613
+ ThemeIDFamilyHotels = 23
614
+ ThemeIDFamilyHotelsNatureDetective = 24
615
+ ThemeIDFamilyHotel = 26
616
+ ThemeIDNatureDetectivSummer = 27
617
+ ThemeIDNatureDetectivWinter = 28
618
+ ThemeIDEcologicHoliday = 79
619
+ ThemeIDHorseBackRiding = 80
620
+ ThemeIDLuxuryHoliday = 81
621
+ ThemeIDPetsFriendlyHoliday = 82
622
+ ThemeIDRoadBike = 83
623
+ ThemeIDRomanticHoliday = 84
624
+ ThemeIDWine = 85
625
+ ThemeIDBicycleTouring = 86
626
+ ThemeIDEBike = 87
627
+
628
+ ## These values were theme_bit
629
+ # Hiking = 1
630
+ # Cycling = 2
631
+ # Family = 4
632
+ # Wellness = 8
633
+ # Food = 16
634
+ # Golf = 32
635
+ # Culture = 64
636
+ # Motorsport = 128
637
+ # CarFree = 256
638
+ # SkiSnowboard = 512
639
+ # SummerActivities = 1024
640
+ # Events = 2048
641
+ # ChristmasMarkets = 4096
642
+ # ActiveWinter = 8192
643
+ # Vitalpina = 16384
644
+ # VitalpinaBreathe = 32768
645
+ # BikeHotelsEBike = 65536
646
+ # BikeHotelsFreeride = 131072
647
+ # BikeHotelsMountainbike = 524288
648
+ # BikeHotelsBikeTours = 1048576
649
+ # BikeHotelsRacingBike = 2097152
650
+ # FamilyHotels = 4194304
651
+ # FamilyHotelsNatureDetective = 8388608
652
+ # FamilyHotel = 33554432
653
+ # FamilyHotelsNatureDetectiveSummer = 67108864
654
+ # FamilyHotelsNatureDetectiveWinter = 134217728
655
+
656
+
657
+ @dataclass
658
+ class SearchAvailability(BaseType):
659
+ date_from: str # Date YYYY-MM-DD
660
+ date_to: str # Date YYYY-MM-DD
661
+ offer_id: list | None = field(default=None) # number[]
662
+ room_id: list | None = field(default=None) # number[]
663
+ duration: int | None = field(default=None)
664
+ room: Room | None = field(default=None)
665
+
666
+ def __post_init__(self):
667
+ super().__init__("search_availability")
668
+
669
+
670
+ @dataclass
671
+ class SearchPriceList(BaseType):
672
+ date_from: str | None = field(default=None) # Date YYYY-MM-DD
673
+ date_to: str | None = field(default=None) # Date YYYY-MM-DD
674
+ service: Board | None = field(default=None)
675
+ room_id: list | None = field(default=None) # number[]
676
+ typ: SearchSpecialType | None = field(default=None)
677
+
678
+ def __post_init__(self):
679
+ super().__init__("search_pricelist")
680
+
681
+
682
+ @dataclass
683
+ class Guest(BaseType):
684
+ firstname: str
685
+ lastname: str
686
+ email: str
687
+ gender: str | None = field(default=None)
688
+ prefix: str | None = field(default=None)
689
+ phone: str | None = field(default=None)
690
+ address: Address | None = field(default=None)
691
+ newsletter: Literal[0, 1] | None = field(default=None) # 0 | 1
692
+
693
+ def __post_init__(self):
694
+ super().__init__("guest")
695
+
696
+
697
+ @dataclass
698
+ class Company(BaseType):
699
+ name: str | None = field(default=None)
700
+ taxnumber: str | None = field(default=None)
701
+ recipient_code: str | None = field(default=None)
702
+ address: Address | None = field(default=None)
703
+
704
+ def __post_init__(self):
705
+ super().__init__("company")
706
+
707
+
708
+ @dataclass
709
+ class Payment(BaseType):
710
+ method: PaymentMethod | None = field(default=None)
711
+ invoice: Literal[0, 1] | None = field(default=None) # 0 | 1
712
+
713
+ def __post_init__(self):
714
+ super().__init__("payment")
715
+
716
+
717
+ class PaymentMethod(IntFlag):
718
+ CreditCardDeposit = 1
719
+ CreditCardAsSecurity = 2
720
+ CreditCardPayment = 8
721
+ BankTransferDeposit = 4
722
+ BankTransferPayment = 16
723
+ AccommodationPayment = 32
724
+
725
+
726
+ @dataclass
727
+ class Details(BaseType):
728
+ extr_price: list | None = field(default=None) # ExtraPrice[]
729
+
730
+ def __post_init__(self):
731
+ super().__init__("Details")
732
+
733
+
734
+ @dataclass
735
+ class ExtraPrice(BaseType):
736
+ price_id: int | None = field(default=None) # number
737
+ price_amount: int | None = field(default=None) # number
738
+
739
+ def __post_init__(self):
740
+ super().__init__("extra_price")
741
+
742
+
743
+ @dataclass
744
+ class Form(BaseType):
745
+ url_success: str | None = field(default=None)
746
+ url_failure: str | None = field(default=None)
747
+
748
+ def __post_init__(self):
749
+ super().__init__("form")
750
+
751
+
752
+ @dataclass
753
+ class Tracking(BaseType):
754
+ partner: str | None = field(default=None)
755
+ media: str | None = field(default=None)
756
+ campaign: str | None = field(default=None)
757
+ companyinfo: str | None = field(default=None)
758
+
759
+ def __post_init__(self):
760
+ super().__init__("tracking")
761
+
762
+
763
+ @dataclass
764
+ class Stars(BaseType):
765
+ min: Literal[1, 2, 3, 3.5, 4, 4.5, 5] | None = field(
766
+ default=None
767
+ ) # number, min can be 1, values are 1, 2, 3, 3.5, 4, 4.5, 5
768
+ max: Literal[1, 2, 3, 3.5, 4, 4.5, 5] | None = field(
769
+ default=None
770
+ ) # number, max can be 5
771
+
772
+ def __post_init__(self):
773
+ super().__init__("stars")
774
+
775
+
776
+ @dataclass
777
+ class Address(BaseType):
778
+ street: str | None = field(default=None)
779
+ zipcode: str | None = field(default=None)
780
+ city: str | None = field(default=None)
781
+ country: str | None = field(default=None)
782
+
783
+ def __post_init__(self):
784
+ super().__init__("address")
785
+
786
+
787
+ @dataclass
788
+ class Room(BaseType):
789
+ room_seq: int | None = field(default=None) # number
790
+ person: list | None = field(
791
+ default=None
792
+ ) # number[], 1..n Persons and their age (store only age inside tags)
793
+ offer_id: int | None = field(default=None) # number
794
+ room_id: int | None = field(default=None) # number
795
+ service: Board | None = field(default=None)
796
+ room_type: RoomType | None = field(default=None)
797
+
798
+ def __post_init__(self):
799
+ super().__init__("room")
800
+
801
+
802
+ class RoomType(Enum):
803
+ All = 0
804
+ Room = 1
805
+ Apartment = 2
806
+ Campsite = 4
807
+
808
+
809
+ @dataclass
810
+ class Rateplan(BaseType):
811
+ code: str # | None = field(default=None)
812
+ source: str # | None = field(default=None)
813
+
814
+ def __post_init__(self):
815
+ super().__init__("rateplan")
816
+
817
+
818
+ @dataclass
819
+ class Validity(BaseType):
820
+ valid: Literal[0, 1] # 0 | 1
821
+ offers: Literal[0, 1] | None = field(default=None) # 0 | 1
822
+ arrival: str | None = field(default=None) # Date YYYY-MM-DD
823
+ departure: str | None = field(default=None) # Date YYYY-MM-DD
824
+ service: Board | None = field(default=None)
825
+ room: list | None = field(default=None) # Room[]
826
+
827
+ def __post_init__(self):
828
+ super().__init__("validity")
829
+
830
+
831
+ def getHotelList_base_xml(root: Root, header: Header):
832
+ xml = '<?xml version="1.0" encoding="utf-8"?>'
833
+ xml += root.version
834
+ xml += header.credentials
835
+ return xml
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Booking Südtirol
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,99 @@
1
+ Metadata-Version: 2.1
2
+ Name: mss-python
3
+ Version: 0.0.0
4
+ Summary: MSS API wrapper for Python projects
5
+ Author-email: HGV <development@hgv.it>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2024 Booking Südtirol
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Repository, https://github.com/bookingsuedtirol/mss-python
29
+ Requires-Python: <4,>=3.7
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+
33
+ # mss-python
34
+
35
+ MSS API client for Python projects
36
+
37
+ <!-- ## Dependencies
38
+
39
+ This library requires an IANA Time Zone database to be present on the operating system (because it uses https://pkg.go.dev/time#LoadLocation). This database comes pre-installed with many Linux distros. If it’s unavailable (such as on Windows), https://pkg.go.dev/time/tzdata can be imported in the main program (which uses mss-go) instead. -->
40
+
41
+ ## Available methods
42
+
43
+ - [x] getHotelList
44
+ - [x] getHotelListByFilter
45
+ - [x] getSpecialList
46
+ - [x] getRoomList
47
+ - [x] getWidgetConfig
48
+ - [x] getBooking
49
+ - [x] cancelBooking
50
+ - [x] prepareBooking
51
+ - [x] getAvailability
52
+ - [x] getRoomAvailability
53
+ - [x] getDayAvailability
54
+ - [x] getPriceList
55
+ - [x] createInquiry
56
+ - [x] getLocationList
57
+ - [x] getThemeList
58
+ - [x] getMasterpackagesList
59
+ - [x] notifyMasterpackages
60
+ - [x] getLastMinuteQuotation
61
+ - [x] validateCoupon
62
+ - [x] getHotelPictures
63
+ - [x] getHotelPictureGroups
64
+ - [x] getOptionalServices
65
+
66
+ ## Example Execution
67
+
68
+ `pip install -r requirements.txt`
69
+
70
+ `python3 -m examples.simple.main`
71
+
72
+ <!-- -
73
+
74
+ TODO
75
+
76
+ - [x] getInquiry - how to find booking IDs?
77
+
78
+
79
+ - [x] getUserSources
80
+
81
+ -->
82
+
83
+ <!-- Warning: Only the methods with a ✓ next to them have been tested so far.
84
+
85
+ ## Before running examples/tests
86
+
87
+ Set the environment variables with:
88
+
89
+ ```Bash
90
+ export $(make env)
91
+ ```
92
+
93
+ ## Examples
94
+
95
+ Run `make simple` or `make advanced`
96
+
97
+ ## Tests
98
+
99
+ Run `make test` -->
@@ -0,0 +1,10 @@
1
+ mss_python/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ mss_python/client.py,sha256=DEMAGATE08MW42Ldtepk95RV6funmyyuFbN9XFhiwPk,3202
3
+ mss_python/request/method_names.py,sha256=DP22Tra-F86FP-6771q7bDwuX6ojPCidFPzztl4UThI,1030
4
+ mss_python/request/methods.py,sha256=pu_U6z8ANktuv0r3P6MmLKW66fwRhiDCp261vNxqrYE,664
5
+ mss_python/request/types_mss.py,sha256=Xq9YYA5-f_j2Gt7bCcHSC6qEzHs0hswpoXm2IGB9SdA,22354
6
+ mss_python-0.0.0.dist-info/LICENSE,sha256=SxLvAIuAvi2G5v6pRCopG3gAH9SLL_-xACJ0QBz_Blw,1074
7
+ mss_python-0.0.0.dist-info/METADATA,sha256=OFmG42saDCkDqNSpQmhaiqb-jqJIZI33sfkYqccFjVE,2939
8
+ mss_python-0.0.0.dist-info/WHEEL,sha256=Mdi9PDNwEZptOjTlUcAth7XJDFtKrHYaQMPulZeBCiQ,91
9
+ mss_python-0.0.0.dist-info/top_level.txt,sha256=-5Wm4NpowVgq9ImH61vhAiBQFoE21fg4r-7Pn7mOYyE,11
10
+ mss_python-0.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (73.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ mss_python