universal-mcp 0.1.21rc2__py3-none-any.whl → 0.1.22__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.
@@ -1,10 +1,11 @@
1
1
  import ast
2
2
  import re
3
+ import subprocess
3
4
  from collections import defaultdict
4
5
  from keyword import iskeyword
5
6
  from pathlib import Path
6
7
 
7
- API_SEGMENT_BASE_CODE = '''
8
+ API_SEGMENT_BASE_CODE = """
8
9
  from typing import Any
9
10
 
10
11
  class APISegmentBase:
@@ -25,7 +26,8 @@ class APISegmentBase:
25
26
 
26
27
  def _delete(self, url: str, params: dict = None, **kwargs):
27
28
  return self.main_app_client._delete(url, params=params, **kwargs)
28
- '''
29
+ """
30
+
29
31
 
30
32
  def get_sanitized_path_segment(openapi_path: str) -> str:
31
33
  # Remove leading/trailing slashes and split
@@ -35,13 +37,13 @@ def get_sanitized_path_segment(openapi_path: str) -> str:
35
37
  return "default_api"
36
38
 
37
39
  # Handle common prefixes like /api/ or /v1/api/ etc.
38
- known_prefixes = ["api"]
40
+ known_prefixes = ["api"]
39
41
  while len(path_parts) > 0 and path_parts[0].lower() in known_prefixes:
40
42
  path_parts.pop(0)
41
- if not path_parts:
42
- return "default_api"
43
+ if not path_parts:
44
+ return "default_api"
43
45
 
44
- segment_to_use = "default_api"
46
+ segment_to_use = "default_api"
45
47
 
46
48
  # check if the current first segment is version-like (e.g., "2", "v1", "v0")
47
49
  if path_parts:
@@ -56,74 +58,73 @@ def get_sanitized_path_segment(openapi_path: str) -> str:
56
58
  # If it's not a version segment, use it directly
57
59
  segment_to_use = path_parts[0]
58
60
  else:
59
-
60
61
  segment_to_use = f"api_{first_segment}"
61
- else: # Path was empty after stripping prefixes (e.g. "/api/")
62
- return "default_api" # segment_to_use remains "default_api"
62
+ else: # Path was empty after stripping prefixes (e.g. "/api/")
63
+ return "default_api" # segment_to_use remains "default_api"
63
64
 
64
65
  # Sanitize the chosen segment to be a valid Python identifier component
65
66
  # Replace non-alphanumeric (excluding underscore) with underscore
66
67
  sanitized_segment = re.sub(r"[^a-zA-Z0-9_]", "_", segment_to_use)
67
-
68
+
68
69
  # Remove leading/trailing underscores that might result from sanitization
69
70
  sanitized_segment = sanitized_segment.strip("_")
70
71
 
71
-
72
72
  if not sanitized_segment:
73
73
  return "default_api"
74
- if sanitized_segment.isdigit(): # e.g. if path was /2/123 -> segment is 123
74
+ if sanitized_segment.isdigit(): # e.g. if path was /2/123 -> segment is 123
75
75
  return f"api_{sanitized_segment}"
76
-
76
+
77
77
  return sanitized_segment
78
78
 
79
+
79
80
  def get_group_name_from_path(openapi_path: str) -> str:
80
81
  processed_path = openapi_path
81
-
82
+
82
83
  # Pattern for /vN, /vN.N, /vN.N.N
83
84
  version_pattern_v_prefix = re.compile(r"^/v[0-9]+(?:\\.[0-9]+){0,2}")
84
85
  # Pattern for just /N (like /2)
85
86
  version_pattern_numeric_prefix = re.compile(r"^/[0-9]+")
86
-
87
+
87
88
  api_prefix_pattern = re.compile(r"^/api")
88
89
 
89
90
  # Strip /api prefix first if present
90
91
  if api_prefix_pattern.match(processed_path):
91
92
  processed_path = api_prefix_pattern.sub("", processed_path)
92
- processed_path = processed_path.lstrip("/") # Ensure we strip leading slash if api was the only thing
93
+ processed_path = processed_path.lstrip("/") # Ensure we strip leading slash if api was the only thing
93
94
  if processed_path and not processed_path.startswith("/"):
94
- processed_path = "/" + processed_path
95
- elif not processed_path: # Path was only /api/
96
- processed_path = "/" # Reset to / so subsequent logic doesn't fail
95
+ processed_path = "/" + processed_path
96
+ elif not processed_path: # Path was only /api/
97
+ processed_path = "/" # Reset to / so subsequent logic doesn't fail
97
98
 
98
99
  # Try to strip /vN style version
99
100
  path_after_v_version_strip = version_pattern_v_prefix.sub("", processed_path)
100
-
101
- if path_after_v_version_strip != processed_path: # /vN was stripped
101
+
102
+ if path_after_v_version_strip != processed_path: # /vN was stripped
102
103
  processed_path = path_after_v_version_strip
103
- else: # /vN was not found, try to strip /N (like /2/)
104
+ else: # /vN was not found, try to strip /N (like /2/)
104
105
  path_after_numeric_version_strip = version_pattern_numeric_prefix.sub("", processed_path)
105
- if path_after_numeric_version_strip != processed_path: # /N was stripped
106
+ if path_after_numeric_version_strip != processed_path: # /N was stripped
106
107
  processed_path = path_after_numeric_version_strip
107
-
108
+
108
109
  processed_path = processed_path.lstrip("/")
109
110
 
110
- path_segments = [segment for segment in processed_path.split("/") if segment] # Ensure no empty segments
111
+ path_segments = [segment for segment in processed_path.split("/") if segment] # Ensure no empty segments
111
112
 
112
113
  group_name_raw = "default"
113
114
  if path_segments and path_segments[0]:
114
115
  # Remove {param} style parts from the segment if any
115
- group_name_raw = re.sub(r"[{}]", "", path_segments[0]) # Corrected regex string
116
-
116
+ group_name_raw = re.sub(r"[{}]", "", path_segments[0]) # Corrected regex string
117
+
117
118
  # Sanitize to make it a valid Python identifier component (lowercase, underscores)
118
- group_name = re.sub(r"[^a-zA-Z0-9_]", "_", group_name_raw).lower() # Corrected regex string
119
+ group_name = re.sub(r"[^a-zA-Z0-9_]", "_", group_name_raw).lower() # Corrected regex string
119
120
  group_name = group_name.strip("_")
120
121
 
121
- if not group_name or group_name.isdigit(): # If empty after sanitization or purely numeric
122
+ if not group_name or group_name.isdigit(): # If empty after sanitization or purely numeric
122
123
  group_name = f"api_{group_name}" if group_name else "default_api"
123
-
124
- if iskeyword(group_name): # Avoid Python keywords
124
+
125
+ if iskeyword(group_name): # Avoid Python keywords
125
126
  group_name += "_"
126
-
127
+
127
128
  return group_name if group_name else "default_api"
128
129
 
129
130
 
@@ -137,11 +138,11 @@ class MethodTransformer(ast.NodeTransformer):
137
138
  return node
138
139
 
139
140
  def visit_Attribute(self, node: ast.Attribute) -> ast.AST:
140
- if isinstance(node.value, ast.Name) and node.value.id == 'self' and node.attr == 'base_url':
141
+ if isinstance(node.value, ast.Name) and node.value.id == "self" and node.attr == "base_url":
141
142
  return ast.Attribute(
142
- value=ast.Attribute(value=ast.Name(id='self', ctx=ast.Load()), attr='main_app_client', ctx=ast.Load()),
143
- attr='base_url',
144
- ctx=ast.Load()
143
+ value=ast.Attribute(value=ast.Name(id="self", ctx=ast.Load()), attr="main_app_client", ctx=ast.Load()),
144
+ attr="base_url",
145
+ ctx=ast.Load(),
145
146
  )
146
147
  return self.generic_visit(node)
147
148
 
@@ -154,78 +155,86 @@ def split_generated_app_file(input_app_file: Path, output_dir: Path):
154
155
  tree = ast.parse(content)
155
156
 
156
157
  main_app_class_node = None
157
- for node_item in tree.body: # Renamed to avoid conflict with ast.Node
158
- if isinstance(node_item, ast.ClassDef) and \
159
- any(isinstance(base, ast.Name) and base.id == 'APIApplication' for base in node_item.bases if isinstance(base, ast.Name)):
158
+ for node_item in tree.body: # Renamed to avoid conflict with ast.Node
159
+ if isinstance(node_item, ast.ClassDef) and any(
160
+ isinstance(base, ast.Name) and base.id == "APIApplication"
161
+ for base in node_item.bases
162
+ if isinstance(base, ast.Name)
163
+ ):
160
164
  main_app_class_node = node_item
161
165
  break
162
-
166
+
163
167
  if not main_app_class_node:
164
168
  raise ValueError("Could not find main APIApplication class in the input file.")
165
169
 
166
170
  grouped_methods = defaultdict(list)
167
171
  other_main_app_body_nodes = []
168
172
  processed_method_names_in_main = set()
169
-
173
+
170
174
  openapi_path_regex = re.compile(r"# openapi_path: (.+)")
171
175
 
172
176
  for item in main_app_class_node.body:
173
177
  if isinstance(item, ast.FunctionDef):
174
178
  path_from_comment = None
175
- if item.body and isinstance(item.body[0], ast.Expr) and \
176
- isinstance(item.body[0].value, ast.Constant) and isinstance(item.body[0].value.value, str):
179
+ if (
180
+ item.body
181
+ and isinstance(item.body[0], ast.Expr)
182
+ and isinstance(item.body[0].value, ast.Constant)
183
+ and isinstance(item.body[0].value.value, str)
184
+ ):
177
185
  docstring_lines = item.body[0].value.value.strip().splitlines()
178
186
  if docstring_lines:
179
187
  match = openapi_path_regex.match(docstring_lines[0].strip())
180
188
  if match:
181
189
  path_from_comment = match.group(1).strip()
182
-
190
+
183
191
  if path_from_comment:
184
192
  group = get_group_name_from_path(path_from_comment)
185
193
  method_node_copy = ast.parse(ast.unparse(item)).body[0]
186
194
  if not isinstance(method_node_copy, ast.FunctionDef):
187
- method_node_copy = item
195
+ method_node_copy = item
188
196
 
189
197
  transformer = MethodTransformer(original_path=path_from_comment)
190
198
  transformed_method_node = transformer.visit(method_node_copy)
191
- if hasattr(ast, 'fix_missing_locations'):
199
+ if hasattr(ast, "fix_missing_locations"):
192
200
  transformed_method_node = ast.fix_missing_locations(transformed_method_node)
193
201
 
194
202
  # Remove the # openapi_path: comment from the docstring
195
- if (transformed_method_node.body and
196
- isinstance(transformed_method_node.body[0], ast.Expr) and
197
- isinstance(transformed_method_node.body[0].value, ast.Constant) and
198
- isinstance(transformed_method_node.body[0].value.value, str)):
199
-
203
+ if (
204
+ transformed_method_node.body
205
+ and isinstance(transformed_method_node.body[0], ast.Expr)
206
+ and isinstance(transformed_method_node.body[0].value, ast.Constant)
207
+ and isinstance(transformed_method_node.body[0].value.value, str)
208
+ ):
200
209
  docstring_expr_node = transformed_method_node.body[0]
201
210
  original_docstring_text = docstring_expr_node.value.value
202
-
211
+
203
212
  all_lines_raw = original_docstring_text.splitlines(True)
204
213
  line_to_remove_idx = -1
205
-
214
+
206
215
  for i, current_line_raw in enumerate(all_lines_raw):
207
216
  current_line_stripped = current_line_raw.strip()
208
- if current_line_stripped: # Found the first significant line
217
+ if current_line_stripped: # Found the first significant line
209
218
  if openapi_path_regex.match(current_line_stripped):
210
219
  line_to_remove_idx = i
211
- break # Only inspect the first significant line
220
+ break # Only inspect the first significant line
212
221
 
213
222
  if line_to_remove_idx != -1:
214
223
  del all_lines_raw[line_to_remove_idx]
215
224
  modified_docstring_text = "".join(all_lines_raw)
216
-
225
+
217
226
  if not modified_docstring_text.strip():
218
- transformed_method_node.body.pop(0) # Docstring is now empty
227
+ transformed_method_node.body.pop(0) # Docstring is now empty
219
228
  else:
220
229
  docstring_expr_node.value.value = modified_docstring_text
221
-
230
+
222
231
  grouped_methods[group].append(transformed_method_node)
223
232
  processed_method_names_in_main.add(item.name)
224
233
  else:
225
234
  other_main_app_body_nodes.append(item)
226
235
  else:
227
236
  other_main_app_body_nodes.append(item)
228
-
237
+
229
238
  # Define segments subfolder
230
239
  segments_foldername = "api_segments"
231
240
  segments_dir = output_dir / segments_foldername
@@ -238,126 +247,222 @@ def split_generated_app_file(input_app_file: Path, output_dir: Path):
238
247
  for group, method_nodes in grouped_methods.items():
239
248
  SegmentClassName = "".join(word.capitalize() for word in group.split("_")) + "Api"
240
249
  segment_filename = f"{group.lower()}_api.py"
241
-
250
+
242
251
  method_names_for_list_tools = [method_node.name for method_node in method_nodes]
243
252
 
244
- list_tools_body = [ast.Return(value=ast.List(
245
- elts=[ast.Attribute(value=ast.Name(id='self', ctx=ast.Load()), attr=name, ctx=ast.Load()) for name in method_names_for_list_tools],
246
- ctx=ast.Load()
247
- ))]
253
+ list_tools_body = [
254
+ ast.Return(
255
+ value=ast.List(
256
+ elts=[
257
+ ast.Attribute(value=ast.Name(id="self", ctx=ast.Load()), attr=name, ctx=ast.Load())
258
+ for name in method_names_for_list_tools
259
+ ],
260
+ ctx=ast.Load(),
261
+ )
262
+ )
263
+ ]
248
264
  list_tools_def = ast.FunctionDef(
249
- name='list_tools',
250
- args=ast.arguments(posonlyargs=[], args=[ast.arg(arg='self')], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
251
- body=list_tools_body, decorator_list=[], returns=None
265
+ name="list_tools",
266
+ args=ast.arguments(
267
+ posonlyargs=[],
268
+ args=[ast.arg(arg="self")],
269
+ vararg=None,
270
+ kwonlyargs=[],
271
+ kw_defaults=[],
272
+ kwarg=None,
273
+ defaults=[],
274
+ ),
275
+ body=list_tools_body,
276
+ decorator_list=[],
277
+ returns=None,
252
278
  )
253
-
279
+
254
280
  init_method_segment = ast.FunctionDef(
255
- name='__init__',
281
+ name="__init__",
256
282
  args=ast.arguments(
257
283
  posonlyargs=[],
258
- args=[ast.arg(arg='self'), ast.arg(arg='main_app_client', annotation=ast.Name(id='Any', ctx=ast.Load()))],
259
- vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]
284
+ args=[
285
+ ast.arg(arg="self"),
286
+ ast.arg(arg="main_app_client", annotation=ast.Name(id="Any", ctx=ast.Load())),
287
+ ],
288
+ vararg=None,
289
+ kwonlyargs=[],
290
+ kw_defaults=[],
291
+ kwarg=None,
292
+ defaults=[],
260
293
  ),
261
294
  body=[
262
- ast.Expr(value=ast.Call(
263
- func=ast.Attribute(value=ast.Call(func=ast.Name(id='super', ctx=ast.Load()), args=[], keywords=[]), attr='__init__', ctx=ast.Load()),
264
- args=[ast.Name(id='main_app_client', ctx=ast.Load())],
265
- keywords=[]
266
- ))
295
+ ast.Expr(
296
+ value=ast.Call(
297
+ func=ast.Attribute(
298
+ value=ast.Call(func=ast.Name(id="super", ctx=ast.Load()), args=[], keywords=[]),
299
+ attr="__init__",
300
+ ctx=ast.Load(),
301
+ ),
302
+ args=[ast.Name(id="main_app_client", ctx=ast.Load())],
303
+ keywords=[],
304
+ )
305
+ )
267
306
  ],
268
- decorator_list=[], returns=None
307
+ decorator_list=[],
308
+ returns=None,
269
309
  )
270
310
 
271
311
  segment_class_body = [init_method_segment] + method_nodes + [list_tools_def]
272
312
  segment_class_node = ast.ClassDef(
273
313
  name=SegmentClassName,
274
- bases=[ast.Name(id='APISegmentBase', ctx=ast.Load())],
275
- keywords=[], body=segment_class_body, decorator_list=[]
314
+ bases=[ast.Name(id="APISegmentBase", ctx=ast.Load())],
315
+ keywords=[],
316
+ body=segment_class_body,
317
+ decorator_list=[],
276
318
  )
277
-
319
+
278
320
  segment_module_body = [
279
- ast.ImportFrom(module='typing', names=[ast.alias(name='Any'), ast.alias(name='Dict'), ast.alias(name='Optional')], level=0),
280
- ast.ImportFrom(module='.api_segment_base', names=[ast.alias(name='APISegmentBase')], level=0), # This relative import is fine as they are in the same dir
281
- segment_class_node
321
+ ast.ImportFrom(
322
+ module="typing",
323
+ names=[ast.alias(name="Any"), ast.alias(name="List"), ast.alias(name="Optional")],
324
+ level=0,
325
+ ),
326
+ ast.ImportFrom(
327
+ module=".api_segment_base", names=[ast.alias(name="APISegmentBase")], level=0
328
+ ), # This relative import is fine as they are in the same dir
329
+ segment_class_node,
282
330
  ]
283
331
  segment_module_ast = ast.Module(body=segment_module_body, type_ignores=[])
284
- if hasattr(ast, 'fix_missing_locations'):
332
+ if hasattr(ast, "fix_missing_locations"):
285
333
  segment_module_ast = ast.fix_missing_locations(segment_module_ast)
286
-
334
+
287
335
  (segments_dir / segment_filename).write_text(ast.unparse(segment_module_ast))
288
- segment_class_details_for_main_app.append({
289
- "attr_name": group.lower(),
290
- "class_name": SegmentClassName,
291
- "module_name": segment_filename.replace(".py", "") # Used for import in main app
292
- })
336
+ # Format the generated segment file with black
337
+ subprocess.run(["black", str(segments_dir / segment_filename)])
338
+ segment_class_details_for_main_app.append(
339
+ {
340
+ "attr_name": group.lower(),
341
+ "class_name": SegmentClassName,
342
+ "module_name": segment_filename.replace(".py", ""), # Used for import in main app
343
+ }
344
+ )
293
345
 
294
346
  new_main_app_body = []
295
347
  main_app_init_node = None
296
348
 
297
349
  for node in other_main_app_body_nodes:
298
350
  if isinstance(node, ast.FunctionDef):
299
- if node.name == '__init__':
351
+ if node.name == "__init__":
300
352
  main_app_init_node = node
301
353
  continue
302
- elif node.name == 'list_tools':
354
+ elif node.name == "list_tools":
303
355
  continue
304
356
  new_main_app_body.append(node)
305
357
 
306
358
  if not main_app_init_node:
307
359
  main_app_init_node = ast.FunctionDef(
308
- name='__init__',
360
+ name="__init__",
309
361
  args=ast.arguments(
310
- posonlyargs=[],
311
- args=[ast.arg(arg='self'), ast.arg(arg='integration', annotation=ast.Name(id='Integration', ctx=ast.Load()), default=ast.Constant(value=None))],
312
- vararg=ast.arg(arg='args'),
313
- kwonlyargs=[], kw_defaults=[],
314
- kwarg=ast.arg(arg='kwargs'),
315
- defaults=[ast.Constant(value=None)]
362
+ posonlyargs=[],
363
+ args=[
364
+ ast.arg(arg="self"),
365
+ ast.arg(
366
+ arg="integration",
367
+ annotation=ast.Name(id="Integration", ctx=ast.Load()),
368
+ default=ast.Constant(value=None),
369
+ ),
370
+ ],
371
+ vararg=ast.arg(arg="args"),
372
+ kwonlyargs=[],
373
+ kw_defaults=[],
374
+ kwarg=ast.arg(arg="kwargs"),
375
+ defaults=[ast.Constant(value=None)],
316
376
  ),
317
377
  body=[
318
- ast.Expr(value=ast.Call(
319
- func=ast.Attribute(value=ast.Call(func=ast.Name(id='super', ctx=ast.Load()), args=[], keywords=[]), attr='__init__', ctx=ast.Load()),
320
- args=[ast.keyword(arg='name', value=ast.Constant(value=main_app_class_node.name.lower())), ast.Name(id='integration', ctx=ast.Load())],
321
- keywords=[ast.keyword(arg=None, value=ast.Name(id='kwargs',ctx=ast.Load()))]
322
- ))
378
+ ast.Expr(
379
+ value=ast.Call(
380
+ func=ast.Attribute(
381
+ value=ast.Call(func=ast.Name(id="super", ctx=ast.Load()), args=[], keywords=[]),
382
+ attr="__init__",
383
+ ctx=ast.Load(),
384
+ ),
385
+ args=[
386
+ ast.keyword(arg="name", value=ast.Constant(value=main_app_class_node.name.lower())),
387
+ ast.Name(id="integration", ctx=ast.Load()),
388
+ ],
389
+ keywords=[ast.keyword(arg=None, value=ast.Name(id="kwargs", ctx=ast.Load()))],
390
+ )
391
+ )
323
392
  ],
324
- decorator_list=[], returns=ast.Constant(value=None)
393
+ decorator_list=[],
394
+ returns=ast.Constant(value=None),
325
395
  )
326
396
 
327
397
  init_segment_instantiations = []
328
398
  for seg_detail in segment_class_details_for_main_app:
329
399
  init_segment_instantiations.append(
330
400
  ast.Assign(
331
- targets=[ast.Attribute(value=ast.Name(id='self', ctx=ast.Load()), attr=seg_detail["attr_name"], ctx=ast.Store())],
332
- value=ast.Call(func=ast.Name(id=seg_detail["class_name"], ctx=ast.Load()), args=[ast.Name(id='self', ctx=ast.Load())], keywords=[])
401
+ targets=[
402
+ ast.Attribute(
403
+ value=ast.Name(id="self", ctx=ast.Load()), attr=seg_detail["attr_name"], ctx=ast.Store()
404
+ )
405
+ ],
406
+ value=ast.Call(
407
+ func=ast.Name(id=seg_detail["class_name"], ctx=ast.Load()),
408
+ args=[ast.Name(id="self", ctx=ast.Load())],
409
+ keywords=[],
410
+ ),
333
411
  )
334
412
  )
335
413
  if not isinstance(main_app_init_node.body, list):
336
- main_app_init_node.body = [main_app_init_node.body] # type: ignore
337
-
414
+ main_app_init_node.body = [main_app_init_node.body] # type: ignore
415
+
338
416
  main_app_init_node.body.extend(init_segment_instantiations)
339
417
  new_main_app_body.insert(0, main_app_init_node)
340
418
 
341
419
  list_tools_calls_for_main = [
342
420
  ast.Call(
343
- func=ast.Attribute(value=ast.Attribute(value=ast.Name(id='self', ctx=ast.Load()), attr=seg_detail["attr_name"], ctx=ast.Load()), attr='list_tools', ctx=ast.Load()),
344
- args=[], keywords=[]
345
- ) for seg_detail in segment_class_details_for_main_app
421
+ func=ast.Attribute(
422
+ value=ast.Attribute(
423
+ value=ast.Name(id="self", ctx=ast.Load()), attr=seg_detail["attr_name"], ctx=ast.Load()
424
+ ),
425
+ attr="list_tools",
426
+ ctx=ast.Load(),
427
+ ),
428
+ args=[],
429
+ keywords=[],
430
+ )
431
+ for seg_detail in segment_class_details_for_main_app
432
+ ]
433
+
434
+ new_list_tools_body = [
435
+ ast.Assign(targets=[ast.Name(id="all_tools", ctx=ast.Store())], value=ast.List(elts=[], ctx=ast.Load()))
346
436
  ]
347
-
348
- new_list_tools_body = [ast.Assign(targets=[ast.Name(id='all_tools', ctx=ast.Store())], value=ast.List(elts=[], ctx=ast.Load()))]
349
437
  if list_tools_calls_for_main:
350
438
  for call_node in list_tools_calls_for_main:
351
- new_list_tools_body.append(ast.Expr(value=ast.Call(
352
- func=ast.Attribute(value=ast.Name(id='all_tools', ctx=ast.Load()), attr='extend', ctx=ast.Load()),
353
- args=[call_node], keywords=[]))
439
+ new_list_tools_body.append(
440
+ ast.Expr(
441
+ value=ast.Call(
442
+ func=ast.Attribute(
443
+ value=ast.Name(id="all_tools", ctx=ast.Load()), attr="extend", ctx=ast.Load()
444
+ ),
445
+ args=[call_node],
446
+ keywords=[],
447
+ )
448
+ )
354
449
  )
355
- new_list_tools_body.append(ast.Return(value=ast.Name(id='all_tools', ctx=ast.Load())))
450
+ new_list_tools_body.append(ast.Return(value=ast.Name(id="all_tools", ctx=ast.Load())))
356
451
 
357
452
  new_main_app_list_tools_def = ast.FunctionDef(
358
- name='list_tools',
359
- args=ast.arguments(posonlyargs=[], args=[ast.arg(arg='self')], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]),
360
- body=new_list_tools_body, decorator_list=[], returns=None
453
+ name="list_tools",
454
+ args=ast.arguments(
455
+ posonlyargs=[],
456
+ args=[ast.arg(arg="self")],
457
+ vararg=None,
458
+ kwonlyargs=[],
459
+ kw_defaults=[],
460
+ kwarg=None,
461
+ defaults=[],
462
+ ),
463
+ body=new_list_tools_body,
464
+ decorator_list=[],
465
+ returns=None,
361
466
  )
362
467
  new_main_app_body.append(new_main_app_list_tools_def)
363
468
 
@@ -373,28 +478,41 @@ def split_generated_app_file(input_app_file: Path, output_dir: Path):
373
478
  if isinstance(top_node, ast.Import | ast.ImportFrom):
374
479
  final_main_module_imports.append(top_node)
375
480
  if isinstance(top_node, ast.ImportFrom):
376
- if top_node.module: # module can be None for from . import x
377
- original_imports_from_main_file.add(top_node.module)
481
+ if top_node.module: # module can be None for from . import x
482
+ original_imports_from_main_file.add(top_node.module)
378
483
  elif isinstance(top_node, ast.Import):
379
- for alias in top_node.names:
380
- original_imports_from_main_file.add(alias.name)
484
+ for alias in top_node.names:
485
+ original_imports_from_main_file.add(alias.name)
381
486
  else:
382
487
  other_top_level_nodes_for_main.append(top_node)
383
-
488
+
384
489
  if "typing" not in original_imports_from_main_file:
385
- final_main_module_imports.insert(0, ast.ImportFrom(module='typing', names=[ast.alias(name='Any'), ast.alias(name='Dict'), ast.alias(name='Optional')], level=0))
490
+ final_main_module_imports.insert(
491
+ 0,
492
+ ast.ImportFrom(
493
+ module="typing",
494
+ names=[ast.alias(name="Any"), ast.alias(name="Dict"), ast.alias(name="Optional")],
495
+ level=0,
496
+ ),
497
+ )
386
498
 
387
499
  for seg_detail in segment_class_details_for_main_app:
388
500
  # Adjust import path for segments subfolder
389
501
  final_main_module_imports.append(
390
- ast.ImportFrom(module=f'.{segments_foldername}.{seg_detail["module_name"]}', names=[ast.alias(name=seg_detail["class_name"])], level=0)
502
+ ast.ImportFrom(
503
+ module=f".{segments_foldername}.{seg_detail['module_name']}",
504
+ names=[ast.alias(name=seg_detail["class_name"])],
505
+ level=0,
506
+ )
391
507
  )
392
-
393
- final_main_app_module_ast = ast.Module(body=final_main_module_imports + other_top_level_nodes_for_main + [main_app_class_node], type_ignores=[])
394
- if hasattr(ast, 'fix_missing_locations'):
508
+
509
+ final_main_app_module_ast = ast.Module(
510
+ body=final_main_module_imports + other_top_level_nodes_for_main + [main_app_class_node], type_ignores=[]
511
+ )
512
+ if hasattr(ast, "fix_missing_locations"):
395
513
  final_main_app_module_ast = ast.fix_missing_locations(final_main_app_module_ast)
396
514
 
397
515
  (output_dir / "app.py").write_text(ast.unparse(final_main_app_module_ast))
398
516
 
399
517
  (output_dir / "__init__.py").touch(exist_ok=True)
400
- (segments_dir / "__init__.py").touch(exist_ok=True)
518
+ (segments_dir / "__init__.py").touch(exist_ok=True)