Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conver tot new class system and update to Broadworks R24 #9

Merged
merged 26 commits into from
Mar 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0132a21
feat: convert all base classes from classforge to attrs
nigelm Jan 31, 2022
4e96e82
fix: schema processor now outputs attrs based classes
nigelm Jan 31, 2022
7360a7f
fix: removed dead code
nigelm Jan 31, 2022
6d89b0d
feat: change base classes to be a custom object
nigelm Feb 3, 2022
9ab0066
fix: regenerate the generated modules
nigelm Feb 3, 2022
a6d4d11
fix: class slots do not include attributes of super class
nigelm Feb 5, 2022
b0cc9ab
doc: add type info to attributes lists
nigelm Feb 5, 2022
7fef38e
fix: corrected type annotations
nigelm Feb 5, 2022
89a9446
fix: better type markup
nigelm Feb 7, 2022
47dd765
fix: doc and return type improvements
nigelm Feb 12, 2022
0cc66a6
doc: update of changelog and contributing
nigelm Feb 12, 2022
012f5bf
doc: update of internals documentation
nigelm Feb 12, 2022
a47fa13
fix: process_schema now produces markdown docs
nigelm Feb 12, 2022
2b17390
fix: make black reformatting of generated files selectable
nigelm Feb 12, 2022
4b76a8b
fix: regenerate all the derived files
nigelm Feb 12, 2022
21cbb24
tests: updated nox configuration
nigelm Feb 12, 2022
81ae17e
fix: tweaked tests to avoid spurious mypy error
nigelm Feb 14, 2022
4024d50
fix: tweaked command to_dict() to put session first
nigelm Feb 14, 2022
6797801
fix: add __str__ and __repr__ methods
nigelm Feb 14, 2022
b594669
fix: ensure session_id is put into received objects
nigelm Feb 14, 2022
45c37c8
fix: tweaks to the trivial live test program
nigelm Feb 14, 2022
9cd81d9
test: add tests for previous session_id fix
nigelm Feb 14, 2022
85c1902
fix: work round issues in Broadworks R24 schemas
nigelm Mar 2, 2022
c0c71c3
feat: Broadworks R24 schemas and derived files
nigelm Mar 2, 2022
eac0682
fix : ensure types in OCI-P objects are checked
nigelm Mar 5, 2022
30e4a29
docs: update README info
nigelm Mar 5, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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