feat(database): add noop mode with config flag and update related tests (#9054)

* test: enhance agent component tests and add new fixture for database-less sessions

- Added `use_noop_session` fixture to facilitate testing without a database.
- Expanded `TestAgentComponent` with new tests for agent responses using the Calculator tool and validation across all OpenAI and Anthropic models.
- Updated `test_component.py` to include tests for message sending without a database, ensuring proper event handling and message integrity.
- Refactored existing tests for clarity and consistency.

* fix: enhance user retrieval logic in get_or_create_super_user and teardown_superuser functions

- Updated user retrieval in both functions to handle different result types from the database query, ensuring robustness against unexpected return values.
- Added checks for `first()` method and list type to improve error handling and maintainability.

* feat: add support for no-op database operations in settings

- Introduced `use_noop_database` configuration option to disable all database operations, controlled by the `LANGFLOW_USE_NOOP_DATABASE` environment variable.
- Updated the `use_noop_session` fixture to reflect the new environment variable for testing without a database.

* feat: implement NoopSession for database-less operations

- Added NoopSession class to provide a no-operation database session, allowing for testing and operation without a real database connection.
- Updated DatabaseService to utilize NoopSession when the `use_noop_database` setting is enabled, ensuring all database operations are disabled in this mode.
- Enhanced error handling and logging for session management, improving robustness in scenarios where the database is not in use.

* refactor: update import paths for NoopSession in test files

- Changed import path for NoopSession from `langflow.services.database.service` to `langflow.services.database.session` in both `test_agent_component.py` and `test_component.py`.
- This refactor improves code organization and aligns with recent changes in the project structure.

* feat: enhance NoopSession with result handling methods

- Added an internal _NoopResult class to the NoopSession, providing methods `first()`, `all()`, and `one_or_none()` for better handling of no-operation results.
- This enhancement improves the usability of NoopSession in testing scenarios by mimicking expected database query behaviors.

* refactor: improve logging for NOOP database session

- Changed the log level from warning to info for the NOOP database session message in the DatabaseService class.
- This adjustment enhances clarity in logging, indicating that all DB operations are disabled without implying an error condition.

* docs: improve docstring formatting for custom_component_update function

- Reformatted the docstring for the custom_component_update function to enhance readability by breaking long lines into multiple lines.
- This change improves documentation clarity, making it easier for developers to understand the function's purpose and behavior.

* docs: add LANGFLOW_USE_NOOP_DATABASE environment variable documentation

- Introduced documentation for the new LANGFLOW_USE_NOOP_DATABASE environment variable, which allows users to enable a no-op database mode, avoiding database connections and operations.
- This addition enhances the clarity of configuration options available for Langflow, aiding users in understanding how to run flows without a database.

* refactor: move NOOP database session logging to settings

- Removed the logging statement from the DatabaseService class and added it to the `set_use_noop_database` field validator in the Settings class.
- This change centralizes the logging for the NOOP database session, improving code organization and ensuring that the message is logged whenever the setting is applied.
This commit is contained in:
Gabriel Luiz Freitas Almeida 2025-07-15 13:52:23 -03:00 • committed by GitHub
commit f1e95940a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 294 additions and 47 deletions

View file

@ -682,7 +682,9 @@ async def custom_component_update(
):
"""Update an existing custom component with new code and configuration.
Processes the provided code and template updates, applies parameter changes (including those loaded from the database), updates the component's build configuration, and validates outputs. Returns the updated component node as a JSON-serializable dictionary.
Processes the provided code and template updates, applies parameter changes (including those loaded from the
database), updates the component's build configuration, and validates outputs. Returns the updated component node as
a JSON-serializable dictionary.
Raises:
HTTPException: If an error occurs during component building or updating.

View file

@ -27,6 +27,7 @@ from langflow.initial_setup.constants import STARTER_FOLDER_NAME
from langflow.services.base import Service
from langflow.services.database import models
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.database.session import NoopSession
from langflow.services.database.utils import Result, TableResults
from langflow.services.deps import get_settings_service
from langflow.services.utils import teardown_superuser
@ -182,14 +183,17 @@ class DatabaseService(Service):
@asynccontextmanager
async def with_session(self):
async with AsyncSession(self.engine, expire_on_commit=False) as session:
# Start of Selection
try:
yield session
except exc.SQLAlchemyError as db_exc:
logger.error(f"Database error during session scope: {db_exc}")
await session.rollback()
raise
if self.settings_service.settings.use_noop_database:
yield NoopSession()
else:
async with AsyncSession(self.engine, expire_on_commit=False) as session:
# Start of Selection
try:
yield session
except exc.SQLAlchemyError as db_exc:
logger.error(f"Database error during session scope: {db_exc}")
await session.rollback()
raise
async def assign_orphaned_flows_to_superuser(self) -> None:
"""Assign orphaned flows to the default superuser when auto login is enabled."""

View file

@ -0,0 +1,62 @@
class NoopSession:
class NoopBind:
class NoopConnect:
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
pass
async def run_sync(self, fn, *args, **kwargs): # noqa: ARG002
return None
def connect(self):
return self.NoopConnect()
bind = NoopBind()
async def add(self, *args, **kwargs):
pass
async def commit(self):
pass
async def rollback(self):
pass
async def execute(self, *args, **kwargs): # noqa: ARG002
return None
async def query(self, *args, **kwargs): # noqa: ARG002
return []
async def close(self):
pass
async def refresh(self, *args, **kwargs):
pass
async def delete(self, *args, **kwargs):
pass
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb):
pass
async def get(self, *args, **kwargs): # noqa: ARG002
return None
async def exec(self, *args, **kwargs): # noqa: ARG002
class _NoopResult:
def first(self):
return None
def all(self):
return []
def one_or_none(self):
return None
return _NoopResult()

View file

@ -126,6 +126,10 @@ class Settings(BaseSettings):
- echo: Enable SQL query logging (development only)
"""
use_noop_database: bool = False
"""If True, disables all database operations and uses a no-op session.
Controlled by LANGFLOW_USE_NOOP_DATABASE env variable."""
# cache configuration
cache_type: Literal["async", "redis", "memory", "disk"] = "async"
"""The cache type can be 'async' or 'redis'."""
@ -268,6 +272,13 @@ class Settings(BaseSettings):
update_starter_projects: bool = True
"""If set to True, Langflow will update starter projects."""
@field_validator("use_noop_database", mode="before")
@classmethod
def set_use_noop_database(cls, value):
if value:
logger.info("Running with NOOP database session. All DB operations are disabled.")
return value
@field_validator("event_delivery", mode="before")
@classmethod
def set_event_delivery(cls, value, info):

View file

@ -29,7 +29,8 @@ async def get_or_create_super_user(session: AsyncSession, username, password, is
from langflow.services.database.models.user.model import User
stmt = select(User).where(User.username == username)
user = (await session.exec(stmt)).first()
result = await session.exec(stmt)
user = result.first()
if user and user.is_superuser:
return None # Superuser already exists
@ -114,7 +115,8 @@ async def teardown_superuser(settings_service, session: AsyncSession) -> None:
from langflow.services.database.models.user.model import User
stmt = select(User).where(User.username == username)
user = (await session.exec(stmt)).first()
result = await session.exec(stmt)
user = result.first()
# Check if super was ever logged in, if not delete it
# if it has logged in, it means the user is using it to login
if user and user.is_superuser is True and not user.last_login_at: