diff --git a/Py3CX/__init__.py b/Py3CX/__init__.py index eb3c52d..810b5bc 100644 --- a/Py3CX/__init__.py +++ b/Py3CX/__init__.py @@ -99,9 +99,27 @@ class TransactionalObject(APIObject): class Py3CX: class _Call(ReadOnlyObject): def __init__(self, tcx, callid): + """Returns a Py3CX.Call object + + Returns a Py3CX.Call object representing a currently-ongoing or historical call on a 3CX system + + Parameters + --- + tcx: Required, Py3CX object representing an authenticated 3CX API session + callid: Required, integer representing the unique identifier of a given call + """ super().__init__(tcx, 'activeCalls') self.params=callid def refresh(self): + """Initialises or updates the properties of the current Py3CX.Call object + + Loads in and stores the properties of the current Py3CX.Call object by performing a GET request to the 'activeCalls' API endpoint, filtering the resulting list by the Call ID. + Future: Calls not present in the activeCalls API call results should be queried against the call history database + + >>> cl=tcx._Call(tcx, 1234) + >>> cl.state + Dialing + """ self.refresh() self.timestamp=self._result.headers.get('date') res=list(filter(lambda cid: cid['Id'] == self.params, self._result.json()['list'])) @@ -117,11 +135,40 @@ class Py3CX: def __repr__(self): return '' % (self.id, self.state) def hangup(self): + """Terminates the current call + + If the call represented by this object is an ongoing call, drops the call by performing a POST request to 'activeCalls/drop' with the Call ID as the Id parameter in the JSON payload + + >>> cl=tcx._Call(tcx, 1234) + >>> cl.state + Connected + >>> cl.hangup() + >>> cl.state + Terminated + """ + self.refresh() + if self.state is 'Terminated': + return self.tcx.rq.post('activeCalls/drop', params={ 'Id': self.params}) - self.params=None + self.refresh() class _User(TransactionalObject): def __init__(self, parent, params): + """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. + + Parameters + --- + parent: Required, Py3CX object representing an authenticated 3CX API session + params: Required, string representing the extension number of a given user + + >>> user=tcx._User(tcx, '1234') + >>> user.enabled + True + >>> user.extension + + """ super().__init__(parent, 'ExtensionList/set') self.params=params self.load() @@ -146,6 +193,23 @@ class Py3CX: self.cancel() class _Extension(ReadOnlyObject): def __init__(self, parent, params, populate=True): + """Returns a Py3CX.Extension object + + Returns a Py3CX.Extension object representing information about the specified extension number. + + Parameters + --- + parent: Required, Py3CX object representing the authenticated 3CX session + params: Required, string representing the extension number to select + populate: Optional, boolean indicator as to whether the object should be populated during instantiation + + >>> extension=tcx.system.list_extensions[0] + >>> extension + + >>> extension=tcx._Extension(tcx, '1234') + >>> extension.number + '1234' + """ super().__init__(parent, api='ExtensionList') self.params=params if populate: @@ -153,6 +217,15 @@ class Py3CX: def __repr__(self): return '' % self.number def load(self): + """Initialises or updates the Py3CX.Extension object with the latest properties from the server + + Performs a GET request to 'ExtensionList', filtering the response by matches against the specified extension number, populating the object's properties with the filtered results + + >>> extension=tcx._Extension(tcx, '1234', populate=False) + >>> extension.load() + >>> extension.number + '1234' + """ self.refresh(params=self.params) res=self._result self.timestamp=res.headers.get('date') @@ -176,7 +249,18 @@ class Py3CX: pass class Status(object): pass - def __init__(self, tcx): + def __init__(self, tcx : 'Py3CX'): + """Returns a Py3CX.System object + + Returns a Py3CX.System object, which contains functionality for obtaining or acting on system-wide components of a 3CX system + + Parameters + --- + tcx: Required, Py3CX instance considered the parent object + + >>> tcx._PhoneSystem(tcx) + + """ self.tcx=tcx self.calllist=None self.extlist=None @@ -238,6 +322,13 @@ class Py3CX: self.refresh_sysstat() @property def list_extensions(self) -> typing.List['Py3CX._Extension']: + """Returns a list of Py3CX.Extension objects + + Returns a list of Py3CX.Extension objects representing every configured extension on a 3CX phone system + + >>> tcx.system.list_extensions + [, ] + """ if self.extlist is None: self.extlist=ReadOnlyObject(self.tcx, api='ExtensionList') self.extlist.refresh() @@ -259,6 +350,13 @@ class Py3CX: return ret @property def list_calls(self) -> typing.List['Py3CX._Call']: + """Returns a list of Py3CX.Call objects + + Returns a list of Py3CX.Call objects representing all current active calls on the phone system + + >>> tcx.system.list_calls + [] + """ if self.calllist is None: self.calllist=ReadOnlyObject(self.tcx, api='activeCalls') self.calllist.refresh() @@ -276,6 +374,17 @@ class Py3CX: ret.append(this) return ret def __init__(self, uri=None, tls_verify=True): + """Returns a Py3CX class object instance + + Returns an instance of the Py3CX class, which is the primary entrypoint of this module. + + Parameters + --- + uri: Optional HTTP/HTTPS URI to your 3CX FQDN. Do not include the /api/. If not explicitly specified, this can instead be set through environment variables + tls_verify: Optional boolean attribute indicating whether the TLS certificate presented by the 3CX phone system should be validated against internal root trust + + >>> tcx=Py3CX(uri='http://myinstance.3cx.com') + """ self.cnf=Py3CXEnvironmentVars() self._tcxsystem=None if uri is not None: @@ -285,6 +394,14 @@ class Py3CX: def __repr__(self): return '' % (self.cnf.auth_user, self.cnf.uri) def authenticate(self, username=None, password=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. + + >>> tcx.authenticate() + >>> tcx.authenticate(username='admin', password='password') + + """ if username is not None: self.cnf.auth_user=username if password is not None: @@ -296,6 +413,17 @@ class Py3CX: self.rq.sess.headers.update({'x-xsrf-token':rs.cookies['XSRF-TOKEN']}) @property def authenticated(self) -> bool: + """Boolean property noting if the current session is logged-in. + + Returns a boolean value indicating whether the Py3CX instance is authenticated against the configured 3CX server, by performing a GET request to the 'CurrentUser' API endpoint. + The expected response code is HTTP 200. If that expectation is not met, an exception is thrown which this function translates to a boolean value. + + >>> tcx.authenticated + False + >>> tcx.authenticate() + >>> tcx.authenticated + True + """ try: self.rq.get('CurrentUser') except APIError: @@ -303,6 +431,13 @@ class Py3CX: return True @property def system(self) -> 'Py3CX._PhoneSystem': + """Returns a Py3CX.System object + + Returns the current instance of (and initialises a new instance of, if one does not exist yet) the Py3CX System class, which contains functionality for system-wide 3CX APIs + + >>> tcx.system + + """ assert self.authenticated, "Py3CX not authenticated yet!" if self._tcxsystem is None: self._tcxsystem=Py3CX._PhoneSystem(self)