tests: update tests to use httpx.AsyncClient (#3984)

* Add async support and dependencies to pyproject.toml files

- Added `asgi-lifespan>=2.1.0` to dependencies.
- Configured `asyncio_mode` and `asyncio_default_fixture_loop_scope` for pytest.
- Updated `tool.uv` section with `asgi-lifespan` in dev-dependencies.

* Convert test fixtures to async and use AsyncClient for HTTP requests

* Handle 'ImportFrom' nodes in AST validation to support module attribute imports

* Convert test cases to use async HTTP client

- Updated test cases in `test_database.py`, `test_endpoints.py`, `test_user.py`, `test_variable.py`, `test_files.py`, `test_chat_endpoint.py`, `test_misc.py`, `test_messages_endpoints.py`, `test_api_key.py`, `test_webhook.py`, and `test_login.py` to use `httpx.AsyncClient` instead of `fastapi.TestClient`.
- Modified test functions to be asynchronous and use `await` for HTTP requests.
- Adjusted fixtures and helper functions to support asynchronous operations.
- Ensured consistency in endpoint paths and request methods across all test cases.

* Refactor string concatenation to f-string in test_chat_endpoint.py

* [autofix.ci] apply automated fixes

* Refactor import validation to use pattern matching for AST nodes

* Set `startup_timeout` and `shutdown_timeout` to `None` in `LifespanManager` for test files.

* Convert test functions to async in `test_messages_endpoints.py`

* Add `api_key_required` marker to assistant component tests

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Gabriel Luiz Freitas Almeida 2024-10-02 14:21:52 -03:00 • committed by GitHub
commit 6febae599b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 444 additions and 322 deletions

View file

@ -301,8 +301,8 @@ dev-dependencies = [
"pytest-split>=0.9.0", "pytest-split>=0.9.0",
"pytest-flakefinder>=1.1.0", "pytest-flakefinder>=1.1.0",
"types-markdown>=3.7.0.20240822", "types-markdown>=3.7.0.20240822",
"packaging>=23.2,<24.0" "packaging>=23.2,<24.0",
"asgi-lifespan>=2.1.0",
] ]
@ -328,7 +328,8 @@ log_cli = true
log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" log_cli_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)"
log_cli_date_format = "%Y-%m-%d %H:%M:%S" log_cli_date_format = "%Y-%m-%d %H:%M:%S"
markers = ["async_test", "api_key_required"] markers = ["async_test", "api_key_required"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
[tool.coverage.run] [tool.coverage.run]
command_line = """ command_line = """
@ -365,4 +366,4 @@ ignore_missing_imports = true
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"

View file

@ -124,10 +124,17 @@ def create_function(code, function_name):
exec_globals = globals().copy() exec_globals = globals().copy()
for node in module.body: for node in module.body:
if isinstance(node, ast.Import): if isinstance(node, ast.Import | ast.ImportFrom):
for alias in node.names: for alias in node.names:
try: try:
exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name) if isinstance(node, ast.ImportFrom):
module_name = node.module
exec_globals[alias.asname or alias.name] = getattr(
importlib.import_module(module_name), alias.name
)
else:
module_name = alias.name
exec_globals[alias.asname or alias.name] = importlib.import_module(module_name)
except ModuleNotFoundError as e: except ModuleNotFoundError as e:
msg = f"Module {alias.name} not found. Please install it and try again." msg = f"Module {alias.name} not found. Please install it and try again."
raise ModuleNotFoundError(msg) from e raise ModuleNotFoundError(msg) from e

View file

@ -134,6 +134,8 @@ console_output_style = "progress"
filterwarnings = ["ignore::DeprecationWarning"] filterwarnings = ["ignore::DeprecationWarning"]
log_cli = true log_cli = true
markers = ["async_test"] markers = ["async_test"]
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
[tool.mypy] [tool.mypy]
plugins = ["pydantic.mypy"] plugins = ["pydantic.mypy"]
@ -189,6 +191,11 @@ ignore = [
"COM812", # Messes with the formatter "COM812", # Messes with the formatter
] ]
[tool.uv]
dev-dependencies = [
"asgi-lifespan>=2.1.0",
]
[build-system] [build-system]
requires = ["hatchling"] requires = ["hatchling"]
build-backend = "hatchling.build" build-backend = "hatchling.build"

View file

