diff --git a/Py3CX/__init__.py b/Py3CX/__init__.py index 810b5bc..387908d 100644 --- a/Py3CX/__init__.py +++ b/Py3CX/__init__.py @@ -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 + + """ 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 '' % (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 - 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) def __repr__(self): return '' % (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})