universal-mcp 0.1.21rc2__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.
- universal_mcp/applications/__init__.py +0 -1
- universal_mcp/integrations/integration.py +4 -8
- universal_mcp/servers/server.py +35 -30
- universal_mcp/tools/adapters.py +21 -0
- universal_mcp/tools/manager.py +122 -37
- universal_mcp/utils/agentr.py +3 -13
- universal_mcp/utils/docstring_parser.py +18 -64
- universal_mcp/utils/openapi/api_splitter.py +250 -132
- universal_mcp/utils/openapi/openapi.py +134 -117
- universal_mcp/utils/openapi/preprocessor.py +272 -29
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/METADATA +2 -1
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/RECORD +15 -15
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.21rc2.dist-info → universal_mcp-0.1.22rc1.dist-info}/licenses/LICENSE +0 -0
@@ -87,34 +87,34 @@ def _openapi_type_to_python_type(schema: dict, required: bool = True) -> str:
|
|
87
87
|
"""
|
88
88
|
openapi_type = schema.get("type")
|
89
89
|
|
90
|
-
|
91
|
-
|
92
|
-
py_type = "dict[str, Any]"
|
90
|
+
if "$ref" in schema and not openapi_type:
|
91
|
+
py_type = "dict[str, Any]"
|
93
92
|
elif openapi_type == "array":
|
94
93
|
items_schema = schema.get("items", {})
|
95
|
-
item_type = _openapi_type_to_python_type(items_schema, required=True)
|
94
|
+
item_type = _openapi_type_to_python_type(items_schema, required=True)
|
96
95
|
py_type = f"List[{item_type}]"
|
97
96
|
elif openapi_type == "object":
|
98
|
-
|
99
97
|
if schema.get("format") in ["binary", "byte"]:
|
100
|
-
|
98
|
+
py_type = "bytes"
|
101
99
|
else:
|
102
|
-
|
103
100
|
if "additionalProperties" in schema and isinstance(schema["additionalProperties"], dict):
|
104
101
|
additional_props_schema = schema["additionalProperties"]
|
105
|
-
|
102
|
+
|
106
103
|
value_type = _openapi_type_to_python_type(additional_props_schema, required=True)
|
107
104
|
py_type = f"dict[str, {value_type}]"
|
108
|
-
elif
|
109
|
-
|
110
|
-
|
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]"
|
111
112
|
else:
|
112
|
-
|
113
113
|
py_type = "dict[str, Any]"
|
114
114
|
elif openapi_type == "integer":
|
115
115
|
py_type = "int"
|
116
116
|
elif openapi_type == "number":
|
117
|
-
py_type = "float"
|
117
|
+
py_type = "float"
|
118
118
|
elif openapi_type == "boolean":
|
119
119
|
py_type = "bool"
|
120
120
|
elif openapi_type == "string":
|
@@ -124,9 +124,8 @@ def _openapi_type_to_python_type(schema: dict, required: bool = True) -> str:
|
|
124
124
|
py_type = "str"
|
125
125
|
else:
|
126
126
|
py_type = "str"
|
127
|
-
else:
|
128
|
-
|
129
|
-
py_type = "Any"
|
127
|
+
else:
|
128
|
+
py_type = "Any"
|
130
129
|
|
131
130
|
if not required:
|
132
131
|
if py_type.startswith("Optional[") and py_type.endswith("]"):
|
@@ -256,7 +255,7 @@ def _determine_function_name(operation: dict[str, Any], path: str, method: str)
|
|
256
255
|
if "operationId" in operation:
|
257
256
|
raw_name = operation["operationId"]
|
258
257
|
cleaned_name = raw_name.replace(".", "_").replace("-", "_")
|
259
|
-
cleaned_name_no_numbers = re.sub(r
|
258
|
+
cleaned_name_no_numbers = re.sub(r"\d+", "", cleaned_name)
|
260
259
|
func_name = convert_to_snake_case(cleaned_name_no_numbers)
|
261
260
|
else:
|
262
261
|
# Generate name from path and method
|
@@ -291,7 +290,7 @@ def _generate_path_params(path: str) -> list[Parameters]:
|
|
291
290
|
where="path",
|
292
291
|
required=True,
|
293
292
|
example=None,
|
294
|
-
schema={"type": "string"}
|
293
|
+
schema={"type": "string"},
|
295
294
|
)
|
296
295
|
)
|
297
296
|
except Exception as e:
|
@@ -340,7 +339,7 @@ def _generate_query_params(operation: dict[str, Any]) -> list[Parameters]:
|
|
340
339
|
where=where,
|
341
340
|
required=required,
|
342
341
|
example=str(example_value) if example_value is not None else None,
|
343
|
-
schema=param_schema if param_schema else {"type": type_value}
|
342
|
+
schema=param_schema if param_schema else {"type": type_value},
|
344
343
|
)
|
345
344
|
query_params.append(parameter)
|
346
345
|
return query_params
|
@@ -372,12 +371,12 @@ def _generate_body_params(schema_to_process: dict[str, Any] | None, overall_body
|
|
372
371
|
name=_sanitize_identifier(param_name),
|
373
372
|
identifier=param_name,
|
374
373
|
description=param_description,
|
375
|
-
type=effective_param_type,
|
374
|
+
type=effective_param_type,
|
376
375
|
where="body",
|
377
376
|
required=param_required,
|
378
377
|
example=str(param_example) if param_example is not None else None,
|
379
378
|
is_file=current_is_file,
|
380
|
-
schema=param_schema_details
|
379
|
+
schema=param_schema_details,
|
381
380
|
)
|
382
381
|
)
|
383
382
|
# print(f"[DEBUG] Final body_params list generated: {body_params}") # DEBUG
|
@@ -403,10 +402,10 @@ def _generate_method_code(path, method, operation):
|
|
403
402
|
|
404
403
|
# --- Determine Function Name and Basic Operation Details ---
|
405
404
|
func_name = _determine_function_name(operation, path, method)
|
406
|
-
method_lower = method.lower()
|
407
|
-
operation.get("summary", "")
|
408
|
-
operation.get("tags", [])
|
409
|
-
|
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
|
+
|
410
409
|
# --- Generate Path and Query Parameters (pre-aliasing) ---
|
411
410
|
path_params = _generate_path_params(path)
|
412
411
|
query_params = _generate_query_params(operation)
|
@@ -415,7 +414,7 @@ def _generate_method_code(path, method, operation):
|
|
415
414
|
# This section selects the primary content type and its schema to be used for the request body.
|
416
415
|
has_body = "requestBody" in operation
|
417
416
|
body_schema_to_use = None
|
418
|
-
selected_content_type = None
|
417
|
+
selected_content_type = None # This will hold the chosen content type string
|
419
418
|
|
420
419
|
if has_body:
|
421
420
|
request_body_spec = operation["requestBody"]
|
@@ -428,7 +427,7 @@ def _generate_method_code(path, method, operation):
|
|
428
427
|
"application/octet-stream",
|
429
428
|
"text/plain",
|
430
429
|
]
|
431
|
-
|
430
|
+
|
432
431
|
found_preferred = False
|
433
432
|
for ct in preferred_content_types:
|
434
433
|
if ct in request_body_content_map:
|
@@ -436,25 +435,25 @@ def _generate_method_code(path, method, operation):
|
|
436
435
|
body_schema_to_use = request_body_content_map[ct].get("schema")
|
437
436
|
found_preferred = True
|
438
437
|
break
|
439
|
-
|
440
|
-
if not found_preferred:
|
438
|
+
|
439
|
+
if not found_preferred: # Check for image/* if no direct match yet
|
441
440
|
for ct_key in request_body_content_map:
|
442
441
|
if ct_key.startswith("image/"):
|
443
442
|
selected_content_type = ct_key
|
444
443
|
body_schema_to_use = request_body_content_map[ct_key].get("schema")
|
445
444
|
found_preferred = True
|
446
445
|
break
|
447
|
-
|
448
|
-
if not found_preferred and request_body_content_map:
|
446
|
+
|
447
|
+
if not found_preferred and request_body_content_map: # Fallback to first listed
|
449
448
|
first_ct_key = next(iter(request_body_content_map))
|
450
449
|
selected_content_type = first_ct_key
|
451
450
|
body_schema_to_use = request_body_content_map[first_ct_key].get("schema")
|
452
451
|
|
453
452
|
# --- Generate Body Parameters (based on selected schema, pre-aliasing) ---
|
454
|
-
if body_schema_to_use:
|
453
|
+
if body_schema_to_use: # If a schema was actually found for the selected content type
|
455
454
|
body_params = _generate_body_params(
|
456
|
-
body_schema_to_use,
|
457
|
-
operation.get("requestBody", {}).get("required", False)
|
455
|
+
body_schema_to_use, # Pass the specific schema
|
456
|
+
operation.get("requestBody", {}).get("required", False), # Pass the overall body requirement
|
458
457
|
)
|
459
458
|
else:
|
460
459
|
body_params = []
|
@@ -526,29 +525,36 @@ def _generate_method_code(path, method, operation):
|
|
526
525
|
current_body_param_names.add(b_param.name)
|
527
526
|
# --- End Alias duplicate parameter names ---
|
528
527
|
|
529
|
-
|
530
528
|
# --- Determine Return Type and Body Characteristics ---
|
531
529
|
return_type = _determine_return_type(operation)
|
532
530
|
|
533
|
-
body_required = has_body and operation["requestBody"].get("required", False)
|
534
|
-
|
531
|
+
body_required = has_body and operation["requestBody"].get("required", False) # Remains useful
|
532
|
+
|
535
533
|
is_array_body = False
|
536
|
-
has_empty_body = False
|
534
|
+
has_empty_body = False
|
537
535
|
|
538
|
-
if has_body and body_schema_to_use:
|
536
|
+
if has_body and body_schema_to_use: # Use the determined body_schema_to_use
|
539
537
|
if body_schema_to_use.get("type") == "array":
|
540
538
|
is_array_body = True
|
541
|
-
|
539
|
+
|
542
540
|
# Check for cases that might lead to an "empty" body parameter (for JSON) in the signature,
|
543
541
|
# or indicate a raw body type where _generate_body_params wouldn't create named params.
|
544
|
-
if
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
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
|
552
558
|
|
553
559
|
# --- Build Function Arguments for Signature ---
|
554
560
|
# This section constructs the list of arguments (required and optional)
|
@@ -588,8 +594,8 @@ def _generate_method_code(path, method, operation):
|
|
588
594
|
# Process Body Parameters / Request Body
|
589
595
|
# This list tracks the *final* names of parameters in the signature that come from the request body,
|
590
596
|
final_request_body_arg_names_for_signature = []
|
591
|
-
final_empty_body_param_name = None
|
592
|
-
raw_body_param_name = None
|
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/*
|
593
599
|
|
594
600
|
if has_body:
|
595
601
|
current_arg_names_set = set(required_args) | {arg.split("=")[0] for arg in optional_args}
|
@@ -624,11 +630,17 @@ def _generate_method_code(path, method, operation):
|
|
624
630
|
final_request_body_arg_names_for_signature.append(final_array_param_name)
|
625
631
|
|
626
632
|
# New: Handle raw body parameter (if body_params is empty but body is expected and not array/empty JSON)
|
627
|
-
elif
|
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
|
+
):
|
628
640
|
# This branch is for raw content types like application/octet-stream, text/plain, image/*
|
629
641
|
# where _generate_body_params returned an empty list because the schema isn't an object with properties.
|
630
642
|
raw_body_param_name_base = "body_content"
|
631
|
-
|
643
|
+
|
632
644
|
temp_raw_body_name = raw_body_param_name_base
|
633
645
|
counter = 1
|
634
646
|
is_first_suffix_attempt = True
|
@@ -643,15 +655,15 @@ def _generate_method_code(path, method, operation):
|
|
643
655
|
|
644
656
|
# For signature with types
|
645
657
|
# Determine type based on selected_content_type for raw body
|
646
|
-
raw_body_schema_for_type = {"type": "string", "format": "binary"}
|
658
|
+
raw_body_schema_for_type = {"type": "string", "format": "binary"} # Default to bytes
|
647
659
|
if selected_content_type and "text" in selected_content_type:
|
648
660
|
raw_body_schema_for_type = {"type": "string"}
|
649
661
|
elif selected_content_type and selected_content_type.startswith("image/"):
|
650
|
-
raw_body_schema_for_type = {"type": "string", "format": "binary"}
|
651
|
-
|
662
|
+
raw_body_schema_for_type = {"type": "string", "format": "binary"} # image is bytes
|
663
|
+
|
652
664
|
raw_body_py_type = _openapi_type_to_python_type(raw_body_schema_for_type, required=body_required)
|
653
665
|
|
654
|
-
if body_required:
|
666
|
+
if body_required: # If the raw body itself is required
|
655
667
|
required_args.append(raw_body_param_name)
|
656
668
|
signature_required_args_typed.append(f"{raw_body_param_name}: {raw_body_py_type}")
|
657
669
|
else:
|
@@ -659,13 +671,13 @@ def _generate_method_code(path, method, operation):
|
|
659
671
|
signature_optional_args_typed.append(f"{raw_body_param_name}: {raw_body_py_type} = None")
|
660
672
|
final_request_body_arg_names_for_signature.append(raw_body_param_name)
|
661
673
|
|
662
|
-
elif body_params:
|
674
|
+
elif body_params: # Object body with discernible properties
|
663
675
|
for param in body_params: # Iterate ALIASED body_params
|
664
|
-
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")
|
665
677
|
|
666
|
-
# Defensive check against already added args
|
678
|
+
# Defensive check against already added args
|
667
679
|
current_arg_names_set_loop = set(required_args) | {arg.split("=")[0] for arg in optional_args}
|
668
|
-
|
680
|
+
|
669
681
|
# For signature with types
|
670
682
|
param_py_type = _openapi_type_to_python_type(param.schema, required=param.required)
|
671
683
|
|
@@ -679,11 +691,16 @@ def _generate_method_code(path, method, operation):
|
|
679
691
|
signature_optional_args_typed.append(f"{arg_name_for_sig}: {param_py_type} = None")
|
680
692
|
final_request_body_arg_names_for_signature.append(arg_name_for_sig)
|
681
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}
|
682
703
|
|
683
|
-
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:
|
684
|
-
empty_body_param_name_base = "request_body" # For empty JSON object
|
685
|
-
current_arg_names_set = set(required_args) | {arg.split('=')[0] for arg in optional_args}
|
686
|
-
|
687
704
|
final_empty_body_param_name = empty_body_param_name_base
|
688
705
|
counter = 1
|
689
706
|
is_first_suffix_attempt = True
|
@@ -710,19 +727,19 @@ def _generate_method_code(path, method, operation):
|
|
710
727
|
|
711
728
|
# Combine required and optional arguments FOR DOCSTRING (as before, without types)
|
712
729
|
args = required_args + optional_args
|
713
|
-
print(f"[DEBUG] Final combined args for DOCSTRING: {args}")
|
730
|
+
print(f"[DEBUG] Final combined args for DOCSTRING: {args}") # DEBUG
|
714
731
|
|
715
732
|
# Combine required and optional arguments FOR SIGNATURE (with types)
|
716
733
|
signature_args_combined_typed = signature_required_args_typed + signature_optional_args_typed
|
717
|
-
print(f"[DEBUG] Final combined args for SIGNATURE: {signature_args_combined_typed}")
|
734
|
+
print(f"[DEBUG] Final combined args for SIGNATURE: {signature_args_combined_typed}") # DEBUG
|
718
735
|
|
719
|
-
# ----- Build Docstring -----
|
736
|
+
# ----- Build Docstring -----
|
720
737
|
# This section constructs the entire docstring for the generated method,
|
721
738
|
# including summary, argument descriptions, return type, and tags.
|
722
739
|
docstring_parts = []
|
723
740
|
# NEW: Add OpenAPI path as the first line of the docstring
|
724
|
-
openapi_path_comment_for_docstring = f"# openapi_path: {path}"
|
725
|
-
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)
|
726
743
|
|
727
744
|
return_type = _determine_return_type(operation)
|
728
745
|
|
@@ -737,7 +754,7 @@ def _generate_method_code(path, method, operation):
|
|
737
754
|
# Args
|
738
755
|
args_doc_lines = []
|
739
756
|
param_details = {}
|
740
|
-
|
757
|
+
|
741
758
|
# Create a combined list of all parameter objects (path, query, body) to fetch details for docstring
|
742
759
|
all_parameter_objects_for_docstring = path_params + query_params + body_params
|
743
760
|
|
@@ -748,18 +765,18 @@ def _generate_method_code(path, method, operation):
|
|
748
765
|
param_details[param_obj.name] = param_obj
|
749
766
|
|
750
767
|
# Fetch request body example
|
751
|
-
example_data = None
|
768
|
+
example_data = None # Initialize example_data here for wider scope
|
752
769
|
|
753
770
|
if has_body:
|
754
771
|
try:
|
755
772
|
json_content = operation["requestBody"]["content"]["application/json"]
|
756
|
-
#From direct content definition
|
773
|
+
# From direct content definition
|
757
774
|
if "example" in json_content:
|
758
775
|
example_data = json_content["example"]
|
759
776
|
elif "examples" in json_content and json_content["examples"]:
|
760
777
|
first_example_key = list(json_content["examples"].keys())[0]
|
761
778
|
example_data = json_content["examples"][first_example_key].get("value")
|
762
|
-
#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)
|
763
780
|
if example_data is None and body_schema_to_use and "example" in body_schema_to_use:
|
764
781
|
example_data = body_schema_to_use["example"]
|
765
782
|
except KeyError:
|
@@ -777,18 +794,24 @@ def _generate_method_code(path, method, operation):
|
|
777
794
|
# Adjust type_hint for file parameters for the docstring
|
778
795
|
if detail.is_file:
|
779
796
|
type_hint = "file (e.g., open('path/to/file', 'rb'))"
|
780
|
-
|
797
|
+
|
781
798
|
arg_line = f" {arg_name} ({type_hint}): {desc}"
|
782
|
-
if detail.example and not detail.is_file:
|
799
|
+
if detail.example and not detail.is_file: # Don't show schema example for file inputs
|
783
800
|
example_str = repr(detail.example)
|
784
801
|
arg_line += f" Example: {example_str}."
|
785
802
|
# Fallback for body parameters if no direct example was found
|
786
|
-
elif
|
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
|
+
):
|
787
810
|
current_body_param_example = example_data[detail.identifier]
|
788
|
-
if current_body_param_example is not None:
|
811
|
+
if current_body_param_example is not None: # Ensure the extracted part is not None
|
789
812
|
try:
|
790
813
|
arg_line += f" Example: {repr(current_body_param_example)}."
|
791
|
-
except Exception:
|
814
|
+
except Exception: # Fallback if repr fails
|
792
815
|
arg_line += " Example: [Could not represent example]."
|
793
816
|
|
794
817
|
args_doc_lines.append(arg_line)
|
@@ -797,19 +820,17 @@ def _generate_method_code(path, method, operation):
|
|
797
820
|
args_doc_lines.append(
|
798
821
|
f" {arg_name} (dict | None): Optional dictionary for an empty JSON request body (e.g., {{}})."
|
799
822
|
)
|
800
|
-
elif arg_name == raw_body_param_name:
|
823
|
+
elif arg_name == raw_body_param_name:
|
801
824
|
raw_body_type_hint = "bytes"
|
802
825
|
raw_body_desc = "Raw binary content for the request body."
|
803
826
|
if selected_content_type and "text" in selected_content_type:
|
804
827
|
raw_body_type_hint = "str"
|
805
828
|
raw_body_desc = "Raw text content for the request body."
|
806
829
|
elif selected_content_type and selected_content_type.startswith("image/"):
|
807
|
-
|
808
|
-
|
809
|
-
args_doc_lines.append(
|
810
|
-
|
811
|
-
)
|
812
|
-
|
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
|
+
|
813
834
|
if args_doc_lines:
|
814
835
|
docstring_parts.append("\n".join(args_doc_lines))
|
815
836
|
|
@@ -825,7 +846,7 @@ def _generate_method_code(path, method, operation):
|
|
825
846
|
raises_section_lines = [
|
826
847
|
"Raises:",
|
827
848
|
" HTTPError: Raised when the API request fails (e.g., non-2XX status code).",
|
828
|
-
" JSONDecodeError: Raised if the response body cannot be parsed as JSON."
|
849
|
+
" JSONDecodeError: Raised if the response body cannot be parsed as JSON.",
|
829
850
|
]
|
830
851
|
docstring_parts.append("\n".join(raises_section_lines))
|
831
852
|
|
@@ -853,7 +874,7 @@ def _generate_method_code(path, method, operation):
|
|
853
874
|
else:
|
854
875
|
signature = f" def {func_name}(self) -> {return_type}:"
|
855
876
|
|
856
|
-
# --- Build Method Body ---
|
877
|
+
# --- Build Method Body ---
|
857
878
|
# This section constructs the executable lines of code within the generated method.
|
858
879
|
body_lines = []
|
859
880
|
|
@@ -861,10 +882,9 @@ def _generate_method_code(path, method, operation):
|
|
861
882
|
for param in path_params:
|
862
883
|
body_lines.append(f" if {param.name} is None:")
|
863
884
|
body_lines.append(
|
864
|
-
f
|
885
|
+
f" raise ValueError(\"Missing required parameter '{param.identifier}'.\")" # Use original name in error, ensure quotes are balanced
|
865
886
|
)
|
866
887
|
|
867
|
-
|
868
888
|
if method_lower not in ["get", "delete"]:
|
869
889
|
body_lines.append(" request_body_data = None")
|
870
890
|
|
@@ -874,7 +894,6 @@ def _generate_method_code(path, method, operation):
|
|
874
894
|
if method_lower in ["post", "put"] and selected_content_type == "multipart/form-data":
|
875
895
|
body_lines.append(" files_data = None")
|
876
896
|
|
877
|
-
|
878
897
|
# --- Build Request Payload (request_body_data and files_data) ---
|
879
898
|
# This section prepares the data to be sent in the request body,
|
880
899
|
# differentiating between files and other data for multipart forms,
|
@@ -883,27 +902,30 @@ def _generate_method_code(path, method, operation):
|
|
883
902
|
# This block will now overwrite the initial None values if a body is present.
|
884
903
|
if is_array_body:
|
885
904
|
# For array request bodies, use the array parameter directly
|
886
|
-
array_arg_name =
|
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
|
887
910
|
body_lines.append(f" # Using array parameter '{array_arg_name}' directly as request body")
|
888
|
-
body_lines.append(f" request_body_data = {array_arg_name}")
|
911
|
+
body_lines.append(f" request_body_data = {array_arg_name}") # Use a neutral temp name
|
889
912
|
# files_data remains None
|
890
913
|
|
891
914
|
elif selected_content_type == "multipart/form-data":
|
892
|
-
body_lines.append(" request_body_data = {}")
|
893
|
-
body_lines.append(" files_data = {}")
|
894
|
-
for b_param in 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
|
895
918
|
if b_param.is_file:
|
896
|
-
body_lines.append(f" if {b_param.name} is not None:")
|
919
|
+
body_lines.append(f" if {b_param.name} is not None:") # Check if file param is provided
|
897
920
|
body_lines.append(f" files_data['{b_param.identifier}'] = {b_param.name}")
|
898
921
|
else:
|
899
|
-
body_lines.append(f" if {b_param.name} is not None:")
|
922
|
+
body_lines.append(f" if {b_param.name} is not None:") # Check if form field is provided
|
900
923
|
body_lines.append(f" request_body_data['{b_param.identifier}'] = {b_param.name}")
|
901
924
|
body_lines.append(" files_data = {k: v for k, v in files_data.items() if v is not None}")
|
902
925
|
# Ensure files_data is None if it's empty after filtering, as httpx expects None, not {}
|
903
926
|
body_lines.append(" if not files_data: files_data = None")
|
904
927
|
|
905
|
-
|
906
|
-
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
|
907
929
|
body_lines.append(" request_body_data = {")
|
908
930
|
for b_param in body_params:
|
909
931
|
body_lines.append(f" '{b_param.identifier}': {b_param.name},")
|
@@ -911,13 +933,14 @@ def _generate_method_code(path, method, operation):
|
|
911
933
|
body_lines.append(
|
912
934
|
" request_body_data = {k: v for k, v in request_body_data.items() if v is not None}"
|
913
935
|
)
|
914
|
-
|
915
|
-
elif raw_body_param_name:
|
936
|
+
|
937
|
+
elif raw_body_param_name: # Raw content type (octet-stream, text, image)
|
916
938
|
body_lines.append(f" request_body_data = {raw_body_param_name}")
|
917
939
|
|
918
|
-
elif has_empty_body and selected_content_type == "application/json":
|
919
|
-
body_lines.append(
|
920
|
-
|
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
|
+
)
|
921
944
|
|
922
945
|
# --- Format URL and Query Parameters for Request ---
|
923
946
|
url = _generate_url(path, path_params)
|
@@ -937,21 +960,20 @@ def _generate_method_code(path, method, operation):
|
|
937
960
|
|
938
961
|
# --- Determine Final Content-Type for API Call (Obsolete Block, selected_content_type is used) ---
|
939
962
|
# The following block for request_body_content_type is largely superseded by selected_content_type,
|
940
|
-
|
963
|
+
|
941
964
|
# Use the selected_content_type determined by the new logic as the primary source of truth.
|
942
965
|
final_content_type_for_api_call = selected_content_type if selected_content_type else "application/json"
|
943
966
|
|
944
967
|
# --- Make HTTP Request ---
|
945
|
-
# This section generates the actual HTTP call
|
968
|
+
# This section generates the actual HTTP call
|
946
969
|
# using the prepared URL, query parameters, request body data, files, and content type.
|
947
970
|
|
948
|
-
|
949
971
|
if method_lower == "get":
|
950
972
|
body_lines.append(" response = self._get(url, params=query_params)")
|
951
973
|
elif method_lower == "post":
|
952
974
|
if selected_content_type == "multipart/form-data":
|
953
975
|
body_lines.append(
|
954
|
-
|
976
|
+
f" response = self._post(url, data=request_body_data, files=files_data, params=query_params, content_type='{final_content_type_for_api_call}')"
|
955
977
|
)
|
956
978
|
else:
|
957
979
|
body_lines.append(
|
@@ -960,23 +982,18 @@ def _generate_method_code(path, method, operation):
|
|
960
982
|
elif method_lower == "put":
|
961
983
|
if selected_content_type == "multipart/form-data":
|
962
984
|
body_lines.append(
|
963
|
-
|
985
|
+
f" response = self._put(url, data=request_body_data, files=files_data, params=query_params, content_type='{final_content_type_for_api_call}')"
|
964
986
|
)
|
965
987
|
else:
|
966
988
|
body_lines.append(
|
967
989
|
f" response = self._put(url, data=request_body_data, params=query_params, content_type='{final_content_type_for_api_call}')"
|
968
990
|
)
|
969
991
|
elif method_lower == "patch":
|
970
|
-
|
971
|
-
body_lines.append(
|
972
|
-
" response = self._patch(url, data=request_body_data, params=query_params)"
|
973
|
-
)
|
992
|
+
body_lines.append(" response = self._patch(url, data=request_body_data, params=query_params)")
|
974
993
|
elif method_lower == "delete":
|
975
994
|
body_lines.append(" response = self._delete(url, params=query_params)")
|
976
995
|
else:
|
977
|
-
body_lines.append(
|
978
|
-
f" response = self._{method_lower}(url, data=request_body_data, params=query_params)"
|
979
|
-
)
|
996
|
+
body_lines.append(f" response = self._{method_lower}(url, data=request_body_data, params=query_params)")
|
980
997
|
|
981
998
|
# --- Handle Response ---
|
982
999
|
body_lines.append(" response.raise_for_status()")
|
@@ -1145,4 +1162,4 @@ if __name__ == "__main__":
|
|
1145
1162
|
|
1146
1163
|
schema = load_schema("openapi.yaml")
|
1147
1164
|
code = generate_api_client(schema)
|
1148
|
-
print(code)
|
1165
|
+
print(code)
|