universal-mcp 0.1.15rc7__py3-none-any.whl → 0.1.16__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.
@@ -91,24 +91,24 @@ def _sanitize_identifier(name: str | None) -> str:
91
91
  """
92
92
  if name is None:
93
93
  return ""
94
-
94
+
95
95
  # Initial replacements for common non-alphanumeric characters
96
96
  sanitized = name.replace("-", "_").replace(".", "_").replace("[", "_").replace("]", "").replace("$", "_")
97
97
 
98
98
  # Remove leading underscores, but preserve a single underscore if the name (after initial replace)
99
99
  # consisted only of underscores.
100
- if sanitized.startswith('_'):
101
- stripped_name = sanitized.lstrip('_')
100
+ if sanitized.startswith("_"):
101
+ stripped_name = sanitized.lstrip("_")
102
102
  sanitized = stripped_name if stripped_name else "_"
103
-
103
+
104
104
  # Append underscore if the sanitized name is a Python keyword
105
105
  if iskeyword(sanitized):
106
106
  sanitized += "_"
107
-
107
+
108
108
  # Special handling for "self" to avoid conflict with instance method's self argument
109
109
  if sanitized == "self":
110
110
  sanitized = "self_arg"
111
-
111
+
112
112
  return sanitized
113
113
 
114
114
 
@@ -202,18 +202,10 @@ def _determine_function_name(operation: dict[str, Any], path: str, method: str)
202
202
  func_name = "_".join(name_parts).replace("-", "_").lower()
203
203
 
204
204
  # Only fix isolated 'a' and 'an' as articles, not when they're part of words
205
- func_name = re.sub(
206
- r"_a([^_a-z])", r"_a_\1", func_name
207
- ) # Fix for patterns like retrieve_ablock -> retrieve_a_block
208
- func_name = re.sub(
209
- r"_a$", r"_a", func_name
210
- ) # Don't change if 'a' is at the end of the name
211
- func_name = re.sub(
212
- r"_an([^_a-z])", r"_an_\1", func_name
213
- ) # Fix for patterns like create_anitem -> create_an_item
214
- func_name = re.sub(
215
- r"_an$", r"_an", func_name
216
- ) # Don't change if 'an' is at the end of the name
205
+ func_name = re.sub(r"_a([^_a-z])", r"_a_\1", func_name) # Fix for patterns like retrieve_ablock -> retrieve_a_block
206
+ func_name = re.sub(r"_a$", r"_a", func_name) # Don't change if 'a' is at the end of the name
207
+ func_name = re.sub(r"_an([^_a-z])", r"_an_\1", func_name) # Fix for patterns like create_anitem -> create_an_item
208
+ func_name = re.sub(r"_an$", r"_an", func_name) # Don't change if 'an' is at the end of the name
217
209
  return func_name
218
210
 
219
211
 
@@ -242,9 +234,7 @@ def _generate_path_params(path: str) -> list[Parameters]:
242
234
  def _generate_url(path: str, path_params: list[Parameters]):
243
235
  formatted_path = path
244
236
  for param in path_params:
245
- formatted_path = formatted_path.replace(
246
- f"{{{param.identifier}}}", f"{{{param.name}}}"
247
- )
237
+ formatted_path = formatted_path.replace(f"{{{param.identifier}}}", f"{{{param.name}}}")
248
238
  return formatted_path
249
239
 
250
240
 
@@ -338,7 +328,7 @@ def _generate_method_code(path, method, operation):
338
328
  Returns:
339
329
  tuple: (method_code, func_name) - The Python code for the method and its name.
340
330
  """
341
- print(f"--- Generating code for: {method.upper()} {path} ---") # Log endpoint being processed
331
+ print(f"--- Generating code for: {method.upper()} {path} ---") # Log endpoint being processed
342
332
 
343
333
  func_name = _determine_function_name(operation, path, method)
344
334
  operation.get("summary", "")
@@ -354,13 +344,12 @@ def _generate_method_code(path, method, operation):
354
344
 
355
345
  # Define the string that "self" sanitizes to. This name will be treated as reserved
356
346
  # for query/body params to force suffixing.
357
- self_sanitized_marker = _sanitize_identifier("self") # This will be "self_arg"
347
+ self_sanitized_marker = _sanitize_identifier("self") # This will be "self_arg"
358
348
 
359
349
  # Base names that will force query/body parameters to be suffixed.
360
350
  # This includes actual path parameter names and the sanitized form of "self".
361
351
  path_param_base_conflict_names = path_param_names | {self_sanitized_marker}
362
352
 
363
-
364
353
  # Alias query parameters
