appose 0.2.0__py3-none-any.whl → 0.4.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.
- appose/__init__.py +90 -6
- appose/environment.py +1 -1
- appose/paths.py +1 -1
- appose/python_worker.py +71 -14
- appose/service.py +34 -14
- appose/types.py +56 -10
- {appose-0.2.0.dist-info → appose-0.4.0.dist-info}/METADATA +16 -15
- appose-0.4.0.dist-info/RECORD +11 -0
- {appose-0.2.0.dist-info → appose-0.4.0.dist-info}/WHEEL +1 -1
- {appose-0.2.0.dist-info → appose-0.4.0.dist-info/licenses}/LICENSE.txt +1 -1
- appose-0.2.0.dist-info/RECORD +0 -11
- {appose-0.2.0.dist-info → appose-0.4.0.dist-info}/top_level.txt +0 -0
appose/__init__.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# #%L
|
3
3
|
# Appose: multi-language interprocess cooperation with shared memory.
|
4
4
|
# %%
|
5
|
-
# Copyright (C) 2023 -
|
5
|
+
# Copyright (C) 2023 - 2025 Appose developers.
|
6
6
|
# %%
|
7
7
|
# Redistribution and use in source and binary forms, with or without
|
8
8
|
# modification, are permitted provided that the following conditions are met:
|
@@ -42,8 +42,6 @@ The steps for using Appose are:
|
|
42
42
|
|
43
43
|
## Examples
|
44
44
|
|
45
|
-
* TODO - move the below code somewhere linkable, for succinctness here.
|
46
|
-
|
47
45
|
Here is a very simple example written in Python:
|
48
46
|
|
49
47
|
import appose
|
@@ -84,7 +82,7 @@ And here is an example using a few more of Appose's features:
|
|
84
82
|
def task_listener(event):
|
85
83
|
match event.responseType:
|
86
84
|
case UPDATE:
|
87
|
-
print(f"Progress {
|
85
|
+
print(f"Progress {event.current}/{event.maximum}")
|
88
86
|
case COMPLETION:
|
89
87
|
numer = task.outputs["numer"]
|
90
88
|
denom = task.outputs["denom"]
|
@@ -127,8 +125,94 @@ But Appose is compatible with any program that abides by the
|
|
127
125
|
2. The worker must issue responses in Appose's response format on its
|
128
126
|
standard output (stdout) stream.
|
129
127
|
|
130
|
-
|
131
|
-
|
128
|
+
### Requests to worker from service
|
129
|
+
|
130
|
+
A *request* is a single line of JSON sent to the worker process via
|
131
|
+
its standard input stream. It has a `task` key taking the form of a
|
132
|
+
UUID, and a `requestType` key with one of the following values:
|
133
|
+
|
134
|
+
#### EXECUTE
|
135
|
+
|
136
|
+
Asynchronously execute a script within the worker process. E.g.:
|
137
|
+
|
138
|
+
{
|
139
|
+
"task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
|
140
|
+
"requestType" : "EXECUTE",
|
141
|
+
"script" : "task.outputs[\"result\"] = computeResult(gamma)\n",
|
142
|
+
"inputs" : {"gamma": 2.2}
|
143
|
+
}
|
144
|
+
|
145
|
+
#### CANCEL
|
146
|
+
|
147
|
+
Cancel a running script. E.g.:
|
148
|
+
|
149
|
+
{
|
150
|
+
"task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
|
151
|
+
"requestType" : "CANCEL"
|
152
|
+
}
|
153
|
+
|
154
|
+
### Responses from worker to service
|
155
|
+
|
156
|
+
A *response* is a single line of JSON with a `task` key taking the form
|
157
|
+
of a UUID, and a `responseType` key with one of the following values:
|
158
|
+
|
159
|
+
#### LAUNCH
|
160
|
+
|
161
|
+
A LAUNCH response is issued to confirm the success of an EXECUTE
|
162
|
+
request.
|
163
|
+
|
164
|
+
{
|
165
|
+
"task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
|
166
|
+
"responseType" : "LAUNCH"
|
167
|
+
}
|
168
|
+
|
169
|
+
#### UPDATE
|
170
|
+
|
171
|
+
An UPDATE response is issued to convey that a task has somehow made
|
172
|
+
progress. The UPDATE response typically comes bundled with a
|
173
|
+
`message` string indicating what has changed, `current` and/or
|
174
|
+
`maximum` progress indicators conveying the step the task has
|
175
|
+
reached, or both.
|
176
|
+
|
177
|
+
{
|
178
|
+
"task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
|
179
|
+
"responseType" : "UPDATE",
|
180
|
+
"message" : "Processing step 0 of 91",
|
181
|
+
"current" : 0,
|
182
|
+
"maximum" : 91
|
183
|
+
}
|
184
|
+
|
185
|
+
#### COMPLETION
|
186
|
+
|
187
|
+
A COMPLETION response is issued to convey that a task has successfully
|
188
|
+
completed execution, as well as report the values of any task outputs.
|
189
|
+
|
190
|
+
{
|
191
|
+
"task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
|
192
|
+
"responseType" : "COMPLETION",
|
193
|
+
"outputs" : {"result" : 91}
|
194
|
+
}
|
195
|
+
|
196
|
+
#### CANCELATION
|
197
|
+
|
198
|
+
A CANCELATION response is issued to confirm the success of a CANCEL
|
199
|
+
request.
|
200
|
+
|
201
|
+
{
|
202
|
+
"task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
|
203
|
+
"responseType" : "CANCELATION"
|
204
|
+
}
|
205
|
+
|
206
|
+
#### FAILURE
|
207
|
+
|
208
|
+
A FAILURE response is issued to convey that a task did not completely
|
209
|
+
and successfully execute, such as an exception being raised.
|
210
|
+
|
211
|
+
{
|
212
|
+
"task" : "87427f91-d193-4b25-8d35-e1292a34b5c4",
|
213
|
+
"responseType" : "FAILURE",
|
214
|
+
"error", "Invalid gamma value"
|
215
|
+
}
|
132
216
|
'''
|
133
217
|
|
134
218
|
from pathlib import Path
|
appose/environment.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# #%L
|
3
3
|
# Appose: multi-language interprocess cooperation with shared memory.
|
4
4
|
# %%
|
5
|
-
# Copyright (C) 2023 -
|
5
|
+
# Copyright (C) 2023 - 2025 Appose developers.
|
6
6
|
# %%
|
7
7
|
# Redistribution and use in source and binary forms, with or without
|
8
8
|
# modification, are permitted provided that the following conditions are met:
|
appose/paths.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# #%L
|
3
3
|
# Appose: multi-language interprocess cooperation with shared memory.
|
4
4
|
# %%
|
5
|
-
# Copyright (C) 2023 -
|
5
|
+
# Copyright (C) 2023 - 2025 Appose developers.
|
6
6
|
# %%
|
7
7
|
# Redistribution and use in source and binary forms, with or without
|
8
8
|
# modification, are permitted provided that the following conditions are met:
|
appose/python_worker.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# #%L
|
3
3
|
# Appose: multi-language interprocess cooperation with shared memory.
|
4
4
|
# %%
|
5
|
-
# Copyright (C) 2023 -
|
5
|
+
# Copyright (C) 2023 - 2025 Appose developers.
|
6
6
|
# %%
|
7
7
|
# Redistribution and use in source and binary forms, with or without
|
8
8
|
# modification, are permitted provided that the following conditions are met:
|
@@ -42,7 +42,8 @@ import ast
|
|
42
42
|
import sys
|
43
43
|
import traceback
|
44
44
|
from threading import Thread
|
45
|
-
from
|
45
|
+
from time import sleep
|
46
|
+
from typing import Any, Dict, Optional
|
46
47
|
|
47
48
|
# NB: Avoid relative imports so that this script can be run standalone.
|
48
49
|
from appose.service import RequestType, ResponseType
|
@@ -53,13 +54,16 @@ class Task:
|
|
53
54
|
def __init__(self, uuid: str) -> None:
|
54
55
|
self.uuid = uuid
|
55
56
|
self.outputs = {}
|
57
|
+
self.finished = False
|
56
58
|
self.cancel_requested = False
|
59
|
+
self.thread = None # Initialize thread attribute
|
57
60
|
|
58
61
|
def update(
|
59
62
|
self,
|
60
63
|
message: Optional[str] = None,
|
61
64
|
current: Optional[int] = None,
|
62
65
|
maximum: Optional[int] = None,
|
66
|
+
info: Optional[Dict[str, Any]] = None,
|
63
67
|
) -> None:
|
64
68
|
args = {}
|
65
69
|
if message is not None:
|
@@ -74,6 +78,7 @@ class Task:
|
|
74
78
|
args["maximum"] = int(maximum)
|
75
79
|
except ValueError:
|
76
80
|
pass
|
81
|
+
args["info"] = info
|
77
82
|
self._respond(ResponseType.UPDATE, args)
|
78
83
|
|
79
84
|
def cancel(self) -> None:
|
@@ -111,7 +116,14 @@ class Task:
|
|
111
116
|
# Last statement of the script looks like an expression. Evaluate!
|
112
117
|
last = ast.Expression(block.body.pop().value)
|
113
118
|
|
114
|
-
|
119
|
+
# NB: When `exec` gets two separate objects as *globals* and
|
120
|
+
# *locals*, the code will be executed as if it were embedded in
|
121
|
+
# a class definition. This means functions and classes defined
|
122
|
+
# in the executed code will not be able to access variables
|
123
|
+
# assigned at the top level, because the "top level" variables
|
124
|
+
# are treated as class variables in a class definition.
|
125
|
+
# See: https://docs.python.org/3/library/functions.html#exec
|
126
|
+
_globals = binding
|
115
127
|
exec(compile(block, "<string>", mode="exec"), _globals, binding)
|
116
128
|
if last is not None:
|
117
129
|
result = eval(
|
@@ -131,10 +143,24 @@ class Task:
|
|
131
143
|
|
132
144
|
self._report_completion()
|
133
145
|
|
134
|
-
#
|
135
|
-
#
|
136
|
-
#
|
137
|
-
|
146
|
+
# HACK: Pre-load toplevel import statements before running the script
|
147
|
+
# as a whole on its own Thread. Why? Because on Windows, some imports
|
148
|
+
# (e.g. numpy) may lead to hangs if loaded from a separate thread.
|
149
|
+
# See https://github.com/apposed/appose/issues/13.
|
150
|
+
block = ast.parse(script, mode="exec")
|
151
|
+
import_nodes = [
|
152
|
+
node
|
153
|
+
for node in block.body
|
154
|
+
if isinstance(node, (ast.Import, ast.ImportFrom))
|
155
|
+
]
|
156
|
+
import_block = ast.Module(body=import_nodes, type_ignores=[])
|
157
|
+
compiled_imports = compile(import_block, filename="<imports>", mode="exec")
|
158
|
+
exec(compiled_imports, globals())
|
159
|
+
|
160
|
+
# Create a thread and save a reference to it, in case its script
|
161
|
+
# ends up killing the thread. This happens e.g. if it calls sys.exit.
|
162
|
+
self.thread = Thread(target=execute_script, name=f"Appose-{self.uuid}")
|
163
|
+
self.thread.start()
|
138
164
|
|
139
165
|
def _report_launch(self) -> None:
|
140
166
|
self._respond(ResponseType.LAUNCH, None)
|
@@ -144,20 +170,31 @@ class Task:
|
|
144
170
|
self._respond(ResponseType.COMPLETION, args)
|
145
171
|
|
146
172
|
def _respond(self, response_type: ResponseType, args: Optional[Args]) -> None:
|
147
|
-
|
173
|
+
already_terminated = False
|
174
|
+
if response_type.is_terminal():
|
175
|
+
if self.finished:
|
176
|
+
# This is not the first terminal response. Let's
|
177
|
+
# remember, in case an exception is generated below,
|
178
|
+
# so that we can avoid infinite recursion loops.
|
179
|
+
already_terminated = True
|
180
|
+
self.finished = True
|
181
|
+
|
182
|
+
response = {}
|
148
183
|
if args is not None:
|
149
184
|
response.update(args)
|
185
|
+
response.update({"task": self.uuid, "responseType": response_type.value})
|
150
186
|
# NB: Flush is necessary to ensure service receives the data!
|
151
187
|
try:
|
152
188
|
print(encode(response), flush=True)
|
153
189
|
except Exception:
|
154
|
-
|
155
|
-
|
190
|
+
if already_terminated:
|
191
|
+
# An exception triggered a failure response which
|
192
|
+
# then triggered another exception. Let's stop here
|
193
|
+
# to avoid the risk of infinite recursion loops.
|
194
|
+
return
|
195
|
+
# Encoding can fail due to unsupported types, when the
|
196
|
+
# response or its elements are not supported by JSON encoding.
|
156
197
|
# No matter what goes wrong, we want to tell the caller.
|
157
|
-
if response_type is ResponseType.FAILURE:
|
158
|
-
# TODO: How to address this hypothetical case
|
159
|
-
# of a failure message triggering another failure?
|
160
|
-
raise
|
161
198
|
self.fail(traceback.format_exc())
|
162
199
|
|
163
200
|
|
@@ -165,6 +202,24 @@ def main() -> None:
|
|
165
202
|
_set_worker(True)
|
166
203
|
|
167
204
|
tasks = {}
|
205
|
+
running = True
|
206
|
+
|
207
|
+
def cleanup_threads():
|
208
|
+
while running:
|
209
|
+
sleep(0.05)
|
210
|
+
dead = {
|
211
|
+
uuid: task
|
212
|
+
for uuid, task in tasks.items()
|
213
|
+
if task.thread is not None and not task.thread.is_alive()
|
214
|
+
}
|
215
|
+
for uuid, task in dead.items():
|
216
|
+
tasks.pop(uuid)
|
217
|
+
if not task.finished:
|
218
|
+
# The task died before reporting a terminal status.
|
219
|
+
# We report this situation as failure by thread death.
|
220
|
+
task.fail("thread death")
|
221
|
+
|
222
|
+
Thread(target=cleanup_threads, name="Appose-Janitor").start()
|
168
223
|
|
169
224
|
while True:
|
170
225
|
try:
|
@@ -193,6 +248,8 @@ def main() -> None:
|
|
193
248
|
continue
|
194
249
|
task.cancel_requested = True
|
195
250
|
|
251
|
+
running = False
|
252
|
+
|
196
253
|
|
197
254
|
if __name__ == "__main__":
|
198
255
|
main()
|
appose/service.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# #%L
|
3
3
|
# Appose: multi-language interprocess cooperation with shared memory.
|
4
4
|
# %%
|
5
|
-
# Copyright (C) 2023 -
|
5
|
+
# Copyright (C) 2023 - 2025 Appose developers.
|
6
6
|
# %%
|
7
7
|
# Redistribution and use in source and binary forms, with or without
|
8
8
|
# modification, are permitted provided that the following conditions are met:
|
@@ -256,11 +256,35 @@ class ResponseType(Enum):
|
|
256
256
|
FAILURE = "FAILURE"
|
257
257
|
CRASH = "CRASH"
|
258
258
|
|
259
|
+
"""
|
260
|
+
True iff response type is COMPLETE, CANCELED, FAILED, or CRASHED.
|
261
|
+
"""
|
262
|
+
|
263
|
+
def is_terminal(self):
|
264
|
+
return self in (
|
265
|
+
ResponseType.COMPLETION,
|
266
|
+
ResponseType.CANCELATION,
|
267
|
+
ResponseType.FAILURE,
|
268
|
+
ResponseType.CRASH,
|
269
|
+
)
|
270
|
+
|
259
271
|
|
260
272
|
class TaskEvent:
|
261
|
-
def __init__(
|
273
|
+
def __init__(
|
274
|
+
self,
|
275
|
+
task: "Task",
|
276
|
+
response_type: ResponseType,
|
277
|
+
message: Optional[str] = None,
|
278
|
+
current: Optional[int] = None,
|
279
|
+
maximum: Optional[int] = None,
|
280
|
+
info: Optional[Dict[str, Any]] = None,
|
281
|
+
) -> None:
|
262
282
|
self.task: "Task" = task
|
263
283
|
self.response_type: ResponseType = response_type
|
284
|
+
self.message: Optional[str] = message
|
285
|
+
self.current: Optional[int] = current
|
286
|
+
self.maximum: Optional[int] = maximum
|
287
|
+
self.info: Optional[Dict[str, Any]] = info
|
264
288
|
|
265
289
|
def __str__(self):
|
266
290
|
return f"[{self.response_type}] {self.task}"
|
@@ -354,13 +378,8 @@ class Task:
|
|
354
378
|
case ResponseType.LAUNCH:
|
355
379
|
self.status = TaskStatus.RUNNING
|
356
380
|
case ResponseType.UPDATE:
|
357
|
-
|
358
|
-
|
359
|
-
maximum = response.get("maximum")
|
360
|
-
if current is not None:
|
361
|
-
self.current = int(current)
|
362
|
-
if maximum is not None:
|
363
|
-
self.maximum = int(maximum)
|
381
|
+
# No extra action needed.
|
382
|
+
pass
|
364
383
|
case ResponseType.COMPLETION:
|
365
384
|
self.service._tasks.pop(self.uuid, None)
|
366
385
|
self.status = TaskStatus.COMPLETE
|
@@ -380,7 +399,11 @@ class Task:
|
|
380
399
|
)
|
381
400
|
return
|
382
401
|
|
383
|
-
|
402
|
+
message = response.get("message")
|
403
|
+
current = response.get("current")
|
404
|
+
maximum = response.get("maximum")
|
405
|
+
info = response.get("info")
|
406
|
+
event = TaskEvent(self, response_type, message, current, maximum, info)
|
384
407
|
for listener in self.listeners:
|
385
408
|
listener(event)
|
386
409
|
|
@@ -397,7 +420,4 @@ class Task:
|
|
397
420
|
self.cv.notify_all()
|
398
421
|
|
399
422
|
def __str__(self):
|
400
|
-
return
|
401
|
-
f"{self.uuid=}, {self.status=}, {self.message=}, "
|
402
|
-
f"{self.current=}, {self.maximum=}, {self.error=}"
|
403
|
-
)
|
423
|
+
return f"{self.uuid=}, {self.status=}, {self.error=}"
|
appose/types.py
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
# #%L
|
3
3
|
# Appose: multi-language interprocess cooperation with shared memory.
|
4
4
|
# %%
|
5
|
-
# Copyright (C) 2023 -
|
5
|
+
# Copyright (C) 2023 - 2025 Appose developers.
|
6
6
|
# %%
|
7
7
|
# Redistribution and use in source and binary forms, with or without
|
8
8
|
# modification, are permitted provided that the following conditions are met:
|
@@ -45,13 +45,29 @@ class SharedMemory(shared_memory.SharedMemory):
|
|
45
45
|
`unlink_on_dispose` flag.
|
46
46
|
"""
|
47
47
|
|
48
|
-
def __init__(self, name: str = None, create: bool = False,
|
49
|
-
|
48
|
+
def __init__(self, name: str = None, create: bool = False, rsize: int = 0):
|
49
|
+
"""
|
50
|
+
Create a new shared memory block, or attach to an existing one.
|
51
|
+
|
52
|
+
:param name:
|
53
|
+
The unique name for the requested shared memory, specified as a
|
54
|
+
string. If create is True (i.e. a new shared memory block) and
|
55
|
+
no name is given, a novel name will be generated.
|
56
|
+
:param create:
|
57
|
+
Whether a new shared memory block is created (True)
|
58
|
+
or an existing one is attached to (False).
|
59
|
+
:param rsize:
|
60
|
+
Requested size in bytes. The true allocated size will be at least
|
61
|
+
this much, but may be rounded up to the next block size multiple,
|
62
|
+
depending on the running platform.
|
63
|
+
"""
|
64
|
+
super().__init__(name=name, create=create, size=rsize)
|
65
|
+
self.rsize = rsize
|
50
66
|
self._unlink_on_dispose = create
|
51
67
|
if _is_worker:
|
52
68
|
# HACK: Remove this shared memory block from the resource_tracker,
|
53
|
-
# which
|
54
|
-
# references are done using them.
|
69
|
+
# which would otherwise want to clean up shared memory blocks
|
70
|
+
# after all known references are done using them.
|
55
71
|
#
|
56
72
|
# There is one resource_tracker per Python process, and they will
|
57
73
|
# each try to delete shared memory blocks known to them when they
|
@@ -60,7 +76,37 @@ class SharedMemory(shared_memory.SharedMemory):
|
|
60
76
|
# As such, the rule Appose follows is: let the service process
|
61
77
|
# always handle cleanup of shared memory blocks, regardless of
|
62
78
|
# which process initially allocated it.
|
63
|
-
|
79
|
+
try:
|
80
|
+
resource_tracker.unregister(self._name, "shared_memory")
|
81
|
+
except ModuleNotFoundError:
|
82
|
+
# Unfortunately, on (some?) Windows systems, we see the error:
|
83
|
+
#
|
84
|
+
# Traceback (most recent call last): # noqa: E501
|
85
|
+
# File "...\site-packages\appose\types.py", line 97, in decode # noqa: E501
|
86
|
+
# return json.loads(the_json, object_hook=_appose_object_hook) # noqa: E501
|
87
|
+
# File "...\lib\json\__init__.py", line 359, in loads # noqa: E501
|
88
|
+
# return cls(**kw).decode(s) # noqa: E501
|
89
|
+
# File "...\lib\json\decoder.py", line 337, in decode # noqa: E501
|
90
|
+
# obj, end = self.raw_decode(s, idx=_w(s, 0).end()) # noqa: E501
|
91
|
+
# File "...\lib\json\decoder.py", line 353, in raw_decode # noqa: E501
|
92
|
+
# obj, end = self.scan_once(s, idx) # noqa: E501
|
93
|
+
# File "...\site-packages\appose\types.py", line 177, in _appose_object_hook # noqa: E501
|
94
|
+
# return SharedMemory(name=(obj["name"]), size=(obj["size"])) # noqa: E501
|
95
|
+
# File "...\site-packages\appose\types.py", line 63, in __init__ # noqa: E501
|
96
|
+
# resource_tracker.unregister(self._name, "shared_memory") # noqa: E501
|
97
|
+
# File "...\lib\multiprocessing\resource_tracker.py", line 159, in unregister # noqa: E501
|
98
|
+
# self._send('UNREGISTER', name, rtype) # noqa: E501
|
99
|
+
# File "...\lib\multiprocessing\resource_tracker.py", line 162, in _send # noqa: E501
|
100
|
+
# self.ensure_running() # noqa: E501
|
101
|
+
# File "...\lib\multiprocessing\resource_tracker.py", line 129, in ensure_running # noqa: E501
|
102
|
+
# pid = util.spawnv_passfds(exe, args, fds_to_pass) # noqa: E501
|
103
|
+
# File "...\lib\multiprocessing\util.py", line 448, in spawnv_passfds # noqa: E501
|
104
|
+
# import _posixsubprocess # noqa: E501
|
105
|
+
# ModuleNotFoundError: No module named '_posixsubprocess' # noqa: E501
|
106
|
+
#
|
107
|
+
# A bug in Python? Regardless: we guard against it here.
|
108
|
+
# See also: https://github.com/imglib/imglib2-appose/issues/1
|
109
|
+
pass
|
64
110
|
|
65
111
|
def unlink_on_dispose(self, value: bool) -> None:
|
66
112
|
"""
|
@@ -116,7 +162,7 @@ class NDArray:
|
|
116
162
|
self.shape = shape
|
117
163
|
self.shm = (
|
118
164
|
SharedMemory(
|
119
|
-
create=True,
|
165
|
+
create=True, rsize=ceil(prod(shape) * _bytes_per_element(dtype))
|
120
166
|
)
|
121
167
|
if shm is None
|
122
168
|
else shm
|
@@ -127,7 +173,7 @@ class NDArray:
|
|
127
173
|
f"NDArray("
|
128
174
|
f"dtype='{self.dtype}', "
|
129
175
|
f"shape={self.shape}, "
|
130
|
-
f"shm='{self.shm.name}' ({self.shm.
|
176
|
+
f"shm='{self.shm.name}' ({self.shm.rsize}))"
|
131
177
|
)
|
132
178
|
|
133
179
|
def ndarray(self):
|
@@ -158,7 +204,7 @@ class _ApposeJSONEncoder(json.JSONEncoder):
|
|
158
204
|
return {
|
159
205
|
"appose_type": "shm",
|
160
206
|
"name": obj.name,
|
161
|
-
"
|
207
|
+
"rsize": obj.rsize,
|
162
208
|
}
|
163
209
|
if isinstance(obj, NDArray):
|
164
210
|
return {
|
@@ -174,7 +220,7 @@ def _appose_object_hook(obj: Dict):
|
|
174
220
|
atype = obj.get("appose_type")
|
175
221
|
if atype == "shm":
|
176
222
|
# Attach to existing shared memory block.
|
177
|
-
return SharedMemory(name=(obj["name"]),
|
223
|
+
return SharedMemory(name=(obj["name"]), rsize=(obj["rsize"]))
|
178
224
|
elif atype == "ndarray":
|
179
225
|
return NDArray(obj["dtype"], obj["shape"], obj["shm"])
|
180
226
|
else:
|
@@ -1,9 +1,9 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: appose
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Appose: multi-language interprocess cooperation with shared memory.
|
5
5
|
Author: Appose developers
|
6
|
-
License:
|
6
|
+
License-Expression: BSD-2-Clause
|
7
7
|
Project-URL: homepage, https://github.com/apposed/appose-python
|
8
8
|
Project-URL: documentation, https://github.com/apposed/appose-python/blob/main/README.md
|
9
9
|
Project-URL: source, https://github.com/apposed/appose-python
|
@@ -17,7 +17,7 @@ Classifier: Intended Audience :: Science/Research
|
|
17
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
18
18
|
Classifier: Programming Language :: Python :: 3.10
|
19
19
|
Classifier: Programming Language :: Python :: 3.11
|
20
|
-
Classifier:
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
21
21
|
Classifier: Operating System :: Microsoft :: Windows
|
22
22
|
Classifier: Operating System :: Unix
|
23
23
|
Classifier: Operating System :: MacOS
|
@@ -29,17 +29,18 @@ Requires-Python: >=3.10
|
|
29
29
|
Description-Content-Type: text/markdown
|
30
30
|
License-File: LICENSE.txt
|
31
31
|
Provides-Extra: dev
|
32
|
-
Requires-Dist: autopep8
|
33
|
-
Requires-Dist: black
|
34
|
-
Requires-Dist: build
|
35
|
-
Requires-Dist: flake8
|
36
|
-
Requires-Dist: flake8-pyproject
|
37
|
-
Requires-Dist: flake8-typing-imports
|
38
|
-
Requires-Dist: isort
|
39
|
-
Requires-Dist: pytest
|
40
|
-
Requires-Dist: numpy
|
41
|
-
Requires-Dist: toml
|
42
|
-
Requires-Dist: validate-pyproject[all]
|
32
|
+
Requires-Dist: autopep8; extra == "dev"
|
33
|
+
Requires-Dist: black; extra == "dev"
|
34
|
+
Requires-Dist: build; extra == "dev"
|
35
|
+
Requires-Dist: flake8; extra == "dev"
|
36
|
+
Requires-Dist: flake8-pyproject; extra == "dev"
|
37
|
+
Requires-Dist: flake8-typing-imports; extra == "dev"
|
38
|
+
Requires-Dist: isort; extra == "dev"
|
39
|
+
Requires-Dist: pytest; extra == "dev"
|
40
|
+
Requires-Dist: numpy; extra == "dev"
|
41
|
+
Requires-Dist: toml; extra == "dev"
|
42
|
+
Requires-Dist: validate-pyproject[all]; extra == "dev"
|
43
|
+
Dynamic: license-file
|
43
44
|
|
44
45
|
# Appose Python
|
45
46
|
|
@@ -0,0 +1,11 @@
|
|
1
|
+
appose/__init__.py,sha256=SRXmNEqFIAajVlwVCSAdhHQytRKZJiWI5Y5-xwioW0w,7544
|
2
|
+
appose/environment.py,sha256=Kg-MfAcmVPDltwqvmrcvus-pP01SNUdn4bE2kdkIVuk,7597
|
3
|
+
appose/paths.py,sha256=WgBHwnZdS3Ot8_hssFqUiTGnunolYRrHLf9rAfEU5r4,2182
|
4
|
+
appose/python_worker.py,sha256=iQoz2t0glbXlR1lgynN-yXPIgtlAbCFtMYYpev-LZyU,9844
|
5
|
+
appose/service.py,sha256=UQn1aoL3uQ8gPKS6nf5oYg0Ea1ZRsaQ3TfLCKeCZLPQ,14581
|
6
|
+
appose/types.py,sha256=uidbt2JwWJ2D3Mbtc4Vl2sRP6WNgKjmfcHjJ9QLWkcs,10557
|
7
|
+
appose-0.4.0.dist-info/licenses/LICENSE.txt,sha256=ZedidA6NyZclNlsx-vl9IVBWRAQSqXdNsCWgtFcwzIE,1313
|
8
|
+
appose-0.4.0.dist-info/METADATA,sha256=cI_E4xetwcHlusk2UzSPOk6SWrNr_vBrGjOC_0H4CFo,5598
|
9
|
+
appose-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
10
|
+
appose-0.4.0.dist-info/top_level.txt,sha256=oqHhw2QGlaFfH3jMR9H5Cd6XYHdEv5DvK1am0iEFPW0,7
|
11
|
+
appose-0.4.0.dist-info/RECORD,,
|
appose-0.2.0.dist-info/RECORD
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
appose/__init__.py,sha256=sGkVQv1eZQp0BOoySsVOGcVMr47rE3PSyqxsk8_EOn4,5404
|
2
|
-
appose/environment.py,sha256=8BBvRIzr6ZIjBeZMQYOHG6Va1gSxMkYc3ieJ9HafeXA,7597
|
3
|
-
appose/paths.py,sha256=dcXD54rOUiCWfM58vqttRY0Eu6YUFMD65ZO_2V4hhEk,2182
|
4
|
-
appose/python_worker.py,sha256=jhe_iFnCS7vJ_N1EycDTt6oq09JhYIOnvvhyqnLG40U,7335
|
5
|
-
appose/service.py,sha256=v5u_HAliJlsoghwC6QIn3CZV2YpnPU8V3GdBcjgA62w,14070
|
6
|
-
appose/types.py,sha256=m5mX5BhdR7REnkSCuP5fGnnQWc_o7qQDyOFHWND8Vvs,6975
|
7
|
-
appose-0.2.0.dist-info/LICENSE.txt,sha256=LcTntIsPpu7JOZfEBAVslv-PVV4vnD9tfvXrS4cROe8,1313
|
8
|
-
appose-0.2.0.dist-info/METADATA,sha256=lqL66Xs9gTSJLr_ISb7ge8lK80xSQh04_Amfsf1jQWg,5586
|
9
|
-
appose-0.2.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
10
|
-
appose-0.2.0.dist-info/top_level.txt,sha256=oqHhw2QGlaFfH3jMR9H5Cd6XYHdEv5DvK1am0iEFPW0,7
|
11
|
-
appose-0.2.0.dist-info/RECORD,,
|
File without changes
|