setta 0.0.3.dev8__py3-none-any.whl → 0.0.3.dev9__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.
@@ -15,7 +15,7 @@
15
15
 
16
16
 
17
17
  <title>setta.dev</title>
18
- <script type="module" crossorigin src="/static/assets/index-8204c872.js"></script>
18
+ <script type="module" crossorigin src="/static/assets/index-d7ede6b5.js"></script>
19
19
  <link rel="stylesheet" href="/static/assets/index-af271c9f.css">
20
20
  </head>
21
21
  <body>
setta/tasks/tasks.py CHANGED
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
+ import copy
2
3
  import logging
4
+ import time
3
5
  from typing import Dict
4
6
 
5
7
  from setta.database.utils import create_new_id
@@ -63,12 +65,12 @@ class Tasks:
63
65
  tasks = []
64
66
  results = []
65
67
 
66
- for sp_info in self.in_memory_subprocesses.values():
67
- for fn_name, dependencies in sp_info["dependencies"].items():
68
+ for sp_key, sp_info in self.in_memory_subprocesses.items():
69
+ for fn_name, fnInfo in sp_info["fnInfo"].items():
68
70
  if (
69
71
  call_all
70
- or None in dependencies
71
- or any(k in dependencies for k in message.content.keys())
72
+ or None in fnInfo["dependencies"]
73
+ or any(k in fnInfo["dependencies"] for k in message.content.keys())
72
74
  ):
73
75
  # Send message to subprocess