@ -11,13 +11,14 @@ from typing import TYPE_CHECKING
import orjson import orjson
import pytest import pytest
from asgi_lifespan import LifespanManager
from loguru import logger from loguru import logger
from pytest import LogCaptureFixture from pytest import LogCaptureFixture
from base.langflow.components.inputs.ChatInput import ChatInput from base.langflow.components.inputs.ChatInput import ChatInput
from dotenv import load_dotenv from dotenv import load_dotenv
from fastapi.testclient import TestClient from fastapi.testclient import TestClient
from httpx import AsyncClient from httpx import ASGITransport, AsyncClient
from sqlmodel import Session, SQLModel, create_engine, select from sqlmodel import Session, SQLModel, create_engine, select
from sqlmodel.pool import StaticPool from sqlmodel.pool import StaticPool
from tests.api_keys import get_openai_api_key from tests.api_keys import get_openai_api_key
@ -260,7 +261,7 @@ def json_memory_chatbot_no_llm():
@pytest.fixture(name="client", autouse=True) @pytest.fixture(name="client", autouse=True)
def client_fixture(session: Session, monkeypatch, request, load_flows_dir): async def client_fixture(session: Session, monkeypatch, request, load_flows_dir):
# Set the database url to a test database # Set the database url to a test database
if "noclient" in request.keywords: if "noclient" in request.keywords:
yield yield
@ -281,8 +282,9 @@ def client_fixture(session: Session, monkeypatch, request, load_flows_dir):
app = create_app() app = create_app()
# app.dependency_overrides[get_session] = get_session_override # app.dependency_overrides[get_session] = get_session_override
with TestClient(app) as client: async with LifespanManager(app, startup_timeout=None, shutdown_timeout=None) as manager:
yield client async with AsyncClient(transport=ASGITransport(app=manager.app), base_url="http://testserver/") as client:
yield client
# app.dependency_overrides.clear() # app.dependency_overrides.clear()
monkeypatch.undo() monkeypatch.undo()
# clear the temp db # clear the temp db
@ -307,12 +309,12 @@ def runner():
@pytest.fixture @pytest.fixture
def test_user(client): async def test_user(client):
user_data = UserCreate( user_data = UserCreate(
username="testuser", username="testuser",
password="testpassword", password="testpassword",
) )
response = client.post("/api/v1/users", json=user_data.model_dump()) response = await client.post("api/v1/users/", json=user_data.model_dump())
assert response.status_code == 201 assert response.status_code == 201
return response.json() return response.json()
@ -337,9 +339,9 @@ def active_user(client):
@pytest.fixture @pytest.fixture
def logged_in_headers(client, active_user): async def logged_in_headers(client, active_user):
login_data = {"username": active_user.username, "password": "testpassword"} login_data = {"username": active_user.username, "password": "testpassword"}
response = client.post("/api/v1/login", data=login_data) response = await client.post("api/v1/login", data=login_data)
assert response.status_code == 200 assert response.status_code == 200
tokens = response.json() tokens = response.json()
a_token = tokens["access_token"] a_token = tokens["access_token"]
@ -375,11 +377,11 @@ def json_two_outputs():
@pytest.fixture @pytest.fixture
def added_flow_with_prompt_and_history(client, json_flow_with_prompt_and_history, logged_in_headers): async 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) flow = orjson.loads(json_flow_with_prompt_and_history)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Basic Chat", description="description", data=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 = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
assert response.json()["name"] == flow.name assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data assert response.json()["data"] == flow.data
@ -388,11 +390,11 @@ def added_flow_with_prompt_and_history(client, json_flow_with_prompt_and_history
@pytest.fixture @pytest.fixture
def added_flow_chat_input(client, json_chat_input, logged_in_headers): async def added_flow_chat_input(client, json_chat_input, logged_in_headers):
flow = orjson.loads(json_chat_input) flow = orjson.loads(json_chat_input)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Chat Input", description="description", data=data) flow = FlowCreate(name="Chat Input", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
assert response.json()["name"] == flow.name assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data assert response.json()["data"] == flow.data
@ -401,11 +403,11 @@ def added_flow_chat_input(client, json_chat_input, logged_in_headers):
@pytest.fixture @pytest.fixture
def added_flow_two_outputs(client, json_two_outputs, logged_in_headers): async def added_flow_two_outputs(client, json_two_outputs, logged_in_headers):
flow = orjson.loads(json_two_outputs) flow = orjson.loads(json_two_outputs)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Two Outputs", description="description", data=data) flow = FlowCreate(name="Two Outputs", description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
assert response.json()["name"] == flow.name assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data assert response.json()["data"] == flow.data
@ -414,11 +416,11 @@ def added_flow_two_outputs(client, json_two_outputs, logged_in_headers):
@pytest.fixture @pytest.fixture
def added_vector_store(client, json_vector_store, logged_in_headers): async def added_vector_store(client, json_vector_store, logged_in_headers):
vector_store = orjson.loads(json_vector_store) vector_store = orjson.loads(json_vector_store)
data = vector_store["data"] data = vector_store["data"]
vector_store = FlowCreate(name="Vector Store", description="description", data=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 = await client.post("api/v1/flows/", json=vector_store.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
assert response.json()["name"] == vector_store.name assert response.json()["name"] == vector_store.name
assert response.json()["data"] == vector_store.data assert response.json()["data"] == vector_store.data
@ -427,13 +429,13 @@ def added_vector_store(client, json_vector_store, logged_in_headers):
@pytest.fixture @pytest.fixture
def added_webhook_test(client, json_webhook_test, logged_in_headers): async def added_webhook_test(client, json_webhook_test, logged_in_headers):
webhook_test = orjson.loads(json_webhook_test) webhook_test = orjson.loads(json_webhook_test)
data = webhook_test["data"] data = webhook_test["data"]
webhook_test = FlowCreate( webhook_test = FlowCreate(
name="Webhook Test", description="description", data=data, endpoint_name=webhook_test["endpoint_name"] name="Webhook Test", description="description", data=data, endpoint_name=webhook_test["endpoint_name"]
) )
response = client.post("api/v1/flows/", json=webhook_test.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=webhook_test.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
assert response.json()["name"] == webhook_test.name assert response.json()["name"] == webhook_test.name
assert response.json()["data"] == webhook_test.data assert response.json()["data"] == webhook_test.data
@ -442,12 +444,12 @@ def added_webhook_test(client, json_webhook_test, logged_in_headers):
@pytest.fixture @pytest.fixture
def flow_component(client: TestClient, logged_in_headers): async def flow_component(client: TestClient, logged_in_headers):
chat_input = ChatInput() chat_input = ChatInput()
graph = Graph(start=chat_input, end=chat_input) graph = Graph(start=chat_input, end=chat_input)
graph_dict = graph.dump(name="Chat Input Component") graph_dict = graph.dump(name="Chat Input Component")
flow = FlowCreate(**graph_dict) flow = FlowCreate(**graph_dict)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
yield response.json() yield response.json()
client.delete(f"api/v1/flows/{response.json()['id']}", headers=logged_in_headers) client.delete(f"api/v1/flows/{response.json()['id']}", headers=logged_in_headers)
@ -473,13 +475,13 @@ def created_api_key(active_user):
@pytest.fixture(name="simple_api_test") @pytest.fixture(name="simple_api_test")
def get_simple_api_test(client, logged_in_headers, json_simple_api_test): async def get_simple_api_test(client, logged_in_headers, json_simple_api_test):
# Once the client is created, we can get the starter project # Once the client is created, we can get the starter project
# Just create a new flow with the simple api test # Just create a new flow with the simple api test
flow = orjson.loads(json_simple_api_test) flow = orjson.loads(json_simple_api_test)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Simple API Test", data=data, description="Simple API Test") flow = FlowCreate(name="Simple API Test", data=data, description="Simple API Test")
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
yield response.json() yield response.json()
client.delete(f"api/v1/flows/{response.json()['id']}", headers=logged_in_headers) client.delete(f"api/v1/flows/{response.json()['id']}", headers=logged_in_headers)

View file

@ -1,8 +1,8 @@
import pytest import pytest
from tests.integration.utils import run_single_component from tests.integration.utils import run_single_component
@pytest.mark.api_key_required
async def test_list_assistants(): async def test_list_assistants():
from langflow.components.astra_assistants import AssistantsListAssistants from langflow.components.astra_assistants import AssistantsListAssistants
@ -34,6 +34,7 @@ async def test_create_assistants():
await run_assistant(assistant_id, thread_id) await run_assistant(assistant_id, thread_id)
@pytest.mark.api_key_required
async def test_create_thread(): async def test_create_thread():
from langflow.components.astra_assistants import AssistantsCreateThread from langflow.components.astra_assistants import AssistantsCreateThread

View file

@ -10,7 +10,7 @@ from langflow.load import run_flow_from_json
@pytest.mark.api_key_required @pytest.mark.api_key_required
def test_run_flow_with_caching_success(client: TestClient, starter_project, created_api_key): async def test_run_flow_with_caching_success(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"] flow_id = starter_project["id"]
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
payload = { payload = {
@ -20,7 +20,7 @@ def test_run_flow_with_caching_success(client: TestClient, starter_project, crea
"tweaks": {"parameter_name": "value"}, "tweaks": {"parameter_name": "value"},
"stream": False, "stream": False,
} }
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) response = await client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
data = response.json() data = response.json()
assert "outputs" in data assert "outputs" in data
@ -28,11 +28,11 @@ def test_run_flow_with_caching_success(client: TestClient, starter_project, crea
@pytest.mark.api_key_required @pytest.mark.api_key_required
def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_key): async def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_key):
invalid_flow_id = uuid4() invalid_flow_id = uuid4()
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
payload = {"input_value": "", "input_type": "text", "output_type": "text", "tweaks": {}, "stream": False} 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) response = await client.post(f"/api/v1/run/{invalid_flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND assert response.status_code == status.HTTP_404_NOT_FOUND
data = response.json() data = response.json()
assert "detail" in data assert "detail" in data
@ -40,16 +40,16 @@ def test_run_flow_with_caching_invalid_flow_id(client: TestClient, created_api_k
@pytest.mark.api_key_required @pytest.mark.api_key_required
def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_project, created_api_key): async def test_run_flow_with_caching_invalid_input_format(client: TestClient, starter_project, created_api_key):
flow_id = starter_project["id"] flow_id = starter_project["id"]
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
payload = {"input_value": {"key": "value"}, "input_type": "text", "output_type": "text", "tweaks": {}} 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) response = await client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
@pytest.mark.api_key_required @pytest.mark.api_key_required
def test_run_flow_with_invalid_tweaks(client, starter_project, created_api_key): async def test_run_flow_with_invalid_tweaks(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"] flow_id = starter_project["id"]
payload = { payload = {
@ -58,12 +58,12 @@ def test_run_flow_with_invalid_tweaks(client, starter_project, created_api_key):
"output_type": "text", "output_type": "text",
"tweaks": {"invalid_tweak": "value"}, "tweaks": {"invalid_tweak": "value"},
} }
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) response = await client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
@pytest.mark.api_key_required @pytest.mark.api_key_required
def test_run_with_inputs_and_outputs(client, starter_project, created_api_key): async def test_run_with_inputs_and_outputs(client, starter_project, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = starter_project["id"] flow_id = starter_project["id"]
payload = { payload = {
@ -73,7 +73,7 @@ def test_run_with_inputs_and_outputs(client, starter_project, created_api_key):
"tweaks": {"parameter_name": "value"}, "tweaks": {"parameter_name": "value"},
"stream": False, "stream": False,
} }
response = client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers) response = await client.post(f"/api/v1/run/{flow_id}", json=payload, headers=headers)
assert response.status_code == status.HTTP_200_OK, response.text assert response.status_code == status.HTTP_200_OK, response.text

View file

@ -1,12 +1,13 @@
import pytest
from uuid import uuid4
from unittest import mock from unittest import mock
from uuid import uuid4
from fastapi import status, HTTPException import pytest
from fastapi import HTTPException, status
from httpx import AsyncClient
@pytest.fixture @pytest.fixture
def body(): async def body():
return { return {
"name": "test_variable", "name": "test_variable",
"value": "test_value", "value": "test_value",
@ -15,8 +16,8 @@ def body():
} }
def test_create_variable(client, body, active_user, logged_in_headers): async def test_create_variable(client: AsyncClient, body, active_user, logged_in_headers):
response = client.post("api/v1/variables", json=body, headers=logged_in_headers) response = await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_201_CREATED == response.status_code assert status.HTTP_201_CREATED == response.status_code
@ -27,105 +28,112 @@ def test_create_variable(client, body, active_user, logged_in_headers):
assert body["value"] != result["value"] assert body["value"] != result["value"]
def test_create_variable__variable_name_alread_exists(client, body, active_user, logged_in_headers): async def test_create_variable__variable_name_already_exists(client: AsyncClient, body, active_user, logged_in_headers):
client.post("api/v1/variables", json=body, headers=logged_in_headers) await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
response = client.post("api/v1/variables", json=body, headers=logged_in_headers) response = await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_400_BAD_REQUEST == response.status_code assert status.HTTP_400_BAD_REQUEST == response.status_code
assert "Variable name already exists" in result["detail"] assert "Variable name already exists" in result["detail"]
def test_create_variable__variable_name_and_value_cannot_be_empty(client, body, active_user, logged_in_headers): async def test_create_variable__variable_name_and_value_cannot_be_empty(
client: AsyncClient, body, active_user, logged_in_headers
):
body["name"] = "" body["name"] = ""
body["value"] = "" body["value"] = ""
response = client.post("api/v1/variables", json=body, headers=logged_in_headers) response = await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_400_BAD_REQUEST == response.status_code assert status.HTTP_400_BAD_REQUEST == response.status_code
assert "Variable name and value cannot be empty" in result["detail"] assert "Variable name and value cannot be empty" in result["detail"]
def test_create_variable__variable_name_cannot_be_empty(client, body, active_user, logged_in_headers): async def test_create_variable__variable_name_cannot_be_empty(
client: AsyncClient, body, active_user, logged_in_headers
):
body["name"] = "" body["name"] = ""
response = client.post("api/v1/variables", json=body, headers=logged_in_headers) response = await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_400_BAD_REQUEST == response.status_code assert status.HTTP_400_BAD_REQUEST == response.status_code
assert "Variable name cannot be empty" in result["detail"] assert "Variable name cannot be empty" in result["detail"]
def test_create_variable__variable_value_cannot_be_empty(client, body, active_user, logged_in_headers): async def test_create_variable__variable_value_cannot_be_empty(
client: AsyncClient, body, active_user, logged_in_headers
):
body["value"] = "" body["value"] = ""
response = client.post("api/v1/variables", json=body, headers=logged_in_headers) response = await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_400_BAD_REQUEST == response.status_code assert status.HTTP_400_BAD_REQUEST == response.status_code
assert "Variable value cannot be empty" in result["detail"] assert "Variable value cannot be empty" in result["detail"]
def test_create_variable__HTTPException(client, body, active_user, logged_in_headers): async def test_create_variable__HTTPException(client: AsyncClient, body, active_user, logged_in_headers):
status_code = 418 status_code = 418
generic_message = "I'm a teapot" generic_message = "I'm a teapot"
with mock.patch("langflow.services.auth.utils.encrypt_api_key") as m: with mock.patch("langflow.services.auth.utils.encrypt_api_key") as m:
m.side_effect = HTTPException(status_code=status_code, detail=generic_message) m.side_effect = HTTPException(status_code=status_code, detail=generic_message)
response = client.post("api/v1/variables", json=body, headers=logged_in_headers) response = await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_418_IM_A_TEAPOT == response.status_code assert status.HTTP_418_IM_A_TEAPOT == response.status_code
assert generic_message in result["detail"] assert generic_message in result["detail"]
def test_create_variable__Exception(client, body, active_user, logged_in_headers): async def test_create_variable__Exception(client: AsyncClient, body, active_user, logged_in_headers):
generic_message = "Generic error message" generic_message = "Generic error message"
with mock.patch("langflow.services.auth.utils.encrypt_api_key") as m: with mock.patch("langflow.services.auth.utils.encrypt_api_key") as m:
m.side_effect = Exception(generic_message) m.side_effect = Exception(generic_message)
response = client.post("api/v1/variables", json=body, headers=logged_in_headers) response = await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_500_INTERNAL_SERVER_ERROR == response.status_code assert status.HTTP_500_INTERNAL_SERVER_ERROR == response.status_code
assert generic_message in result["detail"] assert generic_message in result["detail"]
def test_read_variables(client, body, active_user, logged_in_headers): async def test_read_variables(client: AsyncClient, body, active_user, logged_in_headers):
names = ["test_variable1", "test_variable2", "test_variable3"] names = ["test_variable1", "test_variable2", "test_variable3"]
for name in names: for name in names:
body["name"] = name body["name"] = name
client.post("api/v1/variables", json=body, headers=logged_in_headers) await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
response = client.get("api/v1/variables", headers=logged_in_headers) response = await client.get("api/v1/variables/", headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_200_OK == response.status_code assert status.HTTP_200_OK == response.status_code
assert all(name in [r["name"] for r in result] for name in names) assert all(name in [r["name"] for r in result] for name in names)
def test_read_variables__empty(client, active_user, logged_in_headers): async def test_read_variables__empty(client: AsyncClient, active_user, logged_in_headers):
all_variables = client.get("api/v1/variables", headers=logged_in_headers).json() all_variables = await client.get("api/v1/variables/", headers=logged_in_headers)
all_variables = all_variables.json()
for variable in all_variables: for variable in all_variables:
client.delete(f"api/v1/variables/{variable.get('id')}", headers=logged_in_headers) await client.delete(f"api/v1/variables/{variable.get('id')}", headers=logged_in_headers)
response = client.get("api/v1/variables", headers=logged_in_headers) response = await client.get("api/v1/variables/", headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_200_OK == response.status_code assert status.HTTP_200_OK == response.status_code
assert [] == result assert [] == result
def test_read_variables__(client, active_user, logged_in_headers): # TODO check if this is correct async def test_read_variables__(client: AsyncClient, active_user, logged_in_headers):
generic_message = "Generic error message" generic_message = "Generic error message"
with pytest.raises(Exception) as exc: with pytest.raises(Exception) as exc:
with mock.patch("sqlmodel.Session.exec") as m: with mock.patch("sqlmodel.Session.exec") as m:
m.side_effect = Exception(generic_message) m.side_effect = Exception(generic_message)
response = client.get("api/v1/variables", headers=logged_in_headers) response = await client.get("api/v1/variables/", headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_500_INTERNAL_SERVER_ERROR == response.status_code assert status.HTTP_500_INTERNAL_SERVER_ERROR == response.status_code
@ -134,47 +142,46 @@ def test_read_variables__(client, active_user, logged_in_headers): # TODO check
assert generic_message in str(exc.value) assert generic_message in str(exc.value)
def test_update_variable(client, body, active_user, logged_in_headers): async def test_update_variable(client: AsyncClient, body, active_user, logged_in_headers):
saved = client.post("api/v1/variables", json=body, headers=logged_in_headers).json() saved = await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
saved = saved.json()
body["id"] = saved.get("id") body["id"] = saved.get("id")
body["name"] = "new_name" body["name"] = "new_name"
body["value"] = "new_value" body["value"] = "new_value"
body["type"] = "new_type" body["type"] = "new_type"
body["default_fields"] = ["new_field"] body["default_fields"] = ["new_field"]
response = client.patch(f"api/v1/variables/{saved.get('id')}", json=body, headers=logged_in_headers) response = await client.patch(f"api/v1/variables/{saved.get('id')}", json=body, headers=logged_in_headers)
result = response.json() result = response.json()
assert status.HTTP_200_OK == response.status_code assert status.HTTP_200_OK == response.status_code
assert saved["id"] == result["id"] assert saved["id"] == result["id"]
assert saved["name"] != result["name"] assert saved["name"] != result["name"]
# assert saved["type"] != result["type"] # TODO check if this is correct
assert saved["default_fields"] != result["default_fields"] assert saved["default_fields"] != result["default_fields"]
def test_update_variable__Exception(client, body, active_user, logged_in_headers): async def test_update_variable__Exception(client: AsyncClient, body, active_user, logged_in_headers):
wrong_id = uuid4() wrong_id = uuid4()
body["id"] = str(wrong_id) body["id"] = str(wrong_id)
response = client.patch(f"api/v1/variables/{wrong_id}", json=body, headers=logged_in_headers) response = await client.patch(f"api/v1/variables/{wrong_id}", json=body, headers=logged_in_headers)
result = response.json() result = response.json()
# assert status.HTTP_404_NOT_FOUND == response.status_code # TODO check if this is correct assert status.HTTP_404_NOT_FOUND == response.status_code
assert "Variable not found" in result["detail"] assert "Variable not found" in result["detail"]
def test_delete_variable(client, body, active_user, logged_in_headers): async def test_delete_variable(client: AsyncClient, body, active_user, logged_in_headers):
saved = client.post("api/v1/variables", json=body, headers=logged_in_headers).json() response = await client.post("api/v1/variables/", json=body, headers=logged_in_headers)
saved = response.json()
response = client.delete(f"api/v1/variables/{saved.get('id')}", headers=logged_in_headers) response = await client.delete(f"api/v1/variables/{saved.get('id')}", headers=logged_in_headers)
assert status.HTTP_204_NO_CONTENT == response.status_code assert status.HTTP_204_NO_CONTENT == response.status_code
def test_delete_variable__Exception(client, active_user, logged_in_headers): async def test_delete_variable__Exception(client: AsyncClient, active_user, logged_in_headers):
wrong_id = uuid4() wrong_id = uuid4()
response = client.delete(f"api/v1/variables/{wrong_id}", headers=logged_in_headers) response = await client.delete(f"api/v1/variables/{wrong_id}", headers=logged_in_headers)
# assert status.HTTP_404_NOT_FOUND == response.status_code # TODO check if this is correct
assert status.HTTP_500_INTERNAL_SERVER_ERROR == response.status_code assert status.HTTP_500_INTERNAL_SERVER_ERROR == response.status_code

View file

@ -1,44 +1,42 @@
import pytest import pytest
from httpx import AsyncClient
from langflow.services.database.models.api_key import ApiKeyCreate from langflow.services.database.models.api_key import ApiKeyCreate
@pytest.fixture @pytest.fixture
def api_key(client, logged_in_headers, active_user): async def api_key(client, logged_in_headers, active_user):
api_key = ApiKeyCreate(name="test-api-key") api_key = ApiKeyCreate(name="test-api-key")
response = client.post("api/v1/api_key", data=api_key.model_dump_json(), headers=logged_in_headers) response = await client.post("api/v1/api_key/", data=api_key.model_dump_json(), headers=logged_in_headers)
assert response.status_code == 200, response.text assert response.status_code == 200, response.text
return response.json() return response.json()
def test_get_api_keys(client, logged_in_headers, api_key): async def test_get_api_keys(client: AsyncClient, logged_in_headers, api_key):
response = client.get("api/v1/api_key", headers=logged_in_headers) response = await client.get("api/v1/api_key/", headers=logged_in_headers)
assert response.status_code == 200, response.text assert response.status_code == 200, response.text
data = response.json() data = response.json()
assert "total_count" in data assert "total_count" in data
assert "user_id" in data assert "user_id" in data
assert "api_keys" in data assert "api_keys" in data
assert any("test-api-key" in api_key["name"] for api_key in data["api_keys"]) 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"]) assert all("**" in api_key["api_key"] for api_key in data["api_keys"])
def test_create_api_key(client, logged_in_headers): async def test_create_api_key(client: AsyncClient, logged_in_headers):
api_key_name = "test-api-key" api_key_name = "test-api-key"
response = client.post("api/v1/api_key", json={"name": api_key_name}, headers=logged_in_headers) response = await client.post("api/v1/api_key/", json={"name": api_key_name}, headers=logged_in_headers)
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert "name" in data and data["name"] == api_key_name assert "name" in data and data["name"] == api_key_name
assert "api_key" in data assert "api_key" in data
# When creating the API key is returned which is
# the only time the API key is unmasked
assert "**" not in data["api_key"] assert "**" not in data["api_key"]
def test_delete_api_key(client, logged_in_headers, active_user, api_key): async def test_delete_api_key(client, logged_in_headers, active_user, api_key):
# Assuming a function to create a test API key, returning the key ID
api_key_id = api_key["id"] api_key_id = api_key["id"]
response = client.delete(f"api/v1/api_key/{api_key_id}", headers=logged_in_headers) response = await client.delete(f"api/v1/api_key/{api_key_id}", headers=logged_in_headers)
assert response.status_code == 200 assert response.status_code == 200
data = response.json() data = response.json()
assert data["detail"] == "API Key deleted" assert data["detail"] == "API Key deleted"

View file

@ -1,46 +1,49 @@
import json import json
from uuid import UUID from uuid import UUID
from orjson import orjson from orjson import orjson
from langflow.memory import get_messages from langflow.memory import get_messages
from langflow.services.database.models.flow import FlowCreate, FlowUpdate from langflow.services.database.models.flow import FlowCreate, FlowUpdate
def test_build_flow(client, json_memory_chatbot_no_llm, logged_in_headers): async def test_build_flow(client, json_memory_chatbot_no_llm, logged_in_headers):
flow_id = _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers) flow_id = await _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
with client.stream("POST", f"api/v1/build/{flow_id}/flow", json={}, headers=logged_in_headers) as r: async with client.stream("POST", f"api/v1/build/{flow_id}/flow", json={}, headers=logged_in_headers) as r:
consume_and_assert_stream(r) await consume_and_assert_stream(r)
check_messages(flow_id) check_messages(flow_id)
def test_build_flow_from_request_data(client, json_memory_chatbot_no_llm, logged_in_headers): async def test_build_flow_from_request_data(client, json_memory_chatbot_no_llm, logged_in_headers):
flow_id = _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers) flow_id = await _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
flow_data = client.get("api/v1/flows/" + str(flow_id), headers=logged_in_headers).json() response = await client.get("api/v1/flows/" + str(flow_id), headers=logged_in_headers)
flow_data = response.json()
with client.stream( async with client.stream(
"POST", f"api/v1/build/{flow_id}/flow", json={"data": flow_data["data"]}, headers=logged_in_headers "POST", f"api/v1/build/{flow_id}/flow", json={"data": flow_data["data"]}, headers=logged_in_headers
) as r: ) as r:
consume_and_assert_stream(r) await consume_and_assert_stream(r)
check_messages(flow_id) check_messages(flow_id)
def test_build_flow_with_frozen_path(client, json_memory_chatbot_no_llm, logged_in_headers): async def test_build_flow_with_frozen_path(client, json_memory_chatbot_no_llm, logged_in_headers):
flow_id = _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers) flow_id = await _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers)
flow_data = client.get("api/v1/flows/" + str(flow_id), headers=logged_in_headers).json() response = await client.get("api/v1/flows/" + str(flow_id), headers=logged_in_headers)
flow_data = response.json()
flow_data["data"]["nodes"][0]["data"]["node"]["frozen"] = True flow_data["data"]["nodes"][0]["data"]["node"]["frozen"] = True
response = client.patch( response = await client.patch(
"api/v1/flows/" + str(flow_id), f"api/v1/flows/{flow_id}",
json=FlowUpdate(name="Flow", description="description", data=flow_data["data"]).model_dump(), json=FlowUpdate(name="Flow", description="description", data=flow_data["data"]).model_dump(),
headers=logged_in_headers, headers=logged_in_headers,
) )
response.raise_for_status() response.raise_for_status()
with client.stream("POST", f"api/v1/build/{flow_id}/flow", json={}, headers=logged_in_headers) as r: async with client.stream("POST", f"api/v1/build/{flow_id}/flow", json={}, headers=logged_in_headers) as r:
consume_and_assert_stream(r) await consume_and_assert_stream(r)
check_messages(flow_id) check_messages(flow_id)
@ -57,9 +60,9 @@ def check_messages(flow_id):
assert messages[1].sender_name == "AI" assert messages[1].sender_name == "AI"
def consume_and_assert_stream(r): async def consume_and_assert_stream(r):
count = 0 count = 0
for line in r.iter_lines(): async for line in r.aiter_lines():
# httpx split by \n, but ndjson sends two \n for each line # httpx split by \n, but ndjson sends two \n for each line
if not line: if not line:
continue continue
@ -83,11 +86,11 @@ def consume_and_assert_stream(r):
count += 1 count += 1
def _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers): async def _create_flow(client, json_memory_chatbot_no_llm, logged_in_headers):
vector_store = orjson.loads(json_memory_chatbot_no_llm) vector_store = orjson.loads(json_memory_chatbot_no_llm)
data = vector_store["data"] data = vector_store["data"]
vector_store = FlowCreate(name="Flow", description="description", data=data, endpoint_name="f") vector_store = FlowCreate(name="Flow", description="description", data=data, endpoint_name="f")
response = client.post("api/v1/flows/", json=vector_store.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=vector_store.model_dump(), headers=logged_in_headers)
response.raise_for_status() response.raise_for_status()
flow_id = response.json()["id"] flow_id = response.json()["id"]
return flow_id return flow_id

View file

@ -2,6 +2,7 @@ from pathlib import Path
from tempfile import tempdir from tempfile import tempdir
import pytest import pytest
from langflow.__main__ import app from langflow.__main__ import app
from langflow.services import deps from langflow.services import deps

View file

@ -31,72 +31,72 @@ def json_style():
) )
def test_create_flow(client: TestClient, json_flow: str, active_user, logged_in_headers): async def test_create_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name=str(uuid4()), description="description", data=data) flow = FlowCreate(name=str(uuid4()), description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
assert response.json()["name"] == flow.name assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data assert response.json()["data"] == flow.data
# flow is optional so we can create a flow without a flow # flow is optional so we can create a flow without a flow
flow = FlowCreate(name=str(uuid4())) flow = FlowCreate(name=str(uuid4()))
response = client.post("api/v1/flows/", json=flow.model_dump(exclude_unset=True), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(exclude_unset=True), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
assert response.json()["name"] == flow.name assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data assert response.json()["data"] == flow.data
def test_read_flows(client: TestClient, json_flow: str, active_user, logged_in_headers): async def test_read_flows(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow_data = orjson.loads(json_flow) flow_data = orjson.loads(json_flow)
data = flow_data["data"] data = flow_data["data"]
flow = FlowCreate(name=str(uuid4()), description="description", data=data) flow = FlowCreate(name=str(uuid4()), description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
assert response.json()["name"] == flow.name assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data assert response.json()["data"] == flow.data
flow = FlowCreate(name=str(uuid4()), description="description", data=data) flow = FlowCreate(name=str(uuid4()), description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
assert response.json()["name"] == flow.name assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data assert response.json()["data"] == flow.data
response = client.get("api/v1/flows/", headers=logged_in_headers) response = await client.get("api/v1/flows/", headers=logged_in_headers)
assert response.status_code == 200 assert response.status_code == 200
assert len(response.json()) > 0 assert len(response.json()) > 0
def test_read_flows_components_only(client: TestClient, flow_component: dict, logged_in_headers): async def test_read_flows_components_only(client: TestClient, flow_component: dict, logged_in_headers):
response = client.get("api/v1/flows/", headers=logged_in_headers, params={"components_only": True}) response = await client.get("api/v1/flows/", headers=logged_in_headers, params={"components_only": True})
assert response.status_code == 200 assert response.status_code == 200
names = [flow["name"] for flow in response.json()] names = [flow["name"] for flow in response.json()]
assert any("Chat Input Component" in name for name in names) assert any("Chat Input Component" in name for name in names)
assert all(flow["is_component"] is True for flow in response.json()), [flow["name"] for flow in response.json()] assert all(flow["is_component"] is True for flow in response.json()), [flow["name"] for flow in response.json()]
def test_read_flow(client: TestClient, json_flow: str, logged_in_headers): async def test_read_flow(client: TestClient, json_flow: str, logged_in_headers):
flow = orjson.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
unique_name = str(uuid4()) unique_name = str(uuid4())
flow = FlowCreate(name=unique_name, description="description", data=data) flow = FlowCreate(name=unique_name, description="description", data=data)
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
flow_id = response.json()["id"] # flow_id should be a UUID but is a string flow_id = response.json()["id"] # flow_id should be a UUID but is a string
# turn it into a UUID # turn it into a UUID
flow_id = UUID(flow_id) flow_id = UUID(flow_id)
response = client.get(f"api/v1/flows/{flow_id}", headers=logged_in_headers) response = await client.get(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["name"] == flow.name assert response.json()["name"] == flow.name
assert response.json()["data"] == flow.data assert response.json()["data"] == flow.data
def test_update_flow(client: TestClient, json_flow: str, active_user, logged_in_headers): async def test_update_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", 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 = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
flow_id = response.json()["id"] flow_id = response.json()["id"]
updated_flow = FlowUpdate( updated_flow = FlowUpdate(
@ -104,7 +104,7 @@ def test_update_flow(client: TestClient, json_flow: str, active_user, logged_in_
description="updated description", description="updated description",
data=data, data=data,
) )
response = client.patch(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers) response = await client.patch(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["name"] == updated_flow.name assert response.json()["name"] == updated_flow.name
@ -112,28 +112,28 @@ def test_update_flow(client: TestClient, json_flow: str, active_user, logged_in_
# assert response.json()["data"] == updated_flow.data # assert response.json()["data"] == updated_flow.data
def test_delete_flow(client: TestClient, json_flow: str, active_user, logged_in_headers): async def test_delete_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow = orjson.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
flow = FlowCreate(name="Test Flow", description="description", 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 = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
flow_id = response.json()["id"] flow_id = response.json()["id"]
response = client.delete(f"api/v1/flows/{flow_id}", headers=logged_in_headers) response = await client.delete(f"api/v1/flows/{flow_id}", headers=logged_in_headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["message"] == "Flow deleted successfully" assert response.json()["message"] == "Flow deleted successfully"
def test_delete_flows(client: TestClient, json_flow: str, active_user, logged_in_headers): async def test_delete_flows(client: TestClient, json_flow: str, active_user, logged_in_headers):
# Create ten flows # Create ten flows
number_of_flows = 10 number_of_flows = 10
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)] flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
flow_ids = [] flow_ids = []
for flow in flows: for flow in flows:
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
flow_ids.append(response.json()["id"]) flow_ids.append(response.json()["id"])
response = client.request("DELETE", "api/v1/flows/", headers=logged_in_headers, json=flow_ids) response = await client.request("DELETE", "api/v1/flows/", headers=logged_in_headers, json=flow_ids)
assert response.status_code == 200, response.content assert response.status_code == 200, response.content
assert response.json().get("deleted") == number_of_flows assert response.json().get("deleted") == number_of_flows
@ -147,7 +147,7 @@ async def test_delete_flows_with_transaction_and_build(
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)] flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
flow_ids = [] flow_ids = []
for flow in flows: for flow in flows:
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
flow_ids.append(response.json()["id"]) flow_ids.append(response.json()["id"])
@ -179,19 +179,19 @@ async def test_delete_flows_with_transaction_and_build(
artifacts=build.get("artifacts"), artifacts=build.get("artifacts"),
) )
response = client.request("DELETE", "api/v1/flows/", headers=logged_in_headers, json=flow_ids) response = await client.request("DELETE", "api/v1/flows/", headers=logged_in_headers, json=flow_ids)
assert response.status_code == 200, response.content assert response.status_code == 200, response.content
assert response.json().get("deleted") == number_of_flows assert response.json().get("deleted") == number_of_flows
for flow_id in flow_ids: for flow_id in flow_ids:
response = client.request( response = await client.request(
"GET", "api/v1/monitor/transactions", params={"flow_id": flow_id}, headers=logged_in_headers "GET", "api/v1/monitor/transactions", params={"flow_id": flow_id}, headers=logged_in_headers
) )
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == [] assert response.json() == []
for flow_id in flow_ids: for flow_id in flow_ids:
response = client.request( response = await client.request(
"GET", "api/v1/monitor/builds", params={"flow_id": flow_id}, headers=logged_in_headers "GET", "api/v1/monitor/builds", params={"flow_id": flow_id}, headers=logged_in_headers
) )
assert response.status_code == 200 assert response.status_code == 200
@ -206,7 +206,7 @@ async def test_delete_folder_with_flows_with_transaction_and_build(
folder_name = f"Test Folder {uuid4()}" folder_name = f"Test Folder {uuid4()}"
folder = FolderCreate(name=folder_name, description="Test folder description", components_list=[], flows_list=[]) folder = FolderCreate(name=folder_name, description="Test folder description", components_list=[], flows_list=[])
response = client.post("/api/v1/folders/", json=folder.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/folders/", json=folder.model_dump(), headers=logged_in_headers)
assert response.status_code == 201, f"Expected status code 201, but got {response.status_code}" assert response.status_code == 201, f"Expected status code 201, but got {response.status_code}"
created_folder = response.json() created_folder = response.json()
@ -218,7 +218,7 @@ async def test_delete_folder_with_flows_with_transaction_and_build(
flow_ids = [] flow_ids = []
for flow in flows: for flow in flows:
flow.folder_id = folder_id flow.folder_id = folder_id
response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201 assert response.status_code == 201
flow_ids.append(response.json()["id"]) flow_ids.append(response.json()["id"])
@ -249,25 +249,25 @@ async def test_delete_folder_with_flows_with_transaction_and_build(
artifacts=build.get("artifacts"), artifacts=build.get("artifacts"),
) )
response = client.request("DELETE", f"api/v1/folders/{folder_id}", headers=logged_in_headers) response = await client.request("DELETE", f"api/v1/folders/{folder_id}", headers=logged_in_headers)
assert response.status_code == 204 assert response.status_code == 204
for flow_id in flow_ids: for flow_id in flow_ids:
response = client.request( response = await client.request(
"GET", "api/v1/monitor/transactions", params={"flow_id": flow_id}, headers=logged_in_headers "GET", "api/v1/monitor/transactions", params={"flow_id": flow_id}, headers=logged_in_headers
) )
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == [] assert response.json() == []
for flow_id in flow_ids: for flow_id in flow_ids:
response = client.request( response = await client.request(
"GET", "api/v1/monitor/builds", params={"flow_id": flow_id}, headers=logged_in_headers "GET", "api/v1/monitor/builds", params={"flow_id": flow_id}, headers=logged_in_headers
) )
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {"vertex_builds": {}} assert response.json() == {"vertex_builds": {}}
def test_create_flows(client: TestClient, session: Session, json_flow: str, logged_in_headers): async def test_create_flows(client: TestClient, session: Session, json_flow: str, logged_in_headers):
flow = orjson.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
# Create test data # Create test data
@ -280,7 +280,7 @@ def test_create_flows(client: TestClient, session: Session, json_flow: str, logg
] ]
) )
# Make request to endpoint # Make request to endpoint
response = client.post("api/v1/flows/batch/", json=flow_list.dict(), headers=logged_in_headers) response = await client.post("api/v1/flows/batch/", json=flow_list.dict(), headers=logged_in_headers)
# Check response status code # Check response status code
assert response.status_code == 201 assert response.status_code == 201
# Check response data # Check response data
@ -294,7 +294,7 @@ def test_create_flows(client: TestClient, session: Session, json_flow: str, logg
assert response_data[1]["data"] == data assert response_data[1]["data"] == data
def test_upload_file(client: TestClient, session: Session, json_flow: str, logged_in_headers): async def test_upload_file(client: TestClient, session: Session, json_flow: str, logged_in_headers):
flow = orjson.loads(json_flow) flow = orjson.loads(json_flow)
data = flow["data"] data = flow["data"]
# Create test data # Create test data
@ -307,7 +307,7 @@ def test_upload_file(client: TestClient, session: Session, json_flow: str, logge
] ]
) )
file_contents = orjson_dumps(flow_list.dict()) file_contents = orjson_dumps(flow_list.dict())
response = client.post( response = await client.post(
"api/v1/flows/upload/", "api/v1/flows/upload/",
files={"file": ("examples.json", file_contents, "application/json")}, files={"file": ("examples.json", file_contents, "application/json")},
headers=logged_in_headers, headers=logged_in_headers,
@ -325,7 +325,7 @@ def test_upload_file(client: TestClient, session: Session, json_flow: str, logge
assert response_data[1]["data"] == data assert response_data[1]["data"] == data
def test_download_file( async def test_download_file(
client: TestClient, client: TestClient,
session: Session, session: Session,
json_flow, json_flow,
@ -355,7 +355,7 @@ def test_download_file(
# Make request to endpoint inside the session context # Make request to endpoint inside the session context
flow_ids = [str(db_flow.id) for db_flow in saved_flows] # Convert UUIDs to strings flow_ids = [str(db_flow.id) for db_flow in saved_flows] # Convert UUIDs to strings
flow_ids_json = json.dumps(flow_ids) flow_ids_json = json.dumps(flow_ids)
response = client.post( response = await client.post(
"api/v1/flows/download/", "api/v1/flows/download/",
data=flow_ids_json, data=flow_ids_json,
headers={**logged_in_headers, "Content-Type": "application/json"}, headers={**logged_in_headers, "Content-Type": "application/json"},
@ -368,31 +368,31 @@ def test_download_file(
assert "attachment; filename=" in response.headers["Content-Disposition"] assert "attachment; filename=" in response.headers["Content-Disposition"]
def test_create_flow_with_invalid_data(client: TestClient, active_user, logged_in_headers): async def test_create_flow_with_invalid_data(client: TestClient, active_user, logged_in_headers):
flow = {"name": "a" * 256, "data": "Invalid flow data"} flow = {"name": "a" * 256, "data": "Invalid flow data"}
response = client.post("api/v1/flows/", json=flow, headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow, headers=logged_in_headers)
assert response.status_code == 422 assert response.status_code == 422
def test_get_nonexistent_flow(client: TestClient, active_user, logged_in_headers): async def test_get_nonexistent_flow(client: TestClient, active_user, logged_in_headers):
uuid = uuid4() uuid = uuid4()
response = client.get(f"api/v1/flows/{uuid}", headers=logged_in_headers) response = await client.get(f"api/v1/flows/{uuid}", headers=logged_in_headers)
assert response.status_code == 404 assert response.status_code == 404
def test_update_flow_idempotency(client: TestClient, json_flow: str, active_user, logged_in_headers): async def test_update_flow_idempotency(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow_data = orjson.loads(json_flow) flow_data = orjson.loads(json_flow)
data = flow_data["data"] data = flow_data["data"]
flow_data = FlowCreate(name="Test Flow", description="description", data=data) flow_data = FlowCreate(name="Test Flow", description="description", data=data)
response = client.post("api/v1/flows/", json=flow_data.dict(), headers=logged_in_headers) response = await client.post("api/v1/flows/", json=flow_data.dict(), headers=logged_in_headers)
flow_id = response.json()["id"] flow_id = response.json()["id"]
updated_flow = FlowCreate(name="Updated Flow", description="description", data=data) 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) response1 = await 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) response2 = await client.put(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers)
assert response1.json() == response2.json() assert response1.json() == response2.json()
def test_update_nonexistent_flow(client: TestClient, json_flow: str, active_user, logged_in_headers): async def test_update_nonexistent_flow(client: TestClient, json_flow: str, active_user, logged_in_headers):
flow_data = orjson.loads(json_flow) flow_data = orjson.loads(json_flow)
data = flow_data["data"] data = flow_data["data"]
uuid = uuid4() uuid = uuid4()
@ -401,31 +401,31 @@ def test_update_nonexistent_flow(client: TestClient, json_flow: str, active_user
description="description", description="description",
data=data, data=data,
) )
response = client.patch(f"api/v1/flows/{uuid}", json=updated_flow.model_dump(), headers=logged_in_headers) response = await client.patch(f"api/v1/flows/{uuid}", json=updated_flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 404, response.text assert response.status_code == 404, response.text
def test_delete_nonexistent_flow(client: TestClient, active_user, logged_in_headers): async def test_delete_nonexistent_flow(client: TestClient, active_user, logged_in_headers):
uuid = uuid4() uuid = uuid4()
response = client.delete(f"api/v1/flows/{uuid}", headers=logged_in_headers) response = await client.delete(f"api/v1/flows/{uuid}", headers=logged_in_headers)
assert response.status_code == 404 assert response.status_code == 404
def test_read_only_starter_projects(client: TestClient, active_user, logged_in_headers): async def test_read_only_starter_projects(client: TestClient, active_user, logged_in_headers):
response = client.get("api/v1/flows/", headers=logged_in_headers) response = await client.get("api/v1/flows/", headers=logged_in_headers)
starter_projects = load_starter_projects() starter_projects = load_starter_projects()
assert response.status_code == 200 assert response.status_code == 200
assert len(response.json()) == len(starter_projects) assert len(response.json()) == len(starter_projects)
@pytest.mark.load_flows @pytest.mark.load_flows
def test_load_flows(client: TestClient, load_flows_dir): async def test_load_flows(client: TestClient, load_flows_dir):
response = client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b") response = await client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b")
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["name"] == "BasicExample" assert response.json()["name"] == "BasicExample"
# re-run to ensure updates work well # re-run to ensure updates work well
load_flows_from_directory() load_flows_from_directory()
response = client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b") response = await client.get("api/v1/flows/c54f9130-f2fa-4a3e-b22a-3856d946351b")
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["name"] == "BasicExample" assert response.json()["name"] == "BasicExample"
@ -436,5 +436,5 @@ def test_sqlite_pragmas():
with db_service.with_session() as session: with db_service.with_session() as session:
from sqlalchemy import text from sqlalchemy import text
assert "wal" == session.execute(text("PRAGMA journal_mode;")).fetchone()[0] assert "wal" == session.exec(text("PRAGMA journal_mode;")).scalar()
assert 1 == session.execute(text("PRAGMA synchronous;")).fetchone()[0] assert 1 == session.exec(text("PRAGMA synchronous;")).scalar()

View file

@ -3,14 +3,14 @@ from uuid import UUID, uuid4
import pytest import pytest
from fastapi import status from fastapi import status
from fastapi.testclient import TestClient from httpx import AsyncClient
from langflow.custom.directory_reader.directory_reader import DirectoryReader from langflow.custom.directory_reader.directory_reader import DirectoryReader
from langflow.services.deps import get_settings_service from langflow.services.deps import get_settings_service
def run_post(client, flow_id, headers, post_data): async def run_post(client, flow_id, headers, post_data):
response = client.post( response = await client.post(
f"api/v1/process/{flow_id}", f"api/v1/process/{flow_id}",
headers=headers, headers=headers,
json=post_data, json=post_data,
@ -20,9 +20,9 @@ def run_post(client, flow_id, headers, post_data):
# Helper function to poll task status # Helper function to poll task status
def poll_task_status(client, headers, href, max_attempts=20, sleep_time=1): async def poll_task_status(client, headers, href, max_attempts=20, sleep_time=1):
for _ in range(max_attempts): for _ in range(max_attempts):
task_status_response = client.get( task_status_response = await client.get(
href, href,
headers=headers, headers=headers,
) )
@ -135,7 +135,7 @@ PROMPT_REQUEST = {
# "session_id": None, # "session_id": None,
# } # }
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data) # response = await client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# assert response.status_code == 403 # assert response.status_code == 403
# assert response.json() == {"detail": "Invalid or missing API key"} # assert response.json() == {"detail": "Invalid or missing API key"}
@ -160,7 +160,7 @@ PROMPT_REQUEST = {
# } # }
# invalid_id = uuid.uuid4() # invalid_id = uuid.uuid4()
# response = client.post(f"api/v1/process/{invalid_id}", headers=headers, json=post_data) # response = await client.post(f"api/v1/process/{invalid_id}", headers=headers, json=post_data)
# assert response.status_code == 404 # assert response.status_code == 404
# assert f"Flow {invalid_id} not found" in response.json()["detail"] # assert f"Flow {invalid_id} not found" in response.json()["detail"]
@ -216,7 +216,7 @@ PROMPT_REQUEST = {
# # Make the request to the FastAPI TestClient # # Make the request to the FastAPI TestClient
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data) # response = await client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# # Check the response # # Check the response
# assert response.status_code == 200, response.json() # assert response.status_code == 200, response.json()
@ -253,15 +253,15 @@ PROMPT_REQUEST = {
# # Make the request to the FastAPI TestClient # # Make the request to the FastAPI TestClient
# response = client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data) # response = await client.post(f"api/v1/process/{flow.id}", headers=headers, json=post_data)
# # Check the response # # Check the response
# assert response.status_code == 403, response.json() # assert response.status_code == 403, response.json()
# assert response.json() == {"detail": "Invalid or missing API key"} # assert response.json() == {"detail": "Invalid or missing API key"}
def test_get_all(client: TestClient, logged_in_headers): async def test_get_all(client: AsyncClient, logged_in_headers):
response = client.get("api/v1/all", headers=logged_in_headers) response = await client.get("api/v1/all", headers=logged_in_headers)
assert response.status_code == 200 assert response.status_code == 200
settings = get_settings_service().settings settings = get_settings_service().settings
dir_reader = DirectoryReader(settings.components_path[0]) dir_reader = DirectoryReader(settings.components_path[0])
@ -278,7 +278,7 @@ def test_get_all(client: TestClient, logged_in_headers):
assert "ChatOutput" in json_response["outputs"] assert "ChatOutput" in json_response["outputs"]
def test_post_validate_code(client: TestClient): async def test_post_validate_code(client: AsyncClient):
# Test case with a valid import and function # Test case with a valid import and function
code1 = """ code1 = """
import math import math
@ -286,7 +286,7 @@ import math
def square(x): def square(x):
return x ** 2 return x ** 2
""" """
response1 = client.post("api/v1/validate/code", json={"code": code1}) response1 = await client.post("api/v1/validate/code", json={"code": code1})
assert response1.status_code == 200 assert response1.status_code == 200
assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}} assert response1.json() == {"imports": {"errors": []}, "function": {"errors": []}}
@ -297,7 +297,7 @@ import non_existent_module
def square(x): def square(x):
return x ** 2 return x ** 2
""" """
response2 = client.post("api/v1/validate/code", json={"code": code2}) response2 = await client.post("api/v1/validate/code", json={"code": code2})
assert response2.status_code == 200 assert response2.status_code == 200
assert response2.json() == { assert response2.json() == {
"imports": {"errors": ["No module named 'non_existent_module'"]}, "imports": {"errors": ["No module named 'non_existent_module'"]},
@ -311,7 +311,7 @@ import math
def square(x) def square(x)
return x ** 2 return x ** 2
""" """
response3 = client.post("api/v1/validate/code", json={"code": code3}) response3 = await client.post("api/v1/validate/code", json={"code": code3})
assert response3.status_code == 200 assert response3.status_code == 200
assert response3.json() == { assert response3.json() == {
"imports": {"errors": []}, "imports": {"errors": []},
@ -319,11 +319,11 @@ def square(x)
} }
# Test case with invalid JSON payload # Test case with invalid JSON payload
response4 = client.post("api/v1/validate/code", json={"invalid_key": code1}) response4 = await client.post("api/v1/validate/code", json={"invalid_key": code1})
assert response4.status_code == 422 assert response4.status_code == 422
# Test case with an empty code string # Test case with an empty code string
response5 = client.post("api/v1/validate/code", json={"code": ""}) response5 = await client.post("api/v1/validate/code", json={"code": ""})
assert response5.status_code == 200 assert response5.status_code == 200
assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}} assert response5.json() == {"imports": {"errors": []}, "function": {"errors": []}}
@ -334,7 +334,7 @@ import math
def square(x) def square(x)
return x ** 2 return x ** 2
""" """
response6 = client.post("api/v1/validate/code", json={"code": code6}) response6 = await client.post("api/v1/validate/code", json={"code": code6})
assert response6.status_code == 200 assert response6.status_code == 200
assert response6.json() == { assert response6.json() == {
"imports": {"errors": []}, "imports": {"errors": []},
@ -359,16 +359,16 @@ What is a good name for a company that makes {product}?
INVALID_PROMPT = "This is an invalid prompt without any input variable." INVALID_PROMPT = "This is an invalid prompt without any input variable."
def test_valid_prompt(client: TestClient): async def test_valid_prompt(client: AsyncClient):
PROMPT_REQUEST["template"] = VALID_PROMPT PROMPT_REQUEST["template"] = VALID_PROMPT
response = client.post("api/v1/validate/prompt", json=PROMPT_REQUEST) response = await client.post("api/v1/validate/prompt", json=PROMPT_REQUEST)
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["input_variables"] == ["product"] assert response.json()["input_variables"] == ["product"]
def test_invalid_prompt(client: TestClient): async def test_invalid_prompt(client: AsyncClient):
PROMPT_REQUEST["template"] = INVALID_PROMPT PROMPT_REQUEST["template"] = INVALID_PROMPT
response = client.post( response = await client.post(
"api/v1/validate/prompt", "api/v1/validate/prompt",
json=PROMPT_REQUEST, json=PROMPT_REQUEST,
) )
@ -385,22 +385,22 @@ def test_invalid_prompt(client: TestClient):
("{a}, {b}, and {c} are variables.", ["a", "b", "c"]), ("{a}, {b}, and {c} are variables.", ["a", "b", "c"]),
], ],
) )
def test_various_prompts(client, prompt, expected_input_variables): async def test_various_prompts(client, prompt, expected_input_variables):
PROMPT_REQUEST["template"] = prompt PROMPT_REQUEST["template"] = prompt
response = client.post("api/v1/validate/prompt", json=PROMPT_REQUEST) response = await client.post("api/v1/validate/prompt", json=PROMPT_REQUEST)
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["input_variables"] == expected_input_variables assert response.json()["input_variables"] == expected_input_variables
def test_get_vertices_flow_not_found(client, logged_in_headers): async def test_get_vertices_flow_not_found(client, logged_in_headers):
uuid = uuid4() uuid = uuid4()
response = client.post(f"/api/v1/build/{uuid}/vertices", headers=logged_in_headers) response = await client.post(f"/api/v1/build/{uuid}/vertices", headers=logged_in_headers)
assert response.status_code == 500 assert response.status_code == 500
def test_get_vertices(client, added_flow_with_prompt_and_history, logged_in_headers): async def test_get_vertices(client, added_flow_with_prompt_and_history, logged_in_headers):
flow_id = added_flow_with_prompt_and_history["id"] flow_id = added_flow_with_prompt_and_history["id"]
response = client.post(f"/api/v1/build/{flow_id}/vertices", headers=logged_in_headers) response = await client.post(f"/api/v1/build/{flow_id}/vertices", headers=logged_in_headers)
assert response.status_code == 200 assert response.status_code == 200
assert "ids" in response.json() assert "ids" in response.json()
# The response should contain the list in this order # The response should contain the list in this order
@ -415,22 +415,22 @@ def test_get_vertices(client, added_flow_with_prompt_and_history, logged_in_head
} }
def test_build_vertex_invalid_flow_id(client, logged_in_headers): async def test_build_vertex_invalid_flow_id(client, logged_in_headers):
uuid = uuid4() uuid = uuid4()
response = client.post(f"/api/v1/build/{uuid}/vertices/vertex_id", headers=logged_in_headers) response = await client.post(f"/api/v1/build/{uuid}/vertices/vertex_id", headers=logged_in_headers)
assert response.status_code == 500 assert response.status_code == 500
def test_build_vertex_invalid_vertex_id(client, added_flow_with_prompt_and_history, logged_in_headers): async 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"] 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) response = await client.post(f"/api/v1/build/{flow_id}/vertices/invalid_vertex_id", headers=logged_in_headers)
assert response.status_code == 500 assert response.status_code == 500
def test_successful_run_no_payload(client, simple_api_test, created_api_key): async def test_successful_run_no_payload(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"] flow_id = simple_api_test["id"]
response = client.post(f"/api/v1/run/{flow_id}", headers=headers) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_200_OK, response.text assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content # Add more assertions here to validate the response content
json_response = response.json() json_response = response.json()
@ -455,13 +455,13 @@ def test_successful_run_no_payload(client, simple_api_test, created_api_key):
assert all([result is not None for result in inner_results]), (outputs_dict, output_results_has_results) assert all([result is not None for result in inner_results]), (outputs_dict, output_results_has_results)
def test_successful_run_with_output_type_text(client, simple_api_test, created_api_key): async def test_successful_run_with_output_type_text(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"] flow_id = simple_api_test["id"]
payload = { payload = {
"output_type": "text", "output_type": "text",
} }
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content # Add more assertions here to validate the response content
json_response = response.json() json_response = response.json()
@ -485,14 +485,14 @@ def test_successful_run_with_output_type_text(client, simple_api_test, created_a
assert all([key in result for result in inner_results for key in expected_keys]), outputs_dict assert all([key in result for result in inner_results for key in expected_keys]), outputs_dict
def test_successful_run_with_output_type_any(client, simple_api_test, created_api_key): async def test_successful_run_with_output_type_any(client, simple_api_test, created_api_key):
# This one should have both the ChatOutput and TextOutput components # This one should have both the ChatOutput and TextOutput components
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"] flow_id = simple_api_test["id"]
payload = { payload = {
"output_type": "any", "output_type": "any",
} }
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content # Add more assertions here to validate the response content
json_response = response.json() json_response = response.json()
@ -516,7 +516,7 @@ def test_successful_run_with_output_type_any(client, simple_api_test, created_ap
assert all([key in result for result in inner_results for key in expected_keys]), outputs_dict assert all([key in result for result in inner_results for key in expected_keys]), outputs_dict
def test_successful_run_with_output_type_debug(client, simple_api_test, created_api_key): async def test_successful_run_with_output_type_debug(client, simple_api_test, created_api_key):
# This one should return outputs for all components # This one should return outputs for all components
# Let's just check the amount of outputs(there should be 7) # Let's just check the amount of outputs(there should be 7)
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
@ -524,7 +524,7 @@ def test_successful_run_with_output_type_debug(client, simple_api_test, created_
payload = { payload = {
"output_type": "debug", "output_type": "debug",
} }
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content # Add more assertions here to validate the response content
json_response = response.json() json_response = response.json()
@ -541,7 +541,7 @@ def test_successful_run_with_output_type_debug(client, simple_api_test, created_
assert len(outputs_dict.get("outputs")) == 3 assert len(outputs_dict.get("outputs")) == 3
def test_successful_run_with_input_type_text(client, simple_api_test, created_api_key): async def test_successful_run_with_input_type_text(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"] flow_id = simple_api_test["id"]
payload = { payload = {
@ -549,7 +549,7 @@ def test_successful_run_with_input_type_text(client, simple_api_test, created_ap
"output_type": "debug", "output_type": "debug",
"input_value": "value1", "input_value": "value1",
} }
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content # Add more assertions here to validate the response content
json_response = response.json() json_response = response.json()
@ -574,7 +574,8 @@ def test_successful_run_with_input_type_text(client, simple_api_test, created_ap
), text_input_outputs ), text_input_outputs
def test_successful_run_with_input_type_chat(client, simple_api_test, created_api_key): @pytest.mark.api_key_required
async def test_successful_run_with_input_type_chat(client: AsyncClient, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"] flow_id = simple_api_test["id"]
payload = { payload = {
@ -582,7 +583,7 @@ def test_successful_run_with_input_type_chat(client, simple_api_test, created_ap
"output_type": "debug", "output_type": "debug",
"input_value": "value1", "input_value": "value1",
} }
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content # Add more assertions here to validate the response content
json_response = response.json() json_response = response.json()
@ -606,7 +607,7 @@ def test_successful_run_with_input_type_chat(client, simple_api_test, created_ap
), chat_input_outputs ), chat_input_outputs
def test_invalid_run_with_input_type_chat(client, simple_api_test, created_api_key): async def test_invalid_run_with_input_type_chat(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"] flow_id = simple_api_test["id"]
payload = { payload = {
@ -615,12 +616,12 @@ def test_invalid_run_with_input_type_chat(client, simple_api_test, created_api_k
"input_value": "value1", "input_value": "value1",
"tweaks": {"Chat Input": {"input_value": "value2"}}, "tweaks": {"Chat Input": {"input_value": "value2"}},
} }
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_400_BAD_REQUEST, response.text assert response.status_code == status.HTTP_400_BAD_REQUEST, response.text
assert "If you pass an input_value to the chat input, you cannot pass a tweak with the same name." in response.text assert "If you pass an input_value to the chat input, you cannot pass a tweak with the same name." in response.text
def test_successful_run_with_input_type_any(client, simple_api_test, created_api_key): async def test_successful_run_with_input_type_any(client, simple_api_test, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = simple_api_test["id"] flow_id = simple_api_test["id"]
payload = { payload = {
@ -628,7 +629,7 @@ def test_successful_run_with_input_type_any(client, simple_api_test, created_api
"output_type": "debug", "output_type": "debug",
"input_value": "value1", "input_value": "value1",
} }
response = client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers, json=payload)
assert response.status_code == status.HTTP_200_OK, response.text assert response.status_code == status.HTTP_200_OK, response.text
# Add more assertions here to validate the response content # Add more assertions here to validate the response content
json_response = response.json() json_response = response.json()
@ -660,19 +661,19 @@ def test_successful_run_with_input_type_any(client, simple_api_test, created_api
), any_input_outputs ), any_input_outputs
def test_invalid_flow_id(client, created_api_key): async def test_invalid_flow_id(client, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = "invalid-flow-id" flow_id = "invalid-flow-id"
response = client.post(f"/api/v1/run/{flow_id}", headers=headers) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND, response.text assert response.status_code == status.HTTP_404_NOT_FOUND, response.text
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = UUID(int=0) flow_id = UUID(int=0)
response = client.post(f"/api/v1/run/{flow_id}", headers=headers) response = await client.post(f"/api/v1/run/{flow_id}", headers=headers)
assert response.status_code == status.HTTP_404_NOT_FOUND, response.text assert response.status_code == status.HTTP_404_NOT_FOUND, response.text
# Check if the error detail is as expected # Check if the error detail is as expected
def test_starter_projects(client, created_api_key): async def test_starter_projects(client, created_api_key):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
response = client.get("/api/v1/starter-projects/", headers=headers) response = await client.get("api/v1/starter-projects/", headers=headers)
assert response.status_code == status.HTTP_200_OK, response.text assert response.status_code == status.HTTP_200_OK, response.text

View file

@ -1,7 +1,15 @@
import os
import re import re
import shutil
import tempfile
from contextlib import suppress
from pathlib import Path
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
from asgi_lifespan import LifespanManager
from httpx import ASGITransport, AsyncClient
from sqlmodel import Session
from langflow.services.deps import get_storage_service from langflow.services.deps import get_storage_service
from langflow.services.storage.service import StorageService from langflow.services.storage.service import StorageService
@ -19,12 +27,42 @@ def mock_storage_service():
return service return service
def test_upload_file(client, mock_storage_service, created_api_key, flow): @pytest.fixture(name="files_client", scope="function")
headers = {"x-api-key": created_api_key.api_key} async def files_client_fixture(session: Session, monkeypatch, request, load_flows_dir, mock_storage_service):
# Replace the actual storage service with the mock # Set the database url to a test database
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service if "noclient" in request.keywords:
yield
else:
db_dir = tempfile.mkdtemp()
db_path = Path(db_dir) / "test.db"
monkeypatch.setenv("LANGFLOW_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false")
if "load_flows" in request.keywords:
shutil.copyfile(
pytest.BASIC_EXAMPLE_PATH, os.path.join(load_flows_dir, "c54f9130-f2fa-4a3e-b22a-3856d946351b.json")
)
monkeypatch.setenv("LANGFLOW_LOAD_FLOWS_PATH", load_flows_dir)
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "true")
response = client.post( from langflow.main import create_app
app = create_app()
app.dependency_overrides[get_storage_service] = lambda: mock_storage_service
async with LifespanManager(app, startup_timeout=None, shutdown_timeout=None) as manager:
async with AsyncClient(transport=ASGITransport(app=manager.app), base_url="http://testserver/") as client:
yield client
# app.dependency_overrides.clear()
monkeypatch.undo()
# clear the temp db
with suppress(FileNotFoundError):
db_path.unlink()
async def test_upload_file(files_client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key}
response = await files_client.post(
f"api/v1/files/upload/{flow.id}", f"api/v1/files/upload/{flow.id}",
files={"file": ("test.txt", b"test content")}, files={"file": ("test.txt", b"test content")},
headers=headers, headers=headers,
@ -39,41 +77,36 @@ def test_upload_file(client, mock_storage_service, created_api_key, flow):
assert file_path_pattern.match(response_json["file_path"]) assert file_path_pattern.match(response_json["file_path"])
def test_download_file(client, mock_storage_service, created_api_key, flow): async def test_download_file(files_client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service response = await files_client.get(f"api/v1/files/download/{flow.id}/test.txt", headers=headers)
response = client.get(f"api/v1/files/download/{flow.id}/test.txt", headers=headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.content == b"file content" assert response.content == b"file content"
def test_list_files(client, mock_storage_service, created_api_key, flow): async def test_list_files(files_client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
client.app.dependency_overrides[get_storage_service] = lambda: mock_storage_service response = await files_client.get(f"api/v1/files/list/{flow.id}", headers=headers)
response = client.get(f"api/v1/files/list/{flow.id}", headers=headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {"files": ["file1.txt", "file2.jpg"]} assert response.json() == {"files": ["file1.txt", "file2.jpg"]}
def test_delete_file(client, mock_storage_service, created_api_key, flow): async def test_delete_file(files_client, mock_storage_service, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key} 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) response = await files_client.delete(f"api/v1/files/delete/{flow.id}/test.txt", headers=headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {"message": "File test.txt deleted successfully"} assert response.json() == {"message": "File test.txt deleted successfully"}
def test_file_operations(client, created_api_key, flow): async def test_file_operations(client, created_api_key, flow):
headers = {"x-api-key": created_api_key.api_key} headers = {"x-api-key": created_api_key.api_key}
flow_id = flow.id flow_id = flow.id
file_name = "test.txt" file_name = "test.txt"
file_content = b"Hello, world!" file_content = b"Hello, world!"
# Step 1: Upload the file # Step 1: Upload the file
response = client.post( response = await client.post(
f"api/v1/files/upload/{flow_id}", f"api/v1/files/upload/{flow_id}",
files={"file": (file_name, file_content)}, files={"file": (file_name, file_content)},
headers=headers, headers=headers,
@ -91,21 +124,21 @@ def test_file_operations(client, created_api_key, flow):
full_file_name = response_json["file_path"].split("/")[-1] full_file_name = response_json["file_path"].split("/")[-1]
# Step 2: List files in the folder # Step 2: List files in the folder
response = client.get(f"api/v1/files/list/{flow_id}", headers=headers) response = await client.get(f"api/v1/files/list/{flow_id}", headers=headers)
assert response.status_code == 200 assert response.status_code == 200
assert full_file_name in response.json()["files"] assert full_file_name in response.json()["files"]
# Step 3: Download the file and verify its content # Step 3: Download the file and verify its content
response = client.get(f"api/v1/files/download/{flow_id}/{full_file_name}", headers=headers) response = await client.get(f"api/v1/files/download/{flow_id}/{full_file_name}", headers=headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.content == file_content assert response.content == file_content
assert response.headers["content-type"] == "application/octet-stream" assert response.headers["content-type"] == "application/octet-stream"
# Step 4: Delete the file # Step 4: Delete the file
response = client.delete(f"api/v1/files/delete/{flow_id}/{full_file_name}", headers=headers) response = await client.delete(f"api/v1/files/delete/{flow_id}/{full_file_name}", headers=headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {"message": f"File {full_file_name} deleted successfully"} assert response.json() == {"message": f"File {full_file_name} deleted successfully"}
# Verify that the file is indeed deleted # Verify that the file is indeed deleted
response = client.get(f"api/v1/files/list/{flow_id}", headers=headers) response = await client.get(f"api/v1/files/list/{flow_id}", headers=headers)
assert full_file_name not in response.json()["files"] assert full_file_name not in response.json()["files"]

View file

@ -1,8 +1,9 @@
import pytest import pytest
from sqlalchemy.exc import IntegrityError
from langflow.services.auth.utils import get_password_hash from langflow.services.auth.utils import get_password_hash
from langflow.services.database.models.user import User from langflow.services.database.models.user import User
from langflow.services.deps import session_scope from langflow.services.deps import session_scope
from sqlalchemy.exc import IntegrityError
@pytest.fixture @pytest.fixture
@ -15,7 +16,7 @@ def test_user():
) )
def test_login_successful(client, test_user): async def test_login_successful(client, test_user):
# Adding the test user to the database # Adding the test user to the database
try: try:
with session_scope() as session: with session_scope() as session:
@ -24,22 +25,22 @@ def test_login_successful(client, test_user):
except IntegrityError: except IntegrityError:
pass pass
response = client.post("api/v1/login", data={"username": "testuser", "password": "testpassword"}) response = await client.post("api/v1/login", data={"username": "testuser", "password": "testpassword"})
assert response.status_code == 200 assert response.status_code == 200
assert "access_token" in response.json() assert "access_token" in response.json()
def test_login_unsuccessful_wrong_username(client): async def test_login_unsuccessful_wrong_username(client):
response = client.post("api/v1/login", data={"username": "wrongusername", "password": "testpassword"}) response = await client.post("api/v1/login", data={"username": "wrongusername", "password": "testpassword"})
assert response.status_code == 401 assert response.status_code == 401
assert response.json()["detail"] == "Incorrect username or password" assert response.json()["detail"] == "Incorrect username or password"
def test_login_unsuccessful_wrong_password(client, test_user, session): async def test_login_unsuccessful_wrong_password(client, test_user, session):
# Adding the test user to the database # Adding the test user to the database
session.add(test_user) session.add(test_user)
session.commit() session.commit()
response = client.post("api/v1/login", data={"username": "testuser", "password": "wrongpassword"}) response = await client.post("api/v1/login", data={"username": "testuser", "password": "wrongpassword"})
assert response.status_code == 401 assert response.status_code == 401
assert response.json()["detail"] == "Incorrect username or password" assert response.json()["detail"] == "Incorrect username or password"

View file

@ -1,7 +1,7 @@
from uuid import UUID from uuid import UUID
import pytest import pytest
from fastapi.testclient import TestClient from httpx import AsyncClient
from langflow.memory import add_messagetables from langflow.memory import add_messagetables
@ -12,7 +12,7 @@ from langflow.services.deps import session_scope
@pytest.fixture() @pytest.fixture()
def created_message(): async def created_message():
with session_scope() as session: with session_scope() as session:
message = MessageCreate(text="Test message", sender="User", sender_name="User", session_id="session_id") message = MessageCreate(text="Test message", sender="User", sender_name="User", session_id="session_id")
messagetable = MessageTable.model_validate(message, from_attributes=True) messagetable = MessageTable.model_validate(message, from_attributes=True)
@ -35,18 +35,20 @@ def created_messages(session):
return message_list return message_list
def test_delete_messages(client: TestClient, created_messages, logged_in_headers): @pytest.mark.api_key_required
response = client.request( async def test_delete_messages(client: AsyncClient, created_messages, logged_in_headers):
response = await client.request(
"DELETE", "api/v1/monitor/messages", json=[str(msg.id) for msg in created_messages], headers=logged_in_headers "DELETE", "api/v1/monitor/messages", json=[str(msg.id) for msg in created_messages], headers=logged_in_headers
) )
assert response.status_code == 204, response.text assert response.status_code == 204, response.text
assert response.reason_phrase == "No Content" assert response.reason_phrase == "No Content"
def test_update_message(client: TestClient, logged_in_headers, created_message): @pytest.mark.api_key_required
async def test_update_message(client: AsyncClient, logged_in_headers, created_message):
message_id = created_message.id message_id = created_message.id
message_update = MessageUpdate(text="Updated content") message_update = MessageUpdate(text="Updated content")
response = client.put( response = await client.put(
f"api/v1/monitor/messages/{message_id}", json=message_update.model_dump(), headers=logged_in_headers f"api/v1/monitor/messages/{message_id}", json=message_update.model_dump(), headers=logged_in_headers
) )
assert response.status_code == 200, response.text assert response.status_code == 200, response.text
@ -54,34 +56,36 @@ def test_update_message(client: TestClient, logged_in_headers, created_message):
assert updated_message.text == "Updated content" assert updated_message.text == "Updated content"
def test_update_message_not_found(client: TestClient, logged_in_headers): @pytest.mark.api_key_required
async def test_update_message_not_found(client: AsyncClient, logged_in_headers):
non_existent_id = UUID("00000000-0000-0000-0000-000000000000") non_existent_id = UUID("00000000-0000-0000-0000-000000000000")
message_update = MessageUpdate(text="Updated content") message_update = MessageUpdate(text="Updated content")
response = client.put( response = await client.put(
f"api/v1/monitor/messages/{non_existent_id}", json=message_update.model_dump(), headers=logged_in_headers f"api/v1/monitor/messages/{non_existent_id}", json=message_update.model_dump(), headers=logged_in_headers
) )
assert response.status_code == 404, response.text assert response.status_code == 404, response.text
assert response.json()["detail"] == "Message not found" assert response.json()["detail"] == "Message not found"
def test_delete_messages_session(client: TestClient, created_messages, logged_in_headers): @pytest.mark.api_key_required
async def test_delete_messages_session(client: AsyncClient, created_messages, logged_in_headers):
session_id = "session_id2" session_id = "session_id2"
response = client.delete(f"api/v1/monitor/messages/session/{session_id}", headers=logged_in_headers) response = await client.delete(f"api/v1/monitor/messages/session/{session_id}", headers=logged_in_headers)
assert response.status_code == 204 assert response.status_code == 204
assert response.reason_phrase == "No Content" assert response.reason_phrase == "No Content"
assert len(created_messages) == 3 assert len(created_messages) == 3
response = client.get("api/v1/monitor/messages", headers=logged_in_headers) response = await client.get("api/v1/monitor/messages", headers=logged_in_headers)
assert response.status_code == 200 assert response.status_code == 200
assert len(response.json()) == 0 assert len(response.json()) == 0
# Successfully update session ID for all messages with the old session ID # Successfully update session ID for all messages with the old session ID
def test_successfully_update_session_id(client, session, logged_in_headers, created_messages): async def test_successfully_update_session_id(client, session, logged_in_headers, created_messages):
old_session_id = "session_id2" old_session_id = "session_id2"
new_session_id = "new_session_id" new_session_id = "new_session_id"
response = client.patch( response = await client.patch(
f"api/v1/monitor/messages/session/{old_session_id}", f"api/v1/monitor/messages/session/{old_session_id}",
params={"new_session_id": new_session_id}, params={"new_session_id": new_session_id},
headers=logged_in_headers, headers=logged_in_headers,
@ -93,7 +97,9 @@ def test_successfully_update_session_id(client, session, logged_in_headers, crea
for message in updated_messages: for message in updated_messages:
assert message["session_id"] == new_session_id assert message["session_id"] == new_session_id
response = client.get("api/v1/monitor/messages", headers=logged_in_headers, params={"session_id": new_session_id}) response = await client.get(
"api/v1/monitor/messages", headers=logged_in_headers, params={"session_id": new_session_id}
)
assert response.status_code == 200 assert response.status_code == 200
assert len(response.json()) == len(created_messages) assert len(response.json()) == len(created_messages)
for message in response.json(): for message in response.json():
@ -101,11 +107,11 @@ def test_successfully_update_session_id(client, session, logged_in_headers, crea
# No messages found with the given session ID # No messages found with the given session ID
def test_no_messages_found_with_given_session_id(client, session, logged_in_headers): async def test_no_messages_found_with_given_session_id(client, session, logged_in_headers):
old_session_id = "non_existent_session_id" old_session_id = "non_existent_session_id"
new_session_id = "new_session_id" new_session_id = "new_session_id"
response = client.patch( response = await client.patch(
f"/messages/session/{old_session_id}", params={"new_session_id": new_session_id}, headers=logged_in_headers f"/messages/session/{old_session_id}", params={"new_session_id": new_session_id}, headers=logged_in_headers
) )

View file

@ -1,4 +1,5 @@
import pytest import pytest
from langflow.processing.process import process_tweaks from langflow.processing.process import process_tweaks
from langflow.services.deps import get_session_service from langflow.services.deps import get_session_service

View file

@ -1,6 +1,7 @@
from datetime import datetime from datetime import datetime
import pytest import pytest
from httpx import AsyncClient
from langflow.services.auth.utils import create_super_user, get_password_hash 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 import UserUpdate
@ -22,14 +23,14 @@ def super_user(client):
@pytest.fixture @pytest.fixture
def super_user_headers(client, super_user): async def super_user_headers(client: AsyncClient, super_user):
settings_service = get_settings_service() settings_service = get_settings_service()
auth_settings = settings_service.auth_settings auth_settings = settings_service.auth_settings
login_data = { login_data = {
"username": auth_settings.SUPERUSER, "username": auth_settings.SUPERUSER,
"password": auth_settings.SUPERUSER_PASSWORD, "password": auth_settings.SUPERUSER_PASSWORD,
} }
response = client.post("/api/v1/login", data=login_data) response = await client.post("api/v1/login", data=login_data)
assert response.status_code == 200 assert response.status_code == 200
tokens = response.json() tokens = response.json()
a_token = tokens["access_token"] a_token = tokens["access_token"]
@ -52,9 +53,8 @@ def deactivated_user():
return user return user
def test_user_waiting_for_approval( @pytest.mark.api_key_required
client, async def test_user_waiting_for_approval(client: AsyncClient):
):
# Create a user that is not active and has never logged in # Create a user that is not active and has never logged in
with session_getter(get_db_service()) as session: with session_getter(get_db_service()) as session:
user = User( user = User(
@ -67,50 +67,54 @@ def test_user_waiting_for_approval(
session.commit() session.commit()
login_data = {"username": "waitingforapproval", "password": "testpassword"} login_data = {"username": "waitingforapproval", "password": "testpassword"}
response = client.post("/api/v1/login", data=login_data) response = await client.post("api/v1/login", data=login_data)
assert response.status_code == 400 assert response.status_code == 400
assert response.json()["detail"] == "Waiting for approval" assert response.json()["detail"] == "Waiting for approval"
def test_deactivated_user_cannot_login(client, deactivated_user): @pytest.mark.api_key_required
async def test_deactivated_user_cannot_login(client: AsyncClient, deactivated_user):
login_data = {"username": deactivated_user.username, "password": "testpassword"} login_data = {"username": deactivated_user.username, "password": "testpassword"}
response = client.post("/api/v1/login", data=login_data) response = await client.post("api/v1/login", data=login_data)
assert response.status_code == 401, response.json() assert response.status_code == 401, response.json()
assert response.json()["detail"] == "Inactive user", response.text assert response.json()["detail"] == "Inactive user", response.text
def test_deactivated_user_cannot_access(client, deactivated_user, logged_in_headers): async def test_deactivated_user_cannot_access(client: AsyncClient, deactivated_user, logged_in_headers):
# Assuming the headers for deactivated_user # Assuming the headers for deactivated_user
response = client.get("/api/v1/users", headers=logged_in_headers) response = await client.get("api/v1/users/", headers=logged_in_headers)
assert response.status_code == 403, response.json() assert response.status_code == 403, response.status_code
assert response.json()["detail"] == "The user doesn't have enough privileges", response.text assert response.json()["detail"] == "The user doesn't have enough privileges", response.text
def test_data_consistency_after_update(client, active_user, logged_in_headers, super_user_headers): @pytest.mark.api_key_required
async def test_data_consistency_after_update(client: AsyncClient, active_user, logged_in_headers, super_user_headers):
user_id = active_user.id user_id = active_user.id
update_data = UserUpdate(is_active=False) 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 = await client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=super_user_headers)
assert response.status_code == 200, response.json() assert response.status_code == 200, response.json()
# Fetch the updated user from the database # Fetch the updated user from the database
response = client.get("/api/v1/users/whoami", headers=logged_in_headers) response = await client.get("api/v1/users/whoami", headers=logged_in_headers)
assert response.status_code == 401, response.json() assert response.status_code == 401, response.json()
assert response.json()["detail"] == "User not found or is inactive." assert response.json()["detail"] == "User not found or is inactive."
def test_data_consistency_after_delete(client, test_user, super_user_headers): @pytest.mark.api_key_required
async def test_data_consistency_after_delete(client: AsyncClient, test_user, super_user_headers):
user_id = test_user.get("id") user_id = test_user.get("id")
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers) response = await client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
assert response.status_code == 200, response.json() assert response.status_code == 200, response.json()
# Attempt to fetch the deleted user from the database # Attempt to fetch the deleted user from the database
response = client.get("/api/v1/users", headers=super_user_headers) response = await client.get("api/v1/users/", headers=super_user_headers)
assert response.status_code == 200 assert response.status_code == 200
assert all(user["id"] != user_id for user in response.json()["users"]) assert all(user["id"] != user_id for user in response.json()["users"])
def test_inactive_user(client): @pytest.mark.api_key_required
async def test_inactive_user(client: AsyncClient):
# Create a user that is not active and has a last_login_at value # Create a user that is not active and has a last_login_at value
with session_getter(get_db_service()) as session: with session_getter(get_db_service()) as session:
user = User( user = User(
@ -123,12 +127,13 @@ def test_inactive_user(client):
session.commit() session.commit()
login_data = {"username": "inactiveuser", "password": "testpassword"} login_data = {"username": "inactiveuser", "password": "testpassword"}
response = client.post("/api/v1/login", data=login_data) response = await client.post("api/v1/login", data=login_data)
assert response.status_code == 401 assert response.status_code == 401
assert response.json()["detail"] == "Inactive user" assert response.json()["detail"] == "Inactive user"
def test_add_user(client, test_user): @pytest.mark.api_key_required
async def test_add_user(client: AsyncClient, test_user):
assert test_user["username"] == "testuser" assert test_user["username"] == "testuser"
@ -136,51 +141,55 @@ def test_add_user(client, test_user):
# def test_read_current_user(client: TestClient, active_user): # def test_read_current_user(client: TestClient, active_user):
# # First we need to login to get the access token # # First we need to login to get the access token
# login_data = {"username": "testuser", "password": "testpassword"} # login_data = {"username": "testuser", "password": "testpassword"}
# response = client.post("/api/v1/login", data=login_data) # response = await client.post("api/v1/login", data=login_data)
# assert response.status_code == 200 # assert response.status_code == 200
# headers = {"Authorization": f"Bearer {response.json()['access_token']}"} # headers = {"Authorization": f"Bearer {response.json()['access_token']}"}
# response = client.get("/api/v1/user", headers=headers) # response = await client.get("api/v1/user", headers=headers)
# assert response.status_code == 200, response.json() # assert response.status_code == 200, response.json()
# assert response.json()["username"] == "testuser" # assert response.json()["username"] == "testuser"
def test_read_all_users(client, super_user_headers): @pytest.mark.api_key_required
response = client.get("/api/v1/users", headers=super_user_headers) async def test_read_all_users(client: AsyncClient, super_user_headers):
response = await client.get("api/v1/users/", headers=super_user_headers)
assert response.status_code == 200, response.json() assert response.status_code == 200, response.json()
assert isinstance(response.json()["users"], list) assert isinstance(response.json()["users"], list)
def test_normal_user_cant_read_all_users(client, logged_in_headers): @pytest.mark.api_key_required
response = client.get("/api/v1/users", headers=logged_in_headers) async def test_normal_user_cant_read_all_users(client: AsyncClient, logged_in_headers):
response = await client.get("api/v1/users/", headers=logged_in_headers)
assert response.status_code == 403, response.json() assert response.status_code == 403, response.json()
assert response.json() == {"detail": "The user doesn't have enough privileges"} assert response.json() == {"detail": "The user doesn't have enough privileges"}
def test_patch_user(client, active_user, logged_in_headers): @pytest.mark.api_key_required
async def test_patch_user(client: AsyncClient, active_user, logged_in_headers):
user_id = active_user.id user_id = active_user.id
update_data = UserUpdate( update_data = UserUpdate(
username="newname", username="newname",
) )
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers) response = await client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
assert response.status_code == 200, response.json() assert response.status_code == 200, response.json()
update_data = UserUpdate( update_data = UserUpdate(
profile_image="new_image", profile_image="new_image",
) )
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers) response = await client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
assert response.status_code == 200, response.json() assert response.status_code == 200, response.json()
def test_patch_reset_password(client, active_user, logged_in_headers): @pytest.mark.api_key_required
async def test_patch_reset_password(client: AsyncClient, active_user, logged_in_headers):
user_id = active_user.id user_id = active_user.id
update_data = UserUpdate( update_data = UserUpdate(
password="newpassword", password="newpassword",
) )
response = client.patch( response = await client.patch(
f"/api/v1/users/{user_id}/reset-password", f"/api/v1/users/{user_id}/reset-password",
json=update_data.model_dump(), json=update_data.model_dump(),
headers=logged_in_headers, headers=logged_in_headers,
@ -188,17 +197,18 @@ def test_patch_reset_password(client, active_user, logged_in_headers):
assert response.status_code == 200, response.json() assert response.status_code == 200, response.json()
# Now we need to test if the new password works # Now we need to test if the new password works
login_data = {"username": active_user.username, "password": "newpassword"} login_data = {"username": active_user.username, "password": "newpassword"}
response = client.post("/api/v1/login", data=login_data) response = await client.post("api/v1/login", data=login_data)
assert response.status_code == 200 assert response.status_code == 200
def test_patch_user_wrong_id(client, active_user, logged_in_headers): @pytest.mark.api_key_required
async def test_patch_user_wrong_id(client: AsyncClient, active_user, logged_in_headers):
user_id = "wrong_id" user_id = "wrong_id"
update_data = UserUpdate( update_data = UserUpdate(
username="newname", username="newname",
) )
response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers) response = await client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers)
assert response.status_code == 422, response.json() assert response.status_code == 422, response.json()
json_response = response.json() json_response = response.json()
detail = json_response["detail"] detail = json_response["detail"]
@ -207,16 +217,18 @@ def test_patch_user_wrong_id(client, active_user, logged_in_headers):
assert error["type"] == "uuid_parsing" assert error["type"] == "uuid_parsing"
def test_delete_user(client, test_user, super_user_headers): @pytest.mark.api_key_required
async def test_delete_user(client: AsyncClient, test_user, super_user_headers):
user_id = test_user["id"] user_id = test_user["id"]
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers) response = await client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
assert response.status_code == 200 assert response.status_code == 200
assert response.json() == {"detail": "User deleted"} assert response.json() == {"detail": "User deleted"}
def test_delete_user_wrong_id(client, test_user, super_user_headers): @pytest.mark.api_key_required
async def test_delete_user_wrong_id(client: AsyncClient, test_user, super_user_headers):
user_id = "wrong_id" user_id = "wrong_id"
response = client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers) response = await client.delete(f"/api/v1/users/{user_id}", headers=super_user_headers)
assert response.status_code == 422 assert response.status_code == 422
json_response = response.json() json_response = response.json()
detail = json_response["detail"] detail = json_response["detail"]
@ -225,8 +237,9 @@ def test_delete_user_wrong_id(client, test_user, super_user_headers):
assert error["type"] == "uuid_parsing" assert error["type"] == "uuid_parsing"
def test_normal_user_cant_delete_user(client, test_user, logged_in_headers): @pytest.mark.api_key_required
async def test_normal_user_cant_delete_user(client: AsyncClient, test_user, logged_in_headers):
user_id = test_user["id"] user_id = test_user["id"]
response = client.delete(f"/api/v1/users/{user_id}", headers=logged_in_headers) response = await client.delete(f"/api/v1/users/{user_id}", headers=logged_in_headers)
assert response.status_code == 403 assert response.status_code == 403
assert response.json() == {"detail": "The user doesn't have enough privileges"} assert response.json() == {"detail": "The user doesn't have enough privileges"}

View file

@ -9,7 +9,7 @@ def check_openai_api_key_in_environment_variables():
pass pass
def test_webhook_endpoint(client, added_webhook_test): async def test_webhook_endpoint(client, added_webhook_test):
# The test is as follows: # The test is as follows:
# 1. The flow when run will get a "path" from the payload and save a file with the path as the name. # 1. The flow when run will get a "path" from the payload and save a file with the path as the name.
# We will create a temporary file path and send it to the webhook endpoint, then check if the file exists. # We will create a temporary file path and send it to the webhook endpoint, then check if the file exists.
@ -22,7 +22,7 @@ def test_webhook_endpoint(client, added_webhook_test):
payload = {"path": str(file_path)} payload = {"path": str(file_path)}
response = client.post(endpoint, json=payload) response = await client.post(endpoint, json=payload)
assert response.status_code == 202 assert response.status_code == 202
assert file_path.exists() assert file_path.exists()
@ -30,12 +30,12 @@ def test_webhook_endpoint(client, added_webhook_test):
# Send an invalid payload # Send an invalid payload
payload = {"invalid_key": "invalid_value"} payload = {"invalid_key": "invalid_value"}
response = client.post(endpoint, json=payload) response = await client.post(endpoint, json=payload)
assert response.status_code == 202 assert response.status_code == 202
assert not file_path.exists() assert not file_path.exists()
def test_webhook_flow_on_run_endpoint(client, added_webhook_test, created_api_key): async def test_webhook_flow_on_run_endpoint(client, added_webhook_test, created_api_key):
endpoint_name = added_webhook_test["endpoint_name"] endpoint_name = added_webhook_test["endpoint_name"]
endpoint = f"api/v1/run/{endpoint_name}?stream=false" endpoint = f"api/v1/run/{endpoint_name}?stream=false"
# Just test that "Random Payload" returns 202 # Just test that "Random Payload" returns 202
@ -43,16 +43,16 @@ def test_webhook_flow_on_run_endpoint(client, added_webhook_test, created_api_ke
payload = { payload = {
"output_type": "any", "output_type": "any",
} }
response = client.post(endpoint, headers={"x-api-key": created_api_key.api_key}, json=payload) response = await client.post(endpoint, headers={"x-api-key": created_api_key.api_key}, json=payload)
assert response.status_code == 200, response.json() assert response.status_code == 200, response.json()
def test_webhook_with_random_payload(client, added_webhook_test): async def test_webhook_with_random_payload(client, added_webhook_test):
endpoint_name = added_webhook_test["endpoint_name"] endpoint_name = added_webhook_test["endpoint_name"]
endpoint = f"api/v1/webhook/{endpoint_name}" endpoint = f"api/v1/webhook/{endpoint_name}"
# Just test that "Random Payload" returns 202 # Just test that "Random Payload" returns 202
# returns 202 # returns 202
response = client.post( response = await client.post(
endpoint, endpoint,
json="Random Payload", json="Random Payload",
) )

58
uv.lock generated
View file

@ -225,6 +225,18 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 }, { url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321 },
] ]
[[package]]
name = "asgi-lifespan"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "sniffio" },
]
sdist = { url = "https://files.pythonhosted.org/packages/6a/da/e7908b54e0f8043725a990bf625f2041ecf6bfe8eb7b19407f1c00b630f7/asgi-lifespan-2.1.0.tar.gz", hash = "sha256:5e2effaf0bfe39829cf2d64e7ecc47c7d86d676a6599f7afba378c31f5e3a308", size = 15627 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2f/f5/c36551e93acba41a59939ae6a0fb77ddb3f2e8e8caa716410c65f7341f72/asgi_lifespan-2.1.0-py3-none-any.whl", hash = "sha256:ed840706680e28428c01e14afb3875d7d76d3206f3d5b2f2294e059b5c23804f", size = 10895 },
]
[[package]] [[package]]
name = "asgiref" name = "asgiref"
version = "3.8.1" version = "3.8.1"
@ -335,14 +347,14 @@ wheels = [
[[package]] [[package]]
name = "authlib" name = "authlib"
version = "1.3.2" version = "1.3.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "cryptography" }, { name = "cryptography" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/f3/75/47dbab150ef6f9298e227a40c93c7fed5f3ffb67c9fb62cd49f66285e46e/authlib-1.3.2.tar.gz", hash = "sha256:4b16130117f9eb82aa6eec97f6dd4673c3f960ac0283ccdae2897ee4bc030ba2", size = 147313 } sdist = { url = "https://files.pythonhosted.org/packages/09/47/df70ecd34fbf86d69833fe4e25bb9ecbaab995c8e49df726dd416f6bb822/authlib-1.3.1.tar.gz", hash = "sha256:7ae843f03c06c5c0debd63c9db91f9fda64fa62a42a77419fa15fbb7e7a58917", size = 146074 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/df/4c/9aa0416a403d5cc80292cb030bcd2c918cce2755e314d8c1aa18656e1e12/Authlib-1.3.2-py2.py3-none-any.whl", hash = "sha256:ede026a95e9f5cdc2d4364a52103f5405e75aa156357e831ef2bfd0bc5094dfc", size = 225111 }, { url = "https://files.pythonhosted.org/packages/87/1f/bc95e43ffb57c05b8efcc376dd55a0240bf58f47ddf5a0f92452b6457b75/Authlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:d35800b973099bbadc49b42b256ecb80041ad56b7fe1216a362c7943c088f377", size = 223827 },
] ]
[[package]] [[package]]
@ -2321,6 +2333,19 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/66/2b/a6e68d7ea6f4fbc31cce20e354d6cef484da0a9891ee6a3eaf3aa9659d01/grpcio-1.66.1-cp312-cp312-win_amd64.whl", hash = "sha256:b0aa03d240b5539648d996cc60438f128c7f46050989e35b25f5c18286c86734", size = 4275565 }, { url = "https://files.pythonhosted.org/packages/66/2b/a6e68d7ea6f4fbc31cce20e354d6cef484da0a9891ee6a3eaf3aa9659d01/grpcio-1.66.1-cp312-cp312-win_amd64.whl", hash = "sha256:b0aa03d240b5539648d996cc60438f128c7f46050989e35b25f5c18286c86734", size = 4275565 },
] ]
[[package]]
name = "grpcio-health-checking"
version = "1.62.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "grpcio" },
{ name = "protobuf" },
]
sdist = { url = "https://files.pythonhosted.org/packages/eb/9f/09df9b02fc8eafa3031d878c8a4674a0311293c8c6f1c942cdaeec204126/grpcio-health-checking-1.62.3.tar.gz", hash = "sha256:5074ba0ce8f0dcfe328408ec5c7551b2a835720ffd9b69dade7fa3e0dc1c7a93", size = 15640 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/40/4c/ee3173906196b741ac6ba55a9788ba9ebf2cd05f91715a49b6c3bfbb9d73/grpcio_health_checking-1.62.3-py3-none-any.whl", hash = "sha256:f29da7dd144d73b4465fe48f011a91453e9ff6c8af0d449254cf80021cab3e0d", size = 18547 },
]
[[package]] [[package]]
name = "grpcio-status" name = "grpcio-status"
version = "1.62.3" version = "1.62.3"
@ -2484,7 +2509,7 @@ wheels = [
[[package]] [[package]]
name = "httpx" name = "httpx"
version = "0.27.2" version = "0.27.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "anyio" }, { name = "anyio" },
@ -2493,9 +2518,9 @@ dependencies = [
{ name = "idna" }, { name = "idna" },
{ name = "sniffio" }, { name = "sniffio" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/3da5bdf4408b8b2800061c339f240c1802f2e82d55e50bd39c5a881f47f0/httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5", size = 126413 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, { url = "https://files.pythonhosted.org/packages/41/7b/ddacf6dcebb42466abd03f368782142baa82e08fc0c1f8eaa05b4bae87d5/httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5", size = 75590 },
] ]
[package.optional-dependencies] [package.optional-dependencies]
@ -3486,6 +3511,7 @@ local = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "asgi-lifespan" },
{ name = "dictdiffer" }, { name = "dictdiffer" },
{ name = "httpx" }, { name = "httpx" },
{ name = "ipykernel" }, { name = "ipykernel" },
@ -3607,6 +3633,7 @@ requires-dist = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "asgi-lifespan", specifier = ">=2.1.0" },
{ name = "dictdiffer", specifier = ">=0.9.0" }, { name = "dictdiffer", specifier = ">=0.9.0" },
{ name = "httpx", specifier = ">=0.27.0" }, { name = "httpx", specifier = ">=0.27.0" },
{ name = "ipykernel", specifier = ">=6.29.0" }, { name = "ipykernel", specifier = ">=6.29.0" },
@ -3752,6 +3779,11 @@ local = [
{ name = "sentence-transformers" }, { name = "sentence-transformers" },
] ]
[package.dev-dependencies]
dev = [
{ name = "asgi-lifespan" },
]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "aiofiles", specifier = ">=24.1.0" }, { name = "aiofiles", specifier = ">=24.1.0" },
@ -3852,6 +3884,9 @@ requires-dist = [
{ name = "vulture", marker = "extra == 'dev'", specifier = ">=2.11" }, { name = "vulture", marker = "extra == 'dev'", specifier = ">=2.11" },
] ]
[package.metadata.requires-dev]
dev = [{ name = "asgi-lifespan", specifier = ">=2.1.0" }]
[[package]] [[package]]
name = "langfuse" name = "langfuse"
version = "2.51.0" version = "2.51.0"
@ -7876,16 +7911,21 @@ wheels = [
[[package]] [[package]]
name = "weaviate-client" name = "weaviate-client"
version = "3.26.7" version = "4.8.1"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
dependencies = [ dependencies = [
{ name = "authlib" }, { name = "authlib" },
{ name = "grpcio" },
{ name = "grpcio-health-checking" },
{ name = "grpcio-tools" },
{ name = "httpx" },
{ name = "pydantic" },
{ name = "requests" }, { name = "requests" },
{ name = "validators" }, { name = "validators" },
] ]
sdist = { url = "https://files.pythonhosted.org/packages/f8/2e/9588bae34c1d67d05ccc07d74a4f5d73cce342b916f79ab3a9114c6607bb/weaviate_client-3.26.7.tar.gz", hash = "sha256:ea538437800abc6edba21acf213accaf8a82065584ee8b914bae4a4ad4ef6b70", size = 210480 } sdist = { url = "https://files.pythonhosted.org/packages/4f/4d/650831937f25b8e788870b46a693a6e141d9d3d72bfd708ce88b0b01d69f/weaviate_client-4.8.1.tar.gz", hash = "sha256:2756996a2205bb991f258c064fc502011fc78a40e8786cb072208b1d3d7c9932", size = 681877 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/95/fb326052bc1d73cb3c19fcfaf6ebb477f896af68de07eaa1337e27ee57fa/weaviate_client-3.26.7-py3-none-any.whl", hash = "sha256:48b8d4b71df881b4e5e15964d7ac339434338ccee73779e3af7eab698a92083b", size = 120051 }, { url = "https://files.pythonhosted.org/packages/c8/d8/88610f5aaaffd3d2447fe755b86a8bb06b79472e45ec999baa5040dea9a3/weaviate_client-4.8.1-py3-none-any.whl", hash = "sha256:c16453ebfd9bd4045675f8e50841d1af21aa9af1332f379d0418c4531c03bd44", size = 374526 },
] ]
[[package]] [[package]]