appose 0.1.0__py3-none-any.whl → 0.2.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 +4 -3
- appose/environment.py +5 -3
- appose/paths.py +2 -2
- appose/python_worker.py +32 -10
- appose/service.py +72 -13
- appose/types.py +160 -4
- {appose-0.1.0.dist-info → appose-0.2.0.dist-info}/LICENSE.txt +1 -1
- {appose-0.1.0.dist-info → appose-0.2.0.dist-info}/METADATA +21 -15
- appose-0.2.0.dist-info/RECORD +11 -0
- {appose-0.1.0.dist-info → appose-0.2.0.dist-info}/WHEEL +1 -1
- appose-0.1.0.dist-info/RECORD +0 -11
- {appose-0.1.0.dist-info → appose-0.2.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 Appose developers.
|
5
|
+
# Copyright (C) 2023 - 2024 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:
|
@@ -52,7 +52,7 @@ Here is a very simple example written in Python:
|
|
52
52
|
Task task = groovy.task("""
|
53
53
|
5 + 6
|
54
54
|
""")
|
55
|
-
task.
|
55
|
+
task.wait_for()
|
56
56
|
result = task.outputs.get("result")
|
57
57
|
assert 11 == result
|
58
58
|
|
@@ -103,7 +103,7 @@ And here is an example using a few more of Appose's features:
|
|
103
103
|
# Task is taking too long; request a cancelation.
|
104
104
|
task.cancel()
|
105
105
|
|
106
|
-
task.
|
106
|
+
task.wait_for()
|
107
107
|
|
108
108
|
Of course, the above examples could have been done all in Python. But
|
109
109
|
hopefully they hint at the possibilities of easy cross-language integration.
|
@@ -134,6 +134,7 @@ JSON, one line per request/response.
|
|
134
134
|
from pathlib import Path
|
135
135
|
|
136
136
|
from .environment import Builder, Environment
|
137
|
+
from .types import NDArray, SharedMemory # noqa: F401
|
137
138
|
|
138
139
|
|
139
140
|
def base(directory: Path) -> Builder:
|
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 Appose developers.
|
5
|
+
# Copyright (C) 2023 - 2024 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:
|
@@ -58,6 +58,7 @@ class Environment:
|
|
58
58
|
"""
|
59
59
|
python_exes = [
|
60
60
|
"python",
|
61
|
+
"python3",
|
61
62
|
"python.exe",
|
62
63
|
"bin/python",
|
63
64
|
"bin/python.exe",
|
@@ -109,8 +110,9 @@ class Environment:
|
|
109
110
|
# TODO: Ensure that the classpath includes Appose and its dependencies.
|
110
111
|
|
111
112
|
# Append any explicitly requested classpath elements.
|
112
|
-
|
113
|
-
|
113
|
+
if class_path is not None:
|
114
|
+
for element in class_path:
|
115
|
+
cp[element] = None
|
114
116
|
|
115
117
|
# Build up the service arguments.
|
116
118
|
args = [
|
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 Appose developers.
|
5
|
+
# Copyright (C) 2023 - 2024 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:
|
@@ -51,6 +51,6 @@ def find_exe(dirs: Sequence[str], exes: Sequence[str]) -> Optional[Path]:
|
|
51
51
|
# Candidate is a relative path; check beneath each given directory.
|
52
52
|
for d in dirs:
|
53
53
|
f = Path(d) / exe
|
54
|
-
if can_execute(f):
|
54
|
+
if can_execute(f) and not f.is_dir():
|
55
55
|
return f
|
56
56
|
return None
|
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 Appose developers.
|
5
|
+
# Copyright (C) 2023 - 2024 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:
|
@@ -28,7 +28,14 @@
|
|
28
28
|
###
|
29
29
|
|
30
30
|
"""
|
31
|
-
|
31
|
+
The Appose worker for running Python scripts.
|
32
|
+
|
33
|
+
Like all Appose workers, this program conforms to the Appose worker process
|
34
|
+
contract, meaning it accepts requests on stdin and produces responses on
|
35
|
+
stdout, both formatted according to Appose's assumptions.
|
36
|
+
|
37
|
+
For details, see the Appose README:
|
38
|
+
https://github.com/apposed/appose/blob/-/README.md#workers
|
32
39
|
"""
|
33
40
|
|
34
41
|
import ast
|
@@ -39,7 +46,7 @@ from typing import Optional
|
|
39
46
|
|
40
47
|
# NB: Avoid relative imports so that this script can be run standalone.
|
41
48
|
from appose.service import RequestType, ResponseType
|
42
|
-
from appose.types import Args, decode, encode
|
49
|
+
from appose.types import Args, _set_worker, decode, encode
|
43
50
|
|
44
51
|
|
45
52
|
class Task:
|
@@ -56,11 +63,17 @@ class Task:
|
|
56
63
|
) -> None:
|
57
64
|
args = {}
|
58
65
|
if message is not None:
|
59
|
-
args["message"] = message
|
66
|
+
args["message"] = str(message)
|
60
67
|
if current is not None:
|
61
|
-
|
68
|
+
try:
|
69
|
+
args["current"] = int(current)
|
70
|
+
except ValueError:
|
71
|
+
pass
|
62
72
|
if maximum is not None:
|
63
|
-
|
73
|
+
try:
|
74
|
+
args["maximum"] = int(maximum)
|
75
|
+
except ValueError:
|
76
|
+
pass
|
64
77
|
self._respond(ResponseType.UPDATE, args)
|
65
78
|
|
66
79
|
def cancel(self) -> None:
|
@@ -74,7 +87,6 @@ class Task:
|
|
74
87
|
def execute_script():
|
75
88
|
# Populate script bindings.
|
76
89
|
binding = {"task": self}
|
77
|
-
# TODO: Magically convert shared memory image inputs.
|
78
90
|
if inputs is not None:
|
79
91
|
binding.update(inputs)
|
80
92
|
|
@@ -136,10 +148,22 @@ class Task:
|
|
136
148
|
if args is not None:
|
137
149
|
response.update(args)
|
138
150
|
# NB: Flush is necessary to ensure service receives the data!
|
139
|
-
|
151
|
+
try:
|
152
|
+
print(encode(response), flush=True)
|
153
|
+
except Exception:
|
154
|
+
# Encoding can fail due to unsupported types, when the response
|
155
|
+
# or its elements are not supported by JSON encoding.
|
156
|
+
# 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
|
+
self.fail(traceback.format_exc())
|
140
162
|
|
141
163
|
|
142
164
|
def main() -> None:
|
165
|
+
_set_worker(True)
|
166
|
+
|
143
167
|
tasks = {}
|
144
168
|
|
145
169
|
while True:
|
@@ -165,8 +189,6 @@ def main() -> None:
|
|
165
189
|
case RequestType.CANCEL:
|
166
190
|
task = tasks.get(uuid)
|
167
191
|
if task is None:
|
168
|
-
# TODO: proper logging
|
169
|
-
# Maybe should stdout the error back to Appose calling process.
|
170
192
|
print(f"No such task: {uuid}", file=sys.stderr)
|
171
193
|
continue
|
172
194
|
task.cancel_requested = True
|
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 Appose developers.
|
5
|
+
# Copyright (C) 2023 - 2024 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:
|
@@ -61,6 +61,7 @@ class Service:
|
|
61
61
|
self._process: Optional[subprocess.Popen] = None
|
62
62
|
self._stdout_thread: Optional[threading.Thread] = None
|
63
63
|
self._stderr_thread: Optional[threading.Thread] = None
|
64
|
+
self._monitor_thread: Optional[threading.Thread] = None
|
64
65
|
self._debug_callback: Optional[Callable[[Any], Any]] = None
|
65
66
|
|
66
67
|
def debug(self, debug_callback: Callable[[Any], Any]) -> None:
|
@@ -101,8 +102,12 @@ class Service:
|
|
101
102
|
self._stderr_thread = threading.Thread(
|
102
103
|
target=self._stderr_loop, name=f"{prefix}-Stderr"
|
103
104
|
)
|
105
|
+
self._monitor_thread = threading.Thread(
|
106
|
+
target=self._monitor_loop, name=f"{prefix}-Monitor"
|
107
|
+
)
|
104
108
|
self._stdout_thread.start()
|
105
109
|
self._stderr_thread.start()
|
110
|
+
self._monitor_thread.start()
|
106
111
|
|
107
112
|
def task(self, script: str, inputs: Optional[Args] = None) -> "Task":
|
108
113
|
"""
|
@@ -131,15 +136,24 @@ class Service:
|
|
131
136
|
"""
|
132
137
|
Input loop processing lines from the worker's stdout stream.
|
133
138
|
"""
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
+
while True:
|
140
|
+
stdout = self._process.stdout
|
141
|
+
# noinspection PyBroadException
|
142
|
+
try:
|
143
|
+
line = None if stdout is None else stdout.readline()
|
144
|
+
except Exception:
|
145
|
+
# Something went wrong reading the line. Panic!
|
146
|
+
self._debug_service(format_exc())
|
147
|
+
break
|
148
|
+
|
149
|
+
if not line: # readline returns empty string upon EOF
|
150
|
+
self._debug_service("<worker stdout closed>")
|
151
|
+
return
|
139
152
|
|
140
|
-
|
141
|
-
|
153
|
+
# noinspection PyBroadException
|
154
|
+
try:
|
142
155
|
response = decode(line)
|
156
|
+
self._debug_service(line) # Echo the line to the debug listener.
|
143
157
|
uuid = response.get("task")
|
144
158
|
if uuid is None:
|
145
159
|
self._debug_service("Invalid service message: {line}")
|
@@ -150,8 +164,10 @@ class Service:
|
|
150
164
|
continue
|
151
165
|
# noinspection PyProtectedMember
|
152
166
|
task._handle(response)
|
153
|
-
|
154
|
-
|
167
|
+
except Exception:
|
168
|
+
# Something went wrong decoding the line of JSON.
|
169
|
+
# Skip it and keep going, but log it first.
|
170
|
+
self._debug_service(f"<INVALID> {line}")
|
155
171
|
|
156
172
|
def _stderr_loop(self) -> None:
|
157
173
|
"""
|
@@ -160,14 +176,37 @@ class Service:
|
|
160
176
|
# noinspection PyBroadException
|
161
177
|
try:
|
162
178
|
while True:
|
163
|
-
|
164
|
-
if
|
179
|
+
stderr = self._process.stderr
|
180
|
+
line = None if stderr is None else stderr.readline()
|
181
|
+
if not line: # readline returns empty string upon EOF
|
165
182
|
self._debug_service("<worker stderr closed>")
|
166
183
|
return
|
167
184
|
self._debug_worker(line)
|
168
185
|
except Exception:
|
169
186
|
self._debug_service(format_exc())
|
170
187
|
|
188
|
+
def _monitor_loop(self) -> None:
|
189
|
+
# Wait until the worker process terminates.
|
190
|
+
self._process.wait()
|
191
|
+
|
192
|
+
# Do some sanity checks.
|
193
|
+
exit_code = self._process.returncode
|
194
|
+
if exit_code != 0:
|
195
|
+
self._debug_service(
|
196
|
+
f"<worker process terminated with exit code {exit_code}>"
|
197
|
+
)
|
198
|
+
task_count = len(self._tasks)
|
199
|
+
if task_count > 0:
|
200
|
+
self._debug_service(
|
201
|
+
f"<worker process terminated with {task_count} pending tasks>"
|
202
|
+
)
|
203
|
+
|
204
|
+
# Notify any remaining tasks about the process crash.
|
205
|
+
for task in self._tasks.values():
|
206
|
+
task._crash()
|
207
|
+
|
208
|
+
self._tasks.clear()
|
209
|
+
|
171
210
|
def _debug_service(self, message: str) -> None:
|
172
211
|
self._debug("SERVICE", message)
|
173
212
|
|
@@ -190,15 +229,17 @@ class TaskStatus(Enum):
|
|
190
229
|
COMPLETE = "COMPLETE"
|
191
230
|
CANCELED = "CANCELED"
|
192
231
|
FAILED = "FAILED"
|
232
|
+
CRASHED = "CRASHED"
|
193
233
|
|
194
234
|
def is_finished(self):
|
195
235
|
"""
|
196
|
-
True iff status is COMPLETE, CANCELED, or
|
236
|
+
True iff status is COMPLETE, CANCELED, FAILED, or CRASHED.
|
197
237
|
"""
|
198
238
|
return self in (
|
199
239
|
TaskStatus.COMPLETE,
|
200
240
|
TaskStatus.CANCELED,
|
201
241
|
TaskStatus.FAILED,
|
242
|
+
TaskStatus.CRASHED,
|
202
243
|
)
|
203
244
|
|
204
245
|
|
@@ -213,6 +254,7 @@ class ResponseType(Enum):
|
|
213
254
|
COMPLETION = "COMPLETION"
|
214
255
|
CANCELATION = "CANCELATION"
|
215
256
|
FAILURE = "FAILURE"
|
257
|
+
CRASH = "CRASH"
|
216
258
|
|
217
259
|
|
218
260
|
class TaskEvent:
|
@@ -220,6 +262,9 @@ class TaskEvent:
|
|
220
262
|
self.task: "Task" = task
|
221
263
|
self.response_type: ResponseType = response_type
|
222
264
|
|
265
|
+
def __str__(self):
|
266
|
+
return f"[{self.response_type}] {self.task}"
|
267
|
+
|
223
268
|
|
224
269
|
# noinspection PyProtectedMember
|
225
270
|
class Task:
|
@@ -342,3 +387,17 @@ class Task:
|
|
342
387
|
if self.status.is_finished():
|
343
388
|
with self.cv:
|
344
389
|
self.cv.notify_all()
|
390
|
+
|
391
|
+
def _crash(self):
|
392
|
+
event = TaskEvent(self, ResponseType.CRASH)
|
393
|
+
self.status = TaskStatus.CRASHED
|
394
|
+
for listener in self.listeners:
|
395
|
+
listener(event)
|
396
|
+
with self.cv:
|
397
|
+
self.cv.notify_all()
|
398
|
+
|
399
|
+
def __str__(self):
|
400
|
+
return (
|
401
|
+
f"{self.uuid=}, {self.status=}, {self.message=}, "
|
402
|
+
f"{self.current=}, {self.maximum=}, {self.error=}"
|
403
|
+
)
|
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 Appose developers.
|
5
|
+
# Copyright (C) 2023 - 2024 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:
|
@@ -28,14 +28,170 @@
|
|
28
28
|
###
|
29
29
|
|
30
30
|
import json
|
31
|
-
|
31
|
+
import re
|
32
|
+
from math import ceil, prod
|
33
|
+
from multiprocessing import resource_tracker, shared_memory
|
34
|
+
from typing import Any, Dict, Sequence, Union
|
32
35
|
|
33
36
|
Args = Dict[str, Any]
|
34
37
|
|
35
38
|
|
39
|
+
class SharedMemory(shared_memory.SharedMemory):
|
40
|
+
"""
|
41
|
+
An enhanced version of Python's multiprocessing.shared_memory.SharedMemory
|
42
|
+
class which can be used with a `with` statement. When the program flow
|
43
|
+
exits the `with` block, this class's `dispose()` method will be invoked,
|
44
|
+
which might call `close()` or `unlink()` depending on the value of its
|
45
|
+
`unlink_on_dispose` flag.
|
46
|
+
"""
|
47
|
+
|
48
|
+
def __init__(self, name: str = None, create: bool = False, size: int = 0):
|
49
|
+
super().__init__(name=name, create=create, size=size)
|
50
|
+
self._unlink_on_dispose = create
|
51
|
+
if _is_worker:
|
52
|
+
# HACK: Remove this shared memory block from the resource_tracker,
|
53
|
+
# which wants to clean up shared memory blocks after all known
|
54
|
+
# references are done using them.
|
55
|
+
#
|
56
|
+
# There is one resource_tracker per Python process, and they will
|
57
|
+
# each try to delete shared memory blocks known to them when they
|
58
|
+
# are shutting down, even when other processes still need them.
|
59
|
+
#
|
60
|
+
# As such, the rule Appose follows is: let the service process
|
61
|
+
# always handle cleanup of shared memory blocks, regardless of
|
62
|
+
# which process initially allocated it.
|
63
|
+
resource_tracker.unregister(self._name, "shared_memory")
|
64
|
+
|
65
|
+
def unlink_on_dispose(self, value: bool) -> None:
|
66
|
+
"""
|
67
|
+
Set whether the `unlink()` method should be invoked to destroy
|
68
|
+
the shared memory block when the `dispose()` method is called.
|
69
|
+
|
70
|
+
Note: dispose() is the method called when exiting a `with` block.
|
71
|
+
|
72
|
+
By default, shared memory objects constructed with `create=True`
|
73
|
+
will behave this way, whereas shared memory objects constructed
|
74
|
+
with `create=False` will not. But this method allows to override
|
75
|
+
the behavior.
|
76
|
+
"""
|
77
|
+
self._unlink_on_dispose = value
|
78
|
+
|
79
|
+
def dispose(self) -> None:
|
80
|
+
if self._unlink_on_dispose:
|
81
|
+
self.unlink()
|
82
|
+
else:
|
83
|
+
self.close()
|
84
|
+
|
85
|
+
def __enter__(self) -> "SharedMemory":
|
86
|
+
return self
|
87
|
+
|
88
|
+
def __exit__(self, exc_type, exc_value, exc_tb) -> None:
|
89
|
+
self.dispose()
|
90
|
+
|
91
|
+
|
36
92
|
def encode(data: Args) -> str:
|
37
|
-
return json.dumps(data)
|
93
|
+
return json.dumps(data, cls=_ApposeJSONEncoder, separators=(",", ":"))
|
38
94
|
|
39
95
|
|
40
96
|
def decode(the_json: str) -> Args:
|
41
|
-
return json.loads(the_json)
|
97
|
+
return json.loads(the_json, object_hook=_appose_object_hook)
|
98
|
+
|
99
|
+
|
100
|
+
class NDArray:
|
101
|
+
"""
|
102
|
+
Data structure for a multi-dimensional array.
|
103
|
+
The array contains elements of a data type, arranged in
|
104
|
+
a particular shape, and flattened into SharedMemory.
|
105
|
+
"""
|
106
|
+
|
107
|
+
def __init__(self, dtype: str, shape: Sequence[int], shm: SharedMemory = None):
|
108
|
+
"""
|
109
|
+
Create an NDArray.
|
110
|
+
:param dtype: The type of the data elements; e.g. int8, uint8, float32, float64.
|
111
|
+
:param shape: The dimensional extents; e.g. a stack of 7 image planes
|
112
|
+
with resolution 512x512 would have shape [7, 512, 512].
|
113
|
+
:param shm: The SharedMemory containing the array data, or None to create it.
|
114
|
+
"""
|
115
|
+
self.dtype = dtype
|
116
|
+
self.shape = shape
|
117
|
+
self.shm = (
|
118
|
+
SharedMemory(
|
119
|
+
create=True, size=ceil(prod(shape) * _bytes_per_element(dtype))
|
120
|
+
)
|
121
|
+
if shm is None
|
122
|
+
else shm
|
123
|
+
)
|
124
|
+
|
125
|
+
def __str__(self):
|
126
|
+
return (
|
127
|
+
f"NDArray("
|
128
|
+
f"dtype='{self.dtype}', "
|
129
|
+
f"shape={self.shape}, "
|
130
|
+
f"shm='{self.shm.name}' ({self.shm.size}))"
|
131
|
+
)
|
132
|
+
|
133
|
+
def ndarray(self):
|
134
|
+
"""
|
135
|
+
Create a NumPy ndarray object for working with the array data.
|
136
|
+
No array data is copied; the NumPy array wraps the same SharedMemory.
|
137
|
+
Requires the numpy package to be installed.
|
138
|
+
"""
|
139
|
+
try:
|
140
|
+
import numpy
|
141
|
+
|
142
|
+
return numpy.ndarray(
|
143
|
+
prod(self.shape), dtype=self.dtype, buffer=self.shm.buf
|
144
|
+
).reshape(self.shape)
|
145
|
+
except ModuleNotFoundError:
|
146
|
+
raise ImportError("NumPy is not available.")
|
147
|
+
|
148
|
+
def __enter__(self) -> "NDArray":
|
149
|
+
return self
|
150
|
+
|
151
|
+
def __exit__(self, exc_type, exc_value, exc_tb) -> None:
|
152
|
+
self.shm.dispose()
|
153
|
+
|
154
|
+
|
155
|
+
class _ApposeJSONEncoder(json.JSONEncoder):
|
156
|
+
def default(self, obj):
|
157
|
+
if isinstance(obj, SharedMemory):
|
158
|
+
return {
|
159
|
+
"appose_type": "shm",
|
160
|
+
"name": obj.name,
|
161
|
+
"size": obj.size,
|
162
|
+
}
|
163
|
+
if isinstance(obj, NDArray):
|
164
|
+
return {
|
165
|
+
"appose_type": "ndarray",
|
166
|
+
"dtype": obj.dtype,
|
167
|
+
"shape": obj.shape,
|
168
|
+
"shm": obj.shm,
|
169
|
+
}
|
170
|
+
return super().default(obj)
|
171
|
+
|
172
|
+
|
173
|
+
def _appose_object_hook(obj: Dict):
|
174
|
+
atype = obj.get("appose_type")
|
175
|
+
if atype == "shm":
|
176
|
+
# Attach to existing shared memory block.
|
177
|
+
return SharedMemory(name=(obj["name"]), size=(obj["size"]))
|
178
|
+
elif atype == "ndarray":
|
179
|
+
return NDArray(obj["dtype"], obj["shape"], obj["shm"])
|
180
|
+
else:
|
181
|
+
return obj
|
182
|
+
|
183
|
+
|
184
|
+
def _bytes_per_element(dtype: str) -> Union[int, float]:
|
185
|
+
try:
|
186
|
+
bits = int(re.sub("[^0-9]", "", dtype))
|
187
|
+
except ValueError:
|
188
|
+
raise ValueError(f"Invalid dtype: {dtype}")
|
189
|
+
return bits / 8
|
190
|
+
|
191
|
+
|
192
|
+
_is_worker = False
|
193
|
+
|
194
|
+
|
195
|
+
def _set_worker(value: bool) -> None:
|
196
|
+
global _is_worker
|
197
|
+
_is_worker = value
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: appose
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.2.0
|
4
4
|
Summary: Appose: multi-language interprocess cooperation with shared memory.
|
5
5
|
Author: Appose developers
|
6
6
|
License: Simplified BSD License
|
@@ -8,7 +8,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
|
10
10
|
Project-URL: download, https://pypi.org/project/appose-python
|
11
|
-
Project-URL: tracker, https://github.com/apposed/appose
|
11
|
+
Project-URL: tracker, https://github.com/apposed/appose/issues
|
12
12
|
Keywords: java,javascript,python,cross-language,interprocess
|
13
13
|
Classifier: Development Status :: 2 - Pre-Alpha
|
14
14
|
Classifier: Intended Audience :: Developers
|
@@ -71,16 +71,6 @@ This is the **Python implementation of Appose**.
|
|
71
71
|
|
72
72
|
The name of the package is `appose`.
|
73
73
|
|
74
|
-
### Conda/Mamba
|
75
|
-
|
76
|
-
To use [the conda-forge package](https://anaconda.org/conda-forge/appose),
|
77
|
-
add `appose` to your `environment.yml`'s `dependencies` section:
|
78
|
-
|
79
|
-
```yaml
|
80
|
-
dependencies:
|
81
|
-
- appose
|
82
|
-
```
|
83
|
-
|
84
74
|
### PyPI/Pip
|
85
75
|
|
86
76
|
To use [the PyPI package](https://pypi.org/project/appose),
|
@@ -98,6 +88,16 @@ dependencies = [
|
|
98
88
|
]
|
99
89
|
```
|
100
90
|
|
91
|
+
### Conda/Mamba
|
92
|
+
|
93
|
+
To use [the conda-forge package](https://anaconda.org/conda-forge/appose),
|
94
|
+
add `appose` to your `environment.yml`'s `dependencies` section:
|
95
|
+
|
96
|
+
```yaml
|
97
|
+
dependencies:
|
98
|
+
- appose
|
99
|
+
```
|
100
|
+
|
101
101
|
## Examples
|
102
102
|
|
103
103
|
Here is a minimal example for calling into Java from Python:
|
@@ -107,12 +107,12 @@ import appose
|
|
107
107
|
env = appose.java(vendor="zulu", version="17").build()
|
108
108
|
with env.groovy() as groovy:
|
109
109
|
task = groovy.task("5 + 6")
|
110
|
-
task.
|
111
|
-
result = task.outputs
|
110
|
+
task.wait_for()
|
111
|
+
result = task.outputs["result"]
|
112
112
|
assert 11 == result
|
113
113
|
```
|
114
114
|
|
115
|
-
*Note: The `
|
115
|
+
*Note: The `appose.java` builder is planned, but not yet implemented.*
|
116
116
|
|
117
117
|
Here is an example using a few more of Appose's features:
|
118
118
|
|
@@ -169,3 +169,9 @@ with env.groovy() as groovy:
|
|
169
169
|
|
170
170
|
Of course, the above examples could have been done all in one language. But
|
171
171
|
hopefully they hint at the possibilities of easy cross-language integration.
|
172
|
+
|
173
|
+
## Issue tracker
|
174
|
+
|
175
|
+
All implementations of Appose use the same issue tracker:
|
176
|
+
|
177
|
+
https://github.com/apposed/appose/issues
|
@@ -0,0 +1,11 @@
|
|
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,,
|
appose-0.1.0.dist-info/RECORD
DELETED
@@ -1,11 +0,0 @@
|
|
1
|
-
appose/__init__.py,sha256=IwFrDvIa6QOgV_RVgbIUfwyzswWB4RckLrzA9cts6IE,5340
|
2
|
-
appose/environment.py,sha256=m1a7166dJq69XPWX53d63KoITqmH4fKMNPKbEGSxeC0,7524
|
3
|
-
appose/paths.py,sha256=qBEWWf0LB0Cpk-l7u5uoPx9DTley6HltMTg0vJ2iFEQ,2156
|
4
|
-
appose/python_worker.py,sha256=ZR6AtWXXk_Xcp0nSoWehbl7LYc6z18ZTTvo4QjO_MwU,6474
|
5
|
-
appose/service.py,sha256=LqchMIxQXV6YF_l8sAGEyJapc2lSL6KBttcbLzdABFw,11966
|
6
|
-
appose/types.py,sha256=uuVNLt21eT3JEPtRQCMQ_IcFnp2yLgrJTSxzAnxkKFY,1618
|
7
|
-
appose-0.1.0.dist-info/LICENSE.txt,sha256=BOQExkI6YKvnUVMUqDbPuSxsY7XXL1XRUTTbnfEWII0,1306
|
8
|
-
appose-0.1.0.dist-info/METADATA,sha256=6ohYqhKiVnCvof0UX4HAnMmarKKIyi7hRScNtEO1T9E,5477
|
9
|
-
appose-0.1.0.dist-info/WHEEL,sha256=pkctZYzUS4AYVn6dJ-7367OJZivF2e8RA9b_ZBjif18,92
|
10
|
-
appose-0.1.0.dist-info/top_level.txt,sha256=oqHhw2QGlaFfH3jMR9H5Cd6XYHdEv5DvK1am0iEFPW0,7
|
11
|
-
appose-0.1.0.dist-info/RECORD,,
|
File without changes
|