Skip to content

Commit b133a94

Browse files
authored
Merge pull request #30 from sbarbett/pr-backlog
Functionalities from PR backlog - PRs# 6, 7, 8 - Proxy support, resign zones, RD pool support
2 parents d3e26fe + 894cd21 commit b133a94

File tree

5 files changed

+249
-45
lines changed

5 files changed

+249
-45
lines changed

.plugin-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v2.2.4
1+
v2.2.5

README.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,45 @@ Headers can also be modified after instantiation using the `set_custom_headers()
115115
client.rest_api_connection.set_custom_headers({"boo":"far","user-agent":"goodbye"})
116116
```
117117

118+
### Proxying Requests
119+
120+
In situations where the client needs to send requests from an application behind a proxy, proxy details can be supplied as part of the constructor:
121+
122+
```python
123+
# Define proxy settings using the format required by the `requests` library
124+
proxy_dict = {
125+
"http": "http://proxy.example.com:8080",
126+
"https": "http://proxy.example.com:8080"
127+
}
128+
129+
# Initialize the client with a proxy
130+
client = RestApiClient(
131+
"username",
132+
"password",
133+
proxy=proxy_dict
134+
)
135+
136+
# Make an API request with the proxy enabled
137+
response = client.create_primary_zone("my_account", "example.com")
138+
139+
# Update the proxy dynamically if needed
140+
client.rest_api_connection.set_proxy({
141+
"http": "http://newproxy.example.com:8080",
142+
"https": "http://newproxy.example.com:8080"
143+
})
144+
```
145+
146+
If desired, TLS validation may be disabled using the `verify_https` flag.
147+
148+
```python
149+
client = RestApiClient(
150+
"username",
151+
"password",
152+
proxy=proxy_dict,
153+
verify_https=False
154+
)
155+
```
156+
118157
### Quick Examples
119158
This example shows a complete working python file which will create a primary zone in UltraDNS. This example highlights how to get services using client and make requests.
120159

@@ -161,6 +200,26 @@ def create_cname_record(client, domain):
161200
"""
162201
return client.create_rrset(domain, "CNAME", f"www.{domain}", 300, [domain])
163202

203+
def create_rd_pool(client, domain):
204+
"""Create a pool of A records in UltraDNS. This function will create an RD pool within the specified domain.
205+
206+
Args:
207+
- client (RestApiClient): An instance of the RestApiClient class.
208+
- domain (str): The domain name.
209+
210+
Returns:
211+
- dict: The response body.
212+
"""
213+
return client.create_rd_pool(
214+
domain,
215+
"pool",
216+
300,
217+
[
218+
"192.0.2.2",
219+
"192.0.2.3"
220+
]
221+
)
222+
164223
def delete_zone(client, domain):
165224
"""Delete the zone from UltraDNS.
166225

src/ultra_rest_client/about.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "2.2.4"
1+
__version__ = "0.0.0"
22
PREFIX = "udns-python-rest-client-"
33

44
def get_client_user_agent():

src/ultra_rest_client/connection.py

Lines changed: 68 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import requests
1010
import time
1111
from .about import get_client_user_agent
12+
import urllib3
13+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
1214

1315
class AuthError(Exception):
1416
def __init__(self, message):
@@ -30,30 +32,14 @@ class RestApiConnection:
3032
# Don't let users set these headers
3133
FORBIDDEN_HEADERS = {"Authorization", "Content-Type", "Accept"}
3234

33-
def __init__(self, use_http=False, host="api.ultradns.com", access_token: str = "", refresh_token: str = "", custom_headers=None):
35+
def __init__(self, use_http=False, host="api.ultradns.com", access_token: str = "", refresh_token: str = "", custom_headers=None, proxy=None, verify_https=True):
3436
self.use_http = use_http
3537
self.host = host
3638
self.access_token = access_token
3739
self.refresh_token = refresh_token
3840
self.custom_headers = custom_headers or {}
39-
40-
def _validate_custom_headers(self, headers):
41-
"""Ensure no forbidden headers are being set by the user."""
42-
for header in headers.keys():
43-
if header in self.FORBIDDEN_HEADERS:
44-
raise ValueError(f"Custom headers cannot include '{header}'.")
45-
46-
def set_custom_headers(self, headers):
47-
"""Update custom headers after instantiation."""
48-
self._validate_custom_headers(headers)
49-
self.custom_headers.update(headers)
50-
51-
def _get_connection(self):
52-
if self.host.startswith("https://") or self.host.startswith("http://"):
53-
return self.host
54-
else:
55-
protocol = "http://" if self.use_http else "https://"
56-
return protocol + self.host
41+
self.proxy = proxy
42+
self.verify_https = verify_https
5743

