Skip to content

Commit 9c70221

Browse files
authored
test: enable mypy strict mode (#611)
- Increase type coverage - Fix invalid types Closes #479
1 parent 3df9e69 commit 9c70221

File tree

38 files changed

+230
-93
lines changed

38 files changed

+230
-93
lines changed

hcloud/_client.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import time
44
from http import HTTPStatus
55
from random import uniform
6-
from typing import Protocol
6+
from typing import Any, Protocol
77

88
import requests
99

@@ -72,7 +72,7 @@ def exponential_backoff_function(
7272
"""
7373

7474
def func(retries: int) -> float:
75-
interval = base * multiplier**retries # Exponential backoff
75+
interval: float = base * multiplier**retries # Exponential backoff
7676
interval = min(cap, interval) # Cap backoff
7777
if jitter:
7878
interval = uniform(base, interval) # Add jitter
@@ -292,7 +292,7 @@ def request( # type: ignore[no-untyped-def]
292292
method: str,
293293
url: str,
294294
**kwargs,
295-
) -> dict:
295+
) -> dict[str, Any]:
296296
"""Perform a request to the Hetzner Cloud API.
297297
298298
:param method: Method to perform the request.
@@ -345,7 +345,7 @@ def request( # type: ignore[no-untyped-def]
345345
method: str,
346346
url: str,
347347
**kwargs,
348-
) -> dict:
348+
) -> dict[str, Any]:
349349
"""Perform a request to the provided URL.
350350
351351
:param method: Method to perform the request.
@@ -381,7 +381,7 @@ def request( # type: ignore[no-untyped-def]
381381
continue
382382
raise
383383

384-
def _read_response(self, response: requests.Response) -> dict:
384+
def _read_response(self, response: requests.Response) -> dict[str, Any]:
385385
correlation_id = response.headers.get("X-Correlation-Id")
386386
payload = {}
387387
try:
@@ -404,7 +404,7 @@ def _read_response(self, response: requests.Response) -> dict:
404404
correlation_id=correlation_id,
405405
)
406406

407-
error: dict = payload["error"]
407+
error: dict[str, Any] = payload["error"]
408408
raise APIException(
409409
code=error["code"],
410410
message=error["message"],

hcloud/actions/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .._client import Client
1212

1313

14-
class BoundAction(BoundModelBase, Action):
14+
class BoundAction(BoundModelBase[Action], Action):
1515
_client: ActionsClient
1616

1717
model = Action

hcloud/actions/domain.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
3+
from typing import TYPE_CHECKING, Any, TypedDict
44

55
from .._exceptions import HCloudException
66
from ..core import BaseDomain
@@ -49,8 +49,8 @@ def __init__(
4949
progress: int | None = None,
5050
started: str | None = None,
5151
finished: str | None = None,
52-
resources: list[dict] | None = None,
53-
error: dict | None = None,
52+
resources: list[ActionResource] | None = None,
53+
error: ActionError | None = None,
5454
):
5555
self.id = id
5656
self.command = command
@@ -63,6 +63,17 @@ def __init__(
6363
self.error = error
6464

6565

66+
class ActionResource(TypedDict):
67+
id: int
68+
type: str
69+
70+
71+
class ActionError(TypedDict):
72+
code: str
73+
message: str
74+
details: dict[str, Any]
75+
76+
6677
class ActionException(HCloudException):
6778
"""A generic action exception"""
6879

@@ -80,7 +91,8 @@ def __init__(self, action: Action | BoundAction):
8091

8192
extras.append(action.error["code"])
8293
else:
83-
extras.append(action.command)
94+
if action.command is not None:
95+
extras.append(action.command)
8496

8597
extras.append(str(action.id))
8698
message += f" ({', '.join(extras)})"

hcloud/certificates/client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,17 @@
1515
from .._client import Client
1616

1717

18-
class BoundCertificate(BoundModelBase, Certificate):
18+
class BoundCertificate(BoundModelBase[Certificate], Certificate):
1919
_client: CertificatesClient
2020

2121
model = Certificate
2222

23-
def __init__(self, client: CertificatesClient, data: dict, complete: bool = True):
23+
def __init__(
24+
self,
25+
client: CertificatesClient,
26+
data: dict[str, Any],
27+
complete: bool = True,
28+
):
2429
status = data.get("status")
2530
if status is not None:
2631
error_data = status.get("error")

hcloud/core/client.py

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,16 @@
22

33
import warnings
44
from collections.abc import Callable
5-
from typing import TYPE_CHECKING, Any, ClassVar
5+
from typing import TYPE_CHECKING, Any, ClassVar, Generic, TypeVar
6+
7+
from .domain import BaseDomain
68

79
if TYPE_CHECKING:
810
from .._client import Client, ClientBase
9-
from .domain import BaseDomain
11+
from .domain import Meta
12+
13+
14+
T = TypeVar("T")
1015

1116

1217
class ResourceClientBase:
@@ -23,10 +28,10 @@ def __init__(self, client: Client):
2328

2429
def _iter_pages( # type: ignore[no-untyped-def]
2530
self,
26-
list_function: Callable,
31+
list_function: Callable[..., tuple[list[T], Meta]],
2732
*args,
2833
**kwargs,
29-
) -> list:
34+
) -> list[T]:
3035
results = []
3136

3237
page = 1
@@ -46,7 +51,12 @@ def _iter_pages( # type: ignore[no-untyped-def]
4651

4752
return results
4853

49-
def _get_first_by(self, list_function: Callable, *args, **kwargs): # type: ignore[no-untyped-def]
54+
def _get_first_by( # type: ignore[no-untyped-def]
55+
self,
56+
list_function: Callable[..., tuple[list[T], Meta]],
57+
*args,
58+
**kwargs,
59+
) -> T | None:
5060
entities, _ = list_function(*args, **kwargs)
5161
return entities[0] if entities else None
5262

@@ -69,15 +79,18 @@ def __init__(self, client: Client):
6979
super().__init__(client)
7080

7181

72-
class BoundModelBase:
82+
Domain = TypeVar("Domain", bound=BaseDomain)
83+
84+
85+
class BoundModelBase(Generic[Domain]):
7386
"""Bound Model Base"""
7487

75-
model: type[BaseDomain]
88+
model: type[Domain]
7689

7790
def __init__(
7891
self,
7992
client: ResourceClientBase,
80-
data: dict,
93+
data: dict[str, Any],
8194
complete: bool = True,
8295
):
8396
"""
@@ -90,7 +103,7 @@ def __init__(
90103
"""
91104
self._client = client
92105
self.complete = complete
93-
self.data_model = self.model.from_dict(data)
106+
self.data_model: Domain = self.model.from_dict(data)
94107

95108
def __getattr__(self, name: str): # type: ignore[no-untyped-def]
96109
"""Allow magical access to the properties of the model
@@ -103,9 +116,10 @@ def __getattr__(self, name: str): # type: ignore[no-untyped-def]
103116
value = getattr(self.data_model, name)
104117
return value
105118

106-
def _get_self(self) -> BoundModelBase:
119+
def _get_self(self) -> BoundModelBase[Domain]:
107120
assert hasattr(self._client, "get_by_id")
108-
return self._client.get_by_id(self.data_model.id)
121+
assert hasattr(self.data_model, "id")
122+
return self._client.get_by_id(self.data_model.id) # type: ignore
109123

110124
def reload(self) -> None:
111125
"""Reloads the model and tries to get all data from the API"""

hcloud/core/domain.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77

88

99
class BaseDomain:
10-
__api_properties__: tuple
10+
__api_properties__: tuple[str, ...]
1111

1212
@classmethod
13-
def from_dict(cls, data: dict): # type: ignore[no-untyped-def]
13+
def from_dict(cls, data: dict[str, Any]): # type: ignore[no-untyped-def]
1414
"""
1515
Build the domain object from the data dict.
1616
"""
1717
supported_data = {k: v for k, v in data.items() if k in cls.__api_properties__}
1818
return cls(**supported_data)
1919

2020
def __repr__(self) -> str:
21-
kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__api_properties__] # type: ignore[var-annotated]
21+
kwargs = [f"{key}={getattr(self, key)!r}" for key in self.__api_properties__]
2222
return f"{self.__class__.__qualname__}({', '.join(kwargs)})"
2323

2424
def __eq__(self, other: Any) -> bool:
@@ -119,7 +119,7 @@ def __init__(self, pagination: Pagination | None = None):
119119
self.pagination = pagination
120120

121121
@classmethod
122-
def parse_meta(cls, response: dict) -> Meta:
122+
def parse_meta(cls, response: dict[str, Any]) -> Meta:
123123
"""
124124
If present, extract the meta details from the response and return a meta object.
125125
"""

hcloud/datacenters/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
from .domain import Datacenter, DatacenterServerTypes
99

1010

11-
class BoundDatacenter(BoundModelBase, Datacenter):
11+
class BoundDatacenter(BoundModelBase[Datacenter], Datacenter):
1212
_client: DatacentersClient
1313

1414
model = Datacenter
1515

16-
def __init__(self, client: DatacentersClient, data: dict):
16+
def __init__(self, client: DatacentersClient, data: dict[str, Any]):
1717
location = data.get("location")
1818
if location is not None:
1919
data["location"] = BoundLocation(client._parent.locations, location)

hcloud/firewalls/client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@
1717
from .._client import Client
1818

1919

20-
class BoundFirewall(BoundModelBase, Firewall):
20+
class BoundFirewall(BoundModelBase[Firewall], Firewall):
2121
_client: FirewallsClient
2222

2323
model = Firewall
2424

25-
def __init__(self, client: FirewallsClient, data: dict, complete: bool = True):
25+
def __init__(
26+
self,
27+
client: FirewallsClient,
28+
data: dict[str, Any],
29+
complete: bool = True,
30+
):
2631
rules = data.get("rules", [])
2732
if rules:
2833
rules = [

hcloud/floating_ips/client.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@
1313
from ..servers import BoundServer, Server
1414

1515

16-
class BoundFloatingIP(BoundModelBase, FloatingIP):
16+
class BoundFloatingIP(BoundModelBase[FloatingIP], FloatingIP):
1717
_client: FloatingIPsClient
1818

1919
model = FloatingIP
2020

21-
def __init__(self, client: FloatingIPsClient, data: dict, complete: bool = True):
21+
def __init__(
22+
self,
23+
client: FloatingIPsClient,
24+
data: dict[str, Any],
25+
complete: bool = True,
26+
):
2227
# pylint: disable=import-outside-toplevel
2328
from ..servers import BoundServer
2429

hcloud/floating_ips/domain.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from __future__ import annotations
22

3-
from typing import TYPE_CHECKING
3+
from typing import TYPE_CHECKING, TypedDict
44

55
from ..core import BaseDomain, DomainIdentityMixin
66

77
if TYPE_CHECKING:
88
from ..actions import BoundAction
99
from ..locations import BoundLocation
10+
from ..rdns import DNSPtr
1011
from ..servers import BoundServer
1112
from .client import BoundFloatingIP
1213

@@ -63,10 +64,10 @@ def __init__(
6364
description: str | None = None,
6465
ip: str | None = None,
6566
server: BoundServer | None = None,
66-
dns_ptr: list[dict] | None = None,
67+
dns_ptr: list[DNSPtr] | None = None,
6768
home_location: BoundLocation | None = None,
6869
blocked: bool | None = None,
69-
protection: dict | None = None,
70+
protection: FloatingIPProtection | None = None,
7071
labels: dict[str, str] | None = None,
7172
created: str | None = None,
7273
name: str | None = None,
@@ -85,6 +86,10 @@ def __init__(
8586
self.name = name
8687

8788

89+
class FloatingIPProtection(TypedDict):
90+
delete: bool
91+
92+
8893
class CreateFloatingIPResponse(BaseDomain):
8994
"""Create Floating IP Response Domain
9095

0 commit comments

Comments
 (0)