--- /dev/null
+Copyright (c) 2019 Graph-IT GmbH
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+# Python Graph API
+
+This is a minimal Python API for the [Graph](https://graph-it.com/).
+It works with Python 2 as well as Python 3.
+
+A more detailed documentation can be found in [doc/index.md](doc/index.md),
+which can also be found at [http://docs.graph-it.com/graphit/graph-client-py](http://docs.graph-it.com/graphit/graph-client-py).
# Python Graph API
-This is a minimal Python API for the Graph.
+This is a minimal Python API for the [Graph](https://graph-it.com/).
It works with Python 2 as well as Python 3.
-## Prerequisites
+## Installation
-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:
+The Python Graph API can be installed with `pip` directly from our git repository:
```sh
-# apt install python-msgpack python3-msgpack
+$ pip install git://git.graph-it.com/graphit/graph-client-py.git
```
-With pip, it can be done by:
-```sh
-# pip install msgpack
-```
+The Graph API uses [msgpack](https://msgpack.org/).
+So, the Python binding for it will be installed as a dependency.
In order to use TLS connections to graphs, a client certificate chain has to
be available in a file.
`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.
+
+## Plans
+
+Up to now, only a raw connection is implemented,
+i.e., the Python implementation does not know anything about the actual API calls and just passes them to the graph server.
+
+We should add implementation and documentation for these actual API calls.
+This would also allow to do at least some of the necessary conversions already in the library.
+++ /dev/null
-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('<L', size)[0]
- mesg = b''
- while len(mesg) < size:
- to_be_read = size - len(mesg)
- mesg += self.__sock.recv(to_be_read)
- return unpackb(
- mesg,
- object_pairs_hook=lambda pair_list: OrderedDict(pair_list),
- raw=True)
-
- def __write(self, mesg):
- mesg = packb(mesg, use_bin_type=False)
- size = pack('<L', len(mesg))
- self.__sock.sendall(size)
- self.__sock.sendall(mesg)
-
- def __encode(self, struct):
- if isinstance(struct, dict):
- return {self.__encode(key): self.__encode(value)
- for key, value in struct.items()}
- elif isinstance(struct, list):
- return [self.__encode(element)
- for element in struct]
- elif isinstance(struct, str):
- return struct.encode()
- else:
- return struct
-
- def __call(self, method, params):
- if self.__cid == 0:
- self.__init()
- self.__cid += 1
- req = {b'jsonrpc': b'2.0',
- b'method': self.__encode(method),
- b'params': self.__encode(params),
- b'id': self.__cid}
- self.__write(req)
- res = self.__read()
- if b'jsonrpc' not in res or res[b'jsonrpc'] != req[b'jsonrpc']:
- raise Exception('Not a JSON-RPC 2.0 response!')
- if b'error' in res:
- err = res[b'error'].decode()
- raise Exception('JSON-RPC: Remote error: {0}'.format(err))
- if b'id' not in res or res[b'id'] != req[b'id']:
- raise Exception('JSON-RPC id missing or invalid')
- return res[b'result']
-
- def __getattr__(self, attr):
- return lambda *args: self.__call(attr, args)
--- /dev/null
+from graph.connection_raw import RawConnection as Connection
+__all__ = ["Connection"]
--- /dev/null
+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 RawConnection:
+ 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('<L', size)[0]
+ mesg = b''
+ while len(mesg) < size:
+ to_be_read = size - len(mesg)
+ mesg += self.__sock.recv(to_be_read)
+ return unpackb(
+ mesg,
+ object_pairs_hook=lambda pair_list: OrderedDict(pair_list),
+ raw=True)
+
+ def __write(self, mesg):
+ mesg = packb(mesg, use_bin_type=False)
+ size = pack('<L', len(mesg))
+ self.__sock.sendall(size)
+ self.__sock.sendall(mesg)
+
+ def __encode(self, struct):
+ if isinstance(struct, dict):
+ return {self.__encode(key): self.__encode(value)
+ for key, value in struct.items()}
+ elif isinstance(struct, list):
+ return [self.__encode(element)
+ for element in struct]
+ elif isinstance(struct, str):
+ return struct.encode()
+ else:
+ return struct
+
+ def __call(self, method, params):
+ if self.__cid == 0:
+ self.__init()
+ self.__cid += 1
+ req = {b'jsonrpc': b'2.0',
+ b'method': self.__encode(method),
+ b'params': self.__encode(params),
+ b'id': self.__cid}
+ self.__write(req)
+ res = self.__read()
+ if b'jsonrpc' not in res or res[b'jsonrpc'] != req[b'jsonrpc']:
+ raise Exception('Not a JSON-RPC 2.0 response!')
+ if b'error' in res:
+ err = res[b'error'].decode()
+ raise Exception('JSON-RPC: Remote error: {0}'.format(err))
+ if b'id' not in res or res[b'id'] != req[b'id']:
+ raise Exception('JSON-RPC id missing or invalid')
+ return res[b'result']
+
+ def __getattr__(self, attr):
+ return lambda *args: self.__call(attr, args)
--- /dev/null
+import setuptools
+
+with open("README.md", "r") as readme_file:
+ long_description = readme_file.read()
+
+setuptools.setup(
+ name="graph-graphit",
+ version="0.0.1",
+ author="Graph-IT GmbH",
+ author_email="info@graph-it.com",
+ description="Python implementation of the Graph API",
+ long_description=long_description,
+ long_description_content_type="text/markdown",
+ url="http://docs.graph-it.com/graphit/graph-client-py",
+ packages=setuptools.find_packages(),
+ install_requires=[
+ "msgpack"
+ ],
+ classifiers=[
+ "Programming Language :: Python",
+ "License :: OSI Approved :: MIT License",
+ "Operating System :: OS Independent",
+ ],
+)