Skip to content

Commit 8d940a6

Browse files
committed
Enable ruff TC rules and move type-only imports to TYPE_CHECKING blocks
Move imports only used for type annotations into TYPE_CHECKING blocks and quote type expressions in cast() calls. Reduces runtime import overhead without changing behavior.
1 parent efa3be4 commit 8d940a6

File tree

13 files changed

+82
-55
lines changed

13 files changed

+82
-55
lines changed

pyoverkiz/_case.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44

55
import functools
66
import re
7-
from collections.abc import Callable
8-
from typing import Any
7+
from typing import TYPE_CHECKING, Any
8+
9+
if TYPE_CHECKING:
10+
from collections.abc import Callable
911

1012
_CAMEL_RE = re.compile(r"([A-Z]+)([A-Z][a-z])|([a-z\d])([A-Z])")
1113

pyoverkiz/action_queue.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44

55
import asyncio
66
import contextlib
7-
from collections.abc import Callable, Coroutine, Generator
87
from dataclasses import dataclass
98
from typing import TYPE_CHECKING, Any
109

1110
from pyoverkiz.models import Action
1211

1312
if TYPE_CHECKING:
13+
from collections.abc import Callable, Coroutine, Generator
14+
1415
from pyoverkiz.enums import ExecutionMode
1516

1617

pyoverkiz/auth/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33
from __future__ import annotations
44

55
import datetime
6-
from collections.abc import Mapping
76
from dataclasses import dataclass
8-
from typing import Any, Protocol
7+
from typing import TYPE_CHECKING, Any, Protocol
8+
9+
if TYPE_CHECKING:
10+
from collections.abc import Mapping
911

1012

1113
@dataclass(slots=True)

pyoverkiz/auth/factory.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
from __future__ import annotations
44

5-
import ssl
6-
7-
from aiohttp import ClientSession
5+
from typing import TYPE_CHECKING
86

97
from pyoverkiz.auth.credentials import (
108
Credentials,
@@ -24,7 +22,13 @@
2422
SomfyAuthStrategy,
2523
)
2624
from pyoverkiz.enums import APIType, Server
27-
from pyoverkiz.models import ServerConfig
25+
26+
if TYPE_CHECKING:
27+
import ssl
28+
29+
from aiohttp import ClientSession
30+
31+
from pyoverkiz.models import ServerConfig
2832

2933

3034
def build_auth_strategy(

pyoverkiz/auth/strategies.py

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,25 @@
66
import base64
77
import binascii
88
import json
9-
import ssl
10-
from collections.abc import Mapping
119
from typing import TYPE_CHECKING, Any, cast
1210

1311
if TYPE_CHECKING:
12+
import ssl
13+
from collections.abc import Mapping
14+
1415
from botocore.client import BaseClient
1516

17+
from pyoverkiz.auth.credentials import (
18+
LocalTokenCredentials,
19+
RexelOAuthCodeCredentials,
20+
TokenCredentials,
21+
UsernamePasswordCredentials,
22+
)
23+
from pyoverkiz.models import ServerConfig
24+
1625
from aiohttp import ClientSession, FormData
1726

1827
from pyoverkiz.auth.base import AuthContext, AuthStrategy
19-
from pyoverkiz.auth.credentials import (
20-
LocalTokenCredentials,
21-
RexelOAuthCodeCredentials,
22-
TokenCredentials,
23-
UsernamePasswordCredentials,
24-
)
2528
from pyoverkiz.const import (
2629
COZYTOUCH_ATLANTIC_API,
2730
COZYTOUCH_CLIENT_ID,
@@ -47,7 +50,6 @@
4750
SomfyBadCredentialsError,
4851
SomfyServiceError,
4952
)
50-
from pyoverkiz.models import ServerConfig
5153

5254

5355
class BaseAuthStrategy(AuthStrategy):
@@ -156,7 +158,7 @@ async def refresh_if_needed(self) -> bool:
156158

157159
await self._request_access_token(
158160
grant_type="refresh_token",
159-
extra_fields={"refresh_token": cast(str, self.context.refresh_token)},
161+
extra_fields={"refresh_token": cast("str", self.context.refresh_token)},
160162
)
161163
return True
162164

@@ -348,7 +350,7 @@ async def refresh_if_needed(self) -> bool:
348350
"grant_type": "refresh_token",
349351
"client_id": REXEL_OAUTH_CLIENT_ID,
350352
"scope": REXEL_OAUTH_SCOPE,
351-
"refresh_token": cast(str, self.context.refresh_token),
353+
"refresh_token": cast("str", self.context.refresh_token),
352354
}
353355
)
354356
return True
@@ -426,6 +428,6 @@ def _decode_jwt_payload(token: str) -> dict[str, Any]:
426428
padding = "=" * (-len(payload_segment) % 4)
427429
try:
428430
decoded = base64.urlsafe_b64decode(payload_segment + padding)
429-
return cast(dict[str, Any], json.loads(decoded))
431+
return cast("dict[str, Any]", json.loads(decoded))
430432
except (binascii.Error, json.JSONDecodeError) as error:
431433
raise InvalidTokenError("Malformed JWT received.") from error

pyoverkiz/client.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
import ssl
77
import urllib.parse
88
from pathlib import Path
9-
from types import TracebackType
10-
from typing import Any, cast
9+
from typing import TYPE_CHECKING, Any, cast
1110

1211
import backoff
1312
from aiohttp import (
@@ -16,7 +15,6 @@
1615
ClientSession,
1716
ServerDisconnectedError,
1817
)
19-
from backoff.types import Details
2018

2119
from pyoverkiz._case import decamelize
2220
from pyoverkiz.action_queue import ActionQueue, ActionQueueSettings
@@ -52,14 +50,20 @@
5250
from pyoverkiz.obfuscate import obfuscate_sensitive_data
5351
from pyoverkiz.response_handler import check_response
5452
from pyoverkiz.serializers import prepare_payload
55-
from pyoverkiz.types import JSON
53+
54+
if TYPE_CHECKING:
55+
from types import TracebackType
56+
57+
from backoff.types import Details
58+
59+
from pyoverkiz.types import JSON
5660

5761
_LOGGER = logging.getLogger(__name__)
5862

5963

6064
def _get_client_from_invocation(invocation: Details) -> OverkizClient:
6165
"""Return the `OverkizClient` instance from a backoff invocation."""
62-
return cast(OverkizClient, invocation["args"][0])
66+
return cast("OverkizClient", invocation["args"][0])
6367

6468

