setta 0.0.15.dev1__py3-none-any.whl → 0.0.17__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 CHANGED
@@ -1 +1 @@
1
- __version__ = "0.0.15.dev1"
1
+ __version__ = "0.0.17"
@@ -313,7 +313,7 @@ class Exporter:
313
313
  (
314
314
  used_params,
315
315
  unused_params,
316
- positionalOnlyParams,
316
+ passingStyles,
317
317
  ) = self.export_section_params(id)
318
318
 
319
319
  selected_item_ev_refs = get_selected_item_ev_refs(self.p, id)
@@ -334,7 +334,7 @@ class Exporter:
334
334
  "callable": callable,
335
335
  "usedParams": used_params,
336
336
  "unusedParams": unused_params,
337
- "positionalOnlyParams": positionalOnlyParams,
337
+ "passingStyles": passingStyles,
338
338
  }
339
339
  info["dependencies"] = [*used_params, *used_ref_var_names]
340
340
  info["ref_var_name_positions"] = ref_var_name_positions
@@ -397,7 +397,7 @@ class Exporter:
397
397
  (
398
398
  used_params,
399
399
  unused_params,
400
- positionalOnlyParams,
400
+ passingStyles,
401
401
  ) = self.export_section_params(id)
402
402
  info = {
403
403
  "sectionId": id,
@@ -407,7 +407,7 @@ class Exporter:
407
407
  "callable": None,
408
408
  "usedParams": used_params,
409
409
  "unusedParams": unused_params,
410
- "positionalOnlyParams": positionalOnlyParams,
410
+ "passingStyles": passingStyles,
411
411
  },
412
412
  "dependencies": used_params,
413
413
  "ref_var_name_positions": [], # has to be populated when the code is generated
@@ -424,7 +424,7 @@ class Exporter:
424
424
  var_names = [x for x in var_names if x]
425
425
  used_params = []
426
426
  unused_params = []
427
- positionalOnlyParams = set()
427
+ passingStyles = {}
428
428
  for v in var_names:
429
429
  if no_entered_value(self.output[v]["value"]):
430
430
  unused_params.append(v)
@@ -432,12 +432,10 @@ class Exporter:
432
432
  used_params.append(v)
433
433
 
434
434
  curr_param_info_id = self.output[v]["paramInfoId"]