74
76
  sp_info["subprocess"].parent_conn.send(
@@ -78,6 +80,8 @@ class Tasks:
78
80
  # Create task for receiving response
79
81
  task = asyncio.create_task(
80
82
  self._handle_subprocess_response(
83
+ sp_key,
84
+ fn_name,
81
85
  message.id,
82
86
  sp_info["subprocess"].parent_conn.recv,
83
87
  websocket_manager,
@@ -100,30 +104,40 @@ class Tasks:
100
104
  return {"content": content, "messageType": C.WS_IN_MEMORY_FN_RETURN}
101
105
 
102
106
  async def _handle_subprocess_response(
103
- self, msg_id, recv_fn, websocket_manager, results
107
+ self, subprocess_key, fn_name, msg_id, recv_fn, websocket_manager, results
104
108
  ):
105
109
  # Run the receive function in a thread
110
+ start_time = time.perf_counter()
106
111
  result = await self.task_runner.run(recv_fn, [], RunType.THREAD)
112
+ elapsed_time = time.perf_counter() - start_time
107
113
  if result["status"] == "success":
108
- if websocket_manager is not None and result["content"]:
109
- await websocket_manager.send_message_to_requester(
110
- msg_id, result["content"], result["messageType"]
114
+ self.update_average_subprocess_fn_time(
115
+ subprocess_key, fn_name, elapsed_time
116
+ )
117
+ if websocket_manager is not None:
118
+ if result["content"]:
119
+ await websocket_manager.send_message_to_requester(
120
+ msg_id, result["content"], result["messageType"]
121
+ )
122
+ await self.maybe_send_latest_run_time_info(
123
+ subprocess_key, fn_name, msg_id, websocket_manager
111
124
  )
112
- else:
113
- results.append(result)
125
+ else:
126
+ results.append(result)
114
127
 
115
128
  async def add_custom_fns(self, code_graph, to_cache):
116
129
  for c in code_graph:
117
130
  subprocess_key = c["subprocess_key"]
118
131
  module_name = c["module_name"]
119
132
  sp = self.in_memory_subprocesses.get(subprocess_key, {}).get("subprocess")
120
- if not sp:
121
- logger.debug(f"Creating new subprocess for {module_name}")
122
- sp = SettaInMemoryFnSubprocess(self.stop_event, self.websockets)
123
- self.in_memory_subprocesses[subprocess_key] = {
124
- "subprocess": sp,
125
- "dependencies": {},
126
- }
133
+ if sp:
134
+ sp.close()
135
+ logger.debug(f"Creating new subprocess for {module_name}")
136
+ sp = SettaInMemoryFnSubprocess(self.stop_event, self.websockets)
137
+ self.in_memory_subprocesses[subprocess_key] = {
138
+ "subprocess": sp,
139
+ "fnInfo": {},
140
+ }
127
141
 
128
142
  sp.parent_conn.send(
129
143
  {
@@ -134,17 +148,22 @@ class Tasks:
134
148
  }
135
149
  )
136
150
  result = await self.task_runner.run(sp.parent_conn.recv, [], RunType.THREAD)
137
- sp_info = self.in_memory_subprocesses[subprocess_key]
151
+ fnInfo = self.in_memory_subprocesses[subprocess_key]["fnInfo"]
138
152
 
139
153
  if result["status"] == "success":
140
- sp_info["dependencies"].update(result["content"])
154
+ for k, v in result["content"].items():
155
+ if k not in fnInfo:
156
+ fnInfo[k] = {
157
+ "dependencies": set(),
158
+ "averageRunTime": None,
159
+ "callCount": 0,
160
+ "lastStatsUpdate": time.time(),
161
+ }
162
+ fnInfo[k]["dependencies"].update(v)
141
163
  else:
142
164
  # TODO: store error message and display on frontend?
143
165
  pass
144
166
 
145
- all_dependencies = set()
146
- for d in sp_info["dependencies"].values():
147
- all_dependencies.update(d)
148
167
  initial_result = await self.call_in_memory_subprocess_fn(
149
168
  TaskMessage(id=create_new_id(), content={}), call_all=True
150
169
  )
@@ -153,9 +172,41 @@ class Tasks:
153
172
  f"self.in_memory_subprocesses keys: {self.in_memory_subprocesses.keys()}"
154
173
  )
155
174
 
156
- return all_dependencies, initial_result["content"]
175
+ return initial_result["content"]
157
176
 
158
177
  def close(self):
159
178
  self.stop_event.set()
160
179
  for v in self.in_memory_subprocesses.values():
161
180
  v["subprocess"].close()
181
+
182
+ def update_average_subprocess_fn_time(self, subprocess_key, fn_name, new_time):
183
+ fnInfo = self.in_memory_subprocesses[subprocess_key]["fnInfo"][fn_name]
184
+ current_avg = fnInfo["averageRunTime"]
185
+ new_avg = (
186
+ new_time
187
+ if current_avg is None
188
+ else ((0.9) * current_avg) + (0.1 * new_time)
189
+ )
190
+ fnInfo["averageRunTime"] = new_avg
191
+ fnInfo["callCount"] += 1
192
+ fnInfo["lastStatsUpdate"] = time.time()
193
+
194
+ async def maybe_send_latest_run_time_info(
195
+ self, subprocess_key, fn_name, msg_id, websocket_manager
196
+ ):
197
+ fnInfo = self.in_memory_subprocesses[subprocess_key]["fnInfo"][fn_name]
198
+ if fnInfo["callCount"] % 10 == 0 or (
199
+ fnInfo["lastStatsUpdate"] and (time.time() - fnInfo["lastStatsUpdate"]) > 10
200
+ ):
201
+ newInfo = self.getInMemorySubprocessInfo()
202
+ await websocket_manager.send_message_to_requester(
203
+ msg_id, newInfo, C.WS_IN_MEMORY_FN_AVG_RUN_TIME
204
+ )
205
+
206
+ def getInMemorySubprocessInfo(self):
207
+ output = {}
208
+ for sp_key, sp_info in self.in_memory_subprocesses.items():
209
+ output[sp_key] = {"fnInfo": copy.deepcopy(sp_info["fnInfo"])}
210
+ for fnInfo in output[sp_key]["fnInfo"].values():
211
+ fnInfo["dependencies"] = list(fnInfo["dependencies"])
212
+ return output
setta/tasks/utils.py CHANGED
@@ -53,7 +53,8 @@ class SettaInMemoryFnSubprocess:
53
53
  self.process.daemon = True # Ensure process dies with parent
54
54
  self.process.start()
55
55
 
56
- self.stop_event = stop_event
56
+ self.stop_event = asyncio.Event()
57
+ self.tasks_stop_event = stop_event
57
58
  self.websockets = websockets
58
59
  self.stdout_queue = queue.Queue()
59
60
  self.stdout_processor_task = None
@@ -99,15 +100,15 @@ class SettaInMemoryFnSubprocess:
99
100
  # Import and store module
100
101
  module = import_code_from_string(code, module_name)
101
102
  added_fn_names = add_fns_from_module(fns_dict, module, module_name)
102
- task_metadata = {}
103
+ dependencies = {}
103
104
  for k in added_fn_names:
104
105
  cache[k] = msg["to_cache"]
105
- task_metadata[k] = get_task_metadata(fns_dict[k], cache[k])
106
+ dependencies[k] = get_task_metadata(fns_dict[k], cache[k])
106
107
 
107
108
  self.child_conn.send(
108
109
  {
109
110
  "status": "success",
110
- "content": task_metadata,
111
+ "content": dependencies,
111
112
  }
112
113
  )
113
114
 
@@ -138,22 +139,34 @@ class SettaInMemoryFnSubprocess:
138
139
 
139
140
  def close(self):
140
141
  try:
142
+ logger.debug("Initiating shutdown sequence")
141
143
  self.parent_conn.send({"type": "shutdown"})
142
- self.process.join(timeout=1.0)
143
- except:
144
- pass
144
+ self.process.join(timeout=2) # Add timeout to process join
145
145
 
146
- if self.process.is_alive():
147
- self.process.terminate()
148
- self.process.join()
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}")
149
152
 
150
- # Close both sets of connections
151
- self.parent_conn.close()
152
- self.child_conn.close()
153
- self.stdout_parent_conn.close()
154
- self.stdout_child_conn.close()
153
+ # Set stop event before closing pipes
154
+ self.stop_event.set()
155
+
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")
155
169
 
156
- self.stdout_thread.join()
157
170
  if self.stdout_processor_task:
158
171
  self.stdout_processor_task.cancel()
159
172
 
@@ -183,9 +196,9 @@ class SettaInMemoryFnSubprocess:
183
196
  self.stdout_processor_task = None
184
197
 
185
198
  async def process_stdout_queue(self):
186
- while not self.stop_event.is_set():
199
+ while not self.should_stop():
187
200
  try:
188
- if self.stop_event.is_set():
201
+ if self.should_stop():
189
202
  break
190
203
  if len(self.websockets) > 0:
191
204
  stdout_data = self.stdout_queue.get_nowait()
@@ -198,19 +211,28 @@ class SettaInMemoryFnSubprocess:
198
211
  except asyncio.CancelledError:
199
212
  break
200
213
  except Exception as e:
201
- if self.stop_event.is_set():
214
+ if self.should_stop():
202
215
  break
203
216
  logger.debug(f"Error processing stdout: {e}")
204
217
 
205
218
  def stdout_listener(self):
206
- while not self.stop_event.is_set():
207
- try:
208
- stdout_data = self.stdout_parent_conn.recv()
209
- self.stdout_queue.put(stdout_data) # simple put, no async needed
210
- except Exception as e:
211
- if self.stop_event.is_set():
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():
212
232
  break
213
- logger.debug(f"Error in stdout listener: {e}")
233
+
234
+ def should_stop(self):
235
+ return self.stop_event.is_set() or self.tasks_stop_event.is_set()
214
236
 
215
237
 
216
238
  def add_fns_from_module(fns_dict, module, module_name=None):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: setta
3
- Version: 0.0.3.dev8
3
+ Version: 0.0.3.dev9
4
4
  Home-page: https://setta.dev
5
5
  Project-URL: GitHub, https://github.com/settadev/setta
6
6
  License-File: LICENSE
@@ -1,4 +1,4 @@
1
- setta/__init__.py,sha256=VRMrMIrGmnKm4x2-C0jl9DIpY57NuqIv9j7dygH9Wjo,27
1
+ setta/__init__.py,sha256=mRKlQs_lYaraYvOzYlGQDMNDPQsRu2O-zNqzBFCdPLA,27
2
2
  setta/server.py,sha256=P78BMFvcCokVhjnVlNIKcmpCSvmzSqt-33bx4bhOe6Y,4626
3
3
  setta/start.py,sha256=jEeSiocLeBitvg48fa6k1CpLnPI63JHkQ8O-WVM6ch8,3078
4
4
  setta/cli/__init__.py,sha256=UxZG_VOMuF6lEBT3teUgTS9ulsK3wt3Gu3BbAQiAmt8,47
@@ -87,7 +87,7 @@ setta/routers/artifact.py,sha256=9wHdreg5DsLshhET-6gEDw2Apw_-r8bRF1x3-_dD9mU,266
87
87
  setta/routers/code_info.py,sha256=rDBLkr5VQOlktap3hWA73ls0VrBi5y4mc_SfWzw9ad0,857
88
88
  setta/routers/dependencies.py,sha256=lQDZFcXw-jUXK1q8yglUg3jY9dKoE-xvVcWckKqmOr4,1126
89
89
  setta/routers/in_memory_fn_stdout_websocket.py,sha256=T2BpLzh6PwYQP0qIkFS4r_VfEKBlwl4gkwIaq6r6Phs,604
90
- setta/routers/interactive.py,sha256=juUtPmCyqHSnIb5k6iOYUtfmukp1Z6x25wISlANgUic,4385
90
+ setta/routers/interactive.py,sha256=V68ooA_C2rSqyXeARHifvr_17iCfLrWPERLSrrb_PIU,4316
91
91
  setta/routers/lsp.py,sha256=DAZqdiRKDWJ9ikjwQetV4_8s9U-EDC91ToJA3u57qnU,385
92
92
  setta/routers/projects.py,sha256=p3zPD3jobYOxBGJSSIYS1Aqu1w-PrJCcEN6dPJ0DT6E,5255
93
93
  setta/routers/reference_renaming.py,sha256=Ec1hz2Nz_hYqk8GyGmUcWKvXo-lVEDbIIK2YbX-wi00,3598
@@ -97,7 +97,7 @@ setta/routers/terminals.py,sha256=91I3tVUPJtLyCD_-E_qBQ_k8uuNUrcXl5P7sCTQuDQE,24
97
97
  setta/routers/websocket.py,sha256=6fSROv9C5PobPXppUWwNLDDO0p8VADYaf2KcgIuTQp4,1121
98
98
  setta/static/constants/BaseUITypes.json,sha256=S08qzaNwjclRKuC9U3J0_PpmDTw5aQFwKQcUb0SiiR4,3873
99
99
  setta/static/constants/Settings.json,sha256=-cpSDnjJ9s86z_hfC69xq7A4kxqphKXqssMUUhTN4BQ,3444
100
- setta/static/constants/constants.json,sha256=mijCGv64ZQ6uTvsIhZ22DBiJjBmsEfn8krBef78aQyk,5093
100
+ setta/static/constants/constants.json,sha256=ICVe1HiGoUlSyiuNptfLX7rimHJk_7SfN38MWJEL62c,5151
101
101
  setta/static/constants/db_init.sql,sha256=eSYQrcEEqNPcshOHJEOpSGGkoN_FJwMfZqJFrbnX_8U,8416
102
102
  setta/static/constants/defaultValues.json,sha256=7S8wabxk7CIM23H0FcXdLaIhhldsFUfN8lrzWAIU5pQ,2948
103
103
  setta/static/constants/settingsProject.json,sha256=yfSIQpu2zUcFnUp2JimAkZ0ispvMv8lMPUUTx4AWSi4,10006
@@ -108,7 +108,7 @@ setta/static/frontend/browserconfig.xml,sha256=w0iw1t89kA7-965LTfyLYrFzewTQnUWE_
108
108
  setta/static/frontend/favicon-16x16.png,sha256=q67Crpy8s3wryu7Y3kffPeysN99Lt4XeFygXhPKize8,740
109
109
  setta/static/frontend/favicon-32x32.png,sha256=4NKXYticYdMrRHmVveHjxqnBU1HWgBT5JyJG8lx3BNE,1027
110
110
  setta/static/frontend/favicon.ico,sha256=02qhEBLsvsgBTZX6dcZElMyivlvrR7Yr6wB8ItEZFsc,15086
111
- setta/static/frontend/index.html,sha256=6pKKvUKghX6IJ3Osmo1icsm9NVQaqwUXVSKZRfh2noo,1296
111
+ setta/static/frontend/index.html,sha256=C5t7lfzDhTvNFweX0ISsHGNyj8TkHAz8uv-VwByWtlY,1296
112
112
  setta/static/frontend/manifest.json,sha256=ULPYw5A68_eNhxuUVXqxT045yhkurKPSz6hjyGcnmhQ,492
113
113
  setta/static/frontend/mstile-144x144.png,sha256=wQqckmRWre2NCCevevI3rv4j0tcduVMkpYr2tPj73cs,2692
114
114
  setta/static/frontend/mstile-150x150.png,sha256=FUwy6PipTofnhmJB5CdXWYgwy-2inq_sIOdOwdDklcY,2674
@@ -184,8 +184,8 @@ setta/static/frontend/assets/cormorant-garamond-latin-700-italic-0bc53e12.woff2,
184
184
  setta/static/frontend/assets/cormorant-garamond-latin-ext-700-italic-525738e0.woff2,sha256=Ulc44CPXdUiI5dY86W76HLk7801Fm9_QywCV-8GRtFU,17240
185
185
  setta/static/frontend/assets/cormorant-garamond-vietnamese-700-italic-99563037.woff2,sha256=mVYwN54qI0RLXqyrDGPUNpBHWSJDKjgUyBRWa2FJ_ao,5248
186
186
  setta/static/frontend/assets/erase-5e0448ea.svg,sha256=XgRI6pChr392LJ-pGbwqqkN8OWcJMEtRGX-Gv-90qjw,872
187
- setta/static/frontend/assets/index-8204c872.js,sha256=3vVnv6_MeYW0qxkfUH5ecJwXxeb4lJevbvhWT60vXxQ,2847453
188
187
  setta/static/frontend/assets/index-af271c9f.css,sha256=ryccn_uul30_aMdIhgzMlq6GcE0D6x8fyfneCdgaY9o,232608
188
+ setta/static/frontend/assets/index-d7ede6b5.js,sha256=giu4kQ9aQjEAt6f0_j7KZXnM0XNGtAXSIdOXAO3q_40,2848353
189
189
  setta/static/frontend/assets/inter-all-400-normal-054f12d0.woff,sha256=BU8S0GmcIMyYte4ESEdQJO-WvL2Rb-38m1n0ujdbYxI,128624
190
190
  setta/static/frontend/assets/inter-all-600-normal-c03769e5.woff,sha256=wDdp5VNyQL_IbxcPThD2qI-ETg_QKj7AmCwMCjqDfLE,139072
191
191
  setta/static/frontend/assets/inter-all-800-normal-15dc6e4b.woff,sha256=FdxuS9xuVumB5nbVcCXyt3IWqzS4Z75qiRz_0FV5al0,139120
@@ -232,8 +232,8 @@ setta/static/seed/.DS_Store,sha256=ENxJvDQd7Te_U8gExcXtHE-mAeBUYOHELRfDWgN1NmA,6
232
232
  setta/static/seed/examples/.DS_Store,sha256=1lFlJ5EFymdzGAUAaI30vcaaLHt3F1LwpG7xILf9jsM,6148
233
233
  setta/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
234
234
  setta/tasks/task_runner.py,sha256=gMXpfZWFMQbix2MfrHVCKB7BxQCjO8JH2P8cxUmt1ms,849
235
- setta/tasks/tasks.py,sha256=6VLxadVQecHRw3yGJ-AGWe8ARgzyCbwr5fGga_9px1E,6007
236
- setta/tasks/utils.py,sha256=SFUn2reG9DjinBrdJ84W8CPGqt0XhEQhz5kIS-pNai4,8587
235
+ setta/tasks/tasks.py,sha256=0VYL2P9bOV5b82OHEuIIIhixBit9qcoK817x5JZdpQE,8126
236
+ setta/tasks/utils.py,sha256=9XfSoqYsUf9rBWorZTEqGXfU6J6-nDztIkkgRo1ndhQ,9509
237
237
  setta/tasks/fns/__init__.py,sha256=JhGzzQGaT9BWtF3pOmguh6pzIF9kdG3jdDNLyYZ2w7g,461
238
238
  setta/tasks/fns/codeAreaAutocomplete.py,sha256=gJ5JbjkWDyTothr-UF-YlOxrbVzj2iyOVK7XD3lfhSQ,6416
239
239
  setta/tasks/fns/codeAreaFindTemplateVars.py,sha256=vD9rY8VNPavv6VKa1bnxRPPRDNvFQy6mPIZRl-_3GnY,3708
@@ -254,9 +254,9 @@ setta/utils/generate_new_filename.py,sha256=KBLX6paDmTvXR-027TpqQkfijIXc7mCfhen-
254
254
  setta/utils/section_contents.py,sha256=V2HQPik6DfSXw4j7IalbP5AZ3OEGCbtL5ub3xL-Q_Qo,4141
255
255
  setta/utils/utils.py,sha256=KjzcvgM3Ab3IcE8vaWYtgBpwzPLKg0LmblnHLoYZJHM,9164
256
256
  setta/utils/websocket_manager.py,sha256=S2lEGZsc2OhW0yKLW9VEcH7wi7ppzTfDMQa_hxf3ovc,3772
257
- setta-0.0.3.dev8.dist-info/LICENSE,sha256=us9fuCq9wmiZVzayjKxNZ2iJYF6dROe0Qp57ToCO7XU,11361
258
- setta-0.0.3.dev8.dist-info/METADATA,sha256=HHh9zIUqN15JpKkBkfLsfC3YFSp1WYPrt1uB3Z1Wo1o,829
259
- setta-0.0.3.dev8.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
260
- setta-0.0.3.dev8.dist-info/entry_points.txt,sha256=P0qCESy9fWF2q1EQ9JufGldCSnPHplDPn8J6Bgk5hB0,42
261
- setta-0.0.3.dev8.dist-info/top_level.txt,sha256=8G4lmRzVOnJ11_DescPVHE6MQZH-o06A0nGsDDV2ngY,6
262
- setta-0.0.3.dev8.dist-info/RECORD,,
257
+ setta-0.0.3.dev9.dist-info/LICENSE,sha256=us9fuCq9wmiZVzayjKxNZ2iJYF6dROe0Qp57ToCO7XU,11361
258
+ setta-0.0.3.dev9.dist-info/METADATA,sha256=OXk3_WwJkRIycDIw9YOQFDWJpYQlWXv5TQLznCHNd_g,829
259
+ setta-0.0.3.dev9.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
260
+ setta-0.0.3.dev9.dist-info/entry_points.txt,sha256=P0qCESy9fWF2q1EQ9JufGldCSnPHplDPn8J6Bgk5hB0,42
261
+ setta-0.0.3.dev9.dist-info/top_level.txt,sha256=8G4lmRzVOnJ11_DescPVHE6MQZH-o06A0nGsDDV2ngY,6
262
+ setta-0.0.3.dev9.dist-info/RECORD,,