Skip to content

Commit

Permalink
ruff and mypy clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
cgtobi committed Sep 16, 2024
1 parent 261b5aa commit b594ccd
Show file tree
Hide file tree
Showing 27 changed files with 354 additions and 396 deletions.
83 changes: 22 additions & 61 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -78,66 +78,7 @@ fix = true
line-length = 88

[tool.ruff.lint]
select = [
"B002", # Python does not support the unary prefix increment
"B007", # Loop control variable {name} not used within loop body
"B014", # Exception handler with duplicate exception
"B023", # Function definition does not bind loop variable {name}
"B026", # Star-arg unpacking after a keyword argument is strongly discouraged
"C", # complexity
"COM818", # Trailing comma on bare tuple prohibited
"D", # docstrings
"DTZ003", # Use datetime.now(tz=) instead of datetime.utcnow()
"DTZ004", # Use datetime.fromtimestamp(ts, tz=) instead of datetime.utcfromtimestamp(ts)
"E", # pycodestyle
"F", # pyflakes/autoflake
"G", # flake8-logging-format
"I", # isort
"ICN001", # import concentions; {name} should be imported as {asname}
"N804", # First argument of a class method should be named cls
"N805", # First argument of a method should be named self
"N815", # Variable {name} in class scope should not be mixedCase
"S307", # No builtin eval() allowed
"PGH004", # Use specific rule codes when using noqa
"PLC0414", # Useless import alias. Import alias does not rename original package.
"PL", # pylint
"Q000", # Double quotes found but single quotes preferred
"RUF006", # Store a reference to the return value of asyncio.create_task
"S102", # Use of exec detected
"S103", # bad-file-permissions
"S108", # hardcoded-temp-file
"S306", # suspicious-mktemp-usage
"S307", # suspicious-eval-usage
"S313", # suspicious-xmlc-element-tree-usage
"S314", # suspicious-xml-element-tree-usage
"S315", # suspicious-xml-expat-reader-usage
"S316", # suspicious-xml-expat-builder-usage
"S317", # suspicious-xml-sax-usage
"S318", # suspicious-xml-mini-dom-usage
"S319", # suspicious-xml-pull-dom-usage
"S320", # suspicious-xmle-tree-usage
"S601", # paramiko-call
"S602", # subprocess-popen-with-shell-equals-true
"S604", # call-with-shell-equals-true
"S608", # hardcoded-sql-expression
"S609", # unix-command-wildcard-injection
"SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass
"SIM117", # Merge with-statements that use the same scope
"SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys()
"SIM201", # Use {left} != {right} instead of not {left} == {right}
"SIM208", # Use {expr} instead of not (not {expr})
"SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a}
"SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'.
"SIM401", # Use get from dict with default instead of an if block
"T100", # Trace found: {name} used
"T20", # flake8-print
"TID251", # Banned imports
"TRY004", # Prefer TypeError exception for invalid type
"B904", # Use raise from to specify exception cause
"TRY302", # Remove exception handler; error is immediately re-raised
"UP", # pyupgrade
"W", # pycodestyle
]
select = ["ALL"]

ignore = [
"D202", # No blank lines allowed after function docstring
Expand All @@ -147,6 +88,7 @@ ignore = [
"D407", # Section name underlining
"E501", # line too long
"E731", # do not assign a lambda expression, use a def
"N818", # Exception should be named with an Error suffix
# False positives https://github.com/astral-sh/ruff/issues/5386
"PLC0208", # Use a sequence type instead of a `set` when iterating over values
"PLR0911", # Too many return statements ({returns} > {max_returns})
Expand Down Expand Up @@ -175,9 +117,28 @@ split-on-trailing-comma = false
[tool.ruff.lint.per-file-ignores]
# Allow for main entry & scripts to write to stdout
"src/pyatmo/__main__.py" = ["T201"]
"src/pyatmo/modules/module.py" = ["PGH003"]
"src/pyatmo/auth.py" = ["ASYNC109"]

# Exceptions for tests
"tests/*" = ["D10"]
"tests/*" = [
"D10",
"S105",
"S101",
"ANN201",
"ANN001",
"N802",
"ANN202",
"PTH123",
"ASYNC230",
"PT012",
"DTZ001",
"ANN003",
"ANN002",
"A001",
"ARG001",
"ANN204",
]

[tool.ruff.lint.mccabe]
max-complexity = 25
30 changes: 19 additions & 11 deletions src/pyatmo/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

import logging
from typing import TYPE_CHECKING, Any
from typing import TYPE_CHECKING, Any, cast
from uuid import uuid4

from pyatmo import modules
Expand All @@ -20,7 +20,7 @@
)
from pyatmo.helpers import extract_raw_data
from pyatmo.home import Home
from pyatmo.modules.module import MeasureInterval, Module
from pyatmo.modules.module import Energy, MeasureInterval, Module

if TYPE_CHECKING:
from pyatmo.auth import AbstractAsyncAuth
Expand All @@ -31,7 +31,11 @@
class AsyncAccount:
"""Async class of a Netatmo account."""

def __init__(self, auth: AbstractAsyncAuth, favorite_stations: bool = True) -> None:
def __init__(
self,
auth: AbstractAsyncAuth,
favorite_stations: bool = True, # noqa: FBT001, FBT002
) -> None:
"""Initialize the Netatmo account."""

self.auth: AbstractAsyncAuth = auth
Expand Down Expand Up @@ -72,7 +76,8 @@ def process_topology(self, disabled_homes_ids: list[str] | None = None) -> None:
self.homes[home_id] = Home(self.auth, raw_data=home)

async def async_update_topology(
self, disabled_homes_ids: list[str] | None = None
self,
disabled_homes_ids: list[str] | None = None,
) -> None:
"""Retrieve topology data from /homesdata."""

Expand Down Expand Up @@ -126,12 +131,15 @@ async def async_update_measures(
) -> None:
"""Retrieve measures data from /getmeasure."""

await getattr(self.homes[home_id].modules[module_id], "async_update_measures")(
start_time=start_time,
end_time=end_time,
interval=interval,
days=days,
)
module = self.homes[home_id].modules[module_id]
if module.has_feature("historical_data"):
module = cast(Energy, module)
await module.async_update_measures(
start_time=start_time,
end_time=end_time,
interval=interval,
days=days,
)

def register_public_weather_area(
self,
Expand All @@ -140,7 +148,7 @@ def register_public_weather_area(
lat_sw: str,
lon_sw: str,
required_data_type: str | None = None,
filtering: bool = False,
filtering: bool = False, # noqa: FBT001, FBT002
*,
area_id: str = str(uuid4()),
) -> str:
Expand Down
68 changes: 46 additions & 22 deletions src/pyatmo/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@
import logging
from typing import Any

from aiohttp import ClientError, ClientResponse, ClientSession, ContentTypeError
from aiohttp import (
ClientError,
ClientResponse,
ClientSession,
ClientTimeout,
ContentTypeError,
)

from pyatmo.const import (
AUTHORIZATION_HEADER,
Expand Down Expand Up @@ -51,27 +57,31 @@ async def async_get_image(
try:
access_token = await self.async_get_access_token()
except ClientError as err:
raise ApiError(f"Access token failure: {err}") from err
msg = f"Access token failure: {err}"
raise ApiError(msg) from err
headers = {AUTHORIZATION_HEADER: f"Bearer {access_token}"}

req_args = {"data": params if params is not None else {}}

url = (base_url or self.base_url) + endpoint
async with self.websession.get(
url,
**req_args, # type: ignore
**req_args, # type: ignore # noqa: PGH003
headers=headers,
timeout=timeout,
timeout=ClientTimeout(total=timeout),
) as resp:
resp_content = await resp.read()

if resp.headers.get("content-type") == "image/jpeg":
return resp_content

raise ApiError(
msg = (
f"{resp.status} - "
f"invalid content-type in response"
f"when accessing '{url}'",
f"when accessing '{url}'"
)
raise ApiError(
msg,
)

async def async_post_api_request(
Expand Down Expand Up @@ -104,20 +114,21 @@ async def async_post_request(

async with self.websession.post(
url,
**req_args,
**req_args, # type: ignore # noqa: PGH003
headers=headers,
timeout=timeout,
timeout=ClientTimeout(total=timeout),
) as resp:
return await self.process_response(resp, url)

async def get_access_token(self):
async def get_access_token(self) -> str:
"""Get access token."""
try:
return await self.async_get_access_token()
except ClientError as err:
raise ApiError(f"Access token failure: {err}") from err
msg = f"Access token failure: {err}"
raise ApiError(msg) from err

def prepare_request_arguments(self, params):
def prepare_request_arguments(self, params: dict | None) -> dict:
"""Prepare request arguments."""
req_args = {"data": params if params is not None else {}}

Expand All @@ -131,7 +142,7 @@ def prepare_request_arguments(self, params):

return req_args

async def process_response(self, resp, url):
async def process_response(self, resp: ClientResponse, url: str) -> ClientResponse:
"""Process response."""
resp_status = resp.status
resp_content = await resp.read()
Expand All @@ -142,7 +153,12 @@ async def process_response(self, resp, url):

return await self.handle_success_response(resp, resp_content)

async def handle_error_response(self, resp, resp_status, url):
async def handle_error_response(
self,
resp: ClientResponse,
resp_status: int,
url: str,
) -> None:
"""Handle error response."""
try:
resp_json = await resp.json()
Expand All @@ -159,19 +175,25 @@ async def handle_error_response(self, resp, resp_status, url):
raise ApiErrorThrottling(
message,
)
else:
raise ApiError(
message,
)
raise ApiError(
message,
)

except (JSONDecodeError, ContentTypeError) as exc:
raise ApiError(
msg = (
f"{resp_status} - "
f"{ERRORS.get(resp_status, '')} - "
f"when accessing '{url}'",
f"when accessing '{url}'"
)
raise ApiError(
msg,
) from exc

async def handle_success_response(self, resp, resp_content):
async def handle_success_response(
self,
resp: ClientResponse,
resp_content: bytes,
) -> ClientResponse:
"""Handle success response."""
try:
if "application/json" in resp.headers.get("content-type", []):
Expand All @@ -193,7 +215,8 @@ async def async_addwebhook(self, webhook_url: str) -> None:
params={"url": webhook_url},
)
except asyncio.exceptions.TimeoutError as exc:
raise ApiError("Webhook registration timed out") from exc
msg = "Webhook registration timed out"
raise ApiError(msg) from exc
else:
LOG.debug("addwebhook: %s", resp)

Expand All @@ -205,6 +228,7 @@ async def async_dropwebhook(self) -> None:
params={"app_types": "app_security"},
)
except asyncio.exceptions.TimeoutError as exc:
raise ApiError("Webhook registration timed out") from exc
msg = "Webhook registration timed out"
raise ApiError(msg) from exc
else:
LOG.debug("dropwebhook: %s", resp)
3 changes: 3 additions & 0 deletions src/pyatmo/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,6 @@
MAX_HISTORY_TIME_FRAME = 24 * 2 * 3600

UNKNOWN = "unknown"

ON = True
OFF = False
5 changes: 4 additions & 1 deletion src/pyatmo/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING

from pyatmo.const import RawData
if TYPE_CHECKING:
from pyatmo.const import RawData

EVENT_ATTRIBUTES_MAP = {"id": "entity_id", "type": "event_type", "time": "event_time"}

Expand Down Expand Up @@ -86,6 +88,7 @@ class Event:
message: str | None = None
camera_id: str | None = None
device_id: str | None = None
module_id: str | None = None
person_id: str | None = None
video_id: str | None = None
sub_type: int | None = None
Expand Down
Loading

0 comments on commit b594ccd

Please sign in to comment.