|
| 1 | +# Action queue |
| 2 | + |
| 3 | +The action queue automatically groups rapid, consecutive calls to `execute_action_group()` into a single ActionGroup execution. This minimizes the number of API calls and helps prevent rate limiting issues, such as `TooManyRequestsException`, `TooManyConcurrentRequestsException`, `TooManyExecutionsException`, or `ExecutionQueueFullException` which can occur if actions are sent individually in quick succession. |
| 4 | + |
| 5 | +Important limitation: |
| 6 | +- Gateways only allow a single action per device in each action group. The queue |
| 7 | + merges commands for the same `device_url` into a single action to keep the |
| 8 | + batch valid and preserve command order for that device. |
| 9 | +- If you pass multiple actions for the same `device_url` in a single |
| 10 | + `execute_action_group()` call, the queue will merge them for you. |
| 11 | + |
| 12 | +## Enable with defaults |
| 13 | + |
| 14 | +Set `action_queue=True` to enable batching with default settings: |
| 15 | + |
| 16 | +```python |
| 17 | +import asyncio |
| 18 | + |
| 19 | +from pyoverkiz.auth import UsernamePasswordCredentials |
| 20 | +from pyoverkiz.client import OverkizClient |
| 21 | +from pyoverkiz.enums import OverkizCommand, Server |
| 22 | +from pyoverkiz.models import Action, Command |
| 23 | + |
| 24 | +client = OverkizClient( |
| 25 | + server=Server.SOMFY_EUROPE, |
| 26 | + credentials=UsernamePasswordCredentials("user@example.com", "password"), |
| 27 | + action_queue=True, # uses defaults |
| 28 | +) |
| 29 | + |
| 30 | +action1 = Action( |
| 31 | + device_url="io://1234-5678-1234/12345678", |
| 32 | + commands=[Command(name=OverkizCommand.CLOSE)], |
| 33 | +) |
| 34 | +action2 = Action( |
| 35 | + device_url="io://1234-5678-1234/87654321", |
| 36 | + commands=[Command(name=OverkizCommand.OPEN)], |
| 37 | +) |
| 38 | + |
| 39 | +task1 = asyncio.create_task(client.execute_action_group([action1])) |
| 40 | +task2 = asyncio.create_task(client.execute_action_group([action2])) |
| 41 | +exec_id1, exec_id2 = await asyncio.gather(task1, task2) |
| 42 | + |
| 43 | +print(exec_id1 == exec_id2) |
| 44 | +``` |
| 45 | + |
| 46 | +Defaults: |
| 47 | +- `delay=0.5` |
| 48 | +- `max_actions=20` |
| 49 | + |
| 50 | +## Advanced settings |
| 51 | + |
| 52 | +If you need to tune batching behavior, pass `ActionQueueSettings`: |
| 53 | + |
| 54 | +```python |
| 55 | +import asyncio |
| 56 | + |
| 57 | +from pyoverkiz.action_queue import ActionQueueSettings |
| 58 | +from pyoverkiz.client import OverkizClient |
| 59 | +from pyoverkiz.auth import UsernamePasswordCredentials |
| 60 | +from pyoverkiz.enums import OverkizCommand, Server |
| 61 | +from pyoverkiz.models import Action, Command |
| 62 | + |
| 63 | +client = OverkizClient( |
| 64 | + server=Server.SOMFY_EUROPE, |
| 65 | + credentials=UsernamePasswordCredentials("user@example.com", "password"), |
| 66 | + action_queue=ActionQueueSettings( |
| 67 | + delay=0.5, # seconds to wait before auto-flush |
| 68 | + max_actions=20, # auto-flush when this count is reached |
| 69 | + ), |
| 70 | +) |
| 71 | +``` |
| 72 | + |
| 73 | +## `flush_action_queue()` (force immediate execution) |
| 74 | + |
| 75 | +Normally, queued actions are sent after the delay window or when `max_actions` is reached. Call `flush_action_queue()` to force the queue to execute immediately, which is useful when you want to send any pending actions without waiting for the delay timer to expire. |
| 76 | + |
| 77 | +```python |
| 78 | +from pyoverkiz.action_queue import ActionQueueSettings |
| 79 | +import asyncio |
| 80 | + |
| 81 | +from pyoverkiz.client import OverkizClient |
| 82 | +from pyoverkiz.auth import UsernamePasswordCredentials |
| 83 | +from pyoverkiz.enums import OverkizCommand, Server |
| 84 | +from pyoverkiz.models import Action, Command |
| 85 | + |
| 86 | +client = OverkizClient( |
| 87 | + server=Server.SOMFY_EUROPE, |
| 88 | + credentials=UsernamePasswordCredentials("user@example.com", "password"), |
| 89 | + action_queue=ActionQueueSettings(delay=10.0), # long delay |
| 90 | +) |
| 91 | + |
| 92 | +action = Action( |
| 93 | + device_url="io://1234-5678-1234/12345678", |
| 94 | + commands=[Command(name=OverkizCommand.CLOSE)], |
| 95 | +) |
| 96 | + |
| 97 | +exec_task = asyncio.create_task(client.execute_action_group([action])) |
| 98 | + |
| 99 | +# Give it time to enter the queue |
| 100 | +await asyncio.sleep(0.05) |
| 101 | + |
| 102 | +# Force immediate execution instead of waiting 10 seconds |
| 103 | +await client.flush_action_queue() |
| 104 | + |
| 105 | +exec_id = await exec_task |
| 106 | +print(exec_id) |
| 107 | +``` |
| 108 | + |
| 109 | +Why this matters: |
| 110 | +- It lets you keep a long delay for batching, but still force a quick execution when a user interaction demands it. |
| 111 | +- Useful before shutdown to avoid leaving actions waiting in the queue. |
| 112 | + |
| 113 | +## `get_pending_actions_count()` (best-effort count) |
| 114 | + |
| 115 | +`get_pending_actions_count()` returns a snapshot of how many actions are currently queued. Because the queue can change concurrently (and the method does not acquire the queue lock), the value is approximate. Use it for logging, diagnostics, or UI hints—not for critical control flow. |
| 116 | + |
| 117 | +```python |
| 118 | +from pyoverkiz.client import OverkizClient |
| 119 | +from pyoverkiz.auth import UsernamePasswordCredentials |
| 120 | +from pyoverkiz.enums import OverkizCommand, Server |
| 121 | +from pyoverkiz.models import Action, Command |
| 122 | + |
| 123 | +client = OverkizClient( |
| 124 | + server=Server.SOMFY_EUROPE, |
| 125 | + credentials=UsernamePasswordCredentials("user@example.com", "password"), |
| 126 | + action_queue=True, |
| 127 | +) |
| 128 | + |
| 129 | +action = Action( |
| 130 | + device_url="io://1234-5678-1234/12345678", |
| 131 | + commands=[Command(name=OverkizCommand.CLOSE)], |
| 132 | +) |
| 133 | + |
| 134 | +exec_task = asyncio.create_task(client.execute_action_group([action])) |
| 135 | +await asyncio.sleep(0.01) |
| 136 | + |
| 137 | +pending = client.get_pending_actions_count() |
| 138 | +print(f"Pending actions (approx): {pending}") |
| 139 | + |
| 140 | +exec_id = await exec_task |
| 141 | +print(exec_id) |
| 142 | +``` |
| 143 | + |
| 144 | +Why it’s best-effort: |
| 145 | +- Actions may flush automatically while you read the count. |
| 146 | +- New actions may be added concurrently by other tasks. |
| 147 | +- The count can be briefly stale, so avoid using it to decide whether you must flush or not. |
0 commit comments