365
354
  current_query_param_names = set()
366
355
  for q_param in query_params:
@@ -374,18 +363,19 @@ def _generate_method_code(path, method, operation):
374
363
  # This step is more about ensuring the final suffixed name is unique if multiple query params mapped to same path param name
375
364
  counter = 1
376
365
  final_q_name = temp_q_name
377
- while final_q_name in path_param_base_conflict_names or final_q_name in current_query_param_names : # Check against path/\"self_arg\" and already processed query params
378
- if temp_q_name == original_q_name : # first conflict was with path_param_base_conflict_names
379
- final_q_name = f"{original_q_name}_query" # try simple suffix first
380
- if final_q_name in path_param_base_conflict_names or final_q_name in current_query_param_names:
381
- final_q_name = f"{original_q_name}_query_{counter}" # then add counter
382
- else: # conflict was with another query param after initial suffixing
383
- final_q_name = f"{temp_q_name}_{counter}"
366
+ while (
367
+ final_q_name in path_param_base_conflict_names or final_q_name in current_query_param_names
368
+ ): # Check against path/\"self_arg\" and already processed query params
369
+ if temp_q_name == original_q_name: # first conflict was with path_param_base_conflict_names
370
+ final_q_name = f"{original_q_name}_query" # try simple suffix first
371
+ if final_q_name in path_param_base_conflict_names or final_q_name in current_query_param_names:
372
+ final_q_name = f"{original_q_name}_query_{counter}" # then add counter
373
+ else: # conflict was with another query param after initial suffixing
374
+ final_q_name = f"{temp_q_name}_{counter}"
384
375
  counter += 1
385
376
  q_param.name = final_q_name
386
377
  current_query_param_names.add(q_param.name)
387
378
 
388
-
389
379
  # Alias body parameters
390
380
  # Names to check against: path param names (including "self_arg" marker) and (now aliased) query param names
391
381
  existing_param_names_for_body = path_param_base_conflict_names.union(current_query_param_names)
@@ -402,11 +392,11 @@ def _generate_method_code(path, method, operation):
402
392
  counter = 1
403
393
  final_b_name = temp_b_name
404
394
  while final_b_name in existing_param_names_for_body or final_b_name in current_body_param_names:
405
- if temp_b_name == original_b_name: # first conflict was with existing_param_names_for_body
395
+ if temp_b_name == original_b_name: # first conflict was with existing_param_names_for_body
406
396
  final_b_name = f"{original_b_name}_body"
407
397
  if final_b_name in existing_param_names_for_body or final_b_name in current_body_param_names:
408
398
  final_b_name = f"{original_b_name}_body_{counter}"
409
- else: # conflict was with another body param after initial suffixing
399
+ else: # conflict was with another body param after initial suffixing
410
400
  final_b_name = f"{temp_b_name}_{counter}"
411
401
 
412
402
  counter += 1
@@ -414,7 +404,6 @@ def _generate_method_code(path, method, operation):
414
404
  current_body_param_names.add(b_param.name)
415
405
  # --- End Alias duplicate parameter names ---
416
406
 
417
-
418
407
  return_type = _determine_return_type(operation)
419
408
 
420
409
  has_body = "requestBody" in operation
@@ -432,12 +421,10 @@ def _generate_method_code(path, method, operation):
432
421
  if schema.get("type") == "array":
433
422
  is_array_body = True
434
423
  else:
435
- request_body_properties, required_fields = (
436
- _extract_properties_from_schema(schema)
437
- )
438
- if (
439
- not request_body_properties or len(request_body_properties) == 0
440
- ) and schema.get("additionalProperties") is True:
424
+ request_body_properties, required_fields = _extract_properties_from_schema(schema)
425
+ if (not request_body_properties or len(request_body_properties) == 0) and schema.get(
426
+ "additionalProperties"
427
+ ) is True:
441
428
  has_empty_body = True
