python-roborock 3.9.0__tar.gz → 3.9.2__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 (97) hide show
  1. {python_roborock-3.9.0 → python_roborock-3.9.2}/PKG-INFO +1 -1
  2. {python_roborock-3.9.0 → python_roborock-3.9.2}/pyproject.toml +1 -1
  3. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/cli.py +34 -39
  4. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/containers.py +12 -6
  5. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/device_features.py +1 -1
  6. python_roborock-3.9.2/roborock/devices/cache.py +143 -0
  7. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/device_manager.py +4 -3
  8. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/__init__.py +12 -11
  9. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/device_features.py +5 -5
  10. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/home.py +24 -34
  11. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/network_info.py +9 -8
  12. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/v1_channel.py +21 -12
  13. python_roborock-3.9.0/roborock/devices/cache.py +0 -77
  14. {python_roborock-3.9.0 → python_roborock-3.9.2}/.gitignore +0 -0
  15. {python_roborock-3.9.0 → python_roborock-3.9.2}/LICENSE +0 -0
  16. {python_roborock-3.9.0 → python_roborock-3.9.2}/README.md +0 -0
  17. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/__init__.py +0 -0
  18. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/api.py +0 -0
  19. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/broadcast_protocol.py +0 -0
  20. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/callbacks.py +0 -0
  21. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/cloud_api.py +0 -0
  22. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/command_cache.py +0 -0
  23. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/const.py +0 -0
  24. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/__init__.py +0 -0
  25. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q10/__init__.py +0 -0
  26. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q10/b01_q10_code_mappings.py +0 -0
  27. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q10/b01_q10_containers.py +0 -0
  28. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q7/__init__.py +0 -0
  29. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q7/b01_q7_code_mappings.py +0 -0
  30. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/b01_q7/b01_q7_containers.py +0 -0
  31. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/code_mappings.py +0 -0
  32. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/dyad/__init__.py +0 -0
  33. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/dyad/dyad_code_mappings.py +0 -0
  34. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/dyad/dyad_containers.py +0 -0
  35. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/v1/__init__.py +0 -0
  36. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/v1/v1_clean_modes.py +0 -0
  37. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/v1/v1_code_mappings.py +0 -0
  38. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/v1/v1_containers.py +0 -0
  39. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/zeo/__init__.py +0 -0
  40. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/zeo/zeo_code_mappings.py +0 -0
  41. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/data/zeo/zeo_containers.py +0 -0
  42. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/README.md +0 -0
  43. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/__init__.py +0 -0
  44. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/a01_channel.py +0 -0
  45. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/b01_channel.py +0 -0
  46. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/channel.py +0 -0
  47. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/device.py +0 -0
  48. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/file_cache.py +0 -0
  49. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/local_channel.py +0 -0
  50. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/mqtt_channel.py +0 -0
  51. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/__init__.py +0 -0
  52. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/a01/__init__.py +0 -0
  53. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/b01/__init__.py +0 -0
  54. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/traits_mixin.py +0 -0
  55. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/child_lock.py +0 -0
  56. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/clean_summary.py +0 -0
  57. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/command.py +0 -0
  58. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/common.py +0 -0
  59. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/consumeable.py +0 -0
  60. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/do_not_disturb.py +0 -0
  61. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/dust_collection_mode.py +0 -0
  62. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/flow_led_status.py +0 -0
  63. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/led_status.py +0 -0
  64. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/map_content.py +0 -0
  65. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/maps.py +0 -0
  66. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/rooms.py +0 -0
  67. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/routines.py +0 -0
  68. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/smart_wash_params.py +0 -0
  69. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/status.py +0 -0
  70. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/valley_electricity_timer.py +0 -0
  71. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/volume.py +0 -0
  72. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/devices/traits/v1/wash_towel_mode.py +0 -0
  73. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/exceptions.py +0 -0
  74. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/map/__init__.py +0 -0
  75. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/map/map_parser.py +0 -0
  76. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/mqtt/__init__.py +0 -0
  77. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/mqtt/health_manager.py +0 -0
  78. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/mqtt/roborock_session.py +0 -0
  79. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/mqtt/session.py +0 -0
  80. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocol.py +0 -0
  81. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocols/__init__.py +0 -0
  82. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocols/a01_protocol.py +0 -0
  83. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocols/b01_protocol.py +0 -0
  84. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/protocols/v1_protocol.py +0 -0
  85. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/py.typed +0 -0
  86. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/roborock_future.py +0 -0
  87. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/roborock_message.py +0 -0
  88. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/roborock_typing.py +0 -0
  89. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/util.py +0 -0
  90. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/__init__.py +0 -0
  91. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/roborock_client_v1.py +0 -0
  92. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/roborock_local_client_v1.py +0 -0
  93. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_1_apis/roborock_mqtt_client_v1.py +0 -0
  94. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_a01_apis/__init__.py +0 -0
  95. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_a01_apis/roborock_client_a01.py +0 -0
  96. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/version_a01_apis/roborock_mqtt_client_a01.py +0 -0
  97. {python_roborock-3.9.0 → python_roborock-3.9.2}/roborock/web_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-roborock
3
- Version: 3.9.0
3
+ Version: 3.9.2
4
4
  Summary: A package to control Roborock vacuums.
5
5
  Project-URL: Repository, https://github.com/humbertogontijo/python-roborock