5844
# Authentication
5945
# We need the ability to take in a username and password and get
@@ -69,7 +55,12 @@ def auth(self, username, password):
6955
"username":username,
7056
"password":password
7157
}
72-
response = requests.post(f"{host}/v1/authorization/token", data=payload)
58+
response = requests.post(
59+
f"{host}/v1/authorization/token",
60+
data=payload,
61+
proxies=self.proxy,
62+
verify=self.verify_https
63+
)
7364
if response.status_code == requests.codes.OK:
7465
json_body = response.json()
7566
self.access_token = json_body.get('accessToken')
@@ -83,14 +74,27 @@ def _refresh(self):
8374
"grant_type":"refresh_token",
8475
"refresh_token":self.refresh_token
8576
}
86-
response = requests.post(f"{host}/v1/authorization/token", data=payload)
77+
response = requests.post(
78+
f"{host}/v1/authorization/token",
79+
data=payload,
80+
proxies=self.proxy,
81+
verify=self.verify_https
82+
)
8783
if response.status_code == requests.codes.OK:
8884
json_body = response.json()
8985
self.access_token = json_body.get('accessToken')
9086
self.refresh_token = json_body.get('refreshToken')
9187
else:
9288
raise AuthError(response.json())
9389

90+
# Private Utility Methods
91+
92+
def _validate_custom_headers(self, headers):
93+
"""Ensure no forbidden headers are being set by the user."""
94+
for header in headers.keys():
95+
if header in self.FORBIDDEN_HEADERS:
96+
raise ValueError(f"Custom headers cannot include '{header}'.")
97+
9498
def _build_headers(self, content_type):
9599
"""Construct headers by merging default, custom, and per-request headers."""
96100
headers = {
@@ -105,25 +109,14 @@ def _build_headers(self, content_type):
105109

106110
return headers
107111

108-
def get(self, uri, params=None):
109-
params = params or {}
110-
return self._do_call(uri, "GET", params=params)
111-
112-
def post_multi_part(self, uri, files):
113-
#use empty string for content type so we don't set it
114-
return self._do_call(uri, "POST", files=files, content_type=None)
115-
116-
def post(self, uri, json=None):
117-
return self._do_call(uri, "POST", body=json) if json is not None else self._do_call(uri, "POST")
118-
119-
def put(self, uri, json):
120-
return self._do_call(uri, "PUT", body=json)
112+
def _get_connection(self):
113+
if self.host.startswith("https://") or self.host.startswith("http://"):
114+
return self.host
115+
else:
116+
protocol = "http://" if self.use_http else "https://"
117+
return protocol + self.host
121118

122-
def patch(self, uri, json):
123-
return self._do_call(uri, "PATCH", body=json)
124-
125-
def delete(self, uri):
126-
return self._do_call(uri, "DELETE")
119+
# Main Request Method
127120

128121
def _do_call(self, uri, method, params=None, body=None, retry=True, files=None, content_type="application/json"):
129122
host = self._get_connection()
@@ -133,7 +126,9 @@ def _do_call(self, uri, method, params=None, body=None, retry=True, files=None,
133126
params=params,
134127
data=body,
135128
headers=self._build_headers(content_type),
136-
files=files
129+
files=files,
130+
proxies=self.proxy,
131+
verify=self.verify_https
137132
)
138133
if response.status_code == requests.codes.NO_CONTENT:
139134
return {}
@@ -171,3 +166,36 @@ def _do_call(self, uri, method, params=None, body=None, retry=True, files=None,
171166
return self._do_call(uri, method, params, body, False)
172167

173168
return json_body
169+
170+
# Public HTTP Methods
171+
172+
def get(self, uri, params=None):
173+
params = params or {}
174+
return self._do_call(uri, "GET", params=params)
175+
176+
def post_multi_part(self, uri, files):
177+
#use empty string for content type so we don't set it
178+
return self._do_call(uri, "POST", files=files, content_type=None)
179+
180+
def post(self, uri, json=None):
181+
return self._do_call(uri, "POST", body=json) if json is not None else self._do_call(uri, "POST")
182+
183+
def put(self, uri, json):
184+
return self._do_call(uri, "PUT", body=json)
185+
186+
def patch(self, uri, json):
187+
return self._do_call(uri, "PATCH", body=json)
188+
189+
def delete(self, uri):
190+
return self._do_call(uri, "DELETE")
191+
192+
# Public Utility Methods
193+
194+
def set_custom_headers(self, headers):
195+
"""Update custom headers after instantiation."""
196+
self._validate_custom_headers(headers)
197+
self.custom_headers.update(headers)
198+
199+
def set_proxy(self, proxy):
200+
"""Update the proxy configuration."""
201+
self.proxy = proxy

0 commit comments

Comments
 (0)