more inline docs, added some better error handling

This commit is contained in:
Maff 2019-05-23 20:21:13 +01:00
parent c4869200db
commit 03203f6555
1 changed files with 39 additions and 11 deletions

View File

@ -2,6 +2,10 @@ import typing
#This became a class so we can combine envvar initialisation with cred storage #This became a class so we can combine envvar initialisation with cred storage
class Py3CXEnvironmentVars: class Py3CXEnvironmentVars:
"""Class to initialise module configuration from environment variables
Class to initialise module configuration from environment variables, but doubles as configuration storage for explicit credentials
"""
Prefix='TCX' Prefix='TCX'
SiteComponent='SITE' SiteComponent='SITE'
AuthComponent='API_AUTH' AuthComponent='API_AUTH'
@ -18,16 +22,33 @@ class Py3CXEnvironmentVars:
#Base class for exceptions means one can 'except Py3CXException' #Base class for exceptions means one can 'except Py3CXException'
#TODO: should this instead be a subclass of Py3CX? (except Py3CX.Exception?) #TODO: should this instead be a subclass of Py3CX? (except Py3CX.Exception?)
class Py3CXException(Exception): class Py3CXException(Exception):
"""Base Py3CX Exception class"""
pass pass
class APIError(Py3CXException): class APIError(Py3CXException):
"""Exception class used in event of an error being returned by the API"""
pass pass
class ValidationError(Py3CXException): class ValidationError(Py3CXException):
"""Exception class used in event of a validation error internal to the class"""
pass pass
#Request is a basic wrapper around python-requests to remove the need to always specify the API base uri, and to handle basic result validation on behalf of the caller #Request is a basic wrapper around python-requests to remove the need to always specify the API base uri, and to handle basic result validation on behalf of the caller
class Request: class Request:
"""Internal-use wrapper around Python-Requests"""
from requests import Response, ConnectionError, exceptions from requests import Response, ConnectionError, exceptions
def __init__(self, uri, verify): def __init__(self, uri : str, verify : bool):
"""Initialises a Request object
Initialises a Python-Requests Session object with decorative attributes and wrappers
Parameters
---
uri: Required, string representing the FQDN where the API lives
verify: Required, boolean representing whether to verify the TLS trust chain during connections
>>> rq=Request('http://myinstance.3cx.com',False)
>>> rq
<Request [http://myinstance.3cx.com,False]>
"""
from requests import Session from requests import Session
self.uri=uri self.uri=uri
self.base="%s/api/{}" % uri self.base="%s/api/{}" % uri
@ -36,7 +57,7 @@ class Request:
self.sess.verify=verify self.sess.verify=verify
def __repr__(self): def __repr__(self):
return '<Request [%s,v%s]>' % (self.base,self.sess.verify) return '<Request [%s,v%s]>' % (self.base,self.sess.verify)
def _action(self, api, method, params, expect=200) -> 'Response': def _action(self, api : str, method, params, expect=200) -> 'Response':
try: try:
if isinstance(method, str): if isinstance(method, str):
if method is 'GET': if method is 'GET':
@ -55,9 +76,9 @@ class Request:
except ConnectionError as e: except ConnectionError as e:
raise APIError("ConnectionError raised when communicating: %s" % str(e)) raise APIError("ConnectionError raised when communicating: %s" % str(e))
return resp return resp
def get(self, api, params=None, expect=200) -> 'Response': def get(self, api : str, params=None, expect : int =200) -> 'Response':
return self._action(api, self.sess.get, params=params, expect=expect) return self._action(api, self.sess.get, params=params, expect=expect)
def post(self, api, params, expect=200) -> 'Response': def post(self, api : str, params, expect : int =200) -> 'Response':
return self._action(api, self.sess.post, params=params, expect=expect) return self._action(api, self.sess.post, params=params, expect=expect)
#BasicObject is literally just an object - hacky way of making a variable addressable via var.prop rather than var['prop'] #BasicObject is literally just an object - hacky way of making a variable addressable via var.prop rather than var['prop']
@ -97,8 +118,9 @@ class TransactionalObject(APIObject):
#Main logic goes here #Main logic goes here
class Py3CX: class Py3CX:
"""Base Py3CX class, containing all functionality for interacting with a 3CX phone system"""
class _Call(ReadOnlyObject): class _Call(ReadOnlyObject):
def __init__(self, tcx, callid): def __init__(self, tcx : 'Py3CX', callid : int):
"""Returns a Py3CX.Call object """Returns a Py3CX.Call object
Returns a Py3CX.Call object representing a currently-ongoing or historical call on a 3CX system Returns a Py3CX.Call object representing a currently-ongoing or historical call on a 3CX system
@ -153,7 +175,7 @@ class Py3CX:
'Id': self.params}) 'Id': self.params})
self.refresh() self.refresh()
class _User(TransactionalObject): class _User(TransactionalObject):
def __init__(self, parent, params): def __init__(self, parent : 'Py3CX', params : str):
"""Returns a Py3CX.User object """Returns a Py3CX.User object
Returns a Py3CX.User object which represents a given user account (Extension) on a 3CX system, including more advanced attributes. Returns a Py3CX.User object which represents a given user account (Extension) on a 3CX system, including more advanced attributes.
@ -192,7 +214,7 @@ class Py3CX:
self.extension=Py3CX._Extension(self.tcx, self.params) self.extension=Py3CX._Extension(self.tcx, self.params)
self.cancel() self.cancel()
class _Extension(ReadOnlyObject): class _Extension(ReadOnlyObject):
def __init__(self, parent, params, populate=True): def __init__(self, parent : 'Py3CX', params : str, populate : bool =True):
"""Returns a Py3CX.Extension object """Returns a Py3CX.Extension object
Returns a Py3CX.Extension object representing information about the specified extension number. Returns a Py3CX.Extension object representing information about the specified extension number.
@ -373,7 +395,7 @@ class Py3CX:
this.since=call['LastChangeStatus'] this.since=call['LastChangeStatus']
ret.append(this) ret.append(this)
return ret return ret
def __init__(self, uri=None, tls_verify=True): def __init__(self, uri=None, tls_verify : bool =True):
"""Returns a Py3CX class object instance """Returns a Py3CX class object instance
Returns an instance of the Py3CX class, which is the primary entrypoint of this module. Returns an instance of the Py3CX class, which is the primary entrypoint of this module.
@ -389,11 +411,14 @@ class Py3CX:
self._tcxsystem=None self._tcxsystem=None
if uri is not None: if uri is not None:
self.cnf.uri=uri self.cnf.uri=uri
assert (self.cnf.uri is not None and self.cnf.uri.startswith('http')), "No or invalid URI specified, please provide via uri= or by setting environment variable %s" % self.cnf.URI try:
assert (self.cnf.uri is not None and self.cnf.uri.startswith('http')), "No or invalid URI specified, please provide via uri= or by setting environment variable %s" % self.cnf.URI
except AssertionError as e:
raise ValidationError(str(e))
self.rq=Request(uri=self.cnf.uri, verify=tls_verify) self.rq=Request(uri=self.cnf.uri, verify=tls_verify)
def __repr__(self): def __repr__(self):
return '<Py3CX [%s@%s]>' % (self.cnf.auth_user, self.cnf.uri) return '<Py3CX [%s@%s]>' % (self.cnf.auth_user, self.cnf.uri)
def authenticate(self, username=None, password=None): def authenticate(self, username=None, password=None) -> None:
"""Authenticates against the given API, optionally using explicit credentials. """Authenticates against the given API, optionally using explicit credentials.
Performs a POST request to the 'login' endpoint of the current API server, using either credentials gleaned through the execution environment, or explicitly specified during the function invocation. Performs a POST request to the 'login' endpoint of the current API server, using either credentials gleaned through the execution environment, or explicitly specified during the function invocation.
@ -406,7 +431,10 @@ class Py3CX:
self.cnf.auth_user=username self.cnf.auth_user=username
if password is not None: if password is not None:
self.cnf.auth_pwd=password self.cnf.auth_pwd=password
assert (self.cnf.auth_user is not None and self.cnf.auth_pwd is not None), "Authentication information needed. Please pass username= and password= or define environment variables %s and %s" % (self.cnf.AUTH_USER, self.cnf.AUTH_PWD) try:
assert (None not in (self.cnf.auth_user, self.cnf.auth_pwd))
except AssertionError as e:
raise ValidationError(str(e))
rs=self.rq.post('login', params={ rs=self.rq.post('login', params={
'Username': self.cnf.auth_user, 'Username': self.cnf.auth_user,
'Password': self.cnf.auth_pwd}) 'Password': self.cnf.auth_pwd})