6
6
  Project-URL: Documentation, https://python-roborock.readthedocs.io/
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "python-roborock"
3
- version = "3.9.0"
3
+ version = "3.9.2"
4
4
  description = "A package to control Roborock vacuums."
5
5
  authors = [{ name = "humbertogontijo", email = "humbertogontijo@users.noreply.github.com" }, {name="Lash-L"}, {name="allenporter"}]
6
6
  requires-python = ">=3.11, <4"
@@ -42,7 +42,7 @@ from pyshark.capture.live_capture import LiveCapture, UnknownInterfaceException
42
42
  from pyshark.packet.packet import Packet # type: ignore
43
43
 
44
44
  from roborock import SHORT_MODEL_TO_ENUM, RoborockCommand
45
- from roborock.data import CombinedMapInfo, DeviceData, HomeData, NetworkInfo, RoborockBase, UserData
45
+ from roborock.data import DeviceData, RoborockBase, UserData
46
46
  from roborock.device_features import DeviceFeatures
47
47
  from roborock.devices.cache import Cache, CacheData
48
48
  from roborock.devices.device import RoborockDevice
@@ -116,10 +116,8 @@ class ConnectionCache(RoborockBase):
116
116
 
117
117
  user_data: UserData
118
118
  email: str
119
- home_data: HomeData | None = None
120
- network_info: dict[str, NetworkInfo] | None = None
121
- home_map_info: dict[int, CombinedMapInfo] | None = None
122
- trait_data: dict[str, Any] | None = None
119
+ # TODO: Used new APIs for cache file storage
120
+ cache_data: CacheData | None = None
123
121
 
124
122
 
125
123
  class DeviceConnectionManager:
@@ -134,10 +132,10 @@ class DeviceConnectionManager:
134
132
  async def ensure_device_manager(self) -> DeviceManager:
135
133
  """Ensure device manager is initialized."""
136
134
  if self.device_manager is None:
137
- cache_data = self.context.cache_data()
135
+ connection_cache = self.context.connection_cache()
138
136
  user_params = UserParams(
139
- username=cache_data.email,
140
- user_data=cache_data.user_data,
137
+ username=connection_cache.email,
138
+ user_data=connection_cache.user_data,
141
139
  )
142
140
  self.device_manager = await create_device_manager(user_params, cache=self.context)
143
141
  # Cache devices for quick lookup
@@ -164,7 +162,8 @@ class RoborockContext(Cache):
164
162
  """Context that handles both CLI and session modes internally."""
165
163
 
166
164
  roborock_file = Path("~/.roborock").expanduser()
167
- _cache_data: ConnectionCache | None = None
165
+ roborock_cache_file = Path("~/.roborock.cache").expanduser()
166
+ _connection_cache: ConnectionCache | None = None
168
167
 
169
168
  def __init__(self):
170
169
  self.reload()
@@ -177,22 +176,22 @@ class RoborockContext(Cache):
177
176
  with open(self.roborock_file) as f:
178
177
  data = json.load(f)
179
178
  if data:
180
- self._cache_data = ConnectionCache.from_dict(data)
179
+ self._connection_cache = ConnectionCache.from_dict(data)
181
180
 
182
- def update(self, cache_data: ConnectionCache):
183
- data = json.dumps(cache_data.as_dict(), default=vars, indent=4)
181
+ def update(self, connection_cache: ConnectionCache):
182
+ data = json.dumps(connection_cache.as_dict(), default=vars, indent=4)
184
183
  with open(self.roborock_file, "w") as f:
185
184
  f.write(data)
186
185
  self.reload()
187
186
 
188
187
  def validate(self):
189
- if self._cache_data is None:
188
+ if self._connection_cache is None:
190
189
  raise RoborockException("You must login first")
191
190
 
192
- def cache_data(self) -> ConnectionCache:
191
+ def connection_cache(self) -> ConnectionCache:
193
192
  """Get the cache data."""
194
193
  self.validate()
195
- return cast(ConnectionCache, self._cache_data)
194
+ return cast(ConnectionCache, self._connection_cache)
196
195
 
197
196
  def start_session_mode(self):
198
197
  """Start session mode with a background event loop."""
@@ -229,19 +228,21 @@ class RoborockContext(Cache):
229
228
 
230
229
  async def refresh_devices(self) -> ConnectionCache:
231
230
  """Refresh device data from server (always fetches fresh data)."""
232
- cache_data = self.cache_data()
233
- client = RoborockApiClient(cache_data.email)
234
- home_data = await client.get_home_data_v3(cache_data.user_data)
235
- cache_data.home_data = home_data
236
- self.update(cache_data)
237
- return cache_data
231
+ connection_cache = self.connection_cache()
232
+ client = RoborockApiClient(connection_cache.email)
233
+ home_data = await client.get_home_data_v3(connection_cache.user_data)
234
+ if connection_cache.cache_data is None:
235
+ connection_cache.cache_data = CacheData()
236
+ connection_cache.cache_data.home_data = home_data
237
+ self.update(connection_cache)
238
+ return connection_cache
238
239
 
239
240
  async def get_devices(self) -> ConnectionCache:
240
241
  """Get device data (uses cache if available, fetches if needed)."""
241
- cache_data = self.cache_data()
242
- if not cache_data.home_data:
243
- cache_data = await self.refresh_devices()
244
- return cache_data
242
+ connection_cache = self.connection_cache()
243
+ if (connection_cache.cache_data is None) or (connection_cache.cache_data.home_data is None):
244
+ connection_cache = await self.refresh_devices()
245
+ return connection_cache
245
246
 
246
247
  async def cleanup(self):
247
248
  """Clean up resources (mainly for session mode)."""
@@ -266,22 +267,16 @@ class RoborockContext(Cache):
266
267
  async def get(self) -> CacheData:
267
268
  """Get cached value."""
268
269
  _LOGGER.debug("Getting cache data")
269
- connection_cache = self.cache_data()
270
- return CacheData(
271
- home_data=connection_cache.home_data,
272
- network_info=connection_cache.network_info or {},
273
- home_map_info=connection_cache.home_map_info,
274
- trait_data=connection_cache.trait_data or {},
275
- )
270
+ connection_cache = self.connection_cache()
271
+ if connection_cache.cache_data is not None:
272
+ return connection_cache.cache_data
273
+ return CacheData()
276
274
 
277
275
  async def set(self, value: CacheData) -> None:
278
276
  """Set value in the cache."""
279
277
  _LOGGER.debug("Setting cache data")
280
- connection_cache = self.cache_data()
281
- connection_cache.home_data = value.home_data
282
- connection_cache.network_info = value.network_info
283
- connection_cache.home_map_info = value.home_map_info
284
- connection_cache.trait_data = value.trait_data
278
+ connection_cache = self.connection_cache()
279
+ connection_cache.cache_data = value
285
280
  self.update(connection_cache)
286
281
 
287
282
 
@@ -367,9 +362,9 @@ async def discover(ctx):
367
362
  @async_command
368
363
  async def list_devices(ctx):
369
364
  context: RoborockContext = ctx.obj
370
- cache_data = await context.get_devices()
365
+ connection_cache = await context.get_devices()
371
366
 
372
- home_data = cache_data.home_data
367
+ home_data = connection_cache.cache_data.home_data
373
368
 
374
369
  device_name_id = {device.name: device.duid for device in home_data.get_all_devices()}
375
370
  click.echo(json.dumps(device_name_id, indent=4))
@@ -1,5 +1,6 @@
1
1
  import dataclasses
2
2
  import datetime
3
+ import inspect
3
4
  import json
4
5
  import logging
5
6
  import re
@@ -27,7 +28,11 @@ def _camelize(s: str):
27
28
 
28
29
 
29
30
  def _decamelize(s: str):
30
- return re.sub("([A-Z]+)", "_\\1", s).lower()
31
+ # Split before uppercase letters not at the start, and before numbers
32
+ s = re.sub(r"(?<=[a-z0-9])([A-Z])", r"_\1", s)
33
+ s = re.sub(r"([A-Z]+)([A-Z][a-z])", r"\1_\2", s) # Split acronyms followed by normal camelCase
34
+ s = re.sub(r"([a-zA-Z])([0-9]+)", r"\1_\2", s)
35
+ return s.lower()
31
36
 
32
37
 
33
38
  def _attr_repr(obj: Any) -> str:
@@ -64,11 +69,12 @@ class RoborockBase:
64
69
  if get_origin(class_type) is dict:
65
70
  _, value_type = get_args(class_type) # assume keys are only basic types
66
71
  return {k: RoborockBase._convert_to_class_obj(value_type, v) for k, v in value.items()}
67
- if issubclass(class_type, RoborockBase):
68
- return class_type.from_dict(value)
69
- if issubclass(class_type, RoborockModeEnum):
70
- return class_type.from_code(value)
71
- if class_type is Any:
72
+ if inspect.isclass(class_type):
73
+ if issubclass(class_type, RoborockBase):
74
+ return class_type.from_dict(value)
75
+ if issubclass(class_type, RoborockModeEnum):
76
+ return class_type.from_code(value)
77
+ if class_type is Any or type(class_type) is str:
72
78
  return value
73
79
  return class_type(value) # type: ignore[call-arg]
74
80
 
@@ -313,7 +313,7 @@ class DeviceFeatures(RoborockBase):
313
313
  is_support_incremental_map: bool = field(metadata={"new_feature_str_mask": (4194304, 8)})
314
314
  is_offline_map_supported: bool = field(metadata={"new_feature_str_mask": (16384, 8)})
315
315
  is_super_deep_wash_supported: bool = field(metadata={"new_feature_str_mask": (32768, 8)})
316
- is_ces2022_supported: bool = field(metadata={"new_feature_str_mask": (65536, 8)})
316
+ is_ces_2022_supported: bool = field(metadata={"new_feature_str_mask": (65536, 8)})
317
317
  is_dss_believable: bool = field(metadata={"new_feature_str_mask": (131072, 8)})
318
318
  is_main_brush_up_down_supported_from_str: bool = field(metadata={"new_feature_str_mask": (262144, 8)})
319
319
  is_goto_pure_clean_path_supported: bool = field(metadata={"new_feature_str_mask": (524288, 8)})
