Skip to content

Commit 33d0ee5

Browse files
committed
Enable ruff TC rules and move type-only imports to TYPE_CHECKING blocks
- Move type-only imports behind TYPE_CHECKING guards across 13 files - Ignore TC006 (quoting cast types hurts IDE autocomplete) - Reduces import-time overhead by deferring annotation-only imports
1 parent b034971 commit 33d0ee5

File tree

13 files changed

+83
-55
lines changed

13 files changed

+83
-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,23 +6,26 @@
66
import base64
77
import binascii
88
import json
9-
import ssl
10-
from collections.abc import Mapping
119
from http import HTTPStatus
1210
from typing import TYPE_CHECKING, Any, cast
1311

1412
if TYPE_CHECKING:
13+
import ssl
14+
from collections.abc import Mapping
15+
1516
from botocore.client import BaseClient
1617

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

1928
from pyoverkiz.auth.base import AuthContext, AuthStrategy
20-
from pyoverkiz.auth.credentials import (
21-
LocalTokenCredentials,
22-
RexelOAuthCodeCredentials,
23-
TokenCredentials,
24-
UsernamePasswordCredentials,
25-
)
2629
from pyoverkiz.const import (
2730
COZYTOUCH_ATLANTIC_API,
2831
COZYTOUCH_CLIENT_ID,
@@ -48,7 +51,6 @@
4851
SomfyBadCredentialsError,
4952
SomfyServiceError,
5053
)
51-
from pyoverkiz.models import ServerConfig
5254

5355
MIN_JWT_SEGMENTS = 2
5456

@@ -159,7 +161,7 @@ async def refresh_if_needed(self) -> bool:
159161

160162
await self._request_access_token(
161163
grant_type="refresh_token",
162-
extra_fields={"refresh_token": cast(str, self.context.refresh_token)},
164+
extra_fields={"refresh_token": cast("str", self.context.refresh_token)},
163165
)
164166
return True
165167

@@ -351,7 +353,7 @@ async def refresh_if_needed(self) -> bool:
351353
"grant_type": "refresh_token",
352354
"client_id": REXEL_OAUTH_CLIENT_ID,
353355
"scope": REXEL_OAUTH_SCOPE,
354-
"refresh_token": cast(str, self.context.refresh_token),
356+
"refresh_token": cast("str", self.context.refresh_token),
355357
}
356358
)
357359
return True
@@ -429,6 +431,6 @@ def _decode_jwt_payload(token: str) -> dict[str, Any]:
429431
padding = "=" * (-len(payload_segment) % 4)
430432
try:
431433
decoded = base64.urlsafe_b64decode(payload_segment + padding)
432-
return cast(dict[str, Any], json.loads(decoded))
434+
return cast("dict[str, Any]", json.loads(decoded))
433435
except (binascii.Error, json.JSONDecodeError) as error:
434436
raise InvalidTokenError("Malformed JWT received.") from error

pyoverkiz/client.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77
import urllib.parse
88
from http import HTTPStatus
99
from pathlib import Path
10-
from types import TracebackType
11-
from typing import Any, Self, cast
10+
from typing import TYPE_CHECKING, Any, Self, cast
1211

1312
import backoff
1413
from aiohttp import (
@@ -17,7 +16,6 @@
1716
ClientSession,
1817
ServerDisconnectedError,
1918
)
20-
from backoff.types import Details
2119

2220
from pyoverkiz._case import decamelize
2321
from pyoverkiz.action_queue import ActionQueue, ActionQueueSettings
@@ -53,14 +51,20 @@
5351
from pyoverkiz.obfuscate import obfuscate_sensitive_data
5452
from pyoverkiz.response_handler import check_response
5553
from pyoverkiz.serializers import prepare_payload
56-
from pyoverkiz.types import JSON
54+
55+
if TYPE_CHECKING:
56+
from types import TracebackType
57+
58+
from backoff.types import Details
59+
60+
from pyoverkiz.types import JSON
5761

5862
_LOGGER = logging.getLogger(__name__)
5963

6064

6165
def _get_client_from_invocation(invocation: Details) -> OverkizClient:
6266
"""Return the `OverkizClient` instance from a backoff invocation."""
63-
return cast(OverkizClient, invocation["args"][0])
67+
return cast("OverkizClient", invocation["args"][0])
6468

6569

6670
async def relogin(invocation: Details) -> None:
@@ -417,7 +421,7 @@ async def register_event_listener(self) -> str:
417421
API on a regular basis.
418422
"""
419423
response = await self._post("events/register")
420-
listener_id = cast(str, response.get("id"))
424+
listener_id = cast("str", response.get("id"))
421425
self.event_listener_id = listener_id
422426

423427
return listener_id
@@ -463,7 +467,7 @@ async def get_api_version(self) -> str:
463467
"""Get the API version (local only)."""
464468
response = await self._get("apiVersion")
465469

466-
return cast(str, response["protocolVersion"])
470+
return cast("str", response["protocolVersion"])
467471

468472
@retry_on_too_many_executions
469473
@retry_on_auth_error
@@ -483,7 +487,7 @@ async def _execute_action_group_direct(
483487

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

486-
return cast(str, response["execId"])
490+
return cast("str", response["execId"])
487491

488492
async def execute_action_group(
489493
self,
@@ -569,13 +573,13 @@ async def get_places(self) -> Place:
569573
async def execute_persisted_action_group(self, oid: str) -> str:
570574
"""Execute a server-side action group by its OID (see ``get_action_groups``)."""
571575
response = await self._post(f"exec/{oid}")
572-
return cast(str, response["execId"])
576+
return cast("str", response["execId"])
573577

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

580584
@retry_on_auth_error
581585
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
@@ -4,8 +4,7 @@
44

55
from http import HTTPStatus
66
from json import JSONDecodeError
7-
8-
from aiohttp import ClientResponse
7+
from typing import TYPE_CHECKING
98

109
from pyoverkiz.exceptions import (
1110
AccessDeniedToGatewayError,
@@ -37,6 +36,9 @@
3736
UnknownUserError,
3837
)
3938

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

0 commit comments

Comments
 (0)