* Update model kwargs and temperature values

* Update keyboard shortcuts for advanced editing

* make Message field have no handles

* Update OpenAI API Key handling in OpenAIEmbeddingsComponent

* Remove unnecessary field_type key from CustomComponent class

* Update required field behavior in CustomComponent class

* Refactor AzureOpenAIModel.py: Removed unnecessary "required" attribute from input parameters

* Update BaiduQianfanChatModel and OpenAIModel configurations

* Fix range_spec step type validation

* Update RangeSpec step_type default value to "float"

* Fix Save debounce

* Update parameterUtils to use debounce instead of throttle

* Update input type options in schemas and graph base classes

* Refactor run_flow_with_caching endpoint to include simplified and experimental versions

* Add PythonFunctionComponent and test case for it

* Add nest_asyncio to fix event loop issue

* Refactor test_initial_setup.py to use RunOutputs instead of ResultData

* Remove unused code in test_endpoints.py

* Add asyncio loop to uvicorn command

* Refactor load_session method to handle coroutine result

* Fixed saving

* Fixed debouncing

* Add InputType and OutputType literals to schema.py

* Update input type in Graph class

* Add new schema for simplified API request

* Add delete_messages function and update test_successful_run assertions

* Add STREAM_INFO_TEXT constant to model components

* Add session_id to simplified_run_flow_with_caching endpoint

* Add field_typing import to OpenAIModel.py

* update starter projects

* Add constants for Langflow base module

* Update setup.py to include latest component versions

* Update Starter Examples

* sets starter_project fixture to Basic Prompting

* Refactor test_endpoints.py: Update test names and add new tests for different output types

* Update HuggingFace Spaces link and add image for dark mode

* Remove filepath reference

* Update Vertex params in base.py

* Add tests for different input types

* Add type annotations and improve test coverage

* Add duplicate space link to README

* Update HuggingFace Spaces badge in README

* Add Python 3.10 installation requirement to README

* Refactor flow running endpoints

* Refactor SimplifiedAPIRequest and add documentation for Tweaks

* Refactor input_request parameter in simplified_run_flow function

* Add support for retrieving specific component output

* Add custom Uvicorn worker for Langflow application

* Add asyncio loop to LangflowApplication initialization

* Update Makefile with new variables and start command

* Fix indentation in Makefile

* Refactor run_graph function to add support for running a JSON flow

* Refactor getChatInputField function and update API code

* Update HuggingFace Spaces documentation with duplication process

* Add asyncio event loop to uvicorn command

* Add installation of backend in start target

* udpate some starter projects

* Fix formatting in hugging-face-spaces.mdx

* Update installation instructions for Langflow

* set examples order

* Update start command in Makefile

* Add installation and usage instructions for Langflow

* Update Langflow installation and usage instructions

* Fix langflow command in README.md

* Fix broken link to HuggingFace Spaces guide

* Add new SVG assets for blog post, chat bot, and cloud docs

* Refactor example rendering in NewFlowModal

* Add new SVG file for short bio section

* Remove unused import and add new component

* Update title in usage.mdx

* Update HuggingFace Spaces heading in usage.mdx

* Update usage instructions in getting-started/usage.mdx

* Update cache option in usage documentation

* Remove 'advanced' flag from 'n_messages' parameter in MemoryComponent.py

* Refactor code to improve performance and readability

* Update project names and flow examples

* fix document qa example

* Remove commented out code in sidebars.js

* Delete unused documentation files

* Fix bug in login functionality

* Remove global variables from components

* Fix bug in login functionality

* fix modal returning to input

* Update max-width of chat message sender name

* Update styling for chat message component

* Refactor OpenAIEmbeddingsComponent signature

* Update usage.mdx file

* Update path in Makefile

* Add new migration and what's new documentation files

* Add new chapters and migration guides

* Update version to 0.0.13 in pyproject.toml

* new locks

* Update dependencies in pyproject.toml

* general fixes

* Update dependencies in pyproject.toml and poetry.lock files

* add padding to modal

* ✨ (undrawCards/index.tsx): update the SVG used for BasicPrompt component to undraw_short_bio_re_fmx0.svg to match the desired design
♻️ (undrawCards/index.tsx): adjust the width and height of the BasicPrompt SVG to 65% to improve the visual appearance

* Commented out components/data in sidebars.js

* Refactor component names in outputs.mdx

* Update embedded chat script URL

* Add data component and fix formatting in outputs component

* Update dependencies in poetry.lock and pyproject.toml

* Update dependencies in poetry.lock and pyproject.toml

* Refactor code to improve performance and readability

* Update dependencies in poetry.lock and pyproject.toml

* Fixed IO Modal updates

* Remove dead code at API Modal

* Fixed overflow at CodeTabsComponent tweaks page

* ✨ (NewFlowModal/index.tsx): update the name of the example from "Blog Writter" to "Blog Writer" for better consistency and clarity

* Update dependencies versions

* Update langflow-base to version 0.0.15 and fix setup_env script

* Update dependencies in pyproject.toml

* Lock dependencies in parallel

* Add logging statement to setup_app function

* Fix Ace not having type="module" and breaking build

* Update authentication settings for access token cookie

* Update package versions in package-lock.json

* Add scripts directory to Dockerfile

* Add setup_env command to build_and_run target

* Remove unnecessary make command in setup_env

* Remove unnecessary installation step in build_and_run

* Add debug configuration for CLI

* 🔧 chore(Makefile): refactor build_langflow target to use a separate script for updating dependencies and building
✨ feat(update_dependencies.py): add script to update pyproject.toml dependency version based on langflow-base version in src/backend/base/pyproject.toml

* Add number_of_results parameter to AstraDBSearchComponent

* Update HuggingFace Spaces links

* Remove duplicate imports in hugging-face-spaces.mdx

* Add number_of_results parameter to vector search components

* Fixed supabase not commited

* Revert "Fixed supabase not commited"

This reverts commit afb10a6262.

* Update duplicate-space.png image

* Delete unused files and components

* Add/update script to update dependencies

* Add .bak files to .gitignore

* Update version numbers and remove unnecessary dependencies

* Update langflow-base dependency path

* Add Text import to VertexAiModel.py

* Update langflow-base version to 0.0.16 and update dependencies

* Delete start projects and commit session in delete_start_projects function

* Refactor backend startup script to handle autologin option

* Update poetry installation script to include pipx update check

* Update pipx installation script for different operating systems

* Update Makefile to improve setup process

* Add error handling on streaming and fix streaming bug on error

* Added description to Blog Writer

* Sort base classes alphabetically

* Update duplicate-space.png image

* update position on langflow prompt chaining

* Add Langflow CLI and first steps documentation

* Add exception handling for missing 'content' field in search_with_vector_store method

* Remove unused import and update type hinting

* fix bug on egdes after creating group component

* Refactor APIRequest class and update model imports

* Remove unused imports and fix formatting issues

* Refactor reactflowUtils and styleUtils

* Add CLI documentation to getting-started/cli.mdx

* Add CLI usage instructions

* Add ZoomableImage component to first-steps.mdx

* Update CLI and first steps documentation

* Remove duplicate import and add new imports for ThemedImage and useBaseUrl

* Update Langflow CLI documentation link

* Remove first-steps.mdx and update index.mdx and sidebars.js

* Update Docusaurus dependencies

* Add AstraDB RAG Flow guide

* Remove unused imports

* Remove unnecessary import statement

* Refactor guide for better readability

* Add data component documentation

* Update component headings and add prompt template

* Fix logging level and version display

* Add datetime import and buffer for alembic log

* Update flow names in NewFlowModal and documentation

* Add starter projects to sidebars.js

* Fix error handling in DirectoryReader class

* Handle exception when loading components in setup.py

* Update version numbers in pyproject.toml files

* Update build_langflow_base and build_langflow_backup in Makefile

* Added docs

* Update dependencies and build process

* Add Admonition component for API Key documentation

* Update API endpoint in async-api.mdx

* Remove async-api guidelines

* Fix UnicodeDecodeError in DirectoryReader

* Update dependency version and fix encoding issues

* Add conditional build and publish for base and main projects

* Update version to 1.0.0a2 in pyproject.toml

* Remove duplicate imports and unnecessary code in custom-component.mdx

* Fix poetry lock command in Makefile

* Update package versions in pyproject.toml

* Remove unused components and update imports

* 📦 chore(pre-release-base.yml): add pre-release workflow for base project
📦 chore(pre-release-langflow.yml): add pre-release workflow for langflow project

* Add ChatLiteLLMModelComponent to models package

* Add frontend installation and build steps

* Add Dockerfile for building and pushing base image

* Add emoji package and nest-asyncio dependency

* 📝 (components.mdx): update margin style of ZoomableImage to improve spacing
📝 (features.mdx): update margin style of ZoomableImage to improve spacing
📝 (login.mdx): update margin style of ZoomableImage to improve spacing

* Fix module import error in validate.py

* Fix error message in directory_reader.py

* Update version import and handle ImportError

* Add cryptography and langchain-openai dependencies

* Update poetry installation and remove poetry-monorepo-dependency-plugin

* Update workflow and Dockerfile for Langflow base pre-release

* Update display names and descriptions for AstraDB components

* Update installation instructions for Langflow

* Update Astra DB links and remove unnecessary imports

* Rename AstraDB

* Add new components and images

* Update HuggingFace Spaces URLs

* Update Langflow documentation and add new starter projects

* Update flow name to "Basic Prompting (Hello, world!)" in relevant files

* Update Basic Prompting flow name to "Ahoy World!"

* Remove HuggingFace Spaces documentation

* Add new files and update sidebars.js

* Remove async-tasks.mdx and update sidebars.js

* Update starter project URLs

* Enable migration of global variables

* Update OpenAIEmbeddings deployment and model

* 📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment
📝 (inputs.mdx): add margin to image style to improve spacing and center alignment

📝 (rag-with-astradb.mdx): add margin to image styles to improve spacing and readability

* Update welcome message in index.mdx

* Add global variable feature to Langflow documentation

* Reorganized sidebar categories

* Update migration documentation

* Refactor SplitTextComponent class to accept inputs of type Record and Text

* Adjust embeddings docs

* ✨ (cardComponent/index.tsx): add a minimum height to the card component to ensure consistent layout and prevent content from overlapping when the card is empty or has minimal content

* Update flow name from "Ahoy World!" to "Hello, world!"

* Update documentation for embeddings, models, and vector stores

* Update CreateRecordComponent and parameterUtils.ts

* Add documentation for Text and Record types

* Remove commented lines in sidebars.js

* Add run_flow_from_json function to load.py

* Update Langflow package to run flow from JSON file

* Fix type annotations and import errors

* Refactor tests and fix test data

---------

Co-authored-by: Rodrigo Nader <rodrigosilvanader@gmail.com>
Co-authored-by: anovazzi1 <otavio2204@gmail.com>
Co-authored-by: Lucas Oliveira <lucas.edu.oli@hotmail.com>
Co-authored-by: carlosrcoelho <carlosrodrigo.coelho@gmail.com>
Co-authored-by: cristhianzl <cristhian.lousa@gmail.com>
Co-authored-by: Matheus <jacquesmats@gmail.com>
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-04-04 02:46:44 -03:00 • committed by GitHub
commit 05cd6e4fd7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
853 changed files with 59936 additions and 15456 deletions

View file