@@ -0,0 +1,143 @@
1
+ """This module provides caching functionality for the Roborock device management system.
2
+
3
+ This module defines a cache interface that you may use to cache device
4
+ information to avoid unnecessary API calls. Callers may implement
5
+ this interface to provide their own caching mechanism.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from typing import Any, Protocol
10
+
11
+ from roborock.data import CombinedMapInfo, HomeData, NetworkInfo, RoborockBase
12
+ from roborock.device_features import DeviceFeatures
13
+
14
+
15
+ @dataclass
16
+ class DeviceCacheData(RoborockBase):
17
+ """Data structure for caching device information."""
18
+
19
+ network_info: NetworkInfo | None = None
20
+ """Network information for the device"""
21
+
22
+ home_map_info: dict[int, CombinedMapInfo] | None = None
23
+ """Home map information for the device by map_flag."""
24
+
25
+ home_map_content_base64: dict[int, str] | None = None
26
+ """Home cache content for the device (encoded base64) by map_flag."""
27
+
28
+ device_features: DeviceFeatures | None = None
29
+ """Device features information."""
30
+
31
+ trait_data: dict[str, Any] | None = None
32
+ """Trait-specific cached data used internally for caching device features."""
33
+
34
+
35
+ @dataclass
36
+ class CacheData(RoborockBase):
37
+ """Data structure for caching device information."""
38
+
39
+ home_data: HomeData | None = None
40
+ """Home data containing device and product information."""
41
+
42
+ device_info: dict[str, DeviceCacheData] = field(default_factory=dict)
43
+ """Per-device cached information indexed by device DUID."""
44
+
45
+ network_info: dict[str, NetworkInfo] = field(default_factory=dict)
46
+ """Network information indexed by device DUID.
47
+
48
+ This is deprecated. Use the per-device `network_info` field instead.
49
+ """
50
+
51
+ home_map_info: dict[int, CombinedMapInfo] = field(default_factory=dict)
52
+ """Home map information indexed by map_flag.
53
+
54
+ This is deprecated. Use the per-device `home_map_info` field instead.
55
+ """
56
+
57
+ home_map_content: dict[int, bytes] = field(default_factory=dict)
58
+ """Home cache content for each map data indexed by map_flag.
59
+
60
+ This is deprecated. Use the per-device `home_map_content_base64` field instead.
61
+ """
62
+
63
+ home_map_content_base64: dict[int, str] = field(default_factory=dict)
64
+ """Home cache content for each map data (encoded base64) indexed by map_flag.
65
+
66
+ This is deprecated. Use the per-device `home_map_content_base64` field instead.
67
+ """
68
+
69
+ device_features: DeviceFeatures | None = None
70
+ """Device features information.
71
+
72
+ This is deprecated. Use the per-device `device_features` field instead.
73
+ """
74
+
75
+ trait_data: dict[str, Any] | None = None
76
+ """Trait-specific cached data used internally for caching device features.
77
+
78
+ This is deprecated. Use the per-device `trait_data` field instead.
79
+ """
80
+
81
+
82
+ class Cache(Protocol):
83
+ """Protocol for a cache that can store and retrieve values."""
84
+
85
+ async def get(self) -> CacheData:
86
+ """Get cached value."""
87
+ ...
88
+
89
+ async def set(self, value: CacheData) -> None:
90
+ """Set value in the cache."""
91
+ ...
92
+
93
+
94
+ @dataclass
95
+ class DeviceCache(RoborockBase):
96
+ """Provides a cache interface for a specific device.
97
+
98
+ This is a convenience wrapper around a general Cache implementation to
99
+ provide device-specific caching functionality.
100
+ """
101
+
102
+ def __init__(self, duid: str, cache: Cache) -> None:
103
+ """Initialize the device cache with the given cache implementation."""
104
+ self._duid = duid
105
+ self._cache = cache
106
+
107
+ async def get(self) -> DeviceCacheData:
108
+ """Get cached device-specific information."""
109
+ cache_data = await self._cache.get()
110
+ if self._duid not in cache_data.device_info:
111
+ cache_data.device_info[self._duid] = DeviceCacheData()
112
+ await self._cache.set(cache_data)
113
+ return cache_data.device_info[self._duid]
114
+
115
+ async def set(self, device_cache_data: DeviceCacheData) -> None:
116
+ """Set cached device-specific information."""
117
+ cache_data = await self._cache.get()
118
+ cache_data.device_info[self._duid] = device_cache_data
119
+ await self._cache.set(cache_data)
120
+
121
+
122
+ class InMemoryCache(Cache):
123
+ """In-memory cache implementation."""
124
+
125
+ def __init__(self) -> None:
126
+ """Initialize the in-memory cache."""
127
+ self._data = CacheData()
128
+
129
+ async def get(self) -> CacheData:
130
+ return self._data
131
+
132
+ async def set(self, value: CacheData) -> None:
133
+ self._data = value
134
+
135
+
136
+ class NoCache(Cache):
137
+ """No-op cache implementation."""
138
+
139
+ async def get(self) -> CacheData:
140
+ return CacheData()
141
+
142
+ async def set(self, value: CacheData) -> None:
143
+ pass
@@ -21,7 +21,7 @@ from roborock.mqtt.session import MqttSession
21
21
  from roborock.protocol import create_mqtt_params
22
22
  from roborock.web_api import RoborockApiClient, UserWebApiClient
23
23
 
24
- from .cache import Cache, NoCache
24
+ from .cache import Cache, DeviceCache, NoCache
25
25
  from .channel import Channel
26
26
  from .mqtt_channel import create_mqtt_channel
27
27
  from .traits import Trait, a01, b01, v1
@@ -177,9 +177,10 @@ async def create_device_manager(
177
177
  def device_creator(home_data: HomeData, device: HomeDataDevice, product: HomeDataProduct) -> RoborockDevice:
178
178
  channel: Channel
179
179
  trait: Trait
180
+ device_cache: DeviceCache = DeviceCache(device.duid, cache)
180
181
  match device.pv:
181
182
  case DeviceVersion.V1:
182
- channel = create_v1_channel(user_data, mqtt_params, mqtt_session, device, cache)
183
+ channel = create_v1_channel(user_data, mqtt_params, mqtt_session, device, device_cache)
183
184
  trait = v1.create(
184
185
  device.duid,
185
186
  product,
@@ -188,7 +189,7 @@ async def create_device_manager(
188
189
  channel.mqtt_rpc_channel,
189
190
  channel.map_rpc_channel,
190
191
  web_api,
191
- cache,
192
+ device_cache=device_cache,
192
193
  map_parser_config=map_parser_config,
193
194
  )
194
195
  case DeviceVersion.A01:
@@ -32,11 +32,12 @@ optional traits:
32
32
 
33
33
  import logging
34
34
  from dataclasses import dataclass, field, fields
35
+ from functools import cache
35
36
  from typing import Any, get_args
36
37
 
37
38
  from roborock.data.containers import HomeData, HomeDataProduct, RoborockBase
38
39
  from roborock.data.v1.v1_code_mappings import RoborockDockTypeCode
39
- from roborock.devices.cache import Cache
40
+ from roborock.devices.cache import DeviceCache
40
41
  from roborock.devices.traits import Trait
41
42
  from roborock.map.map_parser import MapParserConfig
42
43
  from roborock.protocols.v1_protocol import V1RpcChannel
@@ -131,7 +132,7 @@ class PropertiesApi(Trait):
131
132
  mqtt_rpc_channel: V1RpcChannel,
132
133
  map_rpc_channel: V1RpcChannel,
133
134
  web_api: UserWebApiClient,
134
- cache: Cache,
135
+ device_cache: DeviceCache,
135
136
  map_parser_config: MapParserConfig | None = None,
136
137
  ) -> None:
137
138
  """Initialize the V1TraitProps."""
@@ -140,16 +141,16 @@ class PropertiesApi(Trait):
140
141
  self._mqtt_rpc_channel = mqtt_rpc_channel
141
142
  self._map_rpc_channel = map_rpc_channel
142
143
  self._web_api = web_api
143
- self._cache = cache
144
+ self._device_cache = device_cache
144
145
 
145
146
  self.status = StatusTrait(product)
146
147
  self.consumables = ConsumableTrait()
147
148
  self.rooms = RoomsTrait(home_data)
148
149
  self.maps = MapsTrait(self.status)
149
150
  self.map_content = MapContentTrait(map_parser_config)
150
- self.home = HomeTrait(self.status, self.maps, self.map_content, self.rooms, cache)
151
- self.device_features = DeviceFeaturesTrait(product.product_nickname, cache)
152
- self.network_info = NetworkInfoTrait(device_uid, cache)
151
+ self.home = HomeTrait(self.status, self.maps, self.map_content, self.rooms, self._device_cache)
152
+ self.device_features = DeviceFeaturesTrait(product.product_nickname, self._device_cache)
153
+ self.network_info = NetworkInfoTrait(device_uid, self._device_cache)
153
154
  self.routines = RoutinesTrait(device_uid, web_api)
154
155
 
155
156
  # Dynamically create any traits that need to be populated
@@ -239,7 +240,7 @@ class PropertiesApi(Trait):
239
240
 
240
241
  async def _get_cached_trait_data(self, name: str) -> Any:
241
242
  """Get the dock type from the status trait or cache."""
242
- cache_data = await self._cache.get()
243
+ cache_data = await self._device_cache.get()
243
244
  if cache_data.trait_data is None:
244
245
  cache_data.trait_data = {}
245
246
  _LOGGER.debug("Cached trait data: %s", cache_data.trait_data)
@@ -247,12 +248,12 @@ class PropertiesApi(Trait):
247
248
 
248
249
  async def _set_cached_trait_data(self, name: str, value: Any) -> None:
249
250
  """Set trait-specific cached data."""
250
- cache_data = await self._cache.get()
251
+ cache_data = await self._device_cache.get()
251
252
  if cache_data.trait_data is None:
252
253
  cache_data.trait_data = {}
253
254
  cache_data.trait_data[name] = value
254
255
  _LOGGER.debug("Updating cached trait data: %s", cache_data.trait_data)
255
- await self._cache.set(cache_data)
256
+ await self._device_cache.set(cache_data)
256
257
 
257
258
  def as_dict(self) -> dict[str, Any]:
258
259
  """Return the trait data as a dictionary."""
@@ -275,7 +276,7 @@ def create(
275
276
  mqtt_rpc_channel: V1RpcChannel,
276
277
  map_rpc_channel: V1RpcChannel,
277
278
  web_api: UserWebApiClient,
278
- cache: Cache,
279
+ device_cache: DeviceCache,
279
280
  map_parser_config: MapParserConfig | None = None,
280
281
  ) -> PropertiesApi:
281
282
  """Create traits for V1 devices."""
@@ -287,6 +288,6 @@ def create(
287
288
  mqtt_rpc_channel,
288
289
  map_rpc_channel,
289
290
  web_api,
290
- cache,
291
+ device_cache,
291
292
  map_parser_config,
292
293
  )
@@ -2,7 +2,7 @@ from dataclasses import fields
2
2
 
3
3
  from roborock.data import AppInitStatus, RoborockProductNickname
4
4
  from roborock.device_features import DeviceFeatures
5
- from roborock.devices.cache import Cache
5
+ from roborock.devices.cache import DeviceCache
6
6
  from roborock.devices.traits.v1 import common
7
7
  from roborock.roborock_typing import RoborockCommand
8
8
 
@@ -12,10 +12,10 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
12
12
 
13
13
  command = RoborockCommand.APP_GET_INIT_STATUS
14
14
 
15
- def __init__(self, product_nickname: RoborockProductNickname, cache: Cache) -> None: # pylint: disable=super-init-not-called
15
+ def __init__(self, product_nickname: RoborockProductNickname, device_cache: DeviceCache) -> None: # pylint: disable=super-init-not-called
16
16
  """Initialize MapContentTrait."""
17
17
  self._nickname = product_nickname
18
- self._cache = cache
18
+ self._device_cache = device_cache
19
19
  # All fields of DeviceFeatures are required. Initialize them to False
20
20
  # so we have some known state.
21
21
  for field in fields(self):
@@ -28,14 +28,14 @@ class DeviceFeaturesTrait(DeviceFeatures, common.V1TraitMixin):
28
28
  change often and this avoids unnecessary RPC calls. This would only
29
29
  ever change with a firmware update, so caching is appropriate.
30
30
  """
