diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml index f63920703..c5ea5f6f7 100644 --- a/.github/workflows/docker-build.yml +++ b/.github/workflows/docker-build.yml @@ -23,14 +23,23 @@ env: POETRY_VERSION: "1.8.2" jobs: - docker_build: - name: Build Docker Image + setup: runs-on: ubuntu-latest + outputs: + base_tags: ${{ steps.set-vars.outputs.base_tags }} + main_tags: ${{ steps.set-vars.outputs.main_tags }} + steps: + - uses: actions/checkout@v4 + - name: Set Dockerfile and Tags + id: set-vars + run: | + echo "base_tags=langflowai/langflow:base-${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "main_tags=langflowai/langflow:${{ inputs.version }},langflowai/langflow:1.0-alpha" >> $GITHUB_OUTPUT + build_base: + runs-on: ubuntu-latest + needs: setup steps: - uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - id: qemu - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub @@ -38,56 +47,54 @@ jobs: with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - - name: Set Dockerfile and Tags - id: set-vars - run: | - if [ "${{ inputs.release_type }}" == "base" ]; then - echo "DOCKERFILE=./docker/build_and_push_base.Dockerfile" >> $GITHUB_ENV - echo "TAGS=langflowai/langflow:base-${{ inputs.version }}" >> $GITHUB_ENV - else - echo "DOCKERFILE=./docker/build_and_push.Dockerfile" >> $GITHUB_ENV - echo "TAGS=langflowai/langflow:${{ inputs.version }},langflowai/langflow:1.0-alpha" >> $GITHUB_ENV - fi - - name: Build and push + - name: Build and push Base Image uses: docker/build-push-action@v5 with: context: . push: true platforms: "linux/amd64,linux/arm64/v8" - file: ${{ env.DOCKERFILE }} - tags: ${{ env.TAGS }} - - name: Wait for Docker Hub to propagate - run: sleep 120 - - name: Build and push (backend) - if: ${{ inputs.release_type == 'main' }} + file: ./docker/build_and_push_base.Dockerfile + tags: ${{ needs.setup.outputs.base_tags }} + + build_components: + if: ${{ inputs.release_type == 'main' }} + runs-on: ubuntu-latest + needs: build_base + strategy: + matrix: + component: [backend, frontend] + include: + - component: backend + dockerfile: ./docker/build_and_push_backend.Dockerfile + tags: langflowai/langflow-backend:${{ inputs.version }},langflowai/langflow-backend:1.0-alpha + - component: frontend + dockerfile: ./docker/frontend/build_and_push_frontend.Dockerfile + tags: langflowai/langflow-frontend:${{ inputs.version }},langflowai/langflow-frontend:1.0-alpha + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - name: Build and push ${{ matrix.component }} uses: docker/build-push-action@v5 with: context: . push: true platforms: "linux/amd64,linux/arm64/v8" - file: ./docker/build_and_push_backend.Dockerfile build-args: | LANGFLOW_IMAGE=langflowai/langflow:${{ inputs.version }} - tags: | - langflowai/langflow-backend:${{ inputs.version }} - langflowai/langflow-backend:1.0-alpha - - name: Build and push (frontend) - if: ${{ inputs.release_type == 'main' }} - uses: docker/build-push-action@v5 - with: - context: . - push: true - file: ./docker/frontend/build_and_push_frontend.Dockerfile - platforms: "linux/amd64,linux/arm64/v8" - tags: | - langflowai/langflow-frontend:${{ inputs.version }} - langflowai/langflow-frontend:1.0-alpha + file: ${{ matrix.dockerfile }} + tags: ${{ matrix.tags }} restart-space: name: Restart HuggingFace Spaces if: ${{ inputs.release_type == 'main' }} runs-on: ubuntu-latest - needs: docker_build + needs: build_base strategy: matrix: python-version: diff --git a/poetry.lock b/poetry.lock index e0e4f8392..3febf4aa2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -471,17 +471,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.125" +version = "1.34.126" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.125-py3-none-any.whl", hash = "sha256:116d9eb3c26cf313a2e1e44ef704d1f98f9eb18e7628695d07b01b44a8683544"}, - {file = "boto3-1.34.125.tar.gz", hash = "sha256:31c4a5e4d6f9e6116be61ff654b424ddbd1afcdefe0e8b870c4796f9108eb1c6"}, + {file = "boto3-1.34.126-py3-none-any.whl", hash = "sha256:7f676daef674fe74f34ce4063228eccc6e60c811f574720e31f230296c4bf29a"}, + {file = "boto3-1.34.126.tar.gz", hash = "sha256:7e8418b47dd43954a9088d504541bed8a42b6d06e712d02befba134c1c4d7c6d"}, ] [package.dependencies] -botocore = ">=1.34.125,<1.35.0" +botocore = ">=1.34.126,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -490,13 +490,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.125" +version = "1.34.126" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.125-py3-none-any.whl", hash = "sha256:71e97e7d2c088f1188ba6976441b5857a5425acd4aaa31b45d13119c9cb86424"}, - {file = "botocore-1.34.125.tar.gz", hash = "sha256:d2882be011ad5b16e7ab4a96360b5b66a0a7e175c1ea06dbf2de473c0a0a33d8"}, + {file = "botocore-1.34.126-py3-none-any.whl", hash = "sha256:7eff883c638fe30e0b036789df32d851e093d12544615a3b90062b42ac85bdbc"}, + {file = "botocore-1.34.126.tar.gz", hash = "sha256:7a8ccb6a7c02456757a984a3a44331b6f51c94cb8b9b287cd045122fd177a4b0"}, ] [package.dependencies] @@ -4113,22 +4113,25 @@ adal = ["adal (>=1.0.2)"] [[package]] name = "langchain" -version = "0.2.3" +version = "0.2.4" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langchain-0.2.3-py3-none-any.whl", hash = "sha256:5dc33cd9c8008693d328b7cb698df69073acecc89ad9c2a95f243b3314f8d834"}, - {file = "langchain-0.2.3.tar.gz", hash = "sha256:81962cc72cce6515f7bd71e01542727870789bf8b666c6913d85559080c1a201"}, + {file = "langchain-0.2.4-py3-none-any.whl", hash = "sha256:a04813215c30f944df006031e2febde872af8fab628dcee825d969e07b6cd621"}, + {file = "langchain-0.2.4.tar.gz", hash = "sha256:e704b5b06222d5eba2d02c76f891321d1bac8952ed54e093831b2bdabf99dcd5"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} -langchain-core = ">=0.2.0,<0.3.0" +langchain-core = ">=0.2.6,<0.3.0" langchain-text-splitters = ">=0.2.0,<0.3.0" langsmith = ">=0.1.17,<0.2.0" -numpy = ">=1,<2" +numpy = [ + {version = ">=1,<2", markers = "python_version < \"3.12\""}, + {version = ">=1.26.0,<2.0.0", markers = "python_version >= \"3.12\""}, +] pydantic = ">=1,<3" PyYAML = ">=5.3" requests = ">=2,<3" @@ -4224,19 +4227,19 @@ tenacity = ">=8.1.0,<9.0.0" [[package]] name = "langchain-core" -version = "0.2.5" +version = "0.2.6" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langchain_core-0.2.5-py3-none-any.whl", hash = "sha256:abe5138f22acff23a079ec538be5268bbf97cf023d51987a0dd474d2a16cae3e"}, - {file = "langchain_core-0.2.5.tar.gz", hash = "sha256:4a5c2f56b22396a63ef4790043660e393adbfa6832b978f023ca996a04b8e752"}, + {file = "langchain_core-0.2.6-py3-none-any.whl", hash = "sha256:90521c9fc95d8f925e0d2e2d952382676aea6d3f8de611eda1b1810874c31e5d"}, + {file = "langchain_core-0.2.6.tar.gz", hash = "sha256:9f0e38da722a558a6e95b6d86de01bd92e84558c47ac8ba599f02eab70a1c873"}, ] [package.dependencies] jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.66,<0.2.0" -packaging = ">=23.2,<24.0" +langsmith = ">=0.1.75,<0.2.0" +packaging = ">=23.2,<25" pydantic = ">=1,<3" PyYAML = ">=5.3" tenacity = ">=8.1.0,<9.0.0" @@ -4409,7 +4412,7 @@ six = "*" [[package]] name = "langflow-base" -version = "0.0.66" +version = "0.0.68" description = "A Python package with a built-in web application" optional = false python-versions = ">=3.10,<3.13" @@ -4506,13 +4509,13 @@ requests = ">=2,<3" [[package]] name = "litellm" -version = "1.40.9" +version = "1.40.12" description = "Library to easily interface with LLM API providers" optional = false python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" files = [ - {file = "litellm-1.40.9-py3-none-any.whl", hash = "sha256:8a4107b9cce114d822de52cbc9bce56f8edc6620f19d0f2257e71834715fb366"}, - {file = "litellm-1.40.9.tar.gz", hash = "sha256:e0ea07d0b55001a6f60bba2b2ecd72d1f0dca07e656f63937adfdf45f31e5ad7"}, + {file = "litellm-1.40.12-py3-none-any.whl", hash = "sha256:42f1648507f29c60543ba5fdf35d38fc161694da043b201508225bae50d3328c"}, + {file = "litellm-1.40.12.tar.gz", hash = "sha256:366bb9c3694b9ef59b3d073bb37ff9ca175ab4090dc187b0a11d2b21db3a6a5d"}, ] [package.dependencies] @@ -7850,13 +7853,13 @@ websockets = ">=11,<13" [[package]] name = "redis" -version = "5.0.5" +version = "5.0.6" description = "Python client for Redis database and key-value store" optional = true python-versions = ">=3.7" files = [ - {file = "redis-5.0.5-py3-none-any.whl", hash = "sha256:30b47d4ebb6b7a0b9b40c1275a19b87bb6f46b3bed82a89012cf56dea4024ada"}, - {file = "redis-5.0.5.tar.gz", hash = "sha256:3417688621acf6ee368dec4a04dd95881be24efd34c79f00d31f62bb528800ae"}, + {file = "redis-5.0.6-py3-none-any.whl", hash = "sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee"}, + {file = "redis-5.0.6.tar.gz", hash = "sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index 47316552c..6435db14c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langflow" -version = "1.0.0a55" +version = "1.0.0a57" description = "A Python package with a built-in web application" authors = ["Langflow "] maintainers = [ diff --git a/src/backend/base/langflow/services/auth/utils.py b/src/backend/base/langflow/services/auth/utils.py index f62d3ce4f..d0ece7562 100644 --- a/src/backend/base/langflow/services/auth/utils.py +++ b/src/backend/base/langflow/services/auth/utils.py @@ -1,14 +1,13 @@ +import warnings from datetime import datetime, timedelta, timezone from typing import Annotated, Coroutine, Optional, Union from uuid import UUID -import warnings from cryptography.fernet import Fernet from fastapi import Depends, HTTPException, Security, status from fastapi.security import APIKeyHeader, APIKeyQuery, OAuth2PasswordBearer - - from jose import JWTError, jwt +from loguru import logger from sqlmodel import Session from starlette.websockets import WebSocket @@ -92,44 +91,58 @@ async def get_current_user_by_jwt( ) -> User: settings_service = get_settings_service() - credentials_exception = HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, - detail="Could not validate credentials", - headers={"WWW-Authenticate": "Bearer"}, - ) - if isinstance(token, Coroutine): token = await token - if settings_service.auth_settings.SECRET_KEY.get_secret_value() is None: - raise credentials_exception + secret_key = settings_service.auth_settings.SECRET_KEY.get_secret_value() + if secret_key is None: + logger.error("Secret key is not set in settings.") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + # Careful not to leak sensitive information + detail="Authentication failure: Verify authentication settings.", + headers={"WWW-Authenticate": "Bearer"}, + ) try: - # Ignore warning about datetime.utcnow with warnings.catch_warnings(): warnings.simplefilter("ignore") - - payload = jwt.decode( - token, - settings_service.auth_settings.SECRET_KEY.get_secret_value(), - algorithms=[settings_service.auth_settings.ALGORITHM], - ) - user_id: UUID = payload.get("sub") # type: ignore - token_type: str = payload.get("type") # type: ignore + payload = jwt.decode(token, secret_key, algorithms=[settings_service.auth_settings.ALGORITHM]) + user_id: UUID = payload.get("sub") + token_type: str = payload.get("type") if expires := payload.get("exp", None): expires_datetime = datetime.fromtimestamp(expires, timezone.utc) - # TypeError: can't compare offset-naive and offset-aware datetimes if datetime.now(timezone.utc) > expires_datetime: - raise credentials_exception + logger.info("Token expired for user") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Token has expired.", + headers={"WWW-Authenticate": "Bearer"}, + ) if user_id is None or token_type: - raise credentials_exception + logger.info(f"Invalid token payload. Token type: {token_type}") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Invalid token details.", + headers={"WWW-Authenticate": "Bearer"}, + ) except JWTError as e: - raise credentials_exception from e + logger.error(f"JWT decoding error: {e}") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) from e - user = get_user_by_id(db, user_id) # type: ignore + user = get_user_by_id(db, user_id) if user is None or not user.is_active: - raise credentials_exception + logger.info("User not found or inactive.") + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="User not found or is inactive.", + headers={"WWW-Authenticate": "Bearer"}, + ) return user diff --git a/src/backend/base/langflow/services/database/service.py b/src/backend/base/langflow/services/database/service.py index db5b63c70..ceeaf3e38 100644 --- a/src/backend/base/langflow/services/database/service.py +++ b/src/backend/base/langflow/services/database/service.py @@ -6,24 +6,21 @@ from typing import TYPE_CHECKING import sqlalchemy as sa from alembic import command, util from alembic.config import Config -from loguru import logger -from sqlalchemy import inspect -from sqlalchemy.exc import OperationalError -from sqlalchemy.engine import Engine -from sqlalchemy import event -from sqlmodel import Session, SQLModel, create_engine, select, text - from langflow.services.base import Service from langflow.services.database import models # noqa from langflow.services.database.models.user.crud import get_user_by_username from langflow.services.database.utils import Result, TableResults from langflow.services.deps import get_settings_service from langflow.services.utils import teardown_superuser +from loguru import logger +from sqlalchemy import event, inspect +from sqlalchemy.engine import Engine +from sqlalchemy.exc import OperationalError +from sqlmodel import Session, SQLModel, create_engine, select, text if TYPE_CHECKING: - from sqlalchemy.engine import Engine - from langflow.services.settings.service import SettingsService + from sqlalchemy.engine import Engine class DatabaseService(Service): @@ -48,12 +45,23 @@ class DatabaseService(Service): connect_args = {"check_same_thread": False} else: connect_args = {} - return create_engine( - self.database_url, - connect_args=connect_args, - pool_size=self.settings_service.settings.pool_size, - max_overflow=self.settings_service.settings.max_overflow, - ) + try: + return create_engine( + self.database_url, + connect_args=connect_args, + pool_size=self.settings_service.settings.pool_size, + max_overflow=self.settings_service.settings.max_overflow, + ) + except sa.exc.NoSuchModuleError as exc: + # sqlalchemy.exc.NoSuchModuleError: Can't load plugin: sqlalchemy.dialects:postgres + if "postgres" in str(exc) and not self.database_url.startswith("postgresql"): + # https://stackoverflow.com/questions/62688256/sqlalchemy-exc-nosuchmoduleerror-cant-load-plugin-sqlalchemy-dialectspostgre + self.database_url = self.database_url.replace("postgres://", "postgresql://") + logger.warning( + "Fixed postgres dialect in database URL. Replacing postgres:// with postgresql://. To avoid this warning, update the database URL." + ) + return self._create_engine() + raise RuntimeError("Error creating database engine") from exc @event.listens_for(Engine, "connect") def on_connection(dbapi_connection, connection_record): diff --git a/src/backend/base/poetry.lock b/src/backend/base/poetry.lock index ab30d8f08..8f57c7d7b 100644 --- a/src/backend/base/poetry.lock +++ b/src/backend/base/poetry.lock @@ -1158,22 +1158,25 @@ files = [ [[package]] name = "langchain" -version = "0.2.3" +version = "0.2.4" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langchain-0.2.3-py3-none-any.whl", hash = "sha256:5dc33cd9c8008693d328b7cb698df69073acecc89ad9c2a95f243b3314f8d834"}, - {file = "langchain-0.2.3.tar.gz", hash = "sha256:81962cc72cce6515f7bd71e01542727870789bf8b666c6913d85559080c1a201"}, + {file = "langchain-0.2.4-py3-none-any.whl", hash = "sha256:a04813215c30f944df006031e2febde872af8fab628dcee825d969e07b6cd621"}, + {file = "langchain-0.2.4.tar.gz", hash = "sha256:e704b5b06222d5eba2d02c76f891321d1bac8952ed54e093831b2bdabf99dcd5"}, ] [package.dependencies] aiohttp = ">=3.8.3,<4.0.0" async-timeout = {version = ">=4.0.0,<5.0.0", markers = "python_version < \"3.11\""} -langchain-core = ">=0.2.0,<0.3.0" +langchain-core = ">=0.2.6,<0.3.0" langchain-text-splitters = ">=0.2.0,<0.3.0" langsmith = ">=0.1.17,<0.2.0" -numpy = ">=1,<2" +numpy = [ + {version = ">=1,<2", markers = "python_version < \"3.12\""}, + {version = ">=1.26.0,<2.0.0", markers = "python_version >= \"3.12\""}, +] pydantic = ">=1,<3" PyYAML = ">=5.3" requests = ">=2,<3" @@ -1205,19 +1208,19 @@ tenacity = ">=8.1.0,<9.0.0" [[package]] name = "langchain-core" -version = "0.2.5" +version = "0.2.6" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0,>=3.8.1" files = [ - {file = "langchain_core-0.2.5-py3-none-any.whl", hash = "sha256:abe5138f22acff23a079ec538be5268bbf97cf023d51987a0dd474d2a16cae3e"}, - {file = "langchain_core-0.2.5.tar.gz", hash = "sha256:4a5c2f56b22396a63ef4790043660e393adbfa6832b978f023ca996a04b8e752"}, + {file = "langchain_core-0.2.6-py3-none-any.whl", hash = "sha256:90521c9fc95d8f925e0d2e2d952382676aea6d3f8de611eda1b1810874c31e5d"}, + {file = "langchain_core-0.2.6.tar.gz", hash = "sha256:9f0e38da722a558a6e95b6d86de01bd92e84558c47ac8ba599f02eab70a1c873"}, ] [package.dependencies] jsonpatch = ">=1.33,<2.0" -langsmith = ">=0.1.66,<0.2.0" -packaging = ">=23.2,<24.0" +langsmith = ">=0.1.75,<0.2.0" +packaging = ">=23.2,<25" pydantic = ">=1,<3" PyYAML = ">=5.3" tenacity = ">=8.1.0,<9.0.0" @@ -1859,13 +1862,13 @@ files = [ [[package]] name = "packaging" -version = "23.2" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, - {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] diff --git a/src/backend/base/pyproject.toml b/src/backend/base/pyproject.toml index 03f3f70f1..a833470a2 100644 --- a/src/backend/base/pyproject.toml +++ b/src/backend/base/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langflow-base" -version = "0.0.66" +version = "0.0.68" description = "A Python package with a built-in web application" authors = ["Langflow "] maintainers = [ diff --git a/src/frontend/src/stores/foldersStore.tsx b/src/frontend/src/stores/foldersStore.tsx index 25247ce76..ce858c3be 100644 --- a/src/frontend/src/stores/foldersStore.tsx +++ b/src/frontend/src/stores/foldersStore.tsx @@ -16,19 +16,19 @@ export const useFolderStore = create((set, get) => ({ get().setLoading(true); getFolders().then( (res) => { - const foldersWithoutStarterProjects = res.filter( - (folder) => folder.name !== STARTER_FOLDER_NAME + const foldersWithoutStarterProjects = res?.filter( + (folder) => folder.name !== STARTER_FOLDER_NAME, ); - const starterProjects = res.find( - (folder) => folder.name === STARTER_FOLDER_NAME + const starterProjects = res?.find( + (folder) => folder.name === STARTER_FOLDER_NAME, ); set({ starterProjectId: starterProjects!.id ?? "" }); set({ folders: foldersWithoutStarterProjects }); const myCollectionId = res?.find( - (f) => f.name === DEFAULT_FOLDER + (f) => f.name === DEFAULT_FOLDER, )?.id; set({ myCollectionId }); @@ -45,7 +45,7 @@ export const useFolderStore = create((set, get) => ({ set({ folders: [] }); get().setLoading(false); reject(error); - } + }, ); } }); @@ -54,24 +54,21 @@ export const useFolderStore = create((set, get) => ({ loading: false, setLoading: (loading) => set(() => ({ loading: loading })), getFolderById: (id) => { - get().setLoadingById(true); if (id) { getFolderById(id).then( (res) => { const setAllFlows = useFlowsManagerStore.getState().setAllFlows; setAllFlows(res.flows); set({ selectedFolder: res }); - get().setLoadingById(false); }, () => { - get().setLoadingById(false); - } + get().getFoldersApi(true); + }, ); } }, selectedFolder: null, loadingById: false, - setLoadingById: (loading) => set(() => ({ loadingById: loading })), getMyCollectionFolder: () => { const folders = get().folders; const myCollectionId = folders?.find((f) => f.name === DEFAULT_FOLDER)?.id; diff --git a/src/frontend/src/types/zustand/folders/index.ts b/src/frontend/src/types/zustand/folders/index.ts index 2c5b2b96b..5e41677b2 100644 --- a/src/frontend/src/types/zustand/folders/index.ts +++ b/src/frontend/src/types/zustand/folders/index.ts @@ -8,8 +8,6 @@ export type FoldersStoreType = { setLoading: (loading: boolean) => void; selectedFolder: FolderType | null; getFolderById: (id: string) => void; - loadingById: boolean; - setLoadingById: (loading: boolean) => void; getMyCollectionFolder: () => void; myCollectionFlows: FolderType | null; myCollectionId: string | null; diff --git a/tests/test_user.py b/tests/test_user.py index 5f894e801..759ce6aaf 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,6 +1,7 @@ from datetime import datetime import pytest + from langflow.services.auth.utils import create_super_user, get_password_hash from langflow.services.database.models.user import UserUpdate from langflow.services.database.models.user.model import User @@ -95,7 +96,7 @@ def test_data_consistency_after_update(client, active_user, logged_in_headers, s # Fetch the updated user from the database response = client.get("/api/v1/users/whoami", headers=logged_in_headers) assert response.status_code == 401, response.json() - assert response.json()["detail"] == "Could not validate credentials" + assert response.json()["detail"] == "User not found or is inactive." def test_data_consistency_after_delete(client, test_user, super_user_headers):