@ -10,12 +10,14 @@ import orjson
import pytest
from fastapi.testclient import TestClient
from httpx import AsyncClient
from sqlmodel import Session, SQLModel, create_engine
from sqlmodel import Session, SQLModel, create_engine, select
from sqlmodel.pool import StaticPool
from typer.testing import CliRunner
from langflow.graph.graph.base import Graph
from langflow.initial_setup.setup import STARTER_FOLDER_NAME
from langflow.services.auth.utils import get_password_hash
from langflow.services.database.models.api_key.model import ApiKey
from langflow.services.database.models.flow.model import Flow, FlowCreate
from langflow.services.database.models.user.model import User, UserCreate
from langflow.services.database.utils import session_getter
@ -28,14 +30,17 @@ if TYPE_CHECKING:
def pytest_configure():
pytest.BASIC_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "basic_example.json"
pytest.COMPLEX_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "complex_example.json"
pytest.COMPLEX_DEPS_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "complex_deps_example.json"
pytest.OPENAPI_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "Openapi.json"
pytest.GROUPED_CHAT_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "grouped_chat.json"
pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "one_group_chat.json"
pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "vector_store_grouped.json"
pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY = (
Path(__file__).parent.absolute() / "data" / "BasicChatwithPromptandHistory.json"
Path(__file__).parent.absolute() / "data" / "BasicChatWithPromptAndHistory.json"
)
pytest.CHAT_INPUT = Path(__file__).parent.absolute() / "data" / "ChatInputTest.json"
pytest.TWO_OUTPUTS = Path(__file__).parent.absolute() / "data" / "TwoOutputsTest.json"
pytest.VECTOR_STORE_PATH = Path(__file__).parent.absolute() / "data" / "Vector_store.json"
pytest.CODE_WITH_SYNTAX_ERROR = """
def get_text():
@ -43,6 +48,13 @@ def get_text():
"""
@pytest.fixture(autouse=True)
def check_openai_api_key_in_environment_variables():
import os
assert os.environ.get("OPENAI_API_KEY") is not None, "OPENAI_API_KEY is not set in environment variables"
@pytest.fixture()
async def async_client() -> AsyncGenerator:
from langflow.main import create_app
@ -181,6 +193,16 @@ def json_vector_store():
return f.read()
@pytest.fixture
def complex_graph_with_groups():
with open(pytest.COMPLEX_DEPS_EXAMPLE_PATH, "r") as f:
flow_graph = json.load(f)
data_graph = flow_graph["data"]
nodes = data_graph["nodes"]
edges = data_graph["edges"]
return Graph(nodes, edges)
@pytest.fixture(name="client", autouse=True)
def client_fixture(session: Session, monkeypatch):
# Set the database url to a test database
@ -225,7 +247,7 @@ def test_user(client):
username="testuser",
password="testpassword",
)
response = client.post("/api/v1/users", json=user_data.model_dump())
response = client.post("/api/v1/users", json=user_data.dict())
assert response.status_code == 201
return response.json()
@ -241,7 +263,7 @@ def active_user(client):
is_superuser=False,
)
# check if user exists
if active_user := session.query(User).filter(User.username == user.username).first():
if active_user := session.exec(select(User).where(User.username == user.username)).first():
return active_user
session.add(user)
session.commit()
@ -264,13 +286,9 @@ def flow(client, json_flow: str, active_user):
from langflow.services.database.models.flow.model import FlowCreate
loaded_json = json.loads(json_flow)
flow_data = FlowCreate(
name="test_flow",
data=loaded_json.get("data"),
user_id=active_user.id,
description="description",
)
flow = Flow.model_validate(flow_data.model_dump())
flow_data = FlowCreate(name="test_flow", data=loaded_json.get("data"), user_id=active_user.id)
flow = Flow.model_validate(flow_data)
with session_getter(get_db_service()) as session:
session.add(flow)
session.commit()
@ -280,11 +298,47 @@ def flow(client, json_flow: str, active_user):
@pytest.fixture
def added_flow(client, json_flow_with_prompt_and_history, logged_in_headers):
def json_chat_input():
with open(pytest.CHAT_INPUT, "r") as f:
return f.read()
@pytest.fixture
def json_two_outputs():
with open(pytest.TWO_OUTPUTS, "r") as f:
return f.read()
@pytest.fixture
def added_flow_with_prompt_and_history(client, json_flow_with_prompt_and_history, logged_in_headers):
flow = orjson.loads(json_flow_with_prompt_and_history)
data = flow["data"]
flow = FlowCreate(name="Basic Chat", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
return response.json()
@pytest.fixture
def added_flow_chat_input(client, json_chat_input, logged_in_headers):
flow = orjson.loads(json_chat_input)
data = flow["data"]
flow = FlowCreate(name="Chat Input", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
return response.json()
@pytest.fixture
def added_flow_two_outputs(client, json_two_outputs, logged_in_headers):
flow = orjson.loads(json_two_outputs)
data = flow["data"]
flow = FlowCreate(name="Two Outputs", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
@ -296,7 +350,7 @@ def added_vector_store(client, json_vector_store, logged_in_headers):
vector_store = orjson.loads(json_vector_store)
data = vector_store["data"]
vector_store = FlowCreate(name="Vector Store", description="description", data=data)
response = client.post("api/v1/flows/", json=vector_store.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/", json=vector_store.dict(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == vector_store.name
assert response.json()["data"] == vector_store.data
@ -304,16 +358,43 @@ def added_vector_store(client, json_vector_store, logged_in_headers):
@pytest.fixture
def test_component_code():
path = Path(__file__).parent.absolute() / "data" / "component.py"
# load the content as a string
with open(path, "r") as f:
return f.read()
def created_api_key(active_user):
hashed = get_password_hash("random_key")
api_key = ApiKey(
name="test_api_key",
user_id=active_user.id,
api_key="random_key",
hashed_api_key=hashed,
)
db_manager = get_db_service()
with session_getter(db_manager) as session:
if existing_api_key := session.exec(select(ApiKey).where(ApiKey.api_key == api_key.api_key)).first():
return existing_api_key
session.add(api_key)
session.commit()
session.refresh(api_key)
return api_key
@pytest.fixture
def test_component_with_templatefield_code():
path = Path(__file__).parent.absolute() / "data" / "component_with_templatefield.py"
# load the content as a string
with open(path, "r") as f:
return f.read()
@pytest.fixture(name="starter_project")
def get_starter_project(active_user):
# once the client is created, we can get the starter project
with session_getter(get_db_service()) as session:
flow = session.exec(
select(Flow).where(Flow.folder == STARTER_FOLDER_NAME).where(Flow.name == "Basic Prompting (Hello, world!)")
).first()
if not flow:
raise ValueError("No starter project found")
new_flow_create = FlowCreate(
name=flow.name,
description=flow.description,
data=flow.data,
user_id=active_user.id,
)
new_flow = Flow.model_validate(new_flow_create, from_attributes=True)
session.add(new_flow)
session.commit()
session.refresh(new_flow)
new_flow_dict = new_flow.model_dump()
return new_flow_dict

View file

@ -0,0 +1,918 @@
{
"name": "ChatInputTest",
"description": "",
"data": {
"nodes": [
{
"width": 384,
"height": 359,
"id": "PromptTemplate-IKKOx",
"type": "genericNode",
"position": {
"x": 880,
"y": 646.9375
},
"data": {
"type": "PromptTemplate",
"node": {
"template": {
"output_parser": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "output_parser",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BaseOutputParser",
"list": false
},
"input_variables": {
"required": true,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "input_variables",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": true,
"value": [
"input"
]
},
"partial_variables": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "partial_variables",
"advanced": false,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"template": {
"required": true,
"placeholder": "",
"show": true,
"multiline": true,
"password": false,
"name": "template",
"advanced": false,
"dynamic": false,
"info": "",
"type": "prompt",
"list": false,
"value": "Input: {input}\nAI:"
},
"template_format": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": "f-string",
"password": false,
"name": "template_format",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"validate_template": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": true,
"password": false,
"name": "validate_template",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"_type": "PromptTemplate",
"input": {
"required": false,
"placeholder": "",
"show": true,
"multiline": true,
"value": "",
"password": false,
"name": "input",
"display_name": "input",
"advanced": false,
"input_types": [
"Document",
"BaseOutputParser",
"str"
],
"dynamic": false,
"info": "",
"type": "str",
"list": false
}
},
"description": "A prompt template for a language model.",
"base_classes": [
"BasePromptTemplate",
"PromptTemplate",
"StringPromptTemplate"
],
"name": "",
"display_name": "PromptTemplate",
"documentation": "https://python.langchain.com/docs/modules/model_io/prompts/prompt_templates/",
"custom_fields": {
"": [
"input"
],
"template": [
"input"
]
},
"output_types": [],
"field_formatters": {
"formatters": {
"openai_api_key": {}
},
"base_formatters": {
"kwargs": {},
"optional": {},
"list": {},
"dict": {},
"union": {},
"multiline": {},
"show": {},
"password": {},
"default": {},
"headers": {},
"dict_code_file": {},
"model_fields": {
"MODEL_DICT": {
"OpenAI": [
"text-davinci-003",
"text-davinci-002",
"text-curie-001",
"text-babbage-001",
"text-ada-001"
],
"ChatOpenAI": [
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo",
"gpt-3.5-turbo-16k-0613",
"gpt-3.5-turbo-16k",
"gpt-4-0613",
"gpt-4-32k-0613",
"gpt-4",
"gpt-4-32k"
],
"Anthropic": [
"claude-v1",
"claude-v1-100k",
"claude-instant-v1",
"claude-instant-v1-100k",
"claude-v1.3",
"claude-v1.3-100k",
"claude-v1.2",
"claude-v1.0",
"claude-instant-v1.1",
"claude-instant-v1.1-100k",
"claude-instant-v1.0"
],
"ChatAnthropic": [
"claude-v1",
"claude-v1-100k",
"claude-instant-v1",
"claude-instant-v1-100k",
"claude-v1.3",
"claude-v1.3-100k",
"claude-v1.2",
"claude-v1.0",
"claude-instant-v1.1",
"claude-instant-v1.1-100k",
"claude-instant-v1.0"
]
}
}
}
},
"beta": false,
"error": null
},
"id": "PromptTemplate-IKKOx"
},
"selected": false,
"positionAbsolute": {
"x": 880,
"y": 646.9375
},
"dragging": false
},
{
"width": 384,
"height": 307,
"id": "LLMChain-e2dhN",
"type": "genericNode",
"position": {
"x": 1449.330344958542,
"y": 880.1760221487797
},
"data": {
"type": "LLMChain",
"node": {
"template": {
"callbacks": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "callbacks",
"advanced": false,
"dynamic": false,
"info": "",
"type": "langchain.callbacks.base.BaseCallbackHandler",
"list": true
},
"llm": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "llm",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BaseLanguageModel",
"list": false
},
"memory": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "memory",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BaseMemory",
"list": false
},
"output_parser": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "output_parser",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BaseLLMOutputParser",
"list": false
},
"prompt": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "prompt",
"advanced": false,
"dynamic": false,
"info": "",
"type": "BasePromptTemplate",
"list": false
},
"llm_kwargs": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "llm_kwargs",
"advanced": false,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"metadata": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "metadata",
"advanced": false,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"output_key": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"value": "text",
"password": false,
"name": "output_key",
"advanced": true,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"return_final_only": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": true,
"password": false,
"name": "return_final_only",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"tags": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "tags",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": true
},
"verbose": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "verbose",
"advanced": true,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"_type": "LLMChain"
},
"description": "Chain to run queries against LLMs.",
"base_classes": [
"Chain",
"LLMChain",
"function",
"Text"
],
"display_name": "LLMChain",
"custom_fields": {},
"output_types": [],
"documentation": "https://python.langchain.com/docs/modules/chains/foundational/llm_chain",
"beta": false,
"error": null
},
"id": "LLMChain-e2dhN"
},
"positionAbsolute": {
"x": 1449.330344958542,
"y": 880.1760221487797
}
},
{
"width": 384,
"height": 621,
"id": "ChatOpenAI-2I57f",
"type": "genericNode",
"position": {
"x": 393.3551923753797,
"y": 1061.025177453298
},
"data": {
"type": "ChatOpenAI",
"node": {
"template": {
"callbacks": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "callbacks",
"advanced": false,
"dynamic": false,
"info": "",
"type": "langchain.callbacks.base.BaseCallbackHandler",
"list": true
},
"cache": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "cache",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"client": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "client",
"advanced": false,
"dynamic": false,
"info": "",
"type": "Any",
"list": false
},
"max_retries": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 6,
"password": false,
"name": "max_retries",
"advanced": false,
"dynamic": false,
"info": "",
"type": "int",
"list": false
},
"max_tokens": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": true,
"name": "max_tokens",
"advanced": false,
"dynamic": false,
"info": "",
"type": "int",
"list": false,
"value": ""
},
"metadata": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "metadata",
"advanced": false,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"model_kwargs": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "model_kwargs",
"advanced": true,
"dynamic": false,
"info": "",
"type": "code",
"list": false
},
"model_name": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "gpt-3.5-turbo-0613",
"password": false,
"options": [
"gpt-3.5-turbo-0613",
"gpt-3.5-turbo",
"gpt-3.5-turbo-16k-0613",
"gpt-3.5-turbo-16k",
"gpt-4-0613",
"gpt-4-32k-0613",
"gpt-4",
"gpt-4-32k"
],
"name": "model_name",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": true
},
"n": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": 1,
"password": false,
"name": "n",
"advanced": false,
"dynamic": false,
"info": "",
"type": "int",
"list": false
},
"openai_api_base": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "openai_api_base",
"display_name": "OpenAI API Base",
"advanced": false,
"dynamic": false,
"info": "\nThe base URL of the OpenAI API. Defaults to https://api.openai.com/v1.\n\nYou can change this to use other APIs like JinaChat, LocalAI and Prem.\n",
"type": "str",
"list": false
},
"openai_api_key": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "",
"password": true,
"name": "openai_api_key",
"display_name": "OpenAI API Key",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"openai_organization": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "openai_organization",
"display_name": "OpenAI Organization",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"openai_proxy": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "openai_proxy",
"display_name": "OpenAI Proxy",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"request_timeout": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "request_timeout",
"advanced": false,
"dynamic": false,
"info": "",
"type": "float",
"list": false
},
"streaming": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "streaming",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"tags": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "tags",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": true
},
"temperature": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": 0.7,
"password": false,
"name": "temperature",
"advanced": false,
"dynamic": false,
"info": "",
"type": "float",
"list": false
},
"tiktoken_model_name": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"password": false,
"name": "tiktoken_model_name",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
},
"verbose": {
"required": false,
"placeholder": "",
"show": false,
"multiline": false,
"value": false,
"password": false,
"name": "verbose",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"_type": "ChatOpenAI"
},
"description": "`OpenAI` Chat large language models API.",
"base_classes": [
"BaseChatModel",
"ChatOpenAI",
"BaseLanguageModel",
"BaseLLM"
],
"display_name": "ChatOpenAI",
"custom_fields": {},
"output_types": [],
"documentation": "https://python.langchain.com/docs/modules/model_io/models/chat/integrations/openai",
"beta": false,
"error": null
},
"id": "ChatOpenAI-2I57f"
},
"selected": false,
"positionAbsolute": {
"x": 393.3551923753797,
"y": 1061.025177453298
},
"dragging": false
},
{
"width": 384,
"height": 359,
"id": "ChatInput-207IY",
"type": "genericNode",
"position": {
"x": 415.1018926651509,
"y": 506.62736462360317
},
"data": {
"type": "ChatInput",
"node": {
"template": {
"code": {
"dynamic": true,
"required": true,
"placeholder": "",
"show": false,
"multiline": true,
"value": "from typing import Optional\nfrom langflow.custom import CustomComponent\n\n\nclass ChatInput(CustomComponent):\n display_name = \"Chat Input\"\n\n def build(self, message: Optional[str] = \"\") -> str:\n return message\n",
"password": false,
"name": "code",
"advanced": false,
"type": "code",
"list": false
},
"_type": "CustomComponent",
"message": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"value": "",
"password": false,
"name": "message",
"display_name": "message",
"advanced": false,
"dynamic": false,
"info": "",
"type": "str",
"list": false
}
},
"description": "Used to get user input from the chat.",
"base_classes": [
"str"
],
"display_name": "Chat Input",
"custom_fields": {
"message": null
},
"output_types": [
"ChatInput"
],
"documentation": "",
"beta": true,
"error": null
},
"id": "ChatInput-207IY"
},
"positionAbsolute": {
"x": 415.1018926651509,
"y": 506.62736462360317
}
},
{
"width": 384,
"height": 389,
"id": "ChatOutput-1jlJy",
"type": "genericNode",
"position": {
"x": 2002.8008888732943,
"y": 926.1397178702218
},
"data": {
"type": "ChatOutput",
"node": {
"template": {
"code": {
"dynamic": true,
"required": true,
"placeholder": "",
"show": true,
"multiline": true,
"value": "from typing import Optional, Text\nfrom langflow.api.v1.schemas import ChatMessage\nfrom langflow.services.utils import get_chat_manager\nfrom langflow.custom import CustomComponent\nfrom anyio.from_thread import start_blocking_portal\nfrom loguru import logger\n\n\nclass ChatOutput(CustomComponent):\n display_name = \"Chat Output\"\n description = \"Used to send a message to the chat.\"\n\n field_config = {\n \"code\": {\n \"show\": False,\n }\n }\n\n def build_config(self):\n return {\"message\": {\"input_types\": [\"Text\"]}}\n\n def build(self, message: Optional[Text], is_ai: bool = False) -> Text:\n if not message:\n return \"\"\n try:\n chat_manager = get_chat_manager()\n chat_message = ChatMessage(message=message, is_bot=is_ai)\n # send_message is a coroutine\n # run in a thread safe manner\n with start_blocking_portal() as portal:\n portal.call(chat_manager.send_message, chat_message)\n chat_manager.chat_history.add_message(\n chat_manager.cache_manager.current_client_id, chat_message\n )\n except Exception as exc:\n logger.exception(exc)\n logger.debug(f\"Error sending message to chat: {exc}\")\n self.repr_value = message\n return message\n",
"password": false,
"name": "code",
"advanced": false,
"type": "code",
"list": false
},
"_type": "CustomComponent",
"is_ai": {
"required": true,
"placeholder": "",
"show": true,
"multiline": false,
"value": true,
"password": false,
"name": "is_ai",
"display_name": "is_ai",
"advanced": false,
"dynamic": false,
"info": "",
"type": "bool",
"list": false
},
"message": {
"required": false,
"placeholder": "",
"show": true,
"multiline": false,
"password": false,
"name": "message",
"display_name": "message",
"advanced": false,
"input_types": [
"Text"
],
"dynamic": false,
"info": "",
"type": "Text",
"list": false
}
},
"description": "Used to send a message to the chat.",
"base_classes": [
"str"
],
"display_name": "Chat Output",
"custom_fields": {
"is_ai": null,
"message": null
},
"output_types": [
"ChatOutput"
],
"documentation": "",
"beta": true,
"error": null
},
"id": "ChatOutput-1jlJy"
},
"selected": true,
"dragging": false,
"positionAbsolute": {
"x": 2002.8008888732943,
"y": 926.1397178702218
}
}
],
"edges": [
{
"source": "PromptTemplate-IKKOx",
"sourceHandle": "PromptTemplate|PromptTemplate-IKKOx|BasePromptTemplate|PromptTemplate|StringPromptTemplate",
"target": "LLMChain-e2dhN",
"targetHandle": "BasePromptTemplate|prompt|LLMChain-e2dhN",
"style": {
"stroke": "#555"
},
"className": "",
"animated": false,
"id": "reactflow__edge-PromptTemplate-IKKOxPromptTemplate|PromptTemplate-IKKOx|StringPromptTemplate|BasePromptTemplate|PromptTemplate-LLMChain-e2dhNBasePromptTemplate|prompt|LLMChain-e2dhN"
},
{
"source": "ChatOpenAI-2I57f",
"sourceHandle": "ChatOpenAI|ChatOpenAI-2I57f|BaseChatModel|ChatOpenAI|BaseLanguageModel|BaseLLM",
"target": "LLMChain-e2dhN",
"targetHandle": "BaseLanguageModel|llm|LLMChain-e2dhN",
"style": {
"stroke": "#555"
},
"className": "",
"animated": false,
"id": "reactflow__edge-ChatOpenAI-2I57fChatOpenAI|ChatOpenAI-2I57f|BaseChatModel|ChatOpenAI|BaseLanguageModel|BaseLLM-LLMChain-e2dhNBaseLanguageModel|llm|LLMChain-e2dhN"
},
{
"source": "ChatInput-207IY",
"sourceHandle": "ChatInput|ChatInput-207IY|str",
"target": "PromptTemplate-IKKOx",
"targetHandle": "Document;BaseOutputParser;str|input|PromptTemplate-IKKOx",
"style": {
"stroke": "#555"
},
"className": "",
"animated": false,
"id": "reactflow__edge-ChatInput-207IYChatInput|ChatInput-207IY|str-PromptTemplate-IKKOxDocument;BaseOutputParser;str|input|PromptTemplate-IKKOx"
},
{
"source": "LLMChain-e2dhN",
"sourceHandle": "LLMChain|LLMChain-e2dhN|Chain|LLMChain|function|Text",
"target": "ChatOutput-1jlJy",
"targetHandle": "Text|message|ChatOutput-1jlJy",
"style": {
"stroke": "#555"
},
"className": "stroke-foreground stroke-connection",
"animated": true,
"id": "reactflow__edge-LLMChain-e2dhNLLMChain|LLMChain-e2dhN|Chain|LLMChain|function|Text-ChatOutput-1jlJyText|message|ChatOutput-1jlJy"
}
],
"viewport": {
"x": -141.98308184453367,
"y": -104.98637616656356,
"zoom": 0.4788209787464315
}
},
"id": "b3388ab9-b5dc-4447-b560-79caef40faa5",
"user_id": "c65bfea3-3eea-4e71-8fc4-106238eb0583"
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
import random
from langflow import CustomComponent
from langflow.custom import CustomComponent
class TestComponent(CustomComponent):

