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
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'
SiteComponent='SITE'
AuthComponent='API_AUTH'
@ -18,16 +22,33 @@ class Py3CXEnvironmentVars:
#Base class for exceptions means one can 'except Py3CXException'
#TODO: should this instead be a subclass of Py3CX? (except Py3CX.Exception?)
class Py3CXException(Exception):
"""Base Py3CX Exception class"""
pass
class APIError(Py3CXException):
"""Exception class used in event of an error being returned by the API"""
pass
class ValidationError(Py3CXException):
"""Exception class used in event of a validation error internal to the class"""
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
class Request:
"""Internal-use wrapper around Python-Requests"""
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
self.uri=uri
self.base="%s/api/{}" % uri
@ -36,7 +57,7 @@ class Request:
self.sess.verify=verify
def __repr__(self):
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:
if isinstance(method, str):
if method is 'GET':
@ -55,9 +76,9 @@ class Request:
except ConnectionError as e:
raise APIError("ConnectionError raised when communicating: %s" % str(e))
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)
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)
#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
class Py3CX:
"""Base Py3CX class, containing all functionality for interacting with a 3CX phone system"""
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 representing a currently-ongoing or historical call on a 3CX system
@ -153,7 +175,7 @@ class Py3CX:
'Id': self.params})
self.refresh()
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 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.cancel()
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 representing information about the specified extension number.
@ -373,7 +395,7 @@ class Py3CX:
this.since=call['LastChangeStatus']
ret.append(this)
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 an instance of the Py3CX class, which is the primary entrypoint of this module.
@ -389,11 +411,14 @@ class Py3CX:
self._tcxsystem=None
if uri is not None:
self.cnf.uri=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)
def __repr__(self):
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.
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
if password is not None:
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={
'Username': self.cnf.auth_user,
'Password': self.cnf.auth_pwd})