dsmq 1.2.2__py3-none-any.whl → 1.2.4__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 CHANGED
@@ -1,6 +1,7 @@
1
1
  import json
2
2
  import time
3
3
  from websockets.sync.client import connect as ws_connect
4
+ from websockets.exceptions import ConnectionClosedError
4
5
 
5
6
  _default_host = "127.0.0.1"
6
7
  _default_port = 30008
@@ -39,7 +40,11 @@ class DSMQClientSideConnection:
39
40
  def get(self, topic):
40
41
  msg = {"action": "get", "topic": topic}
41
42
  self.websocket.send(json.dumps(msg))
42
- msg_text = self.websocket.recv()
43
+ try:
44
+ msg_text = self.websocket.recv()
45
+ except ConnectionClosedError:
46
+ self.close()
47
+
43
48
  msg = json.loads(msg_text)
44
49
  return msg["message"]
45
50
 
dsmq/server.py CHANGED
@@ -5,6 +5,7 @@ import sys
5
5
  from threading import Thread
6
6
  import time
7
7
  from websockets.sync.server import serve as ws_serve
8
+ from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK
8
9
 
9
10
  _default_host = "127.0.0.1"
10
11
  _default_port = 30008
@@ -69,111 +70,120 @@ def request_handler(websocket):
69
70
  last_read_times = {}
70
71
  time_of_last_purge = time.time()
71
72
 
72
- for msg_text in websocket:
73
- msg = json.loads(msg_text)
74
- topic = msg["topic"]
75
- timestamp = time.time()
76
-
77
- if msg["action"] == "put":
78
- msg["timestamp"] = timestamp
73
+ try:
74
+ for msg_text in websocket:
75
+ msg = json.loads(msg_text)
76
+ topic = msg["topic"]
77
+ timestamp = time.time()
78
+
79
+ if msg["action"] == "put":
80
+ msg["timestamp"] = timestamp
81
+
82
+ # This block allows for multiple retries if the database
83
+ # is busy.
84
+ for i_retry in range(_n_retries):
85
+ try:
86
+ cursor.execute(
87
+ """
88
+ INSERT INTO messages (timestamp, topic, message)
89
+ VALUES (:timestamp, :topic, :message)
90
+ """,
91
+ (msg),
92
+ )
93
+ sqlite_conn.commit()
94
+ except sqlite3.OperationalError:
95
+ wait_time = _first_retry * 2**i_retry
96
+ time.sleep(wait_time)
97
+ continue
98
+ break
99
+
100
+ elif msg["action"] == "get":
101
+ try:
102
+ last_read_time = last_read_times[topic]
103
+ except KeyError:
104
+ last_read_times[topic] = client_creation_time
105
+ last_read_time = last_read_times[topic]
106
+ msg["last_read_time"] = last_read_time
107
+
108
+ # This block allows for multiple retries if the database
109
+ # is busy.
110
+ for i_retry in range(_n_retries):
111
+ try:
112
+ cursor.execute(
113
+ """
114
+ SELECT message,
115
+ timestamp
116
+ FROM messages,
117
+ (
118
+ SELECT MIN(timestamp) AS min_time
119
+ FROM messages
120
+ WHERE topic = :topic
121
+ AND timestamp > :last_read_time
122
+ ) a
123
+ WHERE topic = :topic
124
+ AND timestamp = a.min_time
125
+ """,
126
+ msg,
127
+ )
128
+ except sqlite3.OperationalError:
129
+ wait_time = _first_retry * 2**i_retry
130
+ time.sleep(wait_time)
131
+ continue
132
+ break
79
133
 
80
- # This block allows for multiple retries if the database
81
- # is busy.
82
- for i_retry in range(_n_retries):
83
134
  try:
84
- cursor.execute(
85
- """
86
- INSERT INTO messages (timestamp, topic, message)
87
- VALUES (:timestamp, :topic, :message)
88
- """,
89
- (msg),
90
- )
91
- sqlite_conn.commit()
92
- except sqlite3.OperationalError:
93
- wait_time = _first_retry * 2**i_retry
94
- time.sleep(wait_time)
95
- continue
135
+ result = cursor.fetchall()[0]
136
+ message = result[0]
137
+ timestamp = result[1]
138
+ last_read_times[topic] = timestamp
139
+ except IndexError:
140
+ # Handle the case where no results are returned
141
+ message = ""
142
+
143
+ websocket.send(json.dumps({"message": message}))
144
+ elif msg["action"] == "shutdown":
145
+ # Run this from a separate thread to prevent deadlock
146
+ global dsmq_server
147
+
148
+ def shutdown_gracefully(server_to_shutdown):
149
+ server_to_shutdown.shutdown()
150
+
151
+ filenames = os.listdir()
152
+ for filename in filenames:
153
+ if filename[: len(_db_name)] == _db_name:
154
+ try:
155
+ os.remove(filename)
156
+ except FileNotFoundError:
157
+ pass
158
+
159
+ Thread(target=shutdown_gracefully, args=(dsmq_server,)).start()
96
160
  break
97
-
98
- elif msg["action"] == "get":
99
- try:
100
- last_read_time = last_read_times[topic]
101
- except KeyError:
102
- last_read_times[topic] = client_creation_time
103
- last_read_time = last_read_times[topic]
104
- msg["last_read_time"] = last_read_time
105
-
106
- # This block allows for multiple retries if the database
107
- # is busy.
108
- for i_retry in range(_n_retries):
161
+ else:
162
+ raise RuntimeWarning(
163
+ "dsmq client action must either be 'put', 'get', or 'shutdown'"
164
+ )
165
+
166
+ # Periodically clean out messages from the queue that are
167
+ # past their sell buy date.
168
+ # This operation is pretty fast. I clock it at 12 us on my machine.
169
+ if time.time() - time_of_last_purge > _time_to_live:
109
170
  try:
110
171
  cursor.execute(
111
172
  """
112
- SELECT message,
113
- timestamp
114
- FROM messages,
115
- (
116
- SELECT MIN(timestamp) AS min_time
117
- FROM messages
118
- WHERE topic = :topic
119
- AND timestamp > :last_read_time
120
- ) a
121
- WHERE topic = :topic
122
- AND timestamp = a.min_time
173
+ DELETE FROM messages
174
+ WHERE timestamp < :time_threshold
123
175
  """,
124
- msg,
176
+ {"time_threshold": time_of_last_purge},
125
177
  )
178
+ sqlite_conn.commit()
179
+ time_of_last_purge = time.time()
126
180
  except sqlite3.OperationalError:
127
- wait_time = _first_retry * 2**i_retry
128
- time.sleep(wait_time)
129
- continue
130
- break
131
-
132
- try:
133
- result = cursor.fetchall()[0]
134
- message = result[0]
135
- timestamp = result[1]
136
- last_read_times[topic] = timestamp
137
- except IndexError:
138
- # Handle the case where no results are returned
139
- message = ""
140
-
141
- websocket.send(json.dumps({"message": message}))
142
- elif msg["action"] == "shutdown":
143
- # Run this from a separate thread to prevent deadlock
144
- global dsmq_server
145
-
146
- def shutdown_gracefully(server_to_shutdown):
147
- server_to_shutdown.shutdown()
148
-
149
- filenames = os.listdir()
150
- for filename in filenames:
151
- if filename[: len(_db_name)] == _db_name:
152
- try:
153
- os.remove(filename)
154
- except FileNotFoundError:
155
- pass
156
-
157
- Thread(target=shutdown_gracefully, args=(dsmq_server,)).start()
158
- break
159
- else:
160
- raise RuntimeWarning(
161
- "dsmq client action must either be 'put', 'get', or 'shutdown'"
162
- )
163
-
164
- # Periodically clean out messages from the queue that are
165
- # past their sell buy date.
166
- # This operation is pretty fast. I clock it at 12 us on my machine.
167
- if time.time() - time_of_last_purge > _time_to_live:
168
- cursor.execute(
169
- """
170
- DELETE FROM messages
171
- WHERE timestamp < :time_threshold
172
- """,
173
- {"time_threshold": time_of_last_purge},
174
- )
175
- sqlite_conn.commit()
176
- time_of_last_purge = time.time()
181
+ # Database may be locked. Try again next time.
182
+ pass
183
+ except (ConnectionClosedError, ConnectionClosedOK):
184
+ # Something happened on the other end and this handler
185
+ # is no longer needed.
186
+ pass
177
187
 
