MouseTools 4.0.3__tar.gz → 4.1.0__tar.gz

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.
Files changed (28) hide show
  1. {mousetools-4.0.3 → mousetools-4.1.0}/PKG-INFO +24 -2
  2. {mousetools-4.0.3 → mousetools-4.1.0}/README.md +23 -1
  3. mousetools-4.1.0/mousetools/api/search.py +58 -0
  4. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/channels/__init__.py +2 -1
  5. mousetools-4.1.0/mousetools/channels/calendar/__init__.py +311 -0
  6. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/channels/enums.py +1 -4
  7. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/channels/facilities/__init__.py +230 -68
  8. mousetools-4.1.0/mousetools/channels/facilities/enums.py +32 -0
  9. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/channels/facilitystatus/__init__.py +11 -12
  10. mousetools-4.1.0/mousetools/enums.py +34 -0
  11. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools.egg-info/PKG-INFO +24 -2
  12. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools.egg-info/SOURCES.txt +3 -0
  13. {mousetools-4.0.3 → mousetools-4.1.0}/pyproject.toml +1 -1
  14. mousetools-4.0.3/mousetools/channels/facilities/enums.py +0 -30
  15. {mousetools-4.0.3 → mousetools-4.1.0}/LICENSE +0 -0
  16. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/__init__.py +0 -0
  17. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/api/__init__.py +0 -0
  18. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/api/menu.py +0 -0
  19. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/auth.py +0 -0
  20. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/mixins/__init__.py +0 -0
  21. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/mixins/couchbase.py +0 -0
  22. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/mixins/disney.py +0 -0
  23. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools/resources/environments.bin +0 -0
  24. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools.egg-info/dependency_links.txt +0 -0
  25. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools.egg-info/requires.txt +0 -0
  26. {mousetools-4.0.3 → mousetools-4.1.0}/mousetools.egg-info/top_level.txt +0 -0
  27. {mousetools-4.0.3 → mousetools-4.1.0}/setup.cfg +0 -0
  28. {mousetools-4.0.3 → mousetools-4.1.0}/tests/test_mixins.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mousetools
3
- Version: 4.0.3
3
+ Version: 4.1.0
4
4
  Summary: An unofficial Python wrapper for Disney's API
5
5
  Author: Scott Caratozzolo
6
6
  License-Expression: MIT
@@ -25,7 +25,7 @@ Dynamic: license-file
25
25
 
26
26
  # MouseTools
27
27
 
