Skip to content

refactor: api/services/tools/api_tools_manage_service.py to session#32159

Open
asukaminato0721 wants to merge 21 commits intolanggenius:mainfrom
asukaminato0721:24115-services/tools/api_tools_manage_service.py
Open

refactor: api/services/tools/api_tools_manage_service.py to session#32159
asukaminato0721 wants to merge 21 commits intolanggenius:mainfrom
asukaminato0721:24115-services/tools/api_tools_manage_service.py

Conversation

@asukaminato0721
Copy link
Copy Markdown
Contributor

Important

  1. Make sure you have read our contribution guidelines
  2. Ensure there is an associated issue and you have been assigned to it
  3. Use the correct syntax to link this PR: Fixes #<issue number>.

Summary

part of #24115

Screenshots

Before After
... ...

Checklist

  • This change requires a documentation update, included: Dify Document
  • I understand that this PR may be closed in case there was no previous discussion or issues. (This doesn't apply to typos!)
  • I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
  • I've updated the documentation accordingly.
  • I ran make lint and make type-check (backend) and cd web && npx lint-staged (frontend) to appease the lint gods

Copilot AI review requested due to automatic review settings February 9, 2026 14:26
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. refactor labels Feb 9, 2026
@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @asukaminato0721, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant refactoring to the API tool management service by standardizing its database interaction layer. The core change involves migrating all database operations from direct db.session calls to a more robust and controlled session_factory pattern. This improves the overall architecture by centralizing session management, ensuring transactional integrity, and promoting a cleaner separation of concerns for database access within the service.

Highlights

  • Database Session Management Refactor: The api_tools_manage_service.py file has been refactored to utilize a session_factory for managing database sessions, moving away from direct db.session usage. This change ensures proper session creation, usage, and closure, enhancing reliability and consistency in database interactions.
  • Transaction Control: Database write operations, such as creating, updating, and deleting API tool providers, are now explicitly wrapped within session_factory.create_session() as session, session.begin(): blocks. This ensures that transactions are properly committed upon successful completion or rolled back in case of errors.
  • Consistent Querying: All database read operations within the service, including listing and retrieving API tool providers and their tools, now consistently use sessions obtained from session_factory.create_session().

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • api/services/tools/api_tools_manage_service.py
    • Imported session_factory from core.db.session_factory to enable standardized session management.
    • Modified create_api_tool_provider function to use session_factory.create_session() with a transaction block for atomic database operations.
    • Updated list_api_tool_provider_tools function to retrieve database sessions via session_factory.create_session() for queries.
    • Refactored update_api_tool_provider function to utilize session_factory.create_session() for database updates.
    • Changed delete_api_tool_provider function to employ session_factory.create_session() with a transaction block for safe deletion.
    • Adjusted test_api_tool_preview function to use session_factory.create_session() for fetching provider data.
    • Modified list_api_tools function to use session_factory.create_session() for querying all API providers.
Activity
  • This pull request is part of issue [Chore/Refactor] db.session.xxx -> with Session as session #24115, indicating it addresses a larger task or feature.
  • The author has confirmed reading the contribution guidelines.
  • The author has confirmed that tests have been added for each change introduced.
  • The author has confirmed that documentation has been updated accordingly.
  • The author has run linting and type-checking commands, ensuring code quality standards are met.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request refactors api_tools_manage_service.py to use a session factory for database operations, which is a good step towards better session management. However, the refactoring is incomplete and introduces several issues. Multiple methods use fetched database objects outside the session's scope, which can lead to DetachedInstanceError or inconsistent session usage. One update operation is missing transactional support, which is a critical bug. Furthermore, calls to other services that still rely on the global db.session break transactional atomicity. These issues need to be addressed to ensure the refactoring is robust and correct.

Comment thread api/services/tools/api_tools_manage_service.py Outdated
Comment thread api/services/tools/api_tools_manage_service.py
Comment thread api/services/tools/api_tools_manage_service.py
Comment thread api/services/tools/api_tools_manage_service.py
Comment thread api/services/tools/api_tools_manage_service.py
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Refactors ApiToolManageService to migrate database access from Flask-SQLAlchemy’s db.session to the project’s session_factory (pure SQLAlchemy sessions), aligning with the ongoing session migration effort.

Changes:

  • Replaced db.session usage with session_factory.create_session() across CRUD/query paths.
  • Wrapped create/delete flows in explicit transactional blocks (session.begin()).
  • Updated read-only queries to use short-lived sessions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/services/tools/api_tools_manage_service.py
Comment thread api/services/tools/api_tools_manage_service.py
Comment thread api/services/tools/api_tools_manage_service.py
Comment thread api/services/tools/api_tools_manage_service.py Outdated
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 11, 2026 08:52
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/services/tools/api_tools_manage_service.py
Copilot AI review requested due to automatic review settings February 11, 2026 09:01
@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Feb 11, 2026
@asukaminato0721
Copy link
Copy Markdown
Contributor Author

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully refactors the database session management across api/core/tools/tool_label_manager.py and api/services/tools/api_tools_manage_service.py. By replacing the global db object with session_factory and explicit Session passing, the changes enhance modularity, testability, and better control over database transactions. The implementation correctly utilizes with session_factory.create_session() as session and session.begin() where appropriate, ensuring proper session lifecycle management. This is a positive and well-executed refactoring.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/core/tools/tool_label_manager.py
Comment thread api/core/tools/tool_label_manager.py Outdated
Copilot AI review requested due to automatic review settings February 11, 2026 09:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread api/services/tools/api_tools_manage_service.py
Comment on lines +88 to +93

if labels is not None:
ToolLabelManager.update_tool_labels(
ToolTransformService.workflow_provider_to_controller(workflow_tool_provider), labels, session
)
return {"result": "success"}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Same issue as the API provider path: if workflow_tool_provider.id is assigned by the DB on flush/insert, calling update_tool_labels before a session.flush() can write bindings without a valid tool_id. Add session.flush() after session.add(workflow_tool_provider) and before updating labels.

Copilot uses AI. Check for mistakes.
Comment thread api/services/tools/api_tools_manage_service.py
Comment thread api/services/tools/api_tools_manage_service.py Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 11, 2026 09:27
asukaminato0721 and others added 2 commits February 11, 2026 18:28
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"""
if db_tool is None:
raise ValueError("Tool not found")
with session_factory.create_session() as session, session.begin():
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Using session.begin() for a read-only operation is unnecessary. The _get_workflow_tool method only reads data. Based on the codebase convention, read-only operations should use with session_factory.create_session() as session: without the session.begin() call. This will avoid unnecessary transaction overhead.

Suggested change
with session_factory.create_session() as session, session.begin():
with session_factory.create_session() as session:

Copilot uses AI. Check for mistakes.
.where(WorkflowToolProvider.tenant_id == tenant_id, WorkflowToolProvider.id == workflow_tool_id)
.first()
)
with session_factory.create_session() as session, session.begin():
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Using session.begin() for a read-only operation is unnecessary. The list_single_workflow_tools method only reads data. Based on the codebase convention, read-only operations should use with session_factory.create_session() as session: without the session.begin() call. This will avoid unnecessary transaction overhead.

Suggested change
with session_factory.create_session() as session, session.begin():
with session_factory.create_session() as session:

Copilot uses AI. Check for mistakes.
Comment on lines +286 to +323
with session_factory.create_session() as session, session.begin():
workflow_app = (
session.query(App).where(App.id == db_tool.app_id, App.tenant_id == db_tool.tenant_id).first()
)

workflow_app: App | None = (
db.session.query(App).where(App.id == db_tool.app_id, App.tenant_id == db_tool.tenant_id).first()
)

if workflow_app is None:
raise ValueError(f"App {db_tool.app_id} not found")

workflow = workflow_app.workflow
if not workflow:
raise ValueError("Workflow not found")

tool = ToolTransformService.workflow_provider_to_controller(db_tool)
workflow_tools: list[WorkflowTool] = tool.get_tools(tenant_id)
if len(workflow_tools) == 0:
raise ValueError(f"Tool {db_tool.id} not found")

tool_entity = workflow_tools[0].entity
# get output schema from workflow tool entity
output_schema = tool_entity.output_schema

return {
"name": db_tool.name,
"label": db_tool.label,
"workflow_tool_id": db_tool.id,
"workflow_app_id": db_tool.app_id,
"icon": json.loads(db_tool.icon),
"description": db_tool.description,
"parameters": jsonable_encoder(db_tool.parameter_configurations),
"output_schema": output_schema,
"tool": ToolTransformService.convert_tool_entity_to_api_entity(
tool=tool.get_tools(db_tool.tenant_id)[0],
labels=ToolLabelManager.get_tool_labels(tool),
tenant_id=tenant_id,
),
"synced": workflow.version == db_tool.version,
"privacy_policy": db_tool.privacy_policy,
}
if workflow_app is None:
raise ValueError(f"App {db_tool.app_id} not found")

workflow = workflow_app.workflow
if not workflow:
raise ValueError("Workflow not found")

tool = ToolTransformService.workflow_provider_to_controller(db_tool)
workflow_tools: list[WorkflowTool] = tool.get_tools(tenant_id)
if len(workflow_tools) == 0:
raise ValueError(f"Tool {db_tool.id} not found")

tool_entity = workflow_tools[0].entity
# get output schema from workflow tool entity
output_schema = tool_entity.output_schema

return {
"name": db_tool.name,
"label": db_tool.label,
"workflow_tool_id": db_tool.id,
"workflow_app_id": db_tool.app_id,
"icon": json.loads(db_tool.icon),
"description": db_tool.description,
"parameters": jsonable_encoder(db_tool.parameter_configurations),
"output_schema": output_schema,
"tool": ToolTransformService.convert_tool_entity_to_api_entity(
tool=tool.get_tools(db_tool.tenant_id)[0],
labels=ToolLabelManager.get_tool_labels(tool),
tenant_id=tenant_id,
),
"synced": workflow.version == db_tool.version,
"privacy_policy": db_tool.privacy_policy,
}
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Creating a new session within _get_workflow_tool while using a db_tool object from a previous session will cause detached object issues. The db_tool object is loaded in the calling methods (lines 252-258 and 269-275) within one session, but then accessed (e.g., db_tool.app_id, db_tool.tenant_id, db_tool.name, etc.) within a new session created at line 286.

SQLAlchemy objects are bound to their originating session. When accessed from a different session context, lazy-loaded relationships and attributes may fail. Instead of creating a new session here, the session should be passed as a parameter from the calling methods, or the entire query logic should be moved into the calling methods' session context.

Copilot uses AI. Check for mistakes.
user_tool_provider.tools = [
ToolTransformService.convert_tool_entity_to_api_entity(
tool=tool.get_tools(tenant_id)[0],
with session_factory.create_session() as session, session.begin():
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Using session.begin() for a read-only operation is unnecessary. The list_tenant_workflow_tools method only reads data and doesn't perform any writes. Based on the codebase convention, read-only operations should use with session_factory.create_session() as session: without the session.begin() call, as seen in other similar methods like get_tool_labels and get_tools_labels in tool_label_manager.py. This will avoid unnecessary transaction overhead.

Suggested change
with session_factory.create_session() as session, session.begin():
with session_factory.create_session() as session:

Copilot uses AI. Check for mistakes.
.first()
)
return cls._get_workflow_tool(tenant_id, db_tool)
with session_factory.create_session() as session, session.begin():
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Using session.begin() for a read-only operation is unnecessary. The get_workflow_tool_by_tool_id method only reads data. Based on the codebase convention, read-only operations should use with session_factory.create_session() as session: without the session.begin() call. This will avoid unnecessary transaction overhead.

Copilot uses AI. Check for mistakes.
.first()
)
return cls._get_workflow_tool(tenant_id, db_tool)
with session_factory.create_session() as session, session.begin():
Copy link

Copilot AI Feb 11, 2026

Choose a reason for hiding this comment

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

Using session.begin() for a read-only operation is unnecessary. The get_workflow_tool_by_app_id method only reads data. Based on the codebase convention, read-only operations should use with session_factory.create_session() as session: without the session.begin() call. This will avoid unnecessary transaction overhead.

Suggested change
with session_factory.create_session() as session, session.begin():
with session_factory.create_session() as session:

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 1, 2026

Pyrefly Diff

No changes detected.

Copilot AI review requested due to automatic review settings March 1, 2026 08:34
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 1, 2026

Pyrefly Diff

No changes detected.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +312 to +318
session.add(provider)

# delete cache
cache.delete()

# update labels
ToolLabelManager.update_tool_labels(provider_controller, labels, session)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

cache.delete() now runs inside the DB transaction. If the transaction later rolls back/raises, the cache will already be invalidated, causing cache/DB inconsistency. Consider moving cache invalidation to after the session.begin() block successfully commits (or use an after-commit hook) so cache is only cleared on success.

Copilot uses AI. Check for mistakes.
Comment on lines +58 to +63
with session_factory.create_session() as session:
stmt = select(ToolLabelBinding.label_name).where(
ToolLabelBinding.tool_id == provider_id,
ToolLabelBinding.tool_type == controller.provider_type.value,
)
labels = session.scalars(stmt).all()
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

get_tool_labels always creates its own SQLAlchemy session. Callers like ApiToolManageService.list_api_tools already run inside a create_session() context, so this causes extra connections and breaks transactional consistency (labels may be read from a different transaction than the provider query). Consider accepting an optional session: Session | None parameter (creating one only when not provided) and have callers pass their existing session.

Copilot uses AI. Check for mistakes.
Comment on lines +89 to 91
with session_factory.create_session() as session:
labels = session.scalars(select(ToolLabelBinding).where(ToolLabelBinding.tool_id.in_(provider_ids))).all()

Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

get_tools_labels opens a new session even when the caller is already within an existing session/transaction (e.g., WorkflowToolManageService.list_tenant_workflow_tools). This adds avoidable overhead and can return labels from a different transaction snapshot than the tool provider rows. Consider adding an optional session parameter (similar to update_tool_labels) so callers can reuse their session.

Copilot uses AI. Check for mistakes.
Comment on lines +286 to +289
with session_factory.create_session() as session, session.begin():
workflow_app = (
session.query(App).where(App.id == db_tool.app_id, App.tenant_id == db_tool.tenant_id).first()
)
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

_get_workflow_tool creates a new session/transaction even though its callers (get_workflow_tool_by_tool_id / get_workflow_tool_by_app_id) already run inside create_session() + session.begin(). This nested-session pattern adds overhead and can lead to inconsistent reads across different transactions. Consider passing the caller's session into _get_workflow_tool (or removing the inner create_session() block) so all queries run in a single session.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 2, 2026

Pyrefly Diff

No changes detected.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactor size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants