setta 0.0.2__py3-none-any.whl → 0.0.3__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.
- setta/__init__.py +1 -1
- setta/cli/__init__.py +1 -1
- setta/cli/logger.py +1 -3
- setta/code_gen/create_runnable_scripts.py +87 -34
- setta/code_gen/export_selected.py +1 -5
- setta/code_gen/find_placeholders.py +13 -2
- setta/database/backup.py +1 -2
- setta/database/db/artifacts/save.py +20 -4
- setta/database/db/projects/save.py +1 -1
- setta/database/db/sections/load.py +5 -2
- setta/database/db/sections/save.py +2 -2
- setta/database/db_objs.py +6 -0
- setta/database/export_db/export_raw.py +3 -3
- setta/database/export_db/utils.py +2 -3
- setta/database/settings_file.py +3 -3
- setta/lsp/file_watcher.py +24 -23
- setta/lsp/server.py +1 -1
- setta/lsp/writer.py +2 -2
- setta/routers/interactive.py +38 -20
- setta/server.py +3 -4
- setta/start.py +4 -3
- setta/static/constants/constants.json +4 -0
- setta/static/constants/db_init.sql +2 -1
- setta/static/constants/defaultValues.json +2 -1
- setta/static/constants/settingsProject.json +31 -31
- setta/static/frontend/assets/index-03be034e.css +32 -0
- setta/static/frontend/assets/{index-ee99dc72.js → index-59443547.js} +171 -171
- setta/static/frontend/index.html +2 -2
- setta/tasks/tasks.py +166 -98
- setta/tasks/utils.py +108 -21
- setta/terminals/terminals.py +7 -6
- setta/utils/constants.py +5 -2
- setta/utils/websocket_manager.py +8 -3
- setta-0.0.3.dist-info/METADATA +146 -0
- {setta-0.0.2.dist-info → setta-0.0.3.dist-info}/RECORD +39 -40
- setta/database/db_path.py +0 -8
- setta/static/frontend/assets/index-1d4b4ecf.css +0 -32
- setta-0.0.2.dist-info/METADATA +0 -24
- {setta-0.0.2.dist-info → setta-0.0.3.dist-info}/LICENSE +0 -0
- {setta-0.0.2.dist-info → setta-0.0.3.dist-info}/WHEEL +0 -0
- {setta-0.0.2.dist-info → setta-0.0.3.dist-info}/entry_points.txt +0 -0
- {setta-0.0.2.dist-info → setta-0.0.3.dist-info}/top_level.txt +0 -0
setta/static/frontend/index.html
CHANGED
@@ -15,8 +15,8 @@
|
|
15
15
|
|
16
16
|
|
17
17
|
<title>setta.dev</title>
|
18
|
-
<script type="module" crossorigin src="/static/assets/index-
|
19
|
-
<link rel="stylesheet" href="/static/assets/index-
|
18
|
+
<script type="module" crossorigin src="/static/assets/index-59443547.js"></script>
|
19
|
+
<link rel="stylesheet" href="/static/assets/index-03be034e.css">
|
20
20
|
</head>
|
21
21
|
<body>
|
22
22
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
setta/tasks/tasks.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
import asyncio
|
2
|
+
import copy
|
2
3
|
import logging
|
3
|
-
import
|
4
|
-
import threading
|
4
|
+
import time
|
5
5
|
from typing import Dict
|
6
6
|
|
7
7
|
from setta.database.utils import create_new_id
|
8
|
+
from setta.utils.constants import C
|
8
9
|
|
9
10
|
from . import fns
|
10
11
|
from .fns.utils import TaskDefinition, TaskMessage
|
@@ -20,75 +21,31 @@ class Tasks:
|
|
20
21
|
self.task_runner = TaskRunner()
|
21
22
|
self.cache = {}
|
22
23
|
self.fns: Dict[str, TaskDefinition] = {}
|
23
|
-
self.
|
24
|
-
self.websockets = []
|
24
|
+
self.in_memory_subprocesses = {}
|
25
|
+
self.websockets = []
|
26
|
+
self.stop_event = asyncio.Event()
|
25
27
|
add_fns_from_module(self.fns, fns)
|
26
28
|
|
27
|
-
# Start stdout listener thread
|
28
|
-
self._stop_event = asyncio.Event()
|
29
|
-
self.stdout_queue = queue.Queue() # regular Queue
|
30
|
-
self._stdout_processor_task = None
|
31
|
-
self.stdout_thread = threading.Thread(target=self._stdout_listener, daemon=True)
|
32
|
-
self.stdout_thread.start()
|
33
|
-
|
34
|
-
# Backend Changes (Tasks class)
|
35
29
|
async def connect(self, websocket):
|
36
30
|
# Accept the new connection
|
37
31
|
await websocket.accept()
|
38
32
|
self.websockets.append(websocket)
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
self._stdout_processor_task = asyncio.create_task(
|
43
|
-
self._process_stdout_queue()
|
44
|
-
)
|
33
|
+
for k, v in self.in_memory_subprocesses.items():
|
34
|
+
v["subprocess"].start_stdout_processor_task()
|
35
|
+
logger.debug(f"listening to subprocess {k}")
|
45
36
|
|
46
37
|
async def disconnect(self, websocket):
|
47
38
|
self.websockets.remove(websocket)
|
48
39
|
if len(self.websockets) == 0:
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
def _stdout_listener(self):
|
59
|
-
while not self._stop_event.is_set():
|
60
|
-
try:
|
61
|
-
stdout_data = self.in_memory_subprocess.stdout_parent_conn.recv()
|
62
|
-
self.stdout_queue.put(stdout_data) # simple put, no async needed
|
63
|
-
except Exception as e:
|
64
|
-
if self._stop_event.is_set():
|
65
|
-
break
|
66
|
-
logger.debug(f"Error in stdout listener: {e}")
|
67
|
-
|
68
|
-
async def _process_stdout_queue(self):
|
69
|
-
while not self._stop_event.is_set():
|
70
|
-
try:
|
71
|
-
if self._stop_event.is_set():
|
72
|
-
break
|
73
|
-
if len(self.websockets) > 0:
|
74
|
-
stdout_data = self.stdout_queue.get_nowait()
|
75
|
-
stdout_data = stdout_data.replace("\n", "\r\n")
|
76
|
-
for w in self.websockets:
|
77
|
-
await w.send_text(stdout_data)
|
78
|
-
self.stdout_queue.task_done()
|
79
|
-
except queue.Empty:
|
80
|
-
await asyncio.sleep(0.1) # Check for connection every 100ms
|
81
|
-
except asyncio.CancelledError:
|
82
|
-
break
|
83
|
-
except Exception as e:
|
84
|
-
if self._stop_event.is_set():
|
85
|
-
break
|
86
|
-
logger.debug(f"Error processing stdout: {e}")
|
87
|
-
|
88
|
-
async def __call__(self, fn_name, message: TaskMessage):
|
89
|
-
if fn_name in self.fns:
|
90
|
-
return await self.call_regular_fn(fn_name, message)
|
91
|
-
return await self.call_in_memory_subprocess_fn(fn_name, message)
|
40
|
+
for v in self.in_memory_subprocesses.values():
|
41
|
+
await v["subprocess"].stop_stdout_processor_task()
|
42
|
+
|
43
|
+
async def __call__(
|
44
|
+
self, message_type, message: TaskMessage, websocket_manager=None
|
45
|
+
):
|
46
|
+
if message_type == "inMemoryFn":
|
47
|
+
return await self.call_in_memory_subprocess_fn(message, websocket_manager)
|
48
|
+
return await self.call_regular_fn(message_type, message)
|
92
49
|
|
93
50
|
async def call_regular_fn(self, fn_name, message: TaskMessage):
|
94
51
|
fn = self.fns[fn_name]
|
@@ -101,52 +58,163 @@ class Tasks:
|
|
101
58
|
result["messageType"] = fn.return_message_type
|
102
59
|
return result
|
103
60
|
|
104
|
-
async def call_in_memory_subprocess_fn(
|
105
|
-
self
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
61
|
+
async def call_in_memory_subprocess_fn(
|
62
|
+
self,
|
63
|
+
message: TaskMessage,
|
64
|
+
websocket_manager=None,
|
65
|
+
call_all=False,
|
66
|
+
subprocess_key=None,
|
67
|
+
):
|
68
|
+
# Create a list of tasks to run concurrently
|
69
|
+
tasks = []
|
70
|
+
results = []
|
71
|
+
|
72
|
+
for sp_key, sp_info in self.in_memory_subprocesses.items():
|
73
|
+
if subprocess_key and sp_key != subprocess_key:
|
74
|
+
continue
|
75
|
+
for fn_name, fnInfo in sp_info["fnInfo"].items():
|
76
|
+
if (
|
77
|
+
call_all
|
78
|
+
or None in fnInfo["dependencies"]
|
79
|
+
or any(k in fnInfo["dependencies"] for k in message.content.keys())
|
80
|
+
):
|
81
|
+
# Send message to subprocess
|
82
|
+
sp_info["subprocess"].parent_conn.send(
|
83
|
+
{"type": "call", "fn_name": fn_name, "message": message}
|
84
|
+
)
|
85
|
+
|
86
|
+
# Create task for receiving response
|
87
|
+
task = asyncio.create_task(
|
88
|
+
self._handle_subprocess_response(
|
89
|
+
sp_key,
|
90
|
+
fn_name,
|
91
|
+
message.id,
|
92
|
+
sp_info["subprocess"].parent_conn.recv,
|
93
|
+
websocket_manager,
|
94
|
+
results,
|
95
|
+
)
|
96
|
+
)
|
97
|
+
tasks.append(task)
|
98
|
+
|
99
|
+
# Wait for all tasks to complete concurrently
|
100
|
+
if tasks:
|
101
|
+
await asyncio.gather(*tasks)
|
102
|
+
|
103
|
+
if websocket_manager:
|
104
|
+
return {}
|
105
|
+
|
106
|
+
content = []
|
107
|
+
for r in results:
|
108
|
+
if r["content"]:
|
109
|
+
content.extend(r["content"])
|
110
|
+
return {"content": content, "messageType": C.WS_IN_MEMORY_FN_RETURN}
|
111
|
+
|
112
|
+
async def _handle_subprocess_response(
|
113
|
+
self, subprocess_key, fn_name, msg_id, recv_fn, websocket_manager, results
|
114
|
+
):
|
115
|
+
# Run the receive function in a thread
|
116
|
+
start_time = time.perf_counter()
|
117
|
+
result = await self.task_runner.run(recv_fn, [], RunType.THREAD)
|
118
|
+
elapsed_time = time.perf_counter() - start_time
|
119
|
+
if result["status"] == "success":
|
120
|
+
self.update_average_subprocess_fn_time(
|
121
|
+
subprocess_key, fn_name, elapsed_time
|
122
|
+
)
|
123
|
+
if websocket_manager is not None:
|
124
|
+
if result["content"]:
|
125
|
+
await websocket_manager.send_message_to_requester(
|
126
|
+
msg_id, result["content"], result["messageType"]
|
127
|
+
)
|
128
|
+
await self.maybe_send_latest_run_time_info(
|
129
|
+
subprocess_key, fn_name, msg_id, websocket_manager
|
130
|
+
)
|
131
|
+
else:
|
132
|
+
results.append(result)
|
133
|
+
|
134
|
+
async def add_custom_fns(self, code_graph, to_cache):
|
135
|
+
for c in code_graph:
|
136
|
+
subprocess_key = c["subprocess_key"]
|
137
|
+
module_name = c["module_name"]
|
138
|
+
sp = self.in_memory_subprocesses.get(subprocess_key, {}).get("subprocess")
|
139
|
+
if sp:
|
140
|
+
sp.close()
|
141
|
+
logger.debug(f"Creating new subprocess for {module_name}")
|
142
|
+
sp = SettaInMemoryFnSubprocess(self.stop_event, self.websockets)
|
143
|
+
self.in_memory_subprocesses[subprocess_key] = {
|
144
|
+
"subprocess": sp,
|
145
|
+
"fnInfo": {},
|
146
|
+
}
|
147
|
+
|
148
|
+
sp.parent_conn.send(
|
126
149
|
{
|
127
150
|
"type": "import",
|
128
151
|
"code": c["code"],
|
129
|
-
"module_name":
|
152
|
+
"module_name": module_name,
|
130
153
|
"to_cache": to_cache,
|
131
154
|
}
|
132
155
|
)
|
133
|
-
result = await self.task_runner.run(
|
134
|
-
|
135
|
-
|
156
|
+
result = await self.task_runner.run(sp.parent_conn.recv, [], RunType.THREAD)
|
157
|
+
fnInfo = self.in_memory_subprocesses[subprocess_key]["fnInfo"]
|
158
|
+
|
136
159
|
if result["status"] == "success":
|
137
|
-
|
160
|
+
for k, v in result["content"].items():
|
161
|
+
if k not in fnInfo:
|
162
|
+
fnInfo[k] = {
|
163
|
+
"dependencies": set(),
|
164
|
+
"averageRunTime": None,
|
165
|
+
"callCount": 0,
|
166
|
+
"lastStatsUpdate": time.time(),
|
167
|
+
}
|
168
|
+
fnInfo[k]["dependencies"].update(v)
|
138
169
|
else:
|
139
|
-
|
170
|
+
# TODO: store error message and display on frontend?
|
171
|
+
pass
|
172
|
+
|
173
|
+
initial_result = await self.call_in_memory_subprocess_fn(
|
174
|
+
TaskMessage(id=create_new_id(), content={}),
|
175
|
+
call_all=True,
|
176
|
+
subprocess_key=subprocess_key,
|
177
|
+
)
|
140
178
|
|
141
|
-
|
142
|
-
|
143
|
-
|
179
|
+
logger.debug(
|
180
|
+
f"self.in_memory_subprocesses keys: {self.in_memory_subprocesses.keys()}"
|
181
|
+
)
|
144
182
|
|
145
|
-
return
|
183
|
+
return initial_result["content"]
|
146
184
|
|
147
185
|
def close(self):
|
148
|
-
self.
|
149
|
-
self.
|
150
|
-
|
151
|
-
|
152
|
-
|
186
|
+
self.stop_event.set()
|
187
|
+
for v in self.in_memory_subprocesses.values():
|
188
|
+
v["subprocess"].close()
|
189
|
+
|
190
|
+
def update_average_subprocess_fn_time(self, subprocess_key, fn_name, new_time):
|
191
|
+
fnInfo = self.in_memory_subprocesses[subprocess_key]["fnInfo"][fn_name]
|
192
|
+
current_avg = fnInfo["averageRunTime"]
|
193
|
+
new_avg = (
|
194
|
+
new_time
|
195
|
+
if current_avg is None
|
196
|
+
else ((0.9) * current_avg) + (0.1 * new_time)
|
197
|
+
)
|
198
|
+
fnInfo["averageRunTime"] = new_avg
|
199
|
+
fnInfo["callCount"] += 1
|
200
|
+
fnInfo["lastStatsUpdate"] = time.time()
|
201
|
+
|
202
|
+
async def maybe_send_latest_run_time_info(
|
203
|
+
self, subprocess_key, fn_name, msg_id, websocket_manager
|
204
|
+
):
|
205
|
+
fnInfo = self.in_memory_subprocesses[subprocess_key]["fnInfo"][fn_name]
|
206
|
+
if fnInfo["callCount"] % 10 == 0 or (
|
207
|
+
fnInfo["lastStatsUpdate"] and (time.time() - fnInfo["lastStatsUpdate"]) > 10
|
208
|
+
):
|
209
|
+
newInfo = self.getInMemorySubprocessInfo()
|
210
|
+
await websocket_manager.send_message_to_requester(
|
211
|
+
msg_id, newInfo, C.WS_IN_MEMORY_FN_AVG_RUN_TIME
|
212
|
+
)
|
213
|
+
|
214
|
+
def getInMemorySubprocessInfo(self):
|
215
|
+
output = {}
|
216
|
+
for sp_key, sp_info in self.in_memory_subprocesses.items():
|
217
|
+
output[sp_key] = {"fnInfo": copy.deepcopy(sp_info["fnInfo"])}
|
218
|
+
for fnInfo in output[sp_key]["fnInfo"].values():
|
219
|
+
fnInfo["dependencies"] = list(fnInfo["dependencies"])
|
220
|
+
return output
|
setta/tasks/utils.py
CHANGED
@@ -1,14 +1,20 @@
|
|
1
|
+
import asyncio
|
1
2
|
import importlib.util
|
2
3
|
import json
|
4
|
+
import logging
|
3
5
|
import multiprocessing
|
4
|
-
import
|
6
|
+
import queue
|
5
7
|
import sys
|
8
|
+
import threading
|
6
9
|
import traceback
|
7
10
|
import uuid
|
8
11
|
|
9
12
|
from setta.tasks.fns.utils import TaskDefinition
|
13
|
+
from setta.utils.constants import CWD
|
10
14
|
from setta.utils.utils import nested_access
|
11
15
|
|
16
|
+
logger = logging.getLogger(__name__)
|
17
|
+
|
12
18
|
|
13
19
|
def import_code_from_string(code_string, module_name=None, add_to_sys_modules=True):
|
14
20
|
# Generate a unique module name if one isn't provided
|
@@ -16,7 +22,7 @@ def import_code_from_string(code_string, module_name=None, add_to_sys_modules=Tr
|
|
16
22
|
module_name = f"setta_dynamic_module_{uuid.uuid4().hex}"
|
17
23
|
|
18
24
|
# Add current directory to sys.path if it's not already there
|
19
|
-
current_dir =
|
25
|
+
current_dir = str(CWD)
|
20
26
|
if current_dir not in sys.path:
|
21
27
|
sys.path.insert(0, current_dir)
|
22
28
|
|
@@ -40,13 +46,24 @@ def import_code_from_string(code_string, module_name=None, add_to_sys_modules=Tr
|
|
40
46
|
|
41
47
|
|
42
48
|
class SettaInMemoryFnSubprocess:
|
43
|
-
def __init__(self):
|
49
|
+
def __init__(self, stop_event, websockets):
|
44
50
|
self.parent_conn, self.child_conn = multiprocessing.Pipe()
|
45
51
|
self.process = multiprocessing.Process(target=self._subprocess_main)
|
46
52
|
self.stdout_parent_conn, self.stdout_child_conn = multiprocessing.Pipe()
|
47
53
|
self.process.daemon = True # Ensure process dies with parent
|
48
54
|
self.process.start()
|
49
55
|
|
56
|
+
self.stop_event = asyncio.Event()
|
57
|
+
self.tasks_stop_event = stop_event
|
58
|
+
self.websockets = websockets
|
59
|
+
self.stdout_queue = queue.Queue()
|
60
|
+
self.stdout_processor_task = None
|
61
|
+
self.stdout_thread = threading.Thread(target=self.stdout_listener, daemon=True)
|
62
|
+
self.stdout_thread.start()
|
63
|
+
|
64
|
+
if len(self.websockets) > 0:
|
65
|
+
self.start_stdout_processor_task()
|
66
|
+
|
50
67
|
def _subprocess_main(self):
|
51
68
|
"""Main loop in subprocess that handles all requests"""
|
52
69
|
# Initialize store for imported modules
|
@@ -83,15 +100,15 @@ class SettaInMemoryFnSubprocess:
|
|
83
100
|
# Import and store module
|
84
101
|
module = import_code_from_string(code, module_name)
|
85
102
|
added_fn_names = add_fns_from_module(fns_dict, module, module_name)
|
86
|
-
|
103
|
+
dependencies = {}
|
87
104
|
for k in added_fn_names:
|
88
105
|
cache[k] = msg["to_cache"]
|
89
|
-
|
106
|
+
dependencies[k] = get_task_metadata(fns_dict[k], cache[k])
|
90
107
|
|
91
108
|
self.child_conn.send(
|
92
109
|
{
|
93
110
|
"status": "success",
|
94
|
-
"content":
|
111
|
+
"content": dependencies,
|
95
112
|
}
|
96
113
|
)
|
97
114
|
|
@@ -122,20 +139,36 @@ class SettaInMemoryFnSubprocess:
|
|
122
139
|
|
123
140
|
def close(self):
|
124
141
|
try:
|
142
|
+
logger.debug("Initiating shutdown sequence")
|
125
143
|
self.parent_conn.send({"type": "shutdown"})
|
126
|
-
self.process.join(timeout=
|
127
|
-
|
128
|
-
|
144
|
+
self.process.join(timeout=2) # Add timeout to process join
|
145
|
+
|
146
|
+
if self.process.is_alive():
|
147
|
+
logger.debug("Process still alive after timeout, forcing termination")
|
148
|
+
self.process.terminate()
|
149
|
+
self.process.join(timeout=1)
|
150
|
+
except Exception as e:
|
151
|
+
logger.debug(f"Error during process shutdown: {e}")
|
129
152
|
|
130
|
-
|
131
|
-
|
132
|
-
self.process.join()
|
153
|
+
# Set stop event before closing pipes
|
154
|
+
self.stop_event.set()
|
133
155
|
|
134
|
-
# Close
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
156
|
+
# Close all connections
|
157
|
+
for conn in [
|
158
|
+
self.parent_conn,
|
159
|
+
self.child_conn,
|
160
|
+
self.stdout_parent_conn,
|
161
|
+
self.stdout_child_conn,
|
162
|
+
]:
|
163
|
+
conn.close()
|
164
|
+
|
165
|
+
self.stdout_thread.join(timeout=2) # Add timeout to thread join
|
166
|
+
|
167
|
+
if self.stdout_thread.is_alive():
|
168
|
+
logger.debug("Stdout thread failed to terminate within timeout")
|
169
|
+
|
170
|
+
if self.stdout_processor_task:
|
171
|
+
self.stdout_processor_task.cancel()
|
139
172
|
|
140
173
|
def process_message(self, fn_name, message, cache):
|
141
174
|
if fn_name in cache:
|
@@ -147,6 +180,60 @@ class SettaInMemoryFnSubprocess:
|
|
147
180
|
message.content = exporter_obj.output
|
148
181
|
return message.content
|
149
182
|
|
183
|
+
def start_stdout_processor_task(self):
|
184
|
+
if self.stdout_processor_task is None or self.stdout_processor_task.done():
|
185
|
+
self.stdout_processor_task = asyncio.create_task(
|
186
|
+
self.process_stdout_queue()
|
187
|
+
)
|
188
|
+
|
189
|
+
async def stop_stdout_processor_task(self):
|
190
|
+
if self.stdout_processor_task and not self.stdout_processor_task.done():
|
191
|
+
self.stdout_processor_task.cancel()
|
192
|
+
try:
|
193
|
+
await self.stdout_processor_task
|
194
|
+
except asyncio.CancelledError:
|
195
|
+
pass
|
196
|
+
self.stdout_processor_task = None
|
197
|
+
|
198
|
+
async def process_stdout_queue(self):
|
199
|
+
while not self.should_stop():
|
200
|
+
try:
|
201
|
+
if self.should_stop():
|
202
|
+
break
|
203
|
+
if len(self.websockets) > 0:
|
204
|
+
stdout_data = self.stdout_queue.get_nowait()
|
205
|
+
stdout_data = stdout_data.replace("\n", "\r\n")
|
206
|
+
for w in self.websockets:
|
207
|
+
await w.send_text(stdout_data)
|
208
|
+
self.stdout_queue.task_done()
|
209
|
+
except queue.Empty:
|
210
|
+
await asyncio.sleep(0.1) # Check for connection every 100ms
|
211
|
+
except asyncio.CancelledError:
|
212
|
+
break
|
213
|
+
except Exception as e:
|
214
|
+
if self.should_stop():
|
215
|
+
break
|
216
|
+
logger.debug(f"Error processing stdout: {e}")
|
217
|
+
|
218
|
+
def stdout_listener(self):
|
219
|
+
while not self.should_stop():
|
220
|
+
if self.stdout_parent_conn.poll(0.1): # Check for data with timeout
|
221
|
+
try:
|
222
|
+
stdout_data = self.stdout_parent_conn.recv()
|
223
|
+
self.stdout_queue.put(stdout_data)
|
224
|
+
except EOFError: # Pipe was closed
|
225
|
+
break
|
226
|
+
except Exception as e:
|
227
|
+
logger.debug(f"Error in stdout listener: {e}")
|
228
|
+
if self.should_stop():
|
229
|
+
break
|
230
|
+
else: # No data available within timeout
|
231
|
+
if self.should_stop():
|
232
|
+
break
|
233
|
+
|
234
|
+
def should_stop(self):
|
235
|
+
return self.stop_event.is_set() or self.tasks_stop_event.is_set()
|
236
|
+
|
150
237
|
|
151
238
|
def add_fns_from_module(fns_dict, module, module_name=None):
|
152
239
|
count = 1
|
@@ -168,11 +255,11 @@ def add_fns_from_module(fns_dict, module, module_name=None):
|
|
168
255
|
def get_task_metadata(in_memory_fn, exporter_obj):
|
169
256
|
# None means run the task on every change
|
170
257
|
if in_memory_fn.dependencies is None:
|
171
|
-
dependencies = None
|
258
|
+
dependencies = set([None])
|
172
259
|
# Empty array means only run when the task imported.
|
173
260
|
# Non-empty array means run when specified dependencies update.
|
174
261
|
else:
|
175
|
-
dependencies =
|
262
|
+
dependencies = set(
|
176
263
|
exporter_obj.var_name_reverse_mapping[d] for d in in_memory_fn.dependencies
|
177
|
-
|
178
|
-
return
|
264
|
+
)
|
265
|
+
return dependencies
|
setta/terminals/terminals.py
CHANGED
@@ -4,6 +4,7 @@ import json
|
|
4
4
|
import logging
|
5
5
|
import platform
|
6
6
|
import select
|
7
|
+
import shlex
|
7
8
|
import time
|
8
9
|
import traceback
|
9
10
|
from asyncio import CancelledError
|
@@ -44,12 +45,14 @@ def is_command_running_in_pty(pid):
|
|
44
45
|
|
45
46
|
def get_terminal_shell():
|
46
47
|
if USER_SETTINGS["backend"]["defaultTerminalShell"]:
|
47
|
-
return
|
48
|
+
return shlex.split(
|
49
|
+
USER_SETTINGS["backend"]["defaultTerminalShell"], posix=not IS_WINDOWS
|
50
|
+
)
|
48
51
|
if IS_WINDOWS:
|
49
|
-
return "bash.exe"
|
52
|
+
return ["bash.exe"]
|
50
53
|
if IS_MACOS:
|
51
|
-
return "zsh"
|
52
|
-
return "bash"
|
54
|
+
return ["zsh"]
|
55
|
+
return ["bash"]
|
53
56
|
|
54
57
|
|
55
58
|
class TerminalWebsockets:
|
@@ -61,8 +64,6 @@ class TerminalWebsockets:
|
|
61
64
|
def new_terminal(self, projectConfigId, sectionId, isTemporary):
|
62
65
|
if sectionId not in self.PTY_PIDS:
|
63
66
|
terminal_shell = get_terminal_shell()
|
64
|
-
if not IS_WINDOWS:
|
65
|
-
terminal_shell = [terminal_shell]
|
66
67
|
pty_process = PtyProcess.spawn(terminal_shell)
|
67
68
|
self.PTY_PIDS[sectionId] = {
|
68
69
|
"pid": pty_process.pid,
|
setta/utils/constants.py
CHANGED
@@ -10,8 +10,10 @@ CONSTANTS_FOLDER = (
|
|
10
10
|
Path("../../../constants") if is_dev_mode() else Path("../static/constants")
|
11
11
|
)
|
12
12
|
SEED_FOLDER = Path("../../../seed") if is_dev_mode() else Path("../static/seed")
|
13
|
-
|
14
|
-
|
13
|
+
CWD = Path.cwd()
|
14
|
+
SETTA_FILES_FOLDER = CWD / "setta_files"
|
15
|
+
CODE_FOLDER = SETTA_FILES_FOLDER / "code"
|
16
|
+
DB_BACKUP_FOLDER = SETTA_FILES_FOLDER / "backups"
|
15
17
|
CODE_FOLDER_ENV_VARIABLE = "SETTA_CODE_FOLDER"
|
16
18
|
HOST_ENV_VARIABLE = "SETTA_HOST"
|
17
19
|
PORT_ENV_VARIABLE = "SETTA_PORT"
|
@@ -99,6 +101,7 @@ CODE_INFO_TABLE_DATA_JSON_FIELDS = set(
|
|
99
101
|
"positionalOnly",
|
100
102
|
"isPinned",
|
101
103
|
"isFrozen",
|
104
|
+
"ignoreTypeErrors",
|
102
105
|
)
|
103
106
|
)
|
104
107
|
|
setta/utils/websocket_manager.py
CHANGED
@@ -43,7 +43,9 @@ class WebsocketManager:
|
|
43
43
|
if wid == self.server_cli_id:
|
44
44
|
# process task and send any results back to requester
|
45
45
|
result = await tasks(
|
46
|
-
message["messageType"],
|
46
|
+
message["messageType"],
|
47
|
+
TaskMessage.parse_obj(message),
|
48
|
+
websocket_manager=self,
|
47
49
|
)
|
48
50
|
if "content" in result:
|
49
51
|
websocket = self.sockets[fromWebsocketId]["websocket"]
|
@@ -64,10 +66,13 @@ class WebsocketManager:
|
|
64
66
|
if "location" in message:
|
65
67
|
self.sockets[fromWebsocketId]["location"] = message["location"]
|
66
68
|
|
67
|
-
async def send_message_to_requester(self, id, content):
|
69
|
+
async def send_message_to_requester(self, id, content, messageType=None):
|
68
70
|
# just send data to target websocket
|
69
71
|
websocket = self.sockets[self.message_id_to_sender_id[id]]["websocket"]
|
70
|
-
|
72
|
+
return_val = {"id": id, "content": content}
|
73
|
+
if messageType:
|
74
|
+
return_val["messageType"] = messageType
|
75
|
+
await websocket.send_text(json.dumps(return_val))
|
71
76
|
|
72
77
|
async def send_message_to_location(self, content, messageType, location):
|
73
78
|
for w in self.sockets.values():
|