oocana-python-executor 0.15.3__tar.gz → 0.16.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.
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/PKG-INFO +3 -1
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/pyproject.toml +6 -1
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/block.py +4 -4
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/context.py +2 -2
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/executor.py +40 -18
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/service.py +1 -1
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/tests/test_cli.py +1 -1
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/__init__.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/data.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/hook.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/logger.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/matplot/matplotlib_oomol/__init__.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/matplot/matplotlib_oomol/oomol.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/matplot/oomol_matplot_helper.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/secret.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/topic.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/python_executor/utils.py +0 -0
- {oocana_python_executor-0.15.3 → oocana_python_executor-0.16.0}/tests/test_secret.py +0 -0
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: oocana-python-executor
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.16.0
|
|
4
4
|
Summary: a client subscribe mqtt topic to execute oocana's block
|
|
5
5
|
Author-Email: l1shen <lishen1635@gmail.com>, yleaf <11785335+leavesster@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: >=3.9
|
|
8
8
|
Requires-Dist: oocana
|
|
9
|
+
Provides-Extra: debug
|
|
10
|
+
Requires-Dist: debugpy; extra == "debug"
|
|
9
11
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "oocana-python-executor"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.16.0"
|
|
4
4
|
authors = [
|
|
5
5
|
{ name = "l1shen", email = "lishen1635@gmail.com" },
|
|
6
6
|
{ name = "yleaf", email = "11785335+leavesster@users.noreply.github.com" },
|
|
@@ -14,6 +14,11 @@ dependencies = [
|
|
|
14
14
|
[project.license]
|
|
15
15
|
text = "MIT"
|
|
16
16
|
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
debug = [
|
|
19
|
+
"debugpy",
|
|
20
|
+
]
|
|
21
|
+
|
|
17
22
|
[project.scripts]
|
|
18
23
|
python-executor = "python_executor.executor:main"
|
|
19
24
|
|
|
@@ -70,24 +70,24 @@ def load_module(file_path: str, source_dir=None):
|
|
|
70
70
|
|
|
71
71
|
def output_return_object(obj, context: Context):
|
|
72
72
|
if obj is None:
|
|
73
|
-
context.done()
|
|
73
|
+
context.done() if not context.is_done else None
|
|
74
74
|
elif obj is context.keepAlive:
|
|
75
75
|
pass
|
|
76
76
|
elif isinstance(obj, dict):
|
|
77
77
|
for k, v in obj.items():
|
|
78
78
|
context.output(k, v)
|
|
79
|
-
context.done()
|
|
79
|
+
context.done() if not context.is_done else None
|
|
80
80
|
else:
|
|
81
81
|
context.done(f"return object needs to be a dictionary, but get type: {type(obj)}")
|
|
82
82
|
|
|
83
83
|
logger = logging.getLogger(EXECUTOR_NAME)
|
|
84
84
|
|
|
85
|
-
async def run_block(message, mainframe: Mainframe, session_dir: str):
|
|
85
|
+
async def run_block(message, mainframe: Mainframe, session_dir: str, tmp_dir: str):
|
|
86
86
|
|
|
87
87
|
logger.info(f"block {message.get('job_id')} start")
|
|
88
88
|
try:
|
|
89
89
|
payload = ExecutePayload(**message)
|
|
90
|
-
context = createContext(mainframe, payload.session_id, payload.job_id, store, payload.outputs, session_dir)
|
|
90
|
+
context = createContext(mainframe, payload.session_id, payload.job_id, store, payload.outputs, session_dir, tmp_dir = tmp_dir)
|
|
91
91
|
except Exception:
|
|
92
92
|
traceback_str = traceback.format_exc()
|
|
93
93
|
# rust 那边会保证传过来的 message 一定是符合格式的,所以这里不应该出现异常。这里主要是防止 rust 修改错误。
|
|
@@ -9,7 +9,7 @@ from .data import EXECUTOR_NAME
|
|
|
9
9
|
logger = logging.getLogger(EXECUTOR_NAME)
|
|
10
10
|
|
|
11
11
|
def createContext(
|
|
12
|
-
mainframe: Mainframe, session_id: str, job_id: str, store, output, session_dir: str
|
|
12
|
+
mainframe: Mainframe, session_id: str, job_id: str, store, output, session_dir: str, tmp_dir: str
|
|
13
13
|
) -> Context:
|
|
14
14
|
|
|
15
15
|
node_props = mainframe.notify_block_ready(session_id, job_id)
|
|
@@ -59,7 +59,7 @@ def createContext(
|
|
|
59
59
|
|
|
60
60
|
blockInfo = BlockInfo(**node_props)
|
|
61
61
|
|
|
62
|
-
ctx = Context(inputs, blockInfo, mainframe, store, output, session_dir)
|
|
62
|
+
ctx = Context(inputs, blockInfo, mainframe, store, output, session_dir, tmp_dir)
|
|
63
63
|
# 跟 executor 日志分开,避免有的库在 logger 里面使用 print,导致 hook 出现递归调用。
|
|
64
64
|
block_logger = logging.getLogger(f"block {job_id}")
|
|
65
65
|
ctx_handler = ContextHandler(ctx)
|
|
@@ -19,9 +19,9 @@ service_store: dict[str, Literal["launching", "running"]] = {}
|
|
|
19
19
|
job_set = set()
|
|
20
20
|
|
|
21
21
|
# 日志目录 ~/.oocana/sessions/{session_id}
|
|
22
|
-
# executor 的日志都会记录在 [python-executor-{
|
|
23
|
-
# 全局 logger 会记录在 python-{
|
|
24
|
-
def config_logger(session_id: str,
|
|
22
|
+
# executor 的日志都会记录在 [python-executor-{identifier}.log | python-executor.log]
|
|
23
|
+
# 全局 logger 会记录在 python-{identifier}.log | python.log
|
|
24
|
+
def config_logger(session_id: str, identifier: str | None, output: Literal["console", "file"]):
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
format = '%(asctime)s - %(levelname)s - {%(pathname)s:%(lineno)d} - %(message)s'
|
|
@@ -29,7 +29,7 @@ def config_logger(session_id: str, suffix: str | None, output: Literal["console"
|
|
|
29
29
|
logger.setLevel(logging.DEBUG)
|
|
30
30
|
if output == "file":
|
|
31
31
|
executor_dir = os.path.join(oocana_dir(), "sessions", session_id)
|
|
32
|
-
logger_file = os.path.join(executor_dir, f"python-executor-{
|
|
32
|
+
logger_file = os.path.join(executor_dir, f"python-executor-{identifier}.log") if identifier is not None else os.path.join(executor_dir, "python-executor.log")
|
|
33
33
|
|
|
34
34
|
if not os.path.exists(logger_file):
|
|
35
35
|
os.makedirs(os.path.dirname(logger_file), exist_ok=True)
|
|
@@ -37,7 +37,7 @@ def config_logger(session_id: str, suffix: str | None, output: Literal["console"
|
|
|
37
37
|
print(f"setup logging in file {logger_file}")
|
|
38
38
|
h = logging.FileHandler(logger_file)
|
|
39
39
|
|
|
40
|
-
global_logger_file = os.path.join(executor_dir, f"python-{
|
|
40
|
+
global_logger_file = os.path.join(executor_dir, f"python-{identifier}.log") if identifier is not None else os.path.join(executor_dir, "python.log")
|
|
41
41
|
logging.basicConfig(filename=global_logger_file, level=logging.DEBUG, format=format)
|
|
42
42
|
else:
|
|
43
43
|
logging.basicConfig(level=logging.DEBUG, format=format)
|
|
@@ -49,10 +49,10 @@ def config_logger(session_id: str, suffix: str | None, output: Literal["console"
|
|
|
49
49
|
logger.propagate = False
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
async def run_executor(address: str, session_id: str, package: str | None, session_dir: str,
|
|
52
|
+
async def run_executor(address: str, session_id: str, tmp_dir: str, package: str | None, session_dir: str, identifier: str | None = None):
|
|
53
53
|
|
|
54
|
-
if
|
|
55
|
-
mainframe = Mainframe(address, f"python-executor-{
|
|
54
|
+
if identifier is not None:
|
|
55
|
+
mainframe = Mainframe(address, f"python-executor-id-{identifier}", logger)
|
|
56
56
|
else:
|
|
57
57
|
mainframe = Mainframe(address, f"python-executor-{session_id}", logger)
|
|
58
58
|
|
|
@@ -211,12 +211,12 @@ async def run_executor(address: str, session_id: str, package: str | None, sessi
|
|
|
211
211
|
else:
|
|
212
212
|
if not_current_session(message):
|
|
213
213
|
continue
|
|
214
|
-
run_block_in_new_thread(message, mainframe, session_dir=session_dir)
|
|
214
|
+
run_block_in_new_thread(message, mainframe, session_dir=session_dir, tmp_dir=tmp_dir)
|
|
215
215
|
|
|
216
|
-
def run_block_in_new_thread(message, mainframe: Mainframe, session_dir: str):
|
|
216
|
+
def run_block_in_new_thread(message, mainframe: Mainframe, session_dir: str, tmp_dir: str):
|
|
217
217
|
|
|
218
218
|
async def run():
|
|
219
|
-
await run_block(message, mainframe, session_dir=session_dir)
|
|
219
|
+
await run_block(message, mainframe, session_dir=session_dir, tmp_dir=tmp_dir)
|
|
220
220
|
run_in_new_thread(run)
|
|
221
221
|
|
|
222
222
|
def main():
|
|
@@ -224,26 +224,48 @@ def main():
|
|
|
224
224
|
import argparse
|
|
225
225
|
parser = argparse.ArgumentParser(description="run executor with address, session-id, tmp-dir")
|
|
226
226
|
parser.add_argument("--session-id", help="executor subscribe session id", required=True)
|
|
227
|
-
parser.add_argument("--address", help="mqtt address", default="mqtt://127.0.0.1:47688")
|
|
228
227
|
parser.add_argument("--session-dir", help="a tmp dir for whole session", required=True)
|
|
228
|
+
parser.add_argument("--tmp-dir", help="a tmp dir for whole session. It will be cleaned after session success, this behavior is guaranteed by oocana.", required=True)
|
|
229
|
+
parser.add_argument("--address", help="mqtt address", default="mqtt://127.0.0.1:47688")
|
|
229
230
|
parser.add_argument("--output", help="output log to console or file", default="file", choices=["console", "file"])
|
|
230
231
|
parser.add_argument("--package", help="package path, if set, executor will only run same package block", default=None)
|
|
231
232
|
parser.add_argument("--identifier", help="identifier for executor, oocana will think same identifier as one executor", default=None)
|
|
232
|
-
parser.add_argument("--
|
|
233
|
+
parser.add_argument("--debug-port", help="debug port for python", default=None)
|
|
234
|
+
parser.add_argument("--wait-for-client", help="wait for client to connect", default=False, action="store_true")
|
|
233
235
|
|
|
234
|
-
|
|
236
|
+
try:
|
|
237
|
+
args = parser.parse_args()
|
|
238
|
+
except Exception as e:
|
|
239
|
+
print(f"parse args error: {e}")
|
|
240
|
+
# because we hook sys.exit in hook.py and raise a exception, the exit will be reset to 1.
|
|
241
|
+
# parser origin exit code is 2. so we use 2 here.
|
|
242
|
+
sys.exit(2)
|
|
235
243
|
|
|
236
244
|
address: str = args.address
|
|
237
245
|
session_id: str = str(args.session_id)
|
|
238
246
|
output: Literal["console", "file"] = args.output
|
|
239
247
|
package: str | None = args.package
|
|
240
|
-
suffix: str | None = args.suffix
|
|
241
248
|
session_dir: str = args.session_dir
|
|
249
|
+
tmp_dir: str = args.tmp_dir
|
|
242
250
|
identifier: str | None = args.identifier
|
|
243
251
|
|
|
244
|
-
config_logger(session_id,
|
|
245
|
-
|
|
246
|
-
|
|
252
|
+
config_logger(session_id, identifier, output)
|
|
253
|
+
|
|
254
|
+
if args.debug_port is not None and args.debug_port.isdigit():
|
|
255
|
+
try:
|
|
256
|
+
import debugpy
|
|
257
|
+
debugpy.listen(int(args.debug_port))
|
|
258
|
+
logger.info(f"debugpy listen on port {args.debug_port}")
|
|
259
|
+
if args.wait_for_client:
|
|
260
|
+
logger.info("wait for client to connect")
|
|
261
|
+
debugpy.wait_for_client()
|
|
262
|
+
logger.info("client connected")
|
|
263
|
+
except ImportError:
|
|
264
|
+
logger.warning("Warning: debugpy not installed, debugging functionality will not be available")
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.warning(f"Warning: debugpy listen failed: {e}")
|
|
267
|
+
|
|
268
|
+
run_async_code(run_executor(address=address, tmp_dir=tmp_dir, session_id=session_id, package=package, session_dir=session_dir, identifier=identifier))
|
|
247
269
|
|
|
248
270
|
if __name__ == '__main__':
|
|
249
271
|
main()
|
|
@@ -179,7 +179,7 @@ class ServiceRuntime(ServiceContextAbstractClass):
|
|
|
179
179
|
self._runningBlocks.add(job_id)
|
|
180
180
|
self._jobs.add(job_id)
|
|
181
181
|
|
|
182
|
-
context = createContext(self._mainframe, payload["session_id"], payload["job_id"], self._store, payload["outputs"], self._session_dir)
|
|
182
|
+
context = createContext(self._mainframe, payload["session_id"], payload["job_id"], self._store, payload["outputs"], self._session_dir, tmp_dir=self._session_dir) # TODO: tmp_dir need consider global service.
|
|
183
183
|
|
|
184
184
|
if isinstance(self.block_handler, dict):
|
|
185
185
|
handler = self.block_handler.get(block_name)
|
|
@@ -20,7 +20,7 @@ class TestExecutorCLI(unittest.TestCase):
|
|
|
20
20
|
self.assertIsNone(code, "HTTP server failed to start or exit.")
|
|
21
21
|
|
|
22
22
|
def test_cli(self):
|
|
23
|
-
cli_command = [sys.executable, "-u", "-m", "python_executor.executor", "--session-id", "test-session", "--session-dir", "/tmp"]
|
|
23
|
+
cli_command = [sys.executable, "-u", "-m", "python_executor.executor", "--session-id", "test-session", "--session-dir", "/tmp", "--tmp-dir", "/tmp"]
|
|
24
24
|
|
|
25
25
|
print("Starting CLI tool... in", executor_parent_dir)
|
|
26
26
|
|
|
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
|