feat: Add unit tests for run_flow_from_json with fake environment variables (#4015)
* Add tests for run_flow_from_json with fake environment variables - Implemented test_run_flow_with_fake_env to validate flow execution with a fake .env file. - Added test_run_flow_with_fake_env_TWEAKS to check flow execution using environment variables loaded from the fake .env file. * Replace keys in tweaks with their corresponding environment variable values - Implemented a function to recursively replace keys in the tweaks dictionary with values from the provided environment variables. * updated to use better way to load test json file * [autofix.ci] apply automated fixes * [autofix.ci] apply automated fixes * refactor: improve test readability and consistency in load tests - Renamed variable `TWEAKS` to `tweaks_dict` for clarity and consistency across tests. - Updated test function names to follow a consistent naming convention. - Enhanced comments for better understanding of test intentions. - Minor formatting adjustments to improve code readability. * feat: add aload_flow_from_json and arun_flow_from_json to module exports * fix: correct file path handling in aload_flow_from_json function * fix: improve environment variable handling in aload_flow_from_json function --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
This commit is contained in:
parent
e26b411f60
commit
0514d11d9c
6 changed files with 91 additions and 10 deletions
|
|
@ -1,11 +1,12 @@
|
||||||
from .load import aload_flow_from_json, arun_flow_from_json, load_flow_from_json, run_flow_from_json
|
from .load import aload_flow_from_json, arun_flow_from_json, load_flow_from_json, run_flow_from_json
|
||||||
from .utils import get_flow, upload_file
|
from .utils import get_flow, replace_tweaks_with_env, upload_file
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"aload_flow_from_json",
|
"aload_flow_from_json",
|
||||||
"arun_flow_from_json",
|
"arun_flow_from_json",
|
||||||
"get_flow",
|
"get_flow",
|
||||||
"load_flow_from_json",
|
"load_flow_from_json",
|
||||||
|
"replace_tweaks_with_env",
|
||||||
"run_flow_from_json",
|
"run_flow_from_json",
|
||||||
"upload_file",
|
"upload_file",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
import asyncio
|
|
||||||
import json
|
import json
|
||||||
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from aiofile import async_open
|
from aiofile import async_open
|
||||||
from dotenv import load_dotenv
|
from dotenv import dotenv_values
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from langflow.graph import Graph
|
from langflow.graph import Graph
|
||||||
from langflow.graph.schema import RunOutputs
|
from langflow.graph.schema import RunOutputs
|
||||||
|
from langflow.load.utils import replace_tweaks_with_env
|
||||||
from langflow.logging.logger import configure
|
from langflow.logging.logger import configure
|
||||||
from langflow.processing.process import process_tweaks, run_graph
|
from langflow.processing.process import process_tweaks, run_graph
|
||||||
from langflow.utils.async_helpers import run_until_complete
|
from langflow.utils.async_helpers import run_until_complete
|
||||||
|
|
@ -49,14 +50,17 @@ async def aload_flow_from_json(
|
||||||
configure(log_level=log_level, log_file=log_file_path, disable=disable_logs, async_file=True)
|
configure(log_level=log_level, log_file=log_file_path, disable=disable_logs, async_file=True)
|
||||||
|
|
||||||
# override env variables with .env file
|
# override env variables with .env file
|
||||||
if env_file:
|
if env_file and tweaks is not None:
|
||||||
await asyncio.to_thread(load_dotenv, env_file, override=True)
|
async with async_open(Path(env_file), encoding="utf-8") as f:
|
||||||
|
content = await f.read()
|
||||||
|
env_vars = dotenv_values(stream=StringIO(content))
|
||||||
|
tweaks = replace_tweaks_with_env(tweaks=tweaks, env_vars=env_vars)
|
||||||
|
|
||||||
# Update settings with cache and components path
|
# Update settings with cache and components path
|
||||||
await update_settings(cache=cache)
|
await update_settings(cache=cache)
|
||||||
|
|
||||||
if isinstance(flow, str | Path):
|
if isinstance(flow, str | Path):
|
||||||
async with async_open(Path(flow).name, encoding="utf-8") as f:
|
async with async_open(Path(flow), encoding="utf-8") as f:
|
||||||
content = await f.read()
|
content = await f.read()
|
||||||
flow_graph = json.loads(content)
|
flow_graph = json.loads(content)
|
||||||
# If input is a dictionary, assume it's a JSON object
|
# If input is a dictionary, assume it's a JSON object
|
||||||
|
|
|
||||||
|
|
@ -99,5 +99,28 @@ def get_flow(url: str, flow_id: str):
|
||||||
msg = f"Error retrieving flow: {e}"
|
msg = f"Error retrieving flow: {e}"
|
||||||
raise UploadError(msg) from e
|
raise UploadError(msg) from e
|
||||||
|
|
||||||
msg = f"Error retrieving flow: {response.status_code}"
|
|
||||||
raise UploadError(msg)
|
def replace_tweaks_with_env(tweaks: dict, env_vars: dict) -> dict:
|
||||||
|
"""Replace keys in the tweaks dictionary with their corresponding environment variable values.
|
||||||
|
|
||||||
|
This function recursively traverses the tweaks dictionary and replaces any string keys
|
||||||
|
with their values from the provided environment variables. If a key's value is a dictionary,
|
||||||
|
the function will call itself to handle nested dictionaries.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
tweaks (dict): A dictionary containing keys that may correspond to environment variable names.
|
||||||
|
env_vars (dict): A dictionary of environment variables where keys are variable names
|
||||||
|
and values are their corresponding values.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The updated tweaks dictionary with keys replaced by their environment variable values.
|
||||||
|
"""
|
||||||
|
for key, value in tweaks.items():
|
||||||
|
if isinstance(value, dict):
|
||||||
|
# Recursively replace in nested dictionaries
|
||||||
|
tweaks[key] = replace_tweaks_with_env(value, env_vars)
|
||||||
|
elif isinstance(value, str):
|
||||||
|
env_value = env_vars.get(value) # Get the value from the provided environment variables
|
||||||
|
if env_value is not None:
|
||||||
|
tweaks[key] = env_value
|
||||||
|
return tweaks
|
||||||
|
|
|
||||||
|
|
@ -103,6 +103,7 @@ def pytest_configure(config):
|
||||||
pytest.VECTOR_STORE_PATH = data_path / "Vector_store.json"
|
pytest.VECTOR_STORE_PATH = data_path / "Vector_store.json"
|
||||||
pytest.SIMPLE_API_TEST = data_path / "SimpleAPITest.json"
|
pytest.SIMPLE_API_TEST = data_path / "SimpleAPITest.json"
|
||||||
pytest.MEMORY_CHATBOT_NO_LLM = data_path / "MemoryChatbotNoLLM.json"
|
pytest.MEMORY_CHATBOT_NO_LLM = data_path / "MemoryChatbotNoLLM.json"
|
||||||
|
pytest.ENV_VARIABLE_TEST = data_path / "env_variable_test.json"
|
||||||
pytest.LOOP_TEST = data_path / "LoopTest.json"
|
pytest.LOOP_TEST = data_path / "LoopTest.json"
|
||||||
pytest.CODE_WITH_SYNTAX_ERROR = """
|
pytest.CODE_WITH_SYNTAX_ERROR = """
|
||||||
def get_text():
|
def get_text():
|
||||||
|
|
|
||||||
1
src/backend/tests/data/env_variable_test.json
Normal file
1
src/backend/tests/data/env_variable_test.json
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,8 @@
|
||||||
import inspect
|
import inspect
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from dotenv import load_dotenv
|
||||||
from langflow.load import run_flow_from_json
|
from langflow.load import run_flow_from_json
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,5 +29,53 @@ def test_run_flow_from_json_params():
|
||||||
params = func_spec.args + func_spec.kwonlyargs
|
params = func_spec.args + func_spec.kwonlyargs
|
||||||
assert expected_params.issubset(params), "Not all expected parameters are present in run_flow_from_json"
|
assert expected_params.issubset(params), "Not all expected parameters are present in run_flow_from_json"
|
||||||
|
|
||||||
# TODO: Add tests by loading a flow and running it need to text with fake llm and check if it returns the
|
# TODO: Add tests by loading a flow and running it need to text with fake llm and check if it
|
||||||
# correct output
|
# returns the correct output
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def fake_env_file(tmp_path):
|
||||||
|
# Create a fake .env file
|
||||||
|
env_file = tmp_path / ".env"
|
||||||
|
env_file.write_text("TEST_OP=TESTWORKS")
|
||||||
|
return env_file
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_flow_with_fake_env(fake_env_file):
|
||||||
|
# Load the flow from the JSON file
|
||||||
|
# flow_file = Path("src/backend/tests/data/env_variable_test.json")
|
||||||
|
flow_file = pytest.ENV_VARIABLE_TEST
|
||||||
|
tweaks_dict = {"Secret-zIbKs": {"secret_key_input": "TEST_OP"}}
|
||||||
|
|
||||||
|
# Run the flow from JSON, providing the fake env file
|
||||||
|
result = run_flow_from_json(
|
||||||
|
flow=flow_file,
|
||||||
|
input_value="some_input_value",
|
||||||
|
env_file=str(fake_env_file), # Pass the path of the fake env file
|
||||||
|
tweaks=tweaks_dict,
|
||||||
|
)
|
||||||
|
# Extract and check the output data
|
||||||
|
output_data = result[0].outputs[0].results["message"].data["text"]
|
||||||
|
assert output_data == "TESTWORKS"
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_flow_with_fake_env_tweaks(fake_env_file):
|
||||||
|
# Load the flow from the JSON file
|
||||||
|
# flow_file = Path("src/backend/tests/data/env_variable_test.json")
|
||||||
|
flow_file = pytest.ENV_VARIABLE_TEST
|
||||||
|
|
||||||
|
# Load env file and set up tweaks
|
||||||
|
|
||||||
|
load_dotenv(str(fake_env_file))
|
||||||
|
tweaks = {
|
||||||
|
"Secret-zIbKs": {"secret_key_input": os.environ["TEST_OP"]},
|
||||||
|
}
|
||||||
|
# Run the flow from JSON without passing the env_file
|
||||||
|
result = run_flow_from_json(
|
||||||
|
flow=flow_file,
|
||||||
|
input_value="some_input_value",
|
||||||
|
tweaks=tweaks,
|
||||||
|
)
|
||||||
|
# Extract and check the output data
|
||||||
|
output_data = result[0].outputs[0].results["message"].data["text"]
|
||||||
|
assert output_data == "TESTWORKS"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue