Add infrastructure to make project installable by pip.
authorBenjamin Braatz <benjamin.braatz@graph-it.com>
Mon, 15 Jul 2019 09:53:57 +0000 (11:53 +0200)
committerBenjamin Braatz <benjamin.braatz@graph-it.com>
Mon, 15 Jul 2019 09:53:57 +0000 (11:53 +0200)
LICENSE [new file with mode: 0644]
README.md [new file with mode: 0644]
doc/index.md
graph.py [deleted file]
graph/__init__.py [new file with mode: 0644]
graph/connection_raw.py [new file with mode: 0644]
setup.py [new file with mode: 0644]

diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..ea428a2
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,19 @@
+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.
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..4588af3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+# 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).
index feedf617c949919583cf2ba794d8cfdb4544a992..0a7977116687846352f7f93a0b125dd8899bacfd 100644 (file)
@@ -1,22 +1,17 @@
 # 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.
@@ -58,3 +53,11 @@ 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.
+
+## 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.
diff --git a/graph.py b/graph.py
deleted file mode 100644 (file)
index 4f03051..0000000
--- a/graph.py
+++ /dev/null
@@ -1,90 +0,0 @@
-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)
diff --git a/graph/__init__.py b/graph/__init__.py
new file mode 100644 (file)
index 0000000..f5b6299
--- /dev/null
@@ -0,0 +1,2 @@
+from graph.connection_raw import RawConnection as Connection
+__all__ = ["Connection"]
diff --git a/graph/connection_raw.py b/graph/connection_raw.py
new file mode 100644 (file)
index 0000000..4349844
--- /dev/null
@@ -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 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)
diff --git a/setup.py b/setup.py
new file mode 100644 (file)
index 0000000..f5abc08
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,24 @@
+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",
+    ],
+)