Skip to content

Commit c076803

Browse files
committed
Fix multiple lint issues in state.py
1 parent acbd863 commit c076803

File tree

1 file changed

+68
-21
lines changed

1 file changed

+68
-21
lines changed

pyhilo/util/state.py

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
"""Utility functions for state management."""
2+
3+
from __future__ import annotations
4+
15
import asyncio
26
from datetime import datetime
37
from os.path import isfile
4-
from typing import Any, Optional, Type, TypedDict, TypeVar, Union
8+
from typing import Any, ForwardRef, TypedDict, TypeVar, get_type_hints
59

610
import aiofiles
711
import ruyaml as yaml
@@ -10,24 +14,34 @@
1014

1115
lock = asyncio.Lock()
1216

17+
# These should ideally be data classes and not "TypedDict"
18+
1319

1420
class TokenDict(TypedDict):
15-
access: Optional[str]
16-
refresh: Optional[str]
21+
"""Represents a dictionary containing token information."""
22+
23+
access: str | None
24+
refresh: str | None
1725
expires_at: datetime
1826

1927

2028
class AndroidDeviceDict(TypedDict):
29+
"""Represents a dictionary containing Android device information."""
30+
2131
token: str
2232
device_id: int
2333

2434

2535
class WebsocketTransportsDict(TypedDict):
36+
"""Represents a dictionary containing Websocket connection information."""
37+
2638
transport: str
2739
transfer_formats: list[str]
2840

2941

3042
class WebsocketDict(TypedDict, total=False):
43+
"""Represents a dictionary containing registration information."""
44+
3145
token: str
3246
connection_id: str
3347
full_ws_url: str
@@ -36,17 +50,23 @@ class WebsocketDict(TypedDict, total=False):
3650

3751

3852
class RegistrationDict(TypedDict, total=False):
53+
"""Represents a dictionary containing registration information."""
54+
3955
reg_id: str
4056
expires_at: datetime
4157

4258

4359
class FirebaseDict(TypedDict):
44-
fid: Optional[str]
45-
name: Optional[str] # "projects/18450192328/installations/d7N8yHopRWOiTYCrnYLi8a"
60+
"""Represents a dictionary containing Firebase information."""
61+
62+
fid: str | None # "projects/18450192328/installations/d7N8yHopRWOiTYCrnYLi8a"
63+
name: str | None
4664
token: TokenDict
4765

4866

4967
class StateDict(TypedDict, total=False):
68+
"""Represents a dictionary containing the overall application state."""
69+
5070
token: TokenDict
5171
registration: RegistrationDict
5272
firebase: FirebaseDict
@@ -57,32 +77,57 @@ class StateDict(TypedDict, total=False):
5777
T = TypeVar("T", bound="StateDict")
5878

5979

60-
def __get_defaults__(cls: Type[T]) -> dict[str, Any]:
61-
"""Generates a default dict based on typed dict
80+
def _get_defaults(cls: type[T]) -> dict[str, Any]:
81+
"""Generate a default dict based on typed dict
6282
63-
:param cls: TypedDict class
64-
:type cls: Type[T]
65-
:return: Dictionary with empty values
83+
This function recursively creates a nested dictionary structure that mirrors
84+
the structure of a TypedDict (like StateDict, FirebaseDict, etc.). All the
85+
values in the resulting dictionary are initialized to None. This is used to
86+
create a template or a default "empty" state object.
87+
88+
This function is designed to work correctly whether or not
89+
`from __future__ import annotations` is used.
90+
91+
:param cls: The TypedDict class (e.g., StateDict, FirebaseDict) for which
92+
to generate the default dictionary.
93+
:type cls: type[T]
94+
:return: A dictionary with the same structure as the TypedDict, but with
95+
all values set to None.
6696
:rtype: dict[str, Any]
6797
"""
68-
# NOTE(dvd): Find a better way of identifying another TypedDict.
6998
new_dict: StateDict = {}
70-
for k, v in cls.__annotations__.items():
99+
# Iterate through the type hints of the TypedDict class.
100+
# get_type_hints handles both string-based type hints (from
101+
# `from __future__ import annotations`) and regular type hints.
102+
# include_extras=True is added to make sure the function works correctly with `Literal` types.
103+
for k, v in get_type_hints(cls, include_extras=True).items():
104+
# When using `get_type_hints`, some types are returned as `ForwardRef` objects.
105+
# This is a special type used to represent a type that is not yet defined.
106+
# We need to check if `v` is a `ForwardRef` and, if so, get the actual type
107+
# using `v.__forward_value__`.
108+
if isinstance(v, ForwardRef):
109+
v = v.__forward_value__
110+
# Check if the type `v` itself has `__annotations__`.
111+
# If it does, it means that `v` is also a TypedDict (or something that
112+
# behaves like one), indicating a nested structure.
71113
if hasattr(v, "__annotations__"):
72-
new_dict[k] = __get_defaults__(v) # type: ignore
114+
new_dict[k] = _get_defaults(v) # type: ignore[literal-required]
73115
else:
74-
new_dict[k] = None # type: ignore
75-
return new_dict # type: ignore
116+
new_dict[k] = None # type: ignore[literal-required]
117+
return new_dict # type: ignore[return-value]
76118

77119

78120
async def get_state(state_yaml: str) -> StateDict:
79121
"""Read in state yaml.
122+
80123
:param state_yaml: filename where to read the state
81124
:type state_yaml: ``str``
82125
:rtype: ``StateDict``
83126
"""
84-
if not isfile(state_yaml):
85-
return __get_defaults__(StateDict) # type: ignore
127+
if not isfile(
128+
state_yaml
129+
): # noqa: PTH113 - isfile is fine and simpler in this case.
130+
return _get_defaults(StateDict) # type: ignore
86131
async with aiofiles.open(state_yaml, mode="r") as yaml_file:
87132
LOG.debug("Loading state from yaml")
88133
content = await yaml_file.read()
@@ -93,9 +138,11 @@ async def get_state(state_yaml: str) -> StateDict:
93138
async def set_state(
94139
state_yaml: str,
95140
key: str,
96-
state: Union[
97-
TokenDict, RegistrationDict, FirebaseDict, AndroidDeviceDict, WebsocketDict
98-
],
141+
state: TokenDict
142+
| RegistrationDict
143+
| FirebaseDict
144+
| AndroidDeviceDict
145+
| WebsocketDict,
99146
) -> None:
100147
"""Save state yaml.
101148
:param state_yaml: filename where to read the state
@@ -108,7 +155,7 @@ async def set_state(
108155
"""
109156
async with lock: # note ic-dev21: on lock le fichier pour être sûr de finir la job
110157
current_state = await get_state(state_yaml) or {}
111-
merged_state: dict[str, Any] = {key: {**current_state.get(key, {}), **state}} # type: ignore
158+
merged_state: dict[str, Any] = {key: {**current_state.get(key, {}), **state}} # type: ignore[dict-item]
112159
new_state: dict[str, Any] = {**current_state, **merged_state}
113160
async with aiofiles.open(state_yaml, mode="w") as yaml_file:
114161
LOG.debug("Saving state to yaml file")

0 commit comments

Comments
 (0)