View file

@ -1,6 +1,6 @@
import random
from langflow import CustomComponent
from langflow.custom import CustomComponent
from langflow.field_typing import TemplateField

View file

@ -1,4 +1,5 @@
import pytest
from langflow.services.database.models.api_key import ApiKeyCreate
@ -21,7 +22,6 @@ def test_get_api_keys(client, logged_in_headers, api_key):
assert any("test-api-key" in api_key["name"] for api_key in data["api_keys"])
# assert all api keys in data["api_keys"] are masked
assert all("**" in api_key["api_key"] for api_key in data["api_keys"])
# Add more assertions as needed based on the expected data structure and content
def test_create_api_key(client, logged_in_headers):

View file

@ -39,7 +39,8 @@ def langchain_objects_are_equal(obj1, obj2):
# Test build_graph
def test_build_graph(client, basic_data_graph):
@pytest.mark.asyncio
async def test_build_graph(client, basic_data_graph):
graph = Graph.from_payload(basic_data_graph)
assert graph is not None
assert len(graph.vertices) == len(basic_data_graph["nodes"])

View file

@ -17,6 +17,7 @@ def default_settings():
def test_components_path(runner, client, default_settings):
# Create a foldr in the tmp directory
temp_dir = Path(tempdir)
# create a "components" folder
temp_dir = temp_dir / "components"
@ -34,5 +35,4 @@ def test_components_path(runner, client, default_settings):
def test_superuser(runner, client, session):
result = runner.invoke(app, ["superuser"], input="admin\nadmin\n")
assert result.exit_code == 0, result.stdout
assert "Superuser creation failed." not in result.output, result.output
assert "Superuser created successfully." in result.output, result.output
assert "Superuser created successfully." in result.stdout

View file

