Skip to content

Conversation

@lss233
Copy link
Owner

@lss233 lss233 commented May 4, 2025

…ution capabilities

  • Added a new Python Language Server implementation to support LSP features such as code completion, hover information, and signature help.
  • Implemented various diagnostic checkers including import validation, syntax error checking using Jedi, and mandatory function checks.
  • Enhanced the API with a WebSocket endpoint for LSP communication, allowing real-time code analysis and feedback.
  • Updated dependencies in pyproject.toml to include necessary libraries for LSP functionality.
  • Introduced a new CodeBlock class to execute user-defined code dynamically within the workflow.

This commit significantly enhances the development experience by providing real-time feedback and code analysis capabilities.

好的,这是将拉取请求摘要翻译成中文的结果:

Sourcery 总结

将一个全面的 Python 语言服务器与高级代码分析和执行功能集成到工作流系统中

新功能:

  • 添加了 Python 语言服务器实现,支持 LSP 功能,如代码补全、悬停信息和签名帮助
  • 实现了用于在工作流中进行动态代码执行的新的 CodeBlock
  • 引入了 WebSocket 端点,用于实时代码分析和 LSP 通信

增强功能:

  • 增强了类型兼容性系统,以支持双向 Any 类型
  • 添加了多个诊断检查器,用于代码质量和语法验证

文档:

  • 更新了 pyproject.toml 中的项目依赖项,以包含 LSP 和诊断库

测试:

  • 实现了用于导入验证的诊断检查器
  • 添加了使用 Jedi 的语法错误检查
  • 创建了强制性的函数签名验证
Original summary in English

Summary by Sourcery

Integrate a comprehensive Python Language Server with advanced code analysis and execution capabilities into the workflow system

New Features:

  • Added Python Language Server implementation supporting LSP features like code completion, hover information, and signature help
  • Implemented a new CodeBlock for dynamic code execution within workflows
  • Introduced WebSocket endpoint for real-time code analysis and LSP communication

Enhancements:

  • Enhanced type compatibility system to support Any type in both directions
  • Added multiple diagnostic checkers for code quality and syntax validation

Documentation:

  • Updated project dependencies in pyproject.toml to include LSP and diagnostic libraries

Tests:

  • Implemented diagnostic checkers for import validation
  • Added syntax error checking using Jedi
  • Created mandatory function signature validation

…ution capabilities

- Added a new Python Language Server implementation to support LSP features such as code completion, hover information, and signature help.
- Implemented various diagnostic checkers including import validation, syntax error checking using Jedi, and mandatory function checks.
- Enhanced the API with a WebSocket endpoint for LSP communication, allowing real-time code analysis and feedback.
- Updated dependencies in `pyproject.toml` to include necessary libraries for LSP functionality.
- Introduced a new `CodeBlock` class to execute user-defined code dynamically within the workflow.

This commit significantly enhances the development experience by providing real-time feedback and code analysis capabilities.
@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented May 4, 2025

## 审查者指南

此拉取请求集成了使用 `pygls` 和 `jedi` 的 Python 语言服务器 (LSP),并通过新的 WebSocket 端点 (`/code/lsp`) 和自定义的 Quart 传输公开它。它引入了几个基于 `BaseDiagnostic` 类的诊断检查器(语法、导入、pyflakes、强制函数),这些检查器使用 `ast`、`importlib`、`jedi` 和 `pyflakes` 等工具分析代码,报告问题并通过 LSP 提供快速修复。此外,还添加了一个新的 `CodeBlock` 工作流组件,它使用 `exec()` 动态定义和执行包含 `execute` 函数的用户提供的 Python 代码。

#### LSP 和 CodeBlock 组件的类图