28
- [![PyPI version](https://badge.fury.io/py/MouseTools.svg)](https://badge.fury.io/py/MouseTools) [![Downloads](https://pepy.tech/badge/mousetools)](https://pepy.tech/project/mousetools)
28
+ ![PyPI - Version](https://img.shields.io/pypi/v/mousetools?style=flat-square&logo=python&color=00C106&link=https%3A%2F%2Fpypi.org%2Fproject%2Fmousetools%2F) ![PyPI - Downloads](https://img.shields.io/pypi/dm/mousetools?style=flat-square&color=blue&link=https%3A%2F%2Fpypi.org%2Fproject%2Fmousetools%2F)
29
29
 
30
30
 
31
31
  An unofficial Python wrapper for the Disney API. Data is pulled directly from Disney. This package supports Walt Disney World and Disneyland Resort.
@@ -110,8 +110,30 @@ print(mk.description)
110
110
  # "Enter a Land Where Fantasy Reigns"
111
111
  ```
112
112
 
113
+ There is also a search functionality to help find the channels you want.
114
+ ```python
115
+ from mousetools.api.search import Search
116
+ from mousetools.enums import DestinationShort
117
+
118
+ search = Search(DestinationShort.WALT_DISNEY_WORLD)
119
+ print(search("pirates", max_results=1))
120
+ # [{'channel_id': 'wdw.facilities.1_0.en_us.attraction.80010177;entityType=Attraction',
121
+ # 'entity_id': '80010177;entityType=Attraction',
122
+ # 'name': 'Pirates of the Caribbean',
123
+ # 'search_score': 3507.1604}]
124
+ ```
125
+
113
126
  ### License
114
127
  This project is distributed under the MIT license. For more information see [LICENSE](https://gitlab.com/caratozzoloxyz/public/MouseTools/-/blob/master/LICENSE?ref_type=heads)
115
128
 
116
129
  ### Disclaimer
117
130
  This project is in no way affiliated with The Walt Disney Company and all use of Disney Services is subject to the [Disney Terms of Use](https://disneytermsofuse.com/).
131
+
132
+
133
+ ### About Project
134
+
135
+ <a href='https://ko-fi.com/scaratozzolo' target='_blank'>
136
+ <img src='https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=kofi&logoColor=white&link=https%3A%2F%2Fko-fi.com%2Fscaratozzolo border='0' alt='Support my projects' />
137
+ </a>
138
+
139
+ MouseTools is a passion project I've been working on since 2018 when I was first learning Python. The package has evolved and been rewritten multiple times as I've become a better developer and as the Disney API has changed. There is a lot of time exploring Disney's API and figuring out how all the pieces work before any code is even written. I work on this project when I have time or when I have an idea for another project that could use this data.
@@ -1,6 +1,6 @@
1
1
  # MouseTools
2
2
 
3
- [![PyPI version](https://badge.fury.io/py/MouseTools.svg)](https://badge.fury.io/py/MouseTools) [![Downloads](https://pepy.tech/badge/mousetools)](https://pepy.tech/project/mousetools)
3
+ ![PyPI - Version](https://img.shields.io/pypi/v/mousetools?style=flat-square&logo=python&color=00C106&link=https%3A%2F%2Fpypi.org%2Fproject%2Fmousetools%2F) ![PyPI - Downloads](https://img.shields.io/pypi/dm/mousetools?style=flat-square&color=blue&link=https%3A%2F%2Fpypi.org%2Fproject%2Fmousetools%2F)
4
4
 
5
5
 
6
6
  An unofficial Python wrapper for the Disney API. Data is pulled directly from Disney. This package supports Walt Disney World and Disneyland Resort.
@@ -85,8 +85,30 @@ print(mk.description)
85
85
  # "Enter a Land Where Fantasy Reigns"
86
86
  ```
87
87
 
88
+ There is also a search functionality to help find the channels you want.
89
+ ```python
90
+ from mousetools.api.search import Search
91
+ from mousetools.enums import DestinationShort
92
+
93
+ search = Search(DestinationShort.WALT_DISNEY_WORLD)
94
+ print(search("pirates", max_results=1))
95
+ # [{'channel_id': 'wdw.facilities.1_0.en_us.attraction.80010177;entityType=Attraction',
96
+ # 'entity_id': '80010177;entityType=Attraction',
97
+ # 'name': 'Pirates of the Caribbean',
98
+ # 'search_score': 3507.1604}]
99
+ ```
100
+
88
101
  ### License
89
102
  This project is distributed under the MIT license. For more information see [LICENSE](https://gitlab.com/caratozzoloxyz/public/MouseTools/-/blob/master/LICENSE?ref_type=heads)
90
103
 
91
104
  ### Disclaimer
92
105
  This project is in no way affiliated with The Walt Disney Company and all use of Disney Services is subject to the [Disney Terms of Use](https://disneytermsofuse.com/).
106
+
107
+
108
+ ### About Project
109
+
110
+ <a href='https://ko-fi.com/scaratozzolo' target='_blank'>
111
+ <img src='https://img.shields.io/badge/sponsor-30363D?style=for-the-badge&logo=kofi&logoColor=white&link=https%3A%2F%2Fko-fi.com%2Fscaratozzolo border='0' alt='Support my projects' />
112
+ </a>
113
+
114
+ MouseTools is a passion project I've been working on since 2018 when I was first learning Python. The package has evolved and been rewritten multiple times as I've become a better developer and as the Disney API has changed. There is a lot of time exploring Disney's API and figuring out how all the pieces work before any code is even written. I work on this project when I have time or when I have an idea for another project that could use this data.
@@ -0,0 +1,58 @@
1
+ from typing import Optional, Union
2
+
3
+ from mousetools.auth import auth_obj
4
+ from mousetools.channels.enums import DLRCouchbaseChannels, WDWCouchbaseChannels
5
+ from mousetools.enums import DestinationShort
6
+ from mousetools.mixins.disney import DisneyAPIMixin
7
+
8
+
9
+ class Search(DisneyAPIMixin):
10
+ _search_service_base = auth_obj._environments["searchServiceUrl"]
11
+
12
+ def __init__(self, destination: Union[DestinationShort, str]):
13
+ """
14
+ Args:
15
+ destination (Union[DestinationShort, str]): Search the facilities for this destination
16
+ """
17
+ if isinstance(destination, DestinationShort):
18
+ destination = destination.value
19
+ self.destination = destination
20
+ self._facilities_channel = (
21
+ WDWCouchbaseChannels.FACILITIES.value
22
+ if destination == DestinationShort.WALT_DISNEY_WORLD
23
+ else DLRCouchbaseChannels.FACILITIES.value
24
+ )
25
+
26
+ def search(self, query: str, max_results: int = 10) -> Optional[list[dict]]:
27
+ """Send a request to the search service
28
+
29
+ Args:
30
+ query (str): Search query
31
+ max_results (int, optional): Maximum number of results to return in the list. Defaults to 10.
32
+
33
+ Returns:
34
+ (Optional[list[dict]]): List of results
35
+ """
36
+ api_data = self.get_disney_data(
37
+ f"{self._search_service_base}/mobileapi/v1/entities/search?q={query}&brand={self.destination}"
38
+ )
39
+ our_results = []
40
+ for i in api_data.get("results", [])[:max_results]:
41
+ tmp = {}
42
+ tmp["entity_id"] = i["id"]
43
+ tmp["name"] = i["title"][0]
44
+ tmp["search_score"] = i["score"]
45
+ tmp["channel_id"] = f"{self._facilities_channel}.{i['id'].rsplit('=')[-1].lower()}.{i['id']}"
46
+ our_results.append(tmp)
47
+ return our_results
48
+
49
+ def __call__(self, *args, **kwargs) -> Optional[list[dict]]:
50
+ """Call the search function
51
+
52
+ Returns:
53
+ (Optional[list[dict]]): List of results
54
+ """
55
+ return self.search(*args, **kwargs)
56
+
57
+ def __repr__(self):
58
+ return f"Search(destination='{self.destination}')"
@@ -2,7 +2,8 @@ import typing
2
2
  from datetime import datetime, timedelta
3
3
  from enum import Enum
4
4
 
5
- from mousetools.channels.enums import DestinationShort, DestinationTimezones, DLRCouchbaseChannels, WDWCouchbaseChannels
5
+ from mousetools.channels.enums import DestinationTimezones, DLRCouchbaseChannels, WDWCouchbaseChannels
6
+ from mousetools.enums import DestinationShort
6
7
  from mousetools.mixins.couchbase import CouchbaseMixin
7
8
 
8
9
 
@@ -0,0 +1,311 @@
1
+ import logging
2
+ import typing
3
+ from datetime import datetime, timedelta
4
+ from enum import Enum
5
+ from zoneinfo import ZoneInfo
6
+
7
+ from dateutil.parser import isoparse
8
+
9
+ from mousetools.channels import CouchbaseChannel
10
+ from mousetools.channels.enums import CouchbaseChannels, DestinationTimezones
11
+ from mousetools.enums import DestinationShort
12
+ from mousetools.mixins.couchbase import CouchbaseMixin
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class CalendarChildChannel(CouchbaseMixin):
18
+ def __init__(
19
+ self,
20
+ channel_id: str,
21
+ lazy_load: bool = True,
22
+ ):
23
+ """
24
+ Args:
25
+ channel_id (str): Calendar Channel ID
26
+ lazy_load (bool, optional): If True, will not pull data until a method or property is called. Defaults to True.
27
+ """
28
+ if isinstance(channel_id, Enum):
29
+ channel_id = channel_id.value
30
+
31
+ self.channel_id: str = channel_id
32
+ self.entity_id: str = channel_id.rsplit(".", 1)[-1]
33
+
34
+ self._destination_short: DestinationShort = self.channel_id.split(".")[0]
35
+ self._tz: DestinationTimezones = (
36
+ DestinationTimezones.WALT_DISNEY_WORLD
37
+ if self._destination_short == DestinationShort.WALT_DISNEY_WORLD
38
+ else DestinationTimezones.DISNEYLAND_RESORT
39
+ )
40
+ self._refresh_interval: timedelta = timedelta(minutes=30)
41
+
42
+ self._cb_data: typing.Optional[dict] = None
43
+ self._cb_data_pull_time: typing.Optional[datetime] = datetime.now(tz=self._tz.value)
44
+ if not lazy_load:
45
+ self.refresh()
46
+
47
+ def __repr__(self):
48
+ return f"{self.__class__.__name__}(channel_id='{self.channel_id}')"
49
+
50
+ def refresh(self) -> None:
51
+ """Pulls initial data if none exists or if it is older than the refresh interval"""
52
+ if self._cb_data is None or datetime.now(tz=self._tz.value) - self.last_update > self._refresh_interval:
53
+ self._cb_data = self.get_channel_data(self.channel_id)
54
+ self._cb_data_pull_time = datetime.now(tz=self._tz.value)
55
+
56
+ @property
57
+ def calendar_id(self) -> typing.Optional[str]:
58
+ """The ID of the calendar
59
+
60
+ Returns:
61
+ (typing.Optional[str]): The ID of the calendar, or None if no such data exists.
62
+ """
63
+ self.refresh()
64
+ try:
65
+ return self._cb_data["id"]
66
+ except KeyError:
67
+ logger.debug("No id found for %s", self.channel_id)
68
+ return None
69
+
70
+ @property
71
+ def last_update(self) -> typing.Optional[datetime]:
72
+ """The last time the data was updated.
73
+
74
+ Returns:
75
+ (typing.Optional[datetime]): The last time the entity's data was updated, or None if no such data exists.
76
+ """
77
+ if self._cb_data is None:
78
+ self._cb_data = self.get_channel_data(self.channel_id)
79
+
80
+ try:
81
+ dt = isoparse(self._cb_data["lastUpdate"])
82
+ dt = dt.replace(tzinfo=self._tz.value)
83
+ return dt
84
+ except KeyError:
85
+ logger.debug("No last updated found for %s", self.channel_id)
86
+ return self._cb_data_pull_time
87
+
88
+ @property
89
+ def all_closed(self) -> typing.Optional[list[dict]]:
90
+ """The list of closed facilities.
91
+
92
+ Returns:
93
+ (typing.Optional[list[dict]]): The list of closed facilities, or None if no such data exists.
94
+ """
95
+ self.refresh()
96
+ try:
97
+ return self._cb_data["closed"]
98
+ except KeyError:
99
+ logger.debug("No closed found for %s", self.channel_id)
100
+ return None
101
+
102
+ @property
103
+ def all_facility_schedules(self) -> typing.Optional[dict[str, list[dict]]]:
104
+ """The list of facility schedules.
105
+
106
+ Returns:
107
+ (typing.Optional[dict[str, list[dict]]]): The list of facility schedules, or None if no such data exists.
108
+ """
109
+ self.refresh()
110
+ try:
111
+ return self._cb_data["facilitySchedules"]
112
+ except KeyError:
113
+ logger.debug("No facility schedules found for %s", self.channel_id)
114
+ return None
115
+
116
+ @property
117
+ def all_meal_periods(self) -> typing.Optional[dict[str, list[dict]]]:
118
+ """The list of meal periods.
119
+
120
+ Returns:
121
+ (typing.Optional[dict[str, list[dict]]]): The list of meal periods, or None if no such data exists.
122
+ """
123
+ self.refresh()
124
+ try:
125
+ return self._cb_data["mealPeriods"]
126
+ except KeyError:
127
+ logger.debug("No meal periods found for %s", self.channel_id)
128
+ return None
129
+
130
+ @property
131
+ def all_park_hours(self) -> typing.Optional[list[dict]]:
132
+ """The list of park hours.
133
+
134
+ Returns:
135
+ (typing.Optional[list[dict]]): The list of park hours, or None if no such data exists.
136
+ """
137
+ self.refresh()
138
+ try:
139
+ return self._cb_data["parkHours"]
140
+ except KeyError:
141
+ logger.debug("No park hours found for %s", self.channel_id)
142
+ return None
143
+
144
+ @property
145
+ def all_private_events(self) -> typing.Optional[list[dict]]:
146
+ """The list of private events.
147
+
148
+ Returns:
149
+ (typing.Optional[list[dict]]): The list of private events, or None if no such data exists.
150
+ """
151
+ self.refresh()
152
+ try:
153
+ return self._cb_data["privateEvents"]
154
+ except KeyError:
155
+ logger.debug("No private events found for %s", self.channel_id)
156
+ return None
157
+
158
+ @property
159
+ def all_refurbishments(self) -> typing.Optional[list[dict]]:
160
+ """The list of refurbishments.
161
+
162
+ Returns:
163
+ (typing.Optional[list[dict]]): The list of refurbishments, or None if no such data exists.
164
+ """
165
+ self.refresh()
166
+ try:
167
+ return self._cb_data["refurbishments"]
168
+ except KeyError:
169
+ logger.debug("No refurbishments found for %s", self.channel_id)
170
+ return None
171
+
172
+ @property
173
+ def timezone(self) -> ZoneInfo:
174
+ """The timezone of the calendar.
175
+
176
+ Returns:
177
+ (ZoneInfo): The timezone of the calendar.
178
+ """
179
+ return self._tz.value
180
+
181
+ def get_park_hours(self, entity_id: str) -> dict:
182
+ """Get the park hours for the given entity
183
+
184
+ Args:
185
+ entity_id (str): entity id of the facility
186
+
187
+ Returns:
188
+ dict: park hours broken up into their schedule types (Operating, Early Entry, etc.)
189
+ """
190
+ hours = {}
191
+ if self.all_park_hours:
192
+ for i in self.all_park_hours:
193
+ if entity_id in i["facilityId"]:
194
+ hours[i["scheduleType"]] = {}
195
+ hours[i["scheduleType"]]["start"] = isoparse(i["startTime"]).astimezone(self._tz.value)
196
+ hours[i["scheduleType"]]["end"] = isoparse(i["endTime"]).astimezone(self._tz.value)
197
+ return hours
198
+
199
+ def get_meal_periods(self, entity_id: str) -> dict:
200
+ """Get the meal periods for the given entity
201
+
202
+ Args:
203
+ entity_id (str): entity id of the facility
204
+
205
+ Returns:
206
+ dict: meal periods
207
+ """
208
+ periods = {}
209
+ if self.all_meal_periods:
210
+ restaurant = self.all_meal_periods.get(entity_id, [])
211
+ for i in restaurant:
212
+ meal_period = i["facilityId"]
213
+ periods[meal_period] = {}
214
+ periods[meal_period]["start"] = isoparse(i["startTime"]).astimezone(self._tz.value)
215
+ periods[meal_period]["end"] = isoparse(i["endTime"]).astimezone(self._tz.value)
216
+ periods[meal_period]["schedule_type"] = i["scheduleType"]
217
+ return periods
218
+
219
+ def get_refurbishment(self, entity_id: str) -> dict:
220
+ """Get the refurbishment info for the given entity
221
+
222
+ Args:
223
+ entity_id (str): entity id of the facility
224
+
225
+ Returns:
226
+ dict: refurbishments
227
+ """
228
+ refurbishments = {}
229
+ if self.all_refurbishments:
230
+ for i in self.all_refurbishments:
231
+ if entity_id in i["facilityId"]:
232
+ refurbishments["start"] = isoparse(i["startTime"]).astimezone(self._tz.value)
233
+ refurbishments["end"] = isoparse(i["endTime"]).astimezone(self._tz.value)
234
+ return refurbishments
235
+
236
+ def get_closed(self, entity_id: str) -> dict:
237
+ """Get the closed info for the given entity
238
+
239
+ Args:
240
+ entity_id (str): entity id of the facility
241
+
242
+ Returns:
243
+ dict: closed
244
+ """
245
+ closed = {}
246
+ if self.all_closed:
247
+ for i in self.all_closed:
248
+ if entity_id in i["facilityId"]:
249
+ closed["start"] = isoparse(i["startTime"]).astimezone(self._tz.value)
250
+ closed["end"] = isoparse(i["endTime"]).astimezone(self._tz.value)
251
+ return closed
252
+
253
+ def get_facility_schedule(self, entity_id: str) -> list[dict]:
254
+ """Get the schedule info for the given entity
255
+
256
+ Args:
257
+ entity_id (str): entity id of the facility
258
+
259
+ Returns:
260
+ (list[dict]): schedules
261
+ """
262
+ schedules = []
263
+ if self.all_facility_schedules:
264
+ facility = self.all_facility_schedules.get(entity_id, [])
265
+ for i in facility:
266
+ tmp = {}
267
+ tmp["start"] = isoparse(i["startTime"]).astimezone(self._tz.value)
268
+ tmp["end"] = isoparse(i["endTime"]).astimezone(self._tz.value)
269
+ tmp["schedule_type"] = i["scheduleType"]
270
+ schedules.append(tmp)
271
+ return sorted(schedules, key=lambda i: i["start"])
272
+
273
+
274
+ class CalendarChannel(CouchbaseChannel):
275
+ def __init__(self, *args, **kwargs):
276
+ super().__init__(*args, **kwargs)
277
+
278
+ self.available_calendars = {}
279
+ self.refresh_calendars()
280
+
281
+ def refresh_calendars(self):
282
+ """Refreshes the available calendars."""
283
+ available_calendars = {}
284
+ for i in self.get_children_channels():
285
+ available_calendars[i.entity_id] = i
286
+
287
+ self.available_calendars = available_calendars
288
+
289
+ def get_children_channels(self) -> typing.Generator[CalendarChildChannel, None, None]:
290
+ """Gets a list of children channels for the channel.
291
+
292
+ Yields:
293
+ typing.Generator[CalendarChildChannel, None, None]: A generator of CalendarChildChannel
294
+ """
295
+ self.refresh()
296
+ for i in self._cb_data["results"]:
297
+ if CouchbaseChannels.CALENDAR in i["id"]:
298
+ yield CalendarChildChannel(i["id"])
299
+
300
+ def get_calendar(self, day: int, month: int) -> typing.Optional[CalendarChildChannel]:
301
+ """Get the calendar for the given day and month
302
+
303
+ Args:
304
+ day (int): day of the month
305
+ month (int): month of the year
306
+
307
+ Returns:
308
+ typing.Optional[CalendarChildChannel]: calendar for the given day and month
309
+ """
310
+ self.refresh_calendars()
311
+ return self.available_calendars.get(f"{day:02}-{month:02}", None)
@@ -1,10 +1,7 @@
1
1
  from enum import Enum
2
2
  from zoneinfo import ZoneInfo
3
3
 
4
-
5
- class DestinationShort(str, Enum):
6
- WALT_DISNEY_WORLD = "wdw"
7
- DISNEYLAND_RESORT = "dlr"
4
+ from mousetools.enums import DestinationShort
8
5
 
9
6
 
10
7
  class CouchbaseChannels(str, Enum):