435
- positionalOnly = self.p["codeInfo"][curr_param_info_id].get(
436
- "positionalOnly", False
435
+ passingStyles[v] = self.p["codeInfo"][curr_param_info_id].get(
436
+ "passingStyle", None
437
437
  )
438
- if positionalOnly:
439
- positionalOnlyParams.add(v)
440
- return used_params, unused_params, positionalOnlyParams
438
+ return used_params, unused_params, passingStyles
441
439
 
442
440
  def export_param(self, section_id, id):
443
441
  codeInfo = self.p["codeInfo"][id]
@@ -2,7 +2,7 @@ from setta.code_gen.export_selected import get_gen_code_template_var
2
2
  from setta.code_gen.python.make_parseable import make_parseable
3
3
  from setta.utils.utils import replace_at_positions
4
4
 
5
- from ...utils.constants import C
5
+ from ...utils.constants import C, ParameterPassingStyle
6
6
  from ..utils import push_var_deep
7
7
  from .ast_utils import find_missing_imports, find_top_level_symbols
8
8
 
@@ -12,13 +12,22 @@ def get_import_text(code, chars_before_template_var):
12
12
  return f"\n{chars_before_template_var}".join(imports)
13
13
 
14
14
 
15
- def get_mappings(var_names, selected, is_dict, positionalOnlyParams=None):
15
+ def get_mappings(var_names, selected, is_dict=False, passingStyles=None):
16
+ if passingStyles is None:
17
+ passingStyles = {}
16
18
  output = ""
17
19
  relative_positions = []
18
20
  curr_position = 0
19
21
  for idx, v in enumerate(var_names):
20
22
  prefix = "" if idx == 0 else ", "
21
- if is_dict or v not in positionalOnlyParams:
23
+ if passingStyles.get(v) == ParameterPassingStyle.ARGS:
24
+ prefix = "*"
25
+ elif passingStyles.get(v) == ParameterPassingStyle.KWARGS:
26
+ prefix = "**"
27
+ elif is_dict or (
28
+ passingStyles.get(v) == ParameterPassingStyle.DEFAULT
29
+ or passingStyles.get(v) == ParameterPassingStyle.KEYWORD_ONLY
30
+ ):
22
31
  var_decl_prefix = var_declaration(
23
32
  selected[v]["name"],
24
33
  v,
@@ -40,14 +49,12 @@ def get_dict_contents(var_names, selected):
40
49
  return get_mappings(var_names, selected, is_dict=True)
41
50
 
42
51
 
43
- def get_callable_params(var_names, positionalOnlyParams, selected):
44
- return get_mappings(
45
- var_names, selected, is_dict=False, positionalOnlyParams=positionalOnlyParams
46
- )
52
+ def get_callable_params(var_names, passingStyles, selected):
53
+ return get_mappings(var_names, selected, passingStyles=passingStyles)
47
54
 
48
55
 
49
56
  def get_list_of_vars(var_names):
50
- return get_mappings(var_names, None, is_dict=False, positionalOnlyParams=var_names)
57
+ return get_mappings(var_names, None)
51
58
 
52
59
 
53
60
  def get_boolean(value):
@@ -97,7 +104,7 @@ def get_value(x, selected):
97
104
  prefix_len = len(prefix)
98
105
  else:
99
106
  output, relative_positions = get_callable_params(
100
- x["value"]["usedParams"], x["value"]["positionalOnlyParams"], selected
107
+ x["value"]["usedParams"], x["value"]["passingStyles"], selected
101
108
  )
102
109
  prefix = f"{x['value']['callable']}("
103
110
  output = f"{prefix}{output})"
@@ -2,6 +2,8 @@ import re
2
2
 
3
3
  import docstring_parser
4
4
 
5
+ from setta.utils.constants import ParameterPassingStyle
6
+
5
7
 
6
8
  def process_signature_help_response(response):
7
9
  content = None
@@ -40,13 +42,21 @@ def wrapper_parse_signature(signature, docstring, get_proposed_params):
40
42
  params = get_proposed_params(signature, docstring)
41
43
  param_info_list = []
42
44
  positional_only_indicator_idx = -1
45
+ keyword_only_indicator_idx = -1
43
46
 
44
- for idx, (name, default, description) in enumerate(params):
47
+ for idx, (name, _, _) in enumerate(params):
45
48
  if name == "/":
46
49
  positional_only_indicator_idx = idx
50
+ if name == "*":
51
+ keyword_only_indicator_idx = idx
52
+
53
+ for idx, (name, default, description) in enumerate(params):
54
+ if name in [None, "/", "*", "", "..."]:
47
55
  continue
48
- if name in [None, "", "*", "..."]:
49
- continue
56
+
57
+ passingStyle = get_passing_style(
58
+ idx, positional_only_indicator_idx, keyword_only_indicator_idx, name
59
+ )
50
60
 
51
61
  if not default:
52
62
  default = ""
@@ -57,16 +67,13 @@ def wrapper_parse_signature(signature, docstring, get_proposed_params):
57
67
  # Append parsed information to list
58
68
  param_info_list.append(
59
69
  {
60
- "name": name,
70
+ "name": name.lstrip("*"),
61
71
  "defaultVal": default,
62
72
  "description": description,
63
- "positionalOnly": False,
73
+ "passingStyle": passingStyle,
64
74
  }
65
75
  )
66
76
 
67
- for idx in range(0, positional_only_indicator_idx):
68
- param_info_list[idx]["positionalOnly"] = True
69
-
70
77
  return param_info_list
71
78
 
72
79
 
@@ -79,21 +86,55 @@ def get_pyright_param_proposals(signature, _):
79
86
  params = [(p["label"], p["documentation"]["value"]) for p in parameters]
80
87
  proposals = []
81
88
  for param, description in params:
82
- # Extract parameter name and default value
83
- if param.startswith("/"):
84
- proposals.append((param, None, None))
85
- continue
86
- parts = param.split("=")
87
- name = extract_variable_name(parts[0].strip())
88
- default = parts[1].strip() if len(parts) > 1 else None
89
+ name, _, default = parse_param_name(param)
89
90
  proposals.append((name, default, description))
90
91
 
91
92
  return proposals
92
93
 
93
94
 
94
- def extract_variable_name(code_string):
95
- # Find potential variable names
96
- potential_names = re.findall(r"\b[a-zA-Z_]\w*\b", code_string)
95
+ def parse_param_name(param_string):
96
+ # Special cases for / and *
97
+ if param_string in ["/", "*"]:
98
+ return param_string, None, None
99
+
100
+ # Extract default value if present
101
+ default_value = None
102
+ if "=" in param_string:
103
+ param_string, default_part = param_string.split("=", 1)
104
+ default_value = default_part.strip()
105
+
106
+ # Extract type annotation if present
107
+ type_annotation = None
108
+ if ":" in param_string:
109
+ param_string, type_part = param_string.split(":", 1)
110
+ type_annotation = type_part.strip()
97
111
 
98
- # Return the first valid identifier
99
- return next((name for name in potential_names if name.isidentifier()), None)
112
+ # Variable name is what remains, trimmed of whitespace
113
+ name = extract_variable_name(param_string)
114
+
115
+ return name, type_annotation, default_value
116
+
117
+
118
+ def extract_variable_name(code_string):
119
+ # Find potential variable names including * and ** prefixes
120
+ potential_names = re.findall(r"\*{1,2}?[a-zA-Z_]\w*|\b[a-zA-Z_]\w*\b", code_string)
121
+
122
+ # For names with * or **, we don't need to check isidentifier() since they're not standard identifiers
123
+ # Just return the first match
124
+ if potential_names:
125
+ return potential_names[0]
126
+ return None
127
+
128
+
129
+ def get_passing_style(
130
+ idx, positional_only_indicator_idx, keyword_only_indicator_idx, name
131
+ ):
132
+ if name.startswith("**"): # Check for **kwargs first
133
+ return ParameterPassingStyle.KWARGS
134
+ if name.startswith("*"): # Check for *args second
135
+ return ParameterPassingStyle.ARGS
136
+ if 0 <= idx < positional_only_indicator_idx: # Then check position
137
+ return ParameterPassingStyle.POSITIONAL_ONLY
138
+ if keyword_only_indicator_idx > -1 and idx > keyword_only_indicator_idx:
139
+ return ParameterPassingStyle.KEYWORD_ONLY
140
+ return ParameterPassingStyle.DEFAULT
@@ -17,12 +17,17 @@ from setta.code_gen.export_selected import (
17
17
  get_section_type,
18
18
  )
19
19
  from setta.code_gen.find_placeholders import parse_template_var
20
+ from setta.database.db.notifications.load import (
21
+ create_notification_dict,
22
+ load_notification,
23
+ )
24
+ from setta.database.db.notifications.save import save_notification
20
25
  from setta.tasks.fns.utils import replace_template_vars_with_random_names
21
26
  from setta.tasks.tasks import construct_subprocess_key
22
27
  from setta.utils.constants import C
23
28
  from setta.utils.utils import multireplace
24
29
 
25
- from .dependencies import get_lsp_writers, get_tasks
30
+ from .dependencies import get_dbq, get_lsp_writers, get_tasks
26
31
 
27
32
  router = APIRouter()
28
33
 
@@ -36,6 +41,10 @@ class FormatCodeRequest(BaseModel):
36
41
  candidateTemplateVars: dict
37
42
 
38
43
 
44
+ class KillInMemorySubprocessesRequest(BaseModel):
45
+ projectConfigId: str
46
+
47
+
39
48
  @router.post(C.ROUTE_SEND_PROJECT_TO_INTERACTIVE_CODE)
40
49
  async def route_send_project_to_interactive_code(
41
50
  x: UpdateInteractiveCodeRequest,
@@ -68,9 +77,20 @@ async def route_update_interactive_code(
68
77
 
69
78
  async def process_returned_content_from_multiple_tasks(tasks, to_run):
70
79
  all_content = await asyncio.gather(*to_run)
71
- content = [item for sublist in all_content for item in sublist]
80
+ content = []
81
+ exception_occurred = False
82
+ for sublist in all_content:
83
+ for item in sublist:
84
+ if isinstance(item, Exception):
85
+ exception_occurred = True
86
+ else:
87
+ content.append(item)
72
88
  inMemorySubprocessInfo = tasks.getInMemorySubprocessInfo()
73
- return {"inMemorySubprocessInfo": inMemorySubprocessInfo, "content": content}
89
+ return {
90
+ "inMemorySubprocessInfo": inMemorySubprocessInfo,
91
+ "content": content,
92
+ "exceptionOccurred": exception_occurred,
93
+ }
74
94
 
75
95
 
76
96
  async def update_interactive_code(p, tasks, lsp_writers, idx):
@@ -139,6 +159,39 @@ async def update_interactive_code(p, tasks, lsp_writers, idx):
139
159
  return initialContent
140
160
 
141
161
 
162
+ @router.post(C.ROUTE_KILL_IN_MEMORY_SUBPROCESSES)
163
+ async def route_kill_in_memory_subprocesses(
164
+ x: KillInMemorySubprocessesRequest, tasks=Depends(get_tasks), dbq=Depends(get_dbq)
165
+ ):
166
+ notification = None
167
+ try:
168
+ num_killed = tasks.kill_in_memory_subprocesses()
169
+ if num_killed == 0:
170
+ notification = create_notification_dict(
171
+ message="No subprocesses to kill",
172
+ type=C.NOTIFICATION_TYPE_INFO,
173
+ temporary=True,
174
+ )
175
+ else:
176
+ with dbq as db:
177
+ notification_id = save_notification(
178
+ db,
179
+ x.projectConfigId,
180
+ C.NOTIFICATION_TYPE_INFO,
181
+ f"Killed {num_killed} subprocesses",
182
+ )
183
+ notification = load_notification(db, notification_id)
184
+
185
+ except Exception as e:
186
+ notification = create_notification_dict(
187
+ message="Failed to kill subprocesses",
188
+ type=C.NOTIFICATION_TYPE_ERROR,
189
+ temporary=True,
190
+ )
191
+
192
+ return {"notification": notification}
193
+
194
+
142
195
  @router.post(C.ROUTE_FORMAT_CODE)
143
196
  async def route_format_code(x: FormatCodeRequest):
144
197
  p = x.project
setta/routers/projects.py CHANGED
@@ -5,6 +5,11 @@ from fastapi import APIRouter, Depends, HTTPException
5
5
  from pydantic import BaseModel
6
6
 
7
7
  from setta.database.backup import maybe_create_backup
8
+ from setta.database.db.notifications.delete import delete_notification
9
+ from setta.database.db.notifications.load import (
10
+ create_notification_dict,
11
+ load_project_notifications,
12
+ )
8
13
  from setta.database.db.projects.delete import delete_project_configs
9
14
  from setta.database.db.projects.load import (
10
15
  ProjectNotFound,
@@ -85,6 +90,14 @@ class AddDefaultDataForJSONImportRequest(BaseModel):
85
90
  project: dict
86
91
 
87
92
 
93
+ class GetNotificationsRequest(BaseModel):
94
+ projectConfigId: str
95
+
96
+
97
+ class DeleteNotificationRequest(BaseModel):
98
+ notificationId: str
99
+
100
+
88
101
  @router.post(C.ROUTE_ALL_PROJECT_CONFIG_METADATA)
89
102
  def route_all_project_config_metadata(dbq=Depends(get_dbq)):
90
103
  with dbq as db:
@@ -120,9 +133,21 @@ def route_load_full_project(x: ProjectLoadFullRequest, dbq=Depends(get_dbq)):
120
133
  @router.post(C.ROUTE_SAVE_PROJECT)
121
134
  def route_save_project(x: ProjectSaveRequest, dbq=Depends(get_dbq)):
122
135
  with dbq as db:
123
- maybe_create_backup(db.path)
124
- save_project_details(db, x.project)
125
- maybe_export_database(db, db.path)
136
+ notification = None
137
+ try:
138
+ # Perform existing save operations
139
+ maybe_create_backup(db.path)
140
+ save_project_details(db, x.project)
141
+ maybe_export_database(db, db.path)
142
+ # Return success response with notification
143
+ notification = create_notification_dict(message="Saved!", temporary=True)
144
+
145
+ except Exception as e:
146
+ notification = create_notification_dict(
147
+ message="Failed to save", type=C.NOTIFICATION_TYPE_ERROR, temporary=True
148
+ )
149
+
150
+ return {"notification": notification}
126
151
 
127
152
 
128
153
  @router.post(C.ROUTE_CREATE_PROJECT_CONFIG)
@@ -186,3 +211,17 @@ def router_filter_data_for_json_export(x: FilterDataForJSONExportRequest):
186
211
  def router_add_default_data_for_json_import(x: FilterDataForJSONExportRequest):
187
212
  add_defaults_to_project_and_load_json_sources(x.project)
188
213
  return x.project
214
+
215
+
216
+ @router.post(C.ROUTE_GET_NOTIFICATIONS)
217
+ def route_get_notifications(x: GetNotificationsRequest, dbq=Depends(get_dbq)):
218
+ with dbq as db:
219
+ notifications = load_project_notifications(db, x.projectConfigId)
220
+ return notifications
221
+
222
+
223
+ @router.post(C.ROUTE_DELETE_NOTIFICATION)
224
+ def route_delete_notification(x: DeleteNotificationRequest, dbq=Depends(get_dbq)):
225
+ with dbq as db:
226
+ success = delete_notification(db, x.notificationId)
227
+ return {"success": success}
setta/routers/settings.py CHANGED
@@ -1,6 +1,7 @@
1
1
  from fastapi import APIRouter, Depends
2
2
  from pydantic import BaseModel
3
3
 
4
+ from setta.database.db.notifications.load import create_notification_dict
4
5
  from setta.utils.constants import C
5
6
 
6
7
  from .dependencies import get_settings_file
@@ -32,7 +33,16 @@ def route_load_settings(settings_file=Depends(get_settings_file)):
32
33
  def route_save_settings_project(
33
34
  x: SaveSettingsProjectRequest, settings_file=Depends(get_settings_file)
34
35
  ):
35
- return settings_file.save_settings_project(x.settingsProject)
36
+ notification = None
37
+ try:
38
+ settings_file.save_settings_project(x.settingsProject)
39
+ notification = create_notification_dict(message="Saved!", temporary=True)
40
+ except Exception as e:
41
+ notification = create_notification_dict(
42
+ message="Failed to save", type=C.NOTIFICATION_TYPE_ERROR, temporary=True
43
+ )
44
+
45
+ return {"notification": notification}
36
46
 
37
47
 
38
48
  @router.post(C.ROUTE_LOAD_SETTINGS_PROJECT)
@@ -13,11 +13,12 @@
13
13
  "loadShortcut": "Mod + L",
14
14
  "searchShortcut": "Mod + K",
15
15
  "advancedSearchShortcut": "Mod + P",
16
+ "commandPaletteShortcut": "Mod + Shift + P",
16
17
  "resetZoomShortcut": "Mod + Comma",
17
18
  "copyShortcut": "Mod + C",
18
19
  "pasteShortcut": "Mod + V",
19
20
  "pasteAsRefShortcut": "Mod + Shift + V",
20
- "sortingShortcut": "Shift",
21
+ "sortingShortcut": "Mod + Alt",
21
22
  "movingFreeShortcut": "Alt",
22
23
  "showTooltipShortcut": "Q",
23
24
  "tooltipToPlaintextShortcut": "Q + S",
@@ -71,6 +71,7 @@
71
71
  "ROUTE_COPY_SECTIONS": "/copySections",
72
72
  "ROUTE_UPDATE_INTERACTIVE_CODE": "/updateInteractiveCode",
73
73
  "ROUTE_SEND_PROJECT_TO_INTERACTIVE_CODE": "/sendProjectTointeractiveCode",
74
+ "ROUTE_KILL_IN_MEMORY_SUBPROCESSES": "/killInMemorySubprocesses",
74
75
  "ROUTE_FORMAT_CODE": "/formatCode",
75
76
  "ROUTE_SEND_ARTIFACT": "/sendArtfiact",
76
77
  "ROUTE_LOAD_ARTIFACTS": "/loadArtifacts",
@@ -96,6 +97,8 @@
96
97
  "ROUTE_LOAD_ARTIFACT_FROM_DISK": "/loadArtifactFromDisk",
97
98
  "ROUTE_RESTART_LANGUAGE_SERVER": "/restartLanguageServer",
98
99
  "ROUTE_FILE_WATCH_LIST": "/fileWatchList",
100
+ "ROUTE_GET_NOTIFICATIONS": "/getNotifications",
101
+ "ROUTE_DELETE_NOTIFICATION": "/deleteNotification",
99
102
  "NESTED_PARAM": "NESTED_PARAM",
100
103
  "ARGS_PREFIX": "__",
101
104
  "TEMPLATE_PREFIX": "$",
@@ -122,5 +125,10 @@
122
125
  "IMAGE": ["img"],
123
126
  "CHART": ["list"],
124
127
  "CHAT": ["chatHistory"]
125
- }
128
+ },
129
+ "NOTIFICATION_TYPE_SUCCESS": "success",
130
+ "NOTIFICATION_TYPE_ERROR": "error",
131
+ "NOTIFICATION_TYPE_WARNING": "warning",
132
+ "NOTIFICATION_TYPE_INFO": "info",
133
+ "NOTIFICATION_TYPE_SAVE": "save"
126
134
  }
@@ -221,6 +221,21 @@ CREATE TABLE IF NOT EXISTS ArtifactGroup (
221
221
  FOREIGN KEY (artifactId) REFERENCES Artifact(id) ON DELETE CASCADE
222
222
  );
223
223
 
224
+ CREATE TABLE IF NOT EXISTS Notifications (
225
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
226
+ projectConfigId TEXT NOT NULL,
227
+ timestamp TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
228
+ type TEXT NOT NULL,
229
+ message TEXT NOT NULL,
230
+ metadata JSON,
231
+ read_status BOOLEAN NOT NULL DEFAULT 0,
232
+ FOREIGN KEY (projectConfigId) REFERENCES ProjectConfig(id)
233
+ );
234
+
235
+
236
+
237
+
238
+
224
239
  -- INSERTS ---
225
240
  INSERT OR IGNORE INTO Metadata (id, defaultProject) VALUES (1, NULL);
226
241
 
@@ -90,7 +90,7 @@
90
90
  "rcType": "parameter",
91
91
  "defaultVal": "",
92
92
  "description": "",
93
- "positionalOnly": false,
93
+ "passingStyle": "DEFAULT",
94
94
  "isPinned": false,
95
95
  "isFrozen": false,
96
96
  "isSelected": false,