Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
fi

- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
ref: feature/openapi-generator-sdk

Expand Down Expand Up @@ -74,7 +74,7 @@ jobs:
fi

- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Install Packages
run: |-
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v5
Expand Down
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,17 @@ pytest
```python
from bandwidth_numbers import Account, Client

# Bearer Auth Client with Token
client = Client(url="https://dashboard.bandwidth.com/api", account_id=123456,
None, None, None, None, None, "access_token", access_token_expiration=123)

# Bearer Auth Client with Client Credentials
client = Client(url="https://dashboard.bandwidth.com/api", account_id=123456,
None, None None, "client_id", "client_secret")

# Basic Auth Client
client = Client(url="https://dashboard.bandwidth.com/api", account_id=123456, username="foo",
password="bar")
password="bar")
```
or
```python
Expand Down
37 changes: 32 additions & 5 deletions bandwidth_numbers/client.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/usr/bin/env python

import time
import requests
from bandwidth_numbers.utils.config import Config
from bandwidth_numbers.utils.rest import RestClient
from bandwidth_numbers.utils.bearer_auth import BearerAuth

class Client(object):

Expand All @@ -12,13 +15,16 @@ def config(self):
return self._config

def __init__(
self, url=None, account_id=None, username=None, password=None,
filename=None):
self, url=None, account_id=None, username=None,
password=None, filename=None, client_id=None,
client_secret=None, access_token=None, access_token_expiration=int(time.time()) + 3600):

if url is None:
url = "https://dashboard.bandwidth.com/api"

self._config = Config(url, account_id, username, password, filename)
self._config = Config(url, account_id, username, password,
filename, client_id, client_secret,
access_token, access_token_expiration)
self._rest = RestClient()

def _get_uri(self, section=None):
Expand All @@ -33,11 +39,32 @@ def _get_uri(self, section=None):
_section

return res

def _refresh_oauth_token(self):
token_url = 'https://api.bandwidth.com/api/v1/oauth2/token'
auth = (self.config.client_id, self.config.client_secret)
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
data = {'grant_type': 'client_credentials'}
response = requests.request('POST', token_url, auth=auth, headers=headers, data=data)
response.raise_for_status()
token_data = response.json()
self.config.access_token = token_data['access_token']
self.config.access_token_expiration = int(time.time()) + token_data.get('expires_in', 3600)
return BearerAuth(self.config.access_token)

def _configure_auth(self):
now = int(time.time())
if self.config.access_token and self.config.access_token_expiration > now + 60:
return BearerAuth(self.config.access_token)
elif self.config.client_id and self.config.client_secret:
return self._refresh_oauth_token()
else:
return (self.config.username, self.config.password)

def _request(self,method,section=None,params=None,data=None,headers=None):
auth = self._configure_auth()
return self._rest.request(
method, url=self._get_uri(section),
auth=(self.config.username, self.config.password),
method, url=self._get_uri(section), auth=auth,
params=params, data=data, headers=headers)

def delete(self, section=None):
Expand Down
10 changes: 10 additions & 0 deletions bandwidth_numbers/utils/bearer_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Simple Bearer Token Auth for Requests

import requests

class BearerAuth(requests.auth.AuthBase):
def __init__(self, token):
self.token = token
def __call__(self, r):
r.headers["authorization"] = "Bearer " + self.token
return r
133 changes: 96 additions & 37 deletions bandwidth_numbers/utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from future import standard_library

import os
import time

from bandwidth_numbers.utils.py_compat import PY_VER_MAJOR

Expand All @@ -18,6 +19,10 @@
VALUE_PASSWORD = "password"
VALUE_URL = "url"
VALUE_USERNAME = "username"
VALUE_CLIENT_ID = "client_id"
VALUE_CLIENT_SECRET = "client_secret"
VALUE_ACCESS_TOKEN = "access_token"
VALUE_ACCESS_TOKEN_EXPIRATION = "access_token_expiration"

class ConfigData(object):

Expand All @@ -29,6 +34,14 @@ def account_id(self):
def account_id(self, account_id):
self._account_id = account_id

@property
def username(self):
return self._username

@username.setter
def username(self, username):
self._username = username

@property
def password(self):
return self._password
Expand All @@ -46,71 +59,117 @@ def url(self, url):
self._url = url

@property
def username(self):
return self._username
def client_id(self):
return self._client_id

@client_id.setter
def client_id(self, client_id):
self._client_id = client_id

@username.setter
def username(self, username):
self._username = username
@property
def client_secret(self):
return self._client_secret

@client_secret.setter
def client_secret(self, client_secret):
self._client_secret = client_secret

@property
def access_token(self):
return self._access_token

@access_token.setter
def access_token(self, access_token):
self._access_token = access_token

@property
def access_token_expiration(self):
return self._access_token_expiration

@access_token_expiration.setter
def access_token_expiration(self, access_token_expiration):
self._access_token_expiration = access_token_expiration

class Config(ConfigData):

"""Connection and auth settings"""

def __init__(
self, url=None, account_id=None, username=None, password=None,
filename=None):
filename=None, client_id=None, client_secret=None,
access_token=None, access_token_expiration=int(time.time()) + 3600):

if filename is None:
self._account_id = account_id
self._username = username
self._password = password
self._url = url
self._username = username
self._client_id = client_id
self._client_secret = client_secret
self._access_token = access_token
self._access_token_expiration = access_token_expiration
else:
self._account_id = None
self._username = None
self._password = None
self._url = None
self._username = None
self._client_id = None
self._client_secret = None
self._access_token = None
self._access_token_expiration = int(time.time()) + 3600
self.load_from_file(filename)

def load_from_file(self, filename=None):

"""
Loads config values from "filename".
"""
Loads config values from "filename".

See the default file for structure.
Configs larger than MAX_FILE_SIZE are skipped.
Leading and trailing whitespace is removed.

Args:
filename: a UTF-8 config file.
"""

# Skip non-existing and huge files

if not os.path.isfile(filename):
raise ValueError("Config file doesn't exist")

See the default file for structure.
Configs larger than MAX_FILE_SIZE are skipped.
Leading and trailing whitespace is removed.
if os.path.getsize(filename) > MAX_FILE_SIZE:
raise ValueError("Config too large")

Args:
filename: a UTF-8 config file.
"""
with open(filename, encoding="UTF-8") as fp:
self._parser = ConfigParser(allow_no_value = True)
if PY_VER_MAJOR == 3:
self._parser.read_file(fp)
else:
self._parser.readfp(fp)

# Skip non-existing and huge files
self._account_id = self._parser.get(
SECTION_ACCOUNT, VALUE_ACCOUNT_ID
)
self._account_id = self._account_id.strip()

if not os.path.isfile(filename):
raise ValueError("Config file doesn't exist")
self._username = self._parser.get(SECTION_ACCOUNT, VALUE_USERNAME, fallback=None)
self._username = self._username.strip() if self._username else None

if os.path.getsize(filename) > MAX_FILE_SIZE:
raise ValueError("Config too large")
self._password = self._parser.get(SECTION_ACCOUNT, VALUE_PASSWORD, fallback=None)
self._password = self._password.strip() if self._password else None

with open(filename, encoding="UTF-8") as fp:
self._parser = ConfigParser(allow_no_value = True)
if PY_VER_MAJOR == 3:
self._parser.read_file(fp)
else:
self._parser.readfp(fp)
self._url = self._parser.get(SECTION_SRV, VALUE_URL, fallback=None)
self._url = self._url.strip() if self._url else None

self._account_id = self._parser.get(
SECTION_ACCOUNT, VALUE_ACCOUNT_ID
)
self._account_id = self._account_id.strip()
self._client_id = self._parser.get(SECTION_ACCOUNT, VALUE_CLIENT_ID, fallback=None)
self._client_id = self._client_id.strip() if self._client_id else None

self._username = self._parser.get(SECTION_ACCOUNT, VALUE_USERNAME)
self._username = self._username.strip()
self._client_secret = self._parser.get(SECTION_ACCOUNT, VALUE_CLIENT_SECRET, fallback=None)
self._client_secret = self._client_secret.strip() if self._client_secret else None

self._password = self._parser.get(SECTION_ACCOUNT, VALUE_PASSWORD)
self._password = self._password.strip()
self._access_token = self._parser.get(SECTION_ACCOUNT, VALUE_ACCESS_TOKEN, fallback=None)
self._access_token = self._access_token.strip() if self._access_token else None

self._url = self._parser.get(SECTION_SRV, VALUE_URL)
self._url = self._url.strip()
self._access_token_expiration = self._parser.get(SECTION_ACCOUNT,
VALUE_ACCESS_TOKEN_EXPIRATION,
fallback=int(time.time()) + 3600)
Loading