dsmq 0.6.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 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 dsmq
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=dsmq.start_server, args=(HOST, PORT))
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()
@@ -1,9 +1,9 @@
1
1
  import time
2
- import dsmq
2
+ from dsmq.client import connect
3
3
 
4
4
 
5
5
  def run(host="127.0.0.1", port=30008, n_iter=1000):
6
- mq = dsmq.connect_to_server(host=host, port=port)
6
+ mq = connect(host=host, port=port)
7
7
 
8
8
  for i in range(n_iter):
9
9
  time.sleep(1)
@@ -1,9 +1,9 @@
1
1
  import time
2
- import dsmq
2
+ from dsmq.client import connect
3
3
 
4
4
 
5
5
  def run(host="127.0.0.1", port=30008, n_iter=1000):
6
- mq = dsmq.connect_to_server(host=host, port=port)
6
+ mq = connect(host=host, port=port)
7
7
 
8
8
  for i in range(n_iter):
9
9
  time.sleep(1)
@@ -1,80 +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
- DEFAULT_HOST = "127.0.0.1"
9
- DEFAULT_PORT = 30008
7
+ _default_host = "127.0.0.1"
8
+ _default_port = 30008
9
+ _n_retries = 5
10
+ _first_retry = 0.01 # seconds
11
+ _time_to_live = 600.0 # seconds
10
12
 
11
- N_RETRIES = 5
12
- FIRST_RETRY = 0.01 # seconds
13
- TIME_TO_LIVE = 600.0 # seconds
14
13
 
15
-
16
- def start_server(host=DEFAULT_HOST, port=DEFAULT_PORT):
14
+ def serve(host=_default_host, port=_default_port):
17
15
  """
18
16
  For best results, start this running in its own process and walk away.
19
17
  """
20
18
  sqlite_conn = sqlite3.connect("file:mem1?mode=memory&cache=shared")
21
19
  cursor = sqlite_conn.cursor()
22
-
23
20
  cursor.execute("""
24
- CREATE TABLE IF NOT EXISTS messages (timestamp DOUBLE, topic TEXT, message TEXT)
21
+ CREATE TABLE IF NOT EXISTS messages (timestamp DOUBLE, topic TEXT, message TEXT)
25
22
  """)
26
23
 
27
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
28
- # Setting this socket option to re-use the address,
29
- # even if it's already in use.
30
- # This is helpful in recovering from crashes where things didn't
31
- # shut down properly.
32
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
33
-
34
- s.bind((host, port))
35
- s.listen()
36
-
24
+ with ws_serve(request_handler, host, port) as server:
25
+ server.serve_forever()
37
26
  print()
38
27
  print(f"Server started at {host} on port {port}.")
39
28
  print("Waiting for clients...")
40
29
 
41
- while True:
42
- socket_conn, addr = s.accept()
43
- print(f"Connected by {addr}")
44
- Thread(target=_handle_client_connection, args=(socket_conn,)).start()
45
-
46
30
  sqlite_conn.close()
47
31
 
48
32
 
49
- def connect_to_server(host=DEFAULT_HOST, port=DEFAULT_PORT):
50
- return DSMQClientSideConnection(host, port)
51
-
52
-
53
- class DSMQClientSideConnection:
54
- def __init__(self, host, port):
55
- self.dsmq_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
56
- self.dsmq_conn.connect((host, port))
57
-
58
- def get(self, topic):
59
- msg = json.dumps({"action": "get", "topic": topic})
60
- self.dsmq_conn.sendall(bytes(msg, "utf-8"))
61
-
62
- data = self.dsmq_conn.recv(1024)
63
- if not data:
64
- raise RuntimeError("Connection terminated by server")
65
- msg_str = data.decode("utf-8")
66
- msg = json.loads(msg_str)
67
- try:
68
- return msg["message"]
69
- except KeyError:
70
- return ""
71
-
72
- def put(self, topic, msg):
73
- msg = json.dumps({"action": "put", "topic": topic, "message": msg})
74
- self.dsmq_conn.sendall(bytes(msg, "utf-8"))
75
-
76
-
77
- def _handle_client_connection(socket_conn):
33
+ def request_handler(websocket):
78
34
  sqlite_conn = sqlite3.connect("file:mem1?mode=memory&cache=shared")
79
35
  cursor = sqlite_conn.cursor()
80
36
 
@@ -82,20 +38,8 @@ def _handle_client_connection(socket_conn):
82
38
  last_read_times = {}
83
39
  time_of_last_purge = time.time()
84
40
 
85
- while True:
86
- data = socket_conn.recv(1024)
87
- # Check whether the connection has been terminated
88
- if not data:
89
- break
90
-
91
- msg_str = data.decode("utf-8")
92
- try:
93
- msg = json.loads(msg_str)
94
- except json.decoder.JSONDecodeError:
95
- print("Message must be json-friendly")
96
- print(f" Received: {msg}")
97
- continue
98
-
41
+ for msg_text in websocket:
42
+ msg = json.loads(msg_text)
99
43
  topic = msg["topic"]
