dsmq 0.7.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dsmq/client.py +27 -0
- dsmq/demo_linux.py +6 -6
- dsmq/example_get_client.py +2 -2
- dsmq/example_put_client.py +2 -2
- dsmq/{dsmq.py → server.py} +16 -97
- {dsmq-0.7.0.dist-info → dsmq-1.0.0.dist-info}/METADATA +15 -11
- dsmq-1.0.0.dist-info/RECORD +10 -0
- {dsmq-0.7.0.dist-info → dsmq-1.0.0.dist-info}/WHEEL +1 -1
- dsmq/example_server.py +0 -3
- dsmq-0.7.0.dist-info/RECORD +0 -10
- {dsmq-0.7.0.dist-info → dsmq-1.0.0.dist-info}/licenses/LICENSE +0 -0
dsmq/client.py
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
import json
|
2
|
+
from websockets.sync.client import connect as ws_connect
|
3
|
+
|
4
|
+
_default_host = "127.0.0.1"
|
5
|
+
_default_port = 30008
|
6
|
+
|
7
|
+
|
8
|
+
def connect(host=_default_host, port=_default_port):
|
9
|
+
return DSMQClientSideConnection(host, port)
|
10
|
+
|
11
|
+
|
12
|
+
class DSMQClientSideConnection:
|
13
|
+
def __init__(self, host, port):
|
14
|
+
self.uri = f"ws://{host}:{port}"
|
15
|
+
self.websocket = ws_connect(self.uri)
|
16
|
+
|
17
|
+
def get(self, topic):
|
18
|
+
msg = {"action": "get", "topic": topic}
|
19
|
+
self.websocket.send(json.dumps(msg))
|
20
|
+
|
21
|
+
msg_text = self.websocket.recv()
|
22
|
+
msg = json.loads(msg_text)
|
23
|
+
return msg["message"]
|
24
|
+
|
25
|
+
def put(self, topic, msg_body):
|
26
|
+
msg_dict = {"action": "put", "topic": topic, "message": msg_body}
|
27
|
+
self.websocket.send(json.dumps(msg_dict))
|
dsmq/demo_linux.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Heads up: This script only works on Linux because of use of "fork" method.
|
2
2
|
import multiprocessing as mp
|
3
|
-
import
|
4
|
-
import example_get_client
|
5
|
-
import example_put_client
|
3
|
+
from dsmq.server import serve
|
4
|
+
import dsmq.example_get_client
|
5
|
+
import dsmq.example_put_client
|
6
6
|
|
7
7
|
mp.set_start_method("fork")
|
8
8
|
|
@@ -11,11 +11,11 @@ PORT = 25252
|
|
11
11
|
|
12
12
|
|
13
13
|
def test_server_with_clients():
|
14
|
-
p_server = mp.Process(target=
|
14
|
+
p_server = mp.Process(target=serve, args=(HOST, PORT))
|
15
15
|
p_server.start()
|
16
16
|
|
17
|
-
p_putter = mp.Process(target=example_put_client.run, args=(HOST, PORT, 20))
|
18
|
-
p_getter = mp.Process(target=example_get_client.run, args=(HOST, PORT, 20))
|
17
|
+
p_putter = mp.Process(target=dsmq.example_put_client.run, args=(HOST, PORT, 20))
|
18
|
+
p_getter = mp.Process(target=dsmq.example_get_client.run, args=(HOST, PORT, 20))
|
19
19
|
|
20
20
|
p_putter.start()
|
21
21
|
p_getter.start()
|
dsmq/example_get_client.py
CHANGED
dsmq/example_put_client.py
CHANGED
dsmq/{dsmq.py → server.py}
RENAMED
@@ -1,115 +1,36 @@
|
|
1
1
|
import json
|
2
|
-
import socket
|
3
2
|
import sqlite3
|
4
3
|
import sys
|
5
|
-
from threading import Thread
|
6
4
|
import time
|
5
|
+
from websockets.sync.server import serve as ws_serve
|
7
6
|
|
8
7
|
_default_host = "127.0.0.1"
|
9
8
|
_default_port = 30008
|
10
|
-
|
11
|
-
_message_length_offset = 1_000_000
|
12
|
-
_header_length = 23
|
13
9
|
_n_retries = 5
|
14
10
|
_first_retry = 0.01 # seconds
|
15
11
|
_time_to_live = 600.0 # seconds
|
16
12
|
|
17
13
|
|
18
|
-
def
|
14
|
+
def serve(host=_default_host, port=_default_port):
|
19
15
|
"""
|
20
16
|
For best results, start this running in its own process and walk away.
|
21
17
|
"""
|
22
18
|
sqlite_conn = sqlite3.connect("file:mem1?mode=memory&cache=shared")
|
23
19
|
cursor = sqlite_conn.cursor()
|
24
|
-
|
25
20
|
cursor.execute("""
|
26
21
|
CREATE TABLE IF NOT EXISTS messages (timestamp DOUBLE, topic TEXT, message TEXT)
|
27
22
|
""")
|
28
23
|
|
29
|
-
with
|
30
|
-
|
31
|
-
# even if it's already in use.
|
32
|
-
# This is helpful in recovering from crashes where things didn't
|
33
|
-
# shut down properly.
|
34
|
-
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
35
|
-
|
36
|
-
s.bind((host, port))
|
37
|
-
s.listen()
|
38
|
-
|
24
|
+
with ws_serve(request_handler, host, port) as server:
|
25
|
+
server.serve_forever()
|
39
26
|
print()
|
40
27
|
print(f"Server started at {host} on port {port}.")
|
41
28
|
print("Waiting for clients...")
|
42
29
|
|
43
|
-
while True:
|
44
|
-
socket_conn, addr = s.accept()
|
45
|
-
print(f"Connected by {addr}")
|
46
|
-
Thread(target=_handle_client_connection, args=(socket_conn,)).start()
|
47
|
-
|
48
30
|
sqlite_conn.close()
|
49
31
|
|
50
32
|
|
51
|
-
def
|
52
|
-
return DSMQClientSideConnection(host, port)
|
53
|
-
|
54
|
-
|
55
|
-
class DSMQClientSideConnection:
|
56
|
-
def __init__(self, host, port):
|
57
|
-
self.dsmq_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
58
|
-
self.dsmq_conn.connect((host, port))
|
59
|
-
|
60
|
-
def get(self, topic):
|
61
|
-
msg_dict = {"action": "get", "topic": topic}
|
62
|
-
_send_message(self.dsmq_conn, msg_dict)
|
63
|
-
|
64
|
-
msg = _receive_message(self.dsmq_conn)
|
65
|
-
if msg is None:
|
66
|
-
raise RuntimeError("Connection terminated by server")
|
67
|
-
return msg["message"]
|
68
|
-
|
69
|
-
def put(self, topic, msg_body):
|
70
|
-
msg_dict = {"action": "put", "topic": topic, "message": msg_body}
|
71
|
-
_send_message(self.dsmq_conn, msg_dict)
|
72
|
-
|
73
|
-
|
74
|
-
def _send_message(socket_conn, msg_dict):
|
75
|
-
msg = json.dumps(msg_dict)
|
76
|
-
msg_bytes = bytes(msg, "utf-8")
|
77
|
-
n_bytes = len(msg_bytes)
|
78
|
-
|
79
|
-
# Send a header first.
|
80
|
-
# Add a large number to ensure that the message is the same size each time.
|
81
|
-
header_dict = {"msg_length": n_bytes + _message_length_offset}
|
82
|
-
header = json.dumps(header_dict)
|
83
|
-
header_bytes = bytes(header, "utf-8")
|
84
|
-
socket_conn.sendall(header_bytes)
|
85
|
-
|
86
|
-
socket_conn.sendall(msg_bytes)
|
87
|
-
|
88
|
-
|
89
|
-
def _receive_message(socket_conn):
|
90
|
-
# First receive a header
|
91
|
-
header_data = socket_conn.recv(_header_length)
|
92
|
-
if header_data is None:
|
93
|
-
return None
|
94
|
-
if len(header_data) == 0:
|
95
|
-
return None
|
96
|
-
|
97
|
-
header_str = header_data.decode("utf-8")
|
98
|
-
header = json.loads(header_str)
|
99
|
-
msg_length = header["msg_length"] - _message_length_offset
|
100
|
-
|
101
|
-
data = socket_conn.recv(msg_length)
|
102
|
-
if data is None:
|
103
|
-
return None
|
104
|
-
if len(data) == 0:
|
105
|
-
return None
|
106
|
-
|
107
|
-
msg_str = data.decode("utf-8")
|
108
|
-
msg = json.loads(msg_str)
|
109
|
-
return msg
|
110
|
-
|
111
|
-
|
112
|
-
def _handle_client_connection(socket_conn):
|
33
|
+
def request_handler(websocket):
|
113
34
|
sqlite_conn = sqlite3.connect("file:mem1?mode=memory&cache=shared")
|
114
35
|
cursor = sqlite_conn.cursor()
|
115
36
|
|
@@ -117,11 +38,8 @@ def _handle_client_connection(socket_conn):
|
|
117
38
|
last_read_times = {}
|
118
39
|
time_of_last_purge = time.time()
|
119
40
|
|
120
|
-
|
121
|
-
msg =
|
122
|
-
if msg is None:
|
123
|
-
break
|
124
|
-
|
41
|
+
for msg_text in websocket:
|
42
|
+
msg = json.loads(msg_text)
|
125
43
|
topic = msg["topic"]
|
126
44
|
timestamp = time.time()
|
127
45
|
|
@@ -189,10 +107,11 @@ AND timestamp = a.min_time
|
|
189
107
|
# Handle the case where no results are returned
|
190
108
|
message = ""
|
191
109
|
|
192
|
-
|
110
|
+
websocket.send(json.dumps({"message": message}))
|
193
111
|
else:
|
194
112
|
print("Action must either be 'put' or 'get'")
|
195
113
|
|
114
|
+
|
196
115
|
# Periodically clean out messages from the queue that are
|
197
116
|
# past their sell buy date.
|
198
117
|
# This operation is pretty fast. I clock it at 12 us on my machine.
|
@@ -214,21 +133,21 @@ if __name__ == "__main__":
|
|
214
133
|
if len(sys.argv) == 3:
|
215
134
|
host = sys.argv[1]
|
216
135
|
port = int(sys.argv[2])
|
217
|
-
|
136
|
+
serve(host=host, port=port)
|
218
137
|
elif len(sys.argv) == 2:
|
219
138
|
host = sys.argv[1]
|
220
|
-
|
139
|
+
serve(host=host)
|
221
140
|
elif len(sys.argv) == 1:
|
222
|
-
|
141
|
+
serve()
|
223
142
|
else:
|
224
143
|
print(
|
225
144
|
"""
|
226
145
|
Try one of these:
|
227
|
-
$ python3
|
146
|
+
$ python3 server.py
|
228
147
|
|
229
|
-
$ python3
|
148
|
+
$ python3 server.py 127.0.0.1
|
230
149
|
|
231
|
-
$ python3
|
150
|
+
$ python3 server.py 127.0.0.1 25853
|
232
151
|
|
233
|
-
|
152
|
+
"""
|
234
153
|
)
|
@@ -1,8 +1,10 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: dsmq
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0
|
4
4
|
Summary: A dead simple message queue
|
5
|
+
License-File: LICENSE
|
5
6
|
Requires-Python: >=3.10
|
7
|
+
Requires-Dist: websockets>=14.1
|
6
8
|
Description-Content-Type: text/markdown
|
7
9
|
|
8
10
|
# Dead Simple Message Queue
|
@@ -26,9 +28,9 @@ pip install dsmq
|
|
26
28
|
As in `src/dsmq/example_server.py`
|
27
29
|
|
28
30
|
```python
|
29
|
-
from dsmq import
|
31
|
+
from dsmq.server import serve
|
30
32
|
|
31
|
-
|
33
|
+
serve(host="127.0.0.1", port=30008)
|
32
34
|
```
|
33
35
|
|
34
36
|
### Connect a client to a dsmq server
|
@@ -36,7 +38,9 @@ dsmq.start_server(host="127.0.0.1", port=30008)
|
|
36
38
|
As in `src/dsmq/example_put_client.py`
|
37
39
|
|
38
40
|
```python
|
39
|
-
|
41
|
+
from dsmq.client import connect
|
42
|
+
|
43
|
+
mq = connect(host="127.0.0.1", port=12345)
|
40
44
|
```
|
41
45
|
### Add a message to a queue
|
42
46
|
|
@@ -66,7 +70,7 @@ managed
|
|
66
70
|
```python
|
67
71
|
import multiprocessing as mp
|
68
72
|
|
69
|
-
p_mq = mp.Process(target=
|
73
|
+
p_mq = mp.Process(target=serve, args=(config.MQ_HOST, config.MQ_PORT))
|
70
74
|
p_mq.start()
|
71
75
|
|
72
76
|
p_mq.join()
|
@@ -78,7 +82,7 @@ p_mq.close()
|
|
78
82
|
### Demo
|
79
83
|
|
80
84
|
1. Open 3 separate terminal windows.
|
81
|
-
1. In the first, run `src/dsmq/
|
85
|
+
1. In the first, run `src/dsmq/server.py` as a script.
|
82
86
|
1. In the second, run `src/dsmq/example_put_client.py`.
|
83
87
|
1. In the third, run `src/dsmq/example_get_client.py`.
|
84
88
|
|
@@ -109,9 +113,9 @@ get larger than your RAM, you will reach an out-of-memory condition.
|
|
109
113
|
|
110
114
|
|
111
115
|
# API Reference
|
112
|
-
[[source](https://github.com/brohrer/dsmq/blob/main/src/dsmq/
|
116
|
+
[[source](https://github.com/brohrer/dsmq/blob/main/src/dsmq/serve.py)]
|
113
117
|
|
114
|
-
### `
|
118
|
+
### `serve(host="127.0.0.1", port=30008)`
|
115
119
|
|
116
120
|
Kicks off the mesage queue server. This process will be the central exchange
|
117
121
|
for all incoming and outgoing messages.
|
@@ -119,9 +123,9 @@ for all incoming and outgoing messages.
|
|
119
123
|
- `port` (int), port. These will be used by all clients.
|
120
124
|
Non-privileged ports are numbered 1024 and higher.
|
121
125
|
|
122
|
-
### `
|
126
|
+
### `connect(host="127.0.0.1", port=30008)`
|
123
127
|
|
124
|
-
Connects to an existing message queue server.
|
128
|
+
Connects a client to an existing message queue server.
|
125
129
|
- `host` (str), IP address of the *server*.
|
126
130
|
- `port` (int), port on which the server is listening.
|
127
131
|
- returns a `DSMQClientSideConnection` object.
|
@@ -0,0 +1,10 @@
|
|
1
|
+
dsmq/__init__.py,sha256=YCgbnQAk8YbtHRyMcU0v2O7RdRhPhlT-vS_q40a7Q6g,50
|
2
|
+
dsmq/client.py,sha256=sgkz7iabc8hirq0HRfhqlKCPwHVYgvg2C01aoRbvTSo,768
|
3
|
+
dsmq/demo_linux.py,sha256=O5B2DH9pAvCCaYSRE5DV1o2fSx_bXU9hcxTis0ier5I,648
|
4
|
+
dsmq/example_get_client.py,sha256=PvAsDGEAH1kVBifLVg2rx8ZxnAZmvzVCvZq13VgpLds,301
|
5
|
+
dsmq/example_put_client.py,sha256=QxDc3i7KAjjhpwxRRpI0Ke5KTNSPuBf9kkcGyTvUEaw,353
|
6
|
+
dsmq/server.py,sha256=fFM8soN4y8CULX5yk3Temir-pl2K1hp38MOEwqzPN5U,4343
|
7
|
+
dsmq-1.0.0.dist-info/METADATA,sha256=TaoyVGkRJasSK-D6UQ9vNTeY07gOH9At7j1rZgiFxp0,4069
|
8
|
+
dsmq-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
+
dsmq-1.0.0.dist-info/licenses/LICENSE,sha256=3Yu1mAp5VsKmnDtzkiOY7BdmrLeNwwZ3t6iWaLnlL0Y,1071
|
10
|
+
dsmq-1.0.0.dist-info/RECORD,,
|
dsmq/example_server.py
DELETED
dsmq-0.7.0.dist-info/RECORD
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
dsmq/__init__.py,sha256=YCgbnQAk8YbtHRyMcU0v2O7RdRhPhlT-vS_q40a7Q6g,50
|
2
|
-
dsmq/demo_linux.py,sha256=7yLglGmirDLuuyMxppYSK-dfx2Fg2Q0dIWB4cl2yV1c,622
|
3
|
-
dsmq/dsmq.py,sha256=f-dR-Pjjn_gDx-EK1evYQWvDq8ypXbhx6DaCWHCInSc,6744
|
4
|
-
dsmq/example_get_client.py,sha256=chFfB2949PBENmgdUc3ASrATq1m4wvHGBzEnOC-o_Xs,296
|
5
|
-
dsmq/example_put_client.py,sha256=mUKCRhmUieMZEpHLFWFvzeKB6IR7A8l4tWN8TvPzdKU,348
|
6
|
-
dsmq/example_server.py,sha256=kkXOPaaTzVxf9_iIM76zU9pZhkPna_1vcGWkPrhCjus,61
|
7
|
-
dsmq-0.7.0.dist-info/METADATA,sha256=NFG4GQwX9RqIKLR3xvFelzuTVebieS4sJqZ9uFu24Lw,4006
|
8
|
-
dsmq-0.7.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
9
|
-
dsmq-0.7.0.dist-info/licenses/LICENSE,sha256=3Yu1mAp5VsKmnDtzkiOY7BdmrLeNwwZ3t6iWaLnlL0Y,1071
|
10
|
-
dsmq-0.7.0.dist-info/RECORD,,
|
File without changes
|