MouseTools 4.0.1__tar.gz → 4.0.3__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 (25) hide show
  1. {mousetools-4.0.1 → mousetools-4.0.3}/PKG-INFO +3 -2
  2. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/channels/__init__.py +7 -1
  3. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/channels/enums.py +5 -0
  4. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/channels/facilities/__init__.py +33 -35
  5. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/channels/facilitystatus/__init__.py +11 -8
  6. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/mixins/disney.py +2 -1
  7. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools.egg-info/PKG-INFO +3 -2
  8. mousetools-4.0.3/mousetools.egg-info/requires.txt +4 -0
  9. {mousetools-4.0.1 → mousetools-4.0.3}/pyproject.toml +4 -2
  10. {mousetools-4.0.1 → mousetools-4.0.3}/tests/test_mixins.py +13 -0
  11. mousetools-4.0.1/mousetools.egg-info/requires.txt +0 -3
  12. {mousetools-4.0.1 → mousetools-4.0.3}/LICENSE +0 -0
  13. {mousetools-4.0.1 → mousetools-4.0.3}/README.md +0 -0
  14. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/__init__.py +0 -0
  15. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/api/__init__.py +0 -0
  16. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/api/menu.py +0 -0
  17. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/auth.py +0 -0
  18. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/channels/facilities/enums.py +0 -0
  19. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/mixins/__init__.py +0 -0
  20. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/mixins/couchbase.py +0 -0
  21. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools/resources/environments.bin +0 -0
  22. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools.egg-info/SOURCES.txt +0 -0
  23. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools.egg-info/dependency_links.txt +0 -0
  24. {mousetools-4.0.1 → mousetools-4.0.3}/mousetools.egg-info/top_level.txt +0 -0
  25. {mousetools-4.0.1 → mousetools-4.0.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mousetools
3
- Version: 4.0.1
3
+ Version: 4.0.3
4
4
  Summary: An unofficial Python wrapper for Disney's API
5
5
  Author: Scott Caratozzolo
6
6
  License-Expression: MIT
@@ -19,7 +19,8 @@ Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: httpx>=0.27.2
21
21
  Requires-Dist: pycryptodome>=3.22.0
22
- Requires-Dist: tzdata>=2024.1
22
+ Requires-Dist: python-dateutil>=2.9.0.post0
23
+ Requires-Dist: tzdata>=2025.2
23
24
  Dynamic: license-file
24
25
 
25
26
  # MouseTools
@@ -35,6 +35,7 @@ class CouchbaseChannel(CouchbaseMixin):
35
35
  """Pulls initial data if none exists or if it is older than the refresh interval"""
36
36
  if self._cb_data is None or datetime.now(tz=self._tz.value) - self.last_update > self._refresh_interval:
37
37
  self._cb_data = self.get_channel_changes(self.channel_id)
38
+ self.last_update = datetime.now(tz=self._tz.value)
38
39
 
39
40
  def get_children_channel_ids(self) -> list[str]:
40
41
  """Returns a list of child channel ids for the channel."""
@@ -43,4 +44,9 @@ class CouchbaseChannel(CouchbaseMixin):
43
44
  return [i["id"] for i in self._cb_data["results"]]
44
45
 
45
46
  def __repr__(self):
46
- return f"{self.__class__.__name__}({self.channel_id})"
47
+ return f"{self.__class__.__name__}(channel_id='{self.channel_id}')"
48
+
49
+ def __eq__(self, other) -> bool:
50
+ if isinstance(other, CouchbaseChannel):
51
+ return self.channel_id == other.channel_id
52
+ return False
@@ -13,6 +13,7 @@ class CouchbaseChannels(str, Enum):
13
13
  CALENDAR = "calendar"
14
14
  FORECASTED_WAIT_TIME = "forecastedwaittimes"
15
15
  TODAY = "today"
16
+ CHARACTERS = "characters"
16
17
 
17
18
 
18
19
  def get_complete_channel_id(
@@ -41,6 +42,8 @@ class WDWCouchbaseChannels(str, Enum):
41
42
  """Walt Disney World Forecasted Wait Time Channel"""
42
43
  TODAY = get_complete_channel_id(DestinationShort.WALT_DISNEY_WORLD, CouchbaseChannels.TODAY, "1_0", False)
43
44
  """Walt Disney World Today Channel"""
45
+ CHARACTERS = get_complete_channel_id(DestinationShort.WALT_DISNEY_WORLD, CouchbaseChannels.CHARACTERS, "1_0", False)
46
+ """Walt Disney World Characters Channel"""
44
47
 
45
48
 
46
49
  class DLRCouchbaseChannels(str, Enum):
@@ -58,6 +61,8 @@ class DLRCouchbaseChannels(str, Enum):
58
61
  """Disneyland Resort Forecasted Wait Time Channel"""
59
62
  TODAY = get_complete_channel_id(DestinationShort.DISNEYLAND_RESORT, CouchbaseChannels.TODAY, "1_0", False)
60
63
  """Disneyland Resort Today Channel"""
64
+ CHARACTERS = get_complete_channel_id(DestinationShort.DISNEYLAND_RESORT, CouchbaseChannels.CHARACTERS, "1_0", False)
65
+ """Disneyland Resort Characters Channel"""
61
66
 
62
67
 
63
68
  class DestinationTimezones(Enum):
@@ -5,6 +5,8 @@ from datetime import datetime, time, timedelta
5
5
  from enum import Enum
6
6
  from zoneinfo import ZoneInfo
7
7
 
8
+ from dateutil.parser import isoparse
9
+
8
10
  from mousetools.api.menu import Menus
9
11
  from mousetools.channels import CouchbaseChannel
10
12
  from mousetools.channels.enums import (
@@ -62,6 +64,7 @@ class FacilityChildChannel(CouchbaseMixin):
62
64
  self._refresh_interval: timedelta = timedelta(minutes=10)
63
65
 
64
66
  self._cb_data: typing.Optional[dict] = None
67
+ self._cb_data_pull_time: typing.Optional[datetime] = datetime.now(tz=self._tz.value)
65
68
  if not lazy_load:
66
69
  self.refresh()
67
70
 
@@ -69,6 +72,7 @@ class FacilityChildChannel(CouchbaseMixin):
69
72
  """Pulls initial data if none exists or if it is older than the refresh interval"""
70
73
  if self._cb_data is None or datetime.now(tz=self._tz.value) - self.last_update > self._refresh_interval:
71
74
  self._cb_data = self.get_channel_data(self.channel_id)
75
+ self._cb_data_pull_time = datetime.now(tz=self._tz.value)
72
76
 
73
77
  @property
74
78
  def name(self) -> typing.Optional[str]:
@@ -418,12 +422,12 @@ class FacilityChildChannel(CouchbaseMixin):
418
422
  if "wdw" in self.channel_id
419
423
  else DestinationTimezones.DISNEYLAND_RESORT
420
424
  )
421
- dt = datetime.fromisoformat(self._cb_data["lastUpdate"])
425
+ dt = isoparse(self._cb_data["lastUpdate"])
422
426
  dt = dt.replace(tzinfo=tz.value)
423
427
  return dt
424
428
  except KeyError:
425
429
  logger.debug("No last updated found for %s", self.channel_id)
426
- return None
430
+ return self._cb_data_pull_time
427
431
 
428
432
  @property
429
433
  def sponsor_name(self) -> typing.Optional[str]:
@@ -440,6 +444,32 @@ class FacilityChildChannel(CouchbaseMixin):
440
444
  logger.debug("No sponsor name found for %s", self.channel_id)
441
445
  return None
442
446
 
447
+ @property
448
+ def meal_periods(self) -> typing.Optional[list[str]]:
449
+ """
450
+ The meal periods offered by the restaurant.
451
+
452
+ Returns:
453
+ (typing.Optional[list[str]]): The meal periods offered by the restaurant.
454
+ """
455
+ if self._cb_data is None:
456
+ self._cb_data = self.get_channel_data(self.channel_id)
457
+ try:
458
+ return self._cb_data["mealPeriods"]
459
+ except KeyError:
460
+ logger.debug("No meal periods found for %s", self.channel_id)
461
+ return None
462
+
463
+ @property
464
+ def menu(self) -> Menus:
465
+ """
466
+ The menu of the restaurant.
467
+
468
+ Returns:
469
+ (typing.Optional[Menus]): The menu of the restaurant.
470
+ """
471
+ return Menus(self.channel_id)
472
+
443
473
  def __str__(self) -> str:
444
474
  return f"{self.entity_type}: {self.name} ({self.channel_id})"
445
475
 
@@ -482,39 +512,7 @@ ThemeParkFacilityChild = FacilityChildChannel.get_subclass("ThemeParkFacilityChi
482
512
  TourFacilityChild = FacilityChildChannel.get_subclass("TourFacilityChild")
483
513
  TransportationFacilityChild = FacilityChildChannel.get_subclass("TransportationFacilityChild")
484
514
  WaterParkFacilityChild = FacilityChildChannel.get_subclass("WaterParkFacilityChild")
485
-
486
-
487
- class RestaurantFacilityChild(FacilityChildChannel):
488
- """Class for RestaurantFacilityChild Entities."""
489
-
490
- def __init__(self, *args, **kwargs):
491
- super().__init__(*args, **kwargs)
492
-
493
- @property
494
- def meal_periods(self) -> typing.Optional[list[str]]:
495
- """
496
- The meal periods offered by the restaurant.
497
-
498
- Returns:
499
- (typing.Optional[list[str]]): The meal periods offered by the restaurant.
500
- """
501
- if self._cb_data is None:
502
- self._cb_data = self.get_channel_data(self.channel_id)
503
- try:
504
- return self._cb_data["mealPeriods"]
505
- except KeyError:
506
- logger.debug("No meal periods found for %s", self.channel_id)
507
- return None
508
-
509
- @property
510
- def menu(self) -> Menus:
511
- """
512
- The menu of the restaurant.
513
-
514
- Returns:
515
- (typing.Optional[Menus]): The menu of the restaurant.
516
- """
517
- return Menus(self.channel_id)
515
+ RestaurantFacilityChild = FacilityChildChannel.get_subclass("RestaurantFacilityChild")
518
516
 
519
517
 
520
518
  class FacilityChannel(CouchbaseChannel):
@@ -3,6 +3,8 @@ import typing
3
3
  from collections.abc import Generator
4
4
  from datetime import datetime, timedelta
5
5
 
6
+ from dateutil.parser import isoparse
7
+
6
8
  from mousetools.channels import CouchbaseChannel
7
9
  from mousetools.channels.enums import CouchbaseChannels, DestinationShort, DestinationTimezones
8
10
  from mousetools.mixins.couchbase import CouchbaseMixin
@@ -28,16 +30,17 @@ class FacilityStatusChildChannel(CouchbaseMixin):
28
30
  else DestinationTimezones.DISNEYLAND_RESORT
29
31
  )
30
32
  self._refresh_interval: timedelta = timedelta(minutes=10)
33
+ self._cb_data: typing.Optional[dict] = None
34
+ self._cb_data_pull_time: typing.Optional[datetime] = datetime.now(tz=self._tz.value)
31
35
 
32
- if lazy_load:
33
- self._cb_data: typing.Optional[dict] = None
34
- else:
35
- self._cb_data = self.get_channel_data(self.channel_id)
36
+ if not lazy_load:
37
+ self.refresh()
36
38
 
37
39
  def refresh(self) -> None:
38
40
  """Pulls initial data if none exists or if it is older than the refresh interval"""
39
41
  if self._cb_data is None or datetime.now(tz=self._tz.value) - self.last_update > self._refresh_interval:
40
42
  self._cb_data = self.get_channel_data(self.channel_id)
43
+ self._cb_data_pull_time = datetime.now(tz=self._tz.value)
41
44
 
42
45
  def get_status(self) -> typing.Optional[str]:
43
46
  """Get the operating status of the facility
@@ -78,12 +81,12 @@ class FacilityStatusChildChannel(CouchbaseMixin):
78
81
  self._cb_data = self.get_channel_data(self.channel_id)
79
82
 
80
83
  try:
81
- dt = datetime.fromisoformat(self._cb_data["lastUpdate"])
84
+ dt = isoparse(self._cb_data["lastUpdate"])
82
85
  dt = dt.replace(tzinfo=self._tz.value)
83
86
  return dt
84
87
  except KeyError:
85
88
  logger.debug("No last updated found for %s", self.channel_id)
86
- return None
89
+ return self._cb_data_pull_time
87
90
 
88
91
  @property
89
92
  def fast_pass_available(self) -> typing.Optional[bool]:
@@ -99,7 +102,7 @@ class FacilityStatusChildChannel(CouchbaseMixin):
99
102
  self.refresh()
100
103
 
101
104
  try:
102
- dt = datetime.fromisoformat(self._cb_data["fastPassEndTime"])
105
+ dt = isoparse(self._cb_data["fastPassEndTime"])
103
106
  dt = dt.replace(tzinfo=self._tz.value)
104
107
  return dt
105
108
  except (KeyError, ValueError):
@@ -110,7 +113,7 @@ class FacilityStatusChildChannel(CouchbaseMixin):
110
113
  self.refresh()
111
114
 
112
115
  try:
113
- dt = datetime.fromisoformat(self._cb_data["fastPassStartTime"])
116
+ dt = isoparse(self._cb_data["fastPassStartTime"])
114
117
  dt = dt.replace(tzinfo=self._tz.value)
115
118
  return dt
116
119
  except (KeyError, ValueError):
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import logging
2
3
 
3
4
  import httpx
@@ -29,7 +30,7 @@ class DisneyAPIMixin:
29
30
  try:
30
31
  response.raise_for_status()
31
32
  data = response.json()
32
- except httpx.HTTPError as err:
33
+ except (httpx.HTTPError, json.decoder.JSONDecodeError) as err:
33
34
  logger.warning("Request failed. Error: %s", err)
34
35
  return None
35
36
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mousetools
3
- Version: 4.0.1
3
+ Version: 4.0.3
4
4
  Summary: An unofficial Python wrapper for Disney's API
5
5
  Author: Scott Caratozzolo
6
6
  License-Expression: MIT
@@ -19,7 +19,8 @@ Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: httpx>=0.27.2
21
21
  Requires-Dist: pycryptodome>=3.22.0
22
- Requires-Dist: tzdata>=2024.1
22
+ Requires-Dist: python-dateutil>=2.9.0.post0
23
+ Requires-Dist: tzdata>=2025.2
23
24
  Dynamic: license-file
24
25
 
25
26
  # MouseTools
@@ -0,0 +1,4 @@
1
+ httpx>=0.27.2
2
+ pycryptodome>=3.22.0
3
+ python-dateutil>=2.9.0.post0
4
+ tzdata>=2025.2
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mousetools"
3
- version = "4.0.1"
3
+ version = "4.0.3"
4
4
  authors = [{name = "Scott Caratozzolo"}]
5
5
  description = "An unofficial Python wrapper for Disney's API"
6
6
  readme = "README.md"
@@ -10,7 +10,8 @@ requires-python = ">=3.9, <4.0"
10
10
  dependencies = [
11
11
  "httpx>=0.27.2",
12
12
  "pycryptodome>=3.22.0",
13
- "tzdata>=2024.1",
13
+ "python-dateutil>=2.9.0.post0",
14
+ "tzdata>=2025.2",
14
15
  ]
15
16
  classifiers = [
16
17
  "Programming Language :: Python :: 3",
@@ -40,6 +41,7 @@ dev-dependencies = [
40
41
  "commitizen>=4.7.0",
41
42
  "go-task-bin>=3.43.3",
42
43
  "icecream>=2.1.4",
44
+ "pytest-mock>=3.14.0",
43
45
  ]
44
46
 
45
47
  [build-system]
@@ -13,6 +13,15 @@ def test_couchbase_mixin():
13
13
  assert channel_changes
14
14
  assert len(channel_changes["results"]) > 0
15
15
 
16
+ channel_data = cb.get_channel_data("wdw.notreal.1_0")
17
+ assert channel_data.get("error", "") == "not_found"
18
+
19
+ cb._couchbase_url = "http://notreal.com"
20
+ channel_data = cb.get_channel_data("wdw.facilities.1_0.en_us.attraction.80010177;entityType=Attraction")
21
+ channel_changes = cb.get_channel_changes("wdw.facilities.1_0.en_us", since=3900000)
22
+ assert channel_data is None
23
+ assert channel_changes is None
24
+
16
25
 
17
26
  def test_disney_mixin():
18
27
  disney = DisneyAPIMixin()
@@ -22,3 +31,7 @@ def test_disney_mixin():
22
31
 
23
32
  assert data
24
33
  assert "menus" in data
34
+
35
+ data = disney.get_disney_data("http://notreal.com")
36
+
37
+ assert data is None
@@ -1,3 +0,0 @@
1
- httpx>=0.27.2
2
- pycryptodome>=3.22.0
3
- tzdata>=2024.1
File without changes
File without changes
File without changes