Skip to content

Commit

Permalink
Merge pull request #29 from sbarbett/add-custom-headers
Browse files Browse the repository at this point in the history
Add custom headers, update LICENSE - Issues# 28 & 26 - plus general housekeeping
  • Loading branch information
stevedejong authored Feb 25, 2025
2 parents 0ceca77 + 4031e71 commit 24e8954
Show file tree
Hide file tree
Showing 9 changed files with 230 additions and 34 deletions.
178 changes: 175 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,176 @@
.idea
*.iml
*.egg-info
__pycache__

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/

# Ruff stuff:
.ruff_cache/

# PyPI configuration file
.pypirc
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,21 @@ domain = "udns-python-rest-client-test.com."
print(f"Get metadata for zone {domain}: {client.get_zone_metadata(domain)}")
```

### Custom Headers

Custom headers can be defined when instantiating the client:

```python
from ultra_rest_client import RestApiClient
client = RestApiClient('username', 'password', custom_headers={"foo":"bar", "user-agent":"hello"})
```

Headers can also be modified after instantiation using the `set_custom_headers()` method on the connection:

```python
client.rest_api_connection.set_custom_headers({"boo":"far","user-agent":"goodbye"})
```

### Quick Examples
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.

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

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

## Questions

Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ name = "ultra-rest-client"
dynamic = ["version"]
description = "A sample Python client for communicating with the UltraDNS REST API"
readme = "README.md"
license-files = { paths = ["LICENSE"] }
license-files = ["LICENSE"]
authors = [
{ name = "ultradns", email = "[email protected]" },
]
Expand All @@ -27,9 +27,9 @@ dependencies = [
Homepage = "https://github.com/ultradns/python_rest_api_client"

[tool.hatch.version]
path = "ultra_rest_client/about.py"
path = "./src/ultra_rest_client/about.py"

[tool.hatch.build.targets.sdist]
include = [
"/ultra_rest_client",
"/src/ultra_rest_client",
]
21 changes: 0 additions & 21 deletions setup.py

This file was deleted.

2 changes: 2 additions & 0 deletions src/ultra_rest_client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .ultra_rest_client import RestApiClient
from .connection import RestApiConnection
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION = "0.0.0"
VERSION = "2.2.4"
PREFIX = "udns-python-rest-client-"

def get_client_user_agent():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,26 @@ def __str__(self):


class RestApiConnection:
def __init__(self, use_http=False, host="api.ultradns.com", access_token: str = "", refresh_token: str = ""):
# Don't let users set these headers
FORBIDDEN_HEADERS = {"Authorization", "Content-Type", "Accept"}

def __init__(self, use_http=False, host="api.ultradns.com", access_token: str = "", refresh_token: str = "", custom_headers=None):
self.use_http = use_http
self.host = host
self.access_token = access_token
self.refresh_token = refresh_token
self.custom_headers = custom_headers or {}

def _validate_custom_headers(self, headers):
"""Ensure no forbidden headers are being set by the user."""
for header in headers.keys():
if header in self.FORBIDDEN_HEADERS:
raise ValueError(f"Custom headers cannot include '{header}'.")

def set_custom_headers(self, headers):
"""Update custom headers after instantiation."""
self._validate_custom_headers(headers)
self.custom_headers.update(headers)

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

def _build_headers(self, content_type):
"""Construct headers by merging default, custom, and per-request headers."""
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {self.access_token}",
"User-Agent": get_client_user_agent()
}
if content_type:
headers["Content-Type"] = content_type

headers.update(self.custom_headers)

return headers

def get(self, uri, params=None):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import time

class RestApiClient:
def __init__(self, bu: str, pr: str = None, use_token: bool = False, use_http: bool =False, host: str = "api.ultradns.com"):
def __init__(self, bu: str, pr: str = None, use_token: bool = False, use_http: bool =False, host: str = "api.ultradns.com", custom_headers=None):
"""Initialize a Rest API Client.
Arguments:
Expand All @@ -28,14 +28,24 @@ def __init__(self, bu: str, pr: str = None, use_token: bool = False, use_http: b
if use_token:
self.access_token = bu
self.refresh_token = pr
self.rest_api_connection = RestApiConnection(use_http, host, bu, pr)
self.rest_api_connection = RestApiConnection(
use_http,
host,
bu,
pr,
custom_headers
)
if not self.refresh_token:
print(
"Warning: Passing a Bearer token with no refresh token means the client state will expire after an hour.")
else:
if not pr:
raise ValueError("Password is required when providing a username.")
self.rest_api_connection = RestApiConnection(use_http, host)
self.rest_api_connection = RestApiConnection(
use_http,
host,
custom_headers=custom_headers
)
self.rest_api_connection.auth(bu, pr)

# Zones
Expand Down
1 change: 0 additions & 1 deletion ultra_rest_client/__init__.py

This file was deleted.

0 comments on commit 24e8954

Please sign in to comment.