MouseTools 4.4.2__tar.gz → 4.5.1__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 (33) hide show
  1. {mousetools-4.4.2 → mousetools-4.5.1}/PKG-INFO +2 -1
  2. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/channels/__init__.py +10 -0
  3. mousetools-4.5.1/mousetools/channels/manager.py +113 -0
  4. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/mixins/couchbase.py +43 -15
  5. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools.egg-info/PKG-INFO +2 -1
  6. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools.egg-info/SOURCES.txt +2 -0
  7. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools.egg-info/requires.txt +1 -0
  8. {mousetools-4.4.2 → mousetools-4.5.1}/pyproject.toml +2 -1
  9. mousetools-4.5.1/tests/test_manager.py +28 -0
  10. {mousetools-4.4.2 → mousetools-4.5.1}/LICENSE +0 -0
  11. {mousetools-4.4.2 → mousetools-4.5.1}/README.md +0 -0
  12. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/__init__.py +0 -0
  13. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/api/__init__.py +0 -0
  14. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/api/menu.py +0 -0
  15. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/api/search.py +0 -0
  16. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/auth.py +0 -0
  17. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/channels/calendar/__init__.py +0 -0
  18. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/channels/characters/__init__.py +0 -0
  19. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/channels/enums.py +0 -0
  20. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/channels/facilities/__init__.py +0 -0
  21. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/channels/facilities/enums.py +0 -0
  22. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/channels/facilitystatus/__init__.py +0 -0
  23. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/channels/forecastedwaittimes/__init__.py +0 -0
  24. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/channels/today/__init__.py +0 -0
  25. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/decorators.py +0 -0
  26. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/enums.py +0 -0
  27. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/mixins/__init__.py +0 -0
  28. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/mixins/disney.py +0 -0
  29. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools/resources/environments.bin +0 -0
  30. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools.egg-info/dependency_links.txt +0 -0
  31. {mousetools-4.4.2 → mousetools-4.5.1}/mousetools.egg-info/top_level.txt +0 -0
  32. {mousetools-4.4.2 → mousetools-4.5.1}/setup.cfg +0 -0
  33. {mousetools-4.4.2 → mousetools-4.5.1}/tests/test_mixins.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mousetools
3
- Version: 4.4.2
3
+ Version: 4.5.1
4
4
  Summary: An unofficial Python wrapper for Disney's API
5
5
  Author: Scott Caratozzolo
6
6
  License-Expression: MIT
@@ -17,6 +17,7 @@ Classifier: Operating System :: OS Independent
17
17
  Requires-Python: <4.0,>=3.9
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
+ Requires-Dist: more-itertools>=10.7.0
20
21
  Requires-Dist: pycryptodome>=3.22.0
21
22
  Requires-Dist: python-dateutil>=2.9.0.post0
22
23
  Requires-Dist: requests>=2.32.3
@@ -47,6 +47,11 @@ class CouchbaseChildChannel(CouchbaseMixin):
47
47
  self._cb_data = self.get_channel_data(self.channel_id)
48
48
  self._cb_data_pull_time = datetime.now(tz=self._tz.value)
49
49
 
50
+ def _refresh_from_manager(self, cb_data: dict) -> None:
51
+ logger.info("Refreshing new data from manager for %s", self.channel_id)
52
+ self._cb_data = cb_data
53
+ self._cb_data_pull_time = datetime.now(tz=self._tz.value)
54
+
50
55
  @disney_property()
51
56
  def last_update(self) -> Optional[datetime]:
