From 14f3a2c111810bc81c5b93a651a033a809b3871b Mon Sep 17 00:00:00 2001 From: Jordan Frazier <122494242+jordanrfrazier@users.noreply.github.com> Date: Fri, 20 Jun 2025 13:07:50 -0700 Subject: [PATCH] ref: improve starter projects setup performance (#8549) * Update starter project logic to only delete and recreate if necessary * move updates to correct location * [autofix.ci] apply automated fixes * remove unused var * Add to env var md * Use setting service for env vars * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../Configuration/environment-variables.md | 1 + .../base/langflow/initial_setup/setup.py | 98 +++++++++++++------ .../base/langflow/services/settings/base.py | 8 ++ 3 files changed, 78 insertions(+), 29 deletions(-) diff --git a/docs/docs/Configuration/environment-variables.md b/docs/docs/Configuration/environment-variables.md index 389034a51..a3387ba48 100644 --- a/docs/docs/Configuration/environment-variables.md +++ b/docs/docs/Configuration/environment-variables.md @@ -223,6 +223,7 @@ The following table lists the environment variables supported by Langflow. | LANGFLOW_SECRET_KEY | String | Auto-generated | Key used for encrypting sensitive data like API keys. If a key is not provided, a secure key is auto-generated. For production environments with multiple instances, you should explicitly set this to ensure consistent encryption across instances. | | LANGFLOW_STORE | Boolean | `true` | Enable the Langflow Store.
See [`--store` option](./configuration-cli.md#run-store). | | LANGFLOW_STORE_ENVIRONMENT_VARIABLES | Boolean | `true` | Store environment variables as [global variables](../Configuration/configuration-global-variables.md) in the database. | +| LANGFLOW_CREATE_STARTER_PROJECTS | Boolean | `true` | If this option is enabled, Langflow creates starter projects during initialization. Set to `false` to skip all starter project creation and updates. | | LANGFLOW_UPDATE_STARTER_PROJECTS | Boolean | `true` | If this option is enabled, Langflow updates starter projects with the latest component versions when initializing. | | LANGFLOW_SUPERUSER | String | `langflow` | Set the name for the superuser. Required if LANGFLOW_AUTO_LOGIN is set to `false`.
See [`superuser --username` option](./configuration-cli.md#superuser-username). | | LANGFLOW_SUPERUSER_PASSWORD | String | `langflow` | Set the password for the superuser. Required if LANGFLOW_AUTO_LOGIN is set to `false`.
See [`superuser --password` option](./configuration-cli.md#superuser-password). | diff --git a/src/backend/base/langflow/initial_setup/setup.py b/src/backend/base/langflow/initial_setup/setup.py index e66459d33..7fe02bf16 100644 --- a/src/backend/base/langflow/initial_setup/setup.py +++ b/src/backend/base/langflow/initial_setup/setup.py @@ -2,7 +2,6 @@ import asyncio import copy import io import json -import os import re import shutil import zipfile @@ -690,7 +689,7 @@ async def get_all_flows_similar_to_project(session: AsyncSession, folder_id: UUI return list((await session.exec(stmt)).first().flows) -async def delete_start_projects(session, folder_id) -> None: +async def delete_starter_projects(session, folder_id) -> None: flows = await get_all_flows_similar_to_project(session, folder_id) for flow in flows: await session.delete(flow) @@ -703,7 +702,7 @@ async def folder_exists(session, folder_name): return folder is not None -async def create_starter_folder(session): +async def get_or_create_starter_folder(session): if not await folder_exists(session, STARTER_FOLDER_NAME): new_folder = FolderCreate(name=STARTER_FOLDER_NAME, description=STARTER_FOLDER_DESCRIPTION) db_folder = Folder.model_validate(new_folder, from_attributes=True) @@ -899,21 +898,31 @@ async def find_existing_flow(session, flow_id, flow_endpoint_name): return None -async def create_or_update_starter_projects(all_types_dict: dict, *, do_create: bool = True) -> None: +async def create_or_update_starter_projects(all_types_dict: dict) -> None: """Create or update starter projects. Args: all_types_dict (dict): Dictionary containing all component types and their templates - do_create (bool, optional): Whether to create new projects. Defaults to True. """ - successfully_created_projects = 0 + if not get_settings_service().settings.create_starter_projects: + # no-op for environments that don't want to create starter projects. + # note that this doesn't check if the starter projects are already loaded in the db; + # this is intended to be used to skip all startup project logic. + return + async with session_scope() as session: - new_folder = await create_starter_folder(session) + new_folder = await get_or_create_starter_folder(session) starter_projects = await load_starter_projects() - await delete_start_projects(session, new_folder.id) - await copy_profile_pictures() - for project_path, project in starter_projects: - try: + + if get_settings_service().settings.update_starter_projects: + logger.debug("Updating starter projects") + # 1. Delete all existing starter projects + successfully_updated_projects = 0 + await delete_starter_projects(session, new_folder.id) + await copy_profile_pictures() + + # 2. Update all starter projects with the latest component versions (this modifies the actual file data) + for project_path, project in starter_projects: ( project_name, project_description, @@ -925,23 +934,16 @@ async def create_or_update_starter_projects(all_types_dict: dict, *, do_create: project_gradient, project_tags, ) = get_project_data(project) - do_update_starter_projects = ( - os.environ.get("LANGFLOW_UPDATE_STARTER_PROJECTS", "true").lower() == "true" + updated_project_data = update_projects_components_with_latest_component_versions( + project_data.copy(), all_types_dict ) - if do_update_starter_projects: - updated_project_data = update_projects_components_with_latest_component_versions( - project_data.copy(), all_types_dict - ) - updated_project_data = update_edges_with_latest_component_versions(updated_project_data) - if updated_project_data != project_data: - project_data = updated_project_data - # We also need to update the project data in the file - await update_project_file(project_path, project, updated_project_data) - if do_create and project_name and project_data: - existing_flows = await get_all_flows_similar_to_project(session, new_folder.id) - for existing_project in existing_flows: - await session.delete(existing_project) + updated_project_data = update_edges_with_latest_component_versions(updated_project_data) + if updated_project_data != project_data: + project_data = updated_project_data + await update_project_file(project_path, project, updated_project_data) + try: + # Create the updated starter project create_new_project( session=session, project_name=project_name, @@ -955,10 +957,48 @@ async def create_or_update_starter_projects(all_types_dict: dict, *, do_create: project_tags=project_tags, new_folder_id=new_folder.id, ) + except Exception: # noqa: BLE001 + logger.exception(f"Error while creating starter project {project_name}") + + successfully_updated_projects += 1 + logger.debug(f"Successfully updated {successfully_updated_projects} starter projects") + else: + # Even if we're not updating starter projects, we still need to create any that don't exist + logger.debug("Creating new starter projects") + successfully_created_projects = 0 + existing_flows = await get_all_flows_similar_to_project(session, new_folder.id) + existing_flow_names = [existing_flow.name for existing_flow in existing_flows] + for _, project in starter_projects: + ( + project_name, + project_description, + project_is_component, + updated_at_datetime, + project_data, + project_icon, + project_icon_bg_color, + project_gradient, + project_tags, + ) = get_project_data(project) + if project_name not in existing_flow_names: + try: + create_new_project( + session=session, + project_name=project_name, + project_description=project_description, + project_is_component=project_is_component, + updated_at_datetime=updated_at_datetime, + project_data=project_data, + project_icon=project_icon, + project_icon_bg_color=project_icon_bg_color, + project_gradient=project_gradient, + project_tags=project_tags, + new_folder_id=new_folder.id, + ) + except Exception: # noqa: BLE001 + logger.exception(f"Error while creating starter project {project_name}") successfully_created_projects += 1 - except Exception: # noqa: BLE001 - logger.exception(f"Error while creating starter project {project_name}") - logger.debug(f"Successfully created {successfully_created_projects} starter projects") + logger.debug(f"Successfully created {successfully_created_projects} starter projects") async def initialize_super_user_if_needed() -> None: diff --git a/src/backend/base/langflow/services/settings/base.py b/src/backend/base/langflow/services/settings/base.py index d3d4b5715..2c2f1c681 100644 --- a/src/backend/base/langflow/services/settings/base.py +++ b/src/backend/base/langflow/services/settings/base.py @@ -256,6 +256,14 @@ class Settings(BaseSettings): """If set to True, Langflow will only partially load components at startup and fully load them on demand. This significantly reduces startup time but may cause a slight delay when a component is first used.""" + # Starter Projects + create_starter_projects: bool = True + """If set to True, Langflow will create starter projects. If False, skips all starter project setup. + Note that this doesn't check if the starter projects are already loaded in the db; + this is intended to be used to skip all startup project logic.""" + update_starter_projects: bool = True + """If set to True, Langflow will update starter projects.""" + @field_validator("event_delivery", mode="before") @classmethod def set_event_delivery(cls, value, info):