```mermaid
classDiagram
    direction LR

    class Block {
        <<Abstract>>
        +name: str
        +inputs: dict
        +outputs: dict
        +execute() Any
    }

    class CodeBlock {
        +name: str = "code_block"
        +inputs: dict
        +outputs: dict
        +code: str
        +execute(**kwargs) Dict[str, Any]
    }
    Block <|-- CodeBlock
    note for CodeBlock "Executes user-provided Python code using exec()."

    class LanguageServer {
        <<pygls>>
        +feature()
        +thread()
        +publish_diagnostics()
        +show_message()
        +workspace
    }

    class PythonLanguageServer {
        -diagnostic_checkers: List~BaseDiagnostic~
        -mandatory_function_checker: Optional~MandatoryFunctionDiagnostic~
        +configure_mandatory_function_checker(config)
        +_setup_handlers()
        +_get_script(params) Optional~jedi.Script~
        +_get_completions(params) CompletionList
        +_get_hover(params) Optional~Hover~
        +_get_signature_help(params) Optional~SignatureHelp~
        +_publish_diagnostics(ls, doc_uri)
        +_get_code_actions(params) Optional~List~CodeAction~~
    }
    LanguageServer <|-- PythonLanguageServer
    PythonLanguageServer o-- "many" BaseDiagnostic : uses >
    note for PythonLanguageServer "Handles LSP requests using Jedi and diagnostic checkers."

    class asyncio.Transport {
        <<Interface>>
        +write(data)
        +close()
    }

    class QuartWsTransport {
        -queue: asyncio.Queue
        +write(message: str)
        +close()
    }
    asyncio.Transport <|-- QuartWsTransport
    PythonLanguageServer o-- QuartWsTransport : uses >

    class BaseDiagnostic {
        <<ABC>>
        +SOURCE_NAME: str
        +ls: LanguageServer
        +check(doc: Document) List~Diagnostic~
        +get_code_actions(params, diagnostics) List~CodeAction~
        +_create_diagnostic() Diagnostic
    }
    note for BaseDiagnostic "Abstract base for all diagnostic checkers."

    class ImportDiagnostic {
        +SOURCE_NAME: str = "import-check"
        +check(doc: Document) List~Diagnostic~
        +get_code_actions(params, diagnostics) List~CodeAction~
    }
    BaseDiagnostic <|-- ImportDiagnostic
    ImportDiagnostic ..> ast : uses
    ImportDiagnostic ..> importlib.util : uses

    class JediSyntaxErrorDiagnostic {
        +SOURCE_NAME: str = "syntax-error"
        +check(doc: Document) List~Diagnostic~
    }
    BaseDiagnostic <|-- JediSyntaxErrorDiagnostic
    JediSyntaxErrorDiagnostic ..> jedi : uses

    class MandatoryFunctionDiagnostic {
        +SOURCE_NAME: str = "mandatory-function-check"
        -config: dict
        +update_config(config)
        +check(doc: Document) List~Diagnostic~
        +get_code_actions(params, diagnostics) List~CodeAction~
    }
    BaseDiagnostic <|-- MandatoryFunctionDiagnostic
    MandatoryFunctionDiagnostic ..> ast : uses

    class PyflakesDiagnostic {
        +SOURCE_NAME: str = "pyflakes"
        +check(doc: Document) List~Diagnostic~
        +get_code_actions(params, diagnostics) List~CodeAction~
    }
    BaseDiagnostic <|-- PyflakesDiagnostic
    PyflakesDiagnostic ..> pyflakes_api : uses

文件级别更改

变更 详情 文件
实现了 Python 语言服务器和 WebSocket 端点。
  • 添加了一个新的 WebSocket 路由 /code/lsp 来处理 LSP 连接。
  • 使用 pygls 创建了 PythonLanguageServer 来管理 LSP 功能。
  • 使用 jedi 库实现了 LSP 功能(完成、悬停、签名帮助、定义、符号)。
  • 创建了 QuartWsTransport 以将 pygls 与 Quart WebSockets 连接起来。
kirara_ai/web/api/block/routes.py
kirara_ai/web/api/block/python_lsp.py
添加了与 LSP 集成的多个诊断检查器。
  • 为检查器创建了一个 BaseDiagnostic 抽象类。
  • 实现了用于语法错误(使用 jedi)、导入验证(使用 importlib)、静态分析(使用 pyflakes)和强制函数定义(使用 ast)的检查器。
  • 将检查器集成到 LSP 中以在文档更改时发布诊断信息。
  • 为某些诊断添加了代码操作支持(快速修复),例如删除未使用的导入。
kirara_ai/web/api/block/python_lsp.py
kirara_ai/web/api/block/diagnostics/base_diagnostic.py
kirara_ai/web/api/block/diagnostics/jedi_syntax_check.py
kirara_ai/web/api/block/diagnostics/import_check.py
kirara_ai/web/api/block/diagnostics/pyflakes_check.py
kirara_ai/web/api/block/diagnostics/mandatory_function.py
引入了 CodeBlock,用于在工作流中动态执行 Python 代码。
  • 创建了从 Block 继承的 CodeBlock 类。
  • 使用 exec() 定义和运行用户提供的代码,期望有一个 execute 函数。
  • 根据参数动态配置块的输入/输出。
  • 注册了新的 CodeBlock
kirara_ai/workflow/implementations/blocks/system/basic.py
kirara_ai/workflow/implementations/blocks/system_blocks.py
更新了依赖项和类型系统。
  • python-lsp-serverjedipyflakes 添加到项目依赖项。
  • 修改了类型兼容性检查以允许与 Any 类型匹配。
pyproject.toml
kirara_ai/workflow/core/block/type_system.py

提示和命令

与 Sourcery 互动

  • 触发新的审查: 在拉取请求上评论 @sourcery-ai review
  • 继续讨论: 直接回复 Sourcery 的审查评论。
  • 从审查评论生成 GitHub issue: 通过回复审查评论,要求 Sourcery 从审查评论创建一个 issue。您也可以回复审查评论并使用 @sourcery-ai issue 从中创建一个 issue。
  • 生成拉取请求标题: 在拉取请求标题中的任何位置写入 @sourcery-ai 以随时生成标题。您也可以在拉取请求上评论 @sourcery-ai title 以随时(重新)生成标题。
  • 生成拉取请求摘要: 在拉取请求正文中的任何位置写入 @sourcery-ai summary 以随时在您想要的位置生成 PR 摘要。您也可以在拉取请求上评论 @sourcery-ai summary 以随时(重新)生成摘要。
  • 生成审查者指南: 在拉取请求上评论 @sourcery-ai guide 以随时(重新)生成审查者指南。
  • 解决所有 Sourcery 评论: 在拉取请求上评论 @sourcery-ai resolve 以解决所有 Sourcery 评论。如果您已经解决了所有评论并且不想再看到它们,这将非常有用。
  • 驳回所有 Sourcery 审查: 在拉取请求上评论 @sourcery-ai dismiss 以驳回所有现有的 Sourcery 审查。如果您想从新的审查开始,这将特别有用 - 不要忘记评论 @sourcery-ai review 以触发新的审查!

自定义您的体验

访问您的 仪表板 以:

  • 启用或禁用审查功能,例如 Sourcery 生成的拉取请求摘要、审查者指南等。
  • 更改审查语言。
  • 添加、删除或编辑自定义审查说明。
  • 调整其他审查设置。

获取帮助

```
Original review guide in English

