Merge branch 'feature/store' into feat/store/cris

This commit is contained in:
cristhianzl 2023-10-25 23:57:13 -03:00
commit adf849a6fb
6 changed files with 100 additions and 19 deletions

View file

@ -72,6 +72,7 @@ def update_settings(
dev: bool = False, dev: bool = False,
remove_api_keys: bool = False, remove_api_keys: bool = False,
components_path: Optional[Path] = None, components_path: Optional[Path] = None,
store: bool = True,
): ):
"""Update the settings from a config file.""" """Update the settings from a config file."""
@ -90,6 +91,9 @@ def update_settings(
if components_path: if components_path:
logger.debug(f"Adding component path {components_path}") logger.debug(f"Adding component path {components_path}")
settings_service.settings.update_settings(COMPONENTS_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() @app.command()
@ -126,13 +130,6 @@ def run(
default=None, default=None,
), ),
dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"), 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( path: str = typer.Option(
None, None,
help="Path to the frontend directory containing build files. This is for development purposes only.", 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.", help="Run only the backend server without the frontend.",
envvar="LANGFLOW_BACKEND_ONLY", envvar="LANGFLOW_BACKEND_ONLY",
), ),
store: bool = typer.Option(
True,
help="Enables the store features.",
envvar="LANGFLOW_STORE",
),
): ):
""" """
Run the Langflow. Run the Langflow.

View file

@ -11,6 +11,7 @@ from langflow.services.store.schema import (
DownloadComponentResponse, DownloadComponentResponse,
ListComponentResponse, ListComponentResponse,
StoreComponentCreate, StoreComponentCreate,
TagResponse,
) )
from fastapi import APIRouter, Depends, HTTPException, Query from fastapi import APIRouter, Depends, HTTPException, Query
@ -44,6 +45,13 @@ def get_optional_user_store_api_key(
return decrypted 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) @router.post("/components/", response_model=ComponentResponse, status_code=201)
def create_component( def create_component(
component: StoreComponentCreate, component: StoreComponentCreate,
@ -65,15 +73,43 @@ def list_components(
store_api_Key: str = Depends(get_optional_user_store_api_key), store_api_Key: str = Depends(get_optional_user_store_api_key),
): ):
try: try:
fields = ["id", "name", "description", "user_created.name", "is_component"] fields = [
result = store_service.list_components( "id",
store_api_Key, page, limit, fields=fields, filter_by_user=filter_by_user "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,
limit,
fields=fields,
filter_by_user=filter_by_user,
) )
return result return result
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=400, detail=str(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.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) @router.get("/components/{component_id}", response_model=DownloadComponentResponse)
def read_component( def read_component(
component_id: UUID, component_id: UUID,
@ -124,3 +160,14 @@ async def search_endpoint(
) )
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=500, detail=str(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))

View file

@ -52,6 +52,7 @@ class Settings(BaseSettings):
LANGFUSE_PUBLIC_KEY: Optional[str] = None LANGFUSE_PUBLIC_KEY: Optional[str] = None
LANGFUSE_HOST: Optional[str] = None LANGFUSE_HOST: Optional[str] = None
STORE: Optional[bool] = True
STORE_URL: Optional[str] = None STORE_URL: Optional[str] = None
DOWNLOAD_WEBHOOK_URL: Optional[str] = None DOWNLOAD_WEBHOOK_URL: Optional[str] = None

View file

@ -4,6 +4,11 @@ from typing import Optional, List
from uuid import UUID from uuid import UUID
class TagResponse(BaseModel):
id: UUID
name: Optional[str]
class ComponentResponse(BaseModel): class ComponentResponse(BaseModel):
id: UUID id: UUID
status: Optional[str] status: Optional[str]
@ -17,16 +22,15 @@ class ComponentResponse(BaseModel):
description: Optional[str] description: Optional[str]
data: Optional[dict] data: Optional[dict]
tags: Optional[List[int]] tags: Optional[List[int]]
likes_count: Optional[List[UUID]] liked_by_count: Optional[List[UUID]]
parent: Optional[UUID] parent: Optional[UUID]
class ListComponentResponse(BaseModel): class ListComponentResponse(BaseModel):
(["id", "name", "description", "count(likes)", "is_component"])
id: UUID id: UUID
name: Optional[str] name: Optional[str]
description: Optional[str] description: Optional[str]
likes_count: Optional[int] liked_by_count: Optional[int]
is_component: Optional[bool] is_component: Optional[bool]

View file

@ -2,7 +2,7 @@ from datetime import datetime
import json import json
from uuid import UUID from uuid import UUID
from langflow.services.base import Service 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 import httpx
from httpx import HTTPError from httpx import HTTPError
@ -70,7 +70,7 @@ class StoreService(Service):
tags: Optional[List[str]] = None, tags: Optional[List[str]] = None,
date_from: Optional[datetime] = None, date_from: Optional[datetime] = None,
date_to: 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, fields: Optional[List[str]] = None,
filter_by_user: bool = False, filter_by_user: bool = False,
) -> List[ComponentResponse]: ) -> List[ComponentResponse]:
@ -119,20 +119,41 @@ class StoreService(Service):
results = self._get(self.components_url, api_key, params) results = self._get(self.components_url, api_key, params)
return [ComponentResponse(**component) for component in results] return [ComponentResponse(**component) for component in results]
def list_components( def count_components(
self,
api_key: Optional[str] = None,
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, self,
api_key: str, api_key: str,
page: int = 1, page: int = 1,
limit: int = 15, limit: int = 15,
fields: Optional[List[str]] = None, fields: Optional[List[str]] = None,
filter_by_user: bool = False, filter_by_user: bool = False,
) -> List[ListComponentResponse]: ) -> Union[List[ListComponentResponse], List[Dict[str, int]]]:
params = {"page": page, "limit": limit} params = {"page": page, "limit": limit}
# ?aggregate[count]=likes # ?aggregate[count]=likes
params["fields"] = ( params["fields"] = (
",".join(fields) ",".join(fields)
if 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 # Only public components or the ones created by the user
# check for "public" or "Public" # check for "public" or "Public"
@ -189,3 +210,9 @@ class StoreService(Service):
except UnboundLocalError: except UnboundLocalError:
pass pass
raise ValueError(f"Upload failed: {exc}") 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

View file

@ -48,7 +48,7 @@ def test_search_components(mock_httpx: Mock, client):
"filter[name][_like]": "test", "filter[name][_like]": "test",
"page": 1, "page": 1,
"limit": 5, "limit": 5,
"sort": "likes", "sort": "count(liked_by)",
}, },
) )