lifx-emulator 2.4.0__py3-none-any.whl → 3.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. lifx_emulator-3.0.1.dist-info/METADATA +102 -0
  2. lifx_emulator-3.0.1.dist-info/RECORD +18 -0
  3. lifx_emulator-3.0.1.dist-info/entry_points.txt +2 -0
  4. lifx_emulator_app/__init__.py +10 -0
  5. {lifx_emulator → lifx_emulator_app}/__main__.py +2 -3
  6. {lifx_emulator → lifx_emulator_app}/api/__init__.py +1 -1
  7. {lifx_emulator → lifx_emulator_app}/api/app.py +3 -3
  8. {lifx_emulator → lifx_emulator_app}/api/mappers/__init__.py +1 -1
  9. {lifx_emulator → lifx_emulator_app}/api/mappers/device_mapper.py +1 -1
  10. {lifx_emulator → lifx_emulator_app}/api/models.py +1 -2
  11. lifx_emulator_app/api/routers/__init__.py +11 -0
  12. {lifx_emulator → lifx_emulator_app}/api/routers/devices.py +2 -2
  13. {lifx_emulator → lifx_emulator_app}/api/routers/monitoring.py +1 -1
  14. {lifx_emulator → lifx_emulator_app}/api/routers/scenarios.py +1 -1
  15. lifx_emulator_app/api/services/__init__.py +8 -0
  16. {lifx_emulator → lifx_emulator_app}/api/services/device_service.py +3 -2
  17. lifx_emulator/__init__.py +0 -31
  18. lifx_emulator/api/routers/__init__.py +0 -11
  19. lifx_emulator/api/services/__init__.py +0 -8
  20. lifx_emulator/constants.py +0 -33
  21. lifx_emulator/devices/__init__.py +0 -37
  22. lifx_emulator/devices/device.py +0 -395
  23. lifx_emulator/devices/manager.py +0 -256
  24. lifx_emulator/devices/observers.py +0 -139
  25. lifx_emulator/devices/persistence.py +0 -308
  26. lifx_emulator/devices/state_restorer.py +0 -259
  27. lifx_emulator/devices/state_serializer.py +0 -157
  28. lifx_emulator/devices/states.py +0 -381
  29. lifx_emulator/factories/__init__.py +0 -39
  30. lifx_emulator/factories/builder.py +0 -375
  31. lifx_emulator/factories/default_config.py +0 -158
  32. lifx_emulator/factories/factory.py +0 -252
  33. lifx_emulator/factories/firmware_config.py +0 -77
  34. lifx_emulator/factories/serial_generator.py +0 -82
  35. lifx_emulator/handlers/__init__.py +0 -39
  36. lifx_emulator/handlers/base.py +0 -49
  37. lifx_emulator/handlers/device_handlers.py +0 -322
  38. lifx_emulator/handlers/light_handlers.py +0 -503
  39. lifx_emulator/handlers/multizone_handlers.py +0 -249
  40. lifx_emulator/handlers/registry.py +0 -110
  41. lifx_emulator/handlers/tile_handlers.py +0 -488
  42. lifx_emulator/products/__init__.py +0 -28
  43. lifx_emulator/products/generator.py +0 -1079
  44. lifx_emulator/products/registry.py +0 -1530
  45. lifx_emulator/products/specs.py +0 -284
  46. lifx_emulator/products/specs.yml +0 -386
  47. lifx_emulator/protocol/__init__.py +0 -1
  48. lifx_emulator/protocol/base.py +0 -446
  49. lifx_emulator/protocol/const.py +0 -8
  50. lifx_emulator/protocol/generator.py +0 -1384
  51. lifx_emulator/protocol/header.py +0 -159
  52. lifx_emulator/protocol/packets.py +0 -1351
  53. lifx_emulator/protocol/protocol_types.py +0 -817
  54. lifx_emulator/protocol/serializer.py +0 -379
  55. lifx_emulator/repositories/__init__.py +0 -22
  56. lifx_emulator/repositories/device_repository.py +0 -155
  57. lifx_emulator/repositories/storage_backend.py +0 -107
  58. lifx_emulator/scenarios/__init__.py +0 -22
  59. lifx_emulator/scenarios/manager.py +0 -322
  60. lifx_emulator/scenarios/models.py +0 -112
  61. lifx_emulator/scenarios/persistence.py +0 -241
  62. lifx_emulator/server.py +0 -464
  63. lifx_emulator-2.4.0.dist-info/METADATA +0 -107
  64. lifx_emulator-2.4.0.dist-info/RECORD +0 -62
  65. lifx_emulator-2.4.0.dist-info/entry_points.txt +0 -2
  66. lifx_emulator-2.4.0.dist-info/licenses/LICENSE +0 -35
  67. {lifx_emulator-2.4.0.dist-info → lifx_emulator-3.0.1.dist-info}/WHEEL +0 -0
  68. {lifx_emulator → lifx_emulator_app}/api/templates/dashboard.html +0 -0
@@ -1,379 +0,0 @@
1
- """Binary serialization for LIFX protocol packets.
2
-
3
- Handles packing and unpacking of protocol structures using struct module.
4
- All multi-byte values use little-endian byte order per LIFX specification.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import struct
10
- from typing import Any
11
-
12
- # Type format mapping for struct module (little-endian)
13
- TYPE_FORMATS: dict[str, str] = {
14
- "uint8": "B",
15
- "uint16": "H",
16
- "uint32": "I",
17
- "uint64": "Q",
18
- "int8": "b",
19
- "int16": "h",
20
- "int32": "i",
21
- "int64": "q",
22
- "float32": "f",
23
- "bool": "?",
24
- }
25
-
26
- # Type sizes in bytes
27
- TYPE_SIZES: dict[str, int] = {
28
- "uint8": 1,
29
- "uint16": 2,
30
- "uint32": 4,
31
- "uint64": 8,
32
- "int8": 1,
33
- "int16": 2,
34
- "int32": 4,
35
- "int64": 8,
36
- "float32": 4,
37
- "bool": 1,
38
- }
39
-
40
- # Pre-compiled struct.Struct objects for faster pack/unpack (optimization)
41
- _STRUCT_CACHE: dict[str, struct.Struct] = {
42
- "uint8": struct.Struct("<B"),
43
- "uint16": struct.Struct("<H"),
44
- "uint32": struct.Struct("<I"),
45
- "uint64": struct.Struct("<Q"),
46
- "int8": struct.Struct("<b"),
47
- "int16": struct.Struct("<h"),
48
- "int32": struct.Struct("<i"),
49
- "int64": struct.Struct("<q"),
50
- "float32": struct.Struct("<f"),
51
- "bool": struct.Struct("<?"),
52
- }
53
-
54
-
55
- def get_type_size(type_name: str) -> int:
56
- """Get the size in bytes of a type.
57
-
58
- Args:
59
- type_name: Type name (e.g., 'uint16', 'float32')
60
-
61
- Returns:
62
- Size in bytes
63
-
64
- Raises:
65
- ValueError: If type is unknown
66
- """
67
- if type_name not in TYPE_SIZES:
68
- raise ValueError(f"Unknown type: {type_name}")
69
- return TYPE_SIZES[type_name]
70
-
71
-
72
- def pack_value(value: Any, type_name: str) -> bytes:
73
- """Pack a single value into bytes.
74
-
75
- Args:
76
- value: Value to pack
77
- type_name: Type name (e.g., 'uint16', 'float32')
78
-
79
- Returns:
80
- Packed bytes
81
-
82
- Raises:
83
- ValueError: If type is unknown
84
- struct.error: If value doesn't match type
85
- """
86
- if type_name not in _STRUCT_CACHE:
87
- raise ValueError(f"Unknown type: {type_name}")
88
-
89
- return _STRUCT_CACHE[type_name].pack(value)
90
-
91
-
92
- def unpack_value(data: bytes, type_name: str, offset: int = 0) -> tuple[Any, int]:
93
- """Unpack a single value from bytes.
94
-
95
- Args:
96
- data: Bytes to unpack from
97
- type_name: Type name (e.g., 'uint16', 'float32')
98
- offset: Offset in bytes to start unpacking
99
-
100
- Returns:
101
- Tuple of (unpacked_value, new_offset)
102
-
103
- Raises:
104
- ValueError: If type is unknown or data is too short
105
- struct.error: If data format is invalid
106
- """
107
- if type_name not in _STRUCT_CACHE:
108
- raise ValueError(f"Unknown type: {type_name}")
109
-
110
- size = TYPE_SIZES[type_name]
111
-
112
- if len(data) < offset + size:
113
- raise ValueError(
114
- f"Not enough data to unpack {type_name}: "
115
- f"need {offset + size} bytes, got {len(data)}"
116
- )
117
-
118
- value = _STRUCT_CACHE[type_name].unpack_from(data, offset)[0]
119
- return value, offset + size
120
-
121
-
122
- def pack_array(values: list[Any], element_type: str, count: int) -> bytes:
123
- """Pack an array of values into bytes.
124
-
125
- Args:
126
- values: List of values to pack
127
- element_type: Type of each element (e.g., 'uint8', 'uint16')
128
- count: Expected number of elements
129
-
130
- Returns:
131
- Packed bytes
132
-
133
- Raises:
134
- ValueError: If values length doesn't match count or type is unknown
135
- """
136
- if len(values) != count:
137
- raise ValueError(f"Expected {count} values, got {len(values)}")
138
-
139
- # Optimization: Pack entire primitive array at once with single struct call
140
- if element_type in TYPE_FORMATS:
141
- format_str = f"<{count}{TYPE_FORMATS[element_type]}"
142
- return struct.pack(format_str, *values)
143
-
144
- # Fall back to element-by-element for complex types
145
- result = b""
146
- for value in values:
147
- result += pack_value(value, element_type)
148
- return result
149
-
150
-
151
- def unpack_array(
152
- data: bytes, element_type: str, count: int, offset: int = 0
153
- ) -> tuple[list[Any], int]:
154
- """Unpack an array of values from bytes.
155
-
156
- Args:
157
- data: Bytes to unpack from
158
- element_type: Type of each element
159
- count: Number of elements to unpack
160
- offset: Offset in bytes to start unpacking
161
-
162
- Returns:
163
- Tuple of (list_of_values, new_offset)
164
- """
165
- # Optimization: Unpack entire primitive array at once with single struct call
166
- if element_type in TYPE_FORMATS:
167
- format_str = f"<{count}{TYPE_FORMATS[element_type]}"
168
- size = TYPE_SIZES[element_type] * count
169
-
170
- if len(data) < offset + size:
171
- raise ValueError(
172
- f"Not enough data to unpack array: "
173
- f"need {offset + size} bytes, got {len(data)}"
174
- )
175
-
176
- values = list(struct.unpack_from(format_str, data, offset))
177
- return values, offset + size
178
-
179
- # Fall back to element-by-element for complex types
180
- values = []
181
- current_offset = offset
182
-
183
- for _ in range(count):
184
- value, current_offset = unpack_value(data, element_type, current_offset)
185
- values.append(value)
186
-
187
- return values, current_offset
188
-
189
-
190
- def pack_string(value: str, length: int) -> bytes:
191
- """Pack a string into fixed-length byte array.
192
-
193
- Safely truncates at UTF-8 character boundaries to avoid creating
194
- invalid UTF-8 sequences that could crash device firmware (VUL-002 mitigation).
195
-
196
- Args:
197
- value: String to pack
198
- length: Fixed length in bytes
199
-
200
- Returns:
201
- Packed bytes (null-padded if necessary)
202
- """
203
- encoded = value.encode("utf-8")
204
-
205
- # Safe truncation at character boundary
206
- if len(encoded) > length:
207
- # Decode and re-encode to find safe truncation point
208
- truncated = encoded[:length]
209
- # Find valid UTF-8 boundary by trying to decode
210
- while truncated:
211
- try:
212
- truncated.decode("utf-8")
213
- break
214
- except UnicodeDecodeError:
215
- # Remove last byte and try again
216
- truncated = truncated[:-1]
217
- encoded = truncated
218
-
219
- return encoded.ljust(length, b"\x00")
220
-
221
-
222
- def unpack_string(data: bytes, length: int, offset: int = 0) -> tuple[str, int]:
223
- """Unpack a fixed-length string from bytes.
224
-
225
- Args:
226
- data: Bytes to unpack from
227
- length: Length in bytes to read
228
- offset: Offset in bytes to start unpacking
229
-
230
- Returns:
231
- Tuple of (string, new_offset)
232
- """
233
- if len(data) < offset + length:
234
- raise ValueError(
235
- f"Not enough data to unpack string: "
236
- f"need {offset + length} bytes, got {len(data)}"
237
- )
238
-
239
- raw_bytes = data[offset : offset + length]
240
- # Strip null bytes and decode
241
- string = raw_bytes.rstrip(b"\x00").decode("utf-8", errors="replace")
242
- return string, offset + length
243
-
244
-
245
- def pack_reserved(size: int) -> bytes:
246
- """Pack reserved (zero) bytes.
247
-
248
- Args:
249
- size: Number of bytes
250
-
251
- Returns:
252
- Zero bytes
253
- """
254
- return b"\x00" * size
255
-
256
-
257
- def pack_bytes(data: bytes, length: int) -> bytes:
258
- """Pack bytes into fixed-length byte array.
259
-
260
- Args:
261
- data: Bytes to pack
262
- length: Fixed length in bytes
263
-
264
- Returns:
265
- Packed bytes (null-padded or truncated if necessary)
266
- """
267
- if len(data) >= length:
268
- return data[:length]
269
- return data + b"\x00" * (length - len(data))
270
-
271
-
272
- def unpack_bytes(data: bytes, length: int, offset: int = 0) -> tuple[bytes, int]:
273
- """Unpack fixed-length byte array from bytes.
274
-
275
- Args:
276
- data: Bytes to unpack from
277
- length: Length in bytes to read
278
- offset: Offset in bytes to start unpacking
279
-
280
- Returns:
281
- Tuple of (bytes, new_offset)
282
-
283
- Raises:
284
- ValueError: If data is too short
285
- """
286
- if len(data) < offset + length:
287
- raise ValueError(
288
- f"Not enough data to unpack bytes: "
289
- f"need {offset + length} bytes, got {len(data)}"
290
- )
291
-
292
- raw_bytes = data[offset : offset + length]
293
- return raw_bytes, offset + length
294
-
295
-
296
- class FieldSerializer:
297
- """Serializer for structured fields with nested types."""
298
-
299
- def __init__(self, field_definitions: dict[str, dict[str, str]]):
300
- """Initialize serializer with field definitions.
301
-
302
- Args:
303
- field_definitions: Dict mapping field names to their structure definitions
304
- (e.g., {"HSBK": {"hue": "uint16", "saturation": "uint16", ...}})
305
- """
306
- self.field_definitions = field_definitions
307
-
308
- def pack_field(self, field_data: dict[str, Any], field_name: str) -> bytes:
309
- """Pack a structured field.
310
-
311
- Args:
312
- field_data: Dictionary of field values
313
- field_name: Name of the field structure (e.g., "HSBK")
314
-
315
- Returns:
316
- Packed bytes
317
-
318
- Raises:
319
- ValueError: If field_name is unknown
320
- """
321
- if field_name not in self.field_definitions:
322
- raise ValueError(f"Unknown field: {field_name}")
323
-
324
- field_def = self.field_definitions[field_name]
325
- result = b""
326
-
327
- for attr_name, attr_type in field_def.items():
328
- if attr_name not in field_data:
329
- raise ValueError(f"Missing attribute {attr_name} in {field_name}")
330
- result += pack_value(field_data[attr_name], attr_type)
331
-
332
- return result
333
-
334
- def unpack_field(
335
- self, data: bytes, field_name: str, offset: int = 0
336
- ) -> tuple[dict[str, Any], int]:
337
- """Unpack a structured field.
338
-
339
- Args:
340
- data: Bytes to unpack from
341
- field_name: Name of the field structure
342
- offset: Offset to start unpacking
343
-
344
- Returns:
345
- Tuple of (field_dict, new_offset)
346
-
347
- Raises:
348
- ValueError: If field_name is unknown
349
- """
350
- if field_name not in self.field_definitions:
351
- raise ValueError(f"Unknown field: {field_name}")
352
-
353
- field_def = self.field_definitions[field_name]
354
- field_data: dict[str, Any] = {}
355
- current_offset = offset
356
-
357
- for attr_name, attr_type in field_def.items():
358
- value, current_offset = unpack_value(data, attr_type, current_offset)
359
- field_data[attr_name] = value
360
-
361
- return field_data, current_offset
362
-
363
- def get_field_size(self, field_name: str) -> int:
364
- """Get the size in bytes of a field structure.
365
-
366
- Args:
367
- field_name: Name of the field structure
368
-
369
- Returns:
370
- Size in bytes
371
-
372
- Raises:
373
- ValueError: If field_name is unknown
374
- """
375
- if field_name not in self.field_definitions:
376
- raise ValueError(f"Unknown field: {field_name}")
377
-
378
- field_def = self.field_definitions[field_name]
379
- return sum(TYPE_SIZES[attr_type] for attr_type in field_def.values())
@@ -1,22 +0,0 @@
1
- """Repository interfaces and implementations for LIFX emulator.
2
-
3
- This module defines repository abstractions following the Repository Pattern
4
- and Dependency Inversion Principle. Repositories encapsulate data access logic
5
- and provide a clean separation between domain logic and data persistence.
6
- """
7
-
8
- from lifx_emulator.repositories.device_repository import (
9
- DeviceRepository,
10
- IDeviceRepository,
11
- )
12
- from lifx_emulator.repositories.storage_backend import (
13
- IDeviceStorageBackend,
14
- IScenarioStorageBackend,
15
- )
16
-
17
- __all__ = [
18
- "IDeviceRepository",
19
- "DeviceRepository",
20
- "IDeviceStorageBackend",
21
- "IScenarioStorageBackend",
22
- ]
@@ -1,155 +0,0 @@
1
- """Device repository interface and implementation.
2
-
3
- Provides abstraction for device storage and retrieval operations,
4
- following the Repository Pattern and Dependency Inversion Principle.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from typing import Protocol, runtime_checkable
10
-
11
- from lifx_emulator.devices import EmulatedLifxDevice
12
-
13
-
14
- @runtime_checkable
15
- class IDeviceRepository(Protocol):
16
- """Interface for device repository operations.
17
-
18
- This protocol defines the contract for managing device storage and retrieval.
19
- Concrete implementations can use in-memory storage, databases, or other backends.
20
- """
21
-
22
- def add(self, device: EmulatedLifxDevice) -> bool:
23
- """Add a device to the repository.
24
-
25
- Args:
26
- device: Device to add
27
-
28
- Returns:
29
- True if device was added, False if device with same serial already exists
30
- """
31
- ...
32
-
33
- def remove(self, serial: str) -> bool:
34
- """Remove a device from the repository.
35
-
36
- Args:
37
- serial: Serial number of device to remove
38
-
39
- Returns:
40
- True if device was removed, False if not found
41
- """
42
- ...
43
-
44
- def get(self, serial: str) -> EmulatedLifxDevice | None:
45
- """Get a device by serial number.
46
-
47
- Args:
48
- serial: Serial number to look up
49
-
50
- Returns:
51
- Device if found, None otherwise
52
- """
53
- ...
54
-
55
- def get_all(self) -> list[EmulatedLifxDevice]:
56
- """Get all devices.
57
-
58
- Returns:
59
- List of all devices in the repository
60
- """
61
- ...
62
-
63
- def clear(self) -> int:
64
- """Remove all devices from the repository.
65
-
66
- Returns:
67
- Number of devices removed
68
- """
69
- ...
70
-
71
- def count(self) -> int:
72
- """Get the number of devices in the repository.
73
-
74
- Returns:
75
- Number of devices
76
- """
77
- ...
78
-
79
-
80
- class DeviceRepository:
81
- """In-memory device repository implementation.
82
-
83
- Stores devices in a dictionary keyed by serial number.
84
- This is the default implementation used by EmulatedLifxServer.
85
- """
86
-
87
- def __init__(self) -> None:
88
- """Initialize empty device repository."""
89
- self._devices: dict[str, EmulatedLifxDevice] = {}
90
-
91
- def add(self, device: EmulatedLifxDevice) -> bool:
92
- """Add a device to the repository.
93
-
94
- Args:
95
- device: Device to add
96
-
97
- Returns:
98
- True if device was added, False if device with same serial already exists
99
- """
100
- serial = device.state.serial
101
- if serial in self._devices:
102
- return False
103
- self._devices[serial] = device
104
- return True
105
-
106
- def remove(self, serial: str) -> bool:
107
- """Remove a device from the repository.
108
-
109
- Args:
110
- serial: Serial number of device to remove
111
-
112
- Returns:
113
- True if device was removed, False if not found
114
- """
115
- if serial in self._devices:
116
- del self._devices[serial]
117
- return True
118
- return False
119
-
120
- def get(self, serial: str) -> EmulatedLifxDevice | None:
121
- """Get a device by serial number.
122
-
123
- Args:
124
- serial: Serial number to look up
125
-
126
- Returns:
127
- Device if found, None otherwise
128
- """
129
- return self._devices.get(serial)
130
-
131
- def get_all(self) -> list[EmulatedLifxDevice]:
132
- """Get all devices.
133
-
134
- Returns:
135
- List of all devices in the repository
136
- """
137
- return list(self._devices.values())
138
-
139
- def clear(self) -> int:
140
- """Remove all devices from the repository.
141
-
142
- Returns:
143
- Number of devices removed
144
- """
145
- count = len(self._devices)
146
- self._devices.clear()
147
- return count
148
-
149
- def count(self) -> int:
150
- """Get the number of devices in the repository.
151
-
152
- Returns:
153
- Number of devices
154
- """
155
- return len(self._devices)
@@ -1,107 +0,0 @@
1
- """Storage backend interfaces for device and scenario persistence.
2
-
3
- Provides abstraction for persistent storage operations,
4
- following the Repository Pattern and Dependency Inversion Principle.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable
10
-
11
- if TYPE_CHECKING:
12
- from lifx_emulator.scenarios import HierarchicalScenarioManager
13
-
14
-
15
- @runtime_checkable
16
- class IDeviceStorageBackend(Protocol):
17
- """Interface for device state persistence operations.
18
-
19
- This protocol defines the contract for loading and saving device state.
20
- Concrete implementations can use async file I/O, databases,
21
- or other storage backends.
22
- """
23
-
24
- async def save_device_state(self, device_state: Any) -> None:
25
- """Save device state to persistent storage (async).
26
-
27
- Args:
28
- device_state: DeviceState instance to persist
29
- """
30
- raise NotImplementedError
31
-
32
- def load_device_state(self, serial: str) -> dict | None:
33
- """Load device state from persistent storage (sync).
34
-
35
- Args:
36
- serial: Device serial number (12-character hex string)
37
-
38
- Returns:
39
- Dictionary with device state data, or None if not found
40
- """
41
- raise NotImplementedError
42
-
43
- def delete_device_state(self, serial: str) -> bool:
44
- """Delete device state from persistent storage.
45
-
46
- Args:
47
- serial: Device serial number
48
-
49
- Returns:
50
- True if state was deleted, False if not found
51
- """
52
- raise NotImplementedError
53
-
54
- def list_devices(self) -> list[str]:
55
- """List all device serials with saved state.
56
-
57
- Returns:
58
- List of serial numbers
59
- """
60
- raise NotImplementedError
61
-
62
- def delete_all_device_states(self) -> int:
63
- """Delete all device states from persistent storage.
64
-
65
- Returns:
66
- Number of device states deleted
67
- """
68
- raise NotImplementedError
69
-
70
- async def shutdown(self) -> None:
71
- """Gracefully shutdown storage backend, flushing pending writes."""
72
- raise NotImplementedError
73
-
74
-
75
- @runtime_checkable
76
- class IScenarioStorageBackend(Protocol):
77
- """Interface for scenario configuration persistence operations.
78
-
79
- This protocol defines the contract for loading and saving scenario configurations.
80
- Concrete implementations can use async file I/O, databases,
81
- or other storage backends.
82
- """
83
-
84
- async def load(self) -> HierarchicalScenarioManager:
85
- """Load scenario configuration from persistent storage (async).
86
-
87
- Returns:
88
- Scenario manager with loaded configuration, or default manager
89
- if no saved data
90
- """
91
- raise NotImplementedError
92
-
93
- async def save(self, manager: HierarchicalScenarioManager) -> None:
94
- """Save scenario configuration to persistent storage (async).
95
-
96
- Args:
97
- manager: Scenario manager whose configuration should be saved
98
- """
99
- raise NotImplementedError
100
-
101
- async def delete(self) -> bool:
102
- """Delete scenario configuration from persistent storage (async).
103
-
104
- Returns:
105
- True if configuration was deleted, False if it didn't exist
106
- """
107
- raise NotImplementedError
@@ -1,22 +0,0 @@
1
- """Scenario management module for LIFX emulator.
2
-
3
- This module contains all scenario-related functionality including:
4
- - Scenario manager (HierarchicalScenarioManager)
5
- - Scenario models (ScenarioConfig)
6
- - Scenario persistence (async file storage)
7
- - Device type classification (get_device_type)
8
- """
9
-
10
- from lifx_emulator.scenarios.manager import (
11
- HierarchicalScenarioManager,
12
- get_device_type,
13
- )
14
- from lifx_emulator.scenarios.models import ScenarioConfig
15
- from lifx_emulator.scenarios.persistence import ScenarioPersistenceAsyncFile
16
-
17
- __all__ = [
18
- "HierarchicalScenarioManager",
19
- "ScenarioConfig",
20
- "ScenarioPersistenceAsyncFile",
21
- "get_device_type",
22
- ]