Reviewer's Guide

This pull request integrates a Python Language Server (LSP) using pygls and jedi, exposing it via a new WebSocket endpoint (/code/lsp) with a custom Quart transport. It introduces several diagnostic checkers (syntax, imports, pyflakes, mandatory function) built upon a BaseDiagnostic class, which analyze code using tools like ast, importlib, jedi, and pyflakes, reporting issues and offering quick fixes via LSP. Additionally, a new CodeBlock workflow component was added, which uses exec() to dynamically define and execute user-provided Python code containing an execute function.

Class Diagram for LSP and CodeBlock Components

classDiagram
    direction LR

    class Block {
        <<Abstract>>
        +name: str
        +inputs: dict
        +outputs: dict
        +execute() Any
    }

    class CodeBlock {
        +name: str = "code_block"
        +inputs: dict
        +outputs: dict
        +code: str
        +execute(**kwargs) Dict[str, Any]
    }
    Block <|-- CodeBlock
    note for CodeBlock "Executes user-provided Python code using exec()."

    class LanguageServer {
        <<pygls>>
        +feature()
        +thread()
        +publish_diagnostics()
        +show_message()
        +workspace
    }

    class PythonLanguageServer {
        -diagnostic_checkers: List~BaseDiagnostic~
        -mandatory_function_checker: Optional~MandatoryFunctionDiagnostic~
        +configure_mandatory_function_checker(config)
        +_setup_handlers()
        +_get_script(params) Optional~jedi.Script~
        +_get_completions(params) CompletionList
        +_get_hover(params) Optional~Hover~
        +_get_signature_help(params) Optional~SignatureHelp~
        +_publish_diagnostics(ls, doc_uri)
        +_get_code_actions(params) Optional~List~CodeAction~~
    }
    LanguageServer <|-- PythonLanguageServer
    PythonLanguageServer o-- "many" BaseDiagnostic : uses >
    note for PythonLanguageServer "Handles LSP requests using Jedi and diagnostic checkers."

    class asyncio.Transport {
        <<Interface>>
        +write(data)
        +close()
    }

    class QuartWsTransport {
        -queue: asyncio.Queue
        +write(message: str)
        +close()
    }
    asyncio.Transport <|-- QuartWsTransport
    PythonLanguageServer o-- QuartWsTransport : uses >

    class BaseDiagnostic {
        <<ABC>>
        +SOURCE_NAME: str
        +ls: LanguageServer
        +check(doc: Document) List~Diagnostic~
        +get_code_actions(params, diagnostics) List~CodeAction~
        +_create_diagnostic() Diagnostic
    }
    note for BaseDiagnostic "Abstract base for all diagnostic checkers."

    class ImportDiagnostic {
        +SOURCE_NAME: str = "import-check"
        +check(doc: Document) List~Diagnostic~
        +get_code_actions(params, diagnostics) List~CodeAction~
    }
    BaseDiagnostic <|-- ImportDiagnostic
    ImportDiagnostic ..> ast : uses
    ImportDiagnostic ..> importlib.util : uses

    class JediSyntaxErrorDiagnostic {
        +SOURCE_NAME: str = "syntax-error"
        +check(doc: Document) List~Diagnostic~
    }
    BaseDiagnostic <|-- JediSyntaxErrorDiagnostic
    JediSyntaxErrorDiagnostic ..> jedi : uses

    class MandatoryFunctionDiagnostic {
        +SOURCE_NAME: str = "mandatory-function-check"
        -config: dict
        +update_config(config)
        +check(doc: Document) List~Diagnostic~
        +get_code_actions(params, diagnostics) List~CodeAction~
    }
    BaseDiagnostic <|-- MandatoryFunctionDiagnostic
    MandatoryFunctionDiagnostic ..> ast : uses

    class PyflakesDiagnostic {
        +SOURCE_NAME: str = "pyflakes"
        +check(doc: Document) List~Diagnostic~
        +get_code_actions(params, diagnostics) List~CodeAction~
    }
    BaseDiagnostic <|-- PyflakesDiagnostic
    PyflakesDiagnostic ..> pyflakes_api : uses
