Skip to content

Commit 24e8954

Browse files
authored
Merge pull request #29 from sbarbett/add-custom-headers
Add custom headers, update LICENSE - Issues# 28 & 26 - plus general housekeeping
2 parents 0ceca77 + 4031e71 commit 24e8954

File tree

9 files changed

+230
-34
lines changed

9 files changed

+230
-34
lines changed

.gitignore

Lines changed: 175 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,176 @@
1-
.idea
21
*.iml
3-
*.egg-info
4-
__pycache__
2+
3+
# Byte-compiled / optimized / DLL files
4+
__pycache__/
5+
*.py[cod]
6+
*$py.class
7+
8+
# C extensions
9+
*.so
10+
11+
# Distribution / packaging
12+
.Python
13+
build/
14+
develop-eggs/
15+
dist/
16+
downloads/
17+
eggs/
18+
.eggs/
19+
lib/
20+
lib64/
21+
parts/
22+
sdist/
23+
var/
24+
wheels/
25+
share/python-wheels/
26+
*.egg-info/
27+
.installed.cfg
28+
*.egg
29+
MANIFEST
30+
31+
# PyInstaller
32+
# Usually these files are written by a python script from a template
33+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
34+
*.manifest
35+
*.spec
36+
37+
# Installer logs
38+
pip-log.txt
39+
pip-delete-this-directory.txt
40+
41+
# Unit test / coverage reports
42+
htmlcov/
43+
.tox/
44+
.nox/
45+
.coverage
46+
.coverage.*
47+
.cache
48+
nosetests.xml
49+
coverage.xml
50+
*.cover
51+
*.py,cover
52+
.hypothesis/
53+
.pytest_cache/
54+
cover/
55+
56+
# Translations
57+
*.mo
58+
*.pot
59+
60+
# Django stuff:
61+
*.log
62+
local_settings.py
63+
db.sqlite3
64+
db.sqlite3-journal
65+
66+
# Flask stuff:
67+
instance/
68+
.webassets-cache
69+
70+
# Scrapy stuff:
71+
.scrapy
72+
73+
# Sphinx documentation
74+
docs/_build/
75+
76+
# PyBuilder
77+
.pybuilder/
78+
target/
79+
80+
# Jupyter Notebook
81+
.ipynb_checkpoints
82+
83+
# IPython
84+
profile_default/
85+
ipython_config.py
86+
87+
# pyenv
88+
# For a library or package, you might want to ignore these files since the code is
89+
# intended to run in multiple environments; otherwise, check them in:
90+
# .python-version
91+
92+
# pipenv
93+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
95+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
96+
# install all needed dependencies.
97+
#Pipfile.lock
98+
99+
# UV
100+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
101+
# This is especially recommended for binary packages to ensure reproducibility, and is more
102+
# commonly ignored for libraries.
103+
#uv.lock
104+
105+
# poetry
106+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
107+
# This is especially recommended for binary packages to ensure reproducibility, and is more
108+
# commonly ignored for libraries.
109+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
110+
#poetry.lock
111+
112+
# pdm
113+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
114+
#pdm.lock
115+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
116+
# in version control.
117+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
118+
.pdm.toml
119+
.pdm-python
120+
.pdm-build/
121+
122+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
123+
__pypackages__/
124+
125+
# Celery stuff
126+
celerybeat-schedule
127+
celerybeat.pid
128+
129+
# SageMath parsed files
130+
*.sage.py
131+
132+
# Environments
133+
.env
134+
.venv
135+
env/
136+
venv/
137+
ENV/
138+
env.bak/
139+
venv.bak/
140+
141+
# Spyder project settings
142+
.spyderproject
143+
.spyproject
144+
145+
# Rope project settings
146+
.ropeproject
147+
148+
# mkdocs documentation
149+
/site
150+
151+
# mypy
152+
.mypy_cache/
153+
.dmypy.json
154+
dmypy.json
155+
156+
# Pyre type checker
157+
.pyre/
158+
159+
# pytype static type analyzer
160+
.pytype/
161+
162+
# Cython debug symbols
163+
cython_debug/
164+
165+
# PyCharm
166+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
167+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
168+
# and can be added to the global gitignore or merged into this file. For a more nuclear
169+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
170+
.idea/
171+
172+
# Ruff stuff:
173+
.ruff_cache/
174+
175+
# PyPI configuration file
176+
.pypirc

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,21 @@ domain = "udns-python-rest-client-test.com."
100100
print(f"Get metadata for zone {domain}: {client.get_zone_metadata(domain)}")
101101
```
102102

103+
### Custom Headers
104+
105+
Custom headers can be defined when instantiating the client:
106+
107+
```python
108+
from ultra_rest_client import RestApiClient
109+
client = RestApiClient('username', 'password', custom_headers={"foo":"bar", "user-agent":"hello"})
110+
```
111+
112+
Headers can also be modified after instantiation using the `set_custom_headers()` method on the connection:
113+
114+
```python
115+
client.rest_api_connection.set_custom_headers({"boo":"far","user-agent":"goodbye"})
116+
```
117+
103118
### Quick Examples
104119
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.
105120

@@ -208,7 +223,7 @@ For detailed API reference, please refer to the UltraDNS API documentation.
208223
Contributions are always welcome! Please open a pull request with your changes, or open an issue if you encounter any problems or have suggestions.
209224

210225
## License
211-
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
226+
This project is licensed under the Apache-2.0 license License. See the [LICENSE](LICENSE) file for more details.
212227

213228
## Questions
214229

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ name = "ultra-rest-client"
77
dynamic = ["version"]
88
description = "A sample Python client for communicating with the UltraDNS REST API"
99
readme = "README.md"
10-
license-files = { paths = ["LICENSE"] }
10+
license-files = ["LICENSE"]
1111
authors = [
1212
{ name = "ultradns", email = "[email protected]" },
1313
]
@@ -27,9 +27,9 @@ dependencies = [
2727
Homepage = "https://github.com/ultradns/python_rest_api_client"
2828

2929
[tool.hatch.version]
30-
path = "ultra_rest_client/about.py"
30+
path = "./src/ultra_rest_client/about.py"
3131

3232
[tool.hatch.build.targets.sdist]
3333
include = [
34-
"/ultra_rest_client",
34+
"/src/ultra_rest_client",
3535
]

setup.py

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

src/ultra_rest_client/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .ultra_rest_client import RestApiClient
2+
from .connection import RestApiConnection

ultra_rest_client/about.py renamed to 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 = "0.0.0"
1+
VERSION = "2.2.4"
22
PREFIX = "udns-python-rest-client-"
33

44
def get_client_user_agent():

ultra_rest_client/connection.py renamed to src/ultra_rest_client/connection.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,26 @@ def __str__(self):
2727

2828

2929
class RestApiConnection:
30-
def __init__(self, use_http=False, host="api.ultradns.com", access_token: str = "", refresh_token: str = ""):
30+
# Don't let users set these headers
31+
FORBIDDEN_HEADERS = {"Authorization", "Content-Type", "Accept"}
32+
33+
def __init__(self, use_http=False, host="api.ultradns.com", access_token: str = "", refresh_token: str = "", custom_headers=None):
3134
self.use_http = use_http
3235
self.host = host
3336
self.access_token = access_token
3437
self.refresh_token = refresh_token
38+
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)
3550

3651
def _get_connection(self):
3752
if self.host.startswith("https://") or self.host.startswith("http://"):
@@ -77,13 +92,17 @@ def _refresh(self):
7792
raise AuthError(response.json())
7893

7994
def _build_headers(self, content_type):
95+
"""Construct headers by merging default, custom, and per-request headers."""
8096
headers = {
8197
"Accept": "application/json",
8298
"Authorization": f"Bearer {self.access_token}",
8399
"User-Agent": get_client_user_agent()
84100
}
85101
if content_type:
86102
headers["Content-Type"] = content_type
103+
104+
headers.update(self.custom_headers)
105+
87106
return headers
88107

89108
def get(self, uri, params=None):

ultra_rest_client/ultra_rest_client.py renamed to src/ultra_rest_client/ultra_rest_client.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import time
1010

1111
class RestApiClient:
12-
def __init__(self, bu: str, pr: str = None, use_token: bool = False, use_http: bool =False, host: str = "api.ultradns.com"):
12+
def __init__(self, bu: str, pr: str = None, use_token: bool = False, use_http: bool =False, host: str = "api.ultradns.com", custom_headers=None):
1313
"""Initialize a Rest API Client.
1414
1515
Arguments:
@@ -28,14 +28,24 @@ def __init__(self, bu: str, pr: str = None, use_token: bool = False, use_http: b
2828
if use_token:
2929
self.access_token = bu
3030
self.refresh_token = pr
31-
self.rest_api_connection = RestApiConnection(use_http, host, bu, pr)
31+
self.rest_api_connection = RestApiConnection(
32+
use_http,
33+
host,
34+
bu,
35+
pr,
36+
custom_headers
37+
)
3238
if not self.refresh_token:
3339
print(
3440
"Warning: Passing a Bearer token with no refresh token means the client state will expire after an hour.")
3541
else:
3642
if not pr:
3743
raise ValueError("Password is required when providing a username.")
38-
self.rest_api_connection = RestApiConnection(use_http, host)
44+
self.rest_api_connection = RestApiConnection(
45+
use_http,
46+
host,
47+
custom_headers=custom_headers
48+
)
3949
self.rest_api_connection.auth(bu, pr)
4050

4151
# Zones

ultra_rest_client/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)