Skip to content

Commit 94196ca

Browse files
committed
Implement pagination
1 parent 446be04 commit 94196ca

41 files changed

Lines changed: 991 additions & 160 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

hcloud/actions/client.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,34 @@ class BoundAction(BoundModelBase):
99

1010

1111
class ActionsClient(ClientEntityBase):
12+
results_list_attribute_name = 'actions'
1213

1314
def get_by_id(self, id):
14-
# type: (int) -> actions.client.BoundAction
15+
# type: (int) -> BoundAction
1516
response = self._client.request(url="/actions/{action_id}".format(action_id=id), method="GET")
1617
return BoundAction(self, response['action'])
1718

18-
def get_all(self, status=None, sort=None):
19-
# type: # type: (Optional[List[str], Optional[List[str]]) -> List[BoundAction]
19+
def get_list(self,
20+
status=None, # type: Optional[List[str]]
21+
sort=None, # type: Optional[List[str]]
22+
page=None, # type: Optional[int]
23+
per_page=None, # type: Optional[int]
24+
):
25+
# type: (...) -> PageResults[List[BoundAction]]
2026
params = {}
2127
if status is not None:
22-
params.update({"status": status})
28+
params["status"] = status
2329
if sort is not None:
24-
params.update({"sort": sort})
30+
params["sort"] = sort
31+
if page is not None:
32+
params["page"] = page
33+
if per_page is not None:
34+
params["per_page"] = per_page
2535

2636
response = self._client.request(url="/actions", method="GET", params=params)
27-
return [BoundAction(self, action_data) for action_data in response['actions']]
37+
actions = [BoundAction(self, action_data) for action_data in response['actions']]
38+
return self.add_meta_to_result(actions, response)
39+
40+
def get_all(self, status=None, sort=None):
41+
# type: (Optional[List[str]], Optional[List[str]]) -> List[BoundAction]
42+
return super(ActionsClient, self).get_all(status=status, sort=sort)

hcloud/core/client.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,64 @@
11
# -*- coding: utf-8 -*-
2+
from hcloud.core.domain import add_meta_to_result
3+
4+
25
class ClientEntityBase(object):
6+
max_per_page = 50
7+
results_list_attribute_name = None
38

49
def __init__(self, client):
510
self._client = client
611

12+
def is_list_attribute_implemented(self):
13+
if self.results_list_attribute_name is None:
14+
raise NotImplementedError(
15+
"in order to get results list, 'results_list_attribute_name' attribute of {} has to be specified". format(self.__class__.__name__)
16+
)
17+
18+
def add_meta_to_result(self,
19+
results, # type: List[BoundModelBase]
20+
response # type: json
21+
):
22+
# type: (...) -> PageResult
23+
self.is_list_attribute_implemented()
24+
return add_meta_to_result(results, response, self.results_list_attribute_name)
25+
26+
def _get_all(self,
27+
list_function, # type: function
28+
results_list_attribute_name, # type: str
29+
*args,
30+
**kwargs
31+
):
32+
# type (...) -> List[BoundModelBase]
33+
page = 1
34+
35+
results = []
36+
37+
while page:
38+
page_result = list_function(page=page, per_page=self.max_per_page, *args, **kwargs)
39+
result = getattr(page_result, results_list_attribute_name)
40+
if result:
41+
results.extend(result)
42+
meta = page_result.meta
43+
if meta and meta.pagination and meta.pagination.next_page and meta.pagination.next_page:
44+
page = meta.pagination.next_page
45+
else:
46+
page = None
47+
48+
return results
49+
50+
def get_all(self, *args, **kwargs):
51+
# type: (...) -> List[BoundModelBase]
52+
self.is_list_attribute_implemented()
53+
return self._get_all(self.get_list, self.results_list_attribute_name, *args, **kwargs)
54+
55+
def get_actions(self, *args, **kwargs):
56+
# type: (...) -> List[BoundModelBase]
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, 'actions', *args, **kwargs)
61+
762

863
class BoundModelBase(object):
964
model = None

hcloud/core/domain.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
# -*- coding: utf-8 -*-
2+
from collections import namedtuple
3+
4+
25
class BaseDomain(object):
36
__slots__ = ()
47

8+
9+
class DomainIdentityMixin(object):
10+
__slots__ = ()
11+
512
@property
613
def id_or_name(self):
714
if self.id is not None:
@@ -10,3 +17,56 @@ def id_or_name(self):
1017
return self.name
1118
else:
1219
raise ValueError("id or name must be set")
20+
21+
22+
class Pagination(BaseDomain):
23+
__slots__ = (
24+
"page",
25+
"per_page",
26+
"previous_page",
27+
"next_page",
28+
"last_page",
29+
"total_entries",
30+
)
31+
32+
def __init__(self, page, per_page, previous_page=None, next_page=None, last_page=None, total_entries=None):
33+
self.page = page
34+
self.per_page = per_page
35+
self.previous_page = previous_page
36+
self.next_page = next_page
37+
self.last_page = last_page
38+
self.total_entries = total_entries
39+
40+
41+
class Meta(BaseDomain):
42+
43+
__slots__ = (
44+
"pagination",
45+
)
46+
47+
def __init__(
48+
self,
49+
pagination=None,
50+
):
51+
self.pagination = pagination
52+
53+
@classmethod
54+
def parse_meta(cls, json_content):
55+
meta = None
56+
if json_content and "meta" in json_content:
57+
meta = cls()
58+
pagination_json = json_content['meta'].get("pagination")
59+
if pagination_json:
60+
pagination = Pagination(**pagination_json)
61+
meta.pagination = pagination
62+
return meta
63+
64+
65+
def add_meta_to_result(result, json_content, attr_name):
66+
# type: (List[BoundModelBase], json, string) -> PageResult
67+
class_name = 'PageResults{0}'.format(attr_name.capitalize())
68+
PageResults = namedtuple(class_name, [attr_name, 'meta'])
69+
return PageResults(**{
70+
attr_name: result,
71+
'meta': Meta.parse_meta(json_content)
72+
})

hcloud/datacenters/client.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ def __init__(self, client, data):
1616

1717
server_types = data.get("server_types")
1818
if server_types is not None:
19-
DatacenterServerTypes
2019
available = [BoundServerType(client._client.server_types, {"id": server_type}, complete=False) for server_type in server_types['available']]
2120
supported = [BoundServerType(client._client.server_types, {"id": server_type}, complete=False) for server_type in server_types['supported']]
2221
available_for_migration = [BoundServerType(client._client.server_types, {"id": server_type}, complete=False) for server_type in server_types['available_for_migration']]
@@ -26,17 +25,35 @@ def __init__(self, client, data):
2625

2726

2827
class DatacentersClient(ClientEntityBase):
28+
results_list_attribute_name = 'datacenters'
2929

3030
def get_by_id(self, id):
31-
# type: (int) -> datacenters.client.BoundDatacenter
31+
# type: (int) -> BoundDatacenter
3232
response = self._client.request(url="/datacenters/{datacenter_id}".format(datacenter_id=id), method="GET")
3333
return BoundDatacenter(self, response['datacenter'])
3434

35-
def get_all(self, name=None):
36-
# type: (Optional[str]) -> List[BoundAction]
35+
def get_list(self,
36+
name=None, # type: Optional[str]
37+
page=None, # type: Optional[int]
38+
per_page=None # type: Optional[int]
39+
):
40+
# type: (...) -> PageResults[List[BoundDatacenter], Meta]
3741
params = {}
3842
if name is not None:
39-
params.update({"name": name})
43+
params["name"] = name
44+
45+
if page is not None:
46+
params['page'] = page
47+
48+
if per_page is not None:
49+
params['per_page'] = per_page
4050

4151
response = self._client.request(url="/datacenters", method="GET", params=params)
42-
return [BoundDatacenter(self, datacenter_data) for datacenter_data in response['datacenters']]
52+
53+
datacenters = [BoundDatacenter(self, datacenter_data) for datacenter_data in response['datacenters']]
54+
55+
return self.add_meta_to_result(datacenters, response)
56+
57+
def get_all(self, name=None):
58+
# type: (Optional[str]) -> List[BoundDatacenter]
59+
return super(DatacentersClient, self).get_all(name=name)

hcloud/datacenters/domain.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# -*- coding: utf-8 -*-
2-
from hcloud.core.domain import BaseDomain
2+
from hcloud.core.domain import BaseDomain, DomainIdentityMixin
33

44

5-
class Datacenter(BaseDomain):
5+
class Datacenter(BaseDomain, DomainIdentityMixin):
66

77
__slots__ = (
88
"id",

hcloud/hcloud.py

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -50,38 +50,41 @@ def _get_headers(self):
5050
}
5151
return headers
5252

53+
def _raise_exception_from_response(self, response):
54+
raise HcloudAPIException(
55+
code=response.status_code,
56+
message=response.reason,
57+
details={
58+
'content': response.content
59+
}
60+
)
61+
62+
def _raise_exception_from_json_content(self, json_content):
63+
raise HcloudAPIException(
64+
code=json_content['error']['code'],
65+
message=json_content['error']['message'],
66+
details=json_content['error']['details']
67+
)
68+
5369
def request(self, method, url, **kwargs):
5470
response = requests.request(
5571
method,
5672
self.api_endpoint + url,
5773
headers=self._get_headers(),
5874
**kwargs
5975
)
60-
result = response.content
76+
77+
json_content = response.content
6178
try:
62-
if len(response.content) > 0:
63-
result = response.json()
79+
if len(json_content) > 0:
80+
json_content = response.json()
6481
except (TypeError, ValueError):
65-
raise HcloudAPIException(
66-
code=response.status_code,
67-
message=response.reason,
68-
details={
69-
'content': response.content
70-
}
71-
)
82+
self._raise_exception_from_response(response)
7283

7384
if not response.ok:
74-
if len(response.content) > 0:
75-
raise HcloudAPIException(
76-
code=result['error']['code'],
77-
message=result['error']['message'],
78-
details=result['error']['details']
79-
)
85+
if json_content:
86+
self._raise_exception_from_json_content(json_content)
8087
else:
81-
raise HcloudAPIException(
82-
code="unknown_error",
83-
message="An unknown error occurred.",
84-
details=""
85-
)
88+
self._raise_exception_from_response(response)
8689

87-
return result
90+
return json_content

0 commit comments

Comments
 (0)