6569
async def relogin(invocation: Details) -> None:
@@ -418,7 +422,7 @@ async def register_event_listener(self) -> str:
418422
API on a regular basis.
419423
"""
420424
response = await self._post("events/register")
421-
listener_id = cast(str, response.get("id"))
425+
listener_id = cast("str", response.get("id"))
422426
self.event_listener_id = listener_id
423427

424428
return listener_id
@@ -464,7 +468,7 @@ async def get_api_version(self) -> str:
464468
"""Get the API version (local only)."""
465469
response = await self._get("apiVersion")
466470

467-
return cast(str, response["protocolVersion"])
471+
return cast("str", response["protocolVersion"])
468472

469473
@retry_on_too_many_executions
470474
@retry_on_auth_error
@@ -484,7 +488,7 @@ async def _execute_action_group_direct(
484488

485489
response: dict = await self._post(url, prepare_payload(payload))
486490

487-
return cast(str, response["execId"])
491+
return cast("str", response["execId"])
488492

489493
async def execute_action_group(
490494
self,
@@ -571,13 +575,13 @@ async def get_places(self) -> Place:
571575
async def execute_persisted_action_group(self, oid: str) -> str:
572576
"""Execute a server-side action group by its OID (see ``get_action_groups``)."""
573577
response = await self._post(f"exec/{oid}")
574-
return cast(str, response["execId"])
578+
return cast("str", response["execId"])
575579

576580
@retry_on_auth_error
577581
async def schedule_persisted_action_group(self, oid: str, timestamp: int) -> str:
578582
"""Schedule a server-side action group for execution at the given timestamp."""
579583
response = await self._post(f"exec/schedule/{oid}/{timestamp}")
580-
return cast(str, response["triggerId"])
584+
return cast("str", response["triggerId"])
581585

582586
@retry_on_auth_error
583587
async def get_setup_options(self) -> list[Option]:

pyoverkiz/enums/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,4 @@ def _missing_(cls, value: object) -> Self: # type: ignore[override]
3636
message = cls.__missing_message__
3737
logging.getLogger(cls.__module__).warning(message, value, cls)
3838
# Type checker cannot infer UNKNOWN exists on Self, but all subclasses define it
39-
return cast(Self, cls.UNKNOWN) # type: ignore[attr-defined]
39+
return cast("Self", cls.UNKNOWN) # type: ignore[attr-defined]

pyoverkiz/models.py

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import json
66
import logging
77
import re
8-
from collections.abc import Iterator
9-
from typing import Any, cast
8+
from typing import TYPE_CHECKING, Any, cast
109

1110
from attr import define, field
1211

@@ -24,12 +23,16 @@
2423
UIWidget,
2524
UpdateBoxStatus,
2625
)
27-
from pyoverkiz.enums.command import OverkizCommand, OverkizCommandParam
2826
from pyoverkiz.enums.protocol import Protocol
2927
from pyoverkiz.enums.server import APIType, Server
3028
from pyoverkiz.obfuscate import obfuscate_email, obfuscate_id, obfuscate_string
3129
from pyoverkiz.types import DATA_TYPE_TO_PYTHON, StateType
3230

31+
if TYPE_CHECKING:
32+
from collections.abc import Iterator
33+
34+
from pyoverkiz.enums.command import OverkizCommand, OverkizCommandParam
35+
3336
# pylint: disable=unused-argument, too-many-instance-attributes, too-many-locals
3437

3538
# <protocol>://<gatewayId>/<deviceAddress>[#<subsystemId>]
@@ -527,7 +530,7 @@ def value_as_int(self) -> int | None:
527530
if self.type == DataType.NONE:
528531
return None
529532
if self.type == DataType.INTEGER:
530-
return cast(int, self.value)
533+
return cast("int", self.value)
531534
raise TypeError(f"{self.name} is not an integer")
532535

533536
@property
@@ -536,9 +539,9 @@ def value_as_float(self) -> float | None:
536539
if self.type == DataType.NONE:
537540
return None
538541
if self.type == DataType.FLOAT:
539-
return cast(float, self.value)
542+
return cast("float", self.value)
540543
if self.type == DataType.INTEGER:
541-
return float(cast(int, self.value))
544+
return float(cast("int", self.value))
542545
raise TypeError(f"{self.name} is not a float")
543546

544547
@property
@@ -547,7 +550,7 @@ def value_as_bool(self) -> bool | None:
547550
if self.type == DataType.NONE:
548551
return None
549552
if self.type == DataType.BOOLEAN:
550-
return cast(bool, self.value)
553+
return cast("bool", self.value)
551554
raise TypeError(f"{self.name} is not a boolean")
552555

553556
@property
@@ -556,7 +559,7 @@ def value_as_str(self) -> str | None:
556559
if self.type == DataType.NONE:
557560
return None
558561
if self.type == DataType.STRING:
559-
return cast(str, self.value)
562+
return cast("str", self.value)
560563
raise TypeError(f"{self.name} is not a string")
561564

562565
@property
@@ -565,7 +568,7 @@ def value_as_dict(self) -> dict[str, Any] | None:
565568
if self.type == DataType.NONE:
566569
return None
567570
if self.type == DataType.JSON_OBJECT:
568-
return cast(dict, self.value)
571+
return cast("dict", self.value)
569572
raise TypeError(f"{self.name} is not a JSON object")
570573

571574
@property
@@ -574,7 +577,7 @@ def value_as_list(self) -> list[Any] | None:
574577
if self.type == DataType.NONE:
575578
return None
576579
if self.type == DataType.JSON_ARRAY:
577-
return cast(list, self.value)
580+
return cast("list", self.value)
578581
raise TypeError(f"{self.name} is not an array")
579582

580583

@@ -891,7 +894,7 @@ def __init__(
891894
if oid is None and id is None:
892895
raise ValueError("Either 'oid' or 'id' must be provided")
893896

894-
self.id = cast(str, oid or id)
897+
self.id = cast("str", oid or id)
895898
self.creation_time = creation_time
896899
self.last_update_time = last_update_time
897900
self.label = (
@@ -904,7 +907,7 @@ def __init__(
904907
self.notification_text = notification_text
905908
self.notification_title = notification_title
906909
self.actions = [Action(**action) for action in actions]
907-
self.oid = cast(str, oid or id)
910+
self.oid = cast("str", oid or id)
908911

909912

910913
@define(init=False, kw_only=True)

pyoverkiz/obfuscate.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
from __future__ import annotations
44

55
import re
6-
from typing import Any
6+
from typing import TYPE_CHECKING, Any
77

8-
from pyoverkiz.types import JSON
8+
if TYPE_CHECKING:
9+
from pyoverkiz.types import JSON
910

1011

1112
def obfuscate_id(id: str | None) -> str:

pyoverkiz/response_handler.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
from __future__ import annotations
44

55
from json import JSONDecodeError
6-
7-
from aiohttp import ClientResponse
6+
from typing import TYPE_CHECKING
87

98
from pyoverkiz.exceptions import (
109
AccessDeniedToGatewayError,
@@ -36,6 +35,9 @@
3635
UnknownUserError,
3736
)
3837

38+
if TYPE_CHECKING:
39+
from aiohttp import ClientResponse
40+
3941
# Primary dispatch: (errorCode, message_substring) -> error class.
4042
# Checked in order; first match wins. Use errorCode as the primary key to
4143
# reduce brittleness across cloud vs. local API variants.

0 commit comments

Comments
 (0)