Skip to content

Commit 64e8663

Browse files
committed
chore: improves python typing hints
1 parent 8f7cd9c commit 64e8663

17 files changed

Lines changed: 139 additions & 89 deletions

hostingde/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .client import HostingDeClient
2+
from .paginator import HostingDePaginator
3+
4+
__all__ = ['HostingDeClient', 'HostingDePaginator']

hostingde/account/account.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def __init__(self, parent: HostingDeCore):
1616
"""
1717
super().__init__(parent)
1818

19-
def build_uri(self, method: str):
19+
def build_uri(self, method: str) -> str:
2020
"""
2121
Utility method to easily construct URLs for this service.
2222
@@ -30,8 +30,8 @@ def list_subaccounts_names(
3030
limit: Optional[int] = None,
3131
filter: Optional[FilterElement] = None,
3232
sort: Optional[SortConfiguration] = None,
33-
*args,
34-
**kwargs
33+
*args: list,
34+
**kwargs: dict
3535
) -> HostingDePaginator[Account]:
3636
"""
3737
List subaccounts for the current context.

hostingde/api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from hostingde.exceptions import ClientException
33

44

5-
def login(base_url: str, token: str):
5+
def login(base_url: str, token: str) -> HostingDeClient:
66
"""
77
Entry point for the client. Builds and logs the user in.
88

hostingde/dns/dns.py

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def __init__(self, parent: HostingDeCore):
2424
"""
2525
super().__init__(parent)
2626

27-
def build_uri(self, method: str):
27+
def build_uri(self, method: str) -> str:
2828
"""
2929
Utility method to easily construct URLs for this service.
3030
@@ -38,8 +38,8 @@ def list_zones(
3838
limit: Optional[int] = None,
3939
filter: Optional[FilterElement] = None,
4040
sort: Optional[SortConfiguration] = None,
41-
*args,
42-
**kwargs
41+
*args: list,
42+
**kwargs: dict
4343
) -> HostingDePaginator[Zone]:
4444
"""
4545
Retrieves a list of Zone objects from the generic filtering and sorting API.
@@ -113,9 +113,9 @@ def list_zone_configs(
113113
limit: Optional[int] = None,
114114
filter: Optional[FilterElement] = None,
115115
sort: Optional[SortConfiguration] = None,
116-
*args,
117-
**kwargs
118-
) -> HostingDePaginator[Zone]:
116+
*args: list,
117+
**kwargs: dict
118+
) -> HostingDePaginator[ZoneConfig]:
119119
"""
120120
Retrieves a list of ZoneConfig objects from the generic filtering and sorting API.
121121
@@ -174,8 +174,8 @@ def list_records(
174174
limit: Optional[int] = None,
175175
filter: Optional[FilterElement] = None,
176176
sort: Optional[SortConfiguration] = None,
177-
*args,
178-
**kwargs
177+
*args: list,
178+
**kwargs: dict
179179
) -> HostingDePaginator[Record]:
180180
"""
181181
Retrieves a list of Record objects from the generic filtering and sorting API.
@@ -245,8 +245,8 @@ def jobs_find(
245245
limit: Optional[int] = None,
246246
filter: Optional[FilterElement] = None,
247247
sort: Optional[SortConfiguration] = None,
248-
*args,
249-
**kwargs
248+
*args: list,
249+
**kwargs: dict
250250
) -> HostingDePaginator[Job]:
251251
"""
252252
Retrieves a list of Job objects from the generic filtering and sorting API.
@@ -297,10 +297,12 @@ def update_zone(
297297
),
298298
)
299299

300-
if not asynchronous:
301-
JobWaiter(self, zone_config.id).wait()
300+
zone = self._instance(Zone, response.json().get('response', {}))
301+
302+
if not asynchronous and zone.zone_config.id is not None:
303+
JobWaiter(self, zone.zone_config.id).wait()
302304

303-
return self._instance(Zone, response.json().get('response', {}))
305+
return zone
304306

305307
def create_zone(
306308
self,
@@ -342,7 +344,7 @@ def create_zone(
342344

343345
zone: Zone = self._instance(Zone, response.json().get('response', {}))
344346

345-
if not asynchronous:
347+
if not asynchronous and zone.zone_config.id is not None:
346348
JobWaiter(self, zone.zone_config.id).wait()
347349

348350
return zone

hostingde/exceptions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ class ApiException(Exception):
33
The client throws an API exception, whenever the error was unknown. Details are included in the exception.
44
"""
55

6-
def __init__(self, details: dict, *args, **kwargs):
6+
def __init__(self, details: dict, *args: list, **kwargs: dict) -> None:
77
super().__init__(args, kwargs)
88
self.details = details
99

10-
def __str__(self):
10+
def __str__(self) -> str:
1111
return "\n".join([f"{error.get('text')}" for error in self.details.get('errors', [])])
1212

1313

hostingde/hostingde.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import json.decoder
22
from contextlib import contextmanager
3-
from typing import Optional, Type
3+
from typing import Generator, Optional, Type, TypeVar
44

55
from requests import Response
66

7+
import hostingde
78
from hostingde.exceptions import ApiException, ClientException
89
from hostingde.model import Model
910
from hostingde.model.filter import FilterElement
1011
from hostingde.model.sort import SortConfiguration
1112
from hostingde.session import HostingDeSession
1213

14+
T = TypeVar('T', bound='Model')
15+
1316

1417
class HostingDeCore:
1518
def __init__(self, parent: 'HostingDeCore' = None):
@@ -31,7 +34,7 @@ def _post(self, *args, **kwargs):
3134
"""
3235
return self.session.post(*args, **kwargs)
3336

34-
def _request(self, url, model: Optional[Model] = None, **kwargs):
37+
def _request(self, url: str, model: Optional[Model] = None, **kwargs: dict) -> Response:
3538
"""
3639
Execute a new request, given an URL and a model. To generate a URL, you can use the _build_url() utility
3740
method.
@@ -56,17 +59,17 @@ def _request(self, url, model: Optional[Model] = None, **kwargs):
5659

5760
return response
5861

59-
def _instance(self, instance_type: Type[Model], data: dict):
62+
def _instance(self, instance_type: Type[T], data: dict) -> T:
6063
"""
6164
Reconstructs a model from the passed in result.
6265
6366
:param instance_type: The instance type to be reconstructed.
6467
:param data: The data used to reconstruct the model
6568
:return: The parsed model
6669
"""
67-
return instance_type.from_json(data)
70+
return instance_type.from_json(data, self) # type: ignore
6871

69-
def _bool(self, response: Response):
72+
def _bool(self, response: Response) -> bool:
7073
"""
7174
Converts a response to a boolean. True, if the request succeeded, otherwise False. Note that a status of
7275
'pending' also returns False. You may want to use the _async method to wait for the job to complete.
@@ -89,11 +92,11 @@ def _build_uri(self, service: str, method: str) -> str:
8992
def _iter(
9093
self,
9194
url: str,
92-
instance_class: Type[Model],
95+
instance_class: Type[T],
9396
filter: Optional[FilterElement] = None,
9497
limit: Optional[int] = None,
9598
sort: Optional[SortConfiguration] = None,
96-
):
99+
) -> 'hostingde.HostingDePaginator[T]':
97100
"""
98101
Use the generic filtering and sorting API to paginate over results.
99102
@@ -104,9 +107,8 @@ def _iter(
104107
:param sort: The sorting of the resulting list
105108
:return: The iterator for the resultset
106109
"""
107-
from hostingde.paginator import HostingDePaginator
108110

109-
return HostingDePaginator(self, instance_class, url, filter=filter, limit=limit, sort=sort)
111+
return hostingde.HostingDePaginator(self, instance_class, url, filter=filter, limit=limit, sort=sort)
110112

111113
def login(self, url: str, token: str) -> None:
112114
"""
@@ -118,7 +120,7 @@ def login(self, url: str, token: str) -> None:
118120
self.session.base_uri = url
119121
self.session.token_auth(token)
120122

121-
def set_account_context(self, account_id: str):
123+
def set_account_context(self, account_id: Optional[str]) -> None:
122124
"""
123125
Sets the account context for this client.
124126
@@ -128,15 +130,15 @@ def set_account_context(self, account_id: str):
128130
self.session.set_account_context(account_id)
129131

130132
@contextmanager
131-
def switch_account_context(self, account_id: str):
133+
def switch_account_context(self, account_id: str) -> Generator[None, None, None]:
132134
"""
133135
Temporarily switch the account context for this client. After the context guard closes, the context is reset to
134136
the account that was used prior to the guard.
135137
136138
:param account_id: The account id to switch to
137139
:return:
138140
"""
139-
old_id: str = self.session.get_account_context()
141+
old_id: Optional[str] = self.session.get_account_context()
140142

141143
self.session.set_account_context(account_id)
142144

@@ -145,7 +147,7 @@ def switch_account_context(self, account_id: str):
145147
self.session.set_account_context(old_id)
146148

147149
@staticmethod
148-
def new_session():
150+
def new_session() -> HostingDeSession:
149151
"""
150152
Generates a new, unauthenticated session
151153

hostingde/job_waiter.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ def jobs_find(
1515
limit: Optional[int] = None,
1616
filter: Optional[FilterElement] = None,
1717
sort: Optional[SortConfiguration] = None,
18-
*args,
19-
**kwargs
18+
*args: list,
19+
**kwargs: dict
2020
) -> HostingDePaginator[Job]:
2121
"""
2222
Retrieves a list of Job objects from the generic filtering and sorting API.
@@ -34,7 +34,7 @@ def __init__(self, service: AsynchronousClient, id: str):
3434
self.service = service
3535
self.id = id
3636

37-
def wait(self):
37+
def wait(self) -> None:
3838
while True:
3939
jobs = self.service.jobs_find(
4040
filter=FilterCondition('jobObjectId').eq(self.id)

hostingde/model/__init__.py

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1+
from typing import Any, Optional, Type, TypeVar
2+
13
import marshmallow_dataclass
24
from marshmallow import post_dump, Schema
5+
from marshmallow.fields import Field
6+
7+
import hostingde
38

49

5-
def camelcase(s):
10+
def camelcase(s: str) -> str:
611
parts = iter(s.split("_"))
712
return next(parts) + "".join(i.title() for i in parts)
813

@@ -14,15 +19,15 @@ class CamelCaseSchema(Schema):
1419

1520
SKIP_VALUES = {None}
1621

17-
def __init__(self, **kwargs):
22+
def __init__(self, **kwargs: Any):
1823
super().__init__(unknown='EXCLUDE', **kwargs)
1924

20-
def on_bind_field(self, field_name, field_obj):
25+
def on_bind_field(self, field_name: str, field_obj: Field) -> None:
2126
"""Use lower-camel-casing as external representation"""
2227
field_obj.data_key = camelcase(field_obj.data_key or field_name)
2328

2429
@post_dump
25-
def remove_skip_values(self, data, **kwargs):
30+
def remove_skip_values(self, data: dict, **kwargs: dict) -> dict:
2631
"""
2732
Remove None values from the serialization result
2833
:param data: The input data (dict). Already serialized, but may contain None values
@@ -36,28 +41,55 @@ def remove_skip_values(self, data, **kwargs):
3641
}
3742

3843

44+
T = TypeVar('T', bound='Model')
45+
46+
3947
class Model:
4048
"""Represents a basic API Resource model"""
4149

4250
@classmethod
43-
def get_class_instance(cls):
51+
def get_class_instance(cls: Type[T]) -> Type[T]:
4452
return cls
4553

46-
def to_json(self):
54+
def to_json(self) -> dict:
4755
return marshmallow_dataclass.class_schema(self.get_class_instance(), base_schema=CamelCaseSchema)().dump(self)
4856

49-
def __init__(self, **kwargs):
57+
def __init__(self, **kwargs: dict):
5058
"""
5159
Default constructor that sets all kwargs as internal properties
60+
61+
All models allow for the context to be set.
62+
5263
:param kwargs: dictionary of parameters that this class must support
5364
"""
5465
self.__dict__ = kwargs
66+
self.client: Optional[hostingde.HostingDeClient] = None
67+
68+
# Context might be provided as a kwarg
69+
if "context" in kwargs and isinstance(kwargs["context"], hostingde.HostingDeClient):
70+
self.client = kwargs["context"]
71+
72+
def get_client(self) -> 'hostingde.HostingDeClient':
73+
"""
74+
Get the client used to create this object.
75+
76+
Throws an attribute error if no client is set.
77+
:return: an initialized client
78+
"""
79+
if not self.client:
80+
raise AttributeError("Context not set")
81+
82+
return self.client
5583

5684
@classmethod
57-
def from_json(cls, data: dict) -> 'Model':
85+
def from_json(cls: Type[T], data: dict, client: 'hostingde.HostingDeClient' = None) -> T:
5886
"""
5987
Create a new object from a given JSON.
6088
:param data: The json data
89+
:param client: The client to use for this object
6190
:return: New instance of the class
6291
"""
63-
return marshmallow_dataclass.class_schema(cls, base_schema=CamelCaseSchema)().load(data)
92+
inst = marshmallow_dataclass.class_schema(cls, base_schema=CamelCaseSchema)().load(data)
93+
inst.client = client
94+
95+
return inst

0 commit comments

Comments
 (0)