patternpythonMinor
Python wrapper class around HTTP API
Viewed 0 times
aroundapiwrapperhttppythonclass
Problem
I wrote this little class to wrap around a work-in-progress, poorly documented API and I'd like to know what else can be improved. I feel like there's a lot of repetition in the functions and I'd love to know if I could turn all those functions (which do the same thing, just setting a different
```
import json
import requests
BASE_URL = "https://chaos.aa.net.uk/"
class Chaos(object):
"""This class allows access to the Andrews & Arnold API.
Note that it is based on trial and error, there is very little
official documentation about this API yet, so use at your own risk.
"""
def __init__(self, username, password):
"""Initialize the class.
Use the same credentials as on control.aa.net.uk.
Args:
username: username like xx00@x
password: self-explanatory
"""
self.session = requests.session()
self.session.headers["User-Agent"] = "Python Chaos Client"
self.session.auth = (username, password)
def _request(self, **kwargs):
"""Make an API request, lets Requests check the HTTP status code
then checks if the "error" string is present in the response
and raises an exception if that's the case.
Args:
**kwargs: will be passed as-is to python-requests
Returns:
a dict representation of the APi'S JSON reply
Raises:
Exception: the remote server returned an error
"""
resp = self.session.post(BASE_URL, **kwargs)
if resp.status_code != requests.codes.ok:
resp.raise_for_status()
resp = resp.json()
if "error" in resp:
raise APIError(resp["error"])
return resp
def info(self, **kwargs):
return self._request(json={kwargs, {"command": "info"}})
def change(self, **kwargs):
required = ["broadband", "sim", "voip"]
if not any(arg in required for
command in the request object) into just one.```
import json
import requests
BASE_URL = "https://chaos.aa.net.uk/"
class Chaos(object):
"""This class allows access to the Andrews & Arnold API.
Note that it is based on trial and error, there is very little
official documentation about this API yet, so use at your own risk.
"""
def __init__(self, username, password):
"""Initialize the class.
Use the same credentials as on control.aa.net.uk.
Args:
username: username like xx00@x
password: self-explanatory
"""
self.session = requests.session()
self.session.headers["User-Agent"] = "Python Chaos Client"
self.session.auth = (username, password)
def _request(self, **kwargs):
"""Make an API request, lets Requests check the HTTP status code
then checks if the "error" string is present in the response
and raises an exception if that's the case.
Args:
**kwargs: will be passed as-is to python-requests
Returns:
a dict representation of the APi'S JSON reply
Raises:
Exception: the remote server returned an error
"""
resp = self.session.post(BASE_URL, **kwargs)
if resp.status_code != requests.codes.ok:
resp.raise_for_status()
resp = resp.json()
if "error" in resp:
raise APIError(resp["error"])
return resp
def info(self, **kwargs):
return self._request(json={kwargs, {"command": "info"}})
def change(self, **kwargs):
required = ["broadband", "sim", "voip"]
if not any(arg in required for
Solution
You're right, you can cut down on the repetition of code. Although bundling all of the functions into one like you mention would work, you lose a bit of the readability that keeping them as class methods gives. Instead, you could just have an internal
Note that I've corrected a syntax error in the last line: your parameter used to read
This makes your Chaos class a lot shorter:
Since
As a side note, this code only checks that the minimum set of arguments is given. Depending on your API, it might be a problem if you give extra arguments that aren't required or supported.
_command method that collects all of the repeated code:def _command(self, command_name, required, **kwargs):
if not any(arg in required for arg in kwargs):
raise InvalidParameters("Missing object of types: " + ", ".join(required))
return self._request(json={kwargs, {"command": command_name }})Note that I've corrected a syntax error in the last line: your parameter used to read
json={kwargs, {"command": command_name }}.This makes your Chaos class a lot shorter:
class Chaos(object):
"""This class allows access to the Andrews & Arnold API.
Note that it is based on trial and error, there is very little
official documentation about this API yet, so use at your own risk.
"""
def __init__(self, username, password):
"""Initialize the class.
Use the same credentials as on control.aa.net.uk.
Args:
username: username like xx00@x
password: self-explanatory
"""
self.session = requests.session()
self.session.headers["User-Agent"] = "Python Chaos Client"
self.session.auth = (username, password)
def _request(self, **kwargs):
"""Make an API request, lets Requests check the HTTP status code
then checks if the "error" string is present in the response
and raises an exception if that's the case.
Args:
**kwargs: will be passed as-is to python-requests
Returns:
a dict representation of the APi'S JSON reply
Raises:
Exception: the remote server returned an error
"""
resp = self.session.post(BASE_URL, **kwargs)
if resp.status_code != requests.codes.ok:
resp.raise_for_status()
resp = resp.json()
if "error" in resp:
raise APIError(resp["error"])
return resp
def _command(self, command_name, required, **kwargs):
"""Make an API request, checking that the arguments are valid
Args:
command_name: the command name to be passed to the API
required: a list of names of required arguments
**kwargs: will be passed as-is to python-requests
Returns:
a dict representation of the APi'S JSON reply
Raises:
InvalidParameters: the wrong set of arguments was given
"""
if not any(arg in required for arg in kwargs):
raise InvalidParameters("Missing object of types: " + ", ".join(required))
return self._request(json={kwargs, {"command": command_name }})
def info(self, **kwargs):
return self._command('info', [], **kwargs)
def change(self, **kwargs):
return self._command('change', ["broadband", "sim", "voip"], **kwargs)
def check(self, **kwargs):
return self._command('check', ["order"], **kwargs)
def preorder(self, **kwargs):
return self._command('preorder', ["order"], **kwargs)
def order(self, **kwargs):
return self._command('order', ["order"], **kwargs)
def usage(self, **kwargs):
return self._command('usage', ["broadband", "sim", "voip"], **kwargs)
def availability(self, **kwargs):
return self._command('usage', ["broadband"], **kwargs)Since
_request only ever gets called by _command you could compress this further by making them one function, but it's fine as is. As a side note, this code only checks that the minimum set of arguments is given. Depending on your API, it might be a problem if you give extra arguments that aren't required or supported.
Code Snippets
def _command(self, command_name, required, **kwargs):
if not any(arg in required for arg in kwargs):
raise InvalidParameters("Missing object of types: " + ", ".join(required))
return self._request(json={kwargs, {"command": command_name }})class Chaos(object):
"""This class allows access to the Andrews & Arnold API.
Note that it is based on trial and error, there is very little
official documentation about this API yet, so use at your own risk.
"""
def __init__(self, username, password):
"""Initialize the class.
Use the same credentials as on control.aa.net.uk.
Args:
username: username like xx00@x
password: self-explanatory
"""
self.session = requests.session()
self.session.headers["User-Agent"] = "Python Chaos Client"
self.session.auth = (username, password)
def _request(self, **kwargs):
"""Make an API request, lets Requests check the HTTP status code
then checks if the "error" string is present in the response
and raises an exception if that's the case.
Args:
**kwargs: will be passed as-is to python-requests
Returns:
a dict representation of the APi'S JSON reply
Raises:
Exception: the remote server returned an error
"""
resp = self.session.post(BASE_URL, **kwargs)
if resp.status_code != requests.codes.ok:
resp.raise_for_status()
resp = resp.json()
if "error" in resp:
raise APIError(resp["error"])
return resp
def _command(self, command_name, required, **kwargs):
"""Make an API request, checking that the arguments are valid
Args:
command_name: the command name to be passed to the API
required: a list of names of required arguments
**kwargs: will be passed as-is to python-requests
Returns:
a dict representation of the APi'S JSON reply
Raises:
InvalidParameters: the wrong set of arguments was given
"""
if not any(arg in required for arg in kwargs):
raise InvalidParameters("Missing object of types: " + ", ".join(required))
return self._request(json={kwargs, {"command": command_name }})
def info(self, **kwargs):
return self._command('info', [], **kwargs)
def change(self, **kwargs):
return self._command('change', ["broadband", "sim", "voip"], **kwargs)
def check(self, **kwargs):
return self._command('check', ["order"], **kwargs)
def preorder(self, **kwargs):
return self._command('preorder', ["order"], **kwargs)
def order(self, **kwargs):
return self._command('order', ["order"], **kwargs)
def usage(self, **kwargs):
return self._command('usage', ["broadband", "sim", "voip"], **kwargs)
def availability(self, **kwargs):
return self._command('usage', ["broadband"], **kwargs)Context
StackExchange Code Review Q#138879, answer score: 5
Revisions (0)
No revisions yet.