52
57
  """
@@ -101,6 +106,11 @@ class CouchbaseChannel(CouchbaseMixin):
101
106
  self._cb_data = self.get_channel_changes(self.channel_id)
102
107
  self._cb_data_pull_time = datetime.now(tz=self._tz.value)
103
108
 
109
+ def _refresh_from_manager(self, cb_data: dict) -> None:
110
+ logger.info("Refreshing new data from manager for %s", self.channel_id)
111
+ self._cb_data = cb_data
112
+ self._cb_data_pull_time = datetime.now(tz=self._tz.value)
113
+
104
114
  def get_children_channel_ids(self) -> list[str]:
105
115
  """Returns a list of child channel ids for the channel."""
106
116
  self.refresh()
@@ -0,0 +1,113 @@
1
+ import logging
2
+ from typing import Optional, Union
3
+
4
+ from more_itertools import batched
5
+
6
+ from mousetools.channels import CouchbaseChannel, CouchbaseChildChannel
7
+ from mousetools.mixins.couchbase import CouchbaseMixin
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class ChannelManager(CouchbaseMixin):
13
+ def __init__(self, channels: list[Union[CouchbaseChannel, CouchbaseChildChannel]]) -> None:
14
+ self._channel_store = {}
15
+ for channel in channels:
16
+ self._channel_store[channel.channel_id] = channel
17
+
18
+ def add_channel(self, channel: Union[CouchbaseChannel, CouchbaseChildChannel]) -> None:
19
+ """Adds a channel to the manager
20
+
21
+ Args:
22
+ channel (Union[CouchbaseChannel, CouchbaseChildChannel]): Channel to add
23
+ """
24
+ self._channel_store[channel.channel_id] = channel
25
+
26
+ def get_channel(self, channel_id: str) -> Union[CouchbaseChannel, CouchbaseChildChannel]:
27
+ """Returns a channel from the manager
28
+
29
+ Args:
30
+ channel_id (str): Channel ID
31
+
32
+ Returns:
33
+ Union[CouchbaseChannel, CouchbaseChildChannel]: The channel
34
+ """
35
+ return self._channel_store[channel_id]
36
+
37
+ def get_all_channels(self) -> list[Union[CouchbaseChannel, CouchbaseChildChannel]]:
38
+ """Returns a list of all channels in the manager
39
+
40
+ Returns:
41
+ list[Union[CouchbaseChannel, CouchbaseChildChannel]]: List of channels
42
+ """
43
+ return list(self._channel_store.values())
44
+
45
+ def get_channel_ids(self) -> list[str]:
46
+ """Returns a list of all channel ids in the manager
47
+
48
+ Returns:
49
+ list[str]: List of channel ids
50
+ """
51
+ return list(self._channel_store.keys())
52
+
53
+ def remove_channel(
54
+ self, channel_id: str, return_channel: bool = False
55
+ ) -> Optional[Union[CouchbaseChannel, CouchbaseChildChannel]]:
56
+ """Removes a channel from the manager
57
+
58
+ Args:
59
+ channel_id (str): Channel ID
60
+ return_channel (bool, optional): If True, will return the channel. Defaults to False.
61
+
62
+ Returns:
63
+ Optional[Union[CouchbaseChannel, CouchbaseChildChannel]]: The channel
64
+ """
65
+ channel = self._channel_store.pop(channel_id)
66
+ if return_channel:
67
+ return channel
68
+
69
+ def refresh(self) -> None:
70
+ """Refreshes all channels in the manager
71
+
72
+ Raises:
73
+ ValueError: If the channel is not a child or parent channel
74
+ """
75
+ children_channels: dict[str, CouchbaseChildChannel] = {}
76
+ parent_channels: dict[str, CouchbaseChannel] = {}
77
+
78
+ for channel_id, channel in self._channel_store.items():
79
+ if isinstance(channel, CouchbaseChildChannel):
80
+ children_channels[channel_id] = channel
81
+ elif isinstance(channel, CouchbaseChannel):
82
+ parent_channels[channel_id] = channel
83
+ else:
84
+ raise ValueError(f"Channel {channel} is not a child or parent channel")
85
+ logger.info(
86
+ "Refreshing %d children channels and %d parent channels", len(children_channels), len(parent_channels)
87
+ )
88
+
89
+ self._refresh_children(children_channels)
90
+
91
+ self._refresh_parents(parent_channels)
92
+
93
+ def _refresh_children(self, children_channels: dict[str, CouchbaseChildChannel]) -> None:
94
+ if children_channels:
95
+ logger.debug("Refreshing children channels")
96
+ for _channels in batched(children_channels, 100):
97
+ children_results = self.get_channel_data(_channels)
98
+ for channel_id, result in zip(_channels, children_results):
99
+ children_channels[channel_id]._refresh_from_manager(result)
100
+
101
+ def _refresh_parents(self, parent_channels: dict[str, CouchbaseChannel]) -> None:
102
+ if parent_channels:
103
+ logger.debug("Refreshing parent channels")
104
+ for _channels in batched(parent_channels, 100):
105
+ parent_results = self.get_channel_changes(_channels)
106
+ for channel_id in _channels:
107
+ copy_results = parent_results.copy()
108
+ new_results = []
109
+ for result in copy_results["results"]:
110
+ if channel_id in result["id"]:
111
+ new_results.append(result)
112
+ copy_results["results"] = new_results
113
+ parent_channels[channel_id]._refresh_from_manager(copy_results)
@@ -14,28 +14,39 @@ class CouchbaseMixin:
14
14
  _couchbase_url = "https://" + auth_obj._environments["syncGatewayUrl"].split("//")[1].rstrip("/")
15
15
  _headers: dict = auth_obj.get_couchbase_headers()
16
16
 
17
- def get_channel_data(self, channel_name: str) -> Union[dict, None]:
17
+ def get_channel_data(self, channel_name: Union[str, list[str]]) -> Union[list[dict], dict, None]:
18
18
  """
19
19
  Sends a request to the Disney Couchbase API with the given channel ID and returns the data.
20
20
 
21
21
  Args:
22
- channel_name (str): Couchbase channel to request data from.
22
+ channel_name (Union[str, list[str]]): Couchbase channel(s) to request data from.
23
23
 
24
24
  Returns:
25
- (dict): The disney data.
25
+ (Union[list[dict], dict, None]): The disney data.
26
26
  """
27
27
 
28
+ if isinstance(channel_name, str):
29
+ _channel_names = [channel_name]
30
+ return_list = False
31
+ else:
32
+ _channel_names = channel_name
33
+ return_list = True
34
+
28
35
  payload = {
29
- "docs": [
30
- {"id": channel_name},
31
- ],
36
+ "docs": [{"id": name} for name in _channel_names],
32
37
  "json": True,
33
38
  }
34
39
 
35
- logger.info("Sending request for channel data: %s", channel_name)
40
+ logger.info(
41
+ "Sending request for channel data: %s", f"{len(channel_name)} channels" if return_list else _channel_names
42
+ )
36
43
  response = requests.post(self._couchbase_url + "/_bulk_get", json=payload, headers=self._headers, timeout=10)
37
- logger.debug("Response status for %s: %s", channel_name, response.status_code)
38
- data = None
44
+ logger.debug(
45
+ "Response status for %s: %s",
46
+ f"{len(channel_name)} channels" if return_list else _channel_names,
47
+ response.status_code,
48
+ )
49
+ results = []
39
50
  try:
40
51
  response.raise_for_status()
41
52
  raw_data = response.text
@@ -48,6 +59,7 @@ class CouchbaseMixin:
48
59
  if line != "" and line[0] != "-":
49
60
  try:
50
61
  data = json.loads(line)
62
+ results.append(data)
51
63
  except Exception:
52
64
  logger.debug("Failed to parse line: %s", line)
53
65
  continue
@@ -55,21 +67,30 @@ class CouchbaseMixin:
55
67
  logger.error("Request failed: %s", err)
56
68
  return None
57
69
 
58
- return data
70
+ if return_list:
71
+ return results
72
+ else:
73
+ return results[0]
59
74
 
60
- def get_channel_changes(self, channel_name: str, since: Union[int, None] = None) -> Union[dict, None]:
75
+ def get_channel_changes(
76
+ self, channel_name: Union[str, list[str]], since: Union[int, None] = None
77
+ ) -> Union[dict, None]:
61
78
  """Sends a request to the Disney Couchbase API with the given channel ID and returns the data.
62
79
 
63
80
  Args:
64
- channel_name (str): Couchbase channel to request data from.
81
+ channel_name (Union[str, list[str]]): Couchbase channel to request data from.
65
82
  since (Union[int, None], optional): Since when to get changes. Defaults to None.
66
83
 
67
84
  Returns:
68
85
  Union[dict, None]: The disney data.