Loading

File-Level Changes

Change Details Files
Implemented a Python Language Server and WebSocket endpoint.
  • Added a new WebSocket route /code/lsp to handle LSP connections.
  • Created PythonLanguageServer using pygls to manage LSP features.
  • Implemented LSP features (completion, hover, signature help, definition, symbols) using the jedi library.
  • Created QuartWsTransport to bridge pygls with Quart WebSockets.
kirara_ai/web/api/block/routes.py
kirara_ai/web/api/block/python_lsp.py
Added multiple diagnostic checkers integrated with the LSP.
  • Created a BaseDiagnostic abstract class for checkers.
  • Implemented checkers for syntax errors (using jedi), import validation (using importlib), static analysis (using pyflakes), and mandatory function definition (using ast).
  • Integrated checkers into the LSP to publish diagnostics on document changes.
  • Added code action support (quick fixes) for some diagnostics (e.g., remove unused import).
kirara_ai/web/api/block/python_lsp.py
kirara_ai/web/api/block/diagnostics/base_diagnostic.py
kirara_ai/web/api/block/diagnostics/jedi_syntax_check.py
kirara_ai/web/api/block/diagnostics/import_check.py
kirara_ai/web/api/block/diagnostics/pyflakes_check.py
kirara_ai/web/api/block/diagnostics/mandatory_function.py
Introduced a CodeBlock for dynamic Python code execution in workflows.
  • Created CodeBlock class inheriting from Block.
  • Used exec() to define and run user-provided code expecting an execute function.
  • Dynamically configured block inputs/outputs based on parameters.
  • Registered the new CodeBlock.
kirara_ai/workflow/implementations/blocks/system/basic.py
kirara_ai/workflow/implementations/blocks/system_blocks.py
Updated dependencies and type system.
  • Added python-lsp-server, jedi, and pyflakes to project dependencies.
  • Modified type compatibility check to allow matching with Any type.
pyproject.toml
kirara_ai/workflow/core/block/type_system.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@lss233 lss233 changed the title feat: integrate Python Language Server with diagnostics and code exec… feat: 支持代码执行 Block May 4, 2025
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型检查结果 ❌

在 PR 修改的代码行中发现了 6 个类型问题,需要修复。

已对修改的代码行创建了 6 个行级评论。

