From c9b77ec9db7e18092d0fa5f649f90a06577af48f Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 25 Oct 2023 21:47:39 -0300 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=94=A7=20fix(store.py):=20change=20`l?= =?UTF-8?q?ist=5Fcomponents`=20function=20name=20to=20`query=5Fcomponents`?= =?UTF-8?q?=20for=20better=20semantics=20and=20consistency=20=E2=9C=A8=20f?= =?UTF-8?q?eat(store.py):=20add=20`count=5Fcomponents`=20endpoint=20to=20g?= =?UTF-8?q?et=20the=20count=20of=20components=20based=20on=20filter=20crit?= =?UTF-8?q?eria=20=F0=9F=94=A7=20fix(service.py):=20modify=20`query=5Fcomp?= =?UTF-8?q?onents`=20function=20to=20return=20a=20list=20of=20dictionaries?= =?UTF-8?q?=20when=20`count`=20parameter=20is=20True?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/store.py | 25 +++++++++++++++++-- .../langflow/services/store/service.py | 24 ++++++++++++------ 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/backend/langflow/api/v1/store.py b/src/backend/langflow/api/v1/store.py index e63b20aab..e8878a5f5 100644 --- a/src/backend/langflow/api/v1/store.py +++ b/src/backend/langflow/api/v1/store.py @@ -66,14 +66,35 @@ def list_components( ): try: fields = ["id", "name", "description", "user_created.name", "is_component"] - result = store_service.list_components( - store_api_Key, page, limit, fields=fields, filter_by_user=filter_by_user + result = store_service.query_components( + store_api_Key, + page, + limit, + fields=fields, + filter_by_user=filter_by_user, ) return result except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) +@router.get("/components/count", response_model=dict) +def count_components( + filter_by_user: bool = Query(False), + store_service: StoreService = Depends(get_store_service), + store_api_Key: str = Depends(get_optional_user_store_api_key), +): + try: + result = store_service.query_components( + store_api_Key, + count=True, + filter_by_user=filter_by_user, + ) + return {"count": result[0].get("count", 0)} + except Exception as exc: + raise HTTPException(status_code=400, detail=str(exc)) + + @router.get("/components/{component_id}", response_model=DownloadComponentResponse) def read_component( component_id: UUID, diff --git a/src/backend/langflow/services/store/service.py b/src/backend/langflow/services/store/service.py index 39d31996e..47ecc772a 100644 --- a/src/backend/langflow/services/store/service.py +++ b/src/backend/langflow/services/store/service.py @@ -2,7 +2,7 @@ from datetime import datetime import json from uuid import UUID from langflow.services.base import Service -from typing import TYPE_CHECKING, List, Dict, Any, Optional +from typing import TYPE_CHECKING, List, Dict, Any, Optional, Union import httpx from httpx import HTTPError @@ -119,21 +119,27 @@ class StoreService(Service): results = self._get(self.components_url, api_key, params) return [ComponentResponse(**component) for component in results] - def list_components( + def query_components( self, api_key: str, page: int = 1, limit: int = 15, fields: Optional[List[str]] = None, filter_by_user: bool = False, - ) -> List[ListComponentResponse]: + count: bool = False, + ) -> Union[List[ListComponentResponse], List[Dict[str, int]]]: params = {"page": page, "limit": limit} # ?aggregate[count]=likes - params["fields"] = ( - ",".join(fields) - if fields - else ",".join(["id", "name", "description", "count(likes)", "is_component"]) - ) + if count: + params["aggregate"] = json.dumps({"count": "*"}) + else: + params["fields"] = ( + ",".join(fields) + if fields + else ",".join( + ["id", "name", "description", "count(likes)", "is_component"] + ) + ) # Only public components or the ones created by the user # check for "public" or "Public" @@ -151,6 +157,8 @@ class StoreService(Service): ) results = self._get(self.components_url, api_key, params) + if "count" in results[0]: + return results return [ListComponentResponse(**component) for component in results] def download(self, api_key: str, component_id: str) -> DownloadComponentResponse: From 31fb76c9921a2b64537d91de00819e63f1c9d8fe Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 25 Oct 2023 21:50:06 -0300 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=90=9B=20fix(store.py):=20change=20fu?= =?UTF-8?q?nction=20name=20from=20query=5Fcomponents=20to=20count=5Fcompon?= =?UTF-8?q?ents=20to=20improve=20semantics=20and=20accurately=20count=20co?= =?UTF-8?q?mponents=20=E2=9C=A8=20feat(service.py):=20add=20count=5Fcompon?= =?UTF-8?q?ents=20method=20to=20StoreService=20class=20to=20count=20compon?= =?UTF-8?q?ents=20based=20on=20filter=20criteria?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/store.py | 8 ++-- .../langflow/services/store/service.py | 37 ++++++++++++------- 2 files changed, 27 insertions(+), 18 deletions(-) diff --git a/src/backend/langflow/api/v1/store.py b/src/backend/langflow/api/v1/store.py index e8878a5f5..59a88153d 100644 --- a/src/backend/langflow/api/v1/store.py +++ b/src/backend/langflow/api/v1/store.py @@ -85,12 +85,10 @@ def count_components( store_api_Key: str = Depends(get_optional_user_store_api_key), ): try: - result = store_service.query_components( - store_api_Key, - count=True, - filter_by_user=filter_by_user, + result = store_service.count_components( + api_key=store_api_Key, filter_by_user=filter_by_user ) - return {"count": result[0].get("count", 0)} + return {"count": result} except Exception as exc: raise HTTPException(status_code=400, detail=str(exc)) diff --git a/src/backend/langflow/services/store/service.py b/src/backend/langflow/services/store/service.py index 47ecc772a..014012462 100644 --- a/src/backend/langflow/services/store/service.py +++ b/src/backend/langflow/services/store/service.py @@ -119,6 +119,25 @@ class StoreService(Service): results = self._get(self.components_url, api_key, params) return [ComponentResponse(**component) for component in results] + def count_components( + self, + api_key: str, + filter_by_user: bool = False, + ) -> int: + params = {"aggregate": json.dumps({"count": "*"})} + if filter_by_user: + params["deep"] = json.dumps( + { + "components": { + "_filter": {"user_created": {"token": {"_eq": api_key}}} + } + } + ) + else: + params["filter"] = json.dumps({"status": {"_in": ["public", "Public"]}}) + results = self._get(self.components_url, api_key, params) + return results[0].get("count", 0) + def query_components( self, api_key: str, @@ -126,20 +145,14 @@ class StoreService(Service): limit: int = 15, fields: Optional[List[str]] = None, filter_by_user: bool = False, - count: bool = False, ) -> Union[List[ListComponentResponse], List[Dict[str, int]]]: params = {"page": page, "limit": limit} # ?aggregate[count]=likes - if count: - params["aggregate"] = json.dumps({"count": "*"}) - else: - params["fields"] = ( - ",".join(fields) - if fields - else ",".join( - ["id", "name", "description", "count(likes)", "is_component"] - ) - ) + params["fields"] = ( + ",".join(fields) + if fields + else ",".join(["id", "name", "description", "count(likes)", "is_component"]) + ) # Only public components or the ones created by the user # check for "public" or "Public" @@ -157,8 +170,6 @@ class StoreService(Service): ) results = self._get(self.components_url, api_key, params) - if "count" in results[0]: - return results return [ListComponentResponse(**component) for component in results] def download(self, api_key: str, component_id: str) -> DownloadComponentResponse: From 3934096e06ecab0b4207eb00c5e52caf5215ba26 Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 25 Oct 2023 22:03:22 -0300 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=94=A5=20refactor(store.py):=20remove?= =?UTF-8?q?=20count=5Fcomponents=20endpoint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The count_components endpoint was removed as it was no longer being used and served no purpose in the API. --- src/backend/langflow/api/v1/store.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/backend/langflow/api/v1/store.py b/src/backend/langflow/api/v1/store.py index 59a88153d..f2106c51c 100644 --- a/src/backend/langflow/api/v1/store.py +++ b/src/backend/langflow/api/v1/store.py @@ -78,21 +78,6 @@ def list_components( raise HTTPException(status_code=400, detail=str(exc)) -@router.get("/components/count", response_model=dict) -def count_components( - filter_by_user: bool = Query(False), - store_service: StoreService = Depends(get_store_service), - store_api_Key: str = Depends(get_optional_user_store_api_key), -): - try: - result = store_service.count_components( - api_key=store_api_Key, filter_by_user=filter_by_user - ) - return {"count": result} - except Exception as exc: - raise HTTPException(status_code=400, detail=str(exc)) - - @router.get("/components/{component_id}", response_model=DownloadComponentResponse) def read_component( component_id: UUID, From 785e8a6436f76e69c49d5893ca94a93636e44e6e Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 25 Oct 2023 22:23:13 -0300 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=94=A7=20fix(store.py):=20add=20count?= =?UTF-8?q?=5Fcomponents=20endpoint=20to=20retrieve=20the=20count=20of=20c?= =?UTF-8?q?omponents=20in=20the=20store=20=F0=9F=94=A7=20fix(service.py):?= =?UTF-8?q?=20modify=20count=5Fcomponents=20method=20to=20make=20api=5Fkey?= =?UTF-8?q?=20optional=20and=20add=20filter=5Fby=5Fuser=20parameter=20to?= =?UTF-8?q?=20filter=20components=20by=20user?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/api/v1/store.py | 15 +++++++++++++++ src/backend/langflow/services/store/service.py | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/backend/langflow/api/v1/store.py b/src/backend/langflow/api/v1/store.py index f2106c51c..59a88153d 100644 --- a/src/backend/langflow/api/v1/store.py +++ b/src/backend/langflow/api/v1/store.py @@ -78,6 +78,21 @@ def list_components( raise HTTPException(status_code=400, detail=str(exc)) +@router.get("/components/count", response_model=dict) +def count_components( + filter_by_user: bool = Query(False), + store_service: StoreService = Depends(get_store_service), + store_api_Key: str = Depends(get_optional_user_store_api_key), +): + try: + result = store_service.count_components( + api_key=store_api_Key, filter_by_user=filter_by_user + ) + return {"count": result} + except Exception as exc: + raise HTTPException(status_code=400, detail=str(exc)) + + @router.get("/components/{component_id}", response_model=DownloadComponentResponse) def read_component( component_id: UUID, diff --git a/src/backend/langflow/services/store/service.py b/src/backend/langflow/services/store/service.py index 014012462..06d3deea9 100644 --- a/src/backend/langflow/services/store/service.py +++ b/src/backend/langflow/services/store/service.py @@ -121,7 +121,7 @@ class StoreService(Service): def count_components( self, - api_key: str, + api_key: Optional[str] = None, filter_by_user: bool = False, ) -> int: params = {"aggregate": json.dumps({"count": "*"})} From d664131e6c1732a839f9ae68381665b1dfdd66ca Mon Sep 17 00:00:00 2001 From: Gabriel Luiz Freitas Almeida Date: Wed, 25 Oct 2023 23:12:23 -0300 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=94=A7=20fix(=5F=5Fmain=5F=5F.py):=20?= =?UTF-8?q?add=20'store'=20parameter=20to=20update=5Fsettings=20function?= =?UTF-8?q?=20to=20enable/disable=20store=20feature=20=E2=9C=A8=20feat(=5F?= =?UTF-8?q?=5Fmain=5F=5F.py):=20add=20'store'=20option=20to=20the=20run=20?= =?UTF-8?q?command=20to=20enable/disable=20store=20feature=20=F0=9F=94=A7?= =?UTF-8?q?=20fix(store.py):=20add=20check=5Fif=5Fstore=5Fis=5Fenabled=20e?= =?UTF-8?q?ndpoint=20to=20check=20if=20store=20feature=20is=20enabled=20?= =?UTF-8?q?=E2=9C=A8=20feat(store.py):=20add=20get=5Ftags=20endpoint=20to?= =?UTF-8?q?=20retrieve=20tags=20from=20the=20store=20=F0=9F=94=A7=20fix(ba?= =?UTF-8?q?se.py):=20add=20STORE=20setting=20to=20enable/disable=20store?= =?UTF-8?q?=20feature=20=F0=9F=94=A7=20fix(schema.py):=20rename=20'likes?= =?UTF-8?q?=5Fcount'=20field=20to=20'liked=5Fby=5Fcount'=20in=20ComponentR?= =?UTF-8?q?esponse=20and=20ListComponentResponse=20=F0=9F=94=A7=20fix(serv?= =?UTF-8?q?ice.py):=20change=20default=20sorting=20in=20query=5Fcomponents?= =?UTF-8?q?=20function=20to=20sort=20by=20'count(liked=5Fby)'=20=E2=9C=85?= =?UTF-8?q?=20test(test=5Fstore.py):=20update=20sort=20parameter=20in=20te?= =?UTF-8?q?st=5Fsearch=5Fcomponents=20to=20'count(liked=5Fby)'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/backend/langflow/__main__.py | 16 +++++----- src/backend/langflow/api/v1/store.py | 30 ++++++++++++++++++- .../langflow/services/settings/base.py | 1 + src/backend/langflow/services/store/schema.py | 10 +++++-- .../langflow/services/store/service.py | 12 ++++++-- tests/test_store.py | 2 +- 6 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/backend/langflow/__main__.py b/src/backend/langflow/__main__.py index f41eaec11..4d3f76498 100644 --- a/src/backend/langflow/__main__.py +++ b/src/backend/langflow/__main__.py @@ -72,6 +72,7 @@ def update_settings( dev: bool = False, remove_api_keys: bool = False, components_path: Optional[Path] = None, + store: bool = True, ): """Update the settings from a config file.""" @@ -90,6 +91,9 @@ def update_settings( if components_path: logger.debug(f"Adding component path {components_path}") settings_service.settings.update_settings(COMPONENTS_PATH=components_path) + if not store: + logger.debug("Setting store to False") + settings_service.settings.update_settings(STORE=False) @app.command() @@ -126,13 +130,6 @@ def run( default=None, ), dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"), - # This variable does not work but is set by the .env file - # and works with Pydantic - # database_url: str = typer.Option( - # None, - # help="Database URL to connect to. If not provided, a local SQLite database will be used.", - # envvar="LANGFLOW_DATABASE_URL", - # ), path: str = typer.Option( None, help="Path to the frontend directory containing build files. This is for development purposes only.", @@ -153,6 +150,11 @@ def run( help="Run only the backend server without the frontend.", envvar="LANGFLOW_BACKEND_ONLY", ), + store: bool = typer.Option( + True, + help="Enables the store features.", + envvar="LANGFLOW_STORE", + ), ): """ Run the Langflow. diff --git a/src/backend/langflow/api/v1/store.py b/src/backend/langflow/api/v1/store.py index 59a88153d..bbd05f4aa 100644 --- a/src/backend/langflow/api/v1/store.py +++ b/src/backend/langflow/api/v1/store.py @@ -11,6 +11,7 @@ from langflow.services.store.schema import ( DownloadComponentResponse, ListComponentResponse, StoreComponentCreate, + TagResponse, ) from fastapi import APIRouter, Depends, HTTPException, Query @@ -44,6 +45,13 @@ def get_optional_user_store_api_key( return decrypted +@router.get("/") +def check_if_store_is_enabled( + settings_service=Depends(get_settings_service), +): + return {"enabled": settings_service.settings.STORE} + + @router.post("/components/", response_model=ComponentResponse, status_code=201) def create_component( component: StoreComponentCreate, @@ -65,7 +73,16 @@ def list_components( store_api_Key: str = Depends(get_optional_user_store_api_key), ): try: - fields = ["id", "name", "description", "user_created.name", "is_component"] + fields = [ + "id", + "name", + "description", + "user_created.name", + "is_component", + "tags.tags_id.name", + "tags.tags_id.id", + "count(liked_by)", + ] result = store_service.query_components( store_api_Key, page, @@ -143,3 +160,14 @@ async def search_endpoint( ) except Exception as exc: raise HTTPException(status_code=500, detail=str(exc)) + + +@router.get("/tags", response_model=List[TagResponse]) +def get_tags( + store_service: StoreService = Depends(get_store_service), + store_api_Key: str = Depends(get_optional_user_store_api_key), +): + try: + return store_service.get_tags(store_api_Key) + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index c666f48ce..294f5881a 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -52,6 +52,7 @@ class Settings(BaseSettings): LANGFUSE_PUBLIC_KEY: Optional[str] = None LANGFUSE_HOST: Optional[str] = None + STORE: Optional[bool] = True STORE_URL: Optional[str] = None DOWNLOAD_WEBHOOK_URL: Optional[str] = None diff --git a/src/backend/langflow/services/store/schema.py b/src/backend/langflow/services/store/schema.py index 34a0ac574..259d341b2 100644 --- a/src/backend/langflow/services/store/schema.py +++ b/src/backend/langflow/services/store/schema.py @@ -4,6 +4,11 @@ from typing import Optional, List from uuid import UUID +class TagResponse(BaseModel): + id: UUID + name: Optional[str] + + class ComponentResponse(BaseModel): id: UUID status: Optional[str] @@ -17,16 +22,15 @@ class ComponentResponse(BaseModel): description: Optional[str] data: Optional[dict] tags: Optional[List[int]] - likes_count: Optional[List[UUID]] + liked_by_count: Optional[List[UUID]] parent: Optional[UUID] class ListComponentResponse(BaseModel): - (["id", "name", "description", "count(likes)", "is_component"]) id: UUID name: Optional[str] description: Optional[str] - likes_count: Optional[int] + liked_by_count: Optional[int] is_component: Optional[bool] diff --git a/src/backend/langflow/services/store/service.py b/src/backend/langflow/services/store/service.py index 06d3deea9..83a199445 100644 --- a/src/backend/langflow/services/store/service.py +++ b/src/backend/langflow/services/store/service.py @@ -70,7 +70,7 @@ class StoreService(Service): tags: Optional[List[str]] = None, date_from: Optional[datetime] = None, date_to: Optional[datetime] = None, - sort: Optional[List[str]] = ["-likes"], + sort: Optional[List[str]] = ["-count(liked_by)"], fields: Optional[List[str]] = None, filter_by_user: bool = False, ) -> List[ComponentResponse]: @@ -151,7 +151,9 @@ class StoreService(Service): params["fields"] = ( ",".join(fields) if fields - else ",".join(["id", "name", "description", "count(likes)", "is_component"]) + else ",".join( + ["id", "name", "description", "count(liked_by)", "is_component"] + ) ) # Only public components or the ones created by the user # check for "public" or "Public" @@ -208,3 +210,9 @@ class StoreService(Service): except UnboundLocalError: pass raise ValueError(f"Upload failed: {exc}") + + def get_tags(self, api_key: str) -> List[Dict[str, Any]]: + url = f"{self.base_url}/items/tags" + params = {"fields": ",".join(["id", "name"])} + tags = self._get(url, api_key, params) + return tags diff --git a/tests/test_store.py b/tests/test_store.py index 706100fe6..c4fbcca7e 100644 --- a/tests/test_store.py +++ b/tests/test_store.py @@ -48,7 +48,7 @@ def test_search_components(mock_httpx: Mock, client): "filter[name][_like]": "test", "page": 1, "limit": 5, - "sort": "likes", + "sort": "count(liked_by)", }, )