zprocess 2.22.0__tar.gz → 2.24.0__tar.gz

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.
Files changed (56) hide show
  1. {zprocess-2.22.0/zprocess.egg-info → zprocess-2.24.0}/PKG-INFO +1 -1
  2. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/security.py +82 -17
  3. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/tasks.py +52 -8
  4. {zprocess-2.22.0 → zprocess-2.24.0/zprocess.egg-info}/PKG-INFO +1 -1
  5. {zprocess-2.22.0 → zprocess-2.24.0}/.coveragerc +0 -0
  6. {zprocess-2.22.0 → zprocess-2.24.0}/.github/workflows/release.yml +0 -0
  7. {zprocess-2.22.0 → zprocess-2.24.0}/.gitignore +0 -0
  8. {zprocess-2.22.0 → zprocess-2.24.0}/.travis.yml +0 -0
  9. {zprocess-2.22.0 → zprocess-2.24.0}/LICENSE.txt +0 -0
  10. {zprocess-2.22.0 → zprocess-2.24.0}/README.md +0 -0
  11. {zprocess-2.22.0 → zprocess-2.24.0}/doc/Makefile +0 -0
  12. {zprocess-2.22.0 → zprocess-2.24.0}/doc/conf.py +0 -0
  13. {zprocess-2.22.0 → zprocess-2.24.0}/doc/index.rst +0 -0
  14. {zprocess-2.22.0 → zprocess-2.24.0}/doc/requirements.txt +0 -0
  15. {zprocess-2.22.0 → zprocess-2.24.0}/pyproject.toml +0 -0
  16. {zprocess-2.22.0 → zprocess-2.24.0}/requirements.txt +0 -0
  17. {zprocess-2.22.0 → zprocess-2.24.0}/run_tests.py +0 -0
  18. {zprocess-2.22.0 → zprocess-2.24.0}/setup.cfg +0 -0
  19. {zprocess-2.22.0 → zprocess-2.24.0}/setup.py +0 -0
  20. {zprocess-2.22.0 → zprocess-2.24.0}/tests/GUI_output_redirection.py +0 -0
  21. {zprocess-2.22.0 → zprocess-2.24.0}/tests/output_redirection.py +0 -0
  22. {zprocess-2.22.0 → zprocess-2.24.0}/tests/test_remote.py +0 -0
  23. {zprocess-2.22.0 → zprocess-2.24.0}/tests/test_zlock_server.py +0 -0
  24. {zprocess-2.22.0 → zprocess-2.24.0}/tests/test_zlog_server.py +0 -0
  25. {zprocess-2.22.0 → zprocess-2.24.0}/tests/tests.py +0 -0
  26. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/__init__.py +0 -0
  27. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/__version__.py +0 -0
  28. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/clientserver.py +0 -0
  29. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/cryptoproxy.py +0 -0
  30. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/examples/example_child.py +0 -0
  31. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/examples/example_parent.py +0 -0
  32. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/examples/processclass_example.py +0 -0
  33. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/examples/processclass_remote_example.py +0 -0
  34. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/locking/__init__.py +0 -0
  35. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/locking/__main__.py +0 -0
  36. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/makesecret.py +0 -0
  37. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/process_class_wrapper.py +0 -0
  38. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/process_tree.py +0 -0
  39. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/remote/__init__.py +0 -0
  40. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/remote/__main__.py +0 -0
  41. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/remote/curses_server.py +0 -0
  42. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/remote/server.py +0 -0
  43. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/socks/__init__.py +0 -0
  44. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/socks/server.py +0 -0
  45. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/utils.py +0 -0
  46. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlock/__init__.py +0 -0
  47. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlock/__main__.py +0 -0
  48. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlock/server.py +0 -0
  49. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlog/__init__.py +0 -0
  50. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlog/__main__.py +0 -0
  51. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlog/server.py +0 -0
  52. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/SOURCES.txt +0 -0
  53. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/dependency_links.txt +0 -0
  54. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/not-zip-safe +0 -0
  55. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/requires.txt +0 -0
  56. {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zprocess
3
- Version: 2.22.0
3
+ Version: 2.24.0
4
4
  Summary: A set of utilities for multiprocessing using zeromq.
5
5
  Home-page: http://github.com/chrisjbillington/zprocess
6
6
  Author: Chris Billington
@@ -3,6 +3,7 @@ import sys
3
3
  import base64
4
4
  import weakref
5
5
  import os
6
+ import threading
6
7
  import ipaddress
7
8
  import zmq
8
9
  import zmq.auth.thread
@@ -24,6 +25,7 @@ if sys.version_info[0] == 2:
24
25
  else:
25
26
  from time import monotonic
26
27
 
28
+ PYZMQ_VER_MAJOR = int(zmq.__version__.split('.')[0])
27
29
 
28
30
  _bundle_warning = """zprocess warning: pyzmq is using bundled libzmq, which on Windows
29
31
  is not built with the cryptography library libsodium. Encryption/decryption will be
@@ -165,9 +167,10 @@ class SecureSocket(zmq.Socket):
165
167
  are a server or not"""
166
168
  orig_server = self.curve_server
167
169
  if server:
170
+ self.curve_server = True
171
+ self.zap_domain = self.context.zap_domain
168
172
  self.curve_publickey = self.context.server_publickey
169
173
  self.curve_secretkey = self.context.server_secretkey
170
- self.curve_server = True
171
174
  else:
172
175
  self.curve_server = False
173
176
  self.curve_publickey = self.context.client_publickey
@@ -306,6 +309,72 @@ class SecureSocket(zmq.Socket):
306
309
  return msg
307
310
 
308
311
 
312
+ class _ThreadAuthenticator:
313
+ # We roll our own thread authenticator (just implementing what we need) since a)
314
+ # zmq.auth.thread.ThreadAuthenticator uses asyncio, and we do not want to express an
315
+ # opinion on the kerfuffle that is Windows asyncio selectors (see:
316
+ # https://github.com/zeromq/pyzmq/issues/1423) and impose it on the rest of the
317
+ # interpreter, when we're not even using async stuff ourself and b)
318
+ # zmq.auth.thread.ThreadAuthenticator spawns a non-daemon thread which is difficult
319
+ # to ensure gets shut down at interpreter shutdown, and otherwise holds the
320
+ # interpreter open.
321
+ def __init__(self, ctx, zap_domain, allowed_clients):
322
+ if PYZMQ_VER_MAJOR >= 25:
323
+ self.zap_socket = ctx.socket(zmq.REP, socket_class=zmq.Socket)
324
+ else:
325
+ self.zap_socket = ctx.socket(zmq.REP)
326
+ self.zap_socket.linger = 1
327
+ self.zap_socket.bind("inproc://zeromq.zap.01")
328
+
329
+ # Note: we hold a reference to the zap socket, since if the thread crashes, we
330
+ # don't want the socket to be cleaned up and closed - that would have zmq
331
+ # (stupidly and dangerously) fall back to its default authentication method,
332
+ # which is to allow all.
333
+ self.thread = threading.Thread(
334
+ target=self.run,
335
+ args=(self.zap_socket, zap_domain, allowed_clients),
336
+ daemon=True,
337
+ )
338
+ self.started = threading.Event()
339
+ self.thread.start()
340
+
341
+ def run(self, zap_socket, zap_domain, allowed_clients):
342
+ VERSION = b'1.0'
343
+ MECHANISM = b'CURVE'
344
+ while True:
345
+ try:
346
+ msg = zap_socket.recv_multipart()
347
+ except zmq.error.ContextTerminated:
348
+ zap_socket.close()
349
+ return
350
+ version, request_id, domain, address, identity, mechanism = msg[:6]
351
+ credentials = msg[6:]
352
+ if version != VERSION:
353
+ status_code = b"400"
354
+ status_text = b"Invalid version"
355
+ user_id = b""
356
+ elif mechanism != MECHANISM:
357
+ status_code = b"400"
358
+ status_text = b"Security mechanism not supported"
359
+ user_id = b""
360
+ elif domain != zap_domain:
361
+ status_code = b"400"
362
+ status_text = b"Unknown domain"
363
+ user_id = b""
364
+ else:
365
+ key = zmq.utils.z85.encode(credentials[0])
366
+ if key in allowed_clients:
367
+ status_code = b"200"
368
+ status_text = b"OK"
369
+ user_id = key
370
+ else:
371
+ status_code = b"400"
372
+ status_text = b"Unknown key"
373
+ user_id = b""
374
+ response = [VERSION, request_id, status_code, status_text, user_id, b""]
375
+ zap_socket.send_multipart(response)
376
+
377
+
309
378
  class SecureContext(zmq.Context):
310
379
  """A ZeroMQ Context with SecureContext.socket() returning a
311
380
  SecureSocket(), which can authenticate and communicate securely with all
@@ -315,7 +384,10 @@ class SecureContext(zmq.Context):
315
384
 
316
385
  _socket_class = SecureSocket
317
386
  _instances = weakref.WeakValueDictionary()
387
+ zap_domain = b"zprocess"
388
+
318
389
  # Dummy class attrs to distinguish from zmq options:
390
+ auth = None
319
391
  secure = False
320
392
  client_publickey = None
321
393
  client_secretkey = None
@@ -331,22 +403,15 @@ class SecureContext(zmq.Context):
331
403
  self.client_publickey = zmq.curve_public(self.client_secretkey)
332
404
  self.server_publickey = zmq.curve_public(self.server_secretkey)
333
405
 
334
- # There are potential reference cycles causing the authentication thread to
335
- # prevent the interpreter from shutting down. In pyzmq <25, the
336
- # authentication thread holds a reference to the context. So we must avoid
337
- # holding a reference to it. In pyzmq 25+, the authentication thread and
338
- # threadauthenticator objects both hold references to each other. So we
339
- # replace one with a weakref to break the cycle.
340
-
341
- auth = zmq.auth.thread.ThreadAuthenticator(self)
342
- auth.start()
343
-
344
- if auth.thread.authenticator is auth:
345
- auth.thread.authenticator = weakref.proxy(auth)
346
-
347
- # Allow only clients who have the client public key:
348
- auth.allow_any = False
349
- auth.certs['*'] = {self.client_publickey: True}
406
+ # Note: it is crucial we hold a reference to the authenticator, so that in
407
+ # the case the zap authentication thread crashes, the zap socket does not
408
+ # get cleaned up and closed - zmq will interpret that situation as us not
409
+ # requiring any authentication.
410
+ self.auth = _ThreadAuthenticator(
411
+ self,
412
+ zap_domain=self.zap_domain,
413
+ allowed_clients=[self.client_publickey],
414
+ )
350
415
  self.secure = True
351
416
 
352
417
  @classmethod
@@ -11,12 +11,14 @@
11
11
  # #
12
12
  #####################################################################
13
13
  from __future__ import print_function, division, absolute_import, unicode_literals
14
+
14
15
  try:
15
16
  from time import monotonic
16
17
  except ImportError:
17
18
  from time import time as monotonic
18
19
 
19
20
  from bisect import insort
21
+ import copy
20
22
 
21
23
  # This module contains a minimalistic task queue. It is used by the zlock and zlog
22
24
  # servers (zprocess.locking and zprocess.logging respectively) in order to wait for
@@ -42,23 +44,27 @@ from bisect import insort
42
44
  class Task(object):
43
45
  def __init__(self, due_in, func, *args, **kwargs):
44
46
  """Wrapper for a function call to be executed after a specified time interval.
45
- due_in is how long in the future, in seconds, the function should be called,
47
+ `due_in` is how long in the future, in seconds, the function should be called,
46
48
  func is the function to call. All subsequent arguments and keyword arguments
47
- will be passed to the function."""
49
+ will be passed to the function. If added to a TaskQueue with `repeat=True`, or
50
+ if Task.repeat is set to True, then the `due_in` argument is used as the
51
+ repetition interval."""
48
52
  self.due_at = monotonic() + due_in
53
+ self.interval = due_in
49
54
  self.func = func
50
55
  self.args = args
51
56
  self.kwargs = kwargs
52
- self.called = False
57
+ self._called = False
58
+ self.repeat = False
53
59
 
54
60
  def due_in(self):
55
61
  """The time interval in seconds until the task is due"""
56
62
  return self.due_at - monotonic()
57
63
 
58
64
  def __call__(self):
59
- if self.called:
65
+ if self._called:
60
66
  raise RuntimeError('Task has already been called')
61
- self.called = True
67
+ self._called = True
62
68
  return self.func(*self.args, **self.kwargs)
63
69
 
64
70
  def __gt__(self, other):
@@ -71,8 +77,25 @@ class TaskQueue(list):
71
77
  """A list of pending tasks due at certain times. Tasks are stored with the soonest
72
78
  due at the end of the list, to be removed with pop()"""
73
79
 
74
- def add(self, task):
75
- """Insert the task into the queue, maintaining sort order"""
80
+ def pop(self):
81
+ """Returns the next due task. If Task.repeat is True, re-adds a copy of the Task
82
+ to the queue with a due time `Task.interval` after its previous due time."""
83
+ task = super().pop()
84
+ if task.repeat:
85
+ next_rep = copy.copy(task)
86
+ # If next due time would be in the past, add a task due now instead. We
87
+ # don't want to a large number of tasks if e.g. system suspension led to a
88
+ # discontinuous jump forward in the monotonic clock
89
+ next_rep.due_at = max(monotonic(), task.due_at + task.interval)
90
+ self.add(next_rep)
91
+ return task
92
+
93
+ def add(self, task, repeat=None):
94
+ """Insert the task into the queue, maintaining sort order. If repeat is given,
95
+ this sets `Task.repeat`, which controls whether or not a copy of the task will
96
+ be re-added to the queue after it is removed via a call to `TaskQueue.pop()`."""
97
+ if repeat is not None:
98
+ task.repeat = repeat
76
99
  insort(self, task)
77
100
 
78
101
  def next(self):
@@ -80,4 +103,25 @@ class TaskQueue(list):
80
103
  return self[-1]
81
104
 
82
105
  def cancel(self, task):
83
- self.remove(task)
106
+ self.remove(task)
107
+
108
+
109
+ if __name__ == '__main__':
110
+ # Intended use. It's up the caller to sleep() or otherwise wait until the next task
111
+ # is due. It is assumed that the queue is only accessed from a single thread such
112
+ # that it's not possible for any tasks to be added to the queue during such a sleep.
113
+ # More usefully, instead of calling sleep(), an application might be calling
114
+ # select.select() or select.poll() with the time to the next task as the timeout.
115
+ # This way one can implement a simple mainloop for an event-driven application that
116
+ # can respond to network and file events as well as executing time-based tasks. This
117
+ # is how the mainloops of the zlock and zlog servers are implemented.
118
+ import time
119
+
120
+ tasks = TaskQueue()
121
+ task = Task(1, print, 'hello', 'world')
122
+
123
+ tasks.add(task, repeat=True)
124
+ while tasks:
125
+ time.sleep(max(0, tasks.next().due_in())) # don't pass sleep() a negative value
126
+ next_task = tasks.pop()
127
+ next_task()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zprocess
3
- Version: 2.22.0
3
+ Version: 2.24.0
4
4
  Summary: A set of utilities for multiprocessing using zeromq.
5
5
  Home-page: http://github.com/chrisjbillington/zprocess
6
6
  Author: Chris Billington
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes