universal-mcp 0.1.20rc2__py3-none-any.whl → 0.1.22rc1__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.
@@ -19,6 +19,7 @@ class Parameters(BaseModel):
19
19
  required: bool
20
20
  example: str | None = None
21
21
  is_file: bool = False
22
+ schema: dict = {}
22
23
 
23
24
  def __str__(self):
24
25
  return f"{self.name}: ({self.type})"
@@ -79,6 +80,60 @@ def convert_to_snake_case(identifier: str) -> str:
79
80
  return result.strip("_").lower()
80
81
 
81
82
 
83
+ # Added new recursive type mapper
84
+ def _openapi_type_to_python_type(schema: dict, required: bool = True) -> str:
85
+ """
86
+ Recursively map OpenAPI schema to Python type hints.
87
+ """
88
+ openapi_type = schema.get("type")
89
+
90
+ if "$ref" in schema and not openapi_type:
91
+ py_type = "dict[str, Any]"
92
+ elif openapi_type == "array":
93
+ items_schema = schema.get("items", {})
94
+ item_type = _openapi_type_to_python_type(items_schema, required=True)
95
+ py_type = f"List[{item_type}]"
96
+ elif openapi_type == "object":
97
+ if schema.get("format") in ["binary", "byte"]:
98
+ py_type = "bytes"
99
+ else:
100
+ if "additionalProperties" in schema and isinstance(schema["additionalProperties"], dict):
101
+ additional_props_schema = schema["additionalProperties"]
102
+
103
+ value_type = _openapi_type_to_python_type(additional_props_schema, required=True)
104
+ py_type = f"dict[str, {value_type}]"
105
+ elif (
106
+ not schema.get("properties")
107
+ and not schema.get("allOf")
108
+ and not schema.get("oneOf")
109
+ and not schema.get("anyOf")
110
+ ):
111
+ py_type = "dict[str, Any]"
112
+ else:
113
+ py_type = "dict[str, Any]"
114
+ elif openapi_type == "integer":
115
+ py_type = "int"
116
+ elif openapi_type == "number":
117
+ py_type = "float"
118
+ elif openapi_type == "boolean":
119
+ py_type = "bool"
120
+ elif openapi_type == "string":
121
+ if schema.get("format") in ["binary", "byte"]:
122
+ py_type = "bytes"
123
+ elif schema.get("format") == "date" or schema.get("format") == "date-time":
124
+ py_type = "str"
125
+ else:
126
+ py_type = "str"
127
+ else:
128
+ py_type = "Any"
129
+
130
+ if not required:
131
+ if py_type.startswith("Optional[") and py_type.endswith("]"):
132
+ return py_type
133
+ return f"Optional[{py_type}]"
134
+ return py_type
135
+
136
+
82
137
  def _sanitize_identifier(name: str | None) -> str:
