From 9c4db2fdfc062654a762e8416d0ee759ed200c85 Mon Sep 17 00:00:00 2001 From: Benjamin Braatz Date: Thu, 11 Jul 2019 12:49:20 +0200 Subject: [PATCH 1/1] Initial commit of minimal Graph API for Python --- .gitignore | 3 ++ config.json.example | 5 +++ doc/index.md | 60 ++++++++++++++++++++++++++++++ graph.py | 90 +++++++++++++++++++++++++++++++++++++++++++++ test.py | 55 +++++++++++++++++++++++++++ 5 files changed, 213 insertions(+) create mode 100644 .gitignore create mode 100644 config.json.example create mode 100644 doc/index.md create mode 100644 graph.py create mode 100755 test.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e0bb05d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +graph.crt +config.json +*-briefbogen.pdf diff --git a/config.json.example b/config.json.example new file mode 100644 index 0000000..18a60f3 --- /dev/null +++ b/config.json.example @@ -0,0 +1,5 @@ +{ + "name": "graph.example.com", + "unix_url": "unix:///var/lib/graph/var/run/graphserver.sock", + "tls_url": "tls://graph.example.com:4339" +} diff --git a/doc/index.md b/doc/index.md new file mode 100644 index 0000000..a565635 --- /dev/null +++ b/doc/index.md @@ -0,0 +1,60 @@ +# Python Graph API + +This is a minimal Python API for the Graph. +It works with Python 2 as well as Python 3. + +## Prerequisites + +The Graph API uses [msgpack](https://msgpack.org/). +So, the Python binding for it has to be installed. + +For Debian-based distributions these can be done by: +```sh +# apt install python-msgpack python3-msgpack +``` + +With pip, it can be done by: +```sh +# pip install msgpack +``` + +In order to use TLS connections to graphs, a client certificate chain has to +be available in a file. + +## Usage + +A graph connection is initialised by giving a URI and optionally the filename +of the certificate chain to the constructor: +```python +>>> import graph +>>> gc = graph.Connection('tls://graph.de.screwerk.com:4437', 'graph.crt') +``` + +Then all functions of the Graph API can be called on the connection object: +```python +>>> graphmodul_guid = gc.attributsknoten('graphmodul_name', 'graph') +>>> knotens = gc.listeattribute(graphmodul_guid, 'knoten', 'typ, dokumentation, anzahlattribute', 'typ asc') +``` + +Observe that all string data returned by the API are bytestrings (even the +keys in `*attribute` results) and have to be decoded to be used as Python +strings: +```python +>>> for knoten in knotens.values(): +... print("{}: {} ({})".format(knoten[b'typ'].decode(), knoten[b'dokumentation'].decode(), knoten[b'anzahlattribute'].decode())) +... +aktion:

Auszuführende Aktion

+ (5) +aktionfunktion:

Auszuführende Funktion, die an einem Knoten hinterlegt werden und von Änderung an lokalen Attributen getriggert werden kann.

+ (22) +attribut:

Allgemeines Merkmal

+ (16) +[…] +``` + +## Test script + +The `test.py` script reads the configuration of a main graph from +`config.json`, queries this main graph for remote graphs configured in it, +prints a summary of all graph modules in the main graph and all remote graphs +and downloads stationeries in all the graphs if they are configured there. diff --git a/graph.py b/graph.py new file mode 100644 index 0000000..4f03051 --- /dev/null +++ b/graph.py @@ -0,0 +1,90 @@ +from collections import OrderedDict +from socket import socket, AF_UNIX, AF_INET, SOCK_STREAM +from ssl import SSLContext, PROTOCOL_TLSv1 +from struct import pack, unpack +try: + from urllib.parse import urlparse +except ImportError: + from urlparse import urlparse + +from msgpack import packb, unpackb + + +class Connection: + def __init__(self, url, crt_filename=''): + self.__url = url + self.__crt_filename = crt_filename + self.__cid = 0 + self.__sock = False + + def __del__(self): + if self.__sock: + self.__sock.close() + + def __init(self): + res = urlparse(self.__url) + if res.scheme == 'unix': + self.__sock = socket(AF_UNIX, SOCK_STREAM) + self.__sock.connect(res.path) + if res.scheme == 'tcp': + self.__sock = socket(AF_INET, SOCK_STREAM) + self.__sock.connect((res.hostname, res.port)) + if res.scheme == 'tls': + ssl_ctx = SSLContext(PROTOCOL_TLSv1) + ssl_ctx.load_cert_chain(self.__crt_filename) + self.__sock = socket(AF_INET, SOCK_STREAM) + self.__sock = ssl_ctx.wrap_socket(self.__sock) + self.__sock.connect((res.hostname, res.port)) + self.__read() + + def __read(self): + size = self.__sock.recv(4) + size = unpack('