Skip to content

Commit 9c0a96a

Browse files
authored
refactor: improve hcloud.Client (#227)
* refactor: simplify _get_user_agent * refactor: rename json_content variable * refactor: add typings to hcloud.Client * test: modernize test_request_ok
1 parent 6a5c3f4 commit 9c0a96a

2 files changed

Lines changed: 60 additions & 69 deletions

File tree

hcloud/hcloud.py

Lines changed: 51 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import time
2+
from typing import Optional, Union
23

34
import requests
45

@@ -32,24 +33,19 @@ class Client:
3233

3334
def __init__(
3435
self,
35-
token,
36-
api_endpoint="https://api.hetzner.cloud/v1",
37-
application_name=None,
38-
application_version=None,
39-
poll_interval=1,
36+
token: str,
37+
api_endpoint: str = "https://api.hetzner.cloud/v1",
38+
application_name: Optional[str] = None,
39+
application_version: Optional[str] = None,
40+
poll_interval: int = 1,
4041
):
4142
"""Create an new Client instance
4243
43-
:param token: str
44-
Hetzner Cloud API token
45-
:param api_endpoint: str
46-
Hetzner Cloud API endpoint (default is https://api.hetzner.cloud/v1)
47-
:param application_name: str
48-
Your application name (default is None)
49-
:param application_version: str
50-
Your application _version (default is None)
51-
:param poll_interval: int
52-
Interval for polling information from Hetzner Cloud API in seconds (default is 1)
44+
:param token: Hetzner Cloud API token
45+
:param api_endpoint: Hetzner Cloud API endpoint
46+
:param application_name: Your application name
47+
:param application_version: Your application _version
48+
:param poll_interval: Interval for polling information from Hetzner Cloud API in seconds
5349
"""
5450
self.token = token
5551
self._api_endpoint = api_endpoint
@@ -148,85 +144,79 @@ def __init__(
148144
:type: :class:`PlacementGroupsClient <hcloud.placement_groups.client.PlacementGroupsClient>`
149145
"""
150146

151-
def _get_user_agent(self):
147+
def _get_user_agent(self) -> str:
152148
"""Get the user agent of the hcloud-python instance with the user application name (if specified)
153149
154-
:return: str
155-
The user agent of this hcloud-python instance
156-
"""
157-
if self._application_name is not None and self._application_version is None:
158-
return "{application_name} {prefix}/{version}".format(
159-
application_name=self._application_name,
160-
prefix=self.__user_agent_prefix,
161-
version=self._version,
162-
)
163-
elif (
164-
self._application_name is not None and self._application_version is not None
165-
):
166-
return "{application_name}/{application_version} {prefix}/{version}".format(
167-
application_name=self._application_name,
168-
application_version=self._application_version,
169-
prefix=self.__user_agent_prefix,
170-
version=self._version,
171-
)
172-
else:
173-
return "{prefix}/{version}".format(
174-
prefix=self.__user_agent_prefix, version=self._version
175-
)
176-
177-
def _get_headers(self):
150+
:return: The user agent of this hcloud-python instance
151+
"""
152+
user_agents = []
153+
for name, version in [
154+
(self._application_name, self._application_version),
155+
(self.__user_agent_prefix, self._version),
156+
]:
157+
if name is not None:
158+
user_agents.append(name if version is None else f"{name}/{version}")
159+
160+
return " ".join(user_agents)
161+
162+
def _get_headers(self) -> dict:
178163
headers = {
179164
"User-Agent": self._get_user_agent(),
180165
"Authorization": f"Bearer {self.token}",
181166
}
182167
return headers
183168

184-
def _raise_exception_from_response(self, response):
169+
def _raise_exception_from_response(self, response: requests.Response):
185170
raise APIException(
186171
code=response.status_code,
187172
message=response.reason,
188173
details={"content": response.content},
189174
)
190175

191-
def _raise_exception_from_json_content(self, json_content):
176+
def _raise_exception_from_content(self, content: dict):
192177
raise APIException(
193-
code=json_content["error"]["code"],
194-
message=json_content["error"]["message"],
195-
details=json_content["error"]["details"],
178+
code=content["error"]["code"],
179+
message=content["error"]["message"],
180+
details=content["error"]["details"],
196181
)
197182

198-
def request(self, method, url, tries=1, **kwargs):
183+
def request(
184+
self,
185+
method: str,
186+
url: str,
187+
tries: int = 1,
188+
**kwargs,
189+
) -> Union[bytes, dict]:
199190
"""Perform a request to the Hetzner Cloud API, wrapper around requests.request
200191
201-
:param method: str
202-
HTTP Method to perform the Request
203-
:param url: str
204-
URL of the Endpoint
205-
:param tries: int
206-
Tries of the request (used internally, should not be set by the user)
192+
:param method: HTTP Method to perform the Request
193+
:param url: URL of the Endpoint
194+
:param tries: Tries of the request (used internally, should not be set by the user)
207195
:return: Response
208-
:rtype: requests.Response
209196
"""
210197
response = self._requests_session.request(
211-
method, self._api_endpoint + url, headers=self._get_headers(), **kwargs
198+
method=method,
199+
url=self._api_endpoint + url,
200+
headers=self._get_headers(),
201+
**kwargs,
212202
)
213203

214-
json_content = response.content
204+
content = response.content
215205
try:
216-
if len(json_content) > 0:
217-
json_content = response.json()
206+
if len(content) > 0:
207+
content = response.json()
218208
except (TypeError, ValueError):
219209
self._raise_exception_from_response(response)
220210

221211
if not response.ok:
222-
if json_content:
223-
if json_content["error"]["code"] == "rate_limit_exceeded" and tries < 5:
212+
if content:
213+
if content["error"]["code"] == "rate_limit_exceeded" and tries < 5:
224214
time.sleep(tries * self._retry_wait_time)
225215
tries = tries + 1
226216
return self.request(method, url, tries, **kwargs)
227217
else:
228-
self._raise_exception_from_json_content(json_content)
218+
self._raise_exception_from_content(content)
229219
else:
230220
self._raise_exception_from_response(response)
231221

232-
return json_content
222+
return content

tests/unit/test_hcloud.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -81,15 +81,16 @@ def test_request_ok(self, client, response):
8181
response = client.request(
8282
"POST", "/servers", params={"argument": "value"}, timeout=2
8383
)
84-
client._requests_session.request.assert_called_once()
85-
assert client._requests_session.request.call_args[0] == (
86-
"POST",
87-
"https://api.hetzner.cloud/v1/servers",
84+
client._requests_session.request.assert_called_once_with(
85+
method="POST",
86+
url="https://api.hetzner.cloud/v1/servers",
87+
headers={
88+
"User-Agent": "hcloud-python/0.0.0",
89+
"Authorization": "Bearer project_token",
90+
},
91+
params={"argument": "value"},
92+
timeout=2,
8893
)
89-
assert client._requests_session.request.call_args[1]["params"] == {
90-
"argument": "value"
91-
}
92-
assert client._requests_session.request.call_args[1]["timeout"] == 2
9394
assert response == {"result": "data"}
9495

9596
def test_request_fails(self, client, fail_response):

0 commit comments

Comments
 (0)