69
86
  """
87
+
88
+ if isinstance(channel_name, str):
89
+ channel_name = [channel_name]
90
+
70
91
  try:
71
92
  payload = {
72
- "channels": channel_name,
93
+ "channels": ",".join(channel_name),
73
94
  "style": "all_docs",
74
95
  "filter": "sync_gateway/bychannel",
75
96
  "feed": "normal",
@@ -78,9 +99,16 @@ class CouchbaseMixin:
78
99
  if since:
79
100
  payload["since"] = since
80
101
 
81
- logger.info("Sending request for channel changes: %s", channel_name)
102
+ logger.info(
103
+ "Sending request for channel changes: %s",
104
+ f"{len(channel_name)} channels" if len(channel_name) > 1 else channel_name[0],
105
+ )
82
106
  response = requests.post(self._couchbase_url + "/_changes", json=payload, headers=self._headers, timeout=10)
83
- logger.debug("Response status for %s: %s", channel_name, response.status_code)
107
+ logger.debug(
108
+ "Response status for %s: %s",
109
+ f"{len(channel_name)} channels" if len(channel_name) > 1 else channel_name[0],
110
+ response.status_code,
111
+ )
84
112
  response.raise_for_status()
85
113
  data = response.json()
86
114
  except requests.HTTPError as err:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mousetools
3
- Version: 4.4.2
3
+ Version: 4.5.1
4
4
  Summary: An unofficial Python wrapper for Disney's API
5
5
  Author: Scott Caratozzolo
6
6
  License-Expression: MIT
@@ -17,6 +17,7 @@ Classifier: Operating System :: OS Independent
17
17
  Requires-Python: <4.0,>=3.9
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
+ Requires-Dist: more-itertools>=10.7.0
20
21
  Requires-Dist: pycryptodome>=3.22.0
21
22
  Requires-Dist: python-dateutil>=2.9.0.post0
22
23
  Requires-Dist: requests>=2.32.3
@@ -15,6 +15,7 @@ mousetools/api/menu.py
15
15
  mousetools/api/search.py
16
16
  mousetools/channels/__init__.py
17
17
  mousetools/channels/enums.py
18
+ mousetools/channels/manager.py
18
19
  mousetools/channels/calendar/__init__.py
19
20
  mousetools/channels/characters/__init__.py
20
21
  mousetools/channels/facilities/__init__.py
@@ -26,4 +27,5 @@ mousetools/mixins/__init__.py
26
27
  mousetools/mixins/couchbase.py
27
28
  mousetools/mixins/disney.py
28
29
  mousetools/resources/environments.bin
30
+ tests/test_manager.py
29
31
  tests/test_mixins.py
@@ -1,3 +1,4 @@
1
+ more-itertools>=10.7.0
1
2
  pycryptodome>=3.22.0
2
3
  python-dateutil>=2.9.0.post0
3
4
  requests>=2.32.3
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mousetools"
3
- version = "4.4.2"
3
+ version = "4.5.1"
4
4
  authors = [{name = "Scott Caratozzolo"}]
5
5
  description = "An unofficial Python wrapper for Disney's API"
6
6
  readme = "README.md"
@@ -8,6 +8,7 @@ license = "MIT"
8
8
  license-files = ["LICENSE"]
9
9
  requires-python = ">=3.9, <4.0"
10
10
  dependencies = [
11
+ "more-itertools>=10.7.0",
11
12
  "pycryptodome>=3.22.0",
12
13
  "python-dateutil>=2.9.0.post0",
13
14
  "requests>=2.32.3",
@@ -0,0 +1,28 @@
1
+ from mousetools.channels.facilities import FacilityChannel, FacilityChildChannel
2
+ from mousetools.channels.facilities.enums import DLRCouchbaseChannels, WDWCouchbaseChannels
3
+ from mousetools.channels.manager import ChannelManager
4
+
5
+
6
+ def test_manager():
7
+ channel_manager = ChannelManager([])
8
+ assert channel_manager.get_channel_ids() == []
9
+
10
+ wdw = FacilityChannel(WDWCouchbaseChannels.FACILITIES)
11
+ dlr = FacilityChannel(DLRCouchbaseChannels.FACILITIES)
12
+ channel_manager.add_channel(wdw)
13
+ channel_manager.add_channel(dlr)
14
+ assert channel_manager.get_all_channels() == [wdw, dlr]
15
+
16
+ channel = channel_manager.remove_channel(WDWCouchbaseChannels.FACILITIES.value, return_channel=True)
17
+ assert channel.channel_id == WDWCouchbaseChannels.FACILITIES.value
18
+ assert len(channel_manager.get_all_channels()) == 1
19
+
20
+ channel_manager.add_channel(
21
+ FacilityChildChannel("wdw.facilities.1_0.en_us.attraction.80010177;entityType=Attraction")
22
+ )
23
+ assert dlr._cb_data is None
24
+ channel_manager.refresh()
25
+ assert dlr._cb_data is not None
26
+
27
+ channel = channel_manager.get_channel("wdw.facilities.1_0.en_us.attraction.80010177;entityType=Attraction")
28
+ assert channel.channel_id == "wdw.facilities.1_0.en_us.attraction.80010177;entityType=Attraction"
File without changes
File without changes
File without changes