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.
- {zprocess-2.22.0/zprocess.egg-info → zprocess-2.24.0}/PKG-INFO +1 -1
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/security.py +82 -17
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/tasks.py +52 -8
- {zprocess-2.22.0 → zprocess-2.24.0/zprocess.egg-info}/PKG-INFO +1 -1
- {zprocess-2.22.0 → zprocess-2.24.0}/.coveragerc +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/.github/workflows/release.yml +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/.gitignore +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/.travis.yml +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/LICENSE.txt +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/README.md +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/doc/Makefile +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/doc/conf.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/doc/index.rst +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/doc/requirements.txt +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/pyproject.toml +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/requirements.txt +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/run_tests.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/setup.cfg +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/setup.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/tests/GUI_output_redirection.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/tests/output_redirection.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/tests/test_remote.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/tests/test_zlock_server.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/tests/test_zlog_server.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/tests/tests.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/__init__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/__version__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/clientserver.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/cryptoproxy.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/examples/example_child.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/examples/example_parent.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/examples/processclass_example.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/examples/processclass_remote_example.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/locking/__init__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/locking/__main__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/makesecret.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/process_class_wrapper.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/process_tree.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/remote/__init__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/remote/__main__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/remote/curses_server.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/remote/server.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/socks/__init__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/socks/server.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/utils.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlock/__init__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlock/__main__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlock/server.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlog/__init__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlog/__main__.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess/zlog/server.py +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/SOURCES.txt +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/dependency_links.txt +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/not-zip-safe +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/requires.txt +0 -0
- {zprocess-2.22.0 → zprocess-2.24.0}/zprocess.egg-info/top_level.txt +0 -0
|
@@ -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
|
-
#
|
|
335
|
-
#
|
|
336
|
-
#
|
|
337
|
-
#
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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.
|
|
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.
|
|
65
|
+
if self._called:
|
|
60
66
|
raise RuntimeError('Task has already been called')
|
|
61
|
-
self.
|
|
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
|
|
75
|
-
"""
|
|
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()
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|