@ -3,14 +3,21 @@ import types
from uuid import uuid4
import pytest
from langchain_core.documents import Document
from langflow.interface.custom.base import CustomComponent
from langflow.interface.custom.code_parser.code_parser import CodeParser, CodeSyntaxError
from langflow.interface.custom.custom_component.component import Component, ComponentCodeNullError
from langflow.interface.custom.utils import build_custom_component_template
from langflow.interface.custom.code_parser.code_parser import (
CodeParser,
CodeSyntaxError,
)
from langflow.interface.custom.custom_component.component import (
Component,
ComponentCodeNullError,
)
from langflow.services.database.models.flow import Flow, FlowCreate
code_default = """
from langflow import Prompt
from langflow.field_typing import Prompt
from langflow.interface.custom.custom_component import CustomComponent
from langchain.llms.base import BaseLLM
@ -67,16 +74,16 @@ def test_component_init():
"""
Test the initialization of the Component class.
"""
component = Component(code=code_default, _function_entrypoint_name="build")
component = Component(code=code_default, function_entrypoint_name="build")
assert component.code == code_default
assert component._function_entrypoint_name == "build"
assert component.function_entrypoint_name == "build"
def test_component_get_code_tree():
"""
Test the get_code_tree method of the Component class.
"""
component = Component(code=code_default, _function_entrypoint_name="build")
component = Component(code=code_default, function_entrypoint_name="build")
tree = component.get_code_tree(component.code)
assert "imports" in tree
@ -86,22 +93,11 @@ def test_component_code_null_error():
Test the get_function method raises the
ComponentCodeNullError when the code is empty.
"""
component = Component(code="", _function_entrypoint_name="")
component = Component(code="", function_entrypoint_name="")
with pytest.raises(ComponentCodeNullError):
component.get_function()
# TODO: Validate if we should remove this
# def test_component_function_entrypoint_name_null_error():
# """
# Test the get_function method raises the ComponentFunctionEntrypointNameNullError
# when the function_entrypoint_name is empty.
# """
# component = Component(code=code_default, _function_entrypoint_name="")
# with pytest.raises(ComponentFunctionEntrypointNameNullError):
# component.get_function()
def test_custom_component_init():
"""
Test the initialization of the CustomComponent class.
@ -118,7 +114,7 @@ def test_custom_component_build_template_config():
Test the build_template_config property of the CustomComponent class.
"""
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
config = custom_component.template_config
config = custom_component.build_template_config()
assert isinstance(config, dict)
@ -127,7 +123,7 @@ def test_custom_component_get_function():
Test the get_function property of the CustomComponent class.
"""
custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build")
my_function = custom_component.get_function
my_function = custom_component.get_function()
assert isinstance(my_function, types.FunctionType)
@ -201,7 +197,7 @@ def test_component_get_function_valid():
Test the get_function method of the Component
class with valid code and function_entrypoint_name.
"""
component = Component(code="def build(): pass", _function_entrypoint_name="build")
component = Component(code="def build(): pass", function_entrypoint_name="build")
my_function = component.get_function()
assert callable(my_function)
@ -224,7 +220,6 @@ def test_custom_component_get_function_entrypoint_return_type():
Test the get_function_entrypoint_return_type
property of the CustomComponent class.
"""
from langchain_core.documents import Document
custom_component = CustomComponent(code=code_default, function_entrypoint_name="build")
return_type = custom_component.get_function_entrypoint_return_type
@ -309,7 +304,7 @@ def test_code_parser_parse_ann_assign():
stmt = ast.AnnAssign(
target=ast.Name(id="x", ctx=ast.Store()),
annotation=ast.Name(id="int", ctx=ast.Load()),
value=ast.Constant(n=1),
value=ast.Num(n=1),
simple=1,
)
result = parser.parse_ann_assign(stmt)
@ -359,11 +354,21 @@ def test_component_get_code_tree_syntax_error():
Test the get_code_tree method of the Component class
raises the CodeSyntaxError when given incorrect syntax.
"""
component = Component(code="import os as", _function_entrypoint_name="build")
component = Component(code="import os as", function_entrypoint_name="build")
with pytest.raises(CodeSyntaxError):
component.get_code_tree(component.code)
def test_custom_component_class_template_validation_no_code():
"""
Test the _class_template_validation method of the CustomComponent class
raises the HTTPException when the code is None.
"""
custom_component = CustomComponent(code=None, function_entrypoint_name="build")
with pytest.raises(TypeError):
custom_component.get_function()
def test_custom_component_get_code_tree_syntax_error():
"""
Test the get_code_tree method of the CustomComponent class
@ -518,36 +523,3 @@ def test_build_config_field_value_keys(component):
config = component.build_config()
field_values = config["fields"].values()
assert all("type" in value for value in field_values)
def test_create_and_validate_component_valid_code(test_component_code):
component = CustomComponent(code=test_component_code)
assert isinstance(component, CustomComponent)
def test_build_langchain_template_custom_component_valid_code(test_component_code):
component = CustomComponent(code=test_component_code)
frontend_node = build_custom_component_template(component)
assert isinstance(frontend_node, dict)
template = frontend_node["template"]
assert isinstance(template, dict)
assert "param" in template
param_options = template["param"]["options"]
# Now run it again with an update field
frontend_node = build_custom_component_template(component, update_field="param")
new_param_options = frontend_node["template"]["param"]["options"]
assert param_options != new_param_options
def test_build_langchain_template_custom_component_templatefield(test_component_with_templatefield_code):
component = CustomComponent(code=test_component_with_templatefield_code)
frontend_node = build_custom_component_template(component)
assert isinstance(frontend_node, dict)
template = frontend_node["template"]
assert isinstance(template, dict)
assert "param" in template
param_options = template["param"]["options"]
# Now run it again with an update field
frontend_node = build_custom_component_template(component, update_field="param")
new_param_options = frontend_node["template"]["param"]["options"]
assert param_options != new_param_options

View file

@ -1,7 +1,7 @@
# Test this:
from langflow.interface.importing.utils import get_function
import pytest
from langflow.interface.tools.custom import PythonFunctionTool, PythonFunction
from langflow.interface.custom.utils import get_function
from langflow.interface.tools.custom import PythonFunction, PythonFunctionTool
from langflow.utils import constants

View file

@ -0,0 +1,166 @@
import os
from pathlib import Path
from unittest.mock import Mock, patch
import httpx
import pytest
import respx
from httpx import Response
from langflow.components import (
data,
) # Adjust the import according to your project structure
@pytest.fixture
def api_request():
# This fixture provides an instance of APIRequest for each test case
return data.APIRequest()
@pytest.mark.asyncio
@respx.mock
async def test_successful_get_request(api_request):
# Mocking a successful GET request
url = "https://example.com/api/test"
method = "GET"
mock_response = {"success": True}
respx.get(url).mock(return_value=Response(200, json=mock_response))
# Making the request
result = await api_request.make_request(client=httpx.AsyncClient(), method=method, url=url)
# Assertions
assert result.data["status_code"] == 200
assert result.data["result"] == mock_response
@pytest.mark.asyncio
@respx.mock
async def test_failed_request(api_request):
# Mocking a failed GET request
url = "https://example.com/api/test"
method = "GET"
respx.get(url).mock(return_value=Response(404))
# Making the request
result = await api_request.make_request(client=httpx.AsyncClient(), method=method, url=url)
# Assertions
assert result.data["status_code"] == 404
@pytest.mark.asyncio
@respx.mock
async def test_timeout(api_request):
# Mocking a timeout
url = "https://example.com/api/timeout"
method = "GET"
respx.get(url).mock(side_effect=httpx.TimeoutException(message="Timeout", request=None))
# Making the request
result = await api_request.make_request(client=httpx.AsyncClient(), method=method, url=url, timeout=1)
# Assertions
assert result.data["status_code"] == 408
assert result.data["error"] == "Request timed out"
@pytest.mark.asyncio
@respx.mock
async def test_build_with_multiple_urls(api_request):
# This test depends on having a working internet connection and accessible URLs
# It's better to mock these requests using respx or a similar library
# Setup for multiple URLs
method = "GET"
urls = ["https://example.com/api/one", "https://example.com/api/two"]
# You would mock these requests similarly to the single request tests
for url in urls:
respx.get(url).mock(return_value=Response(200, json={"success": True}))
# Do I have to mock the async client?
#
# Execute the build method
results = await api_request.build(method=method, urls=urls)
# Assertions
assert len(results) == len(urls)
@patch("langflow.components.data.Directory.parallel_load_records")
@patch("langflow.components.data.Directory.retrieve_file_paths")
@patch("langflow.components.data.DirectoryComponent.resolve_path")
def test_directory_component_build_with_multithreading(
mock_resolve_path, mock_retrieve_file_paths, mock_parallel_load_records
):
# Arrange
directory_component = data.DirectoryComponent()
path = os.path.dirname(os.path.abspath(__file__))
depth = 1
max_concurrency = 2
load_hidden = False
recursive = True
silent_errors = False
use_multithreading = True
mock_resolve_path.return_value = path
mock_retrieve_file_paths.return_value = [
os.path.join(path, file) for file in os.listdir(path) if file.endswith(".py")
]
mock_parallel_load_records.return_value = [Mock()]
# Act
directory_component.build(
path,
depth,
max_concurrency,
load_hidden,
recursive,
silent_errors,
use_multithreading,
)
# Assert
mock_resolve_path.assert_called_once_with(path)
mock_retrieve_file_paths.assert_called_once_with(path, load_hidden, recursive, depth)
mock_parallel_load_records.assert_called_once_with(
mock_retrieve_file_paths.return_value, silent_errors, max_concurrency
)
def test_directory_without_mocks():
directory_component = data.DirectoryComponent()
from langflow.initial_setup import setup
from langflow.initial_setup.setup import load_starter_projects
projects = load_starter_projects()
# the setup module has a folder where the projects are stored
# the contents of that folder are in the projects variable
# the directory component can be used to load the projects
# and we can validate if the contents are the same as the projects variable
setup_path = Path(setup.__file__).parent / "starter_projects"
results = directory_component.build(str(setup_path), use_multithreading=False)
assert len(results) == len(projects)
# each result is a Record that contains the content attribute
# each are dict that are exactly the same as one of the projects
for result in results:
assert result.text in projects
# in ../docs/docs/components there are many mdx files
# check if the directory component can load them
# just check if the number of results is the same as the number of files
docs_path = Path(__file__).parent.parent / "docs" / "docs" / "components"
results = directory_component.build(str(docs_path), use_multithreading=False)
docs_files = list(docs_path.glob("*.mdx"))
assert len(results) == len(docs_files)
def test_url_component():
url_component = data.URLComponent()
# the url component can be used to load the contents of a website
records = url_component.build(["https://langflow.org"])
assert all(record.data for record in records)
assert all(record.text for record in records)
assert all(record.source for record in records)

View file

@ -3,12 +3,14 @@ from uuid import UUID, uuid4
import orjson
import pytest
from fastapi.testclient import TestClient
from sqlmodel import Session
from langflow.api.v1.schemas import FlowListCreate
from langflow.initial_setup.setup import load_starter_projects
from langflow.services.database.models.base import orjson_dumps
from langflow.services.database.models.flow import Flow, FlowCreate, FlowUpdate
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service
from sqlmodel import Session
@pytest.fixture(scope="module")
@ -29,13 +31,13 @@ def test_create_flow(client: TestClient, json_flow: str, active_user, logged_in_
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
# flow is optional so we can create a flow without a flow
flow = FlowCreate(name="Test Flow", description="description")
response = client.post("api/v1/flows/", json=flow.model_dump(exclude_unset=True), headers=logged_in_headers)
flow = FlowCreate(name="Test Flow")
response = client.post("api/v1/flows/", json=flow.dict(exclude_unset=True), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
@ -45,13 +47,13 @@ def test_read_flows(client: TestClient, json_flow: str, active_user, logged_in_h
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers)
assert response.status_code == 201
assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data
@ -65,7 +67,7 @@ def test_read_flow(client: TestClient, json_flow: str, active_user, logged_in_he
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers)
flow_id = response.json()["id"] # flow_id should be a UUID but is a string
# turn it into a UUID
flow_id = UUID(flow_id)
@ -81,7 +83,7 @@ def test_update_flow(client: TestClient, json_flow: str, active_user, logged_in_
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers)
flow_id = response.json()["id"]
updated_flow = FlowUpdate(
@ -89,7 +91,7 @@ def test_update_flow(client: TestClient, json_flow: str, active_user, logged_in_
description="updated description",
data=data,
)
response = client.patch(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
response = client.patch(f"api/v1/flows/{flow_id}", json=updated_flow.dict(), headers=logged_in_headers)
assert response.status_code == 200
assert response.json()["name"] == updated_flow.name
@ -101,7 +103,7 @@ def test_delete_flow(client: TestClient, json_flow: str, active_user, logged_in_
flow = orjson.loads(json_flow)
data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers)
flow_id = response.json()["id"]
response = client.delete(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
assert response.status_code == 200
@ -119,7 +121,7 @@ def test_create_flows(client: TestClient, session: Session, json_flow: str, logg
]
)
# Make request to endpoint
response = client.post("api/v1/flows/batch/", json=flow_list.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/batch/", json=flow_list.dict(), headers=logged_in_headers)
# Check response status code
assert response.status_code == 201
# Check response data
@ -143,7 +145,7 @@ def test_upload_file(client: TestClient, session: Session, json_flow: str, logge
FlowCreate(name="Flow 2", description="description", data=data),
]
)
file_contents = orjson_dumps(flow_list.model_dump())
file_contents = orjson_dumps(flow_list.dict())
response = client.post(
"api/v1/flows/upload/",
files={"file": ("examples.json", file_contents, "application/json")},
@ -191,7 +193,9 @@ def test_download_file(
assert response.status_code == 200, response.json()
# Check response data
response_data = response.json()["flows"]
assert len(response_data) == 2, response_data
starter_projects = load_starter_projects()
number_of_projects = len(starter_projects) + len(flow_list.flows)
assert len(response_data) == number_of_projects, response_data
assert response_data[0]["name"] == "Flow 1"
assert response_data[0]["description"] == "description"
assert response_data[0]["data"] == data
@ -216,11 +220,11 @@ def test_update_flow_idempotency(client: TestClient, json_flow: str, active_user
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
flow_data = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow_data.model_dump(), headers=logged_in_headers)
response = client.post("api/v1/flows/", json=flow_data.dict(), headers=logged_in_headers)
flow_id = response.json()["id"]
updated_flow = FlowCreate(name="Updated Flow", description="description", data=data)
response1 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
response2 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
response1 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.dict(), headers=logged_in_headers)
response2 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.dict(), headers=logged_in_headers)
assert response1.json() == response2.json()
@ -233,7 +237,7 @@ def test_update_nonexistent_flow(client: TestClient, json_flow: str, active_user
description="description",
data=data,
)
response = client.patch(f"api/v1/flows/{uuid}", json=updated_flow.model_dump(), headers=logged_in_headers)
response = client.patch(f"api/v1/flows/{uuid}", json=updated_flow.dict(), headers=logged_in_headers)
assert response.status_code == 404
@ -243,7 +247,8 @@ def test_delete_nonexistent_flow(client: TestClient, active_user, logged_in_head
assert response.status_code == 404
def test_read_empty_flows(client: TestClient, active_user, logged_in_headers):
def test_read_only_starter_projects(client: TestClient, active_user, logged_in_headers):
response = client.get("api/v1/flows/", headers=logged_in_headers)
starter_projects = load_starter_projects()
assert response.status_code == 200
assert len(response.json()) == 0
assert len(response.json()) == len(starter_projects)

View file

@ -1,59 +0,0 @@
from langflow.template.field.base import TemplateField
from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode
def test_format_jina_fields():
field = TemplateField(name="jina")
EmbeddingFrontendNode.format_jina_fields(field)
assert field.show is True
assert field.advanced is False
field = TemplateField(name="auth")
EmbeddingFrontendNode.format_jina_fields(field)
assert field.password is True
assert field.show is True
assert field.advanced is False
field = TemplateField(name="jina_api_url")
EmbeddingFrontendNode.format_jina_fields(field)
assert field.show is True
assert field.advanced is True
assert field.display_name == "Jina API URL"
assert field.password is False
def test_format_openai_fields():
field = TemplateField(name="openai")
EmbeddingFrontendNode.format_openai_fields(field)
assert field.show is True
assert field.advanced is True
assert field.display_name == "OpenAI"
field = TemplateField(name="openai_api_key")
EmbeddingFrontendNode.format_openai_fields(field)
assert field.password is True
assert field.show is True
assert field.advanced is False
def test_format_field():
field = TemplateField(name="headers")
EmbeddingFrontendNode.format_field(field)
assert field.show is False
field = TemplateField(name="jina")
EmbeddingFrontendNode.format_field(field)
assert field.advanced is False
assert field.show is True
field = TemplateField(name="openai")
EmbeddingFrontendNode.format_field(field)
assert field.advanced is True
assert field.show is True
assert field.display_name == "OpenAI"
field = TemplateField(name="test_field", required=True)
EmbeddingFrontendNode.format_field(field)
assert field.advanced is False
assert field.show is True
assert field.required is True

View file

@ -1,16 +1,11 @@
import time
import uuid
from collections import namedtuple
from uuid import uuid4
import pytest
from fastapi import status
from fastapi.testclient import TestClient
from langflow.interface.tools.constants import CUSTOM_TOOLS
from langflow.processing.process import Result
from langflow.services.auth.utils import get_password_hash
from langflow.services.database.models.api_key.model import ApiKey
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service, get_settings_service
from langflow.interface.custom.directory_reader.directory_reader import DirectoryReader
from langflow.services.deps import get_settings_service
from langflow.template.frontend_node.chains import TimeTravelGuideChainNode
@ -25,7 +20,7 @@ def run_post(client, flow_id, headers, post_data):
# Helper function to poll task status
def poll_task_status(client, headers, href, max_attempts=20, sleep_time=2):
def poll_task_status(client, headers, href, max_attempts=20, sleep_time=1):
for _ in range(max_attempts):
task_status_response = client.get(
href,
@ -114,184 +109,171 @@ PROMPT_REQUEST = {
}
@pytest.fixture
def created_api_key(active_user):
hashed = get_password_hash("random_key")
api_key = ApiKey(
name="test_api_key",
user_id=active_user.id,
api_key="random_key",
hashed_api_key=hashed,
)
db_manager = get_db_service()
with session_getter(db_manager) as session:
if existing_api_key := session.query(ApiKey).filter(ApiKey.api_key == api_key.api_key).first():
return existing_api_key
session.add(api_key)
session.commit()
session.refresh(api_key)
return api_key
# def test_process_flow_invalid_api_key(client, flow, monkeypatch):
# # Mock de process_graph_cached
# from langflow.api.v1 import endpoints
# from langflow.services.database.models.api_key import crud
# settings_service = get_settings_service()
# settings_service.auth_settings.AUTO_LOGIN = False
# async def mock_process_graph_cached(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
# def mock_update_total_uses(*args, **kwargs):
# return created_api_key
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
# headers = {"x-api-key": "invalid_api_key"}
# post_data = {
# "inputs": {"key": "value"},
# "tweaks": None,
# "clear_cache": False,
# "session_id": None,
# }
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# assert response.status_code == 403
# assert response.json() == {"detail": "Invalid or missing API key"}
def test_process_flow_invalid_api_key(client, flow, monkeypatch):
# Mock de process_graph_cached
from langflow.api.v1 import endpoints
from langflow.services.database.models.api_key import crud
# def test_process_flow_invalid_id(client, monkeypatch, created_api_key):
# async def mock_process_graph_cached(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
settings_service = get_settings_service()
settings_service.auth_settings.AUTO_LOGIN = False
# from langflow.api.v1 import endpoints
async def mock_process_graph_cached(*args, **kwargs):
return Result(result={}, session_id="session_id_mock")
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
def mock_update_total_uses(*args, **kwargs):
return created_api_key
# api_key = created_api_key.api_key
# headers = {"x-api-key": api_key}
monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
# post_data = {
# "inputs": {"key": "value"},
# "tweaks": None,
# "clear_cache": False,
# "session_id": None,
# }
headers = {"x-api-key": "invalid_api_key"}
# invalid_id = uuid.uuid4()
# response = client.post(f"api/v1/process/{invalid_id}", headers=headers, json=post_data)
post_data = {
"inputs": {"key": "value"},
"tweaks": None,
"clear_cache": False,
"session_id": None,
}
response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
assert response.status_code == 403
assert response.json() == {"detail": "Invalid or missing API key"}
# assert response.status_code == 404
# assert f"Flow {invalid_id} not found" in response.json()["detail"]
def test_process_flow_invalid_id(client, monkeypatch, created_api_key):
async def mock_process_graph_cached(*args, **kwargs):
return Result(result={}, session_id="session_id_mock")
# def test_process_flow_without_autologin(client, flow, monkeypatch, created_api_key):
# # Mock de process_graph_cached
# from langflow.api.v1 import endpoints
# from langflow.services.database.models.api_key import crud
from langflow.api.v1 import endpoints
# settings_service = get_settings_service()
# settings_service.auth_settings.AUTO_LOGIN = False
monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
# async def mock_process_graph_cached(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
api_key = created_api_key.api_key
headers = {"x-api-key": api_key}
# def mock_process_graph_cached_task(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
post_data = {
"inputs": {"key": "value"},
"tweaks": None,
"clear_cache": False,
"session_id": None,
}
# # The task function is ran like this:
# # if not self.use_celery:
# # return None, await task_func(*args, **kwargs)
# # if not hasattr(task_func, "apply"):
# # raise ValueError(f"Task function {task_func} does not have an apply method")
# # task = task_func.apply(args=args, kwargs=kwargs)
# # result = task.get()
# # return task.id, result
# # So we need to mock the task function to return a task object
# # and then mock the task object to return a result
# # maybe a named tuple would be better here
# task = namedtuple("task", ["id", "get"])
# mock_process_graph_cached_task.apply = lambda *args, **kwargs: task(
# id="task_id_mock", get=lambda: Result(result={}, session_id="session_id_mock")
# )
invalid_id = uuid.uuid4()
response = client.post(f"api/v1/process/{invalid_id}", headers=headers, json=post_data)
# def mock_update_total_uses(*args, **kwargs):
# return created_api_key
assert response.status_code == 404
assert f"Flow {invalid_id} not found" in response.json()["detail"]
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
# monkeypatch.setattr(endpoints, "process_graph_cached_task", mock_process_graph_cached_task)
# api_key = created_api_key.api_key
# headers = {"x-api-key": api_key}
# # Dummy POST data
# post_data = {
# "inputs": {"input": "value"},
# "tweaks": None,
# "clear_cache": False,
# "session_id": None,
# }
# # Make the request to the FastAPI TestClient
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# # Check the response
# assert response.status_code == 200, response.json()
# assert response.json()["result"] == {}, response.json()
# assert response.json()["session_id"] == "session_id_mock", response.json()
def test_process_flow_without_autologin(client, flow, monkeypatch, created_api_key):
# Mock de process_graph_cached
from langflow.api.v1 import endpoints
from langflow.services.database.models.api_key import crud
# def test_process_flow_fails_autologin_off(client, flow, monkeypatch):
# # Mock de process_graph_cached
# from langflow.api.v1 import endpoints
# from langflow.services.database.models.api_key import crud
settings_service = get_settings_service()
settings_service.auth_settings.AUTO_LOGIN = False
# settings_service = get_settings_service()
# settings_service.auth_settings.AUTO_LOGIN = False
async def mock_process_graph_cached(*args, **kwargs):
return Result(result={}, session_id="session_id_mock")
# async def mock_process_graph_cached(*args, **kwargs):
# return Result(result={}, session_id="session_id_mock")
def mock_process_graph_cached_task(*args, **kwargs):
return Result(result={}, session_id="session_id_mock")
# async def mock_update_total_uses(*args, **kwargs):
# return created_api_key
# The task function is ran like this:
# if not self.use_celery:
# return None, await task_func(*args, **kwargs)
# if not hasattr(task_func, "apply"):
# raise ValueError(f"Task function {task_func} does not have an apply method")
# task = task_func.apply(args=args, kwargs=kwargs)
# result = task.get()
# return task.id, result
# So we need to mock the task function to return a task object
# and then mock the task object to return a result
# maybe a named tuple would be better here
task = namedtuple("task", ["id", "get"])
mock_process_graph_cached_task.apply = lambda *args, **kwargs: task(
id="task_id_mock", get=lambda: Result(result={}, session_id="session_id_mock")
)
# monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
# monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
def mock_update_total_uses(*args, **kwargs):
return created_api_key
# headers = {"x-api-key": "api_key"}
monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
monkeypatch.setattr(endpoints, "process_graph_cached_task", mock_process_graph_cached_task)
# # Dummy POST data
# post_data = {
# "inputs": {"key": "value"},
# "tweaks": None,
# "clear_cache": False,
# "session_id": None,
# }
api_key = created_api_key.api_key
headers = {"x-api-key": api_key}
# # Make the request to the FastAPI TestClient
# Dummy POST data
post_data = {
"inputs": {"input": "value"},
"tweaks": None,
"clear_cache": False,
"session_id": None,
}
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# Make the request to the FastAPI TestClient
response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# Check the response
assert response.status_code == 200, response.json()
assert response.json()["result"] == {}, response.json()
assert response.json()["session_id"] == "session_id_mock", response.json()
def test_process_flow_fails_autologin_off(client, flow, monkeypatch):
# Mock de process_graph_cached
from langflow.api.v1 import endpoints
from langflow.services.database.models.api_key import crud
settings_service = get_settings_service()
settings_service.auth_settings.AUTO_LOGIN = False
async def mock_process_graph_cached(*args, **kwargs):
return Result(result={}, session_id="session_id_mock")
async def mock_update_total_uses(*args, **kwargs):
return created_api_key
monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached)
monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses)
headers = {"x-api-key": "api_key"}
# Dummy POST data
post_data = {
"inputs": {"key": "value"},
"tweaks": None,
"clear_cache": False,
"session_id": None,
}
# Make the request to the FastAPI TestClient
response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# Check the response
assert response.status_code == 403, response.json()
assert response.json() == {"detail": "Invalid or missing API key"}
# # Check the response
# assert response.status_code == 403, response.json()
# assert response.json() == {"detail": "Invalid or missing API key"}
def test_get_all(client: TestClient, logged_in_headers):
response = client.get("api/v1/all", headers=logged_in_headers)
assert response.status_code == 200
settings = get_settings_service().settings
dir_reader = DirectoryReader(settings.COMPONENTS_PATH[0])
files = dir_reader.get_files()
# json_response is a dict of dicts
all_names = [component_name for _, components in response.json().items() for component_name in components]
json_response = response.json()
# We need to test the custom nodes
assert "PromptTemplate" in json_response["prompts"]
# All CUSTOM_TOOLS(dict) should be in the response
assert all(tool in json_response["tools"] for tool in CUSTOM_TOOLS.keys())
assert len(all_names) > len(files)
assert "ChatInput" in json_response["inputs"]
assert "Prompt" in json_response["inputs"]
assert "ChatOutput" in json_response["outputs"]
def test_post_validate_code(client: TestClient):
@ -409,173 +391,345 @@ def test_various_prompts(client, prompt, expected_input_variables):
assert response.json()["input_variables"] == expected_input_variables
def test_basic_chat_in_process(client, added_flow, created_api_key):
# Run the /api/v1/process/{flow_id} endpoint
def test_get_vertices_flow_not_found(client, logged_in_headers):
response = client.get("/api/v1/build/nonexistent_id/vertices", headers=logged_in_headers)
assert response.status_code == 500 # Or whatever status code you've set for invalid ID
def test_get_vertices(client, added_flow_with_prompt_and_history, logged_in_headers):
flow_id = added_flow_with_prompt_and_history["id"]
response = client.get(f"/api/v1/build/{flow_id}/vertices", headers=logged_in_headers)
assert response.status_code == 200
assert "ids" in response.json()
# The response should contain the list in this order
# ['ConversationBufferMemory-Lu2Nb', 'PromptTemplate-5Q0W8', 'ChatOpenAI-vy7fV', 'LLMChain-UjBh1']
# The important part is before the - (ConversationBufferMemory, PromptTemplate, ChatOpenAI, LLMChain)
ids = [_id.split("-")[0] for _id in response.json()["ids"]]
assert ids == [
"ChatOpenAI",
"PromptTemplate",
"ConversationBufferMemory",
]
def test_build_vertex_invalid_flow_id(client, logged_in_headers):
response = client.post("/api/v1/build/nonexistent_id/vertices/vertex_id", headers=logged_in_headers)
assert response.status_code == 500
def test_build_vertex_invalid_vertex_id(client, added_flow_with_prompt_and_history, logged_in_headers):
flow_id = added_flow_with_prompt_and_history["id"]
response = client.post(f"/api/v1/build/{flow_id}/vertices/invalid_vertex_id", headers=logged_in_headers)
assert response.status_code == 500
def test_successful_run_no_payload(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
post_data = {"inputs": {"text": "Hi, My name is Gabriel"}}
response = client.post(
f"api/v1/process/{added_flow.get('id')}",
headers=headers,
json=post_data,
)
assert response.status_code == 200, response.json()
# Check the response
assert "Gabriel" in response.json()["result"]["text"]
# session_id should be returned
assert "session_id" in response.json()
assert response.json()["session_id"] is not None
# New request with the same session_id
# asking "What is my name?" should return "Gabriel"
post_data = {
"inputs": {"text": "What is my name?"},
"session_id": response.json()["session_id"],
flow_id = starter_project["id"]
response = client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": ""}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 1
ids = [output.get("component_id") for output in outputs_dict.get("outputs")]
assert all(["ChatOutput" in _id for _id in ids])
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
assert all([name in display_names for name in ["Chat Output"]])
inner_results = [output.get("results").get("result") for output in outputs_dict.get("outputs")]
assert all([len(result) > 0 for result in inner_results]), inner_results
def test_successful_run_with_output_type_text(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]
payload = {
"output_type": "text",
}
response = client.post(
f"api/v1/process/{added_flow.get('id')}",
headers=headers,
json=post_data,
)
assert response.status_code == 200, response.json()
assert "Gabriel" in response.json()["result"]["text"]
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": ""}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 1
ids = [output.get("component_id") for output in outputs_dict.get("outputs")]
assert all(["TextOutput" in _id for _id in ids]), ids
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
assert all([name in display_names for name in ["Prompt Output"]]), display_names
inner_results = [output.get("results").get("result") for output in outputs_dict.get("outputs")]
expected_result = "Langflow"
assert all([expected_result in result for result in inner_results]), inner_results
def test_basic_chat_different_session_ids(client, added_flow, created_api_key):
# Run the /api/v1/process/{flow_id} endpoint
def test_successful_run_with_output_type_any(client, starter_project, created_api_key):
# This one should have both the ChatOutput and TextOutput components
headers = {"x-api-key": created_api_key.api_key}
post_data = {"inputs": {"text": "Hi, My name is Gabriel"}}
response = client.post(
f"api/v1/process/{added_flow.get('id')}",
headers=headers,
json=post_data,
)
assert response.status_code == 200, response.json()
# Check the response
assert "Gabriel" in response.json()["result"]["text"]
# session_id should be returned
assert "session_id" in response.json()
assert response.json()["session_id"] is not None
session_id1 = response.json()["session_id"]
# New request with a different session_id
# asking "What is my name?" should return "Gabriel"
post_data = {
"inputs": {"text": "What is my name?"},
flow_id = starter_project["id"]
payload = {
"output_type": "any",
}
response = client.post(
f"api/v1/process/{added_flow.get('id')}",
headers=headers,
json=post_data,
)
assert response.status_code == 200, response.json()
assert "Gabriel" not in response.json()["result"]["text"]
assert session_id1 != response.json()["session_id"]
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": ""}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 2
ids = [output.get("component_id") for output in outputs_dict.get("outputs")]
assert all(["ChatOutput" in _id or "TextOutput" in _id for _id in ids]), ids
display_names = [output.get("component_display_name") for output in outputs_dict.get("outputs")]
assert all([name in display_names for name in ["Chat Output", "Prompt Output"]]), display_names
inner_results = [output.get("results").get("result") for output in outputs_dict.get("outputs")]
expected_result = "Langflow"
assert all([expected_result in result for result in inner_results]), inner_results
def test_basic_chat_with_two_session_ids_and_names(client, added_flow, created_api_key):
def test_successful_run_with_output_type_debug(client, starter_project, created_api_key):
# This one should return outputs for all components
# Let's just check the amount of outputs(there hsould be 7)
headers = {"x-api-key": created_api_key.api_key}
flow_id = added_flow.get("id")
names = ["Gabriel", "John"]
session_ids = []
for name in names:
post_data = {"inputs": {"text": f"Hi, My name is {name}"}}
response_json = run_post(client, flow_id, headers, post_data)
assert name in response_json["result"]["text"]
assert "session_id" in response_json
assert response_json["session_id"] is not None
session_ids.append(response_json["session_id"])
for i, name in enumerate(names):
post_data = {
"inputs": {"text": "What is my name?"},
"session_id": session_ids[i],
}
response_json = run_post(client, flow_id, headers, post_data)
assert name in response_json["result"]["text"]
flow_id = starter_project["id"]
payload = {
"output_type": "debug",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": ""}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 7
@pytest.mark.async_test
def test_vector_store_in_process(distributed_client, added_vector_store, created_api_key):
# Run the /api/v1/process/{flow_id} endpoint
# To test input_type wel'l just set it with output_type debug and check if the value is correct
def test_successful_run_with_input_type_text(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
post_data = {"inputs": {"input": "What is Langflow?"}}
response = distributed_client.post(
f"api/v1/process/{added_vector_store.get('id')}",
headers=headers,
json=post_data,
)
assert response.status_code == 200, response.json()
# Check the response
assert "Langflow" in response.json()["result"]["output"]
# session_id should be returned
assert "session_id" in response.json()
assert response.json()["session_id"] is not None
flow_id = starter_project["id"]
payload = {
"input_type": "text",
"output_type": "debug",
"input_value": "value1",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": "value1"}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 7
# Now we get all components that contain TextInput in the component_id
text_input_outputs = [output for output in outputs_dict.get("outputs") if "TextInput" in output.get("component_id")]
assert len(text_input_outputs) == 2
# Now we check if the input_value is correct
assert all([output.get("results").get("result") == "value1" for output in text_input_outputs]), text_input_outputs
# Test function without loop
@pytest.mark.async_test
def test_async_task_processing(distributed_client, added_flow, created_api_key):
# Now do the same for "chat" input type
def test_successful_run_with_input_type_chat(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
post_data = {"inputs": {"text": "Hi, My name is Gabriel"}}
# Run the /api/v1/process/{flow_id} endpoint with sync=False
response = distributed_client.post(
f"api/v1/process/{added_flow.get('id')}",
headers=headers,
json={**post_data, "sync": False},
)
assert response.status_code == 200, response.json()
# Extract the task ID from the response
task = response.json().get("task")
task_id = task.get("id")
task_href = task.get("href")
assert task_id is not None
assert task_href is not None
assert task_href == f"api/v1/task/{task_id}"
# Polling the task status using the helper function
task_status_json = poll_task_status(distributed_client, headers, task_href)
assert task_status_json is not None, "Task did not complete in time"
# Validate that the task completed successfully and the result is as expected
assert "result" in task_status_json, task_status_json
assert "text" in task_status_json["result"], task_status_json["result"]
assert "Gabriel" in task_status_json["result"]["text"], task_status_json["result"]
flow_id = starter_project["id"]
payload = {
"input_type": "chat",
"output_type": "debug",
"input_value": "value1",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": "value1"}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 7
# Now we get all components that contain TextInput in the component_id
chat_input_outputs = [output for output in outputs_dict.get("outputs") if "ChatInput" in output.get("component_id")]
assert len(chat_input_outputs) == 1
# Now we check if the input_value is correct
assert all([output.get("results").get("result") == "value1" for output in chat_input_outputs]), chat_input_outputs
# ! Deactivating this until updating the test
# Test function without loop
# @pytest.mark.async_test
# def test_async_task_processing_vector_store(client, added_vector_store, created_api_key):
# headers = {"x-api-key": created_api_key.api_key}
# post_data = {"inputs": {"input": "How do I upload examples?"}}
def test_successful_run_with_input_type_any(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]
payload = {
"input_type": "any",
"output_type": "debug",
"input_value": "value1",
}
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content
json_response = response.json()
assert "session_id" in json_response
assert "outputs" in json_response
outer_outputs = json_response["outputs"]
assert len(outer_outputs) == 1
outputs_dict = outer_outputs[0]
assert len(outputs_dict) == 2
assert "inputs" in outputs_dict
assert "outputs" in outputs_dict
assert outputs_dict.get("inputs") == {"input_value": "value1"}
assert isinstance(outputs_dict.get("outputs"), list)
assert len(outputs_dict.get("outputs")) == 7
# Now we get all components that contain TextInput or ChatInput in the component_id
any_input_outputs = [
output
for output in outputs_dict.get("outputs")
if "TextInput" in output.get("component_id") or "ChatInput" in output.get("component_id")
]
assert len(any_input_outputs) == 3
# Now we check if the input_value is correct
assert all([output.get("results").get("result") == "value1" for output in any_input_outputs]), any_input_outputs
# # Run the /api/v1/process/{flow_id} endpoint with sync=False
# response = client.post(
# f"api/v1/process/{added_vector_store.get('id')}",
# headers=headers,
# json={**post_data, "sync": False},
# )
# assert response.status_code == 200, response.json()
# assert "result" in response.json()
# assert "FAILURE" not in response.json()["result"]
# # Extract the task ID from the response
# task = response.json().get("task")
# task_id = task.get("id")
# task_href = task.get("href")
# assert task_id is not None
# assert task_href is not None
# assert task_href == f"api/v1/task/{task_id}"
def test_run_with_inputs_and_outputs(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]
payload = {
"input_value": "value1",
"input_type": "text",
"output_type": "text",
"tweaks": {"parameter_name": "value"},
"stream": False,
}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_200_OK, response.text
# Validate the response structure and content
# # Polling the task status using the helper function
# task_status_json = poll_task_status(client, headers, task_href)
# assert task_status_json is not None, "Task did not complete in time"
# # Validate that the task completed successfully and the result is as expected
# assert "result" in task_status_json, task_status_json
# assert "output" in task_status_json["result"], task_status_json["result"]
# assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"]
def test_invalid_flow_id(client, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = "invalid-flow-id"
response = client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND
# Check if the error detail is as expected
def test_run_flow_with_caching_success(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"]
headers = {"x-api-key": created_api_key.api_key}
payload = {
"input_value": "value1",
"input_type": "text",
"output_type": "text",
"tweaks": {"parameter_name": "value"},
"stream": False,
}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_200_OK
data = response.json()
assert "outputs" in data
assert "session_id" in data
def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_key):
invalid_flow_id = uuid4()
headers = {"x-api-key": created_api_key.api_key}
payload = {"input_value": "", "input_type": "text", "output_type": "text", "tweaks": {}, "stream": False}
response = client.post(f"/api/v1/run/{invalid_flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND
data = response.json()
assert "detail" in data
assert f"Flow {invalid_flow_id} not found" in data["detail"]
def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"]
headers = {"x-api-key": created_api_key.api_key}
payload = {"input_value": {"key": "value"}, "input_type": "text", "output_type": "text", "tweaks": {}}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
def test_run_flow_with_session_id(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]
payload = {
"input_value": "value1",
"input_type": "text",
"output_type": "text",
"session_id": "test-session-id",
}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND
data = response.json()
assert {"detail": "Session test-session-id not found"} == data
def test_run_flow_with_invalid_session_id(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]
payload = {
"input_value": "value1",
"input_type": "text",
"output_type": "text",
"session_id": "invalid-session-id",
}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND
data = response.json()
assert "detail" in data
assert f"Session {payload['session_id']} not found" in data["detail"]
def test_run_flow_with_invalid_tweaks(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"]
payload = {
"input_value": "value1",
"input_type": "text",
"output_type": "text",
"tweaks": {"invalid_tweak": "value"},
}
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_200_OK

104
tests/test_files.py Normal file
View file

@ -0,0 +1,104 @@
from unittest.mock import MagicMock
import pytest
from langflow.services.deps import get_storage_service
from langflow.services.storage.service import StorageService
@pytest.fixture
def mock_storage_service():
# Create a mock instance of StorageService
service = MagicMock(spec=StorageService)
# Setup mock behaviors for the service methods as needed
service.save_file.return_value = None
service.get_file.return_value = b"file content" # Binary content for files
service.list_files.return_value = ["file1.txt", "file2.jpg"]
service.delete_file.return_value = None
return service
def test_upload_file(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
# Replace the actual storage service with the mock
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.post(
f"api/v1/files/upload/{flow.id}",
files={"file": ("test.txt", b"test content")},
headers=headers,
)
assert response.status_code == 201
assert response.json() == {
"flowId": str(flow.id),
"file_path": f"{flow.id}/test.txt",
}
def test_download_file(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.get(f"api/v1/files/download/{flow.id}/test.txt", headers=headers)
assert response.status_code == 200
assert response.content == b"file content"
def test_list_files(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.get(f"api/v1/files/list/{flow.id}", headers=headers)
assert response.status_code == 200
assert response.json() == {"files": ["file1.txt", "file2.jpg"]}
def test_delete_file(client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
response = client.delete(f"api/v1/files/delete/{flow.id}/test.txt", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": "File test.txt deleted successfully"}
def test_file_operations(client, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
flow_id = flow.id
file_name = "test.txt"
file_content = b"Hello, world!"
# Step 1: Upload the file
response = client.post(
f"api/v1/files/upload/{flow_id}",
files={"file": (file_name, file_content)},
headers=headers,
)
assert response.status_code == 201
assert response.json() == {
"flowId": str(flow_id),
"file_path": f"{flow_id}/{file_name}",
}
# Step 2: List files in the folder
response = client.get(f"api/v1/files/list/{flow_id}", headers=headers)
assert response.status_code == 200
assert file_name in response.json()["files"]
# Step 3: Download the file and verify its content
response = client.get(f"api/v1/files/download/{flow_id}/{file_name}", headers=headers)
assert response.status_code == 200
assert response.content == file_content
# the headers are application/octet-stream
assert response.headers["content-type"] == "application/octet-stream"
# mime_type is inside media_type
# Step 4: Delete the file
response = client.delete(f"api/v1/files/delete/{flow_id}/{file_name}", headers=headers)
assert response.status_code == 200
assert response.json() == {"message": f"File {file_name} deleted successfully"}
# Verify that the file is indeed deleted
response = client.get(f"api/v1/files/list/{flow_id}", headers=headers)
assert file_name not in response.json()["files"]

View file

@ -1,14 +1,9 @@
import copy
import json
import os
import pickle
from pathlib import Path
from typing import Type, Union
import pytest
from langchain.agents import AgentExecutor
from langchain.chains.base import Chain
from langchain.llms.fake import FakeListLLM
from langflow.graph import Graph
from langflow.graph.edge.base import Edge
@ -21,10 +16,8 @@ from langflow.graph.graph.utils import (
update_target_handle,
update_template,
)
from langflow.graph.utils import UnbuiltObject
from langflow.graph.vertex.base import Vertex
from langflow.graph.vertex.types import FileToolVertex, LLMVertex, ToolkitVertex
from langflow.processing.process import get_result_and_thought
from langflow.initial_setup.setup import load_starter_projects
from langflow.utils.payload import get_root_vertex
# Test cases for the graph module
@ -82,8 +75,10 @@ def test_graph_structure(basic_graph):
assert isinstance(node, Vertex)
for edge in basic_graph.edges:
assert isinstance(edge, Edge)
assert edge.source_id in basic_graph.vertex_map.keys()
assert edge.target_id in basic_graph.vertex_map.keys()
source_vertex = basic_graph.get_vertex(edge.source_id)
target_vertex = basic_graph.get_vertex(edge.target_id)
assert source_vertex in basic_graph.vertices
assert target_vertex in basic_graph.vertices
def test_circular_dependencies(basic_graph):
@ -152,55 +147,6 @@ def test_get_node_neighbors_basic(basic_graph):
assert any("OpenAI" in neighbor.data["type"] for neighbor, val in neighbors.items() if val)
# def test_get_node_neighbors_complex(complex_graph):
# """Test getting node neighbors"""
# assert isinstance(complex_graph, Graph)
# # Get root node
# root = get_root_node(complex_graph)
# assert root is not None
# neighbors = complex_graph.get_nodes_with_target(root)
# assert neighbors is not None
# # Neighbors should be a list of nodes
# assert isinstance(neighbors, list)
# # Root Node is an Agent, it requires an LLMChain and tools
# # We need to check if there is a Chain in the one of the neighbors'
# assert any("Chain" in neighbor.data["type"] for neighbor in neighbors)
# # assert Tool is in the neighbors
# assert any("Tool" in neighbor.data["type"] for neighbor in neighbors)
# # Now on to the Chain's neighbors
# chain = next(neighbor for neighbor in neighbors if "Chain" in neighbor.data["type"])
# chain_neighbors = complex_graph.get_nodes_with_target(chain)
# assert chain_neighbors is not None
# # Check if there is a LLM in the chain's neighbors
# assert any("OpenAI" in neighbor.data["type"] for neighbor in chain_neighbors)
# # Chain should have a Prompt as a neighbor
# assert any("Prompt" in neighbor.data["type"] for neighbor in chain_neighbors)
# # Now on to the Tool's neighbors
# tool = next(neighbor for neighbor in neighbors if "Tool" in neighbor.data["type"])
# tool_neighbors = complex_graph.get_nodes_with_target(tool)
# assert tool_neighbors is not None
# # Check if there is an Agent in the tool's neighbors
# assert any("Agent" in neighbor.data["type"] for neighbor in tool_neighbors)
# # This Agent has a Tool that has a PythonFunction as func
# agent = next(
# neighbor for neighbor in tool_neighbors if "Agent" in neighbor.data["type"]
# )
# agent_neighbors = complex_graph.get_nodes_with_target(agent)
# assert agent_neighbors is not None
# # Check if there is a Tool in the agent's neighbors
# assert any("Tool" in neighbor.data["type"] for neighbor in agent_neighbors)
# # This Tool has a PythonFunction as func
# tool = next(
# neighbor for neighbor in agent_neighbors if "Tool" in neighbor.data["type"]
# )
# tool_neighbors = complex_graph.get_nodes_with_target(tool)
# assert tool_neighbors is not None
# # Check if there is a PythonFunction in the tool's neighbors
# assert any(
# "PythonFunctionTool" in neighbor.data["type"] for neighbor in tool_neighbors
# )
def test_get_node(basic_graph):
"""Test getting a single node"""
node_id = basic_graph.vertices[0].id
@ -222,7 +168,6 @@ def test_build_edges(basic_graph):
assert len(basic_graph.edges) == len(basic_graph._edges)
for edge in basic_graph.edges:
assert isinstance(edge, Edge)
assert isinstance(edge.source_id, str)
assert isinstance(edge.target_id, str)
@ -281,83 +226,11 @@ def test_build_params(basic_graph):
assert "memory" in root.params
@pytest.mark.asyncio
async def test_build(basic_graph):
"""Test Node's build method"""
await assert_agent_was_built(basic_graph)
async def assert_agent_was_built(graph):
"""Assert that the agent was built"""
assert isinstance(graph, Graph)
# Now we test the build method
# Build the Agent
result = await graph.build()
# The agent should be a AgentExecutor
assert isinstance(result, Chain)
@pytest.mark.asyncio
async def test_llm_node_build(basic_graph):
llm_node = get_node_by_type(basic_graph, LLMVertex)
assert llm_node is not None
built_object = await llm_node.build()
assert built_object is not UnbuiltObject()
@pytest.mark.asyncio
async def test_toolkit_node_build(client, openapi_graph):
# Write a file to the disk
file_path = "api-with-examples.yaml"
with open(file_path, "w") as f:
f.write("openapi: 3.0.0")
toolkit_node = get_node_by_type(openapi_graph, ToolkitVertex)
assert toolkit_node is not None
built_object = await toolkit_node.build()
assert built_object is not UnbuiltObject
# Remove the file
os.remove(file_path)
assert not Path(file_path).exists()
@pytest.mark.asyncio
async def test_file_tool_node_build(client, openapi_graph):
file_path = "api-with-examples.yaml"
with open(file_path, "w") as f:
f.write("openapi: 3.0.0")
assert Path(file_path).exists()
file_tool_node = get_node_by_type(openapi_graph, FileToolVertex)
assert file_tool_node is not UnbuiltObject and file_tool_node is not None
built_object = await file_tool_node.build()
assert built_object is not UnbuiltObject
# Remove the file
os.remove(file_path)
assert not Path(file_path).exists()
@pytest.mark.asyncio
async def test_get_result_and_thought(basic_graph):
"""Test the get_result_and_thought method"""
responses = [
"Final Answer: I am a response",
]
message = {"input": "Hello"}
# Find the node that is an LLMNode and change the
# _built_object to a FakeListLLM
llm_node = get_node_by_type(basic_graph, LLMVertex)
assert llm_node is not None
llm_node._built_object = FakeListLLM(responses=responses)
llm_node._built = True
langchain_object = await basic_graph.build()
# assert all nodes are built
assert all(node._built for node in basic_graph.vertices)
# now build again and check if FakeListLLM was used
# Get the result and thought
result = get_result_and_thought(langchain_object, message)
assert isinstance(result, dict)
# def test_wrapper_node_build(openapi_graph):
# wrapper_node = get_node_by_type(openapi_graph, WrapperVertex)
# assert wrapper_node is not None
# built_object = wrapper_node.build()
# assert built_object is not None
def test_find_last_node(grouped_chat_json_flow):
@ -471,12 +344,10 @@ def test_update_template(sample_template, sample_nodes):
node2_updated = next((n for n in nodes_copy if n["id"] == "node2"), None)
node3_updated = next((n for n in nodes_copy if n["id"] == "node3"), None)
assert node1_updated is not None
assert node1_updated["data"]["node"]["template"]["some_field"]["show"] is True
assert node1_updated["data"]["node"]["template"]["some_field"]["advanced"] is False
assert node1_updated["data"]["node"]["template"]["some_field"]["display_name"] == "Name1"
assert node2_updated is not None
assert node2_updated["data"]["node"]["template"]["other_field"]["show"] is False
assert node2_updated["data"]["node"]["template"]["other_field"]["advanced"] is True
assert node2_updated["data"]["node"]["template"]["other_field"]["display_name"] == "DisplayName2"
@ -537,27 +408,37 @@ def test_update_source_handle():
@pytest.mark.asyncio
async def test_pickle_graph(json_vector_store):
loaded_json = json.loads(json_vector_store)
graph = Graph.from_payload(loaded_json)
starter_projects = load_starter_projects()
data = starter_projects[0]["data"]
graph = Graph.from_payload(data)
assert isinstance(graph, Graph)
first_result = await graph.build()
assert isinstance(first_result, AgentExecutor)
pickled = pickle.dumps(graph)
assert pickled is not UnbuiltObject
assert pickled is not None
unpickled = pickle.loads(pickled)
assert unpickled is not UnbuiltObject
result = await unpickled.build()
assert isinstance(result, AgentExecutor)
assert unpickled is not None
@pytest.mark.asyncio
async def test_pickle_each_vertex(json_vector_store):
loaded_json = json.loads(json_vector_store)
graph = Graph.from_payload(loaded_json)
starter_projects = load_starter_projects()
data = starter_projects[0]["data"]
graph = Graph.from_payload(data)
assert isinstance(graph, Graph)
for vertex in graph.vertices:
await vertex.build()
pickled = pickle.dumps(vertex)
assert pickled is not UnbuiltObject
assert pickled is not None
unpickled = pickle.loads(pickled)
assert unpickled is not UnbuiltObject
assert unpickled is not None
@pytest.mark.asyncio
async def test_build_ordering(complex_graph_with_groups):
sorted_vertices = complex_graph_with_groups.sort_vertices(stop_component_id="ChatInput-Ay8QQ")
assert sorted_vertices == [
"ChatInput-Ay8QQ",
"RecordsAsText-vkx2A",
"FileLoader-Vo1Cq",
]
sorted_vertices = complex_graph_with_groups.sort_vertices()

View file

@ -0,0 +1,80 @@
from langchain_core.documents import Document
from langflow.components import helpers
from langflow.interface.custom.utils import build_custom_component_template
from langflow.schema import Record
def test_update_record_component():
# Arrange
update_record_component = helpers.UpdateRecordComponent()
# Act
new_data = {"new_key": "new_value"}
existing_record = Record(data={"existing_key": "existing_value"})
result = update_record_component.build(existing_record, new_data)
assert result.data == {"existing_key": "existing_value", "new_key": "new_value"}
assert result.existing_key == "existing_value"
assert result.new_key == "new_value"
def test_document_to_record_component():
# Arrange
document_to_record_component = helpers.DocumentToRecordComponent()
# Act
# Replace with your actual test data
document = Document(page_content="key: value", metadata={"url": "https://example.com"})
result = document_to_record_component.build(document)
# Assert
# Replace with your actual expected result
assert result == [Record(data={"text": "key: value", "url": "https://example.com"})]
def test_uuid_generator_component():
# Arrange
uuid_generator_component = helpers.UUIDGeneratorComponent()
uuid_generator_component.code = open(helpers.IDGenerator.__file__, "r").read()
frontend_node, _ = build_custom_component_template(uuid_generator_component)
# Act
build_config = frontend_node.get("template")
field_name = "unique_id"
build_config = uuid_generator_component.update_build_config(build_config, None, field_name)
unique_id = build_config["unique_id"]["value"]
result = uuid_generator_component.build(unique_id)
# Assert
# UUID should be a string of length 36
assert isinstance(result, str)
assert len(result) == 36
def test_records_as_text_component():
# Arrange
records_as_text_component = helpers.RecordsToTextComponent()
# Act
# Replace with your actual test data
records = [Record(data={"key": "value", "bacon": "eggs"})]
template = "Data:{data} -- Bacon:{bacon}"
result = records_as_text_component.build(records, template=template)
# Assert
# Replace with your actual expected result
assert result == "Data:{'key': 'value', 'bacon': 'eggs'} -- Bacon:eggs"
def test_text_to_record_component():
# Arrange
text_to_record_component = helpers.CreateRecordComponent()
# Act
# Replace with your actual test data
dict_with_text = {"field_1": {"key": "value"}}
result = text_to_record_component.build(number_of_fields=1, **dict_with_text)
# Assert
# Replace with your actual expected result
assert result == Record(data={"key": "value"})