100
44
  timestamp = time.time()
101
45
 
@@ -104,7 +48,7 @@ def _handle_client_connection(socket_conn):
104
48
 
105
49
  # This block allows for multiple retries if the database
106
50
  # is busy.
107
- for i_retry in range(N_RETRIES):
51
+ for i_retry in range(_n_retries):
108
52
  try:
109
53
  cursor.execute(
110
54
  """
@@ -115,7 +59,7 @@ VALUES (:timestamp, :topic, :message)
115
59
  )
116
60
  sqlite_conn.commit()
117
61
  except sqlite3.OperationalError:
118
- wait_time = FIRST_RETRY * 2**i_retry
62
+ wait_time = _first_retry * 2**i_retry
119
63
  time.sleep(wait_time)
120
64
  continue
121
65
  break
@@ -130,7 +74,7 @@ VALUES (:timestamp, :topic, :message)
130
74
 
131
75
  # This block allows for multiple retries if the database
132
76
  # is busy.
133
- for i_retry in range(N_RETRIES):
77
+ for i_retry in range(_n_retries):
134
78
  try:
135
79
  cursor.execute(
136
80
  """
@@ -149,7 +93,7 @@ AND timestamp = a.min_time
149
93
  msg,
150
94
  )
151
95
  except sqlite3.OperationalError:
152
- wait_time = FIRST_RETRY * 2**i_retry
96
+ wait_time = _first_retry * 2**i_retry
153
97
  time.sleep(wait_time)
154
98
  continue
155
99
  break
@@ -163,15 +107,15 @@ AND timestamp = a.min_time
163
107
  # Handle the case where no results are returned
164
108
  message = ""
165
109
 
166
- msg = json.dumps({"message": message})
167
- socket_conn.sendall(bytes(msg, "utf-8"))
110
+ websocket.send(json.dumps({"message": message}))
168
111
  else:
169
112
  print("Action must either be 'put' or 'get'")
170
113
 
114
+
171
115
  # Periodically clean out messages from the queue that are
172
116
  # past their sell buy date.
173
117
  # This operation is pretty fast. I clock it at 12 us on my machine.
174
- if time.time() - time_of_last_purge > TIME_TO_LIVE:
118
+ if time.time() - time_of_last_purge > _time_to_live:
175
119
  cursor.execute(
176
120
  """
177
121
  DELETE FROM messages
@@ -189,21 +133,21 @@ if __name__ == "__main__":
189
133
  if len(sys.argv) == 3:
190
134
  host = sys.argv[1]
191
135
  port = int(sys.argv[2])
192
- start_server(host=host, port=port)
136
+ serve(host=host, port=port)
193
137
  elif len(sys.argv) == 2:
194
138
  host = sys.argv[1]
195
- start_server(host=host)
139
+ serve(host=host)
196
140
  elif len(sys.argv) == 1:
197
- start_server()
141
+ serve()
198
142
  else:
199
143
  print(
200
144
  """
201
145
  Try one of these:
202
- $ python3 dsmq.py
146
+ $ python3 server.py
203
147
 
204
- $ python3 dsmq.py 127.0.0.1
148
+ $ python3 server.py 127.0.0.1
205
149
 
206
- $ python3 dsmq.py 127.0.0.1 25853
150
+ $ python3 server.py 127.0.0.1 25853
207
151
 
208
- """
152
+ """
209
153
  )
@@ -1,8 +1,10 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: dsmq
3
- Version: 0.6.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 dsmq
31
+ from dsmq.server import serve
30
32
 
31
- dsmq.start_server(host="127.0.0.1", port=30008)
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
- mq = dsmq.connect_to_server(host="127.0.0.1", port=12345)
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=dsmq.start_server, args=(config.MQ_HOST, config.MQ_PORT))
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/dsmq.py`.
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/dsmq.py)]
116
+ [[source](https://github.com/brohrer/dsmq/blob/main/src/dsmq/serve.py)]
113
117
 
114
- ### `start_server(host="127.0.0.1", port=30008)`
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
- ### `connect_to_server(host="127.0.0.1", port=30008)`
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.1
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
dsmq/example_server.py DELETED
@@ -1,3 +0,0 @@
1
- import dsmq
2
-
3
- dsmq.start_server(host="127.0.0.1", port=30008)
@@ -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=zg69Tf3x9z26a9NrEXTWgr78kcNKVszmLq0INv3IHuM,6132
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.6.0.dist-info/METADATA,sha256=0mL4N2uHDGtlnH_fqrdNLdpS-flp6x1O1g-uLGzF4b4,4006
8
- dsmq-0.6.0.dist-info/WHEEL,sha256=3U_NnUcV_1B1kPkYaPzN-irRckL5VW_lytn0ytO_kRY,87
9
- dsmq-0.6.0.dist-info/licenses/LICENSE,sha256=3Yu1mAp5VsKmnDtzkiOY7BdmrLeNwwZ3t6iWaLnlL0Y,1071
10
- dsmq-0.6.0.dist-info/RECORD,,