diff --git a/docs/api/mitel_ommclient2.messages.rst b/docs/api/mitel_ommclient2.messages.rst index a762320..1b20a24 100644 --- a/docs/api/mitel_ommclient2.messages.rst +++ b/docs/api/mitel_ommclient2.messages.rst @@ -20,6 +20,14 @@ mitel\_ommclient2.messages.getppdev module :undoc-members: :show-inheritance: +mitel\_ommclient2.messages.getppuser module +------------------------------------------- + +.. automodule:: mitel_ommclient2.messages.getppuser + :members: + :undoc-members: + :show-inheritance: + mitel\_ommclient2.messages.open module -------------------------------------- diff --git a/docs/manual/connection.rst b/docs/manual/connection.rst index 984ebf2..5686bb2 100644 --- a/docs/manual/connection.rst +++ b/docs/manual/connection.rst @@ -15,7 +15,7 @@ to establish a transport to the API. import mitel_ommclient2 - conn = mitel_ommclient2..connection.Connection("omm.local") + conn = mitel_ommclient2.connection.Connection("omm.local") To actually connect to the OMM, you need to call :func:`mitel_ommclient2.connection.Connection.connect`. @@ -45,8 +45,8 @@ You hand over a Request object and receive a response object. .. code-block:: python - >>> request = mitel_ommclient2.messages.Ping() - >>> r = conn.request(request) + >>> m = mitel_ommclient2.messages.Ping() + >>> r = conn.request(m) >>> r.name 'PingResp' diff --git a/docs/manual/messages.rst b/docs/manual/messages.rst index 540ce84..484cd19 100644 --- a/docs/manual/messages.rst +++ b/docs/manual/messages.rst @@ -5,14 +5,13 @@ The API consists of three main message types: request, response and event. They are represented by :class:`mitel_ommclient2.messages.Request`, :class:`mitel_ommclient2.messages.Response` and events aren't supported yet. -There are several subclasses for each messages type, but they are just just overlays -to provide a conveinient interface to message content using attributes. +There are several subclasses for each messages type, which provide a conveinient +interface to message content using attributes. -Each message provides three attributes that define the central interfaces: - - * name - * attrs - * childs +For each message you can access each field directly as class attributes. +There are two special attributes: + * name: returns the message name + * childs: allowes you to access childs by the child name as class attributes Using messages -------------- @@ -27,27 +26,20 @@ and hand it over to :func:`mitel_ommclient2.client.OMMClient2.request` or my_time = int(time.time()) - request = mitel_ommclient2.messages.Ping(timeStamp=my_time) - r = c.request(request) + m = mitel_ommclient2.messages.Ping() + m.timeStamp = my_time + r = c.request(m) ping = r.timeStamp - my_time -Crafting your own messages --------------------------- +A more complex example +---------------------- -It some cases your message class isn't implemented yet. Then you can craft the -message yourself. +This demonstrates how to access message childs. .. code-block:: python - import time - - my_time = int(time.time()) - - request = mitel_ommclient2.messages.Request("Ping") - request.attrs = { - "timeStamp": my_time, - } - r = c.request(request) - - ping = r.attrs["timeStamp"] - my_time + m = messages.GetAccount() + m.id = id + r = self.connection.request(m) + return r.childs.account[0] diff --git a/mitel_ommclient2/client.py b/mitel_ommclient2/client.py index 9d484aa..98972a4 100644 --- a/mitel_ommclient2/client.py +++ b/mitel_ommclient2/client.py @@ -44,7 +44,11 @@ class OMMClient2: self.connection.connect() # Login - r = self.connection.request(messages.Open(self._username, self._password, UserDeviceSyncClient=self._ommsync)) + m = messages.Open() + m.username = self._username + m.password = self._password + m.UserDeviceSyncClient = self._ommsync + r = self.connection.request(m) r.raise_on_error() def get_account(self, id): @@ -54,11 +58,13 @@ class OMMClient2: :param id: User id """ - r = self.connection.request(messages.GetAccount(id)) + m = messages.GetAccount() + m.id = id + r = self.connection.request(m) r.raise_on_error() - if r.account is None: + if r.childs.account is None: return None - return r.account[0] + return r.childs.account[0] def get_device(self, ppn): """ @@ -66,11 +72,14 @@ class OMMClient2: :param ppn: Device id """ - r = self.connection.request(messages.GetPPDev(ppn)) + + m = messages.GetPPDev() + m.ppn = ppn + r = self.connection.request(m) r.raise_on_error() - if r.pp is None: + if r.childs.pp is None: return None - return r.pp[0] + return r.childs.pp[0] def get_devices(self): """ @@ -78,7 +87,10 @@ class OMMClient2: """ next_ppn = 0 while True: - r = self.connection.request(messages.GetPPDev(next_ppn, maxRecords=20)) + m = messages.GetPPDev() + m.ppn = next_ppn + m.maxRecords = 20 + r = self.connection.request(m) try: r.raise_on_error() except exceptions.ENoEnt: @@ -86,7 +98,7 @@ class OMMClient2: break # Output all found devices - for pp in r.pp: + for pp in r.childs.pp: yield pp # Determine next possible ppn @@ -98,11 +110,13 @@ class OMMClient2: :param uid: User id """ - r = self.connection.request(messages.GetPPUser(uid)) + m = messages.GetPPUser() + m.uid = uid + r = self.connection.request(m) r.raise_on_error() - if r.user is None: + if r.childs.user is None: return None - return r.user[0] + return r.childs.user[0] def get_users(self): """ @@ -110,7 +124,10 @@ class OMMClient2: """ next_uid = 0 while True: - r = self.connection.request(messages.GetPPUser(next_uid, maxRecords=20)) + m = messages.GetPPUser() + m.uid = next_uid + m.maxRecords = 20 + r = self.connection.request(m) try: r.raise_on_error() except exceptions.ENoEnt: @@ -118,7 +135,7 @@ class OMMClient2: break # Output all found devices - for user in r.user: + for user in r.childs.user: yield user # Determine next possible ppn diff --git a/mitel_ommclient2/messages/__init__.py b/mitel_ommclient2/messages/__init__.py index 768b4bb..2e7e560 100644 --- a/mitel_ommclient2/messages/__init__.py +++ b/mitel_ommclient2/messages/__init__.py @@ -5,45 +5,98 @@ from xml.dom.minidom import getDOMImplementation, parseString from ..exceptions import exception_classes, OMResponseException -class Request: +class Message: """ - Request message class - - :param name: Name of the message - :param seq: Unique sequence number to associate responses - - Usage:: - >>> req = Request("Ping") - """ - - def __init__(self, name, seq=None): - self.name = name - self.attrs = {} - self.childs = {} - - if seq is not None: - self.attrs["seq"] = seq - - @property - def seq(self): - return self.attrs.get("seq") - - @seq.setter - def seq(self, seq): - self.attrs["seq"] = seq - -class Response: - """ - Response message class + Base message class :param name: Name of the message :param attrs: Message attributes :param childs: Message children """ - def __init__(self, name, attrs={}, childs={}): + + # Fields defined by the base type class + BASE_FIELDS = {} + # Fields defined by subclasses + FIELDS = {} + # Child types + CHILDS = {} + # Fields dicts consist of the field name as name and the field type as value + # Use None if the field type is unknown, any type is allowed then + + + class Childs: + """ + Contains message childs + """ + + CHILDS = {} + + def __init__(self, child_types, child_dict): + self.CHILDS = child_types + self._child_dict = child_dict + + def __getattr__(self, name): + if name in self.CHILDS.keys(): + return self._child_dict.get(name) + else: + raise AttributeError() + + def __setattr__(self, name, value): + if name in self.CHILDS.keys(): + if self.CHILDS[name] is not None and type(value) != self.CHILDS[name]: + raise TypeError() + self._child_dict[name] = value + else: + object.__setattr__(self, name, value) + + + def __init__(self, name=None, attrs={}, childs={}): self.name = name - self.attrs = attrs - self.childs = childs + if not self.name: + self.name = self.__class__.__name__ + self._attrs = attrs + self._childs = childs + self.childs = self.Childs(self.CHILDS, self._childs) + + def __getattr__(self, name): + fields = self.FIELDS | self.BASE_FIELDS + if name in fields.keys(): + return self._attrs.get(name) + else: + raise AttributeError() + + def __setattr__(self, name, value): + fields = self.FIELDS | self.BASE_FIELDS + if name in fields.keys(): + if fields[name] is not None and type(value) != fields[name]: + raise TypeError() + self._attrs[name] = value + else: + object.__setattr__(self, name, value) + + +class Request(Message): + """ + Request message type class + """ + + BASE_FIELDS = { + "seq": int, + } + + +class Response(Message): + """ + Response message type class + """ + + BASE_FIELDS = { + "seq": int, + "errCode": None, + "info": None, + "bad": None, + "maxLen": None, + } def raise_on_error(self): """ @@ -62,25 +115,17 @@ class Response: if self.errCode is not None: raise exception_classes.get(self.errCode, OMResponseException)(response=self) - @property - def seq(self): - return int(self.attrs.get("seq")) - @property - def errCode(self): - return self.attrs.get("errCode") +REQUEST_TYPES = {} +RESPONSE_TYPES = {} - @property - def info(self): - return self.attrs.get("info") +def request_type(c): + REQUEST_TYPES[c.__name__] = c + return c - @property - def bad(self): - return self.attrs.get("bad") - - @property - def maxLen(self): - return self.attrs.get("maxLen") +def response_type(c): + RESPONSE_TYPES[c.__name__] = c + return c from .getaccount import GetAccount, GetAccountResp from .getppdev import GetPPDev, GetPPDevResp @@ -96,11 +141,11 @@ def construct(request): message = impl.createDocument(None, request.name, None) root = message.documentElement - for k, v in request.attrs.items(): + for k, v in request._attrs.items(): root.setAttribute(str(k), str(v)) - for k, v in request.childs.items(): + for k, v in request._childs.items(): child = message.createElement(k) if v is not None: for c_k, c_v in v.items(): @@ -108,18 +153,6 @@ def construct(request): root.appendChild(child) return root.toxml() -def _response_type_by_name(name): - response_types = [ - GetAccountResp, - GetPPDevResp, - GetPPUserResp, - PingResp, - ] - - response_types_dict = {r.__name__: r for r in response_types} - - return response_types_dict.get(name, Response) - def parse(message): message = parseString(message) root = message.documentElement @@ -128,9 +161,15 @@ def parse(message): attrs = {} childs = {} + response_type = RESPONSE_TYPES.get(name) + fields = response_type.FIELDS | response_type.BASE_FIELDS + for i in range(0, root.attributes.length): item = root.attributes.item(i) - attrs[item.name] = item.value + if fields.get(item.name) is not None: + attrs[item.name] = fields[item.name](item.value) + else: + attrs[item.name] = item.value child = root.firstChild while child is not None: @@ -148,4 +187,4 @@ def parse(message): child = child.nextSibling - return _response_type_by_name(name)(name, attrs, childs) + return response_type(name, attrs, childs) diff --git a/mitel_ommclient2/messages/getaccount.py b/mitel_ommclient2/messages/getaccount.py index 9dc6e6f..839e784 100644 --- a/mitel_ommclient2/messages/getaccount.py +++ b/mitel_ommclient2/messages/getaccount.py @@ -1,27 +1,18 @@ #!/usr/bin/env python3 -from . import Request, Response +from . import Request, Response, request_type, response_type +@request_type class GetAccount(Request): - def __init__(self, id, maxRecords=None, **kwargs): - super().__init__("GetAccount", **kwargs) - - self.attrs["id"] = id - - if maxRecords is not None: - self.attrs["maxRecords"] = maxRecords - - @property - def id(self): - return self.attrs.get("id") - - @property - def maxRecords(self): - return self.attrs.get("maxRecords") + FIELDS = { + "id": int, + "maxRecords": int, + } +@response_type class GetAccountResp(Response): - @property - def account(self): - return self.childs.get("account") + CHILDS = { + "account": None, + } diff --git a/mitel_ommclient2/messages/getppdev.py b/mitel_ommclient2/messages/getppdev.py index 5a742a3..322bead 100644 --- a/mitel_ommclient2/messages/getppdev.py +++ b/mitel_ommclient2/messages/getppdev.py @@ -1,27 +1,18 @@ #!/usr/bin/env python3 -from . import Request, Response +from . import Request, Response, request_type, response_type +@request_type class GetPPDev(Request): - def __init__(self, ppn, maxRecords=None, **kwargs): - super().__init__("GetPPDev", **kwargs) - - self.attrs["ppn"] = ppn - - if maxRecords is not None: - self.attrs["maxRecords"] = maxRecords - - @property - def ppn(self): - return self.attrs.get("ppn") - - @property - def maxRecords(self): - return self.attrs.get("maxRecords") + FIELDS = { + "ppn": int, + "maxRecords": int, + } +@response_type class GetPPDevResp(Response): - @property - def pp(self): - return self.childs.get("pp") + CHILDS = { + "pp": None, + } diff --git a/mitel_ommclient2/messages/getppuser.py b/mitel_ommclient2/messages/getppuser.py index e6584da..f06547a 100644 --- a/mitel_ommclient2/messages/getppuser.py +++ b/mitel_ommclient2/messages/getppuser.py @@ -1,27 +1,18 @@ #!/usr/bin/env python3 -from . import Request, Response +from . import Request, Response, request_type, response_type +@request_type class GetPPUser(Request): - def __init__(self, uid, maxRecords=None, **kwargs): - super().__init__("GetPPUser", **kwargs) - - self.attrs["uid"] = uid - - if maxRecords is not None: - self.attrs["maxRecords"] = maxRecords - - @property - def ppn(self): - return self.attrs.get("ppn") - - @property - def maxRecords(self): - return self.attrs.get("maxRecords") + FIELDS = { + "uid": int, + "maxRecords": int, + } +@response_type class GetPPUserResp(Response): - @property - def user(self): - return self.childs.get("user") + CHILDS = { + "user": None, + } diff --git a/mitel_ommclient2/messages/open.py b/mitel_ommclient2/messages/open.py index a992a8e..4ceaec1 100644 --- a/mitel_ommclient2/messages/open.py +++ b/mitel_ommclient2/messages/open.py @@ -1,55 +1,23 @@ #!/usr/bin/env python3 -from . import Request, Response +from . import Request, Response, request_type, response_type +@request_type class Open(Request): - """ - Authenticate Message + FIELDS = { + "username": None, + "password": None, + "UserDeviceSyncClient": None, + } - Needs to be the first message on a new connection. - - :param username: Username - :param password: Password - :param UserDeviceSyncClient: If True login as OMM-Sync client. Some operations in OMM-Sync mode might lead to destroy DECT paring. - """ - def __init__(self, username, password, UserDeviceSyncClient=False, **kwargs): - super().__init__("Open", **kwargs) - - self.attrs["username"] = username - self.attrs["password"] = password - if UserDeviceSyncClient: - self.attrs["UserDeviceSyncClient"] = "true" - - @property - def username(self): - return self.attrs.get("username") - - @property - def password(self): - return self.attrs.get("password") - - @property - def UserDeviceSyncClient(self): - return self.attrs.get("UserDeviceSyncClient") +@response_type class OpenResp(Response): - @property - def protocolVersion(self): - return self.attrs.get("protocolVersion") - - @property - def minPPSwVersion1(self): - return self.attrs.get("minPPSwVersion1") - - @property - def minPPSwVersion2(self): - return self.attrs.get("minPPSwVersion2") - - @property - def ommStbState(self): - return self.attrs.get("ommStbState") - - @property - def publicKey(self): - return self.attrs.get("publicKey") + FIELDS = { + "protocolVersion": None, + "minPPSwVersion1": None, + "minPPSwVersion2": None, + "ommStbState": None, + "publicKey": None, + } diff --git a/mitel_ommclient2/messages/ping.py b/mitel_ommclient2/messages/ping.py index 184db32..f42f5c8 100644 --- a/mitel_ommclient2/messages/ping.py +++ b/mitel_ommclient2/messages/ping.py @@ -1,20 +1,17 @@ #!/usr/bin/env python3 -from . import Request, Response +from . import Request, Response, request_type, response_type +@request_type class Ping(Request): - def __init__(self, timeStamp=None, **kwargs): - super().__init__("Ping", **kwargs) + FIELDS = { + "timeStamp": int, + } - if timeStamp is not None: - self.attrs["timeStamp"] = timeStamp - - @property - def timeStamp(self): - return self.attrs.get("timeStamp") +@response_type class PingResp(Response): - @property - def timeStamp(self): - return self.attrs.get("timeStamp") + FIELDS = { + "timeStamp": int, + }