View file

@ -0,0 +1,92 @@
from datetime import datetime
import pytest
from langflow.graph.graph.base import Graph
from langflow.graph.schema import RunOutputs
from langflow.initial_setup.setup import (
STARTER_FOLDER_NAME,
create_or_update_starter_projects,
get_project_data,
load_starter_projects,
)
from langflow.memory import delete_messages
from langflow.services.database.models.flow.model import Flow
from langflow.services.deps import session_scope
from sqlalchemy import func
from sqlmodel import select
def test_load_starter_projects():
projects = load_starter_projects()
assert isinstance(projects, list)
assert all(isinstance(project, dict) for project in projects)
def test_get_project_data():
projects = load_starter_projects()
for _, project in projects:
(
project_name,
project_description,
project_is_component,
updated_at_datetime,
project_data,
project_icon,
project_icon_bg_color,
) = get_project_data(project)
assert isinstance(project_name, str)
assert isinstance(project_description, str)
assert isinstance(project_is_component, bool)
assert isinstance(updated_at_datetime, datetime)
assert isinstance(project_data, dict)
assert isinstance(project_icon, str) or project_icon is None
assert isinstance(project_icon_bg_color, str) or project_icon_bg_color is None
def test_create_or_update_starter_projects(client):
with session_scope() as session:
# Run the function to create or update projects
create_or_update_starter_projects()
# Get the number of projects returned by load_starter_projects
num_projects = len(load_starter_projects())
# Get the number of projects in the database
num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
# Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
assert num_db_projects == num_projects
@pytest.mark.asyncio
async def test_starter_project_can_run_successfully(client):
with session_scope() as session:
# Run the function to create or update projects
create_or_update_starter_projects()
# Get the number of projects returned by load_starter_projects
num_projects = len(load_starter_projects())
# Get the number of projects in the database
num_db_projects = session.exec(select(func.count(Flow.id)).where(Flow.folder == STARTER_FOLDER_NAME)).one()
# Check that the number of projects in the database is the same as the number of projects returned by load_starter_projects
assert num_db_projects == num_projects
# Get all the starter projects
projects = session.exec(select(Flow).where(Flow.folder == STARTER_FOLDER_NAME)).all()
graphs: list[tuple[str, Graph]] = [
(project.name, Graph.from_payload(project.data, flow_id=project.id))
for project in projects
if "Document" not in project.name or "RAG" not in project.name
]
assert len(graphs) == len(projects)
for name, graph in graphs:
outputs = await graph.arun(
inputs={},
outputs=[],
session_id="test",
)
assert all(isinstance(output, RunOutputs) for output in outputs), f"Project {name} error: {outputs}"
delete_messages(session_id="test")

