dsmq 0.5.0__py3-none-any.whl → 0.7.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/dsmq.py CHANGED
@@ -5,26 +5,25 @@ import sys
5
5
  from threading import Thread
6
6
  import time
7
7
 
8
- DEFAULT_HOST = "127.0.0.1"
9
- DEFAULT_PORT = 30008
8
+ _default_host = "127.0.0.1"
9
+ _default_port = 30008
10
10
 
11
- N_RETRIES = 5
12
- FIRST_RETRY = 0.01 # seconds
13
- TIME_TO_LIVE = 60 # seconds
14
- TIME_TO_LIVE = 5 # seconds
11
+ _message_length_offset = 1_000_000
12
+ _header_length = 23
13
+ _n_retries = 5
14
+ _first_retry = 0.01 # seconds
15
+ _time_to_live = 600.0 # seconds
15
16
 
16
17
 
17
- def start_server(host=DEFAULT_HOST, port=DEFAULT_PORT):
18
+ def start_server(host=_default_host, port=_default_port):
18
19
  """
19
20
  For best results, start this running in its own process and walk away.
20
-
21
-
22
21
  """
23
22
  sqlite_conn = sqlite3.connect("file:mem1?mode=memory&cache=shared")
24
23
  cursor = sqlite_conn.cursor()
25
24
 
26
25
  cursor.execute("""
27
- CREATE TABLE IF NOT EXISTS messages (timestamp DOUBLE, topic TEXT, message TEXT)
26
+ CREATE TABLE IF NOT EXISTS messages (timestamp DOUBLE, topic TEXT, message TEXT)
28
27
  """)
29
28
 
30
29
  with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
@@ -49,7 +48,7 @@ def start_server(host=DEFAULT_HOST, port=DEFAULT_PORT):
49
48
  sqlite_conn.close()
50
49
 
51
50
 
52
- def connect_to_server(host=DEFAULT_HOST, port=DEFAULT_PORT):
51
+ def connect_to_server(host=_default_host, port=_default_port):
53
52
  return DSMQClientSideConnection(host, port)
54
53
 
55
54
 
@@ -59,22 +58,55 @@ class DSMQClientSideConnection:
59
58
  self.dsmq_conn.connect((host, port))
60
59
 
61
60
  def get(self, topic):
62
- msg = json.dumps({"action": "get", "topic": topic})
63
- self.dsmq_conn.sendall(bytes(msg, "utf-8"))
61
+ msg_dict = {"action": "get", "topic": topic}
62
+ _send_message(self.dsmq_conn, msg_dict)
64
63
 
65
- data = self.dsmq_conn.recv(1024)
66
- if not data:
64
+ msg = _receive_message(self.dsmq_conn)
65
+ if msg is None:
67
66
  raise RuntimeError("Connection terminated by server")
68
- msg_str = data.decode("utf-8")
69
- msg = json.loads(msg_str)
70
- try:
71
- return msg["message"]
72
- except KeyError:
73
- return ""
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)
74
85
 
75
- def put(self, topic, msg):
76
- msg = json.dumps({"action": "put", "topic": topic, "message": msg})
77
- self.dsmq_conn.sendall(bytes(msg, "utf-8"))
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
78
110
 
79
111
 
80
112
  def _handle_client_connection(socket_conn):
@@ -86,20 +118,10 @@ def _handle_client_connection(socket_conn):
86
118
  time_of_last_purge = time.time()
87
119
 
88
120
  while True:
89
- data = socket_conn.recv(1024)
90
- # Check whether the connection has been terminated
91
- if not data:
121
+ msg = _receive_message(socket_conn)
122
+ if msg is None:
92
123
  break
93
124
 
94
- msg_str = data.decode("utf-8")
95
- try:
96
- # print("dsmq received ", msg_str)
97
- msg = json.loads(msg_str)
98
- except json.decoder.JSONDecodeError:
99
- print("Message must be json-friendly")
100
- print(f" Received: {msg}")
101
- continue
102
-
103
125
  topic = msg["topic"]
104
126
  timestamp = time.time()
105
127
 
@@ -108,7 +130,7 @@ def _handle_client_connection(socket_conn):
108
130
 
109
131
  # This block allows for multiple retries if the database
110
132
  # is busy.
111
- for i_retry in range(N_RETRIES):
133
+ for i_retry in range(_n_retries):
112
134
  try:
113
135
  cursor.execute(
114
136
  """
@@ -119,7 +141,7 @@ VALUES (:timestamp, :topic, :message)
119
141
  )
120
142
  sqlite_conn.commit()
121
143
  except sqlite3.OperationalError:
122
- wait_time = FIRST_RETRY * 2**i_retry
144
+ wait_time = _first_retry * 2**i_retry
123
145
  time.sleep(wait_time)
124
146
  continue
125
147
  break
@@ -134,7 +156,7 @@ VALUES (:timestamp, :topic, :message)
134
156
 
135
157
  # This block allows for multiple retries if the database
136
158
  # is busy.
137
- for i_retry in range(N_RETRIES):
159
+ for i_retry in range(_n_retries):
138
160
  try:
139
161
  cursor.execute(
140
162
  """
@@ -153,7 +175,7 @@ AND timestamp = a.min_time
153
175
  msg,
154
176
  )
155
177
  except sqlite3.OperationalError:
156
- wait_time = FIRST_RETRY * 2**i_retry
178
+ wait_time = _first_retry * 2**i_retry
157
179
  time.sleep(wait_time)
158
180
  continue
159
181
  break
@@ -167,15 +189,14 @@ AND timestamp = a.min_time
167
189
  # Handle the case where no results are returned
168
190
  message = ""
169
191
 
170
- msg = json.dumps({"message": message})
171
- socket_conn.sendall(bytes(msg, "utf-8"))
192
+ _send_message(socket_conn, {"message": message})
172
193
  else:
173
194
  print("Action must either be 'put' or 'get'")
174
195
 
175
196
  # Periodically clean out messages from the queue that are
176
197
  # past their sell buy date.
177
198
  # This operation is pretty fast. I clock it at 12 us on my machine.
178
- if time.time() - time_of_last_purge > TIME_TO_LIVE:
199
+ if time.time() - time_of_last_purge > _time_to_live:
179
200
  cursor.execute(
180
201
  """
181
202
  DELETE FROM messages
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: dsmq
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: A dead simple message queue
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -97,6 +97,8 @@ a queue before it connected.
97
97
  - A client will get the oldest message available on a requested topic.
98
98
  Queues are first-in-first-out.
99
99
 
100
+ - Messages older than 600 seconds will be deleted from the queue.
101
+
100
102
  - Put and get operations are fairly quick--less than 100 $`\mu`$s of processing
101
103
  time plus any network latency--so it can comfortably handle requests at rates of
102
104
  hundreds of times per second. But if you have several clients reading and writing
@@ -127,7 +129,7 @@ Connects to an existing message queue server.
127
129
  ## `DSMQClientSideConnection` class
128
130
 
129
131
  This is a convenience wrapper, to make the `get()` and `put()` functions
130
- easy to write and remember.
132
+ easy to write and remember. It's under the hood only, not meant to be called directly.
131
133
 
132
134
  ### `put(topic, msg)`
133
135
 
@@ -143,4 +145,4 @@ connected to the server.
143
145
  - `topic` (str)
144
146
  - returns str, the content of the message. If there was no eligble message
145
147
  in the topic, or the topic doesn't yet exist,
146
- returns "".
148
+ returns `""`.
@@ -1,10 +1,10 @@
1
1
  dsmq/__init__.py,sha256=YCgbnQAk8YbtHRyMcU0v2O7RdRhPhlT-vS_q40a7Q6g,50
2
2
  dsmq/demo_linux.py,sha256=7yLglGmirDLuuyMxppYSK-dfx2Fg2Q0dIWB4cl2yV1c,622
3
- dsmq/dsmq.py,sha256=dKYtGkg8Kwll2sCytCqSI5klFs2cwm2pyXAkyQk4RdU,6206
3
+ dsmq/dsmq.py,sha256=f-dR-Pjjn_gDx-EK1evYQWvDq8ypXbhx6DaCWHCInSc,6744
4
4
  dsmq/example_get_client.py,sha256=chFfB2949PBENmgdUc3ASrATq1m4wvHGBzEnOC-o_Xs,296
5
5
  dsmq/example_put_client.py,sha256=mUKCRhmUieMZEpHLFWFvzeKB6IR7A8l4tWN8TvPzdKU,348
6
6
  dsmq/example_server.py,sha256=kkXOPaaTzVxf9_iIM76zU9pZhkPna_1vcGWkPrhCjus,61
7
- dsmq-0.5.0.dist-info/METADATA,sha256=EQISO0WcVsb7smXIie-ZP7pIO1itY8FZfjGw3DPuZDM,3878
8
- dsmq-0.5.0.dist-info/WHEEL,sha256=3U_NnUcV_1B1kPkYaPzN-irRckL5VW_lytn0ytO_kRY,87
9
- dsmq-0.5.0.dist-info/licenses/LICENSE,sha256=3Yu1mAp5VsKmnDtzkiOY7BdmrLeNwwZ3t6iWaLnlL0Y,1071
10
- dsmq-0.5.0.dist-info/RECORD,,
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,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.26.1
2
+ Generator: hatchling 1.26.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any