"""
try:
self.config = config
self.config_name = self.config["name"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Value of type "None" is not indexable (index)

详细信息请参考 mypy 文档

try:
self.config = config
self.config_name = self.config["name"]
self.config_params = self.config["params"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Value of type "None" is not indexable (index)

详细信息请参考 mypy 文档

self.config = config
self.config_name = self.config["name"]
self.config_params = self.config["params"]
self.config_return = self.config["return_type"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Value of type "None" is not indexable (index)

详细信息请参考 mypy 文档

self.config_params = self.config["params"]
self.config_return = self.config["return_type"]
self.param_signatures = [
f"{p['name']}: {p['type_hint']}" for p in self.config_params]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: "None" has no attribute "iter" (not iterable) (attr-defined)

详细信息请参考 mypy 文档

self.config_return = self.config["return_type"]
self.param_signatures = [
f"{p['name']}: {p['type_hint']}" for p in self.config_params]
self.expected_signature_str = f"def {self.config_name}({', '.join(self.param_signatures)}) -> {self.config_return}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Argument 1 to "join" of "str" has incompatible type "None"; expected "Iterable[str]" (arg-type)

详细信息请参考 mypy 文档


def get_code_actions(self, params: CodeActionParams, relevant_diagnostics: List[Diagnostic]) -> List[CodeAction]:
"""为强制函数错误提供代码操作"""
actions = []
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Need type annotation for "actions" (hint: "actions: list[] = ...") (var-annotated)

详细信息请参考 mypy 文档

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lss233 - 我已经查看了你的更改 - 这里有一些反馈:

  • CodeBlock 在用户提供的代码上使用 exec();考虑添加沙盒或探索更安全的执行替代方案,以减轻安全风险。
  • 新的 python_lsp.py 文件非常大;考虑是否可以将与特定功能(例如,完成、悬停、不同的诊断)相关的部分进一步模块化。
  • 审查 WebSocket LSP 处理程序的错误恢复逻辑,以解决客户端-服务器通信中潜在的边缘情况。
这是我在审查期间查看的内容
  • 🟢 一般问题:一切看起来都不错
  • 🟢 安全性:一切看起来都不错
  • 🟢 测试:一切看起来都不错
  • 🟡 复杂性:发现 2 个问题
  • 🟢 文档:一切看起来都不错

Sourcery 对开源是免费的 - 如果你喜欢我们的评论,请考虑分享它们 ✨
帮助我更有用!请点击每个评论上的 👍 或 👎,我将使用反馈来改进你的评论。
Original comment in English

Hey @lss233 - I've reviewed your changes - here's some feedback:

  • The CodeBlock uses exec() on user-provided code; consider adding sandboxing or exploring safer execution alternatives to mitigate security risks.
  • The new python_lsp.py file is quite large; consider if parts related to specific features (e.g., completions, hover, different diagnostics) could be further modularized.
  • Review the WebSocket LSP handler's error recovery logic for potential edge cases in client-server communication.
Here's what I looked at during the review
  • 🟢 General issues: all looks good
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 2 issues found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

registry: BlockRegistry = g.container.resolve(BlockRegistry)
return jsonify(registry.get_type_compatibility_map())

@block_bp.websocket("/code/lsp")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): 考虑将发送者和接收者函数提取到单独的辅助函数中,以减少嵌套。

嵌套的异步发送者/接收者函数增加了复杂性。考虑将它们提取到单独的辅助函数中。例如:

# 在单独的模块中或在模块级别
async def lsp_sender(websocket, queue, logger):
    while True:
        message = await queue.get()
        if message is None:
            break
        await websocket.send(message)

async def lsp_receiver(websocket, lsp_server, logger):
    while True:
        message_str = await websocket.receive()
        try:
            parsed_message = json.loads(
                message_str,
                object_hook=lsp_server.lsp._deserialize_message,
            )
            lsp_server.lsp._procedure_handler(parsed_message)
        except json.JSONDecodeError:
            logger.error(
                f"无法解析收到的 LSP 消息: {message_str}",
                exc_info=True,
            )
        except Exception as e:
            logger.error(f"处理 LSP 消息时出错: {e}", exc_info=True)

然后相应地更新路由处理程序:

@block_bp.websocket("/code/lsp")
async def code_lsp():
    lsp_server = PythonLanguageServer(loop=asyncio.get_event_loop())
    logger = get_logger("Web.Block.LSP")
    queue = asyncio.Queue()
    transport = QuartWsTransport(queue)

    lsp_server.lsp.connection_made(transport)
    lsp_server.lsp._send_only_body = True
    logger.info("LSP WebSocket 连接已建立")

    receive_task = asyncio.create_task(lsp_receiver(websocket, lsp_server, logger))
    send_task = asyncio.create_task(lsp_sender(websocket, queue, logger))
    logger.debug("已创建 LSP WebSocket 发送者和接收者任务")

    try:
        await asyncio.gather(receive_task, send_task)
    except asyncio.CancelledError:
        logger.info("LSP WebSocket 任务已取消")
    except Exception as e:
        logger.error(f"LSP WebSocket 连接错误: {e}", exc_info=True)
    finally:
        send_task.cancel()
        try:
            await send_task
        except asyncio.CancelledError:
            logger.debug("LSP WebSocket 发送者任务已取消")
        receive_task.cancel()
        try:
            await receive_task
        except asyncio.CancelledError:
            logger.debug("LSP WebSocket 接收者任务已取消")
        loop = asyncio.get_event_loop()
        loop.run_in_executor(None, lsp_server.shutdown)
        logger.info("websocket 连接已关闭")

此重构保持了功能,同时减少了嵌套并提高了可测试性。

Original comment in English

issue (complexity): Consider extracting the sender and receiver functions into separate helper functions to reduce nesting.

The nested async sender/receiver functions add complexity. Consider extracting them into separate helper functions. For example:

# In a separate module or at the module level
async def lsp_sender(websocket, queue, logger):
    while True:
        message = await queue.get()
        if message is None:
            break
        await websocket.send(message)

async def lsp_receiver(websocket, lsp_server, logger):
    while True:
        message_str = await websocket.receive()
        try:
            parsed_message = json.loads(
                message_str,
                object_hook=lsp_server.lsp._deserialize_message,
            )
            lsp_server.lsp._procedure_handler(parsed_message)
        except json.JSONDecodeError:
            logger.error(
                f"Unable to parse received LSP message: {message_str}",
                exc_info=True,
            )
        except Exception as e:
            logger.error(f"Error processing LSP message: {e}", exc_info=True)

Then update the route handler accordingly:

@block_bp.websocket("/code/lsp")
async def code_lsp():
    lsp_server = PythonLanguageServer(loop=asyncio.get_event_loop())
    logger = get_logger("Web.Block.LSP")
    queue = asyncio.Queue()
    transport = QuartWsTransport(queue)

    lsp_server.lsp.connection_made(transport)
    lsp_server.lsp._send_only_body = True
    logger.info("LSP WebSocket connection established")

    receive_task = asyncio.create_task(lsp_receiver(websocket, lsp_server, logger))
    send_task = asyncio.create_task(lsp_sender(websocket, queue, logger))
    logger.debug("Created LSP WebSocket sender and receiver tasks")

    try:
        await asyncio.gather(receive_task, send_task)
    except asyncio.CancelledError:
        logger.info("LSP WebSocket task cancelled")
    except Exception as e:
        logger.error(f"LSP WebSocket connection error: {e}", exc_info=True)
    finally:
        send_task.cancel()
        try:
            await send_task
        except asyncio.CancelledError:
            logger.debug("LSP WebSocket sender task cancelled")
        receive_task.cancel()
        try:
            await receive_task
        except asyncio.CancelledError:
            logger.debug("LSP WebSocket receiver task cancelled")
        loop = asyncio.get_event_loop()
        loop.run_in_executor(None, lsp_server.shutdown)
        logger.info("websocket connection closed")

This refactor maintains functionality while reducing nesting and improving testability.

self.inputs[input_spec["name"]] = Input(input_spec["name"], input_spec["label"], Any, 'user-specified object') # type: ignore
for output_spec in outputs:
self.outputs[output_spec["name"]] = Output(output_spec["name"], output_spec["label"], Any, 'user-specified object') # type: ignore
self.code = code
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): 考虑将两个 exec 调用重构为辅助方法,以隔离错误处理并简化测试。

考虑将两个单独的 exec 调用重构为辅助方法。这样,您可以隔离错误处理并简化测试。例如,您可以将定义执行和函数调用提取到如下方法中:

def _execute_definition(self, code: str, exec_globals: Dict[str, Any], exec_locals: Dict[str, Any]) -> None:
    try:
        exec(code, exec_globals, exec_locals)
    except Exception as e:
        logger.error(f"代码定义执行期间出错: {e}", exc_info=True)
        raise RuntimeError(f"提供的代码定义中出错: {e}") from e

def _call_user_function(self, exec_globals: Dict[str, Any], exec_locals: Dict[str, Any], kwargs: Dict[str, Any]) -> None:
    exec_locals['__input_kwargs__'] = kwargs
    exec_globals.update(exec_locals)
    call_code = "__result__ = execute(**__input_kwargs__)"
    try:
        exec(call_code, exec_globals, exec_locals)
    except Exception as e:
        logger.error(f"用户函数 'execute' 执行期间出错: {e}", exc_info=True)
        raise RuntimeError(f"执行用户函数 'execute' 期间出错: {e}") from e

然后,在您的主 execute 方法中,您可以调用这些辅助方法:

def execute(self, **kwargs: Any) -> Dict[str, Any]:
    logger = get_logger("Block.Code")
    exec_globals = globals().copy()
    exec_locals: Dict[str, Any] = {}

    logger.debug(f"正在执行代码定义:\n{self.code}")
    self._execute_definition(self.code, exec_globals, exec_locals)

    if 'execute' not in exec_locals or not callable(exec_locals['execute']):
        raise ValueError("提供的代码必须定义一个名为 'execute' 的可调用函数")

    logger.debug(f"正在执行函数调用: execute(**{list(kwargs.keys())})")
    self._call_user_function(exec_globals, exec_locals, kwargs)

    if '__result__' not in exec_locals:
        logger.error("内部错误:执行用户代码调用后未找到结果 '__result__'。")
        raise RuntimeError("无法从用户代码执行中检索结果。")

    return exec_locals['__result__']

此重构减少了 execute 中的嵌套,同时保持了功能完整。

Original comment in English

issue (complexity): Consider refactoring the two exec calls into helper methods to isolate error handling and simplify testing.

Consider refactoring the two separate exec calls into helper methods. This way you can isolate error handling and simplify testing. For example, you could extract the definition execution and the function call into methods like:

def _execute_definition(self, code: str, exec_globals: Dict[str, Any], exec_locals: Dict[str, Any]) -> None:
    try:
        exec(code, exec_globals, exec_locals)
    except Exception as e:
        logger.error(f"Error during code definition execution: {e}", exc_info=True)
        raise RuntimeError(f"Error in provided code definition: {e}") from e

def _call_user_function(self, exec_globals: Dict[str, Any], exec_locals: Dict[str, Any], kwargs: Dict[str, Any]) -> None:
    exec_locals['__input_kwargs__'] = kwargs
    exec_globals.update(exec_locals)
    call_code = "__result__ = execute(**__input_kwargs__)"
    try:
        exec(call_code, exec_globals, exec_locals)
    except Exception as e:
        logger.error(f"Error during user function 'execute' execution: {e}", exc_info=True)
        raise RuntimeError(f"Error during execution of user function 'execute': {e}") from e

Then, in your main execute method, you can call these helpers:

def execute(self, **kwargs: Any) -> Dict[str, Any]:
    logger = get_logger("Block.Code")
    exec_globals = globals().copy()
    exec_locals: Dict[str, Any] = {}

    logger.debug(f"Executing code definition:\n{self.code}")
    self._execute_definition(self.code, exec_globals, exec_locals)

    if 'execute' not in exec_locals or not callable(exec_locals['execute']):
        raise ValueError("Provided code must define a callable function named 'execute'")

    logger.debug(f"Executing function call: execute(**{list(kwargs.keys())})")
    self._call_user_function(exec_globals, exec_locals, kwargs)

    if '__result__' not in exec_locals:
        logger.error("Internal error: Result '__result__' not found after executing user code call.")
        raise RuntimeError("Failed to retrieve result from user code execution.")

    return exec_locals['__result__']

This refactoring reduces nesting within execute while keeping functionality intact.


try:
position = params.position
line = position.line + 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): 使用命名表达式简化赋值和条件 [×2] (use-named-expression)

Original comment in English

issue (code-quality): Use named expression to simplify assignment and conditional [×2] (use-named-expression)


try:
position = params.position
line = position.line + 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): 我们发现了这些问题:

Original comment in English

issue (code-quality): We've found these issues:

Comment on lines +476 to +477
checker_diagnostics = checker.check(document)
if checker_diagnostics:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): 使用命名表达式简化赋值和条件 (use-named-expression)

Suggested change
checker_diagnostics = checker.check(document)
if checker_diagnostics:
if checker_diagnostics := checker.check(document):
Original comment in English

suggestion (code-quality): Use named expression to simplify assignment and conditional (use-named-expression)

Suggested change
checker_diagnostics = checker.check(document)
if checker_diagnostics:
if checker_diagnostics := checker.check(document):

Comment on lines +511 to +520
relevant_diagnostics = diagnostics_by_source.get(checker_name, [])
if relevant_diagnostics:
try:
checker_actions = checker.get_code_actions(
params, relevant_diagnostics)
if checker_actions:
actions.extend(checker_actions)
except Exception as e:
logger.error(
f"Code action checker '{checker_name}' error: {str(e)}", exc_info=True)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): 使用命名表达式简化赋值和条件 [×2] (use-named-expression)

Original comment in English

issue (code-quality): Use named expression to simplify assignment and conditional [×2] (use-named-expression)

self.code = code

def execute(self, **kwargs: Any) -> Dict[str, Any]: # 使用 Any 兼容各种输入类型
logger = get_logger("Block.Code")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): 我们发现了这些问题:

Original comment in English

issue (code-quality): We've found these issues:

Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型检查结果 ❌

在 PR 修改的代码行中发现了 6 个类型问题,需要修复。

已对修改的代码行创建了 6 个行级评论。

"""
try:
self.config = config
self.config_name = self.config["name"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Value of type "None" is not indexable (index)

详细信息请参考 mypy 文档

try:
self.config = config
self.config_name = self.config["name"]
self.config_params = self.config["params"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Value of type "None" is not indexable (index)

详细信息请参考 mypy 文档

self.config = config
self.config_name = self.config["name"]
self.config_params = self.config["params"]
self.config_return = self.config["return_type"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Value of type "None" is not indexable (index)

详细信息请参考 mypy 文档

self.config_params = self.config["params"]
self.config_return = self.config["return_type"]
self.param_signatures = [
f"{p['name']}: {p['type_hint']}" for p in self.config_params]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: "None" has no attribute "iter" (not iterable) (attr-defined)

详细信息请参考 mypy 文档

self.config_return = self.config["return_type"]
self.param_signatures = [
f"{p['name']}: {p['type_hint']}" for p in self.config_params]
self.expected_signature_str = f"def {self.config_name}({', '.join(self.param_signatures)}) -> {self.config_return}"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Argument 1 to "join" of "str" has incompatible type "None"; expected "Iterable[str]" (arg-type)

详细信息请参考 mypy 文档


def get_code_actions(self, params: CodeActionParams, relevant_diagnostics: List[Diagnostic]) -> List[CodeAction]:
"""为强制函数错误提供代码操作"""
actions = []
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MyPy 类型错误: Need type annotation for "actions" (hint: "actions: list[] = ...") (var-annotated)

详细信息请参考 mypy 文档

@lss233 lss233 enabled auto-merge May 4, 2025 21:29
… class

- Replaced `python-lsp-server` with `pygls` in `pyproject.toml` to streamline language server integration.
- Enhanced the `MandatoryFunctionDiagnostic` class by adding optional configuration attributes for improved flexibility and error handling during function declaration checks.
@github-actions
Copy link

github-actions bot commented May 4, 2025

MyPy 类型检查通过 ✅

PR 修改的代码行通过了类型检查。

@codecov
Copy link

codecov bot commented May 4, 2025

Codecov Report

Attention: Patch coverage is 14.55556% with 769 lines in your changes missing coverage. Please review.

Project coverage is 60.72%. Comparing base (55e8837) to head (7c62ddc).
Report is 6 commits behind head on master.

✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
kirara_ai/web/api/block/python_lsp.py 10.63% 269 Missing ⚠️
...ai/web/api/block/diagnostics/mandatory_function.py 14.09% 128 Missing ⚠️
...irara_ai/web/api/block/diagnostics/import_check.py 10.44% 120 Missing ⚠️
...ara_ai/web/api/block/diagnostics/pyflakes_check.py 18.51% 88 Missing ⚠️
...ra_ai/web/api/block/diagnostics/base_diagnostic.py 20.51% 62 Missing ⚠️
kirara_ai/web/api/block/routes.py 11.76% 45 Missing ⚠️
...ai/workflow/implementations/blocks/system/basic.py 20.00% 32 Missing ⚠️
..._ai/web/api/block/diagnostics/jedi_syntax_check.py 33.33% 24 Missing ⚠️
...i/workflow/implementations/blocks/system_blocks.py 50.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1475      +/-   ##
==========================================
- Coverage   65.84%   60.72%   -5.12%     
==========================================
  Files         161      167       +6     
  Lines        8148     9044     +896     
==========================================
+ Hits         5365     5492     +127     
- Misses       2783     3552     +769     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@lss233 lss233 disabled auto-merge May 5, 2025 16:55
@lss233 lss233 merged commit 8f93dbf into master May 5, 2025
6 of 8 checks passed
@lss233 lss233 deleted the feature/code_block branch May 5, 2025 16:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants