mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 23:47:41 +08:00
feat(utils): Refactor check_method to use ast
- 使用 AST 解析函数源码,相比基于字符串的方法更稳定,能够正确处理具有多行 def 语句的函数 - 为 check_method 添加了单元测试
This commit is contained in:
+29
-29
@@ -1,6 +1,8 @@
|
|||||||
|
import ast
|
||||||
import dis
|
import dis
|
||||||
import inspect
|
import inspect
|
||||||
from types import FunctionType
|
import textwrap
|
||||||
|
from types import FunctionType, MethodType
|
||||||
from typing import Any, Callable, get_type_hints
|
from typing import Any, Callable, get_type_hints
|
||||||
|
|
||||||
|
|
||||||
@@ -39,40 +41,38 @@ class ObjectUtils:
|
|||||||
return len(list(parameters.keys()))
|
return len(list(parameters.keys()))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_method(func: FunctionType) -> bool:
|
def check_method(func: FunctionType | MethodType) -> bool:
|
||||||
"""
|
"""
|
||||||
检查函数是否已实现
|
检查函数是否已实现
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# 尝试通过源代码分析
|
src = inspect.getsource(func)
|
||||||
source = inspect.getsource(func)
|
tree = ast.parse(textwrap.dedent(src))
|
||||||
in_comment = False
|
node = tree.body[0]
|
||||||
for line in source.split('\n'):
|
if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||||
line = line.strip()
|
return True
|
||||||
# 跳过空行
|
body = node.body
|
||||||
if not line:
|
|
||||||
|
for stmt in body:
|
||||||
|
# 跳过 pass
|
||||||
|
if isinstance(stmt, ast.Pass):
|
||||||
continue
|
continue
|
||||||
# 处理"""单行注释
|
# 跳过 docstring 或 ...
|
||||||
if (line.startswith(('"""', "'''"))
|
if isinstance(stmt, ast.Expr):
|
||||||
and line.endswith(('"""', "'''"))
|
expr = stmt.value
|
||||||
and len(line) > 3):
|
if isinstance(expr, ast.Constant) and isinstance(expr.value, str):
|
||||||
continue
|
continue
|
||||||
# 处理"""多行注释
|
if isinstance(expr, ast.Constant) and expr.value is Ellipsis:
|
||||||
if line.startswith(('"""', "'''")):
|
continue
|
||||||
in_comment = not in_comment
|
# 检查 raise NotImplementedError
|
||||||
continue
|
if isinstance(stmt, ast.Raise):
|
||||||
# 在注释中则跳过
|
exc = stmt.exc
|
||||||
if in_comment:
|
if isinstance(exc, ast.Call) and getattr(exc.func, "id", None) == "NotImplementedError":
|
||||||
continue
|
continue
|
||||||
# 跳过#注释、pass语句、装饰器、函数定义行
|
if isinstance(exc, ast.Name) and exc.id == "NotImplementedError":
|
||||||
if (line.startswith('#')
|
continue
|
||||||
or line == "pass"
|
|
||||||
or line.startswith('@')
|
|
||||||
or line.startswith('def ')):
|
|
||||||
continue
|
|
||||||
# 发现有效代码行
|
|
||||||
return True
|
return True
|
||||||
# 没有有效代码行
|
|
||||||
return False
|
return False
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
print(err)
|
print(err)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from tests.test_metainfo import MetaInfoTest
|
from tests.test_metainfo import MetaInfoTest
|
||||||
|
from tests.test_object import ObjectUtilsTest
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
suite = unittest.TestSuite()
|
suite = unittest.TestSuite()
|
||||||
@@ -8,6 +10,7 @@ if __name__ == '__main__':
|
|||||||
# 测试名称识别
|
# 测试名称识别
|
||||||
suite.addTest(MetaInfoTest('test_metainfo'))
|
suite.addTest(MetaInfoTest('test_metainfo'))
|
||||||
suite.addTest(MetaInfoTest('test_emby_format_ids'))
|
suite.addTest(MetaInfoTest('test_emby_format_ids'))
|
||||||
|
suite.addTest(ObjectUtilsTest('test_check_method'))
|
||||||
|
|
||||||
# 运行测试
|
# 运行测试
|
||||||
runner = unittest.TextTestRunner()
|
runner = unittest.TextTestRunner()
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
from unittest import TestCase
|
||||||
|
|
||||||
|
from app.utils.object import ObjectUtils
|
||||||
|
|
||||||
|
|
||||||
|
class ObjectUtilsTest(TestCase):
|
||||||
|
|
||||||
|
def test_check_method(self):
|
||||||
|
def implemented_function():
|
||||||
|
return "Hello"
|
||||||
|
|
||||||
|
def pass_function():
|
||||||
|
pass
|
||||||
|
|
||||||
|
def docstring_function():
|
||||||
|
"""This is a docstring."""
|
||||||
|
|
||||||
|
def ellipsis_function():
|
||||||
|
...
|
||||||
|
|
||||||
|
def not_implemented_function():
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def not_implemented_function_no_call():
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
async def multiple_lines_async_def(_param1: str,
|
||||||
|
_param2: str):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def empty_function():
|
||||||
|
return
|
||||||
|
|
||||||
|
self.assertTrue(ObjectUtils.check_method(implemented_function))
|
||||||
|
self.assertFalse(ObjectUtils.check_method(pass_function))
|
||||||
|
self.assertFalse(ObjectUtils.check_method(docstring_function))
|
||||||
|
self.assertFalse(ObjectUtils.check_method(ellipsis_function))
|
||||||
|
self.assertFalse(ObjectUtils.check_method(not_implemented_function))
|
||||||
|
self.assertFalse(ObjectUtils.check_method(not_implemented_function_no_call))
|
||||||
|
self.assertFalse(ObjectUtils.check_method(multiple_lines_async_def))
|
||||||
|
self.assertTrue(ObjectUtils.check_method(empty_function))
|
||||||
Reference in New Issue
Block a user