Skip to content

Commit

Permalink
Merge pull request #9 from nigelm/r24_bare
Browse files Browse the repository at this point in the history
Convert to new class system and update to Broadworks R24
  • Loading branch information
nigelm authored Mar 5, 2022
2 parents 86d9930 + 30e4a29 commit 4a12d47
Show file tree
Hide file tree
Showing 176 changed files with 557,839 additions and 215,806 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Changelog
All notable changes to this project will be documented in this file.
This is now automatically updated by `python-semantic-release`.

The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
Expand Down
32 changes: 14 additions & 18 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,35 +66,31 @@ $ git checkout -b name-of-your-bugfix-or-feature
```
Now you can make your changes locally.

5. When you're done making changes, use pre-commit to do basic checks and ensure formatting
is consitant, and check that your changes pass the tests::
5. If you use updated schemas, or modify `process_schema.py` you must regenerate
the generated python code:
```bash
$ pre-commit run
$ poetry run pytest
$ poetry run make docs # generate local docs for checking
$ make code
```

6. When you're done making changes, use pre-commit to do basic checks and ensure formatting
is consistant, and check that your changes pass the tests::
```bash
$ git add .; pre-commit run
$ make test
$ make servdocs # generate local docs for checking
```
`pre-commit` may need to be installed onto your system.

6. Commit your changes and push your branch to GitHub:
7. Commit your changes and push your branch to GitHub:
```bash
$ git add .
$ git commit -m "Your detailed description of your changes."
$ git push origin name-of-your-bugfix-or-feature
```

7. Submit a pull request through the GitHub website.
8. Submit a pull request through the GitHub website.


# Deploying

A reminder for the maintainers on how to deploy.
Make sure all your changes are committed (including an entry in `CHANGELOG.md`).
Then run:

```bash
$ bump2version patch # possible: major / minor / patch
$ git push
$ git push --tags
```

Travis will then deploy to PyPI if tests pass.
This is now handled by github actions - there is a manual release action.
17 changes: 15 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
- python objects to match all Broadworks schema objects
- API framework to talk to a Broadworks server
- additional magic to handle authentication and sessions
- Based on Broadworks schema R21
- Based on Broadworks schema R24

## Current Version

Expand Down Expand Up @@ -52,9 +52,22 @@ response = api.command("SystemSoftwareVersionGetRequest")
print(response.version)
```

## Version 2

Despite the bump in version number there are no known major incompatibilities
from previous versions. However the underlying class base has been changed
to a vanilla python slots based system - the thinking behind this is in the
API internals documentation. This will change the underlying requirements.

Additionally at the same time I have converted to Broadworks R24 API schema
files as the basis for generating these classes.


## Credits

The class is built using Michael DeHaan's [`ClassForge`](https://classforge.io/) object system.
The class used to be built using Michael DeHaan's [`ClassForge`]
(https://classforge.io/) object system, however from version 2.0.0 it has
been based on vanilla python slotted objects.

Development on the python version was done by
[Nigel Metheringham `<[email protected]>`](https://github.com/nigelm/)
Expand Down
84 changes: 44 additions & 40 deletions broadworks_ocip/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
import socket
import sys
import uuid
from typing import Any
from typing import Dict
from typing import Type

from classforge import Class
from classforge import Field
import attr
from lxml import etree

import broadworks_ocip.base
Expand All @@ -25,7 +27,8 @@
VERBOSE_DEBUG = 9


class BroadworksAPI(Class):
@attr.s(slots=True, kw_only=True)
class BroadworksAPI:
"""
BroadworksAPI - A class encapsulating the Broadworks OCI-P API
Expand All @@ -47,19 +50,21 @@ class BroadworksAPI(Class):
"""

host: str = Field(type=str, required=True, mutable=False)
port: int = Field(type=int, default=2208, mutable=False)
username: str = Field(type=str, required=True, mutable=False)
password: str = Field(type=str, required=True, mutable=False)
logger = Field(type=logging.Logger)
authenticated: bool = Field(type=bool, default=False)
connect_timeout: int = Field(type=int, default=8)
command_timeout: int = Field(type=int, default=30)
socket = Field(type=socket.socket, default=None) # type: socket.socket
session_id: str = Field(type=str)
_despatch_table = Field(type=dict)

def on_init(self):
host: str = attr.ib()
port: int = attr.ib(default=2208)
username: str = attr.ib()
password: str = attr.ib()
logger: logging.Logger = attr.ib(default=None)
authenticated: bool = attr.ib(default=False)
connect_timeout: int = attr.ib(default=8)
command_timeout: int = attr.ib(default=30)
socket = attr.ib(default=None)
session_id: str = attr.ib(default=None)
_despatch_table: Dict[str, Type[broadworks_ocip.base.OCIType]] = attr.ib(
default=None,
)

def __attrs_post_init__(self) -> None:
"""
Initialise the API object.
Expand All @@ -74,7 +79,7 @@ def on_init(self):
self.build_despatch_table()
self.authenticated = False

def build_despatch_table(self):
def build_despatch_table(self) -> None:
"""
Create a despatch table of commands and types used
"""
Expand Down Expand Up @@ -107,7 +112,7 @@ def build_despatch_table(self):
self._despatch_table = despatch_table
self.logger.debug("Built Broadworks despatch table")

def configure_logger(self):
def configure_logger(self) -> None:
"""
Create and configure a logging object
Expand All @@ -121,7 +126,7 @@ def configure_logger(self):
logger.addHandler(console_handler)
self.logger = logger

def get_type_class(self, command: str):
def get_type_class(self, command: str) -> Type[broadworks_ocip.base.OCIType]:
"""
Given a name (Request/Response/Type) name, return a class object for it
Expand All @@ -141,7 +146,7 @@ def get_type_class(self, command: str):
raise e
return cls

def get_type_object(self, command, **kwargs):
def get_type_object(self, command, **kwargs) -> broadworks_ocip.base.OCIType:
"""
Build the OCIType object instance for a type and parameters
Expand All @@ -163,7 +168,7 @@ def get_type_object(self, command, **kwargs):
cmd = cls(**kwargs)
return cmd

def get_command_object(self, command, **kwargs):
def get_command_object(self, command, **kwargs) -> broadworks_ocip.base.OCICommand:
"""
Build the OCICommand object instance for a command and parameter
Expand All @@ -183,9 +188,9 @@ def get_command_object(self, command, **kwargs):
"""
cls = self.get_type_class(command)
cmd = cls(session_id=self.session_id, **kwargs)
return cmd
return cmd # type: ignore

def get_command_xml(self, command, **kwargs):
def get_command_xml(self, command, **kwargs) -> bytes:
"""
Build the XML for a command and parameter
Expand All @@ -202,7 +207,7 @@ def get_command_xml(self, command, **kwargs):
cmd = self.get_command_object(command, **kwargs)
return cmd.build_xml_()

def send_command(self, command, **kwargs):
def send_command(self, command, **kwargs) -> None:
"""
Build the XML for a command and parameter and send it to the server
Expand All @@ -221,12 +226,10 @@ def send_command(self, command, **kwargs):
self.logger.log(VERBOSE_DEBUG, f"SEND: {str(xml)}")
self.socket.sendall(xml + b"\n")

def receive_response(self):
def receive_response(self) -> broadworks_ocip.base.OCICommand:
"""
Wait and receive response XML from server, and decode it
Arguments:
Raises:
OCIErrorResponse: An error was returned from the server
OCIErrorTimeOut: The client timed out waiting for the server
Expand Down Expand Up @@ -255,7 +258,7 @@ def receive_response(self):
self.logger.log(VERBOSE_DEBUG, f"RECV: {str(content)}")
return self.decode_xml(content)

def decode_xml(self, xml):
def decode_xml(self, xml) -> broadworks_ocip.base.OCICommand:
"""
Decode XML into an OCICommand based object instance
Expand All @@ -270,26 +273,27 @@ def decode_xml(self, xml):
Class instance object
"""
root = etree.fromstring(xml)
extras: Dict[str, Any] = {}
if root.tag != "{C}BroadsoftDocument":
raise ValueError
self.logger.debug("Decoding BroadsoftDocument")
for element in root:
if element.tag == "command":
if element.tag == "sessionId":
extras["session_id"] = element.text
elif element.tag == "command":
command = element.get("{http://www.w3.org/2001/XMLSchema-instance}type")
self.logger.debug(f"Decoding command {command}")
cls = self._despatch_table[command]
result = cls.build_from_etree_(element)
result = cls.build_from_etree_(element, extras)
self.logger.info(f"<<< {result.type_}")
result.post_xml_decode_()
return result
raise OCIErrorUnknown(message="Unknown XML decode", object=root)

def connect(self):
def connect(self) -> None:
"""
Open the connection to the OCI-P server
Arguments:
Raises:
IOError: Communications failure
Expand All @@ -312,12 +316,10 @@ def connect(self):
self.logger.error("Connection timed out")
raise e

def authenticate(self):
def authenticate(self) -> broadworks_ocip.base.OCICommand:
"""
Authenticate the connection to the OCI-P server
Arguments:
Raises:
OCIErrorResponse: An error was returned from the server
Expand All @@ -328,7 +330,9 @@ def authenticate(self):
resp = self.receive_response()
authhash = hashlib.sha1(self.password.encode()).hexdigest().lower()
signed_password = (
hashlib.md5(":".join([resp.nonce, authhash]).encode()).hexdigest().lower()
hashlib.md5(":".join([resp.nonce, authhash]).encode()) # type: ignore
.hexdigest()
.lower()
)
self.send_command(
"LoginRequest14sp4",
Expand All @@ -342,7 +346,7 @@ def authenticate(self):
self.authenticated = True
return resp

def command(self, command, **kwargs):
def command(self, command, **kwargs) -> broadworks_ocip.base.OCICommand:
"""
Send a command and parameters to the server, receive and decode a response
Expand All @@ -365,7 +369,7 @@ def command(self, command, **kwargs):
self.send_command(command, **kwargs)
return self.receive_response()

def close(self, no_log=False):
def close(self, no_log=False) -> None:
"""
Close the connection to the OCI-P server
"""
Expand All @@ -387,7 +391,7 @@ def close(self, no_log=False):
self.logger.info(f"Disconnected from host={self.host} port={self.port}")
self.socket = None

def __del__(self):
def __del__(self) -> None:
self.close(no_log=True)


Expand Down
Loading

0 comments on commit 4a12d47

Please sign in to comment.