Skip to content

Commit 7da2397

Browse files
authored
refactor: simplify client class inheritance complexity (#272)
- Instead of extensively overriding a parent method, use a private helper method. - Prevent having get_actions methods for clients that do not support it. - Make sure we document all the clients methods by not forgetting to override a parent method. - Do not implement inheritance with different method signature. * refactor: rename ClientEntityBase _get_all to _iter_pages * refactor: move GetEntityByNameMixin.get_by_name to ClientEntityBase._get_first_by Remove GetEntityByNameMixin and use _get_first_by method. * refactor: remove ClientEntityBase get_* methods * fix: always pass args before kwargs in _iter_pages * refactor: reuse _get_first_by for all get_by_* methods
1 parent e8a2e1b commit 7da2397

20 files changed

Lines changed: 162 additions & 160 deletions

File tree

hcloud/actions/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,4 @@ def get_all(
101101
Specify how the results are sorted. Choices: `id` `command` `status` `progress` `started` `finished` . You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default)
102102
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
103103
"""
104-
return super().get_all(status=status, sort=sort)
104+
return self._iter_pages(self.get_list, status=status, sort=sort)

hcloud/certificates/client.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import TYPE_CHECKING, Any, NamedTuple
44

55
from ..actions import ActionsPageResult, BoundAction
6-
from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta
6+
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from .domain import (
88
Certificate,
99
CreateManagedCertificateResponse,
@@ -103,7 +103,7 @@ class CertificatesPageResult(NamedTuple):
103103
meta: Meta | None
104104

105105

106-
class CertificatesClient(ClientEntityBase, GetEntityByNameMixin):
106+
class CertificatesClient(ClientEntityBase):
107107
_client: Client
108108

109109
def get_by_id(self, id: int) -> BoundCertificate:
@@ -171,7 +171,7 @@ def get_all(
171171
Can be used to filter certificates by labels. The response will only contain certificates matching the label selector.
172172
:return: List[:class:`BoundCertificate <hcloud.certificates.client.BoundCertificate>`]
173173
"""
174-
return super().get_all(name=name, label_selector=label_selector)
174+
return self._iter_pages(self.get_list, name=name, label_selector=label_selector)
175175

176176
def get_by_name(self, name: str) -> BoundCertificate | None:
177177
"""Get certificate by name
@@ -180,7 +180,7 @@ def get_by_name(self, name: str) -> BoundCertificate | None:
180180
Used to get certificate by name.
181181
:return: :class:`BoundCertificate <hcloud.certificates.client.BoundCertificate>`
182182
"""
183-
return super().get_by_name(name)
183+
return self._get_first_by(name=name)
184184

185185
def create(
186186
self,
@@ -338,7 +338,12 @@ def get_actions(
338338
Specify how the results are sorted. Choices: `id` `id:asc` `id:desc` `command` `command:asc` `command:desc` `status` `status:asc` `status:desc` `progress` `progress:asc` `progress:desc` `started` `started:asc` `started:desc` `finished` `finished:asc` `finished:desc`
339339
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
340340
"""
341-
return super().get_actions(certificate, status=status, sort=sort)
341+
return self._iter_pages(
342+
self.get_actions_list,
343+
certificate,
344+
status=status,
345+
sort=sort,
346+
)
342347

343348
def retry_issuance(
344349
self,

hcloud/core/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from __future__ import annotations
22

3-
from .client import BoundModelBase, ClientEntityBase, GetEntityByNameMixin # noqa: F401
3+
from .client import BoundModelBase, ClientEntityBase # noqa: F401
44
from .domain import BaseDomain, DomainIdentityMixin, Meta, Pagination # noqa: F401

hcloud/core/client.py

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
if TYPE_CHECKING:
66
from .._client import Client
7-
from ..actions import BoundAction
87

98

109
class ClientEntityBase:
@@ -19,7 +18,7 @@ def __init__(self, client: Client):
1918
"""
2019
self._client = client
2120

22-
def _get_all( # type: ignore[no-untyped-def]
21+
def _iter_pages( # type: ignore[no-untyped-def]
2322
self,
2423
list_function: Callable,
2524
*args,
@@ -32,42 +31,21 @@ def _get_all( # type: ignore[no-untyped-def]
3231
# The *PageResult tuples MUST have the following structure
3332
# `(result: List[Bound*], meta: Meta)`
3433
result, meta = list_function(
35-
page=page, per_page=self.max_per_page, *args, **kwargs
34+
*args, page=page, per_page=self.max_per_page, **kwargs
3635
)
3736
if result:
3837
results.extend(result)
3938

40-
if (
41-
meta
42-
and meta.pagination
43-
and meta.pagination.next_page
44-
and meta.pagination.next_page
45-
):
39+
if meta and meta.pagination and meta.pagination.next_page:
4640
page = meta.pagination.next_page
4741
else:
4842
page = 0
4943

5044
return results
5145

52-
def get_all(self, *args, **kwargs) -> list: # type: ignore[no-untyped-def]
46+
def _get_first_by(self, **kwargs): # type: ignore[no-untyped-def]
5347
assert hasattr(self, "get_list")
54-
return self._get_all(self.get_list, *args, **kwargs)
55-
56-
def get_actions(self, *args, **kwargs) -> list[BoundAction]: # type: ignore[no-untyped-def]
57-
if not hasattr(self, "get_actions_list"):
58-
raise ValueError("this endpoint does not support get_actions method")
59-
60-
return self._get_all(self.get_actions_list, *args, **kwargs)
61-
62-
63-
class GetEntityByNameMixin:
64-
"""
65-
Use as a mixin for ClientEntityBase classes
66-
"""
67-
68-
def get_by_name(self, name: str): # type: ignore[no-untyped-def]
69-
assert hasattr(self, "get_list")
70-
entities, _ = self.get_list(name=name)
48+
entities, _ = self.get_list(**kwargs)
7149
return entities[0] if entities else None
7250

7351

hcloud/datacenters/client.py

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

33
from typing import TYPE_CHECKING, Any, NamedTuple
44

5-
from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta
5+
from ..core import BoundModelBase, ClientEntityBase, Meta
66
from ..locations import BoundLocation
77
from ..server_types import BoundServerType
88
from .domain import Datacenter, DatacenterServerTypes
@@ -55,7 +55,7 @@ class DatacentersPageResult(NamedTuple):
5555
meta: Meta | None
5656

5757

58-
class DatacentersClient(ClientEntityBase, GetEntityByNameMixin):
58+
class DatacentersClient(ClientEntityBase):
5959
_client: Client
6060

6161
def get_by_id(self, id: int) -> BoundDatacenter:
@@ -109,7 +109,7 @@ def get_all(self, name: str | None = None) -> list[BoundDatacenter]:
109109
Can be used to filter datacenters by their name.
110110
:return: List[:class:`BoundDatacenter <hcloud.datacenters.client.BoundDatacenter>`]
111111
"""
112-
return super().get_all(name=name)
112+
return self._iter_pages(self.get_list, name=name)
113113

114114
def get_by_name(self, name: str) -> BoundDatacenter | None:
115115
"""Get datacenter by name
@@ -118,4 +118,4 @@ def get_by_name(self, name: str) -> BoundDatacenter | None:
118118
Used to get datacenter by name.
119119
:return: :class:`BoundDatacenter <hcloud.datacenters.client.BoundDatacenter>`
120120
"""
121-
return super().get_by_name(name)
121+
return self._get_first_by(name=name)

hcloud/firewalls/client.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import TYPE_CHECKING, Any, NamedTuple
44

55
from ..actions import ActionsPageResult, BoundAction
6-
from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta
6+
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from .domain import (
88
CreateFirewallResponse,
99
Firewall,
@@ -158,7 +158,7 @@ class FirewallsPageResult(NamedTuple):
158158
meta: Meta | None
159159

160160

161-
class FirewallsClient(ClientEntityBase, GetEntityByNameMixin):
161+
class FirewallsClient(ClientEntityBase):
162162
_client: Client
163163

164164
def get_actions_list(
@@ -218,7 +218,12 @@ def get_actions(
218218
219219
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
220220
"""
221-
return super().get_actions(firewall, status=status, sort=sort)
221+
return self._iter_pages(
222+
self.get_actions_list,
223+
firewall,
224+
status=status,
225+
sort=sort,
226+
)
222227

223228
def get_by_id(self, id: int) -> BoundFirewall:
224229
"""Returns a specific Firewall object.
@@ -287,7 +292,12 @@ def get_all(
287292
Choices: id name created (You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default))
288293
:return: List[:class:`BoundFirewall <hcloud.firewalls.client.BoundFirewall>`]
289294
"""
290-
return super().get_all(label_selector=label_selector, name=name, sort=sort)
295+
return self._iter_pages(
296+
self.get_list,
297+
label_selector=label_selector,
298+
name=name,
299+
sort=sort,
300+
)
291301

292302
def get_by_name(self, name: str) -> BoundFirewall | None:
293303
"""Get Firewall by name
@@ -296,7 +306,7 @@ def get_by_name(self, name: str) -> BoundFirewall | None:
296306
Used to get Firewall by name.
297307
:return: :class:`BoundFirewall <hcloud.firewalls.client.BoundFirewall>`
298308
"""
299-
return super().get_by_name(name)
309+
return self._get_first_by(name=name)
300310

301311
def create(
302312
self,

hcloud/floating_ips/client.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import TYPE_CHECKING, Any, NamedTuple
44

55
from ..actions import ActionsPageResult, BoundAction
6-
from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta
6+
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from ..locations import BoundLocation
88
from .domain import CreateFloatingIPResponse, FloatingIP
99

@@ -139,7 +139,7 @@ class FloatingIPsPageResult(NamedTuple):
139139
meta: Meta | None
140140

141141

142-
class FloatingIPsClient(ClientEntityBase, GetEntityByNameMixin):
142+
class FloatingIPsClient(ClientEntityBase):
143143
_client: Client
144144

145145
def get_actions_list(
@@ -199,7 +199,12 @@ def get_actions(
199199
200200
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
201201
"""
202-
return super().get_actions(floating_ip, status=status, sort=sort)
202+
return self._iter_pages(
203+
self.get_actions_list,
204+
floating_ip,
205+
status=status,
206+
sort=sort,
207+
)
203208

204209
def get_by_id(self, id: int) -> BoundFloatingIP:
205210
"""Returns a specific Floating IP object.
@@ -263,7 +268,7 @@ def get_all(
263268
Can be used to filter networks by their name.
264269
:return: List[:class:`BoundFloatingIP <hcloud.floating_ips.client.BoundFloatingIP>`]
265270
"""
266-
return super().get_all(label_selector=label_selector, name=name)
271+
return self._iter_pages(self.get_list, label_selector=label_selector, name=name)
267272

268273
def get_by_name(self, name: str) -> BoundFloatingIP | None:
269274
"""Get Floating IP by name
@@ -272,7 +277,7 @@ def get_by_name(self, name: str) -> BoundFloatingIP | None:
272277
Used to get Floating IP by name.
273278
:return: :class:`BoundFloatingIP <hcloud.floating_ips.client.BoundFloatingIP>`
274279
"""
275-
return super().get_by_name(name)
280+
return self._get_first_by(name=name)
276281

277282
def create(
278283
self,

hcloud/images/client.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import TYPE_CHECKING, Any, NamedTuple
44

55
from ..actions import ActionsPageResult, BoundAction
6-
from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta
6+
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from .domain import Image
88

99
if TYPE_CHECKING:
@@ -110,7 +110,7 @@ class ImagesPageResult(NamedTuple):
110110
meta: Meta | None
111111

112112

113-
class ImagesClient(ClientEntityBase, GetEntityByNameMixin):
113+
class ImagesClient(ClientEntityBase):
114114
_client: Client
115115

116116
def get_actions_list(
@@ -169,7 +169,12 @@ def get_actions(
169169
Specify how the results are sorted. Choices: `id` `command` `status` `progress` `started` `finished` . You can add one of ":asc", ":desc" to modify sort order. ( ":asc" is default)
170170
:return: List[:class:`BoundAction <hcloud.actions.client.BoundAction>`]
171171
"""
172-
return super().get_actions(image, sort=sort, status=status)
172+
return self._iter_pages(
173+
self.get_actions_list,
174+
image,
175+
sort=sort,
176+
status=status,
177+
)
173178

174179
def get_by_id(self, id: int) -> BoundImage:
175180
"""Get a specific Image
@@ -274,7 +279,8 @@ def get_all(
274279
Include deprecated images in the response. Default: False
275280
:return: List[:class:`BoundImage <hcloud.images.client.BoundImage>`]
276281
"""
277-
return super().get_all(
282+
return self._iter_pages(
283+
self.get_list,
278284
name=name,
279285
label_selector=label_selector,
280286
bound_to=bound_to,
@@ -294,7 +300,7 @@ def get_by_name(self, name: str) -> BoundImage | None:
294300
Used to get image by name.
295301
:return: :class:`BoundImage <hcloud.images.client.BoundImage>`
296302
"""
297-
return super().get_by_name(name)
303+
return self._get_first_by(name=name)
298304

299305
def get_by_name_and_architecture(
300306
self,
@@ -309,9 +315,7 @@ def get_by_name_and_architecture(
309315
Used to identify the image.
310316
:return: :class:`BoundImage <hcloud.images.client.BoundImage>`
311317
"""
312-
entities, _ = self.get_list(name=name, architecture=[architecture])
313-
entity = entities[0] if entities else None
314-
return entity
318+
return self._get_first_by(name=name, architecture=[architecture])
315319

316320
def update(
317321
self,

hcloud/isos/client.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import TYPE_CHECKING, Any, NamedTuple
44
from warnings import warn
55

6-
from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta
6+
from ..core import BoundModelBase, ClientEntityBase, Meta
77
from .domain import Iso
88

99
if TYPE_CHECKING:
@@ -21,7 +21,7 @@ class IsosPageResult(NamedTuple):
2121
meta: Meta | None
2222

2323

24-
class IsosClient(ClientEntityBase, GetEntityByNameMixin):
24+
class IsosClient(ClientEntityBase):
2525
_client: Client
2626

2727
def get_by_id(self, id: int) -> BoundIso:
@@ -111,7 +111,8 @@ def get_all(
111111
)
112112
include_architecture_wildcard = include_wildcard_architecture
113113

114-
return super().get_all(
114+
return self._iter_pages(
115+
self.get_list,
115116
name=name,
116117
architecture=architecture,
117118
include_architecture_wildcard=include_architecture_wildcard,
@@ -124,4 +125,4 @@ def get_by_name(self, name: str) -> BoundIso | None:
124125
Used to get iso by name.
125126
:return: :class:`BoundIso <hcloud.isos.client.BoundIso>`
126127
"""
127-
return super().get_by_name(name)
128+
return self._get_first_by(name=name)

hcloud/load_balancer_types/client.py

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

33
from typing import TYPE_CHECKING, Any, NamedTuple
44

5-
from ..core import BoundModelBase, ClientEntityBase, GetEntityByNameMixin, Meta
5+
from ..core import BoundModelBase, ClientEntityBase, Meta
66
from .domain import LoadBalancerType
77

88
if TYPE_CHECKING:
@@ -20,7 +20,7 @@ class LoadBalancerTypesPageResult(NamedTuple):
2020
meta: Meta | None
2121

2222

23-
class LoadBalancerTypesClient(ClientEntityBase, GetEntityByNameMixin):
23+
class LoadBalancerTypesClient(ClientEntityBase):
2424
_client: Client
2525

2626
def get_by_id(self, id: int) -> BoundLoadBalancerType:
@@ -77,7 +77,7 @@ def get_all(self, name: str | None = None) -> list[BoundLoadBalancerType]:
7777
Can be used to filter Load Balancer type by their name.
7878
:return: List[:class:`BoundLoadBalancerType <hcloud.load_balancer_types.client.BoundLoadBalancerType>`]
7979
"""
80-
return super().get_all(name=name)
80+
return self._iter_pages(self.get_list, name=name)
8181

8282
def get_by_name(self, name: str) -> BoundLoadBalancerType | None:
8383
"""Get Load Balancer type by name
@@ -86,4 +86,4 @@ def get_by_name(self, name: str) -> BoundLoadBalancerType | None:
8686
Used to get Load Balancer type by name.
8787
:return: :class:`BoundLoadBalancerType <hcloud.load_balancer_types.client.BoundLoadBalancerType>`
8888
"""
89-
return super().get_by_name(name)
89+
return self._get_first_by(name=name)

0 commit comments

Comments
 (0)