mvp
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -1,10 +1,47 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
import re
|
||||
from typing import Dict, Any
|
||||
from typing import Dict, Any, Tuple
|
||||
import json
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def is_camel_case(s: str) -> bool:
|
||||
"""
|
||||
检查字符串是否为小驼峰式命名 (lowerCamelCase)。
|
||||
小驼峰式命名要求:
|
||||
1. 以小写字母开头。
|
||||
2. 后续可以跟字母或数字。
|
||||
3. 不能包含下划线或短横线。
|
||||
"""
|
||||
if not s or not isinstance(s, str):
|
||||
return False
|
||||
# 检查是否以小写字母开头,并且不包含下划线或中划线
|
||||
return re.match(r'^[a-z]+([A-Z0-9][a-z0-9]*)*$', s) is not None
|
||||
|
||||
|
||||
def extract_json_from_response(response: Any) -> Tuple[Any, str]:
|
||||
"""
|
||||
从API响应中安全地提取JSON数据。
|
||||
这个响应可以是一个 requests.Response 对象,也可以是一个字典(模拟的响应)。
|
||||
"""
|
||||
if hasattr(response, 'json'): # 类似 requests.Response 对象
|
||||
try:
|
||||
return response.json(), ""
|
||||
except json.JSONDecodeError as e:
|
||||
return None, f"JSON解析失败: {e}"
|
||||
except Exception as e:
|
||||
return None, f"从响应中提取JSON时发生未知错误: {e}"
|
||||
elif isinstance(response, dict): # 模拟的响应字典
|
||||
return response, ""
|
||||
elif isinstance(response, str):
|
||||
try:
|
||||
return json.loads(response), ""
|
||||
except json.JSONDecodeError as e:
|
||||
return None, f"JSON字符串解析失败: {e}"
|
||||
return None, "不支持的响应类型"
|
||||
|
||||
|
||||
def format_url_with_path_params(path_template: str, path_params: Dict[str, Any]) -> str:
|
||||
"""
|
||||
使用提供的路径参数格式化URL路径模板。
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import logging
|
||||
import copy
|
||||
from typing import Dict, List, Any, Optional, Union, Tuple
|
||||
import re
|
||||
|
||||
# 获取模块级别的 logger
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -609,4 +610,204 @@ def find_first_simple_type_field_recursive(
|
||||
return found_in_root_array_item_object
|
||||
|
||||
# effective_logger.debug(f"No simple type field found at path {'.'.join(map(str, path_so_far))}")
|
||||
return None
|
||||
return None
|
||||
|
||||
def util_extract_range_from_description(description: str, logger_param: Optional[logging.Logger] = None) -> Tuple[Optional[float], Optional[float]]:
|
||||
"""从描述文本中提取数值范围信息。"""
|
||||
effective_logger = logger_param or logger
|
||||
if not description:
|
||||
return None, None
|
||||
|
||||
min_value, max_value = None, None
|
||||
|
||||
MIN_VALUE_PATTERNS = [
|
||||
r'(最小|至少|从|大于等于|不小于|>=|>|minimum|min).*?(\d+(\.\d+)?)',
|
||||
r'(\d+(\.\d+)?).*?(起|开始|以上|最小|minimum|min)',
|
||||
]
|
||||
MAX_VALUE_PATTERNS = [
|
||||
r'(最大|至多|不超过|不大于|小于等于|<=|<|maximum|max).*?(\d+(\.\d+)?)',
|
||||
r'(\d+(\.\d+)?).*?(以下|以内|之内|最大|maximum|max)',
|
||||
]
|
||||
|
||||
for pattern in MIN_VALUE_PATTERNS:
|
||||
matches = re.search(pattern, description, re.IGNORECASE)
|
||||
if matches:
|
||||
try:
|
||||
min_value = float(matches.group(2))
|
||||
effective_logger.debug(f"从 '{description}' 中提取到最小值: {min_value}")
|
||||
break
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
for pattern in MAX_VALUE_PATTERNS:
|
||||
matches = re.search(pattern, description, re.IGNORECASE)
|
||||
if matches:
|
||||
try:
|
||||
max_value = float(matches.group(2))
|
||||
effective_logger.debug(f"从 '{description}' 中提取到最大值: {max_value}")
|
||||
break
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
return min_value, max_value
|
||||
|
||||
def util_find_ranged_field_recursive(
|
||||
schema: Dict[str, Any],
|
||||
path: List[Union[str, int]],
|
||||
logger_param: Optional[logging.Logger] = None
|
||||
) -> Optional[Tuple[List[Union[str, int]], Dict[str, Any], Optional[str], Optional[float], Optional[float], str]]:
|
||||
"""
|
||||
递归地在schema中寻找第一个具有范围限制的字段。
|
||||
返回 (路径, schema, 类型, 最小值, 最大值, 描述)。
|
||||
"""
|
||||
effective_logger = logger_param or logger
|
||||
if not isinstance(schema, dict):
|
||||
return None
|
||||
|
||||
schema_type = schema.get('type')
|
||||
description = schema.get('description', '')
|
||||
|
||||
# 检查描述和schema关键字
|
||||
min_val, max_val = util_extract_range_from_description(description, effective_logger)
|
||||
|
||||
schema_min = schema.get('minimum')
|
||||
schema_max = schema.get('maximum')
|
||||
|
||||
if schema_min is not None:
|
||||
min_val = schema_min
|
||||
if schema_max is not None:
|
||||
max_val = schema_max
|
||||
|
||||
# 如果找到范围限制,则认为这是一个目标字段
|
||||
if min_val is not None or max_val is not None:
|
||||
effective_logger.debug(f"在路径 {'.'.join(map(str, path))} 找到范围受限字段。")
|
||||
return path, schema, schema_type, min_val, max_val, description
|
||||
|
||||
# 递归
|
||||
if schema_type == 'object' and 'properties' in schema:
|
||||
for prop_name, prop_schema in schema['properties'].items():
|
||||
result = util_find_ranged_field_recursive(prop_schema, path + [prop_name], effective_logger)
|
||||
if result:
|
||||
return result
|
||||
|
||||
if schema_type == 'array' and 'items' in schema and isinstance(schema['items'], dict):
|
||||
result = util_find_ranged_field_recursive(schema['items'], path + [0], effective_logger)
|
||||
if result:
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
def util_extract_enum_from_description(description: str, logger: logging.Logger) -> Optional[List[str]]:
|
||||
"""
|
||||
从描述字符串中提取枚举值。
|
||||
例如: "排序字段,key=字段名,value=排序方式(可选值为:ASC、DESC)" -> ["ASC", "DESC"]
|
||||
"""
|
||||
if not isinstance(description, str):
|
||||
return None
|
||||
|
||||
# 正则表达式匹配 "可选值为" 后面的内容,支持中文冒号和英文冒号,以及括号
|
||||
# 匹配直到行尾或遇到另一个右括号
|
||||
patterns = [
|
||||
r"可选值为[:|:]([\w\s,、,]+)",
|
||||
r"可选值 *[::] *([\w\s,、,]+)",
|
||||
r"\(可选值为[:|:](.*?)\)",
|
||||
r"(可选值为[:|:](.*?)\)"
|
||||
]
|
||||
|
||||
found_values = None
|
||||
for pattern in patterns:
|
||||
match = re.search(pattern, description)
|
||||
if match:
|
||||
found_values = match.group(1).strip()
|
||||
break
|
||||
|
||||
if not found_values:
|
||||
return None
|
||||
|
||||
# 按逗号、顿号、空格分割,并去除每个值前后的空白
|
||||
enums = [item.strip() for item in re.split(r'[,\s、,]+', found_values) if item.strip()]
|
||||
|
||||
if enums:
|
||||
logger.debug(f"Extracted enums: {enums} from description: '{description[:50]}...'")
|
||||
return enums
|
||||
|
||||
return None
|
||||
|
||||
def util_find_enum_field_recursive(schema: Dict[str, Any], path: List[Union[str, int]], logger: logging.Logger) -> Optional[Tuple[List[Union[str, int]], List[str], str]]:
|
||||
"""
|
||||
递归查找第一个包含可解析枚举值的字段。
|
||||
"""
|
||||
if not isinstance(schema, dict):
|
||||
return None
|
||||
|
||||
# 优先检查当前层级的 description
|
||||
description = schema.get("description", "")
|
||||
enums = util_extract_enum_from_description(description, logger)
|
||||
if enums:
|
||||
return path, enums, description
|
||||
|
||||
# 如果当前层级是对象,则递归其属性
|
||||
if schema.get("type") == "object" and "properties" in schema:
|
||||
for prop_name, prop_schema in schema.get("properties", {}).items():
|
||||
new_path = path + [prop_name]
|
||||
result = util_find_enum_field_recursive(prop_schema, new_path, logger)
|
||||
if result:
|
||||
return result
|
||||
|
||||
# 如果当前层级是数组,则递归其 items
|
||||
if schema.get("type") == "array" and "items" in schema:
|
||||
# 假设我们只检查数组项的结构,路径中用 '[]' 或 index 0 表示
|
||||
new_path = path + [0]
|
||||
result = util_find_enum_field_recursive(schema["items"], new_path, logger)
|
||||
if result:
|
||||
return result
|
||||
|
||||
return None
|
||||
|
||||
def normalize_yapi_responses(responses: dict, method: str) -> dict:
|
||||
"""
|
||||
将YAPI格式的响应规范标准化为OpenAPI格式。
|
||||
如果responses为空或不包含任何成功状态码(2XX),则根据请求方法添加默认状态码。
|
||||
|
||||
Args:
|
||||
responses: YAPI或已解析的响应规范字典
|
||||
method: HTTP请求方法 (GET, POST, PUT, DELETE等)
|
||||
|
||||
Returns:
|
||||
标准化后的响应规范字典
|
||||
"""
|
||||
if not responses:
|
||||
responses = {}
|
||||
|
||||
# 检查是否已经包含成功状态码
|
||||
has_success_code = False
|
||||
for code in responses.keys():
|
||||
if code.startswith('2') or code == 'default':
|
||||
has_success_code = True
|
||||
break
|
||||
|
||||
# 如果没有成功状态码,根据请求方法添加默认状态码
|
||||
if not has_success_code:
|
||||
if method.upper() == 'POST':
|
||||
# POST请求默认使用201 Created
|
||||
default_code = '201'
|
||||
default_desc = 'Created'
|
||||
elif method.upper() == 'DELETE':
|
||||
# DELETE请求默认使用204 No Content
|
||||
default_code = '204'
|
||||
default_desc = 'No Content'
|
||||
else:
|
||||
# GET, PUT, PATCH等默认使用200 OK
|
||||
default_code = '200'
|
||||
default_desc = 'OK'
|
||||
|
||||
# 从responses中查找default响应,如果存在则复制其内容
|
||||
default_response = responses.get('default', {})
|
||||
if not default_response:
|
||||
default_response = {'description': default_desc}
|
||||
elif 'description' not in default_response:
|
||||
default_response['description'] = default_desc
|
||||
|
||||
responses[default_code] = default_response
|
||||
|
||||
return responses
|
||||
Reference in New Issue
Block a user