diff --git a/Makefile b/Makefile index 59dd4a82d..59f326853 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ format: cd src/frontend && npm run format lint: - poetry run mypy --exclude .venv . + poetry run mypy src/backend/langflow poetry run black . --check poetry run ruff . --fix diff --git a/poetry.lock b/poetry.lock index 675118488..c2a290db4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3047,13 +3047,13 @@ server = ["fastapi (>=0.100.0)", "pydantic-settings (>=2.0.1)", "sse-starlette ( [[package]] name = "loguru" -version = "0.7.0" +version = "0.7.1" description = "Python logging made (stupidly) simple" optional = false python-versions = ">=3.5" files = [ - {file = "loguru-0.7.0-py3-none-any.whl", hash = "sha256:b93aa30099fa6860d4727f1b81f8718e965bb96253fa190fab2077aaad6d15d3"}, - {file = "loguru-0.7.0.tar.gz", hash = "sha256:1612053ced6ae84d7959dd7d5e431a0532642237ec21f7fd83ac73fe539e03e1"}, + {file = "loguru-0.7.1-py3-none-any.whl", hash = "sha256:046bf970cb3cad77a28d607cbf042ac25a407db987a1e801c7f7e692469982f9"}, + {file = "loguru-0.7.1.tar.gz", hash = "sha256:7ba2a7d81b79a412b0ded69bd921e012335e80fd39937a633570f273a343579e"}, ] [package.dependencies] @@ -3061,7 +3061,7 @@ colorama = {version = ">=0.3.4", markers = "sys_platform == \"win32\""} win32-setctime = {version = ">=1.0.0", markers = "sys_platform == \"win32\""} [package.extras] -dev = ["Sphinx (==5.3.0)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v0.990)", "pre-commit (==3.2.1)", "pytest (==6.1.2)", "pytest (==7.2.1)", "pytest-cov (==2.12.1)", "pytest-cov (==4.0.0)", "pytest-mypy-plugins (==1.10.1)", "pytest-mypy-plugins (==1.9.3)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.2.0)", "tox (==3.27.1)", "tox (==4.4.6)"] +dev = ["Sphinx (==7.2.5)", "colorama (==0.4.5)", "colorama (==0.4.6)", "freezegun (==1.1.0)", "freezegun (==1.2.2)", "mypy (==v0.910)", "mypy (==v0.971)", "mypy (==v1.4.1)", "pre-commit (==3.3.1)", "pytest (==6.1.2)", "pytest (==7.4.0)", "pytest-cov (==2.12.1)", "pytest-cov (==4.1.0)", "pytest-mypy-plugins (==1.9.3)", "pytest-mypy-plugins (==3.0.0)", "sphinx-autobuild (==2021.3.14)", "sphinx-rtd-theme (==1.3.0)", "tox (==3.27.1)", "tox (==4.11.0)"] [[package]] name = "lxml" @@ -7041,6 +7041,17 @@ files = [ {file = "types_pytz-2023.3.0.1-py3-none-any.whl", hash = "sha256:65152e872137926bb67a8fe6cc9cfd794365df86650c5d5fdc7b167b0f38892e"}, ] +[[package]] +name = "types-pywin32" +version = "306.0.0.4" +description = "Typing stubs for pywin32" +optional = false +python-versions = "*" +files = [ + {file = "types-pywin32-306.0.0.4.tar.gz", hash = "sha256:ae4bbec80d535053236d4bebedf55f58dee89cf5883d277f0fa89e857f3ff337"}, + {file = "types_pywin32-306.0.0.4-py3-none-any.whl", hash = "sha256:f76a343ed6933008af85e158063963f923e54f2f461e697b2929b4178c7b77a1"}, +] + [[package]] name = "types-pyyaml" version = "6.0.12.11" @@ -7773,4 +7784,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "c877b4d713eef71815d858d30976ab21c42e5eadcc2df8159e940e03323681ee" +content-hash = "6523f2e35458c6d0b8d281e20dd2233180128805ea07199c073224b0d6f75ee7" diff --git a/pyproject.toml b/pyproject.toml index f852c2a7c..077a0cb39 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -84,6 +84,7 @@ python-jose = "^3.3.0" metaphor-python = "^0.1.11" markupsafe = "^2.1.3" pywin32 = { version = "^306", markers = "sys_platform == 'win32'" } +loguru = "^0.7.1" [tool.poetry.group.dev.dependencies] black = "^23.1.0" @@ -103,6 +104,7 @@ types-python-jose = "^3.3.4.8" types-passlib = "^1.7.7.13" pytest-mock = "^3.11.1" pytest-xdist = "^3.3.1" +types-pywin32 = "^306.0.0.4" [tool.poetry.extras] diff --git a/src/backend/langflow/__main__.py b/src/backend/langflow/__main__.py index b5ec034de..a08ae9fb0 100644 --- a/src/backend/langflow/__main__.py +++ b/src/backend/langflow/__main__.py @@ -356,7 +356,7 @@ def superuser( with session_getter(db_manager) as session: from langflow.services.auth.utils import create_super_user - if create_super_user(session, username, password): + if create_super_user(db=session, username=username, password=password): # Verify that the superuser was created from langflow.services.database.models.user.user import User diff --git a/src/backend/langflow/alembic/versions/260dbcc8b680_adds_tables.py b/src/backend/langflow/alembic/versions/260dbcc8b680_adds_tables.py index 221e66933..53e049a05 100644 --- a/src/backend/langflow/alembic/versions/260dbcc8b680_adds_tables.py +++ b/src/backend/langflow/alembic/versions/260dbcc8b680_adds_tables.py @@ -30,10 +30,10 @@ def upgrade() -> None: # and other related indices if "flowstyle" in existing_tables: op.drop_table("flowstyle") - if "ix_flowstyle_flow_id" in [ - index["name"] for index in inspector.get_indexes("flowstyle") - ]: - op.drop_index("ix_flowstyle_flow_id", table_name="flowstyle") + if "ix_flowstyle_flow_id" in [ + index["name"] for index in inspector.get_indexes("flowstyle") + ]: + op.drop_index("ix_flowstyle_flow_id", table_name="flowstyle") existing_indices_flow = [] existing_fks_flow = [] diff --git a/src/backend/langflow/api/v1/base.py b/src/backend/langflow/api/v1/base.py index acffc1bc3..f2c2f3f59 100644 --- a/src/backend/langflow/api/v1/base.py +++ b/src/backend/langflow/api/v1/base.py @@ -22,7 +22,7 @@ class ValidatePromptRequest(BaseModel): name: str template: str # optional for tweak call - frontend_node: Optional[FrontendNodeRequest] + frontend_node: Optional[FrontendNodeRequest] = None # Build ValidationResponse class for {"imports": {"errors": []}, "function": {"errors": []}} @@ -42,7 +42,7 @@ class CodeValidationResponse(BaseModel): class PromptValidationResponse(BaseModel): input_variables: list # object return for tweak call - frontend_node: FrontendNodeRequest | object + frontend_node: Optional[FrontendNodeRequest] = None INVALID_CHARACTERS = { diff --git a/src/backend/langflow/api/v1/callback.py b/src/backend/langflow/api/v1/callback.py index 69dbf5082..2a16a0bd2 100644 --- a/src/backend/langflow/api/v1/callback.py +++ b/src/backend/langflow/api/v1/callback.py @@ -10,7 +10,7 @@ from fastapi import WebSocket from langchain.schema import AgentAction, LLMResult, AgentFinish -from langflow.utils.logger import logger +from loguru import logger # https://github.com/hwchase17/chat-langchain/blob/master/callback.py diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index e4fc71343..2b3426915 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -11,17 +11,15 @@ from fastapi.responses import StreamingResponse from langflow.api.utils import build_input_keys_response from langflow.api.v1.schemas import BuildStatus, BuiltResponse, InitResponse, StreamData -from langflow.services import service_manager, ServiceType from langflow.graph.graph.base import Graph from langflow.services.auth.utils import get_current_active_user, get_current_user from langflow.services.utils import get_session -from langflow.utils.logger import logger +from loguru import logger +from langflow.services.utils import get_chat_manager, get_session from cachetools import LRUCache from sqlmodel import Session -from typing import TYPE_CHECKING +from langflow.services.chat.manager import ChatManager -if TYPE_CHECKING: - from langflow.services.chat.manager import ChatManager router = APIRouter(tags=["Chat"]) @@ -34,6 +32,7 @@ async def chat( websocket: WebSocket, token: str = Query(...), db: Session = Depends(get_session), + chat_manager: "ChatManager" = Depends(get_chat_manager), ): """Websocket endpoint for chat.""" try: @@ -48,7 +47,6 @@ async def chat( code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized" ) - chat_manager: "ChatManager" = service_manager.get(ServiceType.CHAT_MANAGER) if client_id in chat_manager.in_memory_cache: await chat_manager.handle_websocket(client_id, websocket) else: @@ -61,19 +59,21 @@ async def chat( await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc)) except Exception as exc: logger.error(f"Error in chat websocket: {exc}") - if isinstance(exc, HTTPException): - exc = exc.detail + messsage = exc.detail if isinstance(exc, HTTPException) else str(exc) if "Could not validate credentials" in str(exc): await websocket.close( code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized" ) else: - await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=str(exc)) + await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage) @router.post("/build/init/{flow_id}", response_model=InitResponse, status_code=201) async def init_build( - graph_data: dict, flow_id: str, current_user=Depends(get_current_active_user) + graph_data: dict, + flow_id: str, + current_user=Depends(get_current_active_user), + chat_manager: "ChatManager" = Depends(get_chat_manager), ): """Initialize the build by storing graph data and returning a unique session ID.""" @@ -88,7 +88,6 @@ async def init_build( return InitResponse(flowId=flow_id) # Delete from cache if already exists - chat_manager = service_manager.get(ServiceType.CHAT_MANAGER) if flow_id in chat_manager.in_memory_cache: with chat_manager.in_memory_cache._lock: chat_manager.in_memory_cache.delete(flow_id) @@ -124,7 +123,9 @@ async def build_status(flow_id: str): @router.get("/build/stream/{flow_id}", response_class=StreamingResponse) -async def stream_build(flow_id: str): +async def stream_build( + flow_id: str, chat_manager: "ChatManager" = Depends(get_chat_manager) +): """Stream the build process based on stored flow data.""" async def event_stream(flow_id): @@ -203,7 +204,6 @@ async def stream_build(flow_id: str): "handle_keys": [], } yield str(StreamData(event="message", data=input_keys_response)) - chat_manager = service_manager.get(ServiceType.CHAT_MANAGER) chat_manager.set_cache(flow_id, langchain_object) # We need to reset the chat history chat_manager.chat_history.empty_history(flow_id) diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index 813aaf415..d1f898105 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -7,7 +7,7 @@ from langflow.services.database.models.flow import Flow from langflow.processing.process import process_graph_cached, process_tweaks from langflow.services.database.models.user.user import User from langflow.services.utils import get_settings_manager -from langflow.utils.logger import logger +from loguru import logger from fastapi import APIRouter, Depends, HTTPException, UploadFile, Body, status import sqlalchemy as sa from langflow.interface.custom.custom_component import CustomComponent @@ -34,14 +34,15 @@ from sqlmodel import Session router = APIRouter(tags=["Base"]) -@router.get("/all") -def get_all(current_user: User = Depends(get_current_active_user)): +@router.get("/all", dependencies=[Depends(get_current_active_user)]) +def get_all( + settings_manager=Depends(get_settings_manager), +): logger.debug("Building langchain types dict") native_components = build_langchain_types_dict() # custom_components is a list of dicts # need to merge all the keys into one dict custom_components_from_file: dict[str, Any] = {} - settings_manager = get_settings_manager() if settings_manager.settings.COMPONENTS_PATH: logger.info( f"Building custom components from {settings_manager.settings.COMPONENTS_PATH}" diff --git a/src/backend/langflow/api/v1/flows.py b/src/backend/langflow/api/v1/flows.py index b215b9f95..be65048d4 100644 --- a/src/backend/langflow/api/v1/flows.py +++ b/src/backend/langflow/api/v1/flows.py @@ -83,6 +83,7 @@ def update_flow( flow_id: UUID, flow: FlowUpdate, current_user: User = Depends(get_current_active_user), + settings_manager=Depends(get_settings_manager), ): """Update a flow.""" @@ -90,7 +91,6 @@ def update_flow( if not db_flow: raise HTTPException(status_code=404, detail="Flow not found") flow_data = flow.dict(exclude_unset=True) - settings_manager = get_settings_manager() if settings_manager.settings.REMOVE_API_KEYS: flow_data = remove_api_keys(flow_data) for key, value in flow_data.items(): diff --git a/src/backend/langflow/api/v1/login.py b/src/backend/langflow/api/v1/login.py index afe67a916..4241b8d47 100644 --- a/src/backend/langflow/api/v1/login.py +++ b/src/backend/langflow/api/v1/login.py @@ -34,9 +34,9 @@ async def login_to_get_access_token( @router.get("/auto_login") -async def auto_login(db: Session = Depends(get_session)): - settings_manager = get_settings_manager() - +async def auto_login( + db: Session = Depends(get_session), settings_manager=Depends(get_settings_manager) +): if settings_manager.auth_settings.AUTO_LOGIN: return create_user_longterm_token(db) diff --git a/src/backend/langflow/api/v1/users.py b/src/backend/langflow/api/v1/users.py index 5094409cb..517dd7f69 100644 --- a/src/backend/langflow/api/v1/users.py +++ b/src/backend/langflow/api/v1/users.py @@ -29,7 +29,7 @@ router = APIRouter(tags=["Users"]) @router.post("/user", response_model=UserRead, status_code=201) def add_user( user: UserCreate, - db: Session = Depends(get_session), + session: Session = Depends(get_session), ) -> User: """ Add a new user to the database. @@ -38,11 +38,11 @@ def add_user( try: new_user.password = get_password_hash(user.password) - db.add(new_user) - db.commit() - db.refresh(new_user) + session.add(new_user) + session.commit() + session.refresh(new_user) except IntegrityError as e: - db.rollback() + session.rollback() raise HTTPException( status_code=400, detail="This username is unavailable." ) from e @@ -65,16 +65,16 @@ def read_all_users( skip: int = 0, limit: int = 10, current_user: Session = Depends(get_current_active_superuser), - db: Session = Depends(get_session), + session: Session = Depends(get_session), ) -> UsersResponse: """ Retrieve a list of users from the database with pagination. """ query = select(User).offset(skip).limit(limit) - users = db.execute(query).fetchall() + users = session.execute(query).fetchall() count_query = select(func.count()).select_from(User) # type: ignore - total_count = db.execute(count_query).scalar() + total_count = session.execute(count_query).scalar() return UsersResponse( total_count=total_count, # type: ignore @@ -87,19 +87,19 @@ def patch_user( user_id: UUID, user: UserUpdate, _: Session = Depends(get_current_active_user), - db: Session = Depends(get_session), + session: Session = Depends(get_session), ) -> User: """ Update an existing user's data. """ - return update_user(user_id, user, db) + return update_user(user_id, user, session) @router.delete("/user/{user_id}") def delete_user( user_id: UUID, current_user: User = Depends(get_current_active_superuser), - db: Session = Depends(get_session), + session: Session = Depends(get_session), ) -> dict: """ Delete a user from the database. @@ -113,12 +113,12 @@ def delete_user( status_code=403, detail="You don't have the permission to delete this user" ) - user_db = db.query(User).filter(User.id == user_id).first() + user_db = session.query(User).filter(User.id == user_id).first() if not user_db: raise HTTPException(status_code=404, detail="User not found") - db.delete(user_db) - db.commit() + session.delete(user_db) + session.commit() return {"detail": "User deleted"} @@ -126,7 +126,7 @@ def delete_user( # TODO: REMOVE - Just for testing purposes @router.post("/super_user", response_model=User) def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev( - db: Session = Depends(get_session), + session: Session = Depends(get_session), ) -> User: """ Add a superuser for testing purposes. @@ -141,11 +141,11 @@ def add_super_user_for_testing_purposes_delete_me_before_merge_into_dev( ) try: - db.add(new_user) - db.commit() - db.refresh(new_user) + session.add(new_user) + session.commit() + session.refresh(new_user) except IntegrityError as e: - db.rollback() + session.rollback() raise HTTPException(status_code=400, detail="User exists") from e return new_user diff --git a/src/backend/langflow/api/v1/validate.py b/src/backend/langflow/api/v1/validate.py index da76d25bc..457db5bd3 100644 --- a/src/backend/langflow/api/v1/validate.py +++ b/src/backend/langflow/api/v1/validate.py @@ -8,7 +8,7 @@ from langflow.api.v1.base import ( validate_prompt, ) from langflow.template.field.base import TemplateField -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.validate import validate_code # build router @@ -35,7 +35,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest): if prompt_request.frontend_node is None: return PromptValidationResponse( input_variables=input_variables, - frontend_node={}, + frontend_node=None, ) old_custom_fields = get_old_custom_fields(prompt_request) diff --git a/src/backend/langflow/graph/edge/base.py b/src/backend/langflow/graph/edge/base.py index dc7eab328..2df20cbde 100644 --- a/src/backend/langflow/graph/edge/base.py +++ b/src/backend/langflow/graph/edge/base.py @@ -1,4 +1,4 @@ -from langflow.utils.logger import logger +from loguru import logger from typing import TYPE_CHECKING if TYPE_CHECKING: diff --git a/src/backend/langflow/graph/graph/base.py b/src/backend/langflow/graph/graph/base.py index 2b22d352c..94964e472 100644 --- a/src/backend/langflow/graph/graph/base.py +++ b/src/backend/langflow/graph/graph/base.py @@ -10,7 +10,7 @@ from langflow.graph.vertex.types import ( ) from langflow.interface.tools.constants import FILE_TOOLS from langflow.utils import payload -from langflow.utils.logger import logger +from loguru import logger from langchain.chains.base import Chain diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index d5c4beed9..0f9a5e8a9 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -3,7 +3,7 @@ from langflow.graph.utils import UnbuiltObject from langflow.interface.initialize import loading from langflow.interface.listing import lazy_load_dict from langflow.utils.constants import DIRECT_TYPES -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import sync_to_async diff --git a/src/backend/langflow/interface/agents/base.py b/src/backend/langflow/interface/agents/base.py index ec8c42aba..574264e47 100644 --- a/src/backend/langflow/interface/agents/base.py +++ b/src/backend/langflow/interface/agents/base.py @@ -8,7 +8,7 @@ from langflow.interface.base import LangChainTypeCreator from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.agents import AgentFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class, build_template_from_method diff --git a/src/backend/langflow/interface/base.py b/src/backend/langflow/interface/base.py index d1ed83b5a..b006a3174 100644 --- a/src/backend/langflow/interface/base.py +++ b/src/backend/langflow/interface/base.py @@ -8,7 +8,7 @@ from pydantic import BaseModel from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode from langflow.template.template.base import Template -from langflow.utils.logger import logger +from loguru import logger # Assuming necessary imports for Field, Template, and FrontendNode classes diff --git a/src/backend/langflow/interface/chains/base.py b/src/backend/langflow/interface/chains/base.py index b906dbd25..755ac82dd 100644 --- a/src/backend/langflow/interface/chains/base.py +++ b/src/backend/langflow/interface/chains/base.py @@ -6,7 +6,7 @@ from langflow.interface.importing.utils import import_class from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.chains import ChainFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class, build_template_from_method from langchain import chains from langchain_experimental.sql import SQLDatabaseChain # type: ignore diff --git a/src/backend/langflow/interface/custom/base.py b/src/backend/langflow/interface/custom/base.py index 06e874fa7..45a3ea215 100644 --- a/src/backend/langflow/interface/custom/base.py +++ b/src/backend/langflow/interface/custom/base.py @@ -8,7 +8,7 @@ from langflow.interface.custom.custom_component import CustomComponent from langflow.template.frontend_node.custom_components import ( CustomComponentFrontendNode, ) -from langflow.utils.logger import logger +from loguru import logger # Assuming necessary imports for Field, Template, and FrontendNode classes diff --git a/src/backend/langflow/interface/custom/directory_reader.py b/src/backend/langflow/interface/custom/directory_reader.py index 44b2d4f1b..01b11a4a6 100644 --- a/src/backend/langflow/interface/custom/directory_reader.py +++ b/src/backend/langflow/interface/custom/directory_reader.py @@ -1,7 +1,7 @@ import os import ast import zlib -from langflow.utils.logger import logger +from loguru import logger class CustomComponentPathValueError(ValueError): diff --git a/src/backend/langflow/interface/document_loaders/base.py b/src/backend/langflow/interface/document_loaders/base.py index db0832ff3..a2c147e16 100644 --- a/src/backend/langflow/interface/document_loaders/base.py +++ b/src/backend/langflow/interface/document_loaders/base.py @@ -5,7 +5,7 @@ from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode from langflow.interface.custom_lists import documentloaders_type_to_cls_dict -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class diff --git a/src/backend/langflow/interface/embeddings/base.py b/src/backend/langflow/interface/embeddings/base.py index 169985d37..1063d10d1 100644 --- a/src/backend/langflow/interface/embeddings/base.py +++ b/src/backend/langflow/interface/embeddings/base.py @@ -6,7 +6,7 @@ from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class diff --git a/src/backend/langflow/interface/initialize/loading.py b/src/backend/langflow/interface/initialize/loading.py index 589d4b3ff..b600fb5b2 100644 --- a/src/backend/langflow/interface/initialize/loading.py +++ b/src/backend/langflow/interface/initialize/loading.py @@ -34,7 +34,7 @@ from langflow.utils import validate from langchain.chains.base import Chain from langchain.vectorstores.base import VectorStore from langchain.document_loaders.base import BaseLoader -from langflow.utils.logger import logger +from loguru import logger if TYPE_CHECKING: from langflow import CustomComponent diff --git a/src/backend/langflow/interface/llms/base.py b/src/backend/langflow/interface/llms/base.py index f562b99ed..87e4937cf 100644 --- a/src/backend/langflow/interface/llms/base.py +++ b/src/backend/langflow/interface/llms/base.py @@ -5,7 +5,7 @@ from langflow.interface.custom_lists import llm_type_to_cls_dict from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.llms import LLMFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class diff --git a/src/backend/langflow/interface/memories/base.py b/src/backend/langflow/interface/memories/base.py index 70665602c..61c6cc430 100644 --- a/src/backend/langflow/interface/memories/base.py +++ b/src/backend/langflow/interface/memories/base.py @@ -6,7 +6,7 @@ from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.memories import MemoryFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class, build_template_from_method from langflow.custom.customs import get_custom_nodes diff --git a/src/backend/langflow/interface/output_parsers/base.py b/src/backend/langflow/interface/output_parsers/base.py index 256b521e1..b6eb36a0e 100644 --- a/src/backend/langflow/interface/output_parsers/base.py +++ b/src/backend/langflow/interface/output_parsers/base.py @@ -7,7 +7,7 @@ from langflow.interface.importing.utils import import_class from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class, build_template_from_method diff --git a/src/backend/langflow/interface/prompts/base.py b/src/backend/langflow/interface/prompts/base.py index 5aa41dfb2..70818429e 100644 --- a/src/backend/langflow/interface/prompts/base.py +++ b/src/backend/langflow/interface/prompts/base.py @@ -8,7 +8,7 @@ from langflow.interface.importing.utils import import_class from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.prompts import PromptFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class diff --git a/src/backend/langflow/interface/retrievers/base.py b/src/backend/langflow/interface/retrievers/base.py index db1cfd165..92e3f2f61 100644 --- a/src/backend/langflow/interface/retrievers/base.py +++ b/src/backend/langflow/interface/retrievers/base.py @@ -7,7 +7,7 @@ from langflow.interface.importing.utils import import_class from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.retrievers import RetrieverFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_method, build_template_from_class diff --git a/src/backend/langflow/interface/run.py b/src/backend/langflow/interface/run.py index 42cea0e98..9d3d95cda 100644 --- a/src/backend/langflow/interface/run.py +++ b/src/backend/langflow/interface/run.py @@ -1,7 +1,7 @@ from typing import Any, Dict, Tuple from langflow.services.cache.utils import memoize_dict from langflow.graph import Graph -from langflow.utils.logger import logger +from loguru import logger @memoize_dict(maxsize=10) diff --git a/src/backend/langflow/interface/text_splitters/base.py b/src/backend/langflow/interface/text_splitters/base.py index 87b778c4c..8b21303ce 100644 --- a/src/backend/langflow/interface/text_splitters/base.py +++ b/src/backend/langflow/interface/text_splitters/base.py @@ -5,7 +5,7 @@ from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode from langflow.interface.custom_lists import textsplitter_type_to_cls_dict -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class diff --git a/src/backend/langflow/interface/toolkits/base.py b/src/backend/langflow/interface/toolkits/base.py index c13ffdbd9..fe0003b15 100644 --- a/src/backend/langflow/interface/toolkits/base.py +++ b/src/backend/langflow/interface/toolkits/base.py @@ -6,7 +6,7 @@ from langflow.interface.base import LangChainTypeCreator from langflow.interface.importing.utils import import_class, import_module from langflow.services.utils import get_settings_manager -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class diff --git a/src/backend/langflow/interface/tools/util.py b/src/backend/langflow/interface/tools/util.py index 38276c620..f92386f18 100644 --- a/src/backend/langflow/interface/tools/util.py +++ b/src/backend/langflow/interface/tools/util.py @@ -3,7 +3,7 @@ import inspect from typing import Dict, Union from langchain.agents.tools import Tool -from langflow.utils.logger import logger +from loguru import logger def get_func_tool_params(func, **kwargs) -> Union[Dict, None]: diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index 824b0af50..47743560e 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -29,7 +29,7 @@ from langflow.template.frontend_node.custom_components import ( from langflow.interface.retrievers.base import retriever_creator from langflow.interface.custom.directory_reader import DirectoryReader -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import get_base_classes import re diff --git a/src/backend/langflow/interface/utilities/base.py b/src/backend/langflow/interface/utilities/base.py index eb8cd60af..9009983b0 100644 --- a/src/backend/langflow/interface/utilities/base.py +++ b/src/backend/langflow/interface/utilities/base.py @@ -8,7 +8,7 @@ from langflow.interface.importing.utils import import_class from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.utilities import UtilitiesFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class diff --git a/src/backend/langflow/interface/utils.py b/src/backend/langflow/interface/utils.py index 1fddbf80f..75e854e16 100644 --- a/src/backend/langflow/interface/utils.py +++ b/src/backend/langflow/interface/utils.py @@ -8,7 +8,7 @@ import re import yaml from langchain.base_language import BaseLanguageModel from PIL.Image import Image -from langflow.utils.logger import logger +from loguru import logger from langflow.services.chat.config import ChatConfig from langflow.services.utils import get_settings_manager diff --git a/src/backend/langflow/interface/vector_store/base.py b/src/backend/langflow/interface/vector_store/base.py index 4b8ca2b64..f7aca8c9c 100644 --- a/src/backend/langflow/interface/vector_store/base.py +++ b/src/backend/langflow/interface/vector_store/base.py @@ -7,7 +7,7 @@ from langflow.interface.importing.utils import import_class from langflow.services.utils import get_settings_manager from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_method diff --git a/src/backend/langflow/interface/wrappers/base.py b/src/backend/langflow/interface/wrappers/base.py index 77e38f921..c4399fb3e 100644 --- a/src/backend/langflow/interface/wrappers/base.py +++ b/src/backend/langflow/interface/wrappers/base.py @@ -3,7 +3,7 @@ from typing import Dict, List, Optional from langchain import requests, sql_database from langflow.interface.base import LangChainTypeCreator -from langflow.utils.logger import logger +from loguru import logger from langflow.utils.util import build_template_from_class, build_template_from_method diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index 57f3e34dc..f567e65bb 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -10,7 +10,7 @@ from langflow.api import router from langflow.interface.utils import setup_llm_caching from langflow.services.database.utils import initialize_database -from langflow.services.manager import initialize_services +from langflow.services.manager import initialize_services, teardown_services from langflow.utils.logger import configure @@ -40,6 +40,7 @@ def create_app(): app.on_event("startup")(initialize_services) app.on_event("startup")(initialize_database) app.on_event("startup")(setup_llm_caching) + app.on_event("shutdown")(teardown_services) return app diff --git a/src/backend/langflow/processing/base.py b/src/backend/langflow/processing/base.py index 13ff6a385..c3d766f15 100644 --- a/src/backend/langflow/processing/base.py +++ b/src/backend/langflow/processing/base.py @@ -4,7 +4,7 @@ from langflow.api.v1.callback import ( StreamingLLMCallbackHandler, ) from langflow.processing.process import fix_memory_inputs, format_actions -from langflow.utils.logger import logger +from loguru import logger from langchain.agents.agent import AgentExecutor diff --git a/src/backend/langflow/processing/process.py b/src/backend/langflow/processing/process.py index 4b2e7b178..c60a53de5 100644 --- a/src/backend/langflow/processing/process.py +++ b/src/backend/langflow/processing/process.py @@ -6,7 +6,7 @@ from langflow.interface.run import ( get_memory_key, update_memory_keys, ) -from langflow.utils.logger import logger +from loguru import logger from langflow.graph import Graph from langchain.chains.base import Chain from langchain.vectorstores.base import VectorStore diff --git a/src/backend/langflow/services/auth/utils.py b/src/backend/langflow/services/auth/utils.py index 1431ee615..485968a38 100644 --- a/src/backend/langflow/services/auth/utils.py +++ b/src/backend/langflow/services/auth/utils.py @@ -37,7 +37,12 @@ async def api_key_security( result: Optional[Union[ApiKey, User]] = None if settings_manager.auth_settings.AUTO_LOGIN: # Get the first user - settings_manager.auth_settings.FIRST_SUPERUSER + if not settings_manager.auth_settings.FIRST_SUPERUSER: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Missing first superuser credentials", + ) + result = get_user_by_username( db, settings_manager.auth_settings.FIRST_SUPERUSER ) @@ -80,6 +85,9 @@ async def get_current_user( if isinstance(token, Coroutine): token = await token + if settings_manager.auth_settings.SECRET_KEY is None: + raise credentials_exception + try: payload = jwt.decode( token, @@ -150,22 +158,16 @@ def create_token(data: dict, expires_delta: timedelta): def create_super_user( + username: str, + password: str, db: Session = Depends(get_session), - username: Optional[str] = None, - password: Optional[str] = None, ) -> User: - settings_manager = get_settings_manager() - - super_user = get_user_by_username( - db, username or settings_manager.auth_settings.FIRST_SUPERUSER - ) + super_user = get_user_by_username(db, username) if not super_user: super_user = User( - username=username or settings_manager.auth_settings.FIRST_SUPERUSER, - password=get_password_hash( - password or settings_manager.auth_settings.FIRST_SUPERUSER_PASSWORD - ), + username=username, + password=get_password_hash(password), is_superuser=True, is_active=True, last_login_at=None, @@ -179,7 +181,15 @@ def create_super_user( def create_user_longterm_token(db: Session = Depends(get_session)) -> dict: - super_user = create_super_user(db) + settings_manager = get_settings_manager() + username = settings_manager.auth_settings.FIRST_SUPERUSER + password = settings_manager.auth_settings.FIRST_SUPERUSER_PASSWORD + if not username or not password: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Missing first superuser credentials", + ) + super_user = create_super_user(db=db, username=username, password=password) access_token_expires_longterm = timedelta(days=365) access_token = create_token( diff --git a/src/backend/langflow/services/base.py b/src/backend/langflow/services/base.py index 6bca6c4e2..aaa966047 100644 --- a/src/backend/langflow/services/base.py +++ b/src/backend/langflow/services/base.py @@ -1,2 +1,8 @@ -class Service: +from abc import ABC + + +class Service(ABC): name: str + + def teardown(self): + pass diff --git a/src/backend/langflow/services/chat/manager.py b/src/backend/langflow/services/chat/manager.py index 6751e97e7..d8492e961 100644 --- a/src/backend/langflow/services/chat/manager.py +++ b/src/backend/langflow/services/chat/manager.py @@ -7,7 +7,7 @@ from langflow.services.cache.manager import Subject from langflow.services.chat.utils import process_graph from langflow.interface.utils import pil_to_base64 from langflow.services.schema import ServiceType -from langflow.utils.logger import logger +from loguru import logger import asyncio diff --git a/src/backend/langflow/services/chat/utils.py b/src/backend/langflow/services/chat/utils.py index 17c976eb9..d9c291757 100644 --- a/src/backend/langflow/services/chat/utils.py +++ b/src/backend/langflow/services/chat/utils.py @@ -2,7 +2,7 @@ from fastapi import WebSocket from langflow.api.v1.schemas import ChatMessage from langflow.processing.base import get_result_and_steps from langflow.interface.utils import try_setting_streaming_options -from langflow.utils.logger import logger +from loguru import logger async def process_graph( diff --git a/src/backend/langflow/services/database/manager.py b/src/backend/langflow/services/database/manager.py index 2b599a0ba..7f8afab6f 100644 --- a/src/backend/langflow/services/database/manager.py +++ b/src/backend/langflow/services/database/manager.py @@ -1,12 +1,13 @@ from pathlib import Path from typing import TYPE_CHECKING from langflow.services.base import Service +from langflow.services.database.models.user.crud import get_user_by_username from langflow.services.database.utils import Result, TableResults from langflow.services.utils import get_settings_manager from sqlalchemy import inspect import sqlalchemy as sa from sqlmodel import SQLModel, Session, create_engine -from langflow.utils.logger import logger +from loguru import logger from alembic.config import Config from alembic import command from langflow.services.database import models # noqa @@ -88,7 +89,7 @@ class DatabaseManager(Service): for table in legacy_tables: if table in inspector.get_table_names(): - logger.warn(f"Legacy table exists: {table}") + logger.warning(f"Legacy table exists: {table}") return True @@ -159,3 +160,23 @@ class DatabaseManager(Service): ) logger.debug("Database and tables created successfully") + + def teardown(self): + logger.debug("Tearing down database") + try: + settings_manager = get_settings_manager() + # remove the default superuser if auto_login is enabled + # using the FIRST_SUPERUSER to get the user + if settings_manager.auth_settings.AUTO_LOGIN: + logger.debug("Removing default superuser") + username = settings_manager.auth_settings.FIRST_SUPERUSER + with Session(self.engine) as session: + user = get_user_by_username(session, username) + session.delete(user) + session.commit() + logger.debug("Default superuser removed") + + except Exception as exc: + logger.error(f"Error tearing down database: {exc}") + + self.engine.dispose() diff --git a/src/backend/langflow/services/database/utils.py b/src/backend/langflow/services/database/utils.py index e6afae184..fd0a8856a 100644 --- a/src/backend/langflow/services/database/utils.py +++ b/src/backend/langflow/services/database/utils.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from typing import TYPE_CHECKING -from langflow.utils.logger import logger +from loguru import logger from contextlib import contextmanager from alembic.util.exc import CommandError from sqlmodel import Session diff --git a/src/backend/langflow/services/manager.py b/src/backend/langflow/services/manager.py index e9895adab..60a93fe16 100644 --- a/src/backend/langflow/services/manager.py +++ b/src/backend/langflow/services/manager.py @@ -1,5 +1,6 @@ from langflow.services.schema import ServiceType from typing import TYPE_CHECKING, List, Optional +from loguru import logger if TYPE_CHECKING: from langflow.services.factory import ServiceFactory @@ -42,6 +43,7 @@ class ServiceManager: """ Create a new service given its name, handling dependencies. """ + logger.debug(f"Create service {service_name}") self._validate_service_creation(service_name) # Create dependencies first @@ -74,9 +76,21 @@ class ServiceManager: Update a service by its name. """ if service_name in self.services: + logger.debug(f"Update service {service_name}") self.services.pop(service_name, None) self.get(service_name) + def teardown(self): + """ + Teardown all the services. + """ + for service in self.services.values(): + logger.debug(f"Teardown service {service.name}") + service.teardown() + self.services = {} + self.factories = {} + self.dependencies = {} + service_manager = ServiceManager() @@ -121,7 +135,7 @@ def initialize_session_manager(): """ Initialize the session manager. """ - from langflow.services.session import factory as session_manager_factory + from langflow.services.session import factory as session_manager_factory # type: ignore from langflow.services.cache import factory as cache_factory initialize_settings_manager() @@ -134,3 +148,10 @@ def initialize_session_manager(): session_manager_factory.SessionManagerFactory(), dependencies=[ServiceType.CACHE_MANAGER], ) + + +def teardown_services(): + """ + Teardown all the services. + """ + service_manager.teardown() diff --git a/src/backend/langflow/services/settings/auth.py b/src/backend/langflow/services/settings/auth.py index 7550d3ddd..d1f8197f0 100644 --- a/src/backend/langflow/services/settings/auth.py +++ b/src/backend/langflow/services/settings/auth.py @@ -5,16 +5,17 @@ from langflow.services.settings.utils import read_secret_from_file, write_secret from pydantic import BaseSettings, Field, validator from passlib.context import CryptContext -from langflow.utils.logger import logger +from loguru import logger class AuthSettings(BaseSettings): # Login settings CONFIG_DIR: str - SECRET_KEY: Optional[str] = Field( - None, + SECRET_KEY: str = Field( + default="", description="Secret key for JWT. If not provided, a random one will be generated.", env="LANGFLOW_SECRET_KEY", + allow_mutation=False, ) ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index 00cd2085f..366ff474d 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -8,7 +8,7 @@ from pathlib import Path import yaml from pydantic import BaseSettings, root_validator, validator -from langflow.utils.logger import logger +from loguru import logger # BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components") BASE_COMPONENTS_PATH = str(Path(__file__).parent.parent.parent / "components") diff --git a/src/backend/langflow/services/settings/manager.py b/src/backend/langflow/services/settings/manager.py index 67e06108e..2d687d784 100644 --- a/src/backend/langflow/services/settings/manager.py +++ b/src/backend/langflow/services/settings/manager.py @@ -1,7 +1,7 @@ from langflow.services.base import Service from langflow.services.settings.auth import AuthSettings from langflow.services.settings.base import Settings -from langflow.utils.logger import logger +from loguru import logger import os import yaml @@ -35,5 +35,10 @@ class SettingsManager(Service): ) settings = Settings(**settings_dict) - auth_settings = AuthSettings(CONFIG_DIR=settings.CONFIG_DIR) + if not settings.CONFIG_DIR: + raise ValueError("CONFIG_DIR must be set in settings") + + auth_settings = AuthSettings( + CONFIG_DIR=settings.CONFIG_DIR, + ) return cls(settings, auth_settings) diff --git a/src/backend/langflow/services/settings/utils.py b/src/backend/langflow/services/settings/utils.py index 5eb4cd787..fae96ff28 100644 --- a/src/backend/langflow/services/settings/utils.py +++ b/src/backend/langflow/services/settings/utils.py @@ -2,7 +2,7 @@ import os from pathlib import Path import platform -from langflow.utils.logger import logger +from loguru import logger def set_secure_permissions(file_path): @@ -43,5 +43,5 @@ def write_secret_to_file(path: Path, value: str) -> None: def read_secret_from_file(path: Path) -> str: - with path.open("rb") as f: + with path.open("r") as f: return f.read() diff --git a/src/backend/langflow/services/utils.py b/src/backend/langflow/services/utils.py index 6860f8928..8b32aef02 100644 --- a/src/backend/langflow/services/utils.py +++ b/src/backend/langflow/services/utils.py @@ -1,10 +1,12 @@ from langflow.services import ServiceType, service_manager -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Generator if TYPE_CHECKING: from langflow.services.database.manager import DatabaseManager from langflow.services.settings.manager import SettingsManager + from langflow.services.chat.manager import ChatManager + from sqlmodel import Session def get_settings_manager() -> "SettingsManager": @@ -15,6 +17,10 @@ def get_db_manager() -> "DatabaseManager": return service_manager.get(ServiceType.DATABASE_MANAGER) -def get_session(): +def get_session() -> Generator["Session", None, None]: db_manager = service_manager.get(ServiceType.DATABASE_MANAGER) yield from db_manager.get_session() + + +def get_chat_manager() -> "ChatManager": + return service_manager.get(ServiceType.CHAT_MANAGER) diff --git a/src/backend/langflow/utils/logger.py b/src/backend/langflow/utils/logger.py index deb0f75ca..1f616486b 100644 --- a/src/backend/langflow/utils/logger.py +++ b/src/backend/langflow/utils/logger.py @@ -1,30 +1,35 @@ -import logging +from typing import Optional +from loguru import logger from pathlib import Path - from rich.logging import RichHandler -logger = logging.getLogger("langflow") +def configure(log_level: str = "DEBUG", log_file: Optional[Path] = None): + log_format = "{time:HH:mm:ss} - {level: <8} - {message}" + logger.remove() # Remove default handlers -def configure(log_level: str = "DEBUG", log_file: Path = None): # type: ignore - log_format = "%(asctime)s - %(levelname)s - %(message)s" - log_level_value = getattr(logging, log_level.upper(), logging.INFO) - - logging.basicConfig( - level=log_level_value, - format=log_format, - datefmt="[%X]", - handlers=[RichHandler(rich_tracebacks=True)], + # Configure loguru to use RichHandler + logger.configure( + handlers=[ + { + "sink": RichHandler(rich_tracebacks=True, markup=True), + "format": log_format, + "level": log_level.upper(), + } + ] ) if log_file: log_file = Path(log_file) log_file.parent.mkdir(parents=True, exist_ok=True) - file_handler = logging.FileHandler(log_file) - file_handler.setFormatter(logging.Formatter(log_format)) - logger.addHandler(file_handler) + logger.add( + sink=str(log_file), + level=log_level.upper(), + format=log_format, + rotation="10 MB", # Log rotation based on file size + ) - logger.info(f"Logger set up with log level: {log_level_value}({log_level})") + logger.info(f"Logger set up with log level: {log_level}") if log_file: logger.info(f"Log file: {log_file}") diff --git a/src/frontend/src/components/headerComponent/index.tsx b/src/frontend/src/components/headerComponent/index.tsx index 5d8c61c4a..e6702e342 100644 --- a/src/frontend/src/components/headerComponent/index.tsx +++ b/src/frontend/src/components/headerComponent/index.tsx @@ -149,16 +149,21 @@ export default function Header(): JSX.Element { My Account {isAdmin && ( - navigate("/admin")}> + navigate("/admin")} + > Admin Page )} navigate("/account/settings")} > Profile Settings { logout(); navigate("/login"); diff --git a/src/frontend/src/components/inputComponent/index.tsx b/src/frontend/src/components/inputComponent/index.tsx index 46acdc1b0..490be0fb0 100644 --- a/src/frontend/src/components/inputComponent/index.tsx +++ b/src/frontend/src/components/inputComponent/index.tsx @@ -54,7 +54,7 @@ export default function InputComponent({ ) : ( { diff --git a/src/frontend/src/pages/signUpPage/index.tsx b/src/frontend/src/pages/signUpPage/index.tsx index c15a905c0..4b83bc07d 100644 --- a/src/frontend/src/pages/signUpPage/index.tsx +++ b/src/frontend/src/pages/signUpPage/index.tsx @@ -33,8 +33,8 @@ export default function SignUp(): JSX.Element { function handleSignup(): void { const { username, password } = inputState; const newUser: UserInputType = { - username, - password, + username: username.trim(), + password: password.trim(), }; addUser(newUser) .then((user) => { diff --git a/tests/test_user.py b/tests/test_user.py index d734e4d61..bc617e127 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -9,7 +9,13 @@ from langflow.services.database.models.user import UserUpdate @pytest.fixture def super_user(client, session): - return create_super_user(session) + settings_manager = get_settings_manager() + auth_settings = settings_manager.auth_settings + return create_super_user( + db=session, + username=auth_settings.FIRST_SUPERUSER, + password=auth_settings.FIRST_SUPERUSER_PASSWORD, + ) @pytest.fixture