Skip to content

Commit f11cdcd

Browse files
authored
Merge pull request #9 from roaldnefs/add-whois-contact-service
Add WhoisContactService to list WHOIS contact info
2 parents c8694de + fb6891e commit f11cdcd

8 files changed

Lines changed: 197 additions & 55 deletions

File tree

docs/user/quickstart.rst

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,31 +66,46 @@ Domains
6666
-------
6767

6868
Using the
69-
:class:`DomainService <transip.v6.services.DomainService>`
69+
:class:`DomainService <transip.v6.services.domain.DomainService>`
7070
service we can retrieve all domains in your TransIP account in the form of a
71-
:class:`Domain <transip.v6.objects.Domain>` object::
71+
:class:`Domain <transip.v6.services.domain.Domain>` object::
7272

7373
>>> domains = client.domains.list()
7474
>>> for domain in domains:
7575
... print(domain)
76-
<class 'transip.v6.objects.domain.Domain'> => {'name': 'transipdemo.be', 'authCode': '##########', 'isTransferLocked': False, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': True, 'tags': []}
77-
<class 'transip.v6.objects.domain.Domain'> => {'name': 'transipdemo.de', 'authCode': '##########', 'isTransferLocked': False, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': False, 'tags': []}
78-
<class 'transip.v6.objects.domain.Domain'> => {'name': 'transipdemo.net', 'authCode': '##########', 'isTransferLocked': True, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': True, 'tags': []}
79-
<class 'transip.v6.objects.domain.Domain'> => {'name': 'transipdemonstratie.com', 'authCode': '##########', 'isTransferLocked': True, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': True, 'tags': []}
80-
<class 'transip.v6.objects.domain.Domain'> => {'name': 'transipdemonstratie.nl', 'authCode': '##########', 'isTransferLocked': False, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': False, 'tags': []}
76+
<class 'transip.v6.services.domain.Domain'> => {'name': 'transipdemo.be', 'authCode': '##########', 'isTransferLocked': False, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': True, 'tags': []}
77+
<class 'transip.v6.services.domain.Domain'> => {'name': 'transipdemo.de', 'authCode': '##########', 'isTransferLocked': False, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': False, 'tags': []}
78+
<class 'transip.v6.services.domain.Domain'> => {'name': 'transipdemo.net', 'authCode': '##########', 'isTransferLocked': True, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': True, 'tags': []}
79+
<class 'transip.v6.services.domain.Domain'> => {'name': 'transipdemonstratie.com', 'authCode': '##########', 'isTransferLocked': True, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': True, 'tags': []}
80+
<class 'transip.v6.services.domain.Domain'> => {'name': 'transipdemonstratie.nl', 'authCode': '##########', 'isTransferLocked': False, 'registrationDate': '2011-04-29', 'renewalDate': '2021-04-29', 'isWhitelabel': False, 'isDnsOnly': False, 'cancellationDate': '', 'cancellationStatus': '', 'hasActionRunning': False, 'supportsLocking': False, 'tags': []}
8181

82-
We could also retrieve a single :class:`Domain <transip.v6.objects.Domain>`
83-
object by its name::
82+
We could also retrieve a single
83+
:class:`Domain <transip.v6.services.domain.Domain>` object by its name::
8484

8585
>>> domain = client.domains.get('transipdemonstratie.nl')
8686
>>> print(f"{domain.name} was registered on {domain.registrationDate}")
8787
transipdemonstratie.nl was registered on 2011-04-29
8888

89-
We could also cancel a single :class:`Domain <transip.v6.objects.Domain>`
90-
object by its name::
89+
We could also cancel a single
90+
:class:`Domain <transip.v6.services.domain.Domain>` object by its name::
9191

9292
>>> client.domains.delete('transipdemonstratie.nl')
9393

94+
Domain Contacts
95+
***************
96+
97+
We could also list the contacts as
98+
:class:`WhoisContact <transip.v6.services.domain.WhoisContact>` objects of a
99+
single :class:`Domain <transip.v6.services.domain.Domain>` object by its name::
100+
101+
>>> domain = client.domains.get('transipdemonstratie.nl')
102+
>>> contacts = domain.contacts()
103+
>>> for contact in contacts:
104+
... print(contact)
105+
<class 'transip.v6.services.domain.WhoisContact'> => {'type': 'registrant', 'firstName': 'TransIP', 'lastName': 'Demo', 'companyName': '', 'companyKvk': '', 'companyType': '', 'street': 'Schipholweg', 'number': '11e', 'postalCode': '2316 XB', 'city': 'LEIDEN', 'phoneNumber': '+31 715241919', 'faxNumber': '', 'email': 'feedback@transip.nl', 'country': 'nl'}
106+
<class 'transip.v6.services.domain.WhoisContact'> => {'type': 'administrative', 'firstName': 'TransIP', 'lastName': 'Demo', 'companyName': '', 'companyKvk': '', 'companyType': '', 'street': 'Schipholweg', 'number': '11e', 'postalCode': '2316 XB', 'city': 'LEIDEN', 'phoneNumber': '+31 715241919', 'faxNumber': '', 'email': 'feedback@transip.nl', 'country': 'nl'}
107+
<class 'transip.v6.services.domain.WhoisContact'> => {'type': 'technical', 'firstName': 'TransIP', 'lastName': 'Demo', 'companyName': '', 'companyKvk': '', 'companyType': '', 'street': 'Schipholweg', 'number': '11e', 'postalCode': '2316 XB', 'city': 'LEIDEN', 'phoneNumber': '+31 715241919', 'faxNumber': '', 'email': 'feedback@transip.nl', 'country': 'nl'}
108+
94109
Invoices
95110
--------
96111

tests/services/test_domains.py

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2020 Roald Nefs <info@roaldnefs.com>
4+
#
5+
# This file is part of python-transip.
6+
7+
# python-transip is free software: you can redistribute it and/or modify
8+
# it under the terms of the GNU Lesser General Public License as published by
9+
# the Free Software Foundation, either version 3 of the License, or
10+
# (at your option) any later version.
11+
12+
# python-transip is distributed in the hope that it will be useful,
13+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
# GNU Lesser General Public License for more details.
16+
17+
# You should have received a copy of the GNU Lesser General Public License
18+
# along with python-transip. If not, see <https://www.gnu.org/licenses/>.
19+
20+
from typing import Type, List
21+
import responses # type: ignore
22+
23+
from transip import TransIP
24+
from transip.v6.services.domain import Domain, WhoisContact
25+
26+
27+
@responses.activate
28+
def test_domains_get(transip_minimal_client: Type[TransIP]) -> None:
29+
responses.add(
30+
responses.GET,
31+
"https://api.transip.nl/v6/domains/example.com",
32+
json={
33+
"domain": {
34+
"name": "example.com",
35+
"authCode": "kJqfuOXNOYQKqh/jO4bYSn54YDqgAt1ksCe+ZG4Ud4nfpzw8qBsfR2JqAj7Ce12SxKcGD09v+yXd6lrm",
36+
"isTransferLocked": False,
37+
"registrationDate": "2016-01-01",
38+
"renewalDate": "2020-01-01",
39+
"isWhitelabel": False,
40+
"cancellationDate": "2020-01-01 12:00:00",
41+
"cancellationStatus": "signed",
42+
"isDnsOnly": False,
43+
"tags": [
44+
"customTag",
45+
"anotherTag"
46+
]
47+
}
48+
},
49+
status=200,
50+
)
51+
52+
domain: Type[Domain] = transip_minimal_client.domains.get("example.com")
53+
assert domain.get_id() == "example.com" # type: ignore
54+
55+
@responses.activate
56+
def test_domains_contacts_list(transip_minimal_client: Type[TransIP]) -> None:
57+
responses.add(
58+
responses.GET,
59+
"https://api.transip.nl/v6/domains/example.com",
60+
json={
61+
"domain": {
62+
"name": "example.com",
63+
"authCode": "kJqfuOXNOYQKqh/jO4bYSn54YDqgAt1ksCe+ZG4Ud4nfpzw8qBsfR2JqAj7Ce12SxKcGD09v+yXd6lrm",
64+
"isTransferLocked": False,
65+
"registrationDate": "2016-01-01",
66+
"renewalDate": "2020-01-01",
67+
"isWhitelabel": False,
68+
"cancellationDate": "2020-01-01 12:00:00",
69+
"cancellationStatus": "signed",
70+
"isDnsOnly": False,
71+
"tags": [
72+
"customTag",
73+
"anotherTag"
74+
]
75+
}
76+
},
77+
status=200,
78+
)
79+
responses.add(
80+
responses.GET,
81+
"https://api.transip.nl/v6/domains/example.com/contacts",
82+
json={
83+
"contacts": [
84+
{
85+
"type": "registrant",
86+
"firstName": "John",
87+
"lastName": "Doe",
88+
"companyName": "Example B.V.",
89+
"companyKvk": "83057825",
90+
"companyType": "BV",
91+
"street": "Easy street",
92+
"number": "12",
93+
"postalCode": "1337 XD",
94+
"city": "Leiden",
95+
"phoneNumber": "+31 715241919",
96+
"faxNumber": "+31 715241919",
97+
"email": "example@example.com",
98+
"country": "nl"
99+
}
100+
]
101+
},
102+
status=200,
103+
)
104+
105+
domain: Type[Domain] = transip_minimal_client.domains.get("example.com")
106+
contacts: List[Type[Domain]] = domain.contacts() # type: ignore
107+
contact: Type[Domain] = contacts[0]
108+
assert len(contacts) == 1
109+
assert contact.companyName == "Example B.V." # type: ignore

transip/base.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,15 @@
2525
class ApiObject:
2626
"""Represents a TransIP API object."""
2727

28-
_id_attr: str = "id"
28+
_id_attr: Optional[str] = "id"
2929

30-
def __init__(self, attrs) -> None:
31-
self.__dict__["_attrs"] = attrs
30+
def __init__(self, service, attrs) -> None:
31+
self.__dict__.update(
32+
{
33+
"service": service,
34+
"_attrs": attrs,
35+
}
36+
)
3237

3338
def __getattr__(self, name: str) -> Any:
3439
try:
@@ -54,9 +59,9 @@ def __dir__(self):
5459

5560
def get_id(self) -> Any:
5661
"""Returns the id of the object."""
57-
if not hasattr(self, self._id_attr):
58-
None
59-
return getattr(self, self._id_attr)
62+
if self._id_attr and hasattr(self, self._id_attr):
63+
return getattr(self, self._id_attr)
64+
return None
6065

6166
@property
6267
def attrs(self):
@@ -69,5 +74,18 @@ class ApiService:
6974
_path: Optional[str] = None
7075
_obj_cls: Optional[Type[ApiObject]] = None
7176

72-
def __init__(self, client: TransIP) -> None:
77+
def __init__(
78+
self,
79+
client: TransIP,
80+
parent: Optional[Type[ApiObject]] = None
81+
) -> None:
7382
self.client: TransIP = client
83+
self._parent: Optional[Type[ApiObject]] = parent
84+
85+
@property
86+
def path(self) -> Optional[str]:
87+
if self._path and self._parent:
88+
return self._path.format(
89+
parent_id=self._parent.get_id() # type: ignore
90+
)
91+
return self._path

transip/mixins.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ class GetMixin:
3232
"""
3333
client: TransIP
3434
_obj_cls: Optional[Type[ApiObject]]
35-
_path: str
35+
path: str
3636

3737
_resp_get_attr: Optional[str] = None
3838

3939
def get(self, id: str, **kwargs) -> Optional[Type[ApiObject]]:
40-
if self._obj_cls or self._path or self._resp_get_attr:
40+
if self._obj_cls or self.path or self._resp_get_attr:
4141
obj: Type[ApiObject] = self._obj_cls( # type: ignore
42-
self.client.get(f"{self._path}/{id}")[self._resp_get_attr]
42+
self,
43+
self.client.get(f"{self.path}/{id}")[self._resp_get_attr]
4344
)
4445
return obj
4546
return None
@@ -49,11 +50,11 @@ class DeleteMixin:
4950
"""Delete a single ApiObject."""
5051

5152
client: TransIP
52-
_path: str
53+
path: str
5354

5455
def delete(self, id: str, **kwargs) -> None:
55-
if self._path:
56-
self.client.delete(f"{self._path}/{id}")
56+
if self.path:
57+
self.client.delete(f"{self.path}/{id}")
5758

5859

5960
class ListMixin:
@@ -64,14 +65,14 @@ class ListMixin:
6465
``_resp_list_attr``: The response attribute which lists all objects
6566
"""
6667
client: TransIP
68+
path: str
6769
_obj_cls: Optional[Type[ApiObject]]
68-
_path: str
6970

7071
_resp_list_attr: Optional[str] = None
7172

7273
def list(self, **kwargs) -> List[Type[ApiObject]]:
7374
objs: List[Type[ApiObject]] = []
74-
if self._obj_cls and self._path and self._resp_list_attr:
75-
for obj in self.client.get(self._path)[self._resp_list_attr]:
76-
objs.append(self._obj_cls(obj)) # type: ignore
75+
if self._obj_cls and self.path and self._resp_list_attr:
76+
for obj in self.client.get(self.path)[self._resp_list_attr]:
77+
objs.append(self._obj_cls(self, obj)) # type: ignore
7778
return objs

transip/v6/objects/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@
1919

2020
from transip.v6.objects.availability_zone import AvailabilityZone # noqa: 401
2121
from transip.v6.objects.invoice import Invoice # noqa: 401
22-
from transip.v6.objects.domain import Domain # noqa: 401
22+
# from transip.v6.objects.domain import Domain # noqa: 401
2323
from transip.v6.objects.ssh_key import SshKey # noqa: 401
2424
from transip.v6.objects.vps import Vps # noqa: 401

transip/v6/objects/domain.py

Lines changed: 0 additions & 25 deletions
This file was deleted.

transip/v6/services/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
# along with python-transip. If not, see <https://www.gnu.org/licenses/>.
1919

2020
from transip.v6.services.availability_zone import AvailabilityZoneService # noqa: 401
21+
from transip.v6.services.domain import ( # noqa: 401
22+
DomainService, WhoisContactService, Domain, WhoisContact
23+
)
2124
from transip.v6.services.domain import DomainService # noqa: 401
2225
from transip.v6.services.invoice import InvoiceService # noqa: 401
2326
from transip.v6.services.ssh_key import SshKeyService # noqa: 401

transip/v6/services/domain.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,20 @@
2121

2222
from transip.base import ApiService, ApiObject
2323
from transip.mixins import GetMixin, DeleteMixin, ListMixin
24-
from transip.v6.objects.domain import Domain
24+
25+
26+
class Domain(ApiObject):
27+
28+
_id_attr: str = "name"
29+
30+
def contacts(self):
31+
service = WhoisContactService(self.service.client, parent=self)
32+
return service.list()
33+
34+
35+
class WhoisContact(ApiObject):
36+
37+
_id_attr: Optional[str] = None
2538

2639

2740
class DomainService(GetMixin, DeleteMixin, ListMixin, ApiService):
@@ -31,3 +44,11 @@ class DomainService(GetMixin, DeleteMixin, ListMixin, ApiService):
3144

3245
_resp_list_attr: str = "domains"
3346
_resp_get_attr: str = "domain"
47+
48+
49+
class WhoisContactService(ListMixin, ApiService):
50+
51+
_path: str = "/domains/{parent_id}/contacts"
52+
_obj_cls: Optional[Type[ApiObject]] = WhoisContact
53+
54+
_resp_list_attr: str = "contacts"

0 commit comments

Comments
 (0)