View file

@ -1,37 +1,19 @@
import json
import pytest
from langchain.chains.base import Chain
from langflow.graph import Graph
from langflow.processing.load import load_flow_from_json
from langflow.utils.payload import get_root_vertex
def test_load_flow_from_json():
"""Test loading a flow from a json file"""
loaded = load_flow_from_json(pytest.BASIC_EXAMPLE_PATH)
assert loaded is not None
assert isinstance(loaded, Chain)
assert isinstance(loaded, Graph)
def test_load_flow_from_json_with_tweaks():
"""Test loading a flow from a json file and applying tweaks"""
tweaks = {"dndnode_82": {"model_name": "test model"}}
tweaks = {"dndnode_82": {"model_name": "gpt-3.5-turbo-16k-0613"}}
loaded = load_flow_from_json(pytest.BASIC_EXAMPLE_PATH, tweaks=tweaks)
assert loaded is not None
assert isinstance(loaded, Chain)
assert loaded.llm.model_name == "test model"
def test_get_root_vertex():
with open(pytest.BASIC_EXAMPLE_PATH, "r") as f:
flow_graph = json.load(f)
data_graph = flow_graph["data"]
nodes = data_graph["nodes"]
edges = data_graph["edges"]
graph = Graph(nodes, edges)
root = get_root_vertex(graph)
assert root is not None
assert hasattr(root, "id")
assert hasattr(root, "data")
assert hasattr(root, "data")
assert isinstance(loaded, Graph)