83
138
  """Cleans a string to be a valid Python identifier.
84
139
 
@@ -200,7 +255,7 @@ def _determine_function_name(operation: dict[str, Any], path: str, method: str)
200
255
  if "operationId" in operation:
201
256
  raw_name = operation["operationId"]
202
257
  cleaned_name = raw_name.replace(".", "_").replace("-", "_")
203
- cleaned_name_no_numbers = re.sub(r'\d+', '', cleaned_name)
258
+ cleaned_name_no_numbers = re.sub(r"\d+", "", cleaned_name)
204
259
  func_name = convert_to_snake_case(cleaned_name_no_numbers)
205
260
  else:
206
261
  # Generate name from path and method
@@ -235,6 +290,7 @@ def _generate_path_params(path: str) -> list[Parameters]:
235
290
  where="path",
236
291
  required=True,
237
292
  example=None,
293
+ schema={"type": "string"},
238
294
  )
239
295
  )
240
296
  except Exception as e:
@@ -283,6 +339,7 @@ def _generate_query_params(operation: dict[str, Any]) -> list[Parameters]:
283
339
  where=where,
284
340
  required=required,
285
341
  example=str(example_value) if example_value is not None else None,
342
+ schema=param_schema if param_schema else {"type": type_value},
286
343
  )
287
344
  query_params.append(parameter)
288
345
  return query_params
@@ -314,11 +371,12 @@ def _generate_body_params(schema_to_process: dict[str, Any] | None, overall_body
314
371
  name=_sanitize_identifier(param_name),
315
372
  identifier=param_name,
316
373
  description=param_description,
317
- type=effective_param_type,
374
+ type=effective_param_type,
318
375
  where="body",
319
376
  required=param_required,
320
377
  example=str(param_example) if param_example is not None else None,
321
- is_file=current_is_file
378
+ is_file=current_is_file,
379
+ schema=param_schema_details,
322
380
  )
323
381
  )
324
382
  # print(f"[DEBUG] Final body_params list generated: {body_params}") # DEBUG
@@ -344,10 +402,10 @@ def _generate_method_code(path, method, operation):
344
402
 
345
403
  # --- Determine Function Name and Basic Operation Details ---
346
404
  func_name = _determine_function_name(operation, path, method)
347
- method_lower = method.lower() # Define method_lower earlier
348
- operation.get("summary", "") # Ensure summary is accessed if needed elsewhere
349
- operation.get("tags", []) # Ensure tags are accessed if needed elsewhere
350
-
405
+ method_lower = method.lower() # Define method_lower earlier
406
+ operation.get("summary", "") # Ensure summary is accessed if needed elsewhere
407
+ operation.get("tags", []) # Ensure tags are accessed if needed elsewhere
408
+
351
409
  # --- Generate Path and Query Parameters (pre-aliasing) ---
352
410
  path_params = _generate_path_params(path)
353
411
  query_params = _generate_query_params(operation)
@@ -356,7 +414,7 @@ def _generate_method_code(path, method, operation):
356
414
  # This section selects the primary content type and its schema to be used for the request body.
357
415
  has_body = "requestBody" in operation
358
416
  body_schema_to_use = None
359
- selected_content_type = None # This will hold the chosen content type string
417
+ selected_content_type = None # This will hold the chosen content type string
360
418
 
361
419
  if has_body:
362
420
  request_body_spec = operation["requestBody"]
@@ -369,7 +427,7 @@ def _generate_method_code(path, method, operation):
369
427
  "application/octet-stream",
370
428
  "text/plain",
371
429
  ]
372
-
430
+
373
431
  found_preferred = False
374
432
  for ct in preferred_content_types:
375
433
  if ct in request_body_content_map:
@@ -377,25 +435,25 @@ def _generate_method_code(path, method, operation):
377
435
  body_schema_to_use = request_body_content_map[ct].get("schema")
378
436
  found_preferred = True
379
437
  break
380
-
381
- if not found_preferred: # Check for image/* if no direct match yet
438
+
439
+ if not found_preferred: # Check for image/* if no direct match yet
382
440
  for ct_key in request_body_content_map:
383
441
  if ct_key.startswith("image/"):
384
442
  selected_content_type = ct_key
385
443
  body_schema_to_use = request_body_content_map[ct_key].get("schema")
386
444
  found_preferred = True
387
445
  break
388
-
389
- if not found_preferred and request_body_content_map: # Fallback to first listed
446
+
447
+ if not found_preferred and request_body_content_map: # Fallback to first listed
390
448
  first_ct_key = next(iter(request_body_content_map))
391
449
  selected_content_type = first_ct_key
392
450
  body_schema_to_use = request_body_content_map[first_ct_key].get("schema")
393
451
 
394
452
  # --- Generate Body Parameters (based on selected schema, pre-aliasing) ---
395
- if body_schema_to_use: # If a schema was actually found for the selected content type
453
+ if body_schema_to_use: # If a schema was actually found for the selected content type
396
454
  body_params = _generate_body_params(
397
- body_schema_to_use, # Pass the specific schema
398
- operation.get("requestBody", {}).get("required", False) # Pass the overall body requirement
455
+ body_schema_to_use, # Pass the specific schema
456
+ operation.get("requestBody", {}).get("required", False), # Pass the overall body requirement
399
457
  )
400
458
  else:
401
459
  body_params = []
@@ -467,29 +525,36 @@ def _generate_method_code(path, method, operation):
467
525
  current_body_param_names.add(b_param.name)
468
526
  # --- End Alias duplicate parameter names ---
469
527
 
470
-
471
528
  # --- Determine Return Type and Body Characteristics ---
472
529
  return_type = _determine_return_type(operation)
473
530
 
474
- body_required = has_body and operation["requestBody"].get("required", False) # Remains useful
475
-
531
+ body_required = has_body and operation["requestBody"].get("required", False) # Remains useful
532
+
476
533
  is_array_body = False
477
- has_empty_body = False
534
+ has_empty_body = False
478
535
 
479
- if has_body and body_schema_to_use: # Use the determined body_schema_to_use
536
+ if has_body and body_schema_to_use: # Use the determined body_schema_to_use
480
537
  if body_schema_to_use.get("type") == "array":
481
538
  is_array_body = True
482
-
539
+
483
540
  # Check for cases that might lead to an "empty" body parameter (for JSON) in the signature,
484
541
  # or indicate a raw body type where _generate_body_params wouldn't create named params.
485
- if not body_params and not is_array_body and selected_content_type == "application/json" and \
486
- (body_schema_to_use == {} or \
487
- (body_schema_to_use.get("type") == "object" and \
488
- not body_schema_to_use.get("properties") and \
489
- not body_schema_to_use.get("allOf") and \
490
- not body_schema_to_use.get("oneOf") and \
491
- not body_schema_to_use.get("anyOf"))):
492
- has_empty_body = True # Indicates a generic 'request_body: dict = None' might be needed for empty JSON
542
+ if (
543
+ not body_params
544
+ and not is_array_body
545
+ and selected_content_type == "application/json"
546
+ and (
547
+ body_schema_to_use == {}
548
+ or (
549
+ body_schema_to_use.get("type") == "object"
550
+ and not body_schema_to_use.get("properties")
551
+ and not body_schema_to_use.get("allOf")
552
+ and not body_schema_to_use.get("oneOf")
553
+ and not body_schema_to_use.get("anyOf")
554
+ )
555
+ )
556
+ ):
557
+ has_empty_body = True # Indicates a generic 'request_body: dict = None' might be needed for empty JSON
493
558
 
494
559
  # --- Build Function Arguments for Signature ---
495
560
  # This section constructs the list of arguments (required and optional)
@@ -497,27 +562,40 @@ def _generate_method_code(path, method, operation):
497
562
  required_args = []
498
563
  optional_args = []
499
564
 
565
+ # Arguments for the function signature with type hints
566
+ signature_required_args_typed = []
567
+ signature_optional_args_typed = []
568
+
500
569
  # Process Path Parameters (Highest Priority)
501
570
  for param in path_params:
502
571
  # Path param names are sanitized but not suffixed by aliasing.
503
572
  if param.name not in required_args: # param.name is the sanitized name
504
573
  required_args.append(param.name)
574
+ # For signature with types
575
+ param_py_type = _openapi_type_to_python_type(param.schema, required=True)
576
+ signature_required_args_typed.append(f"{param.name}: {param_py_type}")
505
577
 
506
578
  # Process Query Parameters
507
579
  for param in query_params: # param.name is the potentially aliased name (e.g., id_query)
508
580
  arg_name_for_sig = param.name
509
581
  current_arg_names_set = set(required_args) | {arg.split("=")[0] for arg in optional_args}
582
+
583
+ # For signature with types
584
+ param_py_type = _openapi_type_to_python_type(param.schema, required=param.required)
585
+
510
586
  if arg_name_for_sig not in current_arg_names_set:
511
587
  if param.required:
512
588
  required_args.append(arg_name_for_sig)
589
+ signature_required_args_typed.append(f"{arg_name_for_sig}: {param_py_type}")
513
590
  else:
514
591
  optional_args.append(f"{arg_name_for_sig}=None")
592
+ signature_optional_args_typed.append(f"{arg_name_for_sig}: {param_py_type} = None")
515
593
 
516
594
  # Process Body Parameters / Request Body
517
595
  # This list tracks the *final* names of parameters in the signature that come from the request body,
518
596
  final_request_body_arg_names_for_signature = []
519
- final_empty_body_param_name = None # For the specific case of has_empty_body (empty JSON object)
520
- raw_body_param_name = None # For raw content like octet-stream, text/plain, image/*
597
+ final_empty_body_param_name = None # For the specific case of has_empty_body (empty JSON object)
598
+ raw_body_param_name = None # For raw content like octet-stream, text/plain, image/*
521
599
 
522
600
  if has_body:
523
601
  current_arg_names_set = set(required_args) | {arg.split("=")[0] for arg in optional_args}
@@ -539,18 +617,30 @@ def _generate_method_code(path, method, operation):
539
617
  final_array_param_name = f"{array_param_name_base}_body_{counter}"
540
618
  counter += 1
541
619
 
620
+ # For signature with types
621
+ # The schema for an array body is body_schema_to_use itself
622
+ array_body_py_type = _openapi_type_to_python_type(body_schema_to_use, required=body_required)
623
+
542
624
  if body_required:
543
625
  required_args.append(final_array_param_name)
626
+ signature_required_args_typed.append(f"{final_array_param_name}: {array_body_py_type}")
544
627
  else:
545
628
  optional_args.append(f"{final_array_param_name}=None")
629
+ signature_optional_args_typed.append(f"{final_array_param_name}: {array_body_py_type} = None")
546
630
  final_request_body_arg_names_for_signature.append(final_array_param_name)
547
631
 
548
632
  # New: Handle raw body parameter (if body_params is empty but body is expected and not array/empty JSON)
549
- elif not body_params and not is_array_body and selected_content_type and selected_content_type not in ["application/json", "application/x-www-form-urlencoded", "multipart/form-data"]:
633
+ elif (
634
+ not body_params
635
+ and not is_array_body
636
+ and selected_content_type
637
+ and selected_content_type
638
+ not in ["application/json", "application/x-www-form-urlencoded", "multipart/form-data"]
639
+ ):
550
640
  # This branch is for raw content types like application/octet-stream, text/plain, image/*
551
641
  # where _generate_body_params returned an empty list because the schema isn't an object with properties.
552
642
  raw_body_param_name_base = "body_content"
553
-
643
+
554
644
  temp_raw_body_name = raw_body_param_name_base
555
645
  counter = 1
556
646
  is_first_suffix_attempt = True
@@ -563,31 +653,54 @@ def _generate_method_code(path, method, operation):
563
653
  counter += 1
564
654
  raw_body_param_name = temp_raw_body_name
565
655
 
566
- if body_required: # If the raw body itself is required
656
+ # For signature with types
657
+ # Determine type based on selected_content_type for raw body
658
+ raw_body_schema_for_type = {"type": "string", "format": "binary"} # Default to bytes
659
+ if selected_content_type and "text" in selected_content_type:
660
+ raw_body_schema_for_type = {"type": "string"}
661
+ elif selected_content_type and selected_content_type.startswith("image/"):
662
+ raw_body_schema_for_type = {"type": "string", "format": "binary"} # image is bytes
663
+
664
+ raw_body_py_type = _openapi_type_to_python_type(raw_body_schema_for_type, required=body_required)
665
+
666
+ if body_required: # If the raw body itself is required
567
667
  required_args.append(raw_body_param_name)
668
+ signature_required_args_typed.append(f"{raw_body_param_name}: {raw_body_py_type}")
568
669
  else:
569
670
  optional_args.append(f"{raw_body_param_name}=None")
671
+ signature_optional_args_typed.append(f"{raw_body_param_name}: {raw_body_py_type} = None")
570
672
  final_request_body_arg_names_for_signature.append(raw_body_param_name)
571
673
 
572
- elif body_params: # Object body with discernible properties
674
+ elif body_params: # Object body with discernible properties
573
675
  for param in body_params: # Iterate ALIASED body_params
574
- arg_name_for_sig = param.name #final aliased name (e.g., "id_body")
676
+ arg_name_for_sig = param.name # final aliased name (e.g., "id_body")
575
677
 
576
- # Defensive check against already added args
678
+ # Defensive check against already added args
577
679
  current_arg_names_set_loop = set(required_args) | {arg.split("=")[0] for arg in optional_args}
680
+
681
+ # For signature with types
682
+ param_py_type = _openapi_type_to_python_type(param.schema, required=param.required)
683
+
578
684
  if arg_name_for_sig not in current_arg_names_set_loop:
579
685
  if param.required:
580
686
  required_args.append(arg_name_for_sig)
687
+ signature_required_args_typed.append(f"{arg_name_for_sig}: {param_py_type}")
581
688
  else:
582
689
  # Parameters model does not store schema 'default'. Optional params default to None.
583
690
  optional_args.append(f"{arg_name_for_sig}=None")
691
+ signature_optional_args_typed.append(f"{arg_name_for_sig}: {param_py_type} = None")
584
692
  final_request_body_arg_names_for_signature.append(arg_name_for_sig)
585
693
 
694
+ if (
695
+ has_empty_body
696
+ and selected_content_type == "application/json"
697
+ and not body_params
698
+ and not is_array_body
699
+ and not raw_body_param_name
700
+ ):
701
+ empty_body_param_name_base = "request_body" # For empty JSON object
702
+ current_arg_names_set = set(required_args) | {arg.split("=")[0] for arg in optional_args}
586
703
 
587
- if has_empty_body and selected_content_type == "application/json" and not body_params and not is_array_body and not raw_body_param_name:
588
- empty_body_param_name_base = "request_body" # For empty JSON object
589
- current_arg_names_set = set(required_args) | {arg.split('=')[0] for arg in optional_args}
590
-
591
704
  final_empty_body_param_name = empty_body_param_name_base
592
705
  counter = 1
593
706
  is_first_suffix_attempt = True
@@ -599,25 +712,34 @@ def _generate_method_code(path, method, operation):
599
712
  final_empty_body_param_name = f"{empty_body_param_name_base}_body_{counter}"
600
713
  counter += 1
601
714
 
602
- # Check if it was somehow added by other logic (e.g. if 'request_body' was an explicit param name)
715
+ # For signature with types
716
+ # Empty body usually implies an empty JSON object, so dict or Optional[dict]
717
+ empty_body_py_type = _openapi_type_to_python_type({"type": "object"}, required=False)
603
718
 
604
719
  if final_empty_body_param_name not in (set(required_args) | {arg.split("=")[0] for arg in optional_args}):
605
720
  optional_args.append(f"{final_empty_body_param_name}=None")
721
+ # Add to typed signature list
722
+ signature_optional_args_typed.append(f"{final_empty_body_param_name}: {empty_body_py_type} = None")
723
+
606
724
  # Track for docstring, even if it's just 'request_body' or 'request_body_body'
607
725
  if final_empty_body_param_name not in final_request_body_arg_names_for_signature:
608
726
  final_request_body_arg_names_for_signature.append(final_empty_body_param_name)
609
727
 
610
- # Combine required and optional arguments
728
+ # Combine required and optional arguments FOR DOCSTRING (as before, without types)
611
729
  args = required_args + optional_args
612
- print(f"[DEBUG] Final combined args for signature: {args}") # DEBUG
730
+ print(f"[DEBUG] Final combined args for DOCSTRING: {args}") # DEBUG
613
731
 
614
- # ----- Build Docstring -----
732
+ # Combine required and optional arguments FOR SIGNATURE (with types)
733
+ signature_args_combined_typed = signature_required_args_typed + signature_optional_args_typed
734
+ print(f"[DEBUG] Final combined args for SIGNATURE: {signature_args_combined_typed}") # DEBUG
735
+
736
+ # ----- Build Docstring -----
615
737
  # This section constructs the entire docstring for the generated method,
616
738
  # including summary, argument descriptions, return type, and tags.
617
739
  docstring_parts = []
618
740
  # NEW: Add OpenAPI path as the first line of the docstring
619
- openapi_path_comment_for_docstring = f"# openapi_path: {path}"
620
- docstring_parts.append(openapi_path_comment_for_docstring)
741
+ # openapi_path_comment_for_docstring = f"# openapi_path: {path}"
742
+ # docstring_parts.append(openapi_path_comment_for_docstring)
621
743
 
622
744
  return_type = _determine_return_type(operation)
623
745
 
@@ -632,7 +754,7 @@ def _generate_method_code(path, method, operation):
632
754
  # Args
633
755
  args_doc_lines = []
634
756
  param_details = {}
635
-
757
+
636
758
  # Create a combined list of all parameter objects (path, query, body) to fetch details for docstring
637
759
  all_parameter_objects_for_docstring = path_params + query_params + body_params
638
760
 
@@ -643,18 +765,18 @@ def _generate_method_code(path, method, operation):
643
765
  param_details[param_obj.name] = param_obj
644
766
 
645
767
  # Fetch request body example
646
- example_data = None # Initialize example_data here for wider scope
768
+ example_data = None # Initialize example_data here for wider scope
647
769
 
648
770
  if has_body:
649
771
  try:
650
772
  json_content = operation["requestBody"]["content"]["application/json"]
651
- #From direct content definition
773
+ # From direct content definition
652
774
  if "example" in json_content:
653
775
  example_data = json_content["example"]
654
776
  elif "examples" in json_content and json_content["examples"]:
655
777
  first_example_key = list(json_content["examples"].keys())[0]
656
778
  example_data = json_content["examples"][first_example_key].get("value")
657
- #If not found directly, try from resolved body schema (for nested/referenced examples)
779
+ # If not found directly, try from resolved body schema (for nested/referenced examples)
658
780
  if example_data is None and body_schema_to_use and "example" in body_schema_to_use:
659
781
  example_data = body_schema_to_use["example"]
660
782
  except KeyError:
@@ -672,18 +794,24 @@ def _generate_method_code(path, method, operation):
672
794
  # Adjust type_hint for file parameters for the docstring
673
795
  if detail.is_file:
674
796
  type_hint = "file (e.g., open('path/to/file', 'rb'))"
675
-
797
+
676
798
  arg_line = f" {arg_name} ({type_hint}): {desc}"
677
- if detail.example and not detail.is_file: # Don't show schema example for file inputs
799
+ if detail.example and not detail.is_file: # Don't show schema example for file inputs
678
800
  example_str = repr(detail.example)
679
801
  arg_line += f" Example: {example_str}."
680
802
  # Fallback for body parameters if no direct example was found
681
- elif not example_str and detail.where == "body" and example_data and isinstance(example_data, dict) and detail.identifier in example_data:
803
+ elif (
804
+ not example_str
805
+ and detail.where == "body"
806
+ and example_data
807
+ and isinstance(example_data, dict)
808
+ and detail.identifier in example_data
809
+ ):
682
810
  current_body_param_example = example_data[detail.identifier]
683
- if current_body_param_example is not None: # Ensure the extracted part is not None
811
+ if current_body_param_example is not None: # Ensure the extracted part is not None
684
812
  try:
685
813
  arg_line += f" Example: {repr(current_body_param_example)}."
686
- except Exception: # Fallback if repr fails
814
+ except Exception: # Fallback if repr fails
687
815
  arg_line += " Example: [Could not represent example]."
688
816
 
689
817
  args_doc_lines.append(arg_line)
@@ -692,19 +820,17 @@ def _generate_method_code(path, method, operation):
692
820
  args_doc_lines.append(
693
821
  f" {arg_name} (dict | None): Optional dictionary for an empty JSON request body (e.g., {{}})."
694
822
  )
695
- elif arg_name == raw_body_param_name:
823
+ elif arg_name == raw_body_param_name:
696
824
  raw_body_type_hint = "bytes"
697
825
  raw_body_desc = "Raw binary content for the request body."
698
826
  if selected_content_type and "text" in selected_content_type:
699
827
  raw_body_type_hint = "str"
700
828
  raw_body_desc = "Raw text content for the request body."
701
829
  elif selected_content_type and selected_content_type.startswith("image/"):
702
- raw_body_type_hint = "bytes (image data)"
703
- raw_body_desc = f"Raw image content ({selected_content_type}) for the request body."
704
- args_doc_lines.append(
705
- f" {arg_name} ({raw_body_type_hint} | None): {raw_body_desc}"
706
- )
707
-
830
+ raw_body_type_hint = "bytes (image data)"
831
+ raw_body_desc = f"Raw image content ({selected_content_type}) for the request body."
832
+ args_doc_lines.append(f" {arg_name} ({raw_body_type_hint} | None): {raw_body_desc}")
833
+
708
834
  if args_doc_lines:
709
835
  docstring_parts.append("\n".join(args_doc_lines))
710
836
 
@@ -720,7 +846,7 @@ def _generate_method_code(path, method, operation):
720
846
  raises_section_lines = [
721
847
  "Raises:",
722
848
  " HTTPError: Raised when the API request fails (e.g., non-2XX status code).",
723
- " JSONDecodeError: Raised if the response body cannot be parsed as JSON."
849
+ " JSONDecodeError: Raised if the response body cannot be parsed as JSON.",
724
850
  ]
725
851
  docstring_parts.append("\n".join(raises_section_lines))
726
852
 
@@ -742,12 +868,13 @@ def _generate_method_code(path, method, operation):
742
868
  # ----- End Build Docstring -----
743
869
 
744
870
  # --- Construct Method Signature String ---
745
- if args:
746
- signature = f" def {func_name}(self, {', '.join(args)}) -> {return_type}:"
871
+ # Use signature_args_combined_typed for the signature
872
+ if signature_args_combined_typed:
873
+ signature = f" def {func_name}(self, {', '.join(signature_args_combined_typed)}) -> {return_type}:"
747
874
  else:
748
875
  signature = f" def {func_name}(self) -> {return_type}:"
749
876
 
750
- # --- Build Method Body ---
877
+ # --- Build Method Body ---
751
878
  # This section constructs the executable lines of code within the generated method.
752
879
  body_lines = []
753
880
 
@@ -755,10 +882,9 @@ def _generate_method_code(path, method, operation):
755
882
  for param in path_params:
756
883
  body_lines.append(f" if {param.name} is None:")
757
884
  body_lines.append(
758
- f' raise ValueError("Missing required parameter \'{param.identifier}\'.")' # Use original name in error, ensure quotes are balanced
885
+ f" raise ValueError(\"Missing required parameter '{param.identifier}'.\")" # Use original name in error, ensure quotes are balanced
759
886
  )
760
887
 
761
-
762
888
  if method_lower not in ["get", "delete"]:
763
889
  body_lines.append(" request_body_data = None")
764
890
 
@@ -768,7 +894,6 @@ def _generate_method_code(path, method, operation):
768
894
  if method_lower in ["post", "put"] and selected_content_type == "multipart/form-data":
769
895
  body_lines.append(" files_data = None")
770
896
 
771
-
772
897
  # --- Build Request Payload (request_body_data and files_data) ---
773
898
  # This section prepares the data to be sent in the request body,
774
899
  # differentiating between files and other data for multipart forms,
@@ -777,27 +902,30 @@ def _generate_method_code(path, method, operation):
777
902
  # This block will now overwrite the initial None values if a body is present.
778
903
  if is_array_body:
779
904
  # For array request bodies, use the array parameter directly
780
- array_arg_name = final_request_body_arg_names_for_signature[0] if final_request_body_arg_names_for_signature else "items_body" # Fallback
905
+ array_arg_name = (
906
+ final_request_body_arg_names_for_signature[0]
907
+ if final_request_body_arg_names_for_signature
908
+ else "items_body"
909
+ ) # Fallback
781
910
  body_lines.append(f" # Using array parameter '{array_arg_name}' directly as request body")
782
- body_lines.append(f" request_body_data = {array_arg_name}") # Use a neutral temp name
911
+ body_lines.append(f" request_body_data = {array_arg_name}") # Use a neutral temp name
783
912
  # files_data remains None
784
913
 
785
914
  elif selected_content_type == "multipart/form-data":
786
- body_lines.append(" request_body_data = {}") # For non-file form fields
787
- body_lines.append(" files_data = {}") # For file fields
788
- for b_param in body_params: # Iterate through ALIASED body_params
915
+ body_lines.append(" request_body_data = {}") # For non-file form fields
916
+ body_lines.append(" files_data = {}") # For file fields
917
+ for b_param in body_params: # Iterate through ALIASED body_params
789
918
  if b_param.is_file:
790
- body_lines.append(f" if {b_param.name} is not None:") # Check if file param is provided
919
+ body_lines.append(f" if {b_param.name} is not None:") # Check if file param is provided
791
920
  body_lines.append(f" files_data['{b_param.identifier}'] = {b_param.name}")
792
921
  else:
793
- body_lines.append(f" if {b_param.name} is not None:") # Check if form field is provided
922
+ body_lines.append(f" if {b_param.name} is not None:") # Check if form field is provided
794
923
  body_lines.append(f" request_body_data['{b_param.identifier}'] = {b_param.name}")
795
924
  body_lines.append(" files_data = {k: v for k, v in files_data.items() if v is not None}")
796
925
  # Ensure files_data is None if it's empty after filtering, as httpx expects None, not {}
797
926
  body_lines.append(" if not files_data: files_data = None")
798
927
 
799
-
800
- elif body_params: # Object request bodies (JSON, x-www-form-urlencoded) with specific parameters
928
+ elif body_params: # Object request bodies (JSON, x-www-form-urlencoded) with specific parameters
801
929
  body_lines.append(" request_body_data = {")
802
930
  for b_param in body_params:
803
931
  body_lines.append(f" '{b_param.identifier}': {b_param.name},")
@@ -805,13 +933,14 @@ def _generate_method_code(path, method, operation):
805
933
  body_lines.append(
806
934
  " request_body_data = {k: v for k, v in request_body_data.items() if v is not None}"
807
935
  )
808
-
809
- elif raw_body_param_name: # Raw content type (octet-stream, text, image)
936
+
937
+ elif raw_body_param_name: # Raw content type (octet-stream, text, image)
810
938
  body_lines.append(f" request_body_data = {raw_body_param_name}")
811
939
 
812
- elif has_empty_body and selected_content_type == "application/json": # Empty JSON object {}
813
- body_lines.append(f" request_body_data = {final_empty_body_param_name} if {final_empty_body_param_name} is not None else {{}}")
814
-
940
+ elif has_empty_body and selected_content_type == "application/json": # Empty JSON object {}
941
+ body_lines.append(
942
+ f" request_body_data = {final_empty_body_param_name} if {final_empty_body_param_name} is not None else {{}}"
943
+ )
815
944
 
816
945
  # --- Format URL and Query Parameters for Request ---
817
946
  url = _generate_url(path, path_params)
@@ -831,21 +960,20 @@ def _generate_method_code(path, method, operation):
831
960
 
832
961
  # --- Determine Final Content-Type for API Call (Obsolete Block, selected_content_type is used) ---
833
962
  # The following block for request_body_content_type is largely superseded by selected_content_type,
834
-
963
+
835
964
  # Use the selected_content_type determined by the new logic as the primary source of truth.
836
965
  final_content_type_for_api_call = selected_content_type if selected_content_type else "application/json"
837
966
 
838
967
  # --- Make HTTP Request ---
839
- # This section generates the actual HTTP call
968
+ # This section generates the actual HTTP call
840
969
  # using the prepared URL, query parameters, request body data, files, and content type.
841
970
 
842
-
843
971
  if method_lower == "get":
844
972
  body_lines.append(" response = self._get(url, params=query_params)")
845
973
  elif method_lower == "post":
846
974
  if selected_content_type == "multipart/form-data":
847
975
  body_lines.append(
848
- f" response = self._post(url, data=request_body_data, files=files_data, params=query_params, content_type='{final_content_type_for_api_call}')"
976
+ f" response = self._post(url, data=request_body_data, files=files_data, params=query_params, content_type='{final_content_type_for_api_call}')"
849
977
  )
850
978
  else:
851
979
  body_lines.append(
@@ -854,23 +982,18 @@ def _generate_method_code(path, method, operation):
854
982
  elif method_lower == "put":
855
983
  if selected_content_type == "multipart/form-data":
856
984
  body_lines.append(
857
- f" response = self._put(url, data=request_body_data, files=files_data, params=query_params, content_type='{final_content_type_for_api_call}')"
985
+ f" response = self._put(url, data=request_body_data, files=files_data, params=query_params, content_type='{final_content_type_for_api_call}')"
858
986
  )
859
987
  else:
860
988
  body_lines.append(
861
989
  f" response = self._put(url, data=request_body_data, params=query_params, content_type='{final_content_type_for_api_call}')"
862
990
  )
863
991
  elif method_lower == "patch":
864
-
865
- body_lines.append(
866
- " response = self._patch(url, data=request_body_data, params=query_params)"
867
- )
992
+ body_lines.append(" response = self._patch(url, data=request_body_data, params=query_params)")
868
993
  elif method_lower == "delete":
869
994
  body_lines.append(" response = self._delete(url, params=query_params)")
870
995
  else:
871
- body_lines.append(
872
- f" response = self._{method_lower}(url, data=request_body_data, params=query_params)"
873
- )
996
+ body_lines.append(f" response = self._{method_lower}(url, data=request_body_data, params=query_params)")
874
997
 
875
998
  # --- Handle Response ---
876
999
  body_lines.append(" response.raise_for_status()")
@@ -965,7 +1088,7 @@ def generate_api_client(schema, class_name: str | None = None):
965
1088
 
966
1089
  # Generate class imports
967
1090
  imports = [
968
- "from typing import Any",
1091
+ "from typing import Any, Optional, List",
969
1092
  "from universal_mcp.applications import APIApplication",
970
1093
  "from universal_mcp.integrations import Integration",
971
1094
  ]