442
429
  elif not request_body_content or all(
443
430
  not c for _, c in request_body_content.items()
@@ -453,13 +440,13 @@ def _generate_method_code(path, method, operation):
453
440
  for param in path_params:
454
441
  # Path param names are sanitized but not suffixed by aliasing.
455
442
  # They are the baseline.
456
- if param.name not in required_args: # param.name is the sanitized name
443
+ if param.name not in required_args: # param.name is the sanitized name
457
444
  required_args.append(param.name)
458
445
 
459
446
  # 2. Process Query Parameters
460
- for param in query_params: # param.name is the potentially aliased name (e.g., id_query)
447
+ for param in query_params: # param.name is the potentially aliased name (e.g., id_query)
461
448
  arg_name_for_sig = param.name
462
- current_arg_names_set = set(required_args) | {arg.split('=')[0] for arg in optional_args}
449
+ current_arg_names_set = set(required_args) | {arg.split("=")[0] for arg in optional_args}
463
450
  if arg_name_for_sig not in current_arg_names_set:
464
451
  if param.required:
465
452
  required_args.append(arg_name_for_sig)
@@ -470,10 +457,10 @@ def _generate_method_code(path, method, operation):
470
457
  # This list tracks the *final* names of parameters in the signature that come from the request body,
471
458
  # used later for docstring example placement.
472
459
  final_request_body_arg_names_for_signature = []
473
- final_empty_body_param_name = None # For the specific case of has_empty_body
460
+ final_empty_body_param_name = None # For the specific case of has_empty_body
474
461
 
475
462
  if has_body:
476
- current_arg_names_set = set(required_args) | {arg.split('=')[0] for arg in optional_args}
463
+ current_arg_names_set = set(required_args) | {arg.split("=")[0] for arg in optional_args}
477
464
  if is_array_body:
478
465
  array_param_name_base = "items" # Default base name
479
466
  if func_name.endswith("_list_input"):
@@ -491,7 +478,7 @@ def _generate_method_code(path, method, operation):
491
478
  else:
492
479
  final_array_param_name = f"{array_param_name_base}_body_{counter}"
493
480
  counter += 1
494
-
481
+
495
482
  if body_required:
496
483
  required_args.append(final_array_param_name)
497
484
  else:
@@ -500,10 +487,10 @@ def _generate_method_code(path, method, operation):
500
487
 
501
488
  elif request_body_properties: # Object body
502
489
  for param in body_params: # Iterate ALIASED body_params
503
- arg_name_for_sig = param.name # This is the final, aliased name (e.g., "id_body")
504
-
490
+ arg_name_for_sig = param.name # This is the final, aliased name (e.g., "id_body")
491
+
505
492
  # Defensive check against already added args (should be covered by aliasing logic)
506
- current_arg_names_set_loop = set(required_args) | {arg.split('=')[0] for arg in optional_args}
493
+ current_arg_names_set_loop = set(required_args) | {arg.split("=")[0] for arg in optional_args}
507
494
  if arg_name_for_sig not in current_arg_names_set_loop:
508
495
  if param.required:
509
496
  required_args.append(arg_name_for_sig)
@@ -516,8 +503,8 @@ def _generate_method_code(path, method, operation):
516
503
  # This is handled *after* specific body params, as it's a fallback.
517
504
  if has_empty_body:
518
505
  empty_body_param_name_base = "request_body"
519
- current_arg_names_set = set(required_args) | {arg.split('=')[0] for arg in optional_args}
520
-
506
+ current_arg_names_set = set(required_args) | {arg.split("=")[0] for arg in optional_args}
507
+
521
508
  final_empty_body_param_name = empty_body_param_name_base
522
509
  counter = 1
523
510
  is_first_suffix_attempt = True
@@ -531,11 +518,11 @@ def _generate_method_code(path, method, operation):
531
518
 
532
519
  # Check if it was somehow added by other logic (e.g. if 'request_body' was an explicit param name)
533
520
  # This check is mostly defensive.
534
- if final_empty_body_param_name not in (set(required_args) | {arg.split('=')[0] for arg in optional_args}):
521
+ if final_empty_body_param_name not in (set(required_args) | {arg.split("=")[0] for arg in optional_args}):
535
522
  optional_args.append(f"{final_empty_body_param_name}=None")
536
523
  # Track for docstring, even if it's just 'request_body' or 'request_body_body'
537
524
  if final_empty_body_param_name not in final_request_body_arg_names_for_signature:
538
- final_request_body_arg_names_for_signature.append(final_empty_body_param_name)
525
+ final_request_body_arg_names_for_signature.append(final_empty_body_param_name)
539
526
 
540
527
  # Combine required and optional arguments
541
528
  args = required_args + optional_args
@@ -547,9 +534,7 @@ def _generate_method_code(path, method, operation):
547
534
  # Summary
548
535
  summary = operation.get("summary", "").strip()
549
536
  if not summary:
550
- summary = operation.get(
551
- "description", f"Execute {method.upper()} {path}"
552
- ).strip()
537
+ summary = operation.get("description", f"Execute {method.upper()} {path}").strip()
553
538
  summary = summary.split("\n")[0]
554
539
  if summary:
555
540
  docstring_parts.append(summary)
@@ -579,9 +564,7 @@ def _generate_method_code(path, method, operation):
579
564
  if example_data is not None:
580
565
  try:
581
566
  example_json = json.dumps(example_data, indent=2)
582
- indented_example = textwrap.indent(
583
- example_json, " " * 8
584
- ) # 8 spaces
567
+ indented_example = textwrap.indent(example_json, " " * 8) # 8 spaces
585
568
  request_body_example_str = f"\n Example:\n ```json\n{indented_example}\n ```"
586
569
  except TypeError:
587
570
  request_body_example_str = f"\n Example: {example_data}"
@@ -591,7 +574,7 @@ def _generate_method_code(path, method, operation):
591
574
  # Identify the last argument related to the request body
592
575
  last_body_arg_name = None
593
576
  # request_body_params contains the names as they appear in the signature
594
- if final_request_body_arg_names_for_signature: # Use the new list with final aliased names
577
+ if final_request_body_arg_names_for_signature: # Use the new list with final aliased names
595
578
  # Find which of these appears last in the combined args list
596
579
  body_args_in_signature = [
597
580
  a.split("=")[0] for a in args if a.split("=")[0] in final_request_body_arg_names_for_signature
@@ -617,28 +600,21 @@ def _generate_method_code(path, method, operation):
617
600
  if arg_name == last_body_arg_name and request_body_example_str:
618
601
  # Remove the simple Example: if it exists before adding the detailed one
619
602
  if example_str and (
620
- f" Example: {example_str}." in arg_line
621
- or f" Example: {example_str} ." in arg_line
603
+ f" Example: {example_str}." in arg_line or f" Example: {example_str} ." in arg_line
622
604
  ):
623
605
  arg_line = arg_line.replace(
624
606
  f" Example: {example_str}.", ""
625
607
  ) # Remove with or without trailing period
626
- arg_line += (
627
- request_body_example_str # Append the formatted JSON example
628
- )
608
+ arg_line += request_body_example_str # Append the formatted JSON example
629
609
 
630
610
  args_doc_lines.append(arg_line)
631
- elif arg_name == final_empty_body_param_name and has_empty_body: # Use potentially suffixed name
611
+ elif arg_name == final_empty_body_param_name and has_empty_body: # Use potentially suffixed name
632
612
  args_doc_lines.append(
633
613
  f" {arg_name} (dict | None): Optional dictionary for arbitrary request body data."
634
614
  )
635
615
  # Also append example here if this is the designated body arg
636
- if (
637
- arg_name == last_body_arg_name and request_body_example_str
638
- ):
639
- args_doc_lines[-1] += (
640
- request_body_example_str
641
- )
616
+ if arg_name == last_body_arg_name and request_body_example_str:
617
+ args_doc_lines[-1] += request_body_example_str
642
618
 
643
619
  if args_doc_lines:
644
620
  docstring_parts.append("\n".join(args_doc_lines))
@@ -650,9 +626,7 @@ def _generate_method_code(path, method, operation):
650
626
  if code.startswith("2"):
651
627
  success_desc = resp_info.get("description", "").strip()
652
628
  break
653
- docstring_parts.append(
654
- f"Returns:\n {return_type}: {success_desc or 'API response data.'}"
655
- ) # Use return_type
629
+ docstring_parts.append(f"Returns:\n {return_type}: {success_desc or 'API response data.'}") # Use return_type
656
630
 
657
631
  # Tags Section
658
632
  operation_tags = operation.get("tags", [])
@@ -668,9 +642,7 @@ def _generate_method_code(path, method, operation):
668
642
  indented_docstring_content = textwrap.indent(docstring_content, doc_indent)
669
643
 
670
644
  # Wrap in triple quotes
671
- formatted_docstring = (
672
- f'\n{doc_indent}"""\n{indented_docstring_content}\n{doc_indent}"""'
673
- )
645
+ formatted_docstring = f'\n{doc_indent}"""\n{indented_docstring_content}\n{doc_indent}"""'
674
646
  # ----- End Build Docstring -----
675
647
 
676
648
  if args:
@@ -685,7 +657,7 @@ def _generate_method_code(path, method, operation):
685
657
  for param in path_params:
686
658
  body_lines.append(f" if {param.name} is None:")
687
659
  body_lines.append(
688
- f' raise ValueError("Missing required parameter \'{param.identifier}\'")' # Use original name in error
660
+ f" raise ValueError(\"Missing required parameter '{param.identifier}'\")" # Use original name in error
689
661
  )
690
662
 
691
663
  # Build request body (handle array and object types differently)
@@ -697,14 +669,12 @@ def _generate_method_code(path, method, operation):
697
669
  elif request_body_properties:
698
670
  # For object request bodies, build the request body from individual parameters
699
671
  body_lines.append(" request_body = {")
700
- for b_param in body_params: # Iterate through original body_params list
672
+ for b_param in body_params: # Iterate through original body_params list
701
673
  # Use b_param.identifier for the key in the request_body dictionary
702
674
  # and b_param.name for the variable name from the function signature
703
675
  body_lines.append(f" '{b_param.identifier}': {b_param.name},")
704
676
  body_lines.append(" }")
705
- body_lines.append(
706
- " request_body = {k: v for k, v in request_body.items() if v is not None}"
707
- )
677
+ body_lines.append(" request_body = {k: v for k, v in request_body.items() if v is not None}")
708
678
 
709
679
  # Format URL directly with path parameters
710
680
  url = _generate_url(path, path_params)
@@ -714,7 +684,7 @@ def _generate_method_code(path, method, operation):
714
684
  # Build query parameters dictionary for the request
715
685
  if query_params:
716
686
  query_params_items = []
717
- for param in query_params: # Iterate through original query_params list
687
+ for param in query_params: # Iterate through original query_params list
718
688
  # Use the original param.identifier for the key, and the (potentially aliased) param.name for the value variable
719
689
  query_params_items.append(f"('{param.identifier}', {param.name})")
720
690
  body_lines.append(
@@ -737,23 +707,15 @@ def _generate_method_code(path, method, operation):
737
707
  if method_lower == "get":
738
708
  body_lines.append(" response = self._get(url, params=query_params)")
739
709
  elif method_lower == "post":
740
- body_lines.append(
741
- f" response = self._post(url, data={request_body_arg}, params=query_params)"
742
- )
710
+ body_lines.append(f" response = self._post(url, data={request_body_arg}, params=query_params)")
743
711
  elif method_lower == "put":
744
- body_lines.append(
745
- f" response = self._put(url, data={request_body_arg}, params=query_params)"
746
- )
712
+ body_lines.append(f" response = self._put(url, data={request_body_arg}, params=query_params)")
747
713
  elif method_lower == "patch":
748
- body_lines.append(
749
- f" response = self._patch(url, data={request_body_arg}, params=query_params)"
750
- )
714
+ body_lines.append(f" response = self._patch(url, data={request_body_arg}, params=query_params)")
751
715
  elif method_lower == "delete":
752
716
  body_lines.append(" response = self._delete(url, params=query_params)")
753
717
  else:
754
- body_lines.append(
755
- f" response = self._{method_lower}(url, data={request_body_arg}, params=query_params)"
756
- )
718
+ body_lines.append(f" response = self._{method_lower}(url, data={request_body_arg}, params=query_params)")
757
719
 
758
720
  # Handle response
759
721
  body_lines.append(" response.raise_for_status()")
@@ -795,11 +757,7 @@ def generate_api_client(schema, class_name: str | None = None):
795
757
  if api_title:
796
758
  # Convert API title to a clean class name
797
759
  if class_name:
798
- clean_name = (
799
- class_name.capitalize()[:-3]
800
- if class_name.endswith("App")
801
- else class_name.capitalize()
802
- )
760
+ clean_name = class_name.capitalize()[:-3] if class_name.endswith("App") else class_name.capitalize()
803
761
  else:
804
762
  base_name = "".join(word.capitalize() for word in api_title.split())
805
763
  clean_name = "".join(c for c in base_name if c.isalnum())
@@ -858,11 +816,7 @@ def generate_api_client(schema, class_name: str | None = None):
858
816
  f"class {class_name}(APIApplication):\n"
859
817
  f" def __init__(self, integration: Integration = None, **kwargs) -> None:\n"
860
818
  f" super().__init__(name='{class_name.lower()}', integration=integration, **kwargs)\n"
861
- f' self.base_url = "{base_url}"\n\n'
862
- + "\n\n".join(methods)
863
- + "\n\n"
864
- + list_tools_method
865
- + "\n"
819
+ f' self.base_url = "{base_url}"\n\n' + "\n\n".join(methods) + "\n\n" + list_tools_method + "\n"
866
820
  )
867
821
  return class_code
868
822
 
@@ -886,9 +840,7 @@ if __name__ == "__main__":
886
840
  "responses": {
887
841
  "200": {
888
842
  "description": "A list of users",
889
- "content": {
890
- "application/json": {"schema": {"type": "array"}}
891
- },
843
+ "content": {"application/json": {"schema": {"type": "array"}}},
892
844
  }
893
845
  },
894
846
  },