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>
This commit is contained in:
Jordan Frazier 2025-06-20 13:07:50 -07:00 committed by GitHub
commit 14f3a2c111
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 78 additions and 29 deletions

View file

@ -223,6 +223,7 @@ The following table lists the environment variables supported by Langflow.
| <Link id="LANGFLOW_SECRET_KEY"/><span class="env-prefix">LANGFLOW_</span>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. |
| <Link id="LANGFLOW_STORE"/><span class="env-prefix">LANGFLOW_</span>STORE | Boolean | `true` | Enable the Langflow Store.<br/>See [`--store` option](./configuration-cli.md#run-store). |
| <Link id="LANGFLOW_STORE_ENVIRONMENT_VARIABLES"/><span class="env-prefix">LANGFLOW_</span>STORE_ENVIRONMENT_VARIABLES | Boolean | `true` | Store environment variables as [global variables](../Configuration/configuration-global-variables.md) in the database. |
| <Link id="LANGFLOW_CREATE_STARTER_PROJECTS"/><span class="env-prefix">LANGFLOW_</span>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. |
| <Link id="LANGFLOW_UPDATE_STARTER_PROJECTS"/><span class="env-prefix">LANGFLOW_</span>UPDATE_STARTER_PROJECTS | Boolean | `true` | If this option is enabled, Langflow updates starter projects with the latest component versions when initializing. |
| <Link id="LANGFLOW_SUPERUSER"/><span class="env-prefix">LANGFLOW_</span>SUPERUSER | String | `langflow` | Set the name for the superuser. Required if <span class="env-prefix">LANGFLOW_</span>AUTO_LOGIN is set to `false`.<br/>See [`superuser --username` option](./configuration-cli.md#superuser-username). |
| <Link id="LANGFLOW_SUPERUSER_PASSWORD"/><span class="env-prefix">LANGFLOW_</span>SUPERUSER_PASSWORD | String | `langflow` | Set the password for the superuser. Required if <span class="env-prefix">LANGFLOW_</span>AUTO_LOGIN is set to `false`.<br/>See [`superuser --password` option](./configuration-cli.md#superuser-password). |

View file

@ -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:

View file

@ -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):