Skip to content
Draft

2.0 #1872

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "uv sync && uv run pre-commit install",
"postCreateCommand": "uv sync --all-extras --dev && uv run pre-commit install",
// Configure tool-specific properties.
"customizations": {
"vscode": {
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v6
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
python-version: ["3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v6
Expand Down
16 changes: 15 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ import time

from pyoverkiz.const import SUPPORTED_SERVERS
from pyoverkiz.client import OverkizClient
from pyoverkiz.enums import Server
from pyoverkiz.models import Action
from pyoverkiz.enums import Server, OverkizCommand

USERNAME = ""
PASSWORD = ""
Expand All @@ -61,6 +62,19 @@ async def main() -> None:
print(f"{device.label} ({device.id}) - {device.controllable_name}")
print(f"{device.widget} - {device.ui_class}")

await client.execute_action_group(
actions=[
Action(
device_url="io://1234-5678-1234/12345678",
commands=[
Command(name=OverkizCommand.SET_CLOSURE, parameters=[100])
]
)
],
label="Execution via Python",
# mode=CommandMode.HIGH_PRIORITY
)

while True:
events = await client.fetch_events()
print(events)
Expand Down
63 changes: 34 additions & 29 deletions pyoverkiz/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
SOMFY_CLIENT_SECRET,
SUPPORTED_SERVERS,
)
from pyoverkiz.enums import APIType, Server
from pyoverkiz.enums import APIType, CommandMode, Server
from pyoverkiz.exceptions import (
AccessDeniedToGatewayException,
BadCredentialsException,
Expand Down Expand Up @@ -71,7 +71,8 @@
UnknownUserException,
)
from pyoverkiz.models import (
Command,
Action,
ActionGroup,
Device,
Event,
Execution,
Expand All @@ -82,11 +83,11 @@
OptionParameter,
OverkizServer,
Place,
Scenario,
Setup,
State,
)
from pyoverkiz.obfuscate import obfuscate_sensitive_data
from pyoverkiz.serializers import prepare_payload
from pyoverkiz.types import JSON


Expand Down Expand Up @@ -630,45 +631,49 @@ async def get_api_version(self) -> str:

@retry_on_too_many_executions
@retry_on_auth_error
async def execute_command(
async def execute_action_group(
self,
device_url: str,
command: Command | str,
actions: list[Action],
mode: CommandMode | None = None,
label: str | None = "python-overkiz-api",
) -> str:
"""Send a command."""
if isinstance(command, str):
command = Command(command)
"""Execute a non-persistent action group.

response: str = await self.execute_commands(device_url, [command], label)
The executed action group does not have to be persisted on the server before use.
Per-session rate-limit : 1 calls per 28min 48s period for all operations of the same category (exec)
"""
# Build a logical (snake_case) payload using model helpers and convert it
# to the exact JSON schema expected by the API (camelCase + small fixes).
payload = {"label": label, "actions": [a.to_payload() for a in actions]}

# Prepare final payload with camelCase keys and special abbreviation handling
final_payload = prepare_payload(payload)

if mode == CommandMode.GEOLOCATED:
url = "exec/apply/geolocated"
elif mode == CommandMode.INTERNAL:
url = "exec/apply/internal"
elif mode == CommandMode.HIGH_PRIORITY:
url = "exec/apply/highPriority"
else:
url = "exec/apply"

return response
response: dict = await self.__post(url, final_payload)

return cast(str, response["execId"])

@retry_on_auth_error
async def cancel_command(self, exec_id: str) -> None:
"""Cancel a running setup-level execution."""
await self.__delete(f"/exec/current/setup/{exec_id}")

@retry_on_auth_error
async def execute_commands(
self,
device_url: str,
commands: list[Command],
label: str | None = "python-overkiz-api",
) -> str:
"""Send several commands in one call."""
payload = {
"label": label,
"actions": [{"deviceURL": device_url, "commands": commands}],
}
response: dict = await self.__post("exec/apply", payload)
return cast(str, response["execId"])

@retry_on_auth_error
async def get_scenarios(self) -> list[Scenario]:
"""List the scenarios."""
async def get_action_groups(self) -> list[ActionGroup]:
"""List the action groups (scenarios)."""
response = await self.__get("actionGroups")
return [Scenario(**scenario) for scenario in humps.decamelize(response)]
return [
ActionGroup(**action_group) for action_group in humps.decamelize(response)
]

@retry_on_auth_error
async def get_places(self) -> Place:
Expand Down
10 changes: 1 addition & 9 deletions pyoverkiz/enums/command.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
"""Command-related enums and parameters used by device commands."""

import sys
from enum import unique

# Since we support Python versions lower than 3.11, we use
# a backport for StrEnum when needed.
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum # type: ignore[import]
from enum import StrEnum, unique


@unique
Expand Down
10 changes: 1 addition & 9 deletions pyoverkiz/enums/execution.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
"""Execution related enums (types, states and subtypes)."""

import logging
import sys
from enum import unique
from enum import StrEnum, unique

_LOGGER = logging.getLogger(__name__)

# Since we support Python versions lower than 3.11, we use
# a backport for StrEnum when needed.
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum # type: ignore[import]


@unique
class ExecutionType(StrEnum):
Expand Down
10 changes: 1 addition & 9 deletions pyoverkiz/enums/gateway.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
"""Enums for gateway types and related helpers."""

import logging
import sys
from enum import IntEnum, unique
from enum import IntEnum, StrEnum, unique

_LOGGER = logging.getLogger(__name__)

# Since we support Python versions lower than 3.11, we use
# a backport for StrEnum when needed.
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum # type: ignore[import]


@unique
class GatewayType(IntEnum):
Expand Down
10 changes: 1 addition & 9 deletions pyoverkiz/enums/general.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
"""General-purpose enums like product types, data types and event names."""

import logging
import sys
from enum import IntEnum, unique
from enum import IntEnum, StrEnum, unique

_LOGGER = logging.getLogger(__name__)

# Since we support Python versions lower than 3.11, we use
# a backport for StrEnum when needed.
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum # type: ignore[import]


@unique
class ProductType(IntEnum):
Expand Down
10 changes: 1 addition & 9 deletions pyoverkiz/enums/measured_value_type.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
"""Measured value type enums used to interpret numeric sensor data."""

import sys
from enum import unique

# Since we support Python versions lower than 3.11, we use
# a backport for StrEnum when needed.
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum # type: ignore[import]
from enum import StrEnum, unique


@unique
Expand Down
10 changes: 1 addition & 9 deletions pyoverkiz/enums/protocol.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
"""Protocol enums describe device URL schemes used by Overkiz."""

import logging
import sys
from enum import unique
from enum import StrEnum, unique

_LOGGER = logging.getLogger(__name__)

# Since we support Python versions lower than 3.11, we use
# a backport for StrEnum when needed.
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum # type: ignore[import]


@unique
class Protocol(StrEnum):
Expand Down
10 changes: 1 addition & 9 deletions pyoverkiz/enums/server.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
"""Server and API type enums used to select target Overkiz endpoints."""

import sys
from enum import unique

# Since we support Python versions lower than 3.11, we use
# a backport for StrEnum when needed.
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum # type: ignore[import]
from enum import StrEnum, unique


@unique
Expand Down
10 changes: 1 addition & 9 deletions pyoverkiz/enums/state.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
"""State and attribute enums describing Overkiz device states and attributes."""

import sys
from enum import unique

# Since we support Python versions lower than 3.11, we use
# a backport for StrEnum when needed.
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum # type: ignore[import]
from enum import StrEnum, unique


@unique
Expand Down
10 changes: 1 addition & 9 deletions pyoverkiz/enums/ui.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,10 @@
"""UI enums for classes and widgets used to interpret device UI metadata."""

import logging
import sys
from enum import unique
from enum import StrEnum, unique

_LOGGER = logging.getLogger(__name__)

# Since we support Python versions lower than 3.11, we use
# a backport for StrEnum when needed.
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
from backports.strenum import StrEnum # type: ignore[import]


@unique
class UIClass(StrEnum):
Expand Down
Loading