View file

@ -1,8 +1,8 @@
import pytest
from langflow.services.auth.utils import get_password_hash
from langflow.services.database.models.user import User
from langflow.services.database.utils import session_getter
from langflow.services.deps import get_db_service
import pytest
from langflow.services.database.models.user import User
from langflow.services.auth.utils import get_password_hash
@pytest.fixture

View file

@ -1,4 +1,5 @@
import pytest
from langflow.processing.process import process_tweaks
from langflow.services.deps import get_session_service
@ -164,6 +165,70 @@ def test_multiple_tweaks():
assert result == expected_result
# Test twekas that just pass the param and value but no node id.
# This is a new feature that was added to the process_tweaks function
def test_tweak_no_node_id():
graph_data = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 1},
"param2": {"value": 2},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 3},
"param2": {"value": 4},
}
}
},
},
]
}
}
tweaks = {"param1": 5}
expected_result = {
"data": {
"nodes": [
{
"id": "node1",
"data": {
"node": {
"template": {
"param1": {"value": 5},
"param2": {"value": 2},
}
}
},
},
{
"id": "node2",
"data": {
"node": {
"template": {
"param1": {"value": 5},
"param2": {"value": 4},
}
}
},
},
]
}
}
result = process_tweaks(graph_data, tweaks)
assert result == expected_result
def test_tweak_not_in_template():
graph_data = {
"data": {
@ -217,11 +282,11 @@ async def test_load_langchain_object_with_no_cached_session(client, basic_graph_
session_service = get_session_service()
session_id1 = "non-existent-session-id"
session_id = session_service.build_key(session_id1, basic_graph_data)
graph1, artifacts1 = await session_service.load_session(session_id, basic_graph_data)
graph1, artifacts1 = await session_service.load_session(session_id, data_graph=basic_graph_data, flow_id="flow_id")
# Clear the cache
session_service.clear_session(session_id)
# Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session(session_id, basic_graph_data)
graph2, artifacts2 = await session_service.load_session(session_id, data_graph=basic_graph_data, flow_id="flow_id")
assert id(graph1) != id(graph2)
# Since the cache was cleared, objects should be different
@ -232,8 +297,8 @@ async def test_load_langchain_object_without_session_id(client, basic_graph_data
# Provide a non-existent session_id
session_service = get_session_service()
session_id1 = None
graph1, artifacts1 = await session_service.load_session(session_id1, basic_graph_data)
graph1, artifacts1 = await session_service.load_session(session_id1, data_graph=basic_graph_data, flow_id="flow_id")
# Use the new session_id to get the langchain_object again
graph2, artifacts2 = await session_service.load_session(session_id1, basic_graph_data)
graph2, artifacts2 = await session_service.load_session(session_id1, data_graph=basic_graph_data, flow_id="flow_id")
assert graph1 == graph2

View file

@ -1,11 +0,0 @@
from fastapi.testclient import TestClient
from langflow.services.deps import get_settings_service
def test_prompts_settings(client: TestClient, logged_in_headers):
settings_service = get_settings_service()
response = client.get("api/v1/all", headers=logged_in_headers)
assert response.status_code == 200
json_response = response.json()
prompts = json_response["prompts"]
assert set(prompts.keys()) == set(settings_service.settings.PROMPTS)

139
tests/test_record.py Normal file
View file

@ -0,0 +1,139 @@
from langchain_core.documents import Document
from langflow.schema import Record
def test_record_initialization():
record = Record(text_key="msg", data={"msg": "Hello, World!", "extra": "value"})
assert record.msg == "Hello, World!"
assert record.extra == "value"
def test_validate_data_with_extra_keys():
record = Record(dummy_key="dummy", data={"key": "value"})
assert record.data["dummy_key"] == "dummy"
assert "dummy_key" in record.data
assert record.key == "value"
def test_conversion_to_document():
record = Record(data={"text": "Sample text", "meta": "data"})
document = record.to_lc_document()
assert document.page_content == "Sample text"
assert document.metadata == {"meta": "data"}
def test_conversion_from_document():
document = Document(page_content="Doc content", metadata={"meta": "info"})
record = Record.from_document(document)
assert record.text == "Doc content"
assert record.meta == "info"
def test_add_method_for_strings():
record1 = Record(data={"text": "Hello"})
record2 = Record(data={"text": " World"})
combined = record1 + record2
assert combined.text == "Hello World"
def test_add_method_for_integers():
record1 = Record(data={"number": 5})
record2 = Record(data={"number": 10})
combined = record1 + record2
assert combined.number == 15
def test_add_method_with_non_overlapping_keys():
record1 = Record(data={"text": "Hello"})
record2 = Record(data={"number": 10})
combined = record1 + record2
assert combined.text == "Hello"
assert combined.number == 10
def test_custom_attribute_get_set_del():
record = Record()
record.custom_attr = "custom_value"
assert record.custom_attr == "custom_value"
del record.custom_attr
assert record.custom_attr == record.default_value
def test_deep_copy():
import copy
record1 = Record(data={"text": "Hello", "number": 10})
record2 = copy.deepcopy(record1)
assert record2.text == "Hello"
assert record2.number == 10
record2.text = "World"
assert record1.text == "Hello" # Ensure original is unchanged
def test_custom_attribute_setting_and_getting():
record = Record()
record.dynamic_attribute = "Dynamic Value"
assert record.dynamic_attribute == "Dynamic Value"
def test_str_and_dir_methods():
record = Record(text_key="text", data={"text": "Test Text", "key": "value"})
assert "Test Text" in str(record)
assert "key" in dir(record)
assert "data" in dir(record)
def test_dir_includes_data_keys():
record = Record(data={"text": "Hello", "new_attr": "value"})
dir_output = dir(record)
# Check for standard attributes
assert "data" in dir_output
assert "text_key" in dir_output
assert "__add__" in dir_output # Checking for a method
# Check for dynamic attributes from data
assert "text" in dir_output
assert "new_attr" in dir_output
# Optionally, verify that dynamically added attributes are listed
record.dynamic_attr = "dynamic"
assert "dynamic_attr" in dir_output or "dynamic_attr" in dir(record) # To account for the change
def test_dir_reflects_attribute_deletion():
record = Record(data={"removable": "I can be removed"})
assert "removable" in dir(record)
# Delete the attribute and check again
del record.removable
assert "removable" not in dir(record)
def test_get_text_with_text_key():
data = {"text": "Hello, World!"}
schema = Record(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "Hello, World!"
def test_get_text_without_text_key():
data = {"other_key": "Hello, World!"}
schema = Record(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "default"
def test_get_text_with_empty_data():
data = {}
schema = Record(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "default"
def test_get_text_with_none_data():
data = None
schema = Record(data=data, text_key="text", default_value="default")
result = schema.get_text()
assert result == "default"

View file

@ -1,6 +1,9 @@
from unittest.mock import MagicMock, patch
from langflow.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
from langflow.services.settings.constants import (
DEFAULT_SUPERUSER,
DEFAULT_SUPERUSER_PASSWORD,
)
from langflow.services.utils import teardown_superuser
# @patch("langflow.services.deps.get_session")
@ -119,11 +122,11 @@ def test_teardown_superuser_no_default_superuser(mock_get_session, mock_get_sett
mock_session = MagicMock()
mock_user = MagicMock()
mock_user.is_superuser = False
mock_session.exec.return_value.filter.return_value.first.return_value = mock_user
mock_session.query.return_value.filter.return_value.first.return_value = mock_user
mock_get_session.return_value = [mock_session]
teardown_superuser(mock_settings_service, mock_session)
mock_session.exec.assert_called_once()
mock_session.query.assert_not_called()
mock_session.delete.assert_not_called()
mock_session.commit.assert_not_called()

View file

@ -1,7 +1,6 @@
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
@ -90,7 +89,7 @@ def test_data_consistency_after_update(client, active_user, logged_in_headers, s
user_id = active_user.id
update_data = UserUpdate(is_active=False)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=super_user_headers)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.dict(), headers=super_user_headers)
assert response.status_code == 200, response.json()
# Fetch the updated user from the database
@ -117,7 +116,7 @@ def test_inactive_user(client):
username="inactiveuser",
password=get_password_hash("testpassword"),
is_active=False,
last_login_at=datetime.now(),
last_login_at=datetime(2023, 1, 1, 0, 0, 0),
)
session.add(user)
session.commit()
@ -164,13 +163,13 @@ def test_patch_user(client, active_user, logged_in_headers):
username="newname",
)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.dict(), headers=logged_in_headers)
assert response.status_code == 200, response.json()
update_data = UserUpdate(
profile_image="new_image",
)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.dict(), headers=logged_in_headers)
assert response.status_code == 200, response.json()
@ -182,7 +181,7 @@ def test_patch_reset_password(client, active_user, logged_in_headers):
response = client.patch(
f"/api/v1/users/{user_id}/reset-password",
json=update_data.model_dump(),
json=update_data.dict(),
headers=logged_in_headers,
)
assert response.status_code == 200, response.json()
@ -198,13 +197,13 @@ def test_patch_user_wrong_id(client, active_user, logged_in_headers):
username="newname",
)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.dict(), headers=logged_in_headers)
assert response.status_code == 422, response.json()
json_response = response.json()
detail = json_response["detail"]
assert detail[0]["type"] == "uuid_parsing"
assert detail[0]["loc"] == ["path", "user_id"]
assert detail[0]["input"] == "wrong_id"
error = detail[0]
assert error["loc"] == ["path", "user_id"]
assert error["type"] == "uuid_parsing"
def test_delete_user(client, test_user, super_user_headers):
@ -220,9 +219,9 @@ def test_delete_user_wrong_id(client, test_user, super_user_headers):
assert response.status_code == 422
json_response = response.json()
detail = json_response["detail"]
assert detail[0]["type"] == "uuid_parsing"
assert detail[0]["loc"] == ["path", "user_id"]
assert detail[0]["input"] == "wrong_id"
error = detail[0]
assert error["loc"] == ["path", "user_id"]
assert error["type"] == "uuid_parsing"
def test_normal_user_cant_delete_user(client, test_user, logged_in_headers):

View file

@ -1,50 +0,0 @@
from fastapi import WebSocketDisconnect
from fastapi.testclient import TestClient
# from langflow.services.chat.manager import ChatService
import pytest
def test_init_build(client, active_user, logged_in_headers):
response = client.post(
"api/v1/build/init/test",
json={"id": "test", "data": {"key": "value"}},
headers=logged_in_headers,
)
assert response.status_code == 201
assert response.json() == {"flowId": "test"}
# def test_stream_build(client):
# client.post(
# "api/v1/build/init", json={"id": "stream_test", "data": {"key": "value"}}
# )
# # Test the stream
# response = client.get("api/v1/build/stream/stream_test")
# assert response.status_code == 200
# assert response.headers["content-type"] == "text/event-stream; charset=utf-8"
def test_websocket_endpoint(client: TestClient, active_user, logged_in_headers):
# Assuming your websocket_endpoint uses chat_service which caches data from stream_build
access_token = logged_in_headers["Authorization"].split(" ")[1]
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect(f"api/v1/chat/non_existing_client_id?token={access_token}") as websocket:
websocket.send_json({"type": "test"})
data = websocket.receive_json()
assert "Please, build the flow before sending messages" in data["message"]
def test_websocket_endpoint_after_build(client, basic_graph_data):
# Assuming your websocket_endpoint uses chat_service which caches data from stream_build
client.post("api/v1/build/init", json=basic_graph_data)
client.get("api/v1/build/stream/websocket_test")
# There should be more to test here, but it depends on the inner workings of your websocket handler
# and how your chat_service and other classes behave. The following is just an example structure.
with pytest.raises(WebSocketDisconnect):
with client.websocket_connect("api/v1/chat/websocket_test") as websocket:
websocket.send_json({"input": "test"})
websocket.receive_json()

View file

@ -0,0 +1,15 @@
from langflow.components import experimental
def test_python_function_component():
# Arrange
python_function_component = experimental.PythonFunctionComponent()
# Act
# function must be a string representation
function = "def function():\n return 'Hello, World!'"
# result is the callable function
result = python_function_component.build(function)
# Assert
assert result() == "Hello, World!"