feat: pagination improvements + tests (#4163)

* 📝 (utils.py): Add Query import and custom_params function to handle custom pagination parameters
📝 (flows.py): Update read_flows function to handle get_all flag and add error handling for missing folders
📝 (folders.py): Add custom_params dependency to read_folder endpoint for custom pagination handling
📝 (model.py): Remove PaginatedFlowResponse class as it is no longer used, add FlowHeader class to represent flow headers without data

✨ (test_database.py): Add pagination support for reading flows with different parameters to improve testing coverage and flexibility.

✅ (test_database.py): add unit tests for reading folders with different scenarios such as pagination, flows, search, and component filter to ensure proper functionality and data retrieval.

* 📝 (utils.py): Add constants MAX_PAGE_SIZE and MIN_PAGE_SIZE for better readability and maintainability
📝 (flows.py): Update get_all parameter default value to True for backward compatibility with frontend
📝 (flows.py): Update error message formatting for clarity
📝 (model.py): Update comments for folder_id, is_component, endpoint_name, and description fields for clarity

* ✨ (test_database.py): Simplify test functions by removing unnecessary parameters and code duplication to improve readability and maintainability.

* formatter

* [autofix.ci] apply automated fixes

* ruff fix

* [autofix.ci] apply automated fixes

---------

Co-authored-by: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Cristhian Zanforlin Lousa 2024-10-20 14:04:26 -03:00 • committed by GitHub
commit b969818738
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 356 additions and 44 deletions

View file

@ -4,7 +4,8 @@ import uuid
from datetime import timedelta from datetime import timedelta
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from fastapi import HTTPException from fastapi import HTTPException, Query
from fastapi_pagination import Params
from loguru import logger from loguru import logger
from sqlalchemy import delete from sqlalchemy import delete
@ -23,6 +24,9 @@ if TYPE_CHECKING:
API_WORDS = ["api", "key", "token"] API_WORDS = ["api", "key", "token"]
MAX_PAGE_SIZE = 50
MIN_PAGE_SIZE = 1
def has_api_terms(word: str): def has_api_terms(word: str):
return "api" in word and ("key" in word or ("token" in word and "tokens" not in word)) return "api" in word and ("key" in word or ("token" in word and "tokens" not in word))
@ -263,3 +267,12 @@ async def cascade_delete_flow(session: Session, flow: Flow):
except Exception as e: except Exception as e:
msg = f"Unable to cascade delete flow: ${flow.id}" msg = f"Unable to cascade delete flow: ${flow.id}"
raise RuntimeError(msg, e) from e raise RuntimeError(msg, e) from e
def custom_params(
page: int | None = Query(None),
size: int | None = Query(None),
):
if page is None and size is None:
return None
return Params(page=page or MIN_PAGE_SIZE, size=size or MAX_PAGE_SIZE)

View file

@ -132,7 +132,7 @@ def read_flows(
settings_service: SettingsService = Depends(get_settings_service), settings_service: SettingsService = Depends(get_settings_service),
remove_example_flows: bool = False, remove_example_flows: bool = False,
components_only: bool = False, components_only: bool = False,
get_all: bool = False, get_all: bool = True,
folder_id: UUID | None = None, folder_id: UUID | None = None,
params: Params = Depends(), params: Params = Depends(),
header_flows: bool = False, header_flows: bool = False,
@ -144,14 +144,18 @@ def read_flows(
session (Session): The database session. session (Session): The database session.
settings_service (SettingsService): The settings service. settings_service (SettingsService): The settings service.
components_only (bool, optional): Whether to return only components. Defaults to False. components_only (bool, optional): Whether to return only components. Defaults to False.
get_all (bool, optional): Whether to return all flows without pagination. Defaults to False.
get_all (bool, optional): Whether to return all flows without pagination. Defaults to True.
**This field must be True because of backward compatibility with the frontend - Release: 1.0.20**
folder_id (UUID, optional): The folder ID. Defaults to None. folder_id (UUID, optional): The folder ID. Defaults to None.
params (Params): Pagination parameters. params (Params): Pagination parameters.
remove_example_flows (bool, optional): Whether to remove example flows. Defaults to False. remove_example_flows (bool, optional): Whether to remove example flows. Defaults to False.
header_flows (bool, optional): Whether to return only specific headers of the flows. Defaults to False. header_flows (bool, optional): Whether to return only specific headers of the flows. Defaults to False.
Returns: Returns:
Union[list[FlowRead], Page[FlowRead]]: A list of flows or a paginated response containing the list of flows. list[FlowRead] | Page[FlowRead] | list[FlowHeader]
A list of flows or a paginated response containing the list of flows or a list of flow headers.
""" """
try: try:
auth_settings = settings_service.auth_settings auth_settings = settings_service.auth_settings
@ -162,6 +166,12 @@ def read_flows(
starter_folder = session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first() starter_folder = session.exec(select(Folder).where(Folder.name == STARTER_FOLDER_NAME)).first()
starter_folder_id = starter_folder.id if starter_folder else None starter_folder_id = starter_folder.id if starter_folder else None
if not starter_folder and not default_folder:
raise HTTPException(
status_code=404,
detail="Starter folder and default folder not found. Please create a folder and add flows to it.",
)
if not folder_id: if not folder_id:
folder_id = default_folder_id folder_id = default_folder_id
@ -178,9 +188,6 @@ def read_flows(
if components_only: if components_only:
stmt = stmt.where(Flow.is_component == True) # noqa: E712 stmt = stmt.where(Flow.is_component == True) # noqa: E712
if not get_all:
stmt = stmt.where(Flow.folder_id == folder_id)
if get_all: if get_all:
flows = session.exec(stmt).all() flows = session.exec(stmt).all()
flows = validate_is_component(flows) flows = validate_is_component(flows)
@ -194,6 +201,8 @@ def read_flows(
for flow in flows for flow in flows
] ]
return flows return flows
stmt = stmt.where(Flow.folder_id == folder_id)
return paginate(session, stmt, params=params) return paginate(session, stmt, params=params)
except Exception as e: except Exception as e:

View file

@ -5,7 +5,7 @@ from fastapi_pagination.ext.sqlmodel import paginate
from sqlalchemy import or_, update from sqlalchemy import or_, update
from sqlmodel import Session, select from sqlmodel import Session, select
from langflow.api.utils import cascade_delete_flow from langflow.api.utils import cascade_delete_flow, custom_params
from langflow.api.v1.flows import create_flows from langflow.api.v1.flows import create_flows
from langflow.api.v1.schemas import FlowListCreate, FlowListReadWithFolderName from langflow.api.v1.schemas import FlowListCreate, FlowListReadWithFolderName
from langflow.helpers.flow import generate_unique_flow_name from langflow.helpers.flow import generate_unique_flow_name
@ -18,6 +18,7 @@ from langflow.services.database.models.folder.model import (
Folder, Folder,
FolderCreate, FolderCreate,
FolderRead, FolderRead,
FolderReadWithFlows,
FolderUpdate, FolderUpdate,
) )
from langflow.services.database.models.folder.pagination_model import FolderWithPaginatedFlows from langflow.services.database.models.folder.pagination_model import FolderWithPaginatedFlows
@ -99,13 +100,13 @@ def read_folders(
raise HTTPException(status_code=500, detail=str(e)) from e raise HTTPException(status_code=500, detail=str(e)) from e
@router.get("/{folder_id}", response_model=FolderWithPaginatedFlows, status_code=200) @router.get("/{folder_id}", response_model=FolderWithPaginatedFlows | FolderReadWithFlows, status_code=200)
def read_folder( def read_folder(
*, *,
session: Session = Depends(get_session), session: Session = Depends(get_session),
folder_id: str, folder_id: str,
current_user: User = Depends(get_current_active_user), current_user: User = Depends(get_current_active_user),
params: Params = Depends(), params: Params | None = Depends(custom_params),
is_component: bool = False, is_component: bool = False,
is_flow: bool = False, is_flow: bool = False,
search: str = "", search: str = "",
@ -121,19 +122,25 @@ def read_folder(
raise HTTPException(status_code=404, detail="Folder not found") raise HTTPException(status_code=404, detail="Folder not found")
try: try:
stmt = select(Flow).where(Flow.folder_id == folder_id, Flow.user_id == current_user.id) if params and params.page and params.size:
stmt = select(Flow).where(Flow.folder_id == folder_id)
if Flow.updated_at is not None: if Flow.updated_at is not None:
stmt = stmt.order_by(Flow.updated_at.desc()) # type: ignore[attr-defined] stmt = stmt.order_by(Flow.updated_at.desc()) # type: ignore[attr-defined]
if is_component: if is_component:
stmt = stmt.where(Flow.is_component == True) # noqa: E712 stmt = stmt.where(Flow.is_component == True) # noqa: E712
if is_flow: if is_flow:
stmt = stmt.where(Flow.is_component == False) # noqa: E712 stmt = stmt.where(Flow.is_component == False) # noqa: E712
if search: if search:
stmt = stmt.where(Flow.name.like(f"%{search}%")) # type: ignore[attr-defined] stmt = stmt.where(Flow.name.like(f"%{search}%")) # type: ignore[attr-defined]
paginated_flows = paginate(session, stmt, params=params) paginated_flows = paginate(session, stmt, params=params)
return FolderWithPaginatedFlows(folder=FolderRead.model_validate(folder), flows=paginated_flows)
flows_from_current_user_in_folder = [flow for flow in folder.flows if flow.user_id == current_user.id]
folder.flows = flows_from_current_user_in_folder
return folder # noqa: TRY300
return FolderWithPaginatedFlows(folder=FolderRead.model_validate(folder), flows=paginated_flows)
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) from e raise HTTPException(status_code=500, detail=str(e)) from e

View file

@ -196,6 +196,24 @@ class FlowRead(FlowBase):
class FlowHeader(BaseModel): class FlowHeader(BaseModel):
"""Model representing a header for a flow - Without the data.
Attributes:
-----------
id : UUID
Unique identifier for the flow.
name : str
The name of the flow.
folder_id : UUID | None, optional
The ID of the folder containing the flow. None if not associated with a folder.
is_component : bool | None, optional
Flag indicating whether the flow is a component.
endpoint_name : str | None, optional
The name of the endpoint associated with this flow.
description : str | None, optional
A description of the flow.
"""
id: UUID id: UUID
name: str name: str
folder_id: UUID | None = None folder_id: UUID | None = None
@ -204,13 +222,6 @@ class FlowHeader(BaseModel):
description: str | None = None description: str | None = None
class PaginatedFlowResponse(BaseModel):
flows: list[FlowRead]
total: int
page_size: int
page_index: int
class FlowUpdate(SQLModel): class FlowUpdate(SQLModel):
name: str | None = None name: str | None = None
description: str | None = None description: str | None = None

View file

@ -67,20 +67,17 @@ async def test_read_flows(client: TestClient, json_flow: str, logged_in_headers)
assert len(response.json()) > 0 assert len(response.json()) > 0
@pytest.mark.usefixtures("active_user") async def test_read_flows_pagination_without_params(client: TestClient, logged_in_headers):
async def test_read_flows_pagination(client: TestClient, logged_in_headers):
response = await client.get("api/v1/flows/", headers=logged_in_headers) response = await client.get("api/v1/flows/", headers=logged_in_headers)
response_json = response.json()
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["page"] == 1 assert len(response_json) == 0
assert response.json()["size"] == 50
assert response.json()["pages"] == 0
assert response.json()["total"] == 0
assert len(response.json()["items"]) == 0
@pytest.mark.usefixtures("active_user")
async def test_read_flows_pagination_with_params(client: TestClient, logged_in_headers): async def test_read_flows_pagination_with_params(client: TestClient, logged_in_headers):
response = await client.get("api/v1/flows/", headers=logged_in_headers, params={"page": 3, "size": 10}) response = await client.get(
"api/v1/flows/", headers=logged_in_headers, params={"page": 3, "size": 10, "get_all": False}
)
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["page"] == 3 assert response.json()["page"] == 3
assert response.json()["size"] == 10 assert response.json()["size"] == 10
@ -89,15 +86,135 @@ async def test_read_flows_pagination_with_params(client: TestClient, logged_in_h
assert len(response.json()["items"]) == 0 assert len(response.json()["items"]) == 0
@pytest.mark.usefixtures("flow_component") async def test_read_flows_pagination_with_flows(client: TestClient, logged_in_headers):
async def test_read_flows_components_only(client: TestClient, logged_in_headers): number_of_flows = 30
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
flow_ids = []
for flow in flows:
response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
flow_ids.append(response.json()["id"])
response = await client.get( response = await client.get(
"api/v1/flows/", headers=logged_in_headers, params={"components_only": True, "get_all": True} "api/v1/flows/", headers=logged_in_headers, params={"page": 3, "size": 10, "get_all": False}
) )
assert response.status_code == 200 assert response.status_code == 200
names = [flow["name"] for flow in response.json()] assert response.json()["page"] == 3
assert any("Chat Input Component" in name for name in names) assert response.json()["size"] == 10
assert all(flow["is_component"] is True for flow in response.json()), [flow["name"] for flow in response.json()] assert response.json()["pages"] == 3
assert response.json()["total"] == number_of_flows
assert len(response.json()["items"]) == 10
response = await client.get(
"api/v1/flows/", headers=logged_in_headers, params={"page": 4, "size": 10, "get_all": False}
)
assert response.status_code == 200
assert response.json()["page"] == 4
assert response.json()["size"] == 10
assert response.json()["pages"] == 3
assert response.json()["total"] == number_of_flows
assert len(response.json()["items"]) == 0
async def test_read_flows_custom_page_size(client: TestClient, logged_in_headers):
number_of_flows = 30
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
for flow in flows:
response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
response = await client.get(
"api/v1/flows/", headers=logged_in_headers, params={"page": 1, "size": 15, "get_all": False}
)
assert response.status_code == 200
assert response.json()["page"] == 1
assert response.json()["size"] == 15
assert response.json()["pages"] == 2
assert response.json()["total"] == number_of_flows
assert len(response.json()["items"]) == 15
async def test_read_flows_invalid_page(client: TestClient, logged_in_headers):
number_of_flows = 30
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
flow_ids = []
for flow in flows:
response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
flow_ids.append(response.json()["id"])
response = await client.get(
"api/v1/flows/", headers=logged_in_headers, params={"page": 0, "size": 10, "get_all": False}
)
assert response.status_code == 422 # Assuming 422 is the status code for invalid input
async def test_read_flows_invalid_size(client: TestClient, logged_in_headers):
number_of_flows = 30
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
flow_ids = []
for flow in flows:
response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
flow_ids.append(response.json()["id"])
response = await client.get(
"api/v1/flows/", headers=logged_in_headers, params={"page": 1, "size": 0, "get_all": False}
)
assert response.status_code == 422 # Assuming 422 is the status code for invalid input
async def test_read_flows_no_pagination_params(client: TestClient, logged_in_headers):
number_of_flows = 30
flows = [FlowCreate(name=f"Flow {i}", description="description", data={}) for i in range(number_of_flows)]
for flow in flows:
response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
response = await client.get("api/v1/flows/", headers=logged_in_headers, params={"get_all": False})
assert response.status_code == 200
# Assert default pagination values, adjust these according to your API's default behavior
assert response.json()["page"] == 1
assert response.json()["size"] == 50
assert response.json()["pages"] == 1
assert response.json()["total"] == number_of_flows
assert len(response.json()["items"]) == number_of_flows
async def test_read_flows_components_only_paginated(client: TestClient, logged_in_headers):
number_of_flows = 10
flows = [
FlowCreate(name=f"Flow {i}", description="description", data={}, is_component=True)
for i in range(number_of_flows)
]
for flow in flows:
response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
response = await client.get(
"api/v1/flows/", headers=logged_in_headers, params={"components_only": True, "get_all": False}
)
assert response.status_code == 200
response_json = response.json()
assert response_json["total"] == 10
assert response_json["pages"] == 1
assert response_json["page"] == 1
assert response_json["size"] == 50
assert all(flow["is_component"] is True for flow in response_json["items"])
async def test_read_flows_components_only(client: TestClient, logged_in_headers):
number_of_flows = 10
flows = [
FlowCreate(name=f"Flow {i}", description="description", data={}, is_component=True)
for i in range(number_of_flows)
]
for flow in flows:
response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
response = await client.get("api/v1/flows/", headers=logged_in_headers, params={"components_only": True})
assert response.status_code == 200
response_json = response.json()
assert all(flow["is_component"] is True for flow in response_json)
async 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):
@ -305,7 +422,9 @@ async def test_get_flows_from_folder_pagination(client: TestClient, logged_in_he
created_folder = response.json() created_folder = response.json()
folder_id = created_folder["id"] folder_id = created_folder["id"]
response = await client.get(f"api/v1/folders/{folder_id}", headers=logged_in_headers) response = await client.get(
f"api/v1/folders/{folder_id}", headers=logged_in_headers, params={"page": 1, "size": 50}
)
assert response.status_code == 200 assert response.status_code == 200
assert response.json()["folder"]["name"] == folder_name assert response.json()["folder"]["name"] == folder_name
assert response.json()["folder"]["description"] == "Test folder description" assert response.json()["folder"]["description"] == "Test folder description"
@ -522,3 +641,156 @@ def test_sqlite_pragmas():
assert session.exec(text("PRAGMA journal_mode;")).scalar() == "wal" assert session.exec(text("PRAGMA journal_mode;")).scalar() == "wal"
assert session.exec(text("PRAGMA synchronous;")).scalar() == 1 assert session.exec(text("PRAGMA synchronous;")).scalar() == 1
@pytest.mark.usefixtures("active_user")
async def test_read_folder(client: TestClient, logged_in_headers):
# Create a new folder
folder_name = f"Test Folder {uuid4()}"
folder = FolderCreate(name=folder_name, description="Test folder description")
response = await client.post("api/v1/folders/", json=folder.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
created_folder = response.json()
folder_id = created_folder["id"]
# Read the folder
response = await client.get(f"api/v1/folders/{folder_id}", headers=logged_in_headers)
assert response.status_code == 200
folder_data = response.json()
assert folder_data["name"] == folder_name
assert folder_data["description"] == "Test folder description"
assert "flows" in folder_data
assert isinstance(folder_data["flows"], list)
@pytest.mark.usefixtures("active_user")
async def test_read_folder_with_pagination(client: TestClient, logged_in_headers):
# Create a new folder
folder_name = f"Test Folder {uuid4()}"
folder = FolderCreate(name=folder_name, description="Test folder description")
response = await client.post("api/v1/folders/", json=folder.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
created_folder = response.json()
folder_id = created_folder["id"]
# Read the folder with pagination
response = await client.get(
f"api/v1/folders/{folder_id}", headers=logged_in_headers, params={"page": 1, "size": 10}
)
assert response.status_code == 200
folder_data = response.json()
assert isinstance(folder_data, dict)
assert "folder" in folder_data
assert "flows" in folder_data
assert folder_data["folder"]["name"] == folder_name
assert folder_data["folder"]["description"] == "Test folder description"
assert folder_data["flows"]["page"] == 1
assert folder_data["flows"]["size"] == 10
assert isinstance(folder_data["flows"]["items"], list)
@pytest.mark.usefixtures("active_user")
async def test_read_folder_with_flows(client: TestClient, json_flow: str, logged_in_headers):
# Create a new folder
folder_name = f"Test Folder {uuid4()}"
flow_name = f"Test Flow {uuid4()}"
folder = FolderCreate(name=folder_name, description="Test folder description")
response = await client.post("api/v1/folders/", json=folder.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
created_folder = response.json()
folder_id = created_folder["id"]
# Create a flow in the folder
flow_data = orjson.loads(json_flow)
data = flow_data["data"]
flow = FlowCreate(name=flow_name, description="description", data=data)
flow.folder_id = folder_id
response = await client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
# Read the folder with flows
response = await client.get(f"api/v1/folders/{folder_id}", headers=logged_in_headers)
assert response.status_code == 200
folder_data = response.json()
assert folder_data["name"] == folder_name
assert folder_data["description"] == "Test folder description"
assert len(folder_data["flows"]) == 1
assert folder_data["flows"][0]["name"] == flow_name
@pytest.mark.usefixtures("active_user")
async def test_read_nonexistent_folder(client: TestClient, logged_in_headers):
nonexistent_id = str(uuid4())
response = await client.get(f"api/v1/folders/{nonexistent_id}", headers=logged_in_headers)
assert response.status_code == 404
assert response.json()["detail"] == "Folder not found"
@pytest.mark.usefixtures("active_user")
async def test_read_folder_with_search(client: TestClient, json_flow: str, logged_in_headers):
# Create a new folder
folder_name = f"Test Folder {uuid4()}"
folder = FolderCreate(name=folder_name, description="Test folder description")
response = await client.post("api/v1/folders/", json=folder.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
created_folder = response.json()
folder_id = created_folder["id"]
# Create two flows in the folder
flow_data = orjson.loads(json_flow)
flow_name_1 = f"Test Flow 1 {uuid4()}"
flow_name_2 = f"Another Flow {uuid4()}"
flow1 = FlowCreate(
name=flow_name_1, description="Test flow description", data=flow_data["data"], folder_id=folder_id
)
flow2 = FlowCreate(
name=flow_name_2, description="Another flow description", data=flow_data["data"], folder_id=folder_id
)
flow1.folder_id = folder_id
flow2.folder_id = folder_id
await client.post("api/v1/flows/", json=flow1.model_dump(), headers=logged_in_headers)
await client.post("api/v1/flows/", json=flow2.model_dump(), headers=logged_in_headers)
# Read the folder with search
response = await client.get(
f"api/v1/folders/{folder_id}", headers=logged_in_headers, params={"search": "Test", "page": 1, "size": 10}
)
assert response.status_code == 200
folder_data = response.json()
assert len(folder_data["flows"]["items"]) == 1
assert folder_data["flows"]["items"][0]["name"] == flow_name_1
@pytest.mark.usefixtures("active_user")
async def test_read_folder_with_component_filter(client: TestClient, json_flow: str, logged_in_headers):
# Create a new folder
folder_name = f"Test Folder {uuid4()}"
folder = FolderCreate(name=folder_name, description="Test folder description")
response = await client.post("api/v1/folders/", json=folder.model_dump(), headers=logged_in_headers)
assert response.status_code == 201
created_folder = response.json()
folder_id = created_folder["id"]
# Create a component flow in the folder
flow_data = orjson.loads(json_flow)
component_flow_name = f"Component Flow {uuid4()}"
component_flow = FlowCreate(
name=component_flow_name,
description="Component flow description",
data=flow_data["data"],
folder_id=folder_id,
is_component=True,
)
component_flow.folder_id = folder_id
await client.post("api/v1/flows/", json=component_flow.model_dump(), headers=logged_in_headers)
# Read the folder with component filter
response = await client.get(
f"api/v1/folders/{folder_id}", headers=logged_in_headers, params={"is_component": True, "page": 1, "size": 10}
)
assert response.status_code == 200
folder_data = response.json()
assert len(folder_data["flows"]["items"]) == 1
assert folder_data["flows"]["items"][0]["name"] == component_flow_name
assert folder_data["flows"]["items"][0]["is_component"] == True # noqa: E712