This commit is contained in:
gongwenxin
2025-06-16 14:49:49 +08:00
parent adc1a0053f
commit df90a5377f
210 changed files with 323584 additions and 12804 deletions
+38 -1
View File
@@ -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路径模板。
+202 -1
View File
@@ -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