Skip to content

Commit 413472d

Browse files
authored
feat: move hcloud.hcloud module to hcloud._client (#243)
* feat: move hcloud.hcloud module to hcloud._client * feat: add deprecation for hcloud.hcloud module
1 parent cf64e54 commit 413472d

6 files changed

Lines changed: 416 additions & 401 deletions

File tree

hcloud/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1+
from ._client import Client # noqa
12
from ._exceptions import APIException, HCloudException # noqa
2-
from .hcloud import Client # noqa

hcloud/_client.py

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import time
2+
from typing import Optional, Union
3+
4+
import requests
5+
6+
from .__version__ import VERSION
7+
from ._exceptions import APIException
8+
from .actions.client import ActionsClient
9+
from .certificates.client import CertificatesClient
10+
from .datacenters.client import DatacentersClient
11+
from .firewalls.client import FirewallsClient
12+
from .floating_ips.client import FloatingIPsClient
13+
from .images.client import ImagesClient
14+
from .isos.client import IsosClient
15+
from .load_balancer_types.client import LoadBalancerTypesClient
16+
from .load_balancers.client import LoadBalancersClient
17+
from .locations.client import LocationsClient
18+
from .networks.client import NetworksClient
19+
from .placement_groups.client import PlacementGroupsClient
20+
from .primary_ips.client import PrimaryIPsClient
21+
from .server_types.client import ServerTypesClient
22+
from .servers.client import ServersClient
23+
from .ssh_keys.client import SSHKeysClient
24+
from .volumes.client import VolumesClient
25+
26+
27+
class Client:
28+
"""Base Client for accessing the Hetzner Cloud API"""
29+
30+
_version = VERSION
31+
_retry_wait_time = 0.5
32+
__user_agent_prefix = "hcloud-python"
33+
34+
def __init__(
35+
self,
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,
41+
):
42+
"""Create an new Client instance
43+
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
49+
"""
50+
self.token = token
51+
self._api_endpoint = api_endpoint
52+
self._application_name = application_name
53+
self._application_version = application_version
54+
self._requests_session = requests.Session()
55+
self.poll_interval = poll_interval
56+
57+
self.datacenters = DatacentersClient(self)
58+
"""DatacentersClient Instance
59+
60+
:type: :class:`DatacentersClient <hcloud.datacenters.client.DatacentersClient>`
61+
"""
62+
self.locations = LocationsClient(self)
63+
"""LocationsClient Instance
64+
65+
:type: :class:`LocationsClient <hcloud.locations.client.LocationsClient>`
66+
"""
67+
self.servers = ServersClient(self)
68+
"""ServersClient Instance
69+
70+
:type: :class:`ServersClient <hcloud.servers.client.ServersClient>`
71+
"""
72+
self.server_types = ServerTypesClient(self)
73+
"""ServerTypesClient Instance
74+
75+
:type: :class:`ServerTypesClient <hcloud.server_types.client.ServerTypesClient>`
76+
"""
77+
self.volumes = VolumesClient(self)
78+
"""VolumesClient Instance
79+
80+
:type: :class:`VolumesClient <hcloud.volumes.client.VolumesClient>`
81+
"""
82+
self.actions = ActionsClient(self)
83+
"""ActionsClient Instance
84+
85+
:type: :class:`ActionsClient <hcloud.actions.client.ActionsClient>`
86+
"""
87+
self.images = ImagesClient(self)
88+
"""ImagesClient Instance
89+
90+
:type: :class:`ImagesClient <hcloud.images.client.ImagesClient>`
91+
"""
92+
self.isos = IsosClient(self)
93+
"""ImagesClient Instance
94+
95+
:type: :class:`IsosClient <hcloud.isos.client.IsosClient>`
96+
"""
97+
self.ssh_keys = SSHKeysClient(self)
98+
"""SSHKeysClient Instance
99+
100+
:type: :class:`SSHKeysClient <hcloud.ssh_keys.client.SSHKeysClient>`
101+
"""
102+
self.floating_ips = FloatingIPsClient(self)
103+
"""FloatingIPsClient Instance
104+
105+
:type: :class:`FloatingIPsClient <hcloud.floating_ips.client.FloatingIPsClient>`
106+
"""
107+
self.primary_ips = PrimaryIPsClient(self)
108+
"""PrimaryIPsClient Instance
109+
110+
:type: :class:`PrimaryIPsClient <hcloud.primary_ips.client.PrimaryIPsClient>`
111+
"""
112+
self.networks = NetworksClient(self)
113+
"""NetworksClient Instance
114+
115+
:type: :class:`NetworksClient <hcloud.networks.client.NetworksClient>`
116+
"""
117+
self.certificates = CertificatesClient(self)
118+
"""CertificatesClient Instance
119+
120+
:type: :class:`CertificatesClient <hcloud.certificates.client.CertificatesClient>`
121+
"""
122+
123+
self.load_balancers = LoadBalancersClient(self)
124+
"""LoadBalancersClient Instance
125+
126+
:type: :class:`LoadBalancersClient <hcloud.load_balancers.client.LoadBalancersClient>`
127+
"""
128+
129+
self.load_balancer_types = LoadBalancerTypesClient(self)
130+
"""LoadBalancerTypesClient Instance
131+
132+
:type: :class:`LoadBalancerTypesClient <hcloud.load_balancer_types.client.LoadBalancerTypesClient>`
133+
"""
134+
135+
self.firewalls = FirewallsClient(self)
136+
"""FirewallsClient Instance
137+
138+
:type: :class:`FirewallsClient <hcloud.firewalls.client.FirewallsClient>`
139+
"""
140+
141+
self.placement_groups = PlacementGroupsClient(self)
142+
"""PlacementGroupsClient Instance
143+
144+
:type: :class:`PlacementGroupsClient <hcloud.placement_groups.client.PlacementGroupsClient>`
145+
"""
146+
147+
def _get_user_agent(self) -> str:
148+
"""Get the user agent of the hcloud-python instance with the user application name (if specified)
149+
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:
163+
headers = {
164+
"User-Agent": self._get_user_agent(),
165+
"Authorization": f"Bearer {self.token}",
166+
}
167+
return headers
168+
169+
def _raise_exception_from_response(self, response: requests.Response):
170+
raise APIException(
171+
code=response.status_code,
172+
message=response.reason,
173+
details={"content": response.content},
174+
)
175+
176+
def _raise_exception_from_content(self, content: dict):
177+
raise APIException(
178+
code=content["error"]["code"],
179+
message=content["error"]["message"],
180+
details=content["error"]["details"],
181+
)
182+
183+
def request(
184+
self,
185+
method: str,
186+
url: str,
187+
tries: int = 1,
188+
**kwargs,
189+
) -> Union[bytes, dict]:
190+
"""Perform a request to the Hetzner Cloud API, wrapper around requests.request
191+
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)
195+
:return: Response
196+
"""
197+
response = self._requests_session.request(
198+
method=method,
199+
url=self._api_endpoint + url,
200+
headers=self._get_headers(),
201+
**kwargs,
202+
)
203+
204+
content = response.content
205+
try:
206+
if len(content) > 0:
207+
content = response.json()
208+
except (TypeError, ValueError):
209+
self._raise_exception_from_response(response)
210+
211+
if not response.ok:
212+
if content:
213+
if content["error"]["code"] == "rate_limit_exceeded" and tries < 5:
214+
time.sleep(tries * self._retry_wait_time)
215+
tries = tries + 1
216+
return self.request(method, url, tries, **kwargs)
217+
else:
218+
self._raise_exception_from_content(content)
219+
else:
220+
self._raise_exception_from_response(response)
221+
222+
return content

0 commit comments

Comments
 (0)