31
- cache_data = await self._cache.get()
31
+ cache_data = await self._device_cache.get()
32
32
  if cache_data.device_features is not None:
33
33
  self._update_trait_values(cache_data.device_features)
34
34
  return
35
35
  # Save cached device features
36
36
  await super().refresh()
37
37
  cache_data.device_features = self
38
- await self._cache.set(cache_data)
38
+ await self._device_cache.set(cache_data)
39
39
 
40
40
  def _parse_response(self, response: common.V1ResponseData) -> DeviceFeatures:
41
41
  """Parse the response from the device into a MapContentTrait instance."""
@@ -22,7 +22,7 @@ from typing import Self
22
22
 
23
23
  from roborock.data import CombinedMapInfo, RoborockBase
24
24
  from roborock.data.v1.v1_code_mappings import RoborockStateCode
25
- from roborock.devices.cache import Cache
25
+ from roborock.devices.cache import DeviceCache
26
26
  from roborock.devices.traits.v1 import common
27
27
  from roborock.exceptions import RoborockDeviceBusy, RoborockException
28
28
  from roborock.roborock_typing import RoborockCommand
@@ -48,7 +48,7 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
48
48
  maps_trait: MapsTrait,
49
49
  map_content: MapContentTrait,
50
50
  rooms_trait: RoomsTrait,
51
- cache: Cache,
51
+ device_cache: DeviceCache,
52
52
  ) -> None:
53
53
  """Initialize the HomeTrait.
54
54
 
@@ -70,7 +70,7 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
70
70
  self._maps_trait = maps_trait
71
71
  self._map_content = map_content
72
72
  self._rooms_trait = rooms_trait
73
- self._cache = cache
73
+ self._device_cache = device_cache
74
74
  self._discovery_completed = False
75
75
  self._home_map_info: dict[int, CombinedMapInfo] | None = None
76
76
  self._home_map_content: dict[int, MapContent] | None = None
@@ -86,21 +86,16 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
86
86
 
87
87
  After discovery, the home cache will be populated and can be accessed via the `home_map_info` property.
88
88
  """
89
- cache_data = await self._cache.get()
90
- if cache_data.home_map_info and (cache_data.home_map_content or cache_data.home_map_content_base64):
89
+ device_cache_data = await self._device_cache.get()
90
+ if device_cache_data and device_cache_data.home_map_info:
91
91
  _LOGGER.debug("Home cache already populated, skipping discovery")
92
- self._home_map_info = cache_data.home_map_info
92
+ self._home_map_info = device_cache_data.home_map_info
93
93
  self._discovery_completed = True
94
94
  try:
95
- if cache_data.home_map_content_base64:
96
- self._home_map_content = {
97
- k: self._map_content.parse_map_content(base64.b64decode(v))
98
- for k, v in cache_data.home_map_content_base64.items()
99
- }
100
- else:
101
- self._home_map_content = {
102
- k: self._map_content.parse_map_content(v) for k, v in cache_data.home_map_content.items()
103
- }
95
+ self._home_map_content = {
96
+ k: self._map_content.parse_map_content(base64.b64decode(v))
97
+ for k, v in (device_cache_data.home_map_content_base64 or {}).items()
98
+ }
104
99
  except (ValueError, RoborockException) as ex:
105
100
  _LOGGER.warning("Failed to parse cached home map content, will re-discover: %s", ex)
106
101
  self._home_map_content = {}
@@ -223,15 +218,14 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
223
218
  self, home_map_info: dict[int, CombinedMapInfo], home_map_content: dict[int, MapContent]
224
219
  ) -> None:
225
220
  """Update the entire home cache with new map info and content."""
226
- cache_data = await self._cache.get()
227
- cache_data.home_map_info = home_map_info
228
- cache_data.home_map_content_base64 = {
221
+ device_cache_data = await self._device_cache.get()
222
+ device_cache_data.home_map_info = home_map_info
223
+ device_cache_data.home_map_content_base64 = {
229
224
  k: base64.b64encode(v.raw_api_response).decode("utf-8")
230
225
  for k, v in home_map_content.items()
231
226
  if v.raw_api_response
232
227
  }
233
- cache_data.home_map_content = {}
234
- await self._cache.set(cache_data)
228
+ await self._device_cache.set(device_cache_data)
235
229
  self._home_map_info = home_map_info
236
230
  self._home_map_content = home_map_content
237
231
 
@@ -247,21 +241,17 @@ class HomeTrait(RoborockBase, common.V1TraitMixin):
247
241
  # completed and we want to keep it fresh. Otherwise just update the
248
242
  # in memory map below.
249
243
  if update_cache:
250
- cache_data = await self._cache.get()
251
- cache_data.home_map_info[map_flag] = map_info
252
- # Migrate existing cached content to base64 if needed
253
- if cache_data.home_map_content and not cache_data.home_map_content_base64:
254
- cache_data.home_map_content_base64 = {
255
- k: base64.b64encode(v).decode("utf-8") for k, v in cache_data.home_map_content.items()
256
- }
257
- cache_data.home_map_content = {}
244
+ device_cache_data = await self._device_cache.get()
245
+ if device_cache_data.home_map_info is None:
246
+ device_cache_data.home_map_info = {}
247
+ device_cache_data.home_map_info[map_flag] = map_info
258
248
  if map_content.raw_api_response:
259
- if cache_data.home_map_content_base64 is None:
260
- cache_data.home_map_content_base64 = {}
261
- cache_data.home_map_content_base64[map_flag] = base64.b64encode(map_content.raw_api_response).decode(
262
- "utf-8"
263
- )
264
- await self._cache.set(cache_data)
249
+ if device_cache_data.home_map_content_base64 is None:
250
+ device_cache_data.home_map_content_base64 = {}
251
+ device_cache_data.home_map_content_base64[map_flag] = base64.b64encode(
252
+ map_content.raw_api_response
253
+ ).decode("utf-8")
254
+ await self._device_cache.set(device_cache_data)
265
255
 
266
256
  if self._home_map_info is None:
267
257
  self._home_map_info = {}
@@ -5,7 +5,7 @@ from __future__ import annotations
5
5
  import logging
6
6
 
7
7
  from roborock.data import NetworkInfo
8
- from roborock.devices.cache import Cache
8
+ from roborock.devices.cache import DeviceCache
9
9
  from roborock.devices.traits.v1 import common
10
10
  from roborock.roborock_typing import RoborockCommand
11
11
 
@@ -24,19 +24,19 @@ class NetworkInfoTrait(NetworkInfo, common.V1TraitMixin):
24
24
 
25
25
  command = RoborockCommand.GET_NETWORK_INFO
26
26
 
27
- def __init__(self, device_uid: str, cache: Cache) -> None: # pylint: disable=super-init-not-called
27
+ def __init__(self, device_uid: str, device_cache: DeviceCache) -> None: # pylint: disable=super-init-not-called
28
28
  """Initialize the trait."""
29
29
  self._device_uid = device_uid
30
- self._cache = cache
30
+ self._device_cache = device_cache
31
31
  self.ip = ""
32
32
 
33
33
  async def refresh(self) -> None:
34
34
  """Refresh the network info from the cache."""
35
35
 
36
- cache_data = await self._cache.get()
37
- if cache_data.network_info and (network_info := cache_data.network_info.get(self._device_uid)):
36
+ device_cache_data = await self._device_cache.get()
37
+ if device_cache_data.network_info:
38
38
  _LOGGER.debug("Using cached network info for device %s", self._device_uid)
39
- self._update_trait_values(network_info)
39
+ self._update_trait_values(device_cache_data.network_info)
40
40
  return
41
41
 
42
42
  # Load from device if not in cache
@@ -44,8 +44,9 @@ class NetworkInfoTrait(NetworkInfo, common.V1TraitMixin):
44
44
  await super().refresh()
45
45
 
46
46
  # Update the cache with the new network info
47
- cache_data.network_info[self._device_uid] = self
48
- await self._cache.set(cache_data)
47
+ device_cache_data = await self._device_cache.get()
48
+ device_cache_data.network_info = self
49
+ await self._device_cache.set(device_cache_data)
49
50
 
50
51
  def _parse_response(self, response: common.V1ResponseData) -> NetworkInfo:
51
52
  """Parse the response from the device into a NetworkInfo."""
@@ -31,7 +31,7 @@ from roborock.protocols.v1_protocol import (
31
31
  from roborock.roborock_message import RoborockMessage, RoborockMessageProtocol
32
32
  from roborock.roborock_typing import RoborockCommand
33
33
 
34
- from .cache import Cache
34
+ from .cache import DeviceCache
35
35
  from .channel import Channel
36
36
  from .local_channel import LocalChannel, LocalSession, create_local_session
37
37
  from .mqtt_channel import MqttChannel
@@ -170,7 +170,7 @@ class V1Channel(Channel):
170
170
  security_data: SecurityData,
171
171
  mqtt_channel: MqttChannel,
172
172
  local_session: LocalSession,
173
- cache: Cache,
173
+ device_cache: DeviceCache,
174
174
  ) -> None:
175
175
  """Initialize the V1Channel."""
176
176
  self._device_uid = device_uid
@@ -182,7 +182,7 @@ class V1Channel(Channel):
182
182
  self._mqtt_unsub: Callable[[], None] | None = None
183
183
  self._local_unsub: Callable[[], None] | None = None
184
184
  self._callback: Callable[[RoborockMessage], None] | None = None
185
- self._cache = cache
185
+ self._device_cache = device_cache
186
186
  self._reconnect_task: asyncio.Task[None] | None = None
187
187
  self._last_network_info_refresh: datetime.datetime | None = None
188
188
 
@@ -318,24 +318,27 @@ class V1Channel(Channel):
318
318
 
319
319
  This is a cloud only command used to get the local device's IP address.
320
320
  """
321
- cache_data = await self._cache.get()
322
- if prefer_cache and cache_data.network_info and (network_info := cache_data.network_info.get(self._device_uid)):
321
+ device_cache_data = await self._device_cache.get()
322
+
323
+ if prefer_cache and device_cache_data.network_info:
323
324
  _LOGGER.debug("Using cached network info for device %s", self._device_uid)
324
- return network_info
325
+ return device_cache_data.network_info
325
326
  try:
326
327
  network_info = await self.mqtt_rpc_channel.send_command(
327
328
  RoborockCommand.GET_NETWORK_INFO, response_type=NetworkInfo
328
329
  )
329
330
  except RoborockException as e:
330
331
  _LOGGER.debug("Error fetching network info for device %s", self._device_uid)
331
- if cache_data.network_info and (network_info := cache_data.network_info.get(self._device_uid)):
332
+ if device_cache_data.network_info:
332
333
  _LOGGER.debug("Falling back to cached network info for device %s after error", self._device_uid)
333
- return network_info
334
+ return device_cache_data.network_info
334
335
  raise RoborockException(f"Network info failed for device {self._device_uid}") from e
335
336
  _LOGGER.debug("Network info for device %s: %s", self._device_uid, network_info)
336
337
  self._last_network_info_refresh = datetime.datetime.now(datetime.UTC)
337
- cache_data.network_info[self._device_uid] = network_info
338
- await self._cache.set(cache_data)
338
+
339
+ device_cache_data = await self._device_cache.get()
340
+ device_cache_data.network_info = network_info
341
+ await self._device_cache.set(device_cache_data)
339
342
  return network_info
340
343
 
341
344
  async def _local_connect(self, *, prefer_cache: bool = True) -> None:
@@ -425,10 +428,16 @@ def create_v1_channel(
425
428
  mqtt_params: MqttParams,
426
429
  mqtt_session: MqttSession,
427
430
  device: HomeDataDevice,
428
- cache: Cache,
431
+ device_cache: DeviceCache,
429
432
  ) -> V1Channel:
430
433
  """Create a V1Channel for the given device."""
431
434
  security_data = create_security_data(user_data.rriot)
432
435
  mqtt_channel = MqttChannel(mqtt_session, device.duid, device.local_key, user_data.rriot, mqtt_params)
433
436
  local_session = create_local_session(device.local_key)
434
- return V1Channel(device.duid, security_data, mqtt_channel, local_session=local_session, cache=cache)
437
+ return V1Channel(
438
+ device.duid,
439
+ security_data,
440
+ mqtt_channel,
441
+ local_session=local_session,
442
+ device_cache=device_cache,
443
+ )
@@ -1,77 +0,0 @@
1
- """This module provides caching functionality for the Roborock device management system.
2
-
3
- This module defines a cache interface that you may use to cache device
4
- information to avoid unnecessary API calls. Callers may implement
5
- this interface to provide their own caching mechanism.
6
- """
7
-
8
- from dataclasses import dataclass, field
9
- from typing import Any, Protocol
10
-
11
- from roborock.data import CombinedMapInfo, HomeData, NetworkInfo, RoborockBase
12
- from roborock.device_features import DeviceFeatures
13
-
14
-
15
- @dataclass
16
- class CacheData(RoborockBase):
17
- """Data structure for caching device information."""
18
-
19
- home_data: HomeData | None = None
20
- """Home data containing device and product information."""
21
-
22
- network_info: dict[str, NetworkInfo] = field(default_factory=dict)
23
- """Network information indexed by device DUID."""
24
-
25
- home_map_info: dict[int, CombinedMapInfo] = field(default_factory=dict)
26
- """Home map information indexed by map_flag."""
27
-
28
- home_map_content: dict[int, bytes] = field(default_factory=dict)
29
- """Home cache content for each map data indexed by map_flag.
30
-
31
- This is deprecated in favor of `home_map_content_base64`.
32
- """
33
-
34
- home_map_content_base64: dict[int, str] = field(default_factory=dict)
35
- """Home cache content for each map data (encoded base64) indexed by map_flag."""
36
-
37
- device_features: DeviceFeatures | None = None
38
- """Device features information."""
39
-
40
- trait_data: dict[str, Any] | None = None
41
- """Trait-specific cached data used internally for caching device features."""
42
-
43
-
44
- class Cache(Protocol):
45
- """Protocol for a cache that can store and retrieve values."""
46
-
47
- async def get(self) -> CacheData:
48
- """Get cached value."""
49
- ...
50
-
51
- async def set(self, value: CacheData) -> None:
52
- """Set value in the cache."""
53
- ...
54
-
55
-
56
- class InMemoryCache(Cache):
57
- """In-memory cache implementation."""
58
-
59
- def __init__(self) -> None:
60
- """Initialize the in-memory cache."""
61
- self._data = CacheData()
62
-
63
- async def get(self) -> CacheData:
64
- return self._data
65
-
66
- async def set(self, value: CacheData) -> None:
67
- self._data = value
68
-
69
-
70
- class NoCache(Cache):
71
- """No-op cache implementation."""
72
-
73
- async def get(self) -> CacheData:
74
- return CacheData()
75
-
76
- async def set(self, value: CacheData) -> None:
77
- pass
File without changes