Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,5 @@ coverage.xml
# Sphinx documentation
docs/_build/

# Jetbrains IDE
.idea
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "auto_version"]
path = auto_version
url = https://github.com/moble/auto_version.git
31 changes: 8 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,31 +1,16 @@
jsonsocket
==========

This is a small Python library for sending data over sockets.
This is a single-file Python3 library for sending json-like data over sockets.

It allows sending lists, dictionaries, strings, etc. It can handle very large data (I've tested it with 10GB of data). Any JSON-serializable data is accepted.
(Original repos was Python2, forked it to support Python3)

Examples:
It allows sending lists, dictionaries, strings, etc. Any JSON-serializable data is accepted.

```python
from jsonsocket import Client, Server

host = 'localhost'
port = '8000'

# Client code:
client = Client()
client.connect(host, port).send({'some_list': [123, 456]})
response = client.recv()
# response now is {'data': {'some_list': [123, 456]}}
client.close()


# Server code:
server = Server(host, port)
server.accept()
data = server.recv()
# data now is: {'some_list': [123, 456]}
server.send({'data': data}).close()
Example & test:

```bash
$python3 server_test.py &
$python3 client_test.py
```

1 change: 1 addition & 0 deletions auto_version
Submodule auto_version added at b9da16
134 changes: 0 additions & 134 deletions jsonsocket.py

This file was deleted.

Empty file added jsonsocket/__init__.py
Empty file.
4 changes: 4 additions & 0 deletions jsonsocket/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import sys

broadcast_address = "<broadcast>"
python3 = sys.version_info[0]>=3
22 changes: 22 additions & 0 deletions jsonsocket/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
class IncorrectLength(Exception):
def __init__(self,message,length):
super(IncorrectLength, self).__init__("Incorrect transmitted length")
self.content = message
self.length = length


class NoClient(Exception):
def __init__(self):
super(NoClient, self).__init__('Cannot send data, no client is connected')


class ConnectFirst(Exception):
def __init__(self):
super(ConnectFirst, self).__init__('You have to connect first before sending data')


class AddressAlreadyInUse(Exception):
def __init__(self, host, port):
super(AddressAlreadyInUse, self).__init__("Address %s:%i already in use" % (host, port))
self.host = host
self.port = port
69 changes: 69 additions & 0 deletions jsonsocket/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import sys
from time import time

from jsonsocket.constants import python3
from jsonsocket.errors import IncorrectLength
from jsonsocket.serialize import serialize, deserialize
from socket import timeout as tmout


class TimeoutError(Exception):
pass


def send(socket, data, socket_type="tcp", *args):
serialized = serialize(data)
# send the length of the serialized data first
message = '{}\n'.format(len(serialized)).encode('utf-8')

if socket_type == "tcp":
socket.send(message)
# send the serialized data
socket.sendall(serialized)
elif socket_type == "udp":
padding = "\n"
if python3:
padding = bytes(padding, "utf-8")
content = message + serialized + padding
socket.sendto(content, args)


def receive(socket, socket_type="tcp", timeout=None, skip_size_info=False):
# read the length of the data, letter by letter until we reach EOL
length_str = ''
tcp = socket_type == "tcp"
t0 = time()
if tcp:
char = socket.recv(1)
while char != b'\n' and (timeout is None or (timeout is not None and (time() - t0) < timeout)):
length_str += char.decode('utf-8')
char = socket.recv(1)
if length_str == '':
raise TimeoutError("Timeout listening for data")
total = int(length_str)
# use a memoryview to receive the data chunk by chunk efficiently
view = memoryview(bytearray(total))
next_offset = 0
while total - next_offset > 0:
recv_size = socket.recv_into(view[next_offset:], total - next_offset)
next_offset += recv_size
deserialized = deserialize(view.tobytes())
return deserialized
else:
if timeout:
socket.settimeout(timeout)
try:
char, addr = socket.recvfrom(2048 ** 2)
if type(char)==bytes:
char = char.decode("utf-8")
try:
if not skip_size_info:
length, char = char.split("\n")[:2]
if len(char) != int(length):
raise IncorrectLength(char,length)
except ValueError:
pass
deserialized = deserialize(char)
return deserialized, addr
except tmout:
raise TimeoutError("Timout listening for data")
95 changes: 95 additions & 0 deletions jsonsocket/serialize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import json
import struct
from base64 import b64encode, b64decode

use_numpy, use_pandas = True, True
try:
import numpy as np
except ImportError:
use_numpy = False

try:
import pandas as pd
except ImportError:
use_pandas = False

complex_number_length = len(b64encode(struct.pack("ff", 0, 0)).decode("utf-8")) + 1
complex1 = np.array([1, 1j])


class BetterEncoder(json.JSONEncoder):
def default(self, obj):
if use_numpy and isinstance(obj, np.ndarray):
if "complex" in str(obj.dtype):
shape = obj.shape
values = obj.ravel()
values = np.concatenate([np.real(values), np.imag(values)]).reshape((2, len(values))).T.ravel()
encoded = b64encode(struct.pack("f" * len(values), *values)).decode("utf-8")
res = dict(_decode_type="numpy_complex", _shape=shape, _content=encoded)
return res
return obj.tolist()

if use_pandas:
typename = obj.__class__.__name__
res = None
if isinstance(obj, pd.DataFrame):
content = dict(zip(obj.columns, obj.values.T.tolist()))
for key, value in content.items():
unique = np.unique(value)
if len(unique) == 1:
content[key] = unique[0]
res = dict(_decode_type=typename, _content=content)
elif isinstance(obj, pd.Series):
res = dict(_decode_type=typename, _content=obj.to_dict())
if res:
return res
if "int" == type(obj).__name__[:3]:
return int(obj)
if "float" == type(obj).__name__[:5]:
return float(obj)
if "complex" == type(obj).__name__[:7]:
encoded = b64encode(struct.pack("ff", np.real(obj), np.imag(obj)))
encoded = "c" + encoded.decode("utf-8")
return encoded
return json.JSONEncoder.default(self, obj)


class BetterDecoder(json.JSONDecoder):
def __init__(self, *args, **kwargs):
super(BetterDecoder, self).__init__(object_hook=self.default, *args, **kwargs)

def default(self, obj):
if type(obj) == str and len(obj) == complex_number_length and obj[0] == "c":
obj = np.array(struct.unpack("ff", b64decode(obj[1:])))
if "_decode_type" in obj and obj["_decode_type"] == "numpy_complex":
content = obj["_content"]
shape = obj["_shape"]
content = b64decode(content)
content = struct.unpack("f" * (len(content) // 4), content)
content = np.reshape(content, (-1, 2))
content = content * complex1[None, :]
content = np.sum(content,axis=1)
content = np.reshape(content, shape)
return content
if use_pandas and isinstance(obj, dict):
if "_decode_type" in obj and "_content" in obj:
obj = eval("pd.%s(obj[\"_content\"])" % obj["_decode_type"])
return obj


def serialize(data):
try:
res = json.dumps(data, cls=BetterEncoder).encode('utf-8')
except (TypeError, ValueError) as e:
raise Exception('You can only send JSON-serializable data. Error is : %s' % e)
return res


def deserialize(data):
if type(data) == bytes:
data = data.decode('utf-8')
try:
res = json.loads(data, cls=BetterDecoder)
except (TypeError, ValueError) as e:
raise Exception('Data received was not in JSON format. Error is %s' % str(e))
return res
Loading