178
188
  sqlite_conn.close()
179
189
 
@@ -1,6 +1,5 @@
1
1
  import multiprocessing as mp
2
2
  import time
3
- from websockets.exceptions import ConnectionClosed
4
3
  from dsmq.server import serve
5
4
  from dsmq.client import connect
6
5
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dsmq
3
- Version: 1.2.2
3
+ Version: 1.2.4
4
4
  Summary: A dead simple message queue
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.10
@@ -0,0 +1,12 @@
1
+ dsmq/__init__.py,sha256=YCgbnQAk8YbtHRyMcU0v2O7RdRhPhlT-vS_q40a7Q6g,50
2
+ dsmq/client.py,sha256=p6irQZOE4b2fpTUwSrEUraPmrJzvT8QSU01ak9qpGCQ,2351
3
+ dsmq/demo.py,sha256=K53cC5kN7K4kNJlPq7c5OTIMHRCKTo9hYX2aIos57rU,542
4
+ dsmq/example_get_client.py,sha256=PvAsDGEAH1kVBifLVg2rx8ZxnAZmvzVCvZq13VgpLds,301
5
+ dsmq/example_put_client.py,sha256=QxDc3i7KAjjhpwxRRpI0Ke5KTNSPuBf9kkcGyTvUEaw,353
6
+ dsmq/server.py,sha256=5c4mHWz6yslSxpOwrZ3vOCMcx_cm8zMF8iJNnQF8ft0,7249
7
+ dsmq/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ dsmq/tests/integration_test.py,sha256=dLsQGCmpXv4zRb93TriccH7TbUyD9MHcLckAQqfDOK4,5980
9
+ dsmq-1.2.4.dist-info/METADATA,sha256=CswX8bQph2pF-k-VMCLAPqaho3Mhny4xDLJ70xwWa9I,4730
10
+ dsmq-1.2.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ dsmq-1.2.4.dist-info/licenses/LICENSE,sha256=3Yu1mAp5VsKmnDtzkiOY7BdmrLeNwwZ3t6iWaLnlL0Y,1071
12
+ dsmq-1.2.4.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- dsmq/__init__.py,sha256=YCgbnQAk8YbtHRyMcU0v2O7RdRhPhlT-vS_q40a7Q6g,50
2
- dsmq/client.py,sha256=1fQLRExDkdLogu8M5aofkYp3-Qy5a2y3aMYy7qC3NkE,2214
3
- dsmq/demo.py,sha256=K53cC5kN7K4kNJlPq7c5OTIMHRCKTo9hYX2aIos57rU,542
4
- dsmq/example_get_client.py,sha256=PvAsDGEAH1kVBifLVg2rx8ZxnAZmvzVCvZq13VgpLds,301
5
- dsmq/example_put_client.py,sha256=QxDc3i7KAjjhpwxRRpI0Ke5KTNSPuBf9kkcGyTvUEaw,353
6
- dsmq/server.py,sha256=kCqBzgikvJlCgAfLHd18LS7fmkYW6e0RhmdwqdeXJ7Q,6079
7
- dsmq/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- dsmq/tests/integration_test.py,sha256=lC97LAzdQixv75OwjqjKTvYnSZpsP0zuzFP8ocUnjl8,6031
9
- dsmq-1.2.2.dist-info/METADATA,sha256=1n3_ZM8haJ5U99_fx6vZWpVYzfSR1RLUn_Ich87HaBo,4730
10
- dsmq-1.2.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
- dsmq-1.2.2.dist-info/licenses/LICENSE,sha256=3Yu1mAp5VsKmnDtzkiOY7BdmrLeNwwZ3t6iWaLnlL0Y,1071
12
- dsmq-1.2.2.dist-info/RECORD,,
File without changes