diff --git a/pyproject.toml b/pyproject.toml index 40e58dd90..51c7909f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -152,7 +152,7 @@ dev-dependencies = [ "types-redis>=4.6.0.5", "ipykernel>=6.29.0", "mypy>=1.11.0", - "ruff>=0.8.2,<0.9.0", + "ruff>=0.8.4,<0.9.0", "httpx>=0.27.0", "pytest>=8.2.0", "types-requests>=2.32.0", diff --git a/src/backend/base/langflow/components/agentql/agentql_api.py b/src/backend/base/langflow/components/agentql/agentql_api.py index c620baa17..f31128b93 100644 --- a/src/backend/base/langflow/components/agentql/agentql_api.py +++ b/src/backend/base/langflow/components/agentql/agentql_api.py @@ -90,7 +90,7 @@ class AgentQL(Component): except httpx.HTTPStatusError as e: response = e.response - if response.status_code in [401, 403]: + if response.status_code in {401, 403}: self.status = "Please, provide a valid API Key. You can create one at https://dev.agentql.com." else: try: diff --git a/src/backend/base/langflow/components/data/directory.py b/src/backend/base/langflow/components/data/directory.py index 0d2e532e9..8854cab6f 100644 --- a/src/backend/base/langflow/components/data/directory.py +++ b/src/backend/base/langflow/components/data/directory.py @@ -71,7 +71,7 @@ class DirectoryComponent(Component): def load_directory(self) -> list[Data]: path = self.path - types = self.types if self.types else TEXT_FILE_TYPES + types = self.types or TEXT_FILE_TYPES depth = self.depth max_concurrency = self.max_concurrency load_hidden = self.load_hidden diff --git a/src/backend/base/langflow/components/langwatch/langwatch.py b/src/backend/base/langflow/components/langwatch/langwatch.py index b49517696..f35b1265b 100644 --- a/src/backend/base/langflow/components/langwatch/langwatch.py +++ b/src/backend/base/langflow/components/langwatch/langwatch.py @@ -131,12 +131,12 @@ class LangWatchComponent(Component): # Clear component's dynamic attributes for attr in list(self.__dict__.keys()): - if attr not in default_keys and attr not in [ + if attr not in default_keys and attr not in { "evaluators", "dynamic_inputs", "_code", "current_evaluator", - ]: + }: delattr(self, attr) # Add new dynamic inputs @@ -177,7 +177,7 @@ class LangWatchComponent(Component): input_fields = [ field for field in evaluator.get("requiredFields", []) + evaluator.get("optionalFields", []) - if field not in ["input", "output"] + if field not in {"input", "output"} ] for field in input_fields: diff --git a/src/backend/base/langflow/components/logic/conditional_router.py b/src/backend/base/langflow/components/logic/conditional_router.py index 80c2739a6..634ac2b20 100644 --- a/src/backend/base/langflow/components/logic/conditional_router.py +++ b/src/backend/base/langflow/components/logic/conditional_router.py @@ -126,8 +126,7 @@ class ConditionalRouterComponent(Component): def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None) -> dict: if field_name == "operator": if field_value == "matches regex": - if "case_sensitive" in build_config: - del build_config["case_sensitive"] + build_config.pop("case_sensitive", None) # Ensure case_sensitive is present for all other operators elif "case_sensitive" not in build_config: case_sensitive_input = next( diff --git a/src/backend/base/langflow/components/models/openrouter.py b/src/backend/base/langflow/components/models/openrouter.py index 45d945b92..6c3d84dfb 100644 --- a/src/backend/base/langflow/components/models/openrouter.py +++ b/src/backend/base/langflow/components/models/openrouter.py @@ -22,7 +22,7 @@ class OpenRouterComponent(LCModelComponent): display_name = "OpenRouter" description = ( - "OpenRouter provides unified access to multiple AI models " "from different providers through a single API." + "OpenRouter provides unified access to multiple AI models from different providers through a single API." ) icon = "OpenRouter" @@ -180,9 +180,7 @@ class OpenRouterComponent(LCModelComponent): build_config["model_name"]["value"] = models[0]["id"] tooltips = { - model["id"]: ( - f"{model['name']}\n" f"Context Length: {model['context_length']}\n" f"{model['description']}" - ) + model["id"]: (f"{model['name']}\nContext Length: {model['context_length']}\n{model['description']}") for model in models } build_config["model_name"]["tooltips"] = tooltips diff --git a/src/backend/base/langflow/components/needle/needle.py b/src/backend/base/langflow/components/needle/needle.py index 6450bf897..772e7a48e 100644 --- a/src/backend/base/langflow/components/needle/needle.py +++ b/src/backend/base/langflow/components/needle/needle.py @@ -10,7 +10,7 @@ from langflow.utils.constants import MESSAGE_SENDER_AI class NeedleComponent(Component): display_name = "Needle Retriever" - description = "A retriever that uses the Needle API to search collections " "and generates responses using OpenAI." + description = "A retriever that uses the Needle API to search collections and generates responses using OpenAI." documentation = "https://docs.needle-ai.com" icon = "search" name = "needle" @@ -105,8 +105,8 @@ class NeedleComponent(Component): if str(output_type).lower().strip() == "chunks": # If chunks selected, include full context and answer docs = result["source_documents"] - context = "\n\n".join([f"Document {i+1}:\n{doc.page_content}" for i, doc in enumerate(docs)]) - text_content = f"Question: {query}\n\n" f"Context:\n{context}\n\n" f"Answer: {result['answer']}" + context = "\n\n".join([f"Document {i + 1}:\n{doc.page_content}" for i, doc in enumerate(docs)]) + text_content = f"Question: {query}\n\nContext:\n{context}\n\nAnswer: {result['answer']}" else: # If answer selected, only include the answer text_content = result["answer"] diff --git a/src/backend/base/langflow/components/processing/dataframe_operations.py b/src/backend/base/langflow/components/processing/dataframe_operations.py index be7a13033..f217d3770 100644 --- a/src/backend/base/langflow/components/processing/dataframe_operations.py +++ b/src/backend/base/langflow/components/processing/dataframe_operations.py @@ -144,7 +144,7 @@ class DataFrameOperationsComponent(Component): build_config["new_column_value"]["show"] = True elif field_value == "Select Columns": build_config["columns_to_select"]["show"] = True - elif field_value in ["Head", "Tail"]: + elif field_value in {"Head", "Tail"}: build_config["num_rows"]["show"] = True elif field_value == "Replace Value": build_config["column_name"]["show"] = True diff --git a/src/backend/base/langflow/components/processing/merge_data.py b/src/backend/base/langflow/components/processing/merge_data.py index 015c6991f..bfefe19e2 100644 --- a/src/backend/base/langflow/components/processing/merge_data.py +++ b/src/backend/base/langflow/components/processing/merge_data.py @@ -74,7 +74,7 @@ class MergeDataComponent(Component): for key, value in data_input.data.items(): if key in result_data and isinstance(value, str): if isinstance(result_data[key], list): - cast(list[str], result_data[key]).append(value) + cast("list[str]", result_data[key]).append(value) else: result_data[key] = [result_data[key], value] else: diff --git a/src/backend/base/langflow/components/vectorstores/astradb.py b/src/backend/base/langflow/components/vectorstores/astradb.py index fb68e3b4c..e65beeae1 100644 --- a/src/backend/base/langflow/components/vectorstores/astradb.py +++ b/src/backend/base/langflow/components/vectorstores/astradb.py @@ -295,7 +295,7 @@ class AstraDBVectorStoreComponent(LCVectorStoreComponent): def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None): # Always attempt to update the database list - if field_name in ["token", "api_endpoint", "collection_name"]: + if field_name in {"token", "api_endpoint", "collection_name"}: # Update the database selector build_config["api_endpoint"]["options"] = self._initialize_database_options() diff --git a/src/backend/base/langflow/graph/graph/base.py b/src/backend/base/langflow/graph/graph/base.py index ced2c2af4..6e01a5d69 100644 --- a/src/backend/base/langflow/graph/graph/base.py +++ b/src/backend/base/langflow/graph/graph/base.py @@ -1101,7 +1101,8 @@ class Graph: return False return self.vertex_edges_are_identical(vertex, other_vertex) - def vertex_edges_are_identical(self, vertex: Vertex, other_vertex: Vertex) -> bool: + @staticmethod + def vertex_edges_are_identical(vertex: Vertex, other_vertex: Vertex) -> bool: same_length = len(vertex.edges) == len(other_vertex.edges) if not same_length: return False @@ -1747,7 +1748,8 @@ class Graph: new_edge = Edge(source, target, edge) return new_edge - def _get_vertex_class(self, node_type: str, node_base_type: str, node_id: str) -> type[Vertex]: + @staticmethod + def _get_vertex_class(node_type: str, node_base_type: str, node_id: str) -> type[Vertex]: """Returns the node class based on the node type.""" # First we check for the node_base_type node_name = node_id.split("-")[0] @@ -1830,7 +1832,8 @@ class Graph: self._record_snapshot() return self - def get_children_by_vertex_type(self, vertex: Vertex, vertex_type: str) -> list[Vertex]: + @staticmethod + def get_children_by_vertex_type(vertex: Vertex, vertex_type: str) -> list[Vertex]: """Returns the children of a vertex based on the vertex type.""" children = [] vertex_types = [vertex.data["type"]] @@ -2059,7 +2062,8 @@ class Graph: self._first_layer = first_layer return first_layer - def sort_interface_components_first(self, vertices_layers: list[list[str]]) -> list[list[str]]: + @staticmethod + def sort_interface_components_first(vertices_layers: list[list[str]]) -> list[list[str]]: """Sorts the vertices in the graph so that vertices containing ChatInput or ChatOutput come first.""" def contains_interface_component(vertex): @@ -2097,9 +2101,6 @@ class Graph: This method is responsible for building the run map for the graph, which maps each node in the graph to its corresponding run function. - - Returns: - None """ self.run_manager.build_run_map(predecessor_map=self.predecessor_map, vertices_to_run=self.vertices_to_run) @@ -2169,7 +2170,8 @@ class Graph: in_degree[vertex.id] = 0 return in_degree - def build_adjacency_maps(self, edges: list[CycleEdge]) -> tuple[dict[str, list[str]], dict[str, list[str]]]: + @staticmethod + def build_adjacency_maps(edges: list[CycleEdge]) -> tuple[dict[str, list[str]], dict[str, list[str]]]: """Returns the adjacency maps for the graph.""" predecessor_map: dict[str, list[str]] = defaultdict(list) successor_map: dict[str, list[str]] = defaultdict(list) diff --git a/src/backend/base/langflow/graph/graph/utils.py b/src/backend/base/langflow/graph/graph/utils.py index 12f0cd502..833ba7897 100644 --- a/src/backend/base/langflow/graph/graph/utils.py +++ b/src/backend/base/langflow/graph/graph/utils.py @@ -110,9 +110,6 @@ def update_template(template, g_nodes) -> None: Args: template (dict): The new template to update the node with. g_nodes (list): The list of nodes in the graph. - - Returns: - None """ for value in template.values(): if not value.get("proxy"): @@ -161,9 +158,6 @@ def set_new_target_handle(proxy_id, new_edge, target_handle, node) -> None: new_edge (dict): The new edge to be created. target_handle (dict): The target handle of the edge. node (dict): The node containing the edge. - - Returns: - None """ new_edge["target"] = proxy_id type_ = target_handle.get("type") diff --git a/src/backend/base/langflow/graph/vertex/base.py b/src/backend/base/langflow/graph/vertex/base.py index 4c012e7aa..bda54255b 100644 --- a/src/backend/base/langflow/graph/vertex/base.py +++ b/src/backend/base/langflow/graph/vertex/base.py @@ -423,7 +423,7 @@ class Vertex: else: msg = f"Invalid value type {type(val)} for field {field_name}" raise ValueError(msg) - elif val is not None and val != "": + elif val: params[field_name] = val if field.get("load_from_db"): @@ -596,7 +596,8 @@ class Vertex: result = await value.get_result(self, target_handle_name=key) self.params[key][sub_key] = result - def _is_vertex(self, value): + @staticmethod + def _is_vertex(value): """Checks if the provided value is an instance of Vertex.""" return isinstance(value, Vertex) diff --git a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json index aa2e69396..c13996410 100644 --- a/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json +++ b/src/backend/base/langflow/initial_setup/starter_projects/Vector Store RAG.json @@ -3137,7 +3137,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import os\nfrom collections import defaultdict\n\nfrom astrapy import AstraDBAdmin, DataAPIClient\nfrom astrapy.admin import parse_api_endpoint\nfrom langchain_astradb import AstraDBVectorStore\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import DictInput, FloatInput, MessageTextInput, NestedDictInput\nfrom langflow.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\nfrom langflow.utils.version import get_version_info\n\n\nclass AstraDBVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB\"\n description: str = \"Ingest and search documents in Astra DB\"\n documentation: str = \"https://docs.datastax.com/en/langflow/astra-components.html\"\n name = \"AstraDB\"\n icon: str = \"AstraDB\"\n\n _cached_vector_store: AstraDBVectorStore | None = None\n\n base_inputs = LCVectorStoreComponent.inputs\n if \"search_query\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n tool_mode=True,\n )\n )\n if \"ingest_data\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n DataInput(\n name=\"ingest_data\",\n display_name=\"Ingest Data\",\n )\n )\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"api_endpoint\",\n display_name=\"Database\",\n info=\"The Astra DB Database to use.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"Default database\"],\n value=\"Default database\",\n ),\n DropdownInput(\n name=\"collection_name\",\n display_name=\"Collection\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"+ Create new collection\"],\n value=\"+ Create new collection\",\n ),\n StrInput(\n name=\"collection_name_new\",\n display_name=\"Collection Name\",\n info=\"Name of the new collection to create.\",\n advanced=os.getenv(\"LANGFLOW_HOST\") is not None,\n required=os.getenv(\"LANGFLOW_HOST\") is None,\n ),\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"embedding_choice\",\n display_name=\"Embedding Model or Astra Vectorize\",\n info=\"Determines whether to use Astra Vectorize for the collection.\",\n options=[\"Embedding Model\", \"Astra Vectorize\"],\n real_time_refresh=True,\n value=\"Embedding Model\",\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n *base_inputs,\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Search Results\",\n info=\"Number of search results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n NestedDictInput(\n name=\"advanced_search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n ),\n StrInput(\n name=\"content_field\",\n display_name=\"Content Field\",\n info=\"Field to use as the text content field for the vector store.\",\n advanced=True,\n ),\n BoolInput(\n name=\"ignore_invalid_documents\",\n display_name=\"Ignore Invalid Documents\",\n info=\"Boolean flag to determine whether to ignore invalid documents at runtime.\",\n advanced=True,\n ),\n NestedDictInput(\n name=\"astradb_vectorstore_kwargs\",\n display_name=\"AstraDBVectorStore Parameters\",\n info=\"Optional dictionary of additional parameters for the AstraDBVectorStore.\",\n advanced=True,\n ),\n ]\n\n def del_fields(self, build_config, field_list):\n for field in field_list:\n if field in build_config:\n del build_config[field]\n\n return build_config\n\n def insert_in_dict(self, build_config, field_name, new_parameters):\n # Insert the new key-value pair after the found key\n for new_field_name, new_parameter in new_parameters.items():\n # Get all the items as a list of tuples (key, value)\n items = list(build_config.items())\n\n # Find the index of the key to insert after\n idx = len(items)\n for i, (key, _) in enumerate(items):\n if key == field_name:\n idx = i + 1\n break\n\n items.insert(idx, (new_field_name, new_parameter))\n\n # Clear the original dictionary and update with the modified items\n build_config.clear()\n build_config.update(items)\n\n return build_config\n\n def get_vectorize_providers(self):\n try:\n self.log(\"Dynamically updating list of Vectorize providers.\")\n\n # Get the admin object\n admin = AstraDBAdmin(token=self.token)\n db_admin = admin.get_database_admin(self.get_api_endpoint())\n\n # Get the list of embedding providers\n embedding_providers = db_admin.find_embedding_providers().as_dict()\n\n vectorize_providers_mapping = {}\n # Map the provider display name to the provider key and models\n for provider_key, provider_data in embedding_providers[\"embeddingProviders\"].items():\n display_name = provider_data[\"displayName\"]\n models = [model[\"name\"] for model in provider_data[\"models\"]]\n\n vectorize_providers_mapping[display_name] = [provider_key, models]\n\n # Sort the resulting dictionary\n return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching Vectorize providers: {e}\")\n\n return {}\n\n def get_database_list(self):\n # Get the admin object\n db_admin = AstraDBAdmin(token=self.token)\n db_list = list(db_admin.list_databases())\n\n # Generate the api endpoint for each database\n return {db.info.name: f\"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com\" for db in db_list}\n\n def get_api_endpoint(self):\n # Get the database name (or endpoint)\n database = self.api_endpoint\n\n # If the database is not set, get the first database in the list\n if not database or database == \"Default database\":\n database, _ = next(iter(self.get_database_list().items()))\n\n # If the database is a URL, return it\n if database.startswith(\"https://\"):\n return database\n\n # Otherwise, get the URL from the database list\n return self.get_database_list().get(database)\n\n def get_database(self):\n try:\n client = DataAPIClient(token=self.token)\n\n return client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n )\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting database: {e}\")\n\n return None\n\n def _initialize_database_options(self):\n if not self.token:\n return [\"Default database\"]\n try:\n databases = [\"Default database\", *list(self.get_database_list().keys())]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching databases: {e}\")\n\n return [\"Default database\"]\n\n return databases\n\n def _initialize_collection_options(self):\n database = self.get_database()\n if database is None:\n return [\"+ Create new collection\"]\n\n try:\n collections = [collection.name for collection in database.list_collections(keyspace=self.keyspace or None)]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching collections: {e}\")\n\n return [\"+ Create new collection\"]\n\n return [*collections, \"+ Create new collection\"]\n\n def get_collection_choice(self):\n collection_name = self.collection_name\n if collection_name == \"+ Create new collection\":\n return self.collection_name_new\n\n return collection_name\n\n def get_collection_options(self):\n # Only get the options if the collection exists\n database = self.get_database()\n if database is None:\n return None\n\n collection_name = self.get_collection_choice()\n\n try:\n collection = database.get_collection(collection_name, keyspace=self.keyspace or None)\n collection_options = collection.options()\n except Exception as _: # noqa: BLE001\n return None\n\n return collection_options.vector\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n # Always attempt to update the database list\n if field_name in [\"token\", \"api_endpoint\", \"collection_name\"]:\n # Update the database selector\n build_config[\"api_endpoint\"][\"options\"] = self._initialize_database_options()\n\n # Set the default API endpoint if not set\n if build_config[\"api_endpoint\"][\"value\"] == \"Default database\":\n build_config[\"api_endpoint\"][\"value\"] = build_config[\"api_endpoint\"][\"options\"][0]\n\n # Update the collection selector\n build_config[\"collection_name\"][\"options\"] = self._initialize_collection_options()\n\n # Update the choice of embedding model based on collection name\n if field_name == \"collection_name\":\n # Detect if it is a new collection\n is_new_collection = field_value == \"+ Create new collection\"\n\n # Set the advanced and required fields based on the collection choice\n build_config[\"embedding_choice\"].update(\n {\n \"advanced\": not is_new_collection,\n \"value\": \"Embedding Model\" if is_new_collection else build_config[\"embedding_choice\"].get(\"value\"),\n }\n )\n\n # Set the advanced field for the embedding model\n build_config[\"embedding_model\"][\"advanced\"] = not is_new_collection\n\n # Set the advanced and required fields for the new collection name\n build_config[\"collection_name_new\"].update(\n {\n \"advanced\": not is_new_collection,\n \"required\": is_new_collection,\n \"value\": \"\" if not is_new_collection else build_config[\"collection_name_new\"].get(\"value\"),\n }\n )\n\n # Get the collection options for the selected collection\n collection_options = self.get_collection_options()\n\n # If the collection options are available (DB exists), show the advanced options\n if collection_options:\n build_config[\"embedding_choice\"][\"advanced\"] = True\n\n if collection_options.service:\n # Remove unnecessary fields when a service is set\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": True},\n \"embedding_choice\": {\"value\": \"Astra Vectorize\"},\n }\n else:\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": False},\n \"embedding_provider\": {\"advanced\": False},\n \"embedding_choice\": {\"value\": \"Embedding Model\"},\n }\n\n # Apply updates to the build_config\n for key, value in updates.items():\n build_config[key].update(value)\n\n elif field_name == \"embedding_choice\":\n if field_value == \"Astra Vectorize\":\n build_config[\"embedding_model\"][\"advanced\"] = True\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n\n new_parameter = DropdownInput(\n name=\"embedding_provider\",\n display_name=\"Embedding Provider\",\n options=vectorize_providers.keys(),\n value=\"\",\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_choice\", {\"embedding_provider\": new_parameter})\n else:\n build_config[\"embedding_model\"][\"advanced\"] = False\n\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n elif field_name == \"embedding_provider\":\n self.del_fields(\n build_config,\n [\"model\", \"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n model_options = vectorize_providers[field_value][1]\n\n new_parameter = DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n info=\"The embedding model to use for the selected provider. Each provider has a different set of \"\n \"models available (full list at \"\n \"https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\\n\\n\"\n f\"{', '.join(model_options)}\",\n options=model_options,\n value=None,\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_provider\", {\"model\": new_parameter})\n\n elif field_name == \"model\":\n self.del_fields(\n build_config,\n [\"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n new_parameter_1 = DictInput(\n name=\"z_01_model_parameters\",\n display_name=\"Model Parameters\",\n list=True,\n ).to_dict()\n\n new_parameter_2 = MessageTextInput(\n name=\"z_02_api_key_name\",\n display_name=\"API Key Name\",\n info=\"The name of the embeddings provider API key stored on Astra. \"\n \"If set, it will override the 'ProviderKey' in the authentication parameters.\",\n ).to_dict()\n\n new_parameter_3 = SecretStrInput(\n load_from_db=False,\n name=\"z_03_provider_api_key\",\n display_name=\"Provider API Key\",\n info=\"An alternative to the Astra Authentication that passes an API key for the provider \"\n \"with each request to Astra DB. \"\n \"This may be used when Vectorize is configured for the collection, \"\n \"but no corresponding provider secret is stored within Astra's key management system.\",\n ).to_dict()\n\n new_parameter_4 = DictInput(\n name=\"z_04_authentication\",\n display_name=\"Authentication Parameters\",\n list=True,\n ).to_dict()\n\n self.insert_in_dict(\n build_config,\n \"model\",\n {\n \"z_01_model_parameters\": new_parameter_1,\n \"z_02_api_key_name\": new_parameter_2,\n \"z_03_provider_api_key\": new_parameter_3,\n \"z_04_authentication\": new_parameter_4,\n },\n )\n\n return build_config\n\n def build_vectorize_options(self, **kwargs):\n for attribute in [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ]:\n if not hasattr(self, attribute):\n setattr(self, attribute, None)\n\n # Fetch values from kwargs if any self.* attributes are None\n provider_mapping = self.get_vectorize_providers()\n provider_value = provider_mapping.get(self.embedding_provider, [None])[0] or kwargs.get(\"embedding_provider\")\n model_name = self.model or kwargs.get(\"model\")\n authentication = {**(self.z_04_authentication or {}), **kwargs.get(\"z_04_authentication\", {})}\n parameters = self.z_01_model_parameters or kwargs.get(\"z_01_model_parameters\", {})\n\n # Set the API key name if provided\n api_key_name = self.z_02_api_key_name or kwargs.get(\"z_02_api_key_name\")\n provider_key = self.z_03_provider_api_key or kwargs.get(\"z_03_provider_api_key\")\n if api_key_name:\n authentication[\"providerKey\"] = api_key_name\n if authentication:\n provider_key = None\n authentication[\"providerKey\"] = authentication[\"providerKey\"].split(\".\")[0]\n\n # Set authentication and parameters to None if no values are provided\n if not authentication:\n authentication = None\n if not parameters:\n parameters = None\n\n return {\n # must match astrapy.info.CollectionVectorServiceOptions\n \"collection_vector_service_options\": {\n \"provider\": provider_value,\n \"modelName\": model_name,\n \"authentication\": authentication,\n \"parameters\": parameters,\n },\n \"collection_embedding_api_key\": provider_key,\n }\n\n @check_cached_vector_store\n def build_vector_store(self, vectorize_options=None):\n try:\n from langchain_astradb import AstraDBVectorStore\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n # Initialize parameters based on the collection name\n is_new_collection = self.get_collection_options() is None\n\n # Get the embedding model\n embedding_params = {\"embedding\": self.embedding_model} if self.embedding_choice == \"Embedding Model\" else {}\n\n # Use the embedding model if the choice is set to \"Embedding Model\"\n if self.embedding_choice == \"Astra Vectorize\" and is_new_collection:\n from astrapy.info import CollectionVectorServiceOptions\n\n # Build the vectorize options dictionary\n dict_options = vectorize_options or self.build_vectorize_options(\n embedding_provider=getattr(self, \"embedding_provider\", None) or None,\n model=getattr(self, \"model\", None) or None,\n z_01_model_parameters=getattr(self, \"z_01_model_parameters\", None) or None,\n z_02_api_key_name=getattr(self, \"z_02_api_key_name\", None) or None,\n z_03_provider_api_key=getattr(self, \"z_03_provider_api_key\", None) or None,\n z_04_authentication=getattr(self, \"z_04_authentication\", {}) or {},\n )\n\n # Set the embedding dictionary\n embedding_params = {\n \"collection_vector_service_options\": CollectionVectorServiceOptions.from_dict(\n dict_options.get(\"collection_vector_service_options\")\n ),\n \"collection_embedding_api_key\": dict_options.get(\"collection_embedding_api_key\"),\n }\n\n # Get the running environment for Langflow\n environment = parse_api_endpoint(self.get_api_endpoint()).environment if self.get_api_endpoint() else None\n\n # Get Langflow version and platform information\n __version__ = get_version_info()[\"version\"]\n langflow_prefix = \"\"\n if os.getenv(\"LANGFLOW_HOST\") is not None:\n langflow_prefix = \"ds-\"\n\n # Bundle up the auto-detect parameters\n autodetect_params = {\n \"autodetect_collection\": not is_new_collection, # TODO: May want to expose this option\n \"content_field\": self.content_field or None,\n \"ignore_invalid_documents\": self.ignore_invalid_documents,\n }\n\n # Attempt to build the Vector Store object\n try:\n vector_store = AstraDBVectorStore(\n # Astra DB Authentication Parameters\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.keyspace or None,\n collection_name=self.get_collection_choice(),\n environment=environment,\n # Astra DB Usage Tracking Parameters\n ext_callers=[(f\"{langflow_prefix}langflow\", __version__)],\n # Astra DB Vector Store Parameters\n **autodetect_params or {},\n **embedding_params or {},\n **self.astradb_vectorstore_kwargs or {},\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n if self.search_type == \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n if self.search_type == \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n return \"similarity\"\n\n def _build_search_args(self):\n query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None\n\n if query:\n args = {\n \"query\": query,\n \"search_type\": self._map_search_type(),\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n elif self.advanced_search_filter:\n args = {\n \"n\": self.number_of_results,\n }\n else:\n return {}\n\n filter_arg = self.advanced_search_filter or {}\n if filter_arg:\n args[\"filter\"] = filter_arg\n\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n vector_store = vector_store or self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n try:\n search_args = self._build_search_args()\n except Exception as e:\n msg = f\"Error in AstraDBVectorStore._build_search_args: {e}\"\n raise ValueError(msg) from e\n\n if not search_args:\n self.log(\"No search input or filters provided. Skipping search.\")\n return []\n\n docs = []\n search_method = \"search\" if \"query\" in search_args else \"metadata_search\"\n\n try:\n self.log(f\"Calling vector_store.{search_method} with args: {search_args}\")\n docs = getattr(vector_store, search_method)(**search_args)\n except Exception as e:\n msg = f\"Error performing {search_method} in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n return data\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n" + "value": "import os\nfrom collections import defaultdict\n\nfrom astrapy import AstraDBAdmin, DataAPIClient\nfrom astrapy.admin import parse_api_endpoint\nfrom langchain_astradb import AstraDBVectorStore\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import DictInput, FloatInput, MessageTextInput, NestedDictInput\nfrom langflow.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\nfrom langflow.utils.version import get_version_info\n\n\nclass AstraDBVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB\"\n description: str = \"Ingest and search documents in Astra DB\"\n documentation: str = \"https://docs.datastax.com/en/langflow/astra-components.html\"\n name = \"AstraDB\"\n icon: str = \"AstraDB\"\n\n _cached_vector_store: AstraDBVectorStore | None = None\n\n base_inputs = LCVectorStoreComponent.inputs\n if \"search_query\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n tool_mode=True,\n )\n )\n if \"ingest_data\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n DataInput(\n name=\"ingest_data\",\n display_name=\"Ingest Data\",\n )\n )\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"api_endpoint\",\n display_name=\"Database\",\n info=\"The Astra DB Database to use.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"Default database\"],\n value=\"Default database\",\n ),\n DropdownInput(\n name=\"collection_name\",\n display_name=\"Collection\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"+ Create new collection\"],\n value=\"+ Create new collection\",\n ),\n StrInput(\n name=\"collection_name_new\",\n display_name=\"Collection Name\",\n info=\"Name of the new collection to create.\",\n advanced=os.getenv(\"LANGFLOW_HOST\") is not None,\n required=os.getenv(\"LANGFLOW_HOST\") is None,\n ),\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"embedding_choice\",\n display_name=\"Embedding Model or Astra Vectorize\",\n info=\"Determines whether to use Astra Vectorize for the collection.\",\n options=[\"Embedding Model\", \"Astra Vectorize\"],\n real_time_refresh=True,\n value=\"Embedding Model\",\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n *base_inputs,\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Search Results\",\n info=\"Number of search results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n NestedDictInput(\n name=\"advanced_search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n ),\n StrInput(\n name=\"content_field\",\n display_name=\"Content Field\",\n info=\"Field to use as the text content field for the vector store.\",\n advanced=True,\n ),\n BoolInput(\n name=\"ignore_invalid_documents\",\n display_name=\"Ignore Invalid Documents\",\n info=\"Boolean flag to determine whether to ignore invalid documents at runtime.\",\n advanced=True,\n ),\n NestedDictInput(\n name=\"astradb_vectorstore_kwargs\",\n display_name=\"AstraDBVectorStore Parameters\",\n info=\"Optional dictionary of additional parameters for the AstraDBVectorStore.\",\n advanced=True,\n ),\n ]\n\n def del_fields(self, build_config, field_list):\n for field in field_list:\n if field in build_config:\n del build_config[field]\n\n return build_config\n\n def insert_in_dict(self, build_config, field_name, new_parameters):\n # Insert the new key-value pair after the found key\n for new_field_name, new_parameter in new_parameters.items():\n # Get all the items as a list of tuples (key, value)\n items = list(build_config.items())\n\n # Find the index of the key to insert after\n idx = len(items)\n for i, (key, _) in enumerate(items):\n if key == field_name:\n idx = i + 1\n break\n\n items.insert(idx, (new_field_name, new_parameter))\n\n # Clear the original dictionary and update with the modified items\n build_config.clear()\n build_config.update(items)\n\n return build_config\n\n def get_vectorize_providers(self):\n try:\n self.log(\"Dynamically updating list of Vectorize providers.\")\n\n # Get the admin object\n admin = AstraDBAdmin(token=self.token)\n db_admin = admin.get_database_admin(self.get_api_endpoint())\n\n # Get the list of embedding providers\n embedding_providers = db_admin.find_embedding_providers().as_dict()\n\n vectorize_providers_mapping = {}\n # Map the provider display name to the provider key and models\n for provider_key, provider_data in embedding_providers[\"embeddingProviders\"].items():\n display_name = provider_data[\"displayName\"]\n models = [model[\"name\"] for model in provider_data[\"models\"]]\n\n vectorize_providers_mapping[display_name] = [provider_key, models]\n\n # Sort the resulting dictionary\n return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching Vectorize providers: {e}\")\n\n return {}\n\n def get_database_list(self):\n # Get the admin object\n db_admin = AstraDBAdmin(token=self.token)\n db_list = list(db_admin.list_databases())\n\n # Generate the api endpoint for each database\n return {db.info.name: f\"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com\" for db in db_list}\n\n def get_api_endpoint(self):\n # Get the database name (or endpoint)\n database = self.api_endpoint\n\n # If the database is not set, get the first database in the list\n if not database or database == \"Default database\":\n database, _ = next(iter(self.get_database_list().items()))\n\n # If the database is a URL, return it\n if database.startswith(\"https://\"):\n return database\n\n # Otherwise, get the URL from the database list\n return self.get_database_list().get(database)\n\n def get_database(self):\n try:\n client = DataAPIClient(token=self.token)\n\n return client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n )\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting database: {e}\")\n\n return None\n\n def _initialize_database_options(self):\n if not self.token:\n return [\"Default database\"]\n try:\n databases = [\"Default database\", *list(self.get_database_list().keys())]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching databases: {e}\")\n\n return [\"Default database\"]\n\n return databases\n\n def _initialize_collection_options(self):\n database = self.get_database()\n if database is None:\n return [\"+ Create new collection\"]\n\n try:\n collections = [collection.name for collection in database.list_collections(keyspace=self.keyspace or None)]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching collections: {e}\")\n\n return [\"+ Create new collection\"]\n\n return [*collections, \"+ Create new collection\"]\n\n def get_collection_choice(self):\n collection_name = self.collection_name\n if collection_name == \"+ Create new collection\":\n return self.collection_name_new\n\n return collection_name\n\n def get_collection_options(self):\n # Only get the options if the collection exists\n database = self.get_database()\n if database is None:\n return None\n\n collection_name = self.get_collection_choice()\n\n try:\n collection = database.get_collection(collection_name, keyspace=self.keyspace or None)\n collection_options = collection.options()\n except Exception as _: # noqa: BLE001\n return None\n\n return collection_options.vector\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n # Always attempt to update the database list\n if field_name in {\"token\", \"api_endpoint\", \"collection_name\"}:\n # Update the database selector\n build_config[\"api_endpoint\"][\"options\"] = self._initialize_database_options()\n\n # Set the default API endpoint if not set\n if build_config[\"api_endpoint\"][\"value\"] == \"Default database\":\n build_config[\"api_endpoint\"][\"value\"] = build_config[\"api_endpoint\"][\"options\"][0]\n\n # Update the collection selector\n build_config[\"collection_name\"][\"options\"] = self._initialize_collection_options()\n\n # Update the choice of embedding model based on collection name\n if field_name == \"collection_name\":\n # Detect if it is a new collection\n is_new_collection = field_value == \"+ Create new collection\"\n\n # Set the advanced and required fields based on the collection choice\n build_config[\"embedding_choice\"].update(\n {\n \"advanced\": not is_new_collection,\n \"value\": \"Embedding Model\" if is_new_collection else build_config[\"embedding_choice\"].get(\"value\"),\n }\n )\n\n # Set the advanced field for the embedding model\n build_config[\"embedding_model\"][\"advanced\"] = not is_new_collection\n\n # Set the advanced and required fields for the new collection name\n build_config[\"collection_name_new\"].update(\n {\n \"advanced\": not is_new_collection,\n \"required\": is_new_collection,\n \"value\": \"\" if not is_new_collection else build_config[\"collection_name_new\"].get(\"value\"),\n }\n )\n\n # Get the collection options for the selected collection\n collection_options = self.get_collection_options()\n\n # If the collection options are available (DB exists), show the advanced options\n if collection_options:\n build_config[\"embedding_choice\"][\"advanced\"] = True\n\n if collection_options.service:\n # Remove unnecessary fields when a service is set\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": True},\n \"embedding_choice\": {\"value\": \"Astra Vectorize\"},\n }\n else:\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": False},\n \"embedding_provider\": {\"advanced\": False},\n \"embedding_choice\": {\"value\": \"Embedding Model\"},\n }\n\n # Apply updates to the build_config\n for key, value in updates.items():\n build_config[key].update(value)\n\n elif field_name == \"embedding_choice\":\n if field_value == \"Astra Vectorize\":\n build_config[\"embedding_model\"][\"advanced\"] = True\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n\n new_parameter = DropdownInput(\n name=\"embedding_provider\",\n display_name=\"Embedding Provider\",\n options=vectorize_providers.keys(),\n value=\"\",\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_choice\", {\"embedding_provider\": new_parameter})\n else:\n build_config[\"embedding_model\"][\"advanced\"] = False\n\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n elif field_name == \"embedding_provider\":\n self.del_fields(\n build_config,\n [\"model\", \"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n model_options = vectorize_providers[field_value][1]\n\n new_parameter = DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n info=\"The embedding model to use for the selected provider. Each provider has a different set of \"\n \"models available (full list at \"\n \"https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\\n\\n\"\n f\"{', '.join(model_options)}\",\n options=model_options,\n value=None,\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_provider\", {\"model\": new_parameter})\n\n elif field_name == \"model\":\n self.del_fields(\n build_config,\n [\"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n new_parameter_1 = DictInput(\n name=\"z_01_model_parameters\",\n display_name=\"Model Parameters\",\n list=True,\n ).to_dict()\n\n new_parameter_2 = MessageTextInput(\n name=\"z_02_api_key_name\",\n display_name=\"API Key Name\",\n info=\"The name of the embeddings provider API key stored on Astra. \"\n \"If set, it will override the 'ProviderKey' in the authentication parameters.\",\n ).to_dict()\n\n new_parameter_3 = SecretStrInput(\n load_from_db=False,\n name=\"z_03_provider_api_key\",\n display_name=\"Provider API Key\",\n info=\"An alternative to the Astra Authentication that passes an API key for the provider \"\n \"with each request to Astra DB. \"\n \"This may be used when Vectorize is configured for the collection, \"\n \"but no corresponding provider secret is stored within Astra's key management system.\",\n ).to_dict()\n\n new_parameter_4 = DictInput(\n name=\"z_04_authentication\",\n display_name=\"Authentication Parameters\",\n list=True,\n ).to_dict()\n\n self.insert_in_dict(\n build_config,\n \"model\",\n {\n \"z_01_model_parameters\": new_parameter_1,\n \"z_02_api_key_name\": new_parameter_2,\n \"z_03_provider_api_key\": new_parameter_3,\n \"z_04_authentication\": new_parameter_4,\n },\n )\n\n return build_config\n\n def build_vectorize_options(self, **kwargs):\n for attribute in [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ]:\n if not hasattr(self, attribute):\n setattr(self, attribute, None)\n\n # Fetch values from kwargs if any self.* attributes are None\n provider_mapping = self.get_vectorize_providers()\n provider_value = provider_mapping.get(self.embedding_provider, [None])[0] or kwargs.get(\"embedding_provider\")\n model_name = self.model or kwargs.get(\"model\")\n authentication = {**(self.z_04_authentication or {}), **kwargs.get(\"z_04_authentication\", {})}\n parameters = self.z_01_model_parameters or kwargs.get(\"z_01_model_parameters\", {})\n\n # Set the API key name if provided\n api_key_name = self.z_02_api_key_name or kwargs.get(\"z_02_api_key_name\")\n provider_key = self.z_03_provider_api_key or kwargs.get(\"z_03_provider_api_key\")\n if api_key_name:\n authentication[\"providerKey\"] = api_key_name\n if authentication:\n provider_key = None\n authentication[\"providerKey\"] = authentication[\"providerKey\"].split(\".\")[0]\n\n # Set authentication and parameters to None if no values are provided\n if not authentication:\n authentication = None\n if not parameters:\n parameters = None\n\n return {\n # must match astrapy.info.CollectionVectorServiceOptions\n \"collection_vector_service_options\": {\n \"provider\": provider_value,\n \"modelName\": model_name,\n \"authentication\": authentication,\n \"parameters\": parameters,\n },\n \"collection_embedding_api_key\": provider_key,\n }\n\n @check_cached_vector_store\n def build_vector_store(self, vectorize_options=None):\n try:\n from langchain_astradb import AstraDBVectorStore\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n # Initialize parameters based on the collection name\n is_new_collection = self.get_collection_options() is None\n\n # Get the embedding model\n embedding_params = {\"embedding\": self.embedding_model} if self.embedding_choice == \"Embedding Model\" else {}\n\n # Use the embedding model if the choice is set to \"Embedding Model\"\n if self.embedding_choice == \"Astra Vectorize\" and is_new_collection:\n from astrapy.info import CollectionVectorServiceOptions\n\n # Build the vectorize options dictionary\n dict_options = vectorize_options or self.build_vectorize_options(\n embedding_provider=getattr(self, \"embedding_provider\", None) or None,\n model=getattr(self, \"model\", None) or None,\n z_01_model_parameters=getattr(self, \"z_01_model_parameters\", None) or None,\n z_02_api_key_name=getattr(self, \"z_02_api_key_name\", None) or None,\n z_03_provider_api_key=getattr(self, \"z_03_provider_api_key\", None) or None,\n z_04_authentication=getattr(self, \"z_04_authentication\", {}) or {},\n )\n\n # Set the embedding dictionary\n embedding_params = {\n \"collection_vector_service_options\": CollectionVectorServiceOptions.from_dict(\n dict_options.get(\"collection_vector_service_options\")\n ),\n \"collection_embedding_api_key\": dict_options.get(\"collection_embedding_api_key\"),\n }\n\n # Get the running environment for Langflow\n environment = parse_api_endpoint(self.get_api_endpoint()).environment if self.get_api_endpoint() else None\n\n # Get Langflow version and platform information\n __version__ = get_version_info()[\"version\"]\n langflow_prefix = \"\"\n if os.getenv(\"LANGFLOW_HOST\") is not None:\n langflow_prefix = \"ds-\"\n\n # Bundle up the auto-detect parameters\n autodetect_params = {\n \"autodetect_collection\": not is_new_collection, # TODO: May want to expose this option\n \"content_field\": self.content_field or None,\n \"ignore_invalid_documents\": self.ignore_invalid_documents,\n }\n\n # Attempt to build the Vector Store object\n try:\n vector_store = AstraDBVectorStore(\n # Astra DB Authentication Parameters\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.keyspace or None,\n collection_name=self.get_collection_choice(),\n environment=environment,\n # Astra DB Usage Tracking Parameters\n ext_callers=[(f\"{langflow_prefix}langflow\", __version__)],\n # Astra DB Vector Store Parameters\n **autodetect_params or {},\n **embedding_params or {},\n **self.astradb_vectorstore_kwargs or {},\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n if self.search_type == \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n if self.search_type == \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n return \"similarity\"\n\n def _build_search_args(self):\n query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None\n\n if query:\n args = {\n \"query\": query,\n \"search_type\": self._map_search_type(),\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n elif self.advanced_search_filter:\n args = {\n \"n\": self.number_of_results,\n }\n else:\n return {}\n\n filter_arg = self.advanced_search_filter or {}\n if filter_arg:\n args[\"filter\"] = filter_arg\n\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n vector_store = vector_store or self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n try:\n search_args = self._build_search_args()\n except Exception as e:\n msg = f\"Error in AstraDBVectorStore._build_search_args: {e}\"\n raise ValueError(msg) from e\n\n if not search_args:\n self.log(\"No search input or filters provided. Skipping search.\")\n return []\n\n docs = []\n search_method = \"search\" if \"query\" in search_args else \"metadata_search\"\n\n try:\n self.log(f\"Calling vector_store.{search_method} with args: {search_args}\")\n docs = getattr(vector_store, search_method)(**search_args)\n except Exception as e:\n msg = f\"Error performing {search_method} in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n return data\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n" }, "collection_name": { "_input_type": "DropdownInput", @@ -3538,7 +3538,7 @@ "show": true, "title_case": false, "type": "code", - "value": "import os\nfrom collections import defaultdict\n\nfrom astrapy import AstraDBAdmin, DataAPIClient\nfrom astrapy.admin import parse_api_endpoint\nfrom langchain_astradb import AstraDBVectorStore\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import DictInput, FloatInput, MessageTextInput, NestedDictInput\nfrom langflow.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\nfrom langflow.utils.version import get_version_info\n\n\nclass AstraDBVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB\"\n description: str = \"Ingest and search documents in Astra DB\"\n documentation: str = \"https://docs.datastax.com/en/langflow/astra-components.html\"\n name = \"AstraDB\"\n icon: str = \"AstraDB\"\n\n _cached_vector_store: AstraDBVectorStore | None = None\n\n base_inputs = LCVectorStoreComponent.inputs\n if \"search_query\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n tool_mode=True,\n )\n )\n if \"ingest_data\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n DataInput(\n name=\"ingest_data\",\n display_name=\"Ingest Data\",\n )\n )\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"api_endpoint\",\n display_name=\"Database\",\n info=\"The Astra DB Database to use.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"Default database\"],\n value=\"Default database\",\n ),\n DropdownInput(\n name=\"collection_name\",\n display_name=\"Collection\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"+ Create new collection\"],\n value=\"+ Create new collection\",\n ),\n StrInput(\n name=\"collection_name_new\",\n display_name=\"Collection Name\",\n info=\"Name of the new collection to create.\",\n advanced=os.getenv(\"LANGFLOW_HOST\") is not None,\n required=os.getenv(\"LANGFLOW_HOST\") is None,\n ),\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"embedding_choice\",\n display_name=\"Embedding Model or Astra Vectorize\",\n info=\"Determines whether to use Astra Vectorize for the collection.\",\n options=[\"Embedding Model\", \"Astra Vectorize\"],\n real_time_refresh=True,\n value=\"Embedding Model\",\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n *base_inputs,\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Search Results\",\n info=\"Number of search results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n NestedDictInput(\n name=\"advanced_search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n ),\n StrInput(\n name=\"content_field\",\n display_name=\"Content Field\",\n info=\"Field to use as the text content field for the vector store.\",\n advanced=True,\n ),\n BoolInput(\n name=\"ignore_invalid_documents\",\n display_name=\"Ignore Invalid Documents\",\n info=\"Boolean flag to determine whether to ignore invalid documents at runtime.\",\n advanced=True,\n ),\n NestedDictInput(\n name=\"astradb_vectorstore_kwargs\",\n display_name=\"AstraDBVectorStore Parameters\",\n info=\"Optional dictionary of additional parameters for the AstraDBVectorStore.\",\n advanced=True,\n ),\n ]\n\n def del_fields(self, build_config, field_list):\n for field in field_list:\n if field in build_config:\n del build_config[field]\n\n return build_config\n\n def insert_in_dict(self, build_config, field_name, new_parameters):\n # Insert the new key-value pair after the found key\n for new_field_name, new_parameter in new_parameters.items():\n # Get all the items as a list of tuples (key, value)\n items = list(build_config.items())\n\n # Find the index of the key to insert after\n idx = len(items)\n for i, (key, _) in enumerate(items):\n if key == field_name:\n idx = i + 1\n break\n\n items.insert(idx, (new_field_name, new_parameter))\n\n # Clear the original dictionary and update with the modified items\n build_config.clear()\n build_config.update(items)\n\n return build_config\n\n def get_vectorize_providers(self):\n try:\n self.log(\"Dynamically updating list of Vectorize providers.\")\n\n # Get the admin object\n admin = AstraDBAdmin(token=self.token)\n db_admin = admin.get_database_admin(self.get_api_endpoint())\n\n # Get the list of embedding providers\n embedding_providers = db_admin.find_embedding_providers().as_dict()\n\n vectorize_providers_mapping = {}\n # Map the provider display name to the provider key and models\n for provider_key, provider_data in embedding_providers[\"embeddingProviders\"].items():\n display_name = provider_data[\"displayName\"]\n models = [model[\"name\"] for model in provider_data[\"models\"]]\n\n vectorize_providers_mapping[display_name] = [provider_key, models]\n\n # Sort the resulting dictionary\n return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching Vectorize providers: {e}\")\n\n return {}\n\n def get_database_list(self):\n # Get the admin object\n db_admin = AstraDBAdmin(token=self.token)\n db_list = list(db_admin.list_databases())\n\n # Generate the api endpoint for each database\n return {db.info.name: f\"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com\" for db in db_list}\n\n def get_api_endpoint(self):\n # Get the database name (or endpoint)\n database = self.api_endpoint\n\n # If the database is not set, get the first database in the list\n if not database or database == \"Default database\":\n database, _ = next(iter(self.get_database_list().items()))\n\n # If the database is a URL, return it\n if database.startswith(\"https://\"):\n return database\n\n # Otherwise, get the URL from the database list\n return self.get_database_list().get(database)\n\n def get_database(self):\n try:\n client = DataAPIClient(token=self.token)\n\n return client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n )\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting database: {e}\")\n\n return None\n\n def _initialize_database_options(self):\n if not self.token:\n return [\"Default database\"]\n try:\n databases = [\"Default database\", *list(self.get_database_list().keys())]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching databases: {e}\")\n\n return [\"Default database\"]\n\n return databases\n\n def _initialize_collection_options(self):\n database = self.get_database()\n if database is None:\n return [\"+ Create new collection\"]\n\n try:\n collections = [collection.name for collection in database.list_collections(keyspace=self.keyspace or None)]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching collections: {e}\")\n\n return [\"+ Create new collection\"]\n\n return [*collections, \"+ Create new collection\"]\n\n def get_collection_choice(self):\n collection_name = self.collection_name\n if collection_name == \"+ Create new collection\":\n return self.collection_name_new\n\n return collection_name\n\n def get_collection_options(self):\n # Only get the options if the collection exists\n database = self.get_database()\n if database is None:\n return None\n\n collection_name = self.get_collection_choice()\n\n try:\n collection = database.get_collection(collection_name, keyspace=self.keyspace or None)\n collection_options = collection.options()\n except Exception as _: # noqa: BLE001\n return None\n\n return collection_options.vector\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n # Always attempt to update the database list\n if field_name in [\"token\", \"api_endpoint\", \"collection_name\"]:\n # Update the database selector\n build_config[\"api_endpoint\"][\"options\"] = self._initialize_database_options()\n\n # Set the default API endpoint if not set\n if build_config[\"api_endpoint\"][\"value\"] == \"Default database\":\n build_config[\"api_endpoint\"][\"value\"] = build_config[\"api_endpoint\"][\"options\"][0]\n\n # Update the collection selector\n build_config[\"collection_name\"][\"options\"] = self._initialize_collection_options()\n\n # Update the choice of embedding model based on collection name\n if field_name == \"collection_name\":\n # Detect if it is a new collection\n is_new_collection = field_value == \"+ Create new collection\"\n\n # Set the advanced and required fields based on the collection choice\n build_config[\"embedding_choice\"].update(\n {\n \"advanced\": not is_new_collection,\n \"value\": \"Embedding Model\" if is_new_collection else build_config[\"embedding_choice\"].get(\"value\"),\n }\n )\n\n # Set the advanced field for the embedding model\n build_config[\"embedding_model\"][\"advanced\"] = not is_new_collection\n\n # Set the advanced and required fields for the new collection name\n build_config[\"collection_name_new\"].update(\n {\n \"advanced\": not is_new_collection,\n \"required\": is_new_collection,\n \"value\": \"\" if not is_new_collection else build_config[\"collection_name_new\"].get(\"value\"),\n }\n )\n\n # Get the collection options for the selected collection\n collection_options = self.get_collection_options()\n\n # If the collection options are available (DB exists), show the advanced options\n if collection_options:\n build_config[\"embedding_choice\"][\"advanced\"] = True\n\n if collection_options.service:\n # Remove unnecessary fields when a service is set\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": True},\n \"embedding_choice\": {\"value\": \"Astra Vectorize\"},\n }\n else:\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": False},\n \"embedding_provider\": {\"advanced\": False},\n \"embedding_choice\": {\"value\": \"Embedding Model\"},\n }\n\n # Apply updates to the build_config\n for key, value in updates.items():\n build_config[key].update(value)\n\n elif field_name == \"embedding_choice\":\n if field_value == \"Astra Vectorize\":\n build_config[\"embedding_model\"][\"advanced\"] = True\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n\n new_parameter = DropdownInput(\n name=\"embedding_provider\",\n display_name=\"Embedding Provider\",\n options=vectorize_providers.keys(),\n value=\"\",\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_choice\", {\"embedding_provider\": new_parameter})\n else:\n build_config[\"embedding_model\"][\"advanced\"] = False\n\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n elif field_name == \"embedding_provider\":\n self.del_fields(\n build_config,\n [\"model\", \"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n model_options = vectorize_providers[field_value][1]\n\n new_parameter = DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n info=\"The embedding model to use for the selected provider. Each provider has a different set of \"\n \"models available (full list at \"\n \"https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\\n\\n\"\n f\"{', '.join(model_options)}\",\n options=model_options,\n value=None,\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_provider\", {\"model\": new_parameter})\n\n elif field_name == \"model\":\n self.del_fields(\n build_config,\n [\"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n new_parameter_1 = DictInput(\n name=\"z_01_model_parameters\",\n display_name=\"Model Parameters\",\n list=True,\n ).to_dict()\n\n new_parameter_2 = MessageTextInput(\n name=\"z_02_api_key_name\",\n display_name=\"API Key Name\",\n info=\"The name of the embeddings provider API key stored on Astra. \"\n \"If set, it will override the 'ProviderKey' in the authentication parameters.\",\n ).to_dict()\n\n new_parameter_3 = SecretStrInput(\n load_from_db=False,\n name=\"z_03_provider_api_key\",\n display_name=\"Provider API Key\",\n info=\"An alternative to the Astra Authentication that passes an API key for the provider \"\n \"with each request to Astra DB. \"\n \"This may be used when Vectorize is configured for the collection, \"\n \"but no corresponding provider secret is stored within Astra's key management system.\",\n ).to_dict()\n\n new_parameter_4 = DictInput(\n name=\"z_04_authentication\",\n display_name=\"Authentication Parameters\",\n list=True,\n ).to_dict()\n\n self.insert_in_dict(\n build_config,\n \"model\",\n {\n \"z_01_model_parameters\": new_parameter_1,\n \"z_02_api_key_name\": new_parameter_2,\n \"z_03_provider_api_key\": new_parameter_3,\n \"z_04_authentication\": new_parameter_4,\n },\n )\n\n return build_config\n\n def build_vectorize_options(self, **kwargs):\n for attribute in [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ]:\n if not hasattr(self, attribute):\n setattr(self, attribute, None)\n\n # Fetch values from kwargs if any self.* attributes are None\n provider_mapping = self.get_vectorize_providers()\n provider_value = provider_mapping.get(self.embedding_provider, [None])[0] or kwargs.get(\"embedding_provider\")\n model_name = self.model or kwargs.get(\"model\")\n authentication = {**(self.z_04_authentication or {}), **kwargs.get(\"z_04_authentication\", {})}\n parameters = self.z_01_model_parameters or kwargs.get(\"z_01_model_parameters\", {})\n\n # Set the API key name if provided\n api_key_name = self.z_02_api_key_name or kwargs.get(\"z_02_api_key_name\")\n provider_key = self.z_03_provider_api_key or kwargs.get(\"z_03_provider_api_key\")\n if api_key_name:\n authentication[\"providerKey\"] = api_key_name\n if authentication:\n provider_key = None\n authentication[\"providerKey\"] = authentication[\"providerKey\"].split(\".\")[0]\n\n # Set authentication and parameters to None if no values are provided\n if not authentication:\n authentication = None\n if not parameters:\n parameters = None\n\n return {\n # must match astrapy.info.CollectionVectorServiceOptions\n \"collection_vector_service_options\": {\n \"provider\": provider_value,\n \"modelName\": model_name,\n \"authentication\": authentication,\n \"parameters\": parameters,\n },\n \"collection_embedding_api_key\": provider_key,\n }\n\n @check_cached_vector_store\n def build_vector_store(self, vectorize_options=None):\n try:\n from langchain_astradb import AstraDBVectorStore\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n # Initialize parameters based on the collection name\n is_new_collection = self.get_collection_options() is None\n\n # Get the embedding model\n embedding_params = {\"embedding\": self.embedding_model} if self.embedding_choice == \"Embedding Model\" else {}\n\n # Use the embedding model if the choice is set to \"Embedding Model\"\n if self.embedding_choice == \"Astra Vectorize\" and is_new_collection:\n from astrapy.info import CollectionVectorServiceOptions\n\n # Build the vectorize options dictionary\n dict_options = vectorize_options or self.build_vectorize_options(\n embedding_provider=getattr(self, \"embedding_provider\", None) or None,\n model=getattr(self, \"model\", None) or None,\n z_01_model_parameters=getattr(self, \"z_01_model_parameters\", None) or None,\n z_02_api_key_name=getattr(self, \"z_02_api_key_name\", None) or None,\n z_03_provider_api_key=getattr(self, \"z_03_provider_api_key\", None) or None,\n z_04_authentication=getattr(self, \"z_04_authentication\", {}) or {},\n )\n\n # Set the embedding dictionary\n embedding_params = {\n \"collection_vector_service_options\": CollectionVectorServiceOptions.from_dict(\n dict_options.get(\"collection_vector_service_options\")\n ),\n \"collection_embedding_api_key\": dict_options.get(\"collection_embedding_api_key\"),\n }\n\n # Get the running environment for Langflow\n environment = parse_api_endpoint(self.get_api_endpoint()).environment if self.get_api_endpoint() else None\n\n # Get Langflow version and platform information\n __version__ = get_version_info()[\"version\"]\n langflow_prefix = \"\"\n if os.getenv(\"LANGFLOW_HOST\") is not None:\n langflow_prefix = \"ds-\"\n\n # Bundle up the auto-detect parameters\n autodetect_params = {\n \"autodetect_collection\": not is_new_collection, # TODO: May want to expose this option\n \"content_field\": self.content_field or None,\n \"ignore_invalid_documents\": self.ignore_invalid_documents,\n }\n\n # Attempt to build the Vector Store object\n try:\n vector_store = AstraDBVectorStore(\n # Astra DB Authentication Parameters\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.keyspace or None,\n collection_name=self.get_collection_choice(),\n environment=environment,\n # Astra DB Usage Tracking Parameters\n ext_callers=[(f\"{langflow_prefix}langflow\", __version__)],\n # Astra DB Vector Store Parameters\n **autodetect_params or {},\n **embedding_params or {},\n **self.astradb_vectorstore_kwargs or {},\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n if self.search_type == \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n if self.search_type == \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n return \"similarity\"\n\n def _build_search_args(self):\n query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None\n\n if query:\n args = {\n \"query\": query,\n \"search_type\": self._map_search_type(),\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n elif self.advanced_search_filter:\n args = {\n \"n\": self.number_of_results,\n }\n else:\n return {}\n\n filter_arg = self.advanced_search_filter or {}\n if filter_arg:\n args[\"filter\"] = filter_arg\n\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n vector_store = vector_store or self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n try:\n search_args = self._build_search_args()\n except Exception as e:\n msg = f\"Error in AstraDBVectorStore._build_search_args: {e}\"\n raise ValueError(msg) from e\n\n if not search_args:\n self.log(\"No search input or filters provided. Skipping search.\")\n return []\n\n docs = []\n search_method = \"search\" if \"query\" in search_args else \"metadata_search\"\n\n try:\n self.log(f\"Calling vector_store.{search_method} with args: {search_args}\")\n docs = getattr(vector_store, search_method)(**search_args)\n except Exception as e:\n msg = f\"Error performing {search_method} in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n return data\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n" + "value": "import os\nfrom collections import defaultdict\n\nfrom astrapy import AstraDBAdmin, DataAPIClient\nfrom astrapy.admin import parse_api_endpoint\nfrom langchain_astradb import AstraDBVectorStore\n\nfrom langflow.base.vectorstores.model import LCVectorStoreComponent, check_cached_vector_store\nfrom langflow.helpers import docs_to_data\nfrom langflow.inputs import DictInput, FloatInput, MessageTextInput, NestedDictInput\nfrom langflow.io import (\n BoolInput,\n DataInput,\n DropdownInput,\n HandleInput,\n IntInput,\n SecretStrInput,\n StrInput,\n)\nfrom langflow.schema import Data\nfrom langflow.utils.version import get_version_info\n\n\nclass AstraDBVectorStoreComponent(LCVectorStoreComponent):\n display_name: str = \"Astra DB\"\n description: str = \"Ingest and search documents in Astra DB\"\n documentation: str = \"https://docs.datastax.com/en/langflow/astra-components.html\"\n name = \"AstraDB\"\n icon: str = \"AstraDB\"\n\n _cached_vector_store: AstraDBVectorStore | None = None\n\n base_inputs = LCVectorStoreComponent.inputs\n if \"search_query\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n MessageTextInput(\n name=\"search_query\",\n display_name=\"Search Query\",\n tool_mode=True,\n )\n )\n if \"ingest_data\" not in [input_.name for input_ in base_inputs]:\n base_inputs.append(\n DataInput(\n name=\"ingest_data\",\n display_name=\"Ingest Data\",\n )\n )\n\n inputs = [\n SecretStrInput(\n name=\"token\",\n display_name=\"Astra DB Application Token\",\n info=\"Authentication token for accessing Astra DB.\",\n value=\"ASTRA_DB_APPLICATION_TOKEN\",\n required=True,\n advanced=os.getenv(\"ASTRA_ENHANCED\", \"false\").lower() == \"true\",\n real_time_refresh=True,\n ),\n DropdownInput(\n name=\"api_endpoint\",\n display_name=\"Database\",\n info=\"The Astra DB Database to use.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"Default database\"],\n value=\"Default database\",\n ),\n DropdownInput(\n name=\"collection_name\",\n display_name=\"Collection\",\n info=\"The name of the collection within Astra DB where the vectors will be stored.\",\n required=True,\n refresh_button=True,\n real_time_refresh=True,\n options=[\"+ Create new collection\"],\n value=\"+ Create new collection\",\n ),\n StrInput(\n name=\"collection_name_new\",\n display_name=\"Collection Name\",\n info=\"Name of the new collection to create.\",\n advanced=os.getenv(\"LANGFLOW_HOST\") is not None,\n required=os.getenv(\"LANGFLOW_HOST\") is None,\n ),\n StrInput(\n name=\"keyspace\",\n display_name=\"Keyspace\",\n info=\"Optional keyspace within Astra DB to use for the collection.\",\n advanced=True,\n ),\n DropdownInput(\n name=\"embedding_choice\",\n display_name=\"Embedding Model or Astra Vectorize\",\n info=\"Determines whether to use Astra Vectorize for the collection.\",\n options=[\"Embedding Model\", \"Astra Vectorize\"],\n real_time_refresh=True,\n value=\"Embedding Model\",\n ),\n HandleInput(\n name=\"embedding_model\",\n display_name=\"Embedding Model\",\n input_types=[\"Embeddings\"],\n info=\"Allows an embedding model configuration.\",\n ),\n *base_inputs,\n IntInput(\n name=\"number_of_results\",\n display_name=\"Number of Search Results\",\n info=\"Number of search results to return.\",\n advanced=True,\n value=4,\n ),\n DropdownInput(\n name=\"search_type\",\n display_name=\"Search Type\",\n info=\"Search type to use\",\n options=[\"Similarity\", \"Similarity with score threshold\", \"MMR (Max Marginal Relevance)\"],\n value=\"Similarity\",\n advanced=True,\n ),\n FloatInput(\n name=\"search_score_threshold\",\n display_name=\"Search Score Threshold\",\n info=\"Minimum similarity score threshold for search results. \"\n \"(when using 'Similarity with score threshold')\",\n value=0,\n advanced=True,\n ),\n NestedDictInput(\n name=\"advanced_search_filter\",\n display_name=\"Search Metadata Filter\",\n info=\"Optional dictionary of filters to apply to the search query.\",\n advanced=True,\n ),\n StrInput(\n name=\"content_field\",\n display_name=\"Content Field\",\n info=\"Field to use as the text content field for the vector store.\",\n advanced=True,\n ),\n BoolInput(\n name=\"ignore_invalid_documents\",\n display_name=\"Ignore Invalid Documents\",\n info=\"Boolean flag to determine whether to ignore invalid documents at runtime.\",\n advanced=True,\n ),\n NestedDictInput(\n name=\"astradb_vectorstore_kwargs\",\n display_name=\"AstraDBVectorStore Parameters\",\n info=\"Optional dictionary of additional parameters for the AstraDBVectorStore.\",\n advanced=True,\n ),\n ]\n\n def del_fields(self, build_config, field_list):\n for field in field_list:\n if field in build_config:\n del build_config[field]\n\n return build_config\n\n def insert_in_dict(self, build_config, field_name, new_parameters):\n # Insert the new key-value pair after the found key\n for new_field_name, new_parameter in new_parameters.items():\n # Get all the items as a list of tuples (key, value)\n items = list(build_config.items())\n\n # Find the index of the key to insert after\n idx = len(items)\n for i, (key, _) in enumerate(items):\n if key == field_name:\n idx = i + 1\n break\n\n items.insert(idx, (new_field_name, new_parameter))\n\n # Clear the original dictionary and update with the modified items\n build_config.clear()\n build_config.update(items)\n\n return build_config\n\n def get_vectorize_providers(self):\n try:\n self.log(\"Dynamically updating list of Vectorize providers.\")\n\n # Get the admin object\n admin = AstraDBAdmin(token=self.token)\n db_admin = admin.get_database_admin(self.get_api_endpoint())\n\n # Get the list of embedding providers\n embedding_providers = db_admin.find_embedding_providers().as_dict()\n\n vectorize_providers_mapping = {}\n # Map the provider display name to the provider key and models\n for provider_key, provider_data in embedding_providers[\"embeddingProviders\"].items():\n display_name = provider_data[\"displayName\"]\n models = [model[\"name\"] for model in provider_data[\"models\"]]\n\n vectorize_providers_mapping[display_name] = [provider_key, models]\n\n # Sort the resulting dictionary\n return defaultdict(list, dict(sorted(vectorize_providers_mapping.items())))\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching Vectorize providers: {e}\")\n\n return {}\n\n def get_database_list(self):\n # Get the admin object\n db_admin = AstraDBAdmin(token=self.token)\n db_list = list(db_admin.list_databases())\n\n # Generate the api endpoint for each database\n return {db.info.name: f\"https://{db.info.id}-{db.info.region}.apps.astra.datastax.com\" for db in db_list}\n\n def get_api_endpoint(self):\n # Get the database name (or endpoint)\n database = self.api_endpoint\n\n # If the database is not set, get the first database in the list\n if not database or database == \"Default database\":\n database, _ = next(iter(self.get_database_list().items()))\n\n # If the database is a URL, return it\n if database.startswith(\"https://\"):\n return database\n\n # Otherwise, get the URL from the database list\n return self.get_database_list().get(database)\n\n def get_database(self):\n try:\n client = DataAPIClient(token=self.token)\n\n return client.get_database(\n api_endpoint=self.get_api_endpoint(),\n token=self.token,\n )\n except Exception as e: # noqa: BLE001\n self.log(f\"Error getting database: {e}\")\n\n return None\n\n def _initialize_database_options(self):\n if not self.token:\n return [\"Default database\"]\n try:\n databases = [\"Default database\", *list(self.get_database_list().keys())]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching databases: {e}\")\n\n return [\"Default database\"]\n\n return databases\n\n def _initialize_collection_options(self):\n database = self.get_database()\n if database is None:\n return [\"+ Create new collection\"]\n\n try:\n collections = [collection.name for collection in database.list_collections(keyspace=self.keyspace or None)]\n except Exception as e: # noqa: BLE001\n self.log(f\"Error fetching collections: {e}\")\n\n return [\"+ Create new collection\"]\n\n return [*collections, \"+ Create new collection\"]\n\n def get_collection_choice(self):\n collection_name = self.collection_name\n if collection_name == \"+ Create new collection\":\n return self.collection_name_new\n\n return collection_name\n\n def get_collection_options(self):\n # Only get the options if the collection exists\n database = self.get_database()\n if database is None:\n return None\n\n collection_name = self.get_collection_choice()\n\n try:\n collection = database.get_collection(collection_name, keyspace=self.keyspace or None)\n collection_options = collection.options()\n except Exception as _: # noqa: BLE001\n return None\n\n return collection_options.vector\n\n def update_build_config(self, build_config: dict, field_value: str, field_name: str | None = None):\n # Always attempt to update the database list\n if field_name in {\"token\", \"api_endpoint\", \"collection_name\"}:\n # Update the database selector\n build_config[\"api_endpoint\"][\"options\"] = self._initialize_database_options()\n\n # Set the default API endpoint if not set\n if build_config[\"api_endpoint\"][\"value\"] == \"Default database\":\n build_config[\"api_endpoint\"][\"value\"] = build_config[\"api_endpoint\"][\"options\"][0]\n\n # Update the collection selector\n build_config[\"collection_name\"][\"options\"] = self._initialize_collection_options()\n\n # Update the choice of embedding model based on collection name\n if field_name == \"collection_name\":\n # Detect if it is a new collection\n is_new_collection = field_value == \"+ Create new collection\"\n\n # Set the advanced and required fields based on the collection choice\n build_config[\"embedding_choice\"].update(\n {\n \"advanced\": not is_new_collection,\n \"value\": \"Embedding Model\" if is_new_collection else build_config[\"embedding_choice\"].get(\"value\"),\n }\n )\n\n # Set the advanced field for the embedding model\n build_config[\"embedding_model\"][\"advanced\"] = not is_new_collection\n\n # Set the advanced and required fields for the new collection name\n build_config[\"collection_name_new\"].update(\n {\n \"advanced\": not is_new_collection,\n \"required\": is_new_collection,\n \"value\": \"\" if not is_new_collection else build_config[\"collection_name_new\"].get(\"value\"),\n }\n )\n\n # Get the collection options for the selected collection\n collection_options = self.get_collection_options()\n\n # If the collection options are available (DB exists), show the advanced options\n if collection_options:\n build_config[\"embedding_choice\"][\"advanced\"] = True\n\n if collection_options.service:\n # Remove unnecessary fields when a service is set\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": True},\n \"embedding_choice\": {\"value\": \"Astra Vectorize\"},\n }\n else:\n # Update the providers mapping\n updates = {\n \"embedding_model\": {\"advanced\": False},\n \"embedding_provider\": {\"advanced\": False},\n \"embedding_choice\": {\"value\": \"Embedding Model\"},\n }\n\n # Apply updates to the build_config\n for key, value in updates.items():\n build_config[key].update(value)\n\n elif field_name == \"embedding_choice\":\n if field_value == \"Astra Vectorize\":\n build_config[\"embedding_model\"][\"advanced\"] = True\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n\n new_parameter = DropdownInput(\n name=\"embedding_provider\",\n display_name=\"Embedding Provider\",\n options=vectorize_providers.keys(),\n value=\"\",\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_choice\", {\"embedding_provider\": new_parameter})\n else:\n build_config[\"embedding_model\"][\"advanced\"] = False\n\n self.del_fields(\n build_config,\n [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ],\n )\n\n elif field_name == \"embedding_provider\":\n self.del_fields(\n build_config,\n [\"model\", \"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n # Update the providers mapping\n vectorize_providers = self.get_vectorize_providers()\n model_options = vectorize_providers[field_value][1]\n\n new_parameter = DropdownInput(\n name=\"model\",\n display_name=\"Model\",\n info=\"The embedding model to use for the selected provider. Each provider has a different set of \"\n \"models available (full list at \"\n \"https://docs.datastax.com/en/astra-db-serverless/databases/embedding-generation.html):\\n\\n\"\n f\"{', '.join(model_options)}\",\n options=model_options,\n value=None,\n required=True,\n real_time_refresh=True,\n ).to_dict()\n\n self.insert_in_dict(build_config, \"embedding_provider\", {\"model\": new_parameter})\n\n elif field_name == \"model\":\n self.del_fields(\n build_config,\n [\"z_01_model_parameters\", \"z_02_api_key_name\", \"z_03_provider_api_key\", \"z_04_authentication\"],\n )\n\n new_parameter_1 = DictInput(\n name=\"z_01_model_parameters\",\n display_name=\"Model Parameters\",\n list=True,\n ).to_dict()\n\n new_parameter_2 = MessageTextInput(\n name=\"z_02_api_key_name\",\n display_name=\"API Key Name\",\n info=\"The name of the embeddings provider API key stored on Astra. \"\n \"If set, it will override the 'ProviderKey' in the authentication parameters.\",\n ).to_dict()\n\n new_parameter_3 = SecretStrInput(\n load_from_db=False,\n name=\"z_03_provider_api_key\",\n display_name=\"Provider API Key\",\n info=\"An alternative to the Astra Authentication that passes an API key for the provider \"\n \"with each request to Astra DB. \"\n \"This may be used when Vectorize is configured for the collection, \"\n \"but no corresponding provider secret is stored within Astra's key management system.\",\n ).to_dict()\n\n new_parameter_4 = DictInput(\n name=\"z_04_authentication\",\n display_name=\"Authentication Parameters\",\n list=True,\n ).to_dict()\n\n self.insert_in_dict(\n build_config,\n \"model\",\n {\n \"z_01_model_parameters\": new_parameter_1,\n \"z_02_api_key_name\": new_parameter_2,\n \"z_03_provider_api_key\": new_parameter_3,\n \"z_04_authentication\": new_parameter_4,\n },\n )\n\n return build_config\n\n def build_vectorize_options(self, **kwargs):\n for attribute in [\n \"embedding_provider\",\n \"model\",\n \"z_01_model_parameters\",\n \"z_02_api_key_name\",\n \"z_03_provider_api_key\",\n \"z_04_authentication\",\n ]:\n if not hasattr(self, attribute):\n setattr(self, attribute, None)\n\n # Fetch values from kwargs if any self.* attributes are None\n provider_mapping = self.get_vectorize_providers()\n provider_value = provider_mapping.get(self.embedding_provider, [None])[0] or kwargs.get(\"embedding_provider\")\n model_name = self.model or kwargs.get(\"model\")\n authentication = {**(self.z_04_authentication or {}), **kwargs.get(\"z_04_authentication\", {})}\n parameters = self.z_01_model_parameters or kwargs.get(\"z_01_model_parameters\", {})\n\n # Set the API key name if provided\n api_key_name = self.z_02_api_key_name or kwargs.get(\"z_02_api_key_name\")\n provider_key = self.z_03_provider_api_key or kwargs.get(\"z_03_provider_api_key\")\n if api_key_name:\n authentication[\"providerKey\"] = api_key_name\n if authentication:\n provider_key = None\n authentication[\"providerKey\"] = authentication[\"providerKey\"].split(\".\")[0]\n\n # Set authentication and parameters to None if no values are provided\n if not authentication:\n authentication = None\n if not parameters:\n parameters = None\n\n return {\n # must match astrapy.info.CollectionVectorServiceOptions\n \"collection_vector_service_options\": {\n \"provider\": provider_value,\n \"modelName\": model_name,\n \"authentication\": authentication,\n \"parameters\": parameters,\n },\n \"collection_embedding_api_key\": provider_key,\n }\n\n @check_cached_vector_store\n def build_vector_store(self, vectorize_options=None):\n try:\n from langchain_astradb import AstraDBVectorStore\n except ImportError as e:\n msg = (\n \"Could not import langchain Astra DB integration package. \"\n \"Please install it with `pip install langchain-astradb`.\"\n )\n raise ImportError(msg) from e\n\n # Initialize parameters based on the collection name\n is_new_collection = self.get_collection_options() is None\n\n # Get the embedding model\n embedding_params = {\"embedding\": self.embedding_model} if self.embedding_choice == \"Embedding Model\" else {}\n\n # Use the embedding model if the choice is set to \"Embedding Model\"\n if self.embedding_choice == \"Astra Vectorize\" and is_new_collection:\n from astrapy.info import CollectionVectorServiceOptions\n\n # Build the vectorize options dictionary\n dict_options = vectorize_options or self.build_vectorize_options(\n embedding_provider=getattr(self, \"embedding_provider\", None) or None,\n model=getattr(self, \"model\", None) or None,\n z_01_model_parameters=getattr(self, \"z_01_model_parameters\", None) or None,\n z_02_api_key_name=getattr(self, \"z_02_api_key_name\", None) or None,\n z_03_provider_api_key=getattr(self, \"z_03_provider_api_key\", None) or None,\n z_04_authentication=getattr(self, \"z_04_authentication\", {}) or {},\n )\n\n # Set the embedding dictionary\n embedding_params = {\n \"collection_vector_service_options\": CollectionVectorServiceOptions.from_dict(\n dict_options.get(\"collection_vector_service_options\")\n ),\n \"collection_embedding_api_key\": dict_options.get(\"collection_embedding_api_key\"),\n }\n\n # Get the running environment for Langflow\n environment = parse_api_endpoint(self.get_api_endpoint()).environment if self.get_api_endpoint() else None\n\n # Get Langflow version and platform information\n __version__ = get_version_info()[\"version\"]\n langflow_prefix = \"\"\n if os.getenv(\"LANGFLOW_HOST\") is not None:\n langflow_prefix = \"ds-\"\n\n # Bundle up the auto-detect parameters\n autodetect_params = {\n \"autodetect_collection\": not is_new_collection, # TODO: May want to expose this option\n \"content_field\": self.content_field or None,\n \"ignore_invalid_documents\": self.ignore_invalid_documents,\n }\n\n # Attempt to build the Vector Store object\n try:\n vector_store = AstraDBVectorStore(\n # Astra DB Authentication Parameters\n token=self.token,\n api_endpoint=self.get_api_endpoint(),\n namespace=self.keyspace or None,\n collection_name=self.get_collection_choice(),\n environment=environment,\n # Astra DB Usage Tracking Parameters\n ext_callers=[(f\"{langflow_prefix}langflow\", __version__)],\n # Astra DB Vector Store Parameters\n **autodetect_params or {},\n **embedding_params or {},\n **self.astradb_vectorstore_kwargs or {},\n )\n except Exception as e:\n msg = f\"Error initializing AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self._add_documents_to_vector_store(vector_store)\n\n return vector_store\n\n def _add_documents_to_vector_store(self, vector_store) -> None:\n documents = []\n for _input in self.ingest_data or []:\n if isinstance(_input, Data):\n documents.append(_input.to_lc_document())\n else:\n msg = \"Vector Store Inputs must be Data objects.\"\n raise TypeError(msg)\n\n if documents:\n self.log(f\"Adding {len(documents)} documents to the Vector Store.\")\n try:\n vector_store.add_documents(documents)\n except Exception as e:\n msg = f\"Error adding documents to AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n else:\n self.log(\"No documents to add to the Vector Store.\")\n\n def _map_search_type(self) -> str:\n if self.search_type == \"Similarity with score threshold\":\n return \"similarity_score_threshold\"\n if self.search_type == \"MMR (Max Marginal Relevance)\":\n return \"mmr\"\n return \"similarity\"\n\n def _build_search_args(self):\n query = self.search_query if isinstance(self.search_query, str) and self.search_query.strip() else None\n\n if query:\n args = {\n \"query\": query,\n \"search_type\": self._map_search_type(),\n \"k\": self.number_of_results,\n \"score_threshold\": self.search_score_threshold,\n }\n elif self.advanced_search_filter:\n args = {\n \"n\": self.number_of_results,\n }\n else:\n return {}\n\n filter_arg = self.advanced_search_filter or {}\n if filter_arg:\n args[\"filter\"] = filter_arg\n\n return args\n\n def search_documents(self, vector_store=None) -> list[Data]:\n vector_store = vector_store or self.build_vector_store()\n\n self.log(f\"Search input: {self.search_query}\")\n self.log(f\"Search type: {self.search_type}\")\n self.log(f\"Number of results: {self.number_of_results}\")\n\n try:\n search_args = self._build_search_args()\n except Exception as e:\n msg = f\"Error in AstraDBVectorStore._build_search_args: {e}\"\n raise ValueError(msg) from e\n\n if not search_args:\n self.log(\"No search input or filters provided. Skipping search.\")\n return []\n\n docs = []\n search_method = \"search\" if \"query\" in search_args else \"metadata_search\"\n\n try:\n self.log(f\"Calling vector_store.{search_method} with args: {search_args}\")\n docs = getattr(vector_store, search_method)(**search_args)\n except Exception as e:\n msg = f\"Error performing {search_method} in AstraDBVectorStore: {e}\"\n raise ValueError(msg) from e\n\n self.log(f\"Retrieved documents: {len(docs)}\")\n\n data = docs_to_data(docs)\n self.log(f\"Converted documents to data: {len(data)}\")\n self.status = data\n return data\n\n def get_retriever_kwargs(self):\n search_args = self._build_search_args()\n return {\n \"search_type\": self._map_search_type(),\n \"search_kwargs\": search_args,\n }\n" }, "collection_name": { "_input_type": "DropdownInput", diff --git a/src/backend/base/langflow/interface/listing.py b/src/backend/base/langflow/interface/listing.py index d7f88b311..de87df1c9 100644 --- a/src/backend/base/langflow/interface/listing.py +++ b/src/backend/base/langflow/interface/listing.py @@ -1,3 +1,5 @@ +from typing_extensions import override + from langflow.services.deps import get_settings_service from langflow.utils.lazy_load import LazyLoadDictBase @@ -13,6 +15,7 @@ class AllTypesDict(LazyLoadDictBase): "Custom": ["Custom Tool", "Python Function"], } + @override def get_type_dict(self): from langflow.interface.types import get_all_types_dict diff --git a/src/backend/base/langflow/logging/logger.py b/src/backend/base/langflow/logging/logger.py index e705ee3b5..51a78a9d7 100644 --- a/src/backend/base/langflow/logging/logger.py +++ b/src/backend/base/langflow/logging/logger.py @@ -15,7 +15,7 @@ from loguru._file_sink import FileSink from loguru._simple_sinks import AsyncSink from platformdirs import user_cache_dir from rich.logging import RichHandler -from typing_extensions import NotRequired +from typing_extensions import NotRequired, override from langflow.settings import DEV @@ -285,6 +285,7 @@ class InterceptHandler(logging.Handler): See https://loguru.readthedocs.io/en/stable/overview.html#entirely-compatible-with-standard-logging. """ + @override def emit(self, record) -> None: # Get corresponding Loguru level if it exists try: diff --git a/src/backend/base/langflow/main.py b/src/backend/base/langflow/main.py index c696d3f7e..e2cd6944e 100644 --- a/src/backend/base/langflow/main.py +++ b/src/backend/base/langflow/main.py @@ -19,7 +19,7 @@ from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor from pydantic import PydanticDeprecatedSince20 from pydantic_core import PydanticSerializationError from rich import print as rprint -from starlette.middleware.base import BaseHTTPMiddleware +from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint from langflow.api import health_check_router, log_router, router from langflow.initial_setup.setup import ( @@ -45,7 +45,7 @@ class RequestCancelledMiddleware(BaseHTTPMiddleware): def __init__(self, app) -> None: super().__init__(app) - async def dispatch(self, request: Request, call_next): + async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: sentinel = object() async def cancel_handler(): @@ -68,7 +68,7 @@ class RequestCancelledMiddleware(BaseHTTPMiddleware): class JavaScriptMIMETypeMiddleware(BaseHTTPMiddleware): - async def dispatch(self, request: Request, call_next): + async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response: try: response = await call_next(request) except Exception as exc: diff --git a/src/backend/base/langflow/middleware.py b/src/backend/base/langflow/middleware.py index 18cccc580..bed3ae8bf 100644 --- a/src/backend/base/langflow/middleware.py +++ b/src/backend/base/langflow/middleware.py @@ -26,7 +26,8 @@ class ContentSizeLimitMiddleware: self.app = app self.logger = logger - def receive_wrapper(self, receive): + @staticmethod + def receive_wrapper(receive): received = 0 async def inner(): diff --git a/src/backend/base/langflow/schema/dataframe.py b/src/backend/base/langflow/schema/dataframe.py index 7fe494502..c5c943406 100644 --- a/src/backend/base/langflow/schema/dataframe.py +++ b/src/backend/base/langflow/schema/dataframe.py @@ -32,7 +32,7 @@ class DataFrame(pandas_DataFrame): >>> dataset = DataFrame({"name": ["John", "Jane"], "age": [30, 25]}) """ - def __init__(self, data: None | list[dict | Data] | dict | pd.DataFrame = None, **kwargs): + def __init__(self, data: list[dict | Data] | dict | pd.DataFrame | None = None, **kwargs): if data is None: super().__init__(**kwargs) return diff --git a/src/backend/base/langflow/schema/log.py b/src/backend/base/langflow/schema/log.py index 402106a50..7710f99cb 100644 --- a/src/backend/base/langflow/schema/log.py +++ b/src/backend/base/langflow/schema/log.py @@ -6,7 +6,7 @@ from typing_extensions import Protocol from langflow.schema.message import ContentBlock, Message from langflow.schema.playground_events import PlaygroundEvent -LoggableType: TypeAlias = str | dict | list | int | float | bool | None | BaseModel | PlaygroundEvent +LoggableType: TypeAlias = str | dict | list | int | float | bool | BaseModel | PlaygroundEvent | None class LogFunctionType(Protocol): diff --git a/src/backend/base/langflow/services/auth/factory.py b/src/backend/base/langflow/services/auth/factory.py index fbf734b3a..dc84b63dd 100644 --- a/src/backend/base/langflow/services/auth/factory.py +++ b/src/backend/base/langflow/services/auth/factory.py @@ -1,3 +1,5 @@ +from typing_extensions import override + from langflow.services.auth.service import AuthService from langflow.services.factory import ServiceFactory @@ -8,5 +10,6 @@ class AuthServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(AuthService) + @override def create(self, settings_service): return AuthService(settings_service) diff --git a/src/backend/base/langflow/services/cache/factory.py b/src/backend/base/langflow/services/cache/factory.py index 3def8ebc1..ea432812c 100644 --- a/src/backend/base/langflow/services/cache/factory.py +++ b/src/backend/base/langflow/services/cache/factory.py @@ -2,6 +2,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from langflow.logging.logger import logger from langflow.services.cache.disk import AsyncDiskCache from langflow.services.cache.service import AsyncInMemoryCache, CacheService, RedisCache, ThreadingInMemoryCache @@ -15,6 +17,7 @@ class CacheServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(CacheService) + @override def create(self, settings_service: SettingsService): # Here you would have logic to create and configure a CacheService # based on the settings_service diff --git a/src/backend/base/langflow/services/database/service.py b/src/backend/base/langflow/services/database/service.py index dc96aa692..85d155078 100644 --- a/src/backend/base/langflow/services/database/service.py +++ b/src/backend/base/langflow/services/database/service.py @@ -187,7 +187,8 @@ class DatabaseService(Service): await session.commit() logger.debug("Successfully assigned orphaned flows to the default superuser") - def _generate_unique_flow_name(self, original_name: str, existing_names: set[str]) -> str: + @staticmethod + def _generate_unique_flow_name(original_name: str, existing_names: set[str]) -> str: """Generate a unique flow name by adding or incrementing a suffix.""" if original_name not in existing_names: return original_name @@ -212,7 +213,8 @@ class DatabaseService(Service): return new_name - def _check_schema_health(self, connection) -> bool: + @staticmethod + def _check_schema_health(connection) -> bool: inspector = inspect(connection) model_mapping: dict[str, type[SQLModel]] = { @@ -249,7 +251,8 @@ class DatabaseService(Service): async with self.with_session() as session, session.bind.connect() as conn: await conn.run_sync(self._check_schema_health) - def init_alembic(self, alembic_cfg) -> None: + @staticmethod + def init_alembic(alembic_cfg) -> None: logger.info("Initializing alembic") command.ensure_version(alembic_cfg) # alembic_cfg.attributes["connection"].commit() @@ -317,7 +320,8 @@ class DatabaseService(Service): should_initialize_alembic = True await asyncio.to_thread(self._run_migrations, should_initialize_alembic, fix) - def try_downgrade_upgrade_until_success(self, alembic_cfg, retries=5) -> None: + @staticmethod + def try_downgrade_upgrade_until_success(alembic_cfg, retries=5) -> None: # Try -1 then head, if it fails, try -2 then head, etc. # until we reach the number of retries for i in range(1, retries + 1): diff --git a/src/backend/base/langflow/services/session/factory.py b/src/backend/base/langflow/services/session/factory.py index 806067529..52fc43b62 100644 --- a/src/backend/base/langflow/services/session/factory.py +++ b/src/backend/base/langflow/services/session/factory.py @@ -1,5 +1,7 @@ from typing import TYPE_CHECKING +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.session.service import SessionService @@ -11,5 +13,6 @@ class SessionServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(SessionService) + @override def create(self, cache_service: "CacheService"): return SessionService(cache_service) diff --git a/src/backend/base/langflow/services/session/service.py b/src/backend/base/langflow/services/session/service.py index 420ef9778..fd8ff75f8 100644 --- a/src/backend/base/langflow/services/session/service.py +++ b/src/backend/base/langflow/services/session/service.py @@ -38,7 +38,8 @@ class SessionService(Service): return graph, artifacts - def build_key(self, session_id, data_graph) -> str: + @staticmethod + def build_key(session_id, data_graph) -> str: json_hash = compute_dict_hash(data_graph) return f"{session_id}{':' if session_id else ''}{json_hash}" diff --git a/src/backend/base/langflow/services/settings/factory.py b/src/backend/base/langflow/services/settings/factory.py index 30f3ca9c3..07d93a130 100644 --- a/src/backend/base/langflow/services/settings/factory.py +++ b/src/backend/base/langflow/services/settings/factory.py @@ -1,3 +1,5 @@ +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.settings.service import SettingsService @@ -13,6 +15,7 @@ class SettingsServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(SettingsService) + @override def create(self): # Here you would have logic to create and configure a SettingsService diff --git a/src/backend/base/langflow/services/shared_component_cache/factory.py b/src/backend/base/langflow/services/shared_component_cache/factory.py index c9c464967..4e3d36b70 100644 --- a/src/backend/base/langflow/services/shared_component_cache/factory.py +++ b/src/backend/base/langflow/services/shared_component_cache/factory.py @@ -1,5 +1,7 @@ from typing import TYPE_CHECKING +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.shared_component_cache.service import SharedComponentCacheService @@ -11,5 +13,6 @@ class SharedComponentCacheServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(SharedComponentCacheService) + @override def create(self, settings_service: "SettingsService"): return SharedComponentCacheService(expiration_time=settings_service.settings.cache_expire) diff --git a/src/backend/base/langflow/services/socket/factory.py b/src/backend/base/langflow/services/socket/factory.py index 68fe8337c..57a95553d 100644 --- a/src/backend/base/langflow/services/socket/factory.py +++ b/src/backend/base/langflow/services/socket/factory.py @@ -1,5 +1,7 @@ from typing import TYPE_CHECKING +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.socket.service import SocketIOService @@ -13,5 +15,6 @@ class SocketIOFactory(ServiceFactory): service_class=SocketIOService, ) + @override def create(self, cache_service: "CacheService"): return SocketIOService(cache_service) diff --git a/src/backend/base/langflow/services/state/factory.py b/src/backend/base/langflow/services/state/factory.py index 350d7bdcd..b7397d2f8 100644 --- a/src/backend/base/langflow/services/state/factory.py +++ b/src/backend/base/langflow/services/state/factory.py @@ -1,3 +1,5 @@ +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.settings.service import SettingsService from langflow.services.state.service import InMemoryStateService @@ -7,6 +9,7 @@ class StateServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(InMemoryStateService) + @override def create(self, settings_service: SettingsService): return InMemoryStateService( settings_service, diff --git a/src/backend/base/langflow/services/storage/factory.py b/src/backend/base/langflow/services/storage/factory.py index f42a84ece..0ac531bc7 100644 --- a/src/backend/base/langflow/services/storage/factory.py +++ b/src/backend/base/langflow/services/storage/factory.py @@ -1,4 +1,5 @@ from loguru import logger +from typing_extensions import override from langflow.services.factory import ServiceFactory from langflow.services.session.service import SessionService @@ -12,6 +13,7 @@ class StorageServiceFactory(ServiceFactory): StorageService, ) + @override def create(self, session_service: SessionService, settings_service: SettingsService): storage_type = settings_service.settings.storage_type if storage_type.lower() == "local": diff --git a/src/backend/base/langflow/services/storage/local.py b/src/backend/base/langflow/services/storage/local.py index befbe2198..acfb42164 100644 --- a/src/backend/base/langflow/services/storage/local.py +++ b/src/backend/base/langflow/services/storage/local.py @@ -21,12 +21,15 @@ class LocalStorageService(StorageService): async def save_file(self, flow_id: str, file_name: str, data: bytes) -> None: """Save a file in the local storage. - :param flow_id: The identifier for the flow. - :param file_name: The name of the file to be saved. - :param data: The byte content of the file. - :raises FileNotFoundError: If the specified flow does not exist. - :raises IsADirectoryError: If the file name is a directory. - :raises PermissionError: If there is no permission to write the file. + Args: + flow_id: The identifier for the flow. + file_name: The name of the file to be saved. + data: The byte content of the file. + + Raises: + FileNotFoundError: If the specified flow does not exist. + IsADirectoryError: If the file name is a directory. + PermissionError: If there is no permission to write the file. """ folder_path = self.data_dir / flow_id await folder_path.mkdir(parents=True, exist_ok=True) @@ -43,10 +46,15 @@ class LocalStorageService(StorageService): async def get_file(self, flow_id: str, file_name: str) -> bytes: """Retrieve a file from the local storage. - :param flow_id: The identifier for the flow. - :param file_name: The name of the file to be retrieved. - :return: The byte content of the file. - :raises FileNotFoundError: If the file does not exist. + Args: + flow_id: The identifier for the flow. + file_name: The name of the file to be retrieved. + + Returns: + The byte content of the file. + + Raises: + FileNotFoundError: If the file does not exist. """ file_path = self.data_dir / flow_id / file_name if not await file_path.exists(): @@ -63,9 +71,14 @@ class LocalStorageService(StorageService): async def list_files(self, flow_id: str): """List all files in a specified flow. - :param flow_id: The identifier for the flow. - :return: A list of file names. - :raises FileNotFoundError: If the flow directory does not exist. + Args: + flow_id: The identifier for the flow. + + Returns: + A list of file names. + + Raises: + FileNotFoundError: If the flow directory does not exist. """ if not isinstance(flow_id, str): flow_id = str(flow_id) diff --git a/src/backend/base/langflow/services/storage/s3.py b/src/backend/base/langflow/services/storage/s3.py index 46ce643c3..abddfbe91 100644 --- a/src/backend/base/langflow/services/storage/s3.py +++ b/src/backend/base/langflow/services/storage/s3.py @@ -18,10 +18,13 @@ class S3StorageService(StorageService): async def save_file(self, folder: str, file_name: str, data) -> None: """Save a file to the S3 bucket. - :param folder: The folder in the bucket to save the file. - :param file_name: The name of the file to be saved. - :param data: The byte content of the file. - :raises Exception: If an error occurs during file saving. + Args: + folder: The folder in the bucket to save the file. + file_name: The name of the file to be saved. + data: The byte content of the file. + + Raises: + Exception: If an error occurs during file saving. """ try: self.s3_client.put_object(Bucket=self.bucket, Key=f"{folder}/{file_name}", Body=data) @@ -36,10 +39,15 @@ class S3StorageService(StorageService): async def get_file(self, folder: str, file_name: str): """Retrieve a file from the S3 bucket. - :param folder: The folder in the bucket where the file is stored. - :param file_name: The name of the file to be retrieved. - :return: The byte content of the file. - :raises Exception: If an error occurs during file retrieval. + Args: + folder: The folder in the bucket where the file is stored. + file_name: The name of the file to be retrieved. + + Returns: + The byte content of the file. + + Raises: + Exception: If an error occurs during file retrieval. """ try: response = self.s3_client.get_object(Bucket=self.bucket, Key=f"{folder}/{file_name}") @@ -52,9 +60,14 @@ class S3StorageService(StorageService): async def list_files(self, folder: str): """List all files in a specified folder of the S3 bucket. - :param folder: The folder in the bucket to list files from. - :return: A list of file names. - :raises Exception: If an error occurs during file listing. + Args: + folder: The folder in the bucket to list files from. + + Returns: + A list of file names. + + Raises: + Exception: If an error occurs during file listing. """ try: response = self.s3_client.list_objects_v2(Bucket=self.bucket, Prefix=folder) @@ -69,9 +82,12 @@ class S3StorageService(StorageService): async def delete_file(self, folder: str, file_name: str) -> None: """Delete a file from the S3 bucket. - :param folder: The folder in the bucket where the file is stored. - :param file_name: The name of the file to be deleted. - :raises Exception: If an error occurs during file deletion. + Args: + folder: The folder in the bucket where the file is stored. + file_name: The name of the file to be deleted. + + Raises: + Exception: If an error occurs during file deletion. """ try: self.s3_client.delete_object(Bucket=self.bucket, Key=f"{folder}/{file_name}") diff --git a/src/backend/base/langflow/services/store/factory.py b/src/backend/base/langflow/services/store/factory.py index 0a4f18c4a..e34324f48 100644 --- a/src/backend/base/langflow/services/store/factory.py +++ b/src/backend/base/langflow/services/store/factory.py @@ -2,6 +2,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.store.service import StoreService @@ -13,5 +15,6 @@ class StoreServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(StoreService) + @override def create(self, settings_service: SettingsService): return StoreService(settings_service) diff --git a/src/backend/base/langflow/services/store/service.py b/src/backend/base/langflow/services/store/service.py index 90447effa..8dd9006c3 100644 --- a/src/backend/base/langflow/services/store/service.py +++ b/src/backend/base/langflow/services/store/service.py @@ -164,7 +164,8 @@ class StoreService(Service): except Exception: # noqa: BLE001 logger.opt(exception=True).debug("Webhook failed") - def build_tags_filter(self, tags: list[str]): + @staticmethod + def build_tags_filter(tags: list[str]): tags_filter: dict[str, Any] = {"tags": {"_and": []}} for tag in tags: tags_filter["tags"]["_and"].append({"_some": {"tags_id": {"name": {"_eq": tag}}}}) @@ -249,7 +250,8 @@ class StoreService(Service): return filter_conditions - def build_liked_filter(self): + @staticmethod + def build_liked_filter(): user_data = user_data_var.get() # params["filter"] = json.dumps({"user_created": {"_eq": user_data["id"]}}) if not user_data: diff --git a/src/backend/base/langflow/services/task/factory.py b/src/backend/base/langflow/services/task/factory.py index c030776de..aebde3450 100644 --- a/src/backend/base/langflow/services/task/factory.py +++ b/src/backend/base/langflow/services/task/factory.py @@ -1,3 +1,5 @@ +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.task.service import TaskService @@ -6,6 +8,7 @@ class TaskServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(TaskService) + @override def create(self): # Here you would have logic to create and configure a TaskService return TaskService() diff --git a/src/backend/base/langflow/services/telemetry/factory.py b/src/backend/base/langflow/services/telemetry/factory.py index 1fb087de7..0de5dde29 100644 --- a/src/backend/base/langflow/services/telemetry/factory.py +++ b/src/backend/base/langflow/services/telemetry/factory.py @@ -2,6 +2,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.telemetry.service import TelemetryService @@ -13,5 +15,6 @@ class TelemetryServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(TelemetryService) + @override def create(self, settings_service: SettingsService): return TelemetryService(settings_service) diff --git a/src/backend/base/langflow/services/telemetry/service.py b/src/backend/base/langflow/services/telemetry/service.py index 908b50094..57e883cae 100644 --- a/src/backend/base/langflow/services/telemetry/service.py +++ b/src/backend/base/langflow/services/telemetry/service.py @@ -132,7 +132,8 @@ class TelemetryService(Service): except Exception: # noqa: BLE001 logger.exception("Error flushing logs") - async def _cancel_task(self, task: asyncio.Task, cancel_msg: str) -> None: + @staticmethod + async def _cancel_task(task: asyncio.Task, cancel_msg: str) -> None: task.cancel(cancel_msg) await asyncio.wait([task]) if not task.cancelled(): diff --git a/src/backend/base/langflow/services/tracing/arize_phoenix.py b/src/backend/base/langflow/services/tracing/arize_phoenix.py index 4dcb30f67..84e9273b2 100644 --- a/src/backend/base/langflow/services/tracing/arize_phoenix.py +++ b/src/backend/base/langflow/services/tracing/arize_phoenix.py @@ -319,7 +319,8 @@ class ArizePhoenixTracer(BaseTracer): return value - def _error_to_string(self, error: Exception | None): + @staticmethod + def _error_to_string(error: Exception | None): """Converts an error to a string with traceback details.""" error_message = None if error: @@ -327,11 +328,13 @@ class ArizePhoenixTracer(BaseTracer): error_message = f"{error.__class__.__name__}: {error}\n\n{string_stacktrace}" return error_message - def _get_current_timestamp(self) -> int: + @staticmethod + def _get_current_timestamp() -> int: """Gets the current UTC timestamp in nanoseconds.""" return int(datetime.now(timezone.utc).timestamp() * 1_000_000_000) - def _safe_json_dumps(self, obj: Any, **kwargs: Any) -> str: + @staticmethod + def _safe_json_dumps(obj: Any, **kwargs: Any) -> str: """A convenience wrapper around `json.dumps` that ensures that any object can be safely encoded.""" return json.dumps(obj, default=str, ensure_ascii=False, **kwargs) @@ -359,6 +362,7 @@ class ArizePhoenixTracer(BaseTracer): else: current_span.set_status(Status(StatusCode.OK)) + @override def get_langchain_callback(self) -> BaseCallbackHandler | None: """Returns the LangChain callback handler if applicable.""" return None diff --git a/src/backend/base/langflow/services/tracing/factory.py b/src/backend/base/langflow/services/tracing/factory.py index f1971622e..10f8a24f7 100644 --- a/src/backend/base/langflow/services/tracing/factory.py +++ b/src/backend/base/langflow/services/tracing/factory.py @@ -2,6 +2,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.tracing.service import TracingService @@ -13,5 +15,6 @@ class TracingServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(TracingService) + @override def create(self, settings_service: SettingsService): return TracingService(settings_service) diff --git a/src/backend/base/langflow/services/tracing/langfuse.py b/src/backend/base/langflow/services/tracing/langfuse.py index b8080f8b2..19a5f1fdb 100644 --- a/src/backend/base/langflow/services/tracing/langfuse.py +++ b/src/backend/base/langflow/services/tracing/langfuse.py @@ -135,7 +135,8 @@ class LangFuseTracer(BaseTracer): return None return None # self._callback - def _get_config(self) -> dict: + @staticmethod + def _get_config() -> dict: secret_key = os.getenv("LANGFUSE_SECRET_KEY", None) public_key = os.getenv("LANGFUSE_PUBLIC_KEY", None) host = os.getenv("LANGFUSE_HOST", None) diff --git a/src/backend/base/langflow/services/tracing/langsmith.py b/src/backend/base/langflow/services/tracing/langsmith.py index dff06f675..b8f513633 100644 --- a/src/backend/base/langflow/services/tracing/langsmith.py +++ b/src/backend/base/langflow/services/tracing/langsmith.py @@ -7,6 +7,7 @@ from datetime import datetime, timezone from typing import TYPE_CHECKING, Any from loguru import logger +from typing_extensions import override from langflow.schema.data import Data from langflow.services.tracing.base import BaseTracer @@ -140,7 +141,8 @@ class LangSmithTracer(BaseTracer): child.post() self._child_link[trace_name] = child.get_url() - def _error_to_string(self, error: Exception | None): + @staticmethod + def _error_to_string(error: Exception | None): error_message = None if error: string_stacktrace = traceback.format_exception(error) @@ -163,5 +165,6 @@ class LangSmithTracer(BaseTracer): self._run_tree.post() self._run_link = self._run_tree.get_url() + @override def get_langchain_callback(self) -> BaseCallbackHandler | None: return None diff --git a/src/backend/base/langflow/services/tracing/service.py b/src/backend/base/langflow/services/tracing/service.py index 09587b625..e4a055269 100644 --- a/src/backend/base/langflow/services/tracing/service.py +++ b/src/backend/base/langflow/services/tracing/service.py @@ -264,7 +264,8 @@ class TracingService(Service): self.outputs[trace_name] |= outputs or {} self.outputs_metadata[trace_name] |= output_metadata or {} - def _cleanup_inputs(self, inputs: dict[str, Any]): + @staticmethod + def _cleanup_inputs(inputs: dict[str, Any]): inputs = inputs.copy() for key in inputs: if "api_key" in key: diff --git a/src/backend/base/langflow/services/utils.py b/src/backend/base/langflow/services/utils.py index 9bf41e353..d207e3c53 100644 --- a/src/backend/base/langflow/services/utils.py +++ b/src/backend/base/langflow/services/utils.py @@ -177,9 +177,6 @@ async def clean_transactions(settings_service: SettingsService, session: AsyncSe Args: settings_service: The settings service containing configuration like max_transactions_to_keep session: The database session to use for the deletion - - Returns: - None """ try: # Delete transactions using bulk delete @@ -209,9 +206,6 @@ async def clean_vertex_builds(settings_service: SettingsService, session: AsyncS Args: settings_service: The settings service containing configuration like max_vertex_builds_to_keep session: The database session to use for the deletion - - Returns: - None """ try: # Delete vertex builds using bulk delete diff --git a/src/backend/base/langflow/services/variable/factory.py b/src/backend/base/langflow/services/variable/factory.py index 160bb92f6..8eb74334d 100644 --- a/src/backend/base/langflow/services/variable/factory.py +++ b/src/backend/base/langflow/services/variable/factory.py @@ -2,6 +2,8 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing_extensions import override + from langflow.services.factory import ServiceFactory from langflow.services.variable.service import DatabaseVariableService, VariableService @@ -13,6 +15,7 @@ class VariableServiceFactory(ServiceFactory): def __init__(self) -> None: super().__init__(VariableService) + @override def create(self, settings_service: SettingsService): # here you would have logic to create and configure a VariableService # based on the settings_service diff --git a/src/backend/base/langflow/services/variable/service.py b/src/backend/base/langflow/services/variable/service.py index f7b7e92ec..44a71ff4e 100644 --- a/src/backend/base/langflow/services/variable/service.py +++ b/src/backend/base/langflow/services/variable/service.py @@ -6,6 +6,7 @@ from typing import TYPE_CHECKING from loguru import logger from sqlmodel import select +from typing_extensions import override from langflow.services.auth import utils as auth_utils from langflow.services.base import Service @@ -141,6 +142,7 @@ class DatabaseVariableService(VariableService, Service): await session.refresh(db_variable) return db_variable + @override async def delete_variable( self, user_id: UUID | str, @@ -155,6 +157,7 @@ class DatabaseVariableService(VariableService, Service): await session.delete(variable) await session.commit() + @override async def delete_variable_by_id(self, user_id: UUID | str, variable_id: UUID, session: AsyncSession) -> None: stmt = select(Variable).where(Variable.user_id == user_id, Variable.id == variable_id) variable = (await session.exec(stmt)).first() diff --git a/src/backend/base/langflow/utils/concurrency.py b/src/backend/base/langflow/utils/concurrency.py index 4d47aa916..6484b8396 100644 --- a/src/backend/base/langflow/utils/concurrency.py +++ b/src/backend/base/langflow/utils/concurrency.py @@ -36,7 +36,8 @@ class KeyedWorkerLockManager: def __init__(self) -> None: self.locks_dir = Path(user_cache_dir("langflow"), ensure_exists=True) / "worker_locks" - def _validate_key(self, key: str) -> bool: + @staticmethod + def _validate_key(key: str) -> bool: """Validate that the string only contains alphanumeric characters and underscores. Parameters: diff --git a/src/backend/base/langflow/utils/util.py b/src/backend/base/langflow/utils/util.py index 6bcd80421..b0cb98456 100644 --- a/src/backend/base/langflow/utils/util.py +++ b/src/backend/base/langflow/utils/util.py @@ -12,6 +12,7 @@ from docstring_parser import parse from langflow.logging.logger import logger from langflow.schema import Data from langflow.services.deps import get_settings_service +from langflow.services.utils import initialize_settings_service from langflow.template.frontend_node.constants import FORCE_SHOW_FIELDS from langflow.utils import constants @@ -416,8 +417,6 @@ async def update_settings( max_file_size_upload: int = 100, ) -> None: """Update the settings from a config file.""" - from langflow.services.utils import initialize_settings_service - # Check for database_url in the environment variables initialize_settings_service() diff --git a/src/backend/base/langflow/utils/util_strings.py b/src/backend/base/langflow/utils/util_strings.py index 13f4f3870..47503f784 100644 --- a/src/backend/base/langflow/utils/util_strings.py +++ b/src/backend/base/langflow/utils/util_strings.py @@ -4,7 +4,11 @@ from langflow.utils import constants def truncate_long_strings(data, max_length=None): - """Recursively traverse the dictionary or list and truncate strings longer than max_length.""" + """Recursively traverse the dictionary or list and truncate strings longer than max_length. + + Returns: + The data with strings truncated if they exceed the max length. + """ if max_length is None: max_length = constants.MAX_TEXT_LENGTH diff --git a/src/backend/base/langflow/utils/validate.py b/src/backend/base/langflow/utils/validate.py index 40c029385..86dec60ab 100644 --- a/src/backend/base/langflow/utils/validate.py +++ b/src/backend/base/langflow/utils/validate.py @@ -170,9 +170,15 @@ def create_function(code, function_name): def create_class(code, class_name): """Dynamically create a class from a string of code and a specified class name. - :param code: String containing the Python code defining the class - :param class_name: Name of the class to be created - :return: A function that, when called, returns an instance of the created class + Args: + code: String containing the Python code defining the class + class_name: Name of the class to be created + + Returns: + A function that, when called, returns an instance of the created class + + Raises: + ValueError: If the code contains syntax errors or the class definition is invalid """ if not hasattr(ast, "TypeIgnore"): ast.TypeIgnore = create_type_ignore_class() @@ -199,7 +205,8 @@ def create_class(code, class_name): def create_type_ignore_class(): """Create a TypeIgnore class for AST module if it doesn't exist. - :return: TypeIgnore class + Returns: + TypeIgnore class """ class TypeIgnore(ast.AST): @@ -211,8 +218,15 @@ def create_type_ignore_class(): def prepare_global_scope(code, module): """Prepares the global scope with necessary imports from the provided code module. - :param module: AST parsed module - :return: Dictionary representing the global scope with imported modules + Args: + code: The Python code + module: AST parsed module + + Returns: + Dictionary representing the global scope with imported modules + + Raises: + ModuleNotFoundError: If a module is not found in the code """ exec_globals = globals().copy() exec_globals.update(get_default_imports(code)) @@ -250,9 +264,12 @@ def prepare_global_scope(code, module): def extract_class_code(module, class_name): """Extracts the AST node for the specified class from the module. - :param module: AST parsed module - :param class_name: Name of the class to extract - :return: AST node of the specified class + Args: + module: AST parsed module + class_name: Name of the class to extract + + Returns: + AST node of the specified class """ class_code = next(node for node in module.body if isinstance(node, ast.ClassDef) and node.name == class_name) @@ -263,8 +280,11 @@ def extract_class_code(module, class_name): def compile_class_code(class_code): """Compiles the AST node of a class into a code object. - :param class_code: AST node of the class - :return: Compiled code object of the class + Args: + class_code: AST node of the class + + Returns: + Compiled code object of the class """ return compile(ast.Module(body=[class_code], type_ignores=[]), "", "exec") @@ -272,10 +292,13 @@ def compile_class_code(class_code): def build_class_constructor(compiled_class, exec_globals, class_name): """Builds a constructor function for the dynamically created class. - :param compiled_class: Compiled code object of the class - :param exec_globals: Global scope with necessary imports - :param class_name: Name of the class - :return: Constructor function for the class + Args: + compiled_class: Compiled code object of the class + exec_globals: Global scope with necessary imports + class_name: Name of the class + + Returns: + Constructor function for the class """ exec(compiled_class, exec_globals, locals()) exec_globals[class_name] = locals()[class_name] @@ -313,9 +336,12 @@ def get_default_imports(code_string): def find_names_in_code(code, names): """Finds if any of the specified names are present in the given code string. - :param code: The source code as a string. - :param names: A list of names to check for in the code. - :return: A set of names that are found in the code. + Args: + code: The source code as a string. + names: A list of names to check for in the code. + + Returns: + A set of names that are found in the code. """ return {name for name in names if name in code} @@ -339,7 +365,7 @@ def extract_class_name(code: str) -> str: str: Name of the first Component subclass found Raises: - ValueError: If no Component subclass is found in the code + TypeError: If no Component subclass is found in the code """ try: module = ast.parse(code) diff --git a/src/backend/base/langflow/utils/version.py b/src/backend/base/langflow/utils/version.py index 817247dda..0ef79548d 100644 --- a/src/backend/base/langflow/utils/version.py +++ b/src/backend/base/langflow/utils/version.py @@ -1,4 +1,7 @@ +from importlib import metadata + import httpx +from packaging import version as pkg_version from langflow.logging.logger import logger @@ -22,8 +25,6 @@ def _get_version_info(): Raises: ValueError: If the package is not found from the list of package names. """ - from importlib import metadata - package_options = [ ("langflow", "Langflow"), ("langflow-base", "Langflow Base"), @@ -57,8 +58,9 @@ VERSION_INFO = _get_version_info() def is_pre_release(v: str) -> bool: """Whether the version is a pre-release version. - Returns a boolean indicating whether the version is a pre-release version, - as per the definition of a pre-release segment from PEP 440. + Returns: + Whether the version is a pre-release version, + as per the definition of a pre-release segment from PEP 440. """ return any(label in v for label in ["a", "b", "rc"]) @@ -66,15 +68,14 @@ def is_pre_release(v: str) -> bool: def is_nightly(v: str) -> bool: """Whether the version is a dev (nightly) version. - Returns a boolean indicating whether the version is a dev (nightly) version, - as per the definition of a dev segment from PEP 440. + Returns: + Whether the version is a dev (nightly) version, + as per the definition of a dev segment from PEP 440. """ return "dev" in v def fetch_latest_version(package_name: str, *, include_prerelease: bool) -> str | None: - from packaging import version as pkg_version - package_name = package_name.replace(" ", "-").lower() try: response = httpx.get(f"https://pypi.org/pypi/{package_name}/json") diff --git a/src/backend/base/langflow/worker.py b/src/backend/base/langflow/worker.py index e79f7146b..439733063 100644 --- a/src/backend/base/langflow/worker.py +++ b/src/backend/base/langflow/worker.py @@ -18,7 +18,11 @@ def test_celery(word: str) -> str: @celery_app.task(bind=True, soft_time_limit=30, max_retries=3) def build_vertex(self, vertex: Vertex) -> Vertex: - """Build a vertex.""" + """Build a vertex. + + Returns: + The built vertex. + """ try: vertex.task_id = self.request.id async_to_sync(vertex.build)() diff --git a/src/backend/langflow/version/version.py b/src/backend/langflow/version/version.py index d73dfc5e8..9d14fdc33 100644 --- a/src/backend/langflow/version/version.py +++ b/src/backend/langflow/version/version.py @@ -1,6 +1,7 @@ """Module for package versioning.""" import contextlib +from importlib import metadata def get_version() -> str: @@ -14,8 +15,6 @@ def get_version() -> str: Raises: ValueError: If the package is not found from the list of package names. """ - from importlib import metadata - pkg_names = [ "langflow", "langflow-base", diff --git a/src/backend/tests/conftest.py b/src/backend/tests/conftest.py index b854d6820..faa9373e9 100644 --- a/src/backend/tests/conftest.py +++ b/src/backend/tests/conftest.py @@ -17,8 +17,10 @@ from blockbuster import blockbuster_ctx from dotenv import load_dotenv from fastapi.testclient import TestClient from httpx import ASGITransport, AsyncClient +from langflow.components.inputs import ChatInput from langflow.graph import Graph from langflow.initial_setup.constants import STARTER_FOLDER_NAME +from langflow.main import create_app from langflow.services.auth.utils import get_password_hash from langflow.services.database.models.api_key.model import ApiKey from langflow.services.database.models.flow.model import Flow, FlowCreate @@ -155,8 +157,6 @@ def caplog(caplog: pytest.LogCaptureFixture): @pytest.fixture async def async_client() -> AsyncGenerator: - from langflow.main import create_app - app = create_app() async with AsyncClient(app=app, base_url="http://testserver", http2=True) as client: yield client @@ -228,8 +228,6 @@ def distributed_client_fixture( # def get_session_override(): # return session - from langflow.main import create_app - app = create_app() # app.dependency_overrides[get_session] = get_session_override @@ -357,8 +355,6 @@ async def client_fixture( monkeypatch.setenv("LANGFLOW_LOAD_FLOWS_PATH", load_flows_dir) monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "true") - from langflow.main import create_app - app = create_app() db_service = get_db_service() db_service.database_url = f"sqlite:///{db_path}" @@ -482,8 +478,6 @@ async def flow( json_flow: str, active_user, ): - from langflow.services.database.models.flow.model import FlowCreate - loaded_json = json.loads(json_flow) flow_data = FlowCreate(name="test_flow", data=loaded_json.get("data"), user_id=active_user.id) @@ -577,8 +571,6 @@ async def added_webhook_test(client, json_webhook_test, logged_in_headers): @pytest.fixture async def flow_component(client: AsyncClient, logged_in_headers): - from langflow.components.inputs import ChatInput - chat_input = ChatInput() graph = Graph(start=chat_input, end=chat_input) graph_dict = graph.dump(name="Chat Input Component") diff --git a/src/backend/tests/integration/components/assistants/test_assistants_components.py b/src/backend/tests/integration/components/assistants/test_assistants_components.py index d808d3de6..48ea33503 100644 --- a/src/backend/tests/integration/components/assistants/test_assistants_components.py +++ b/src/backend/tests/integration/components/assistants/test_assistants_components.py @@ -1,12 +1,17 @@ import pytest +from langflow.components.astra_assistants import ( + AssistantsCreateAssistant, + AssistantsCreateThread, + AssistantsGetAssistantName, + AssistantsListAssistants, + AssistantsRun, +) from tests.integration.utils import run_single_component @pytest.mark.api_key_required async def test_list_assistants(): - from langflow.components.astra_assistants import AssistantsListAssistants - results = await run_single_component( AssistantsListAssistants, inputs={}, @@ -16,8 +21,6 @@ async def test_list_assistants(): @pytest.mark.api_key_required async def test_create_assistants(): - from langflow.components.astra_assistants import AssistantsCreateAssistant - results = await run_single_component( AssistantsCreateAssistant, inputs={ @@ -36,8 +39,6 @@ async def test_create_assistants(): @pytest.mark.api_key_required async def test_create_thread(): - from langflow.components.astra_assistants import AssistantsCreateThread - results = await run_single_component( AssistantsCreateThread, inputs={}, @@ -48,8 +49,6 @@ async def test_create_thread(): async def get_assistant_name(assistant_id): - from langflow.components.astra_assistants import AssistantsGetAssistantName - results = await run_single_component( AssistantsGetAssistantName, inputs={ @@ -60,8 +59,6 @@ async def get_assistant_name(assistant_id): async def run_assistant(assistant_id, thread_id): - from langflow.components.astra_assistants import AssistantsRun - results = await run_single_component( AssistantsRun, inputs={ diff --git a/src/backend/tests/integration/components/astra/test_astra_component.py b/src/backend/tests/integration/components/astra/test_astra_component.py index a7622631a..9c768bda2 100644 --- a/src/backend/tests/integration/components/astra/test_astra_component.py +++ b/src/backend/tests/integration/components/astra/test_astra_component.py @@ -2,6 +2,7 @@ import os import pytest from astrapy.db import AstraDB +from langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions from langchain_core.documents import Document from langflow.components.embeddings import OpenAIEmbeddingsComponent from langflow.components.vectorstores import AstraDBVectorStoreComponent @@ -37,8 +38,6 @@ def astradb_client(): @pytest.mark.api_key_required async def test_base(astradb_client: AstraDB): - from langflow.components.embeddings import OpenAIEmbeddingsComponent - application_token = get_astradb_application_token() api_endpoint = get_astradb_api_endpoint() @@ -88,8 +87,6 @@ async def test_astra_embeds_and_search(): @pytest.mark.api_key_required def test_astra_vectorize(): - from langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions - application_token = get_astradb_application_token() api_endpoint = get_astradb_api_endpoint() @@ -132,8 +129,6 @@ def test_astra_vectorize(): @pytest.mark.api_key_required def test_astra_vectorize_with_provider_api_key(): """Tests vectorize using an openai api key.""" - from langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions - application_token = get_astradb_application_token() api_endpoint = get_astradb_api_endpoint() @@ -189,8 +184,6 @@ def test_astra_vectorize_with_provider_api_key(): @pytest.mark.api_key_required def test_astra_vectorize_passes_authentication(): """Tests vectorize using the authentication parameter.""" - from langchain_astradb import AstraDBVectorStore, CollectionVectorServiceOptions - store = None try: application_token = get_astradb_application_token() diff --git a/src/backend/tests/integration/components/output_parsers/test_output_parser.py b/src/backend/tests/integration/components/output_parsers/test_output_parser.py index 57322a7da..17d4d48b2 100644 --- a/src/backend/tests/integration/components/output_parsers/test_output_parser.py +++ b/src/backend/tests/integration/components/output_parsers/test_output_parser.py @@ -10,7 +10,7 @@ from tests.integration.utils import ComponentInputHandle, run_single_component @pytest.mark.api_key_required async def test_csv_output_parser_openai(): - format_instructions = ComponentInputHandle( + format_instructions_ = ComponentInputHandle( clazz=OutputParserComponent, inputs={}, output_name="format_instructions", @@ -24,7 +24,7 @@ async def test_csv_output_parser_openai(): clazz=PromptComponent, inputs={ "template": "List the first five positive integers.\n\n{format_instructions}", - "format_instructions": format_instructions, + "format_instructions": format_instructions_, }, output_name="prompt", ) diff --git a/src/backend/tests/performance/test_server_init.py b/src/backend/tests/performance/test_server_init.py index a97f4f489..858e9c8c6 100644 --- a/src/backend/tests/performance/test_server_init.py +++ b/src/backend/tests/performance/test_server_init.py @@ -28,7 +28,7 @@ async def test_initialize_services(): @pytest.mark.benchmark -async def test_setup_llm_caching(): +def test_setup_llm_caching(): """Benchmark LLM caching setup.""" from langflow.interface.utils import setup_llm_caching diff --git a/src/backend/tests/unit/api/v1/test_files.py b/src/backend/tests/unit/api/v1/test_files.py index 05a67b206..10ac4e345 100644 --- a/src/backend/tests/unit/api/v1/test_files.py +++ b/src/backend/tests/unit/api/v1/test_files.py @@ -11,6 +11,7 @@ import anyio import pytest from asgi_lifespan import LifespanManager from httpx import ASGITransport, AsyncClient +from langflow.main import create_app from langflow.services.deps import get_storage_service from langflow.services.storage.service import StorageService from sqlmodel import Session @@ -60,8 +61,6 @@ async def files_client_fixture( monkeypatch.setenv("LANGFLOW_LOAD_FLOWS_PATH", load_flows_dir) monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "true") - from langflow.main import create_app - app = create_app() return app, db_path @@ -81,14 +80,14 @@ async def files_client_fixture( @pytest.fixture -async def max_file_size_upload_fixture(monkeypatch): +def max_file_size_upload_fixture(monkeypatch): monkeypatch.setenv("LANGFLOW_MAX_FILE_SIZE_UPLOAD", "1") yield monkeypatch.undo() @pytest.fixture -async def max_file_size_upload_10mb_fixture(monkeypatch): +def max_file_size_upload_10mb_fixture(monkeypatch): monkeypatch.setenv("LANGFLOW_MAX_FILE_SIZE_UPLOAD", "10") yield monkeypatch.undo() diff --git a/src/backend/tests/unit/base/tools/test_component_toolkit.py b/src/backend/tests/unit/base/tools/test_component_toolkit.py index 41cf6ca46..07a67e797 100644 --- a/src/backend/tests/unit/base/tools/test_component_toolkit.py +++ b/src/backend/tests/unit/base/tools/test_component_toolkit.py @@ -11,7 +11,7 @@ from langflow.schema.data import Data from pydantic import BaseModel -async def test_component_tool(): +def test_component_tool(): calculator_component = CalculatorToolComponent() component_toolkit = ComponentToolkit(component=calculator_component) component_tool = component_toolkit.get_tools()[0] diff --git a/src/backend/tests/unit/base/tools/test_toolmodemixin.py b/src/backend/tests/unit/base/tools/test_toolmodemixin.py index 13c2081b5..8e68ef6af 100644 --- a/src/backend/tests/unit/base/tools/test_toolmodemixin.py +++ b/src/backend/tests/unit/base/tools/test_toolmodemixin.py @@ -118,7 +118,7 @@ class AllInputsComponent(Component): return data -async def test_component_inputs_toolkit(): +def test_component_inputs_toolkit(): component = AllInputsComponent() component_toolkit = ComponentToolkit(component=component) component_tool = component_toolkit.get_tools()[0] diff --git a/src/backend/tests/unit/components/helpers/test_structured_output_component.py b/src/backend/tests/unit/components/helpers/test_structured_output_component.py index adebc6a43..30af49ea7 100644 --- a/src/backend/tests/unit/components/helpers/test_structured_output_component.py +++ b/src/backend/tests/unit/components/helpers/test_structured_output_component.py @@ -1,8 +1,11 @@ +import re from unittest.mock import MagicMock, patch import pytest from langchain_core.language_models import BaseLanguageModel from langflow.components.helpers.structured_output import StructuredOutputComponent +from langflow.helpers.base_model import build_model_from_schema +from langflow.inputs.inputs import TableInput from langflow.schema.data import Data from pydantic import BaseModel from typing_extensions import override @@ -12,8 +15,6 @@ class TestStructuredOutputComponent: # Ensure that the structured output is successfully generated with the correct BaseModel instance returned by # the mock function def test_successful_structured_output_generation_with_patch_with_config(self): - from unittest.mock import patch - class MockLanguageModel(BaseLanguageModel): @override def with_structured_output(self, *args, **kwargs): @@ -87,15 +88,11 @@ class TestStructuredOutputComponent: multiple=False, ) - with pytest.raises(TypeError, match="Language model does not support structured output."): + with pytest.raises(TypeError, match=re.escape("Language model does not support structured output.")): component.build_structured_output() # Correctly builds the output model from the provided schema def test_correctly_builds_output_model(self): - # Import internal organization modules, packages, and libraries - from langflow.helpers.base_model import build_model_from_schema - from langflow.inputs.inputs import TableInput - # Setup component = StructuredOutputComponent() schema = [ @@ -134,10 +131,6 @@ class TestStructuredOutputComponent: # Properly handles multiple outputs when 'multiple' is set to True def test_handles_multiple_outputs(self): - # Import internal organization modules, packages, and libraries - from langflow.helpers.base_model import build_model_from_schema - from langflow.inputs.inputs import TableInput - # Setup component = StructuredOutputComponent() schema = [ @@ -261,5 +254,5 @@ class TestStructuredOutputComponent: multiple=False, ) - with pytest.raises(TypeError, match="Language model does not support structured output."): + with pytest.raises(TypeError, match=re.escape("Language model does not support structured output.")): component.build_structured_output() diff --git a/src/backend/tests/unit/components/processing/test_dataframe_operations.py b/src/backend/tests/unit/components/processing/test_dataframe_operations.py index df8fdab22..b706598b9 100644 --- a/src/backend/tests/unit/components/processing/test_dataframe_operations.py +++ b/src/backend/tests/unit/components/processing/test_dataframe_operations.py @@ -44,7 +44,7 @@ def test_operations(sample_dataframe, operation, expected_columns, expected_valu component.new_column_name = "Z" elif operation == "Select Columns": component.columns_to_select = ["A", "C"] - elif operation in ("Head", "Tail"): + elif operation in {"Head", "Tail"}: component.num_rows = 1 elif operation == "Replace Value": component.column_name = "C" diff --git a/src/backend/tests/unit/components/prototypes/test_create_data_component.py b/src/backend/tests/unit/components/prototypes/test_create_data_component.py index 22fb8a23d..edfe222e4 100644 --- a/src/backend/tests/unit/components/prototypes/test_create_data_component.py +++ b/src/backend/tests/unit/components/prototypes/test_create_data_component.py @@ -1,3 +1,5 @@ +import re + import pytest from langflow.components.processing import CreateDataComponent from langflow.schema import Data @@ -48,7 +50,7 @@ def test_update_build_config_exceed_limit(create_data_component): "value": False, }, } - with pytest.raises(ValueError, match="Number of fields cannot exceed 15."): + with pytest.raises(ValueError, match=re.escape("Number of fields cannot exceed 15.")): create_data_component.update_build_config(build_config, 16, "number_of_fields") diff --git a/src/backend/tests/unit/components/prototypes/test_update_data_component.py b/src/backend/tests/unit/components/prototypes/test_update_data_component.py index 747b37569..5cd7d25de 100644 --- a/src/backend/tests/unit/components/prototypes/test_update_data_component.py +++ b/src/backend/tests/unit/components/prototypes/test_update_data_component.py @@ -1,3 +1,5 @@ +import re + import pytest from langflow.components.processing import UpdateDataComponent from langflow.schema import Data @@ -48,7 +50,7 @@ def test_update_build_config_exceed_limit(update_data_component): "value": False, }, } - with pytest.raises(ValueError, match="Number of fields cannot exceed 15."): + with pytest.raises(ValueError, match=re.escape("Number of fields cannot exceed 15.")): update_data_component.update_build_config(build_config, 16, "number_of_fields") diff --git a/src/backend/tests/unit/custom/custom_component/test_component_events.py b/src/backend/tests/unit/custom/custom_component/test_component_events.py index 6947efa70..ec6eaf745 100644 --- a/src/backend/tests/unit/custom/custom_component/test_component_events.py +++ b/src/backend/tests/unit/custom/custom_component/test_component_events.py @@ -14,11 +14,6 @@ from langflow.schema.properties import Properties, Source from langflow.template.field.base import Output -async def create_event_queue(): - """Create a queue for testing events.""" - return asyncio.Queue() - - def blocking_cb(manager, event_type, data): time.sleep(0.01) manager.send_event(event_type=event_type, data=data) @@ -43,7 +38,7 @@ class ComponentForTesting(Component): async def test_component_message_sending(): """Test component's message sending functionality.""" # Create event queue and manager - queue = await create_event_queue() + queue = asyncio.Queue() event_manager = EventManager(queue) event_manager.register_event("on_message", "message", callback=blocking_cb) @@ -75,7 +70,7 @@ async def test_component_message_sending(): async def test_component_tool_output(): """Test component's tool output functionality.""" # Create event queue and manager - queue = await create_event_queue() + queue = asyncio.Queue() event_manager = EventManager(queue) # Create component @@ -110,7 +105,7 @@ async def test_component_tool_output(): async def test_component_error_handling(): """Test component's error handling.""" # Create event queue and manager - queue = await create_event_queue() + queue = asyncio.Queue() event_manager = EventManager(queue) # Create component @@ -141,7 +136,7 @@ async def test_component_error_handling(): async def test_component_build_results(): """Test component's build_results functionality.""" # Create event queue and manager - queue = await create_event_queue() + queue = asyncio.Queue() event_manager = EventManager(queue) # Create component @@ -173,7 +168,7 @@ async def test_component_build_results(): async def test_component_logging(): """Test component's logging functionality.""" # Create event queue and manager - queue = await create_event_queue() + queue = asyncio.Queue() event_manager = EventManager(queue) # Create component @@ -207,7 +202,7 @@ async def test_component_logging(): @pytest.mark.usefixtures("client") async def test_component_streaming_message(): """Test component's streaming message functionality.""" - queue = await create_event_queue() + queue = asyncio.Queue() event_manager = EventManager(queue) event_manager.register_event("on_token", "token", blocking_cb) diff --git a/src/backend/tests/unit/events/test_event_manager.py b/src/backend/tests/unit/events/test_event_manager.py index 8da06b6f5..7fca8092d 100644 --- a/src/backend/tests/unit/events/test_event_manager.py +++ b/src/backend/tests/unit/events/test_event_manager.py @@ -40,7 +40,7 @@ class TestEventManager: def test_accessing_non_registered_event_callback_with_recommended_fix(self): queue = asyncio.Queue() manager = EventManager(queue) - result = manager.__getattr__("non_registered_event") + result = manager.non_registered_event assert result == manager.noop # Accessing a registered event callback via __getattr__ @@ -130,8 +130,6 @@ class TestEventManager: assert len(manager.events) == 1000 # Verifying the uniqueness of event IDs for each event triggered using await with asyncio decorator - import pytest - async def test_event_id_uniqueness_with_await(self): queue = asyncio.Queue() manager = EventManager(queue) diff --git a/src/backend/tests/unit/exceptions/test_api.py b/src/backend/tests/unit/exceptions/test_api.py index 9934eb7ce..0a27250b8 100644 --- a/src/backend/tests/unit/exceptions/test_api.py +++ b/src/backend/tests/unit/exceptions/test_api.py @@ -1,11 +1,10 @@ from unittest.mock import Mock, patch +from langflow.exceptions.api import APIException, ExceptionBody from langflow.services.database.models.flow.model import Flow def test_api_exception(): - from langflow.exceptions.api import APIException, ExceptionBody - mock_exception = Exception("Test exception") mock_flow = Mock(spec=Flow) mock_outdated_components = ["component1", "component2"] @@ -45,8 +44,6 @@ def test_api_exception(): def test_api_exception_no_flow(): - from langflow.exceptions.api import APIException, ExceptionBody - # Mock data mock_exception = Exception("Test exception") diff --git a/src/backend/tests/unit/graph/edge/test_edge_base.py b/src/backend/tests/unit/graph/edge/test_edge_base.py index a3e212c07..6450a9a9e 100644 --- a/src/backend/tests/unit/graph/edge/test_edge_base.py +++ b/src/backend/tests/unit/graph/edge/test_edge_base.py @@ -1,3 +1,5 @@ +import re + import pytest from langflow.components.inputs import ChatInput from langflow.components.models import OpenAIModelComponent @@ -25,5 +27,7 @@ Answer: chat_output = ChatOutput() chat_output.set(input_value=openai_component.text_response) - with pytest.raises(ValueError, match="Component OpenAI field 'input_values' might not be a valid input."): + with pytest.raises( + ValueError, match=re.escape("Component OpenAI field 'input_values' might not be a valid input.") + ): Graph(start=chat_input, end=chat_output) diff --git a/src/backend/tests/unit/graph/graph/test_base.py b/src/backend/tests/unit/graph/graph/test_base.py index 3e03f3f30..ad051c2ed 100644 --- a/src/backend/tests/unit/graph/graph/test_base.py +++ b/src/backend/tests/unit/graph/graph/test_base.py @@ -20,7 +20,7 @@ async def test_graph_not_prepared(): await graph.astep() -async def test_graph(caplog: pytest.LogCaptureFixture): +def test_graph(caplog: pytest.LogCaptureFixture): chat_input = ChatInput() chat_output = ChatOutput() graph = Graph() diff --git a/src/backend/tests/unit/io/test_io_schema.py b/src/backend/tests/unit/io/test_io_schema.py index 0c7497a1b..74c3a8780 100644 --- a/src/backend/tests/unit/io/test_io_schema.py +++ b/src/backend/tests/unit/io/test_io_schema.py @@ -2,14 +2,14 @@ from typing import TYPE_CHECKING, Literal import pytest from langflow.components.inputs import ChatInput +from langflow.inputs.inputs import DropdownInput, FileInput, IntInput, NestedDictInput, StrInput +from langflow.io.schema import create_input_schema if TYPE_CHECKING: from pydantic.fields import FieldInfo def test_create_input_schema(): - from langflow.io.schema import create_input_schema - schema = create_input_schema(ChatInput.inputs) assert schema.__name__ == "InputSchema" @@ -17,9 +17,6 @@ def test_create_input_schema(): class TestCreateInputSchema: # Single input type is converted to list and processed correctly def test_single_input_type_conversion(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field") schema = create_input_schema([input_instance]) assert schema.__name__ == "InputSchema" @@ -27,9 +24,6 @@ class TestCreateInputSchema: # Multiple input types are processed and included in the schema def test_multiple_input_types(self): - from langflow.inputs.inputs import IntInput, StrInput - from langflow.io.schema import create_input_schema - inputs = [StrInput(name="str_field"), IntInput(name="int_field")] schema = create_input_schema(inputs) assert schema.__name__ == "InputSchema" @@ -38,9 +32,6 @@ class TestCreateInputSchema: # Fields are correctly created with appropriate types and attributes def test_fields_creation_with_correct_types_and_attributes(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field", info="Test Info", required=True) schema = create_input_schema([input_instance]) field_info = schema.model_fields["test_field"] @@ -49,18 +40,12 @@ class TestCreateInputSchema: # Schema model is created and returned successfully def test_schema_model_creation(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field") schema = create_input_schema([input_instance]) assert schema.__name__ == "InputSchema" # Default values are correctly assigned to fields def test_default_values_assignment(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field", value="default_value") schema = create_input_schema([input_instance]) field_info = schema.model_fields["test_field"] @@ -68,16 +53,11 @@ class TestCreateInputSchema: # Empty list of inputs is handled without errors def test_empty_list_of_inputs(self): - from langflow.io.schema import create_input_schema - schema = create_input_schema([]) assert schema.__name__ == "InputSchema" # Input with missing optional attributes (e.g., display_name, info) is processed correctly def test_missing_optional_attributes(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field") schema = create_input_schema([input_instance]) field_info = schema.model_fields["test_field"] @@ -86,9 +66,6 @@ class TestCreateInputSchema: # Input with is_list attribute set to True is processed correctly def test_is_list_attribute_processing(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field", is_list=True) schema = create_input_schema([input_instance]) field_info: FieldInfo = schema.model_fields["test_field"] @@ -96,9 +73,6 @@ class TestCreateInputSchema: # Input with options attribute is processed correctly def test_options_attribute_processing(self): - from langflow.inputs.inputs import DropdownInput - from langflow.io.schema import create_input_schema - input_instance = DropdownInput(name="test_field", options=["option1", "option2"]) schema = create_input_schema([input_instance]) field_info = schema.model_fields["test_field"] @@ -106,9 +80,6 @@ class TestCreateInputSchema: # Non-standard field types are handled correctly def test_non_standard_field_types_handling(self): - from langflow.inputs.inputs import FileInput - from langflow.io.schema import create_input_schema - input_instance = FileInput(name="file_field") schema = create_input_schema([input_instance]) field_info = schema.model_fields["file_field"] @@ -116,9 +87,6 @@ class TestCreateInputSchema: # Inputs with mixed required and optional fields are processed correctly def test_mixed_required_optional_fields_processing(self): - from langflow.inputs.inputs import IntInput, StrInput - from langflow.io.schema import create_input_schema - inputs = [ StrInput(name="required_field", required=True), IntInput(name="optional_field", required=False), @@ -132,9 +100,6 @@ class TestCreateInputSchema: # Inputs with complex nested structures are handled correctly def test_complex_nested_structures_handling(self): - from langflow.inputs.inputs import NestedDictInput - from langflow.io.schema import create_input_schema - nested_input = NestedDictInput(name="nested_field", value={"key": "value"}) schema = create_input_schema([nested_input]) @@ -145,9 +110,6 @@ class TestCreateInputSchema: # Creating a schema from a single input type def test_single_input_type_replica(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field") schema = create_input_schema([input_instance]) assert schema.__name__ == "InputSchema" @@ -155,18 +117,12 @@ class TestCreateInputSchema: # Creating a schema from a list of input types def test_passing_input_type_directly(self): - from langflow.inputs.inputs import IntInput, StrInput - from langflow.io.schema import create_input_schema - inputs = StrInput(name="str_field"), IntInput(name="int_field") with pytest.raises(TypeError): create_input_schema(inputs) # Handling input types with options correctly def test_options_handling(self): - from langflow.inputs.inputs import DropdownInput - from langflow.io.schema import create_input_schema - input_instance = DropdownInput(name="test_field", options=["option1", "option2"]) schema = create_input_schema([input_instance]) field_info = schema.model_fields["test_field"] @@ -174,9 +130,6 @@ class TestCreateInputSchema: # Handling input types with is_list attribute correctly def test_is_list_handling(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field", is_list=True) schema = create_input_schema([input_instance]) field_info = schema.model_fields["test_field"] @@ -184,9 +137,6 @@ class TestCreateInputSchema: # Converting FieldTypes to corresponding Python types def test_field_types_conversion(self): - from langflow.inputs.inputs import IntInput - from langflow.io.schema import create_input_schema - input_instance = IntInput(name="int_field") schema = create_input_schema([input_instance]) field_info = schema.model_fields["int_field"] @@ -194,9 +144,6 @@ class TestCreateInputSchema: # Setting default values for non-required fields def test_default_values_for_non_required_fields(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field", value="default_value") schema = create_input_schema([input_instance]) field_info = schema.model_fields["test_field"] @@ -204,9 +151,6 @@ class TestCreateInputSchema: # Handling input types with missing attributes def test_missing_attributes_handling(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field") schema = create_input_schema([input_instance]) field_info = schema.model_fields["test_field"] @@ -217,9 +161,6 @@ class TestCreateInputSchema: # Handling input types with None as default value def test_none_default_value_handling(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test_field", value=None) schema = create_input_schema([input_instance]) field_info = schema.model_fields["test_field"] @@ -227,9 +168,6 @@ class TestCreateInputSchema: # Handling input types with special characters in names def test_special_characters_in_names_handling(self): - from langflow.inputs.inputs import StrInput - from langflow.io.schema import create_input_schema - input_instance = StrInput(name="test@field#name") schema = create_input_schema([input_instance]) assert "test@field#name" in schema.model_fields diff --git a/src/backend/tests/unit/schema/test_schema_data.py b/src/backend/tests/unit/schema/test_schema_data.py index 6eff0ac68..5d7554e60 100644 --- a/src/backend/tests/unit/schema/test_schema_data.py +++ b/src/backend/tests/unit/schema/test_schema_data.py @@ -1,3 +1,5 @@ +import base64 + import pytest from langchain_core.messages import AIMessage, HumanMessage from langflow.schema.data import Data @@ -9,8 +11,6 @@ def sample_image(tmp_path): """Create a sample image file for testing.""" image_path = tmp_path / "test_image.png" # Create a small black 1x1 pixel PNG file - import base64 - image_content = base64.b64decode( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" ) diff --git a/src/backend/tests/unit/schema/test_schema_message.py b/src/backend/tests/unit/schema/test_schema_message.py index 94d181cb9..b8b2b40dd 100644 --- a/src/backend/tests/unit/schema/test_schema_message.py +++ b/src/backend/tests/unit/schema/test_schema_message.py @@ -1,3 +1,4 @@ +import base64 import shutil from datetime import datetime, timezone from pathlib import Path @@ -29,8 +30,6 @@ def sample_image(langflow_cache_dir): # Create the image in the flow directory image_path = flow_dir / "test_image.png" # Create a small black 1x1 pixel PNG file - import base64 - image_content = base64.b64decode( "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACklEQVR4nGMAAQAABQABDQottAAAAABJRU5ErkJggg==" ) diff --git a/src/backend/tests/unit/test_data_class.py b/src/backend/tests/unit/test_data_class.py index 83ee12bd0..9af22f7dd 100644 --- a/src/backend/tests/unit/test_data_class.py +++ b/src/backend/tests/unit/test_data_class.py @@ -1,3 +1,5 @@ +import copy + import pytest from langchain_core.documents import Document from langflow.schema import Data @@ -62,8 +64,6 @@ def test_custom_attribute_get_set_del(): def test_deep_copy(): - import copy - record1 = Data(data={"text": "Hello", "number": 10}) record2 = copy.deepcopy(record1) assert record2.text == "Hello" diff --git a/src/backend/tests/unit/test_database.py b/src/backend/tests/unit/test_database.py index adea3a63b..9c2485b1f 100644 --- a/src/backend/tests/unit/test_database.py +++ b/src/backend/tests/unit/test_database.py @@ -13,6 +13,7 @@ from langflow.services.database.models.flow import Flow, FlowCreate, FlowUpdate from langflow.services.database.models.folder.model import FolderCreate from langflow.services.database.utils import session_getter from langflow.services.deps import get_db_service +from sqlalchemy import text @pytest.fixture(scope="module") @@ -619,8 +620,6 @@ async def test_sqlite_pragmas(): db_service = get_db_service() async with db_service.with_session() as session: - from sqlalchemy import text - assert (await session.exec(text("PRAGMA journal_mode;"))).scalar() == "wal" assert (await session.exec(text("PRAGMA synchronous;"))).scalar() == 1 diff --git a/src/backend/tests/unit/test_schema.py b/src/backend/tests/unit/test_schema.py index cce9d8bcd..ea29f61f5 100644 --- a/src/backend/tests/unit/test_schema.py +++ b/src/backend/tests/unit/test_schema.py @@ -64,7 +64,7 @@ class TestInput: # Empty lists and edge cases assert set(post_process_type(list)) == {list} assert set(post_process_type(Union[int, None])) == {int, NoneType} # noqa: UP007 - assert set(post_process_type(Union[None, list[None]])) == {None, NoneType} # noqa: UP007 + assert set(post_process_type(Union[list[None], None])) == {None, NoneType} # noqa: UP007 # Handling complex nested structures assert set(post_process_type(Union[SequenceABC[int | str], list[float]])) == {int, str, float} # noqa: UP007 diff --git a/src/backend/tests/unit/test_telemetry.py b/src/backend/tests/unit/test_telemetry.py index 618493c64..ad56a0cb7 100644 --- a/src/backend/tests/unit/test_telemetry.py +++ b/src/backend/tests/unit/test_telemetry.py @@ -1,3 +1,4 @@ +import re import threading from concurrent.futures import ThreadPoolExecutor, as_completed @@ -48,7 +49,7 @@ def test_increment_counter_empty_label(opentelemetry_instance): def test_increment_counter_missing_mandatory_label(opentelemetry_instance): - with pytest.raises(ValueError, match="Missing required labels: {'flow_id'}"): + with pytest.raises(ValueError, match=re.escape("Missing required labels: {'flow_id'}")): opentelemetry_instance.increment_counter(metric_name="num_files_uploaded", value=5, labels={"service": "one"}) diff --git a/src/backend/tests/unit/utils/test_image_utils.py b/src/backend/tests/unit/utils/test_image_utils.py index f1f38c40b..b8df85825 100644 --- a/src/backend/tests/unit/utils/test_image_utils.py +++ b/src/backend/tests/unit/utils/test_image_utils.py @@ -16,51 +16,57 @@ def sample_image(tmp_path): return image_path -class TestImageUtils: - def test_convert_image_to_base64_success(self, sample_image): - """Test successful conversion of image to base64.""" - base64_str = convert_image_to_base64(sample_image) - assert isinstance(base64_str, str) - # Verify it's valid base64 - assert base64.b64decode(base64_str) +def test_convert_image_to_base64_success(sample_image): + """Test successful conversion of image to base64.""" + base64_str = convert_image_to_base64(sample_image) + assert isinstance(base64_str, str) + # Verify it's valid base64 + assert base64.b64decode(base64_str) - def test_convert_image_to_base64_empty_path(self): - """Test conversion with empty path.""" - with pytest.raises(ValueError, match="Image path cannot be empty"): - convert_image_to_base64("") - def test_convert_image_to_base64_nonexistent_file(self): - """Test conversion with non-existent file.""" - with pytest.raises(FileNotFoundError, match="Image file not found"): - convert_image_to_base64("nonexistent.png") +def test_convert_image_to_base64_empty_path(): + """Test conversion with empty path.""" + with pytest.raises(ValueError, match="Image path cannot be empty"): + convert_image_to_base64("") - def test_convert_image_to_base64_directory(self, tmp_path): - """Test conversion with directory path instead of file.""" - with pytest.raises(ValueError, match="Path is not a file"): - convert_image_to_base64(tmp_path) - def test_create_data_url_success(self, sample_image): - """Test successful creation of data URL.""" - data_url = create_data_url(sample_image) - assert data_url.startswith("data:image/png;base64,") - # Verify the base64 part is valid - base64_part = data_url.split(",")[1] - assert base64.b64decode(base64_part) +def test_convert_image_to_base64_nonexistent_file(): + """Test conversion with non-existent file.""" + with pytest.raises(FileNotFoundError, match="Image file not found"): + convert_image_to_base64("nonexistent.png") - def test_create_data_url_with_custom_mime(self, sample_image): - """Test creation of data URL with custom MIME type.""" - custom_mime = "image/custom" - data_url = create_data_url(sample_image, mime_type=custom_mime) - assert data_url.startswith(f"data:{custom_mime};base64,") - def test_create_data_url_invalid_file(self): - """Test creation of data URL with invalid file.""" - with pytest.raises(FileNotFoundError): - create_data_url("nonexistent.jpg") +def test_convert_image_to_base64_directory(tmp_path): + """Test conversion with directory path instead of file.""" + with pytest.raises(ValueError, match="Path is not a file"): + convert_image_to_base64(tmp_path) - def test_create_data_url_unrecognized_extension(self, tmp_path): - """Test creation of data URL with unrecognized file extension.""" - invalid_file = tmp_path / "test.unknown" - invalid_file.touch() - with pytest.raises(ValueError, match="Could not determine MIME type"): - create_data_url(invalid_file) + +def test_create_data_url_success(sample_image): + """Test successful creation of data URL.""" + data_url = create_data_url(sample_image) + assert data_url.startswith("data:image/png;base64,") + # Verify the base64 part is valid + base64_part = data_url.split(",")[1] + assert base64.b64decode(base64_part) + + +def test_create_data_url_with_custom_mime(sample_image): + """Test creation of data URL with custom MIME type.""" + custom_mime = "image/custom" + data_url = create_data_url(sample_image, mime_type=custom_mime) + assert data_url.startswith(f"data:{custom_mime};base64,") + + +def test_create_data_url_invalid_file(): + """Test creation of data URL with invalid file.""" + with pytest.raises(FileNotFoundError): + create_data_url("nonexistent.jpg") + + +def test_create_data_url_unrecognized_extension(tmp_path): + """Test creation of data URL with unrecognized file extension.""" + invalid_file = tmp_path / "test.unknown" + invalid_file.touch() + with pytest.raises(ValueError, match="Could not determine MIME type"): + create_data_url(invalid_file) diff --git a/src/backend/tests/unit/utils/test_truncate_long_strings.py b/src/backend/tests/unit/utils/test_truncate_long_strings.py index aa7ce3f95..5a08d5744 100644 --- a/src/backend/tests/unit/utils/test_truncate_long_strings.py +++ b/src/backend/tests/unit/utils/test_truncate_long_strings.py @@ -1,6 +1,7 @@ import math import pytest +from langflow.utils.constants import MAX_TEXT_LENGTH from langflow.utils.util_strings import truncate_long_strings @@ -48,8 +49,6 @@ def test_truncate_long_strings_negative_max_length(): # Test for None max_length (should use default MAX_TEXT_LENGTH) def test_truncate_long_strings_none_max_length(): - from langflow.utils.constants import MAX_TEXT_LENGTH - long_string = "a" * (MAX_TEXT_LENGTH + 10) result = truncate_long_strings(long_string, None) assert len(result) == MAX_TEXT_LENGTH + 3 # +3 for "..." diff --git a/uv.lock b/uv.lock index 18d632097..83afb0c25 100644 --- a/uv.lock +++ b/uv.lock @@ -532,7 +532,7 @@ name = "blessed" version = "1.20.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "jinxed", marker = "platform_system == 'Windows'" }, + { name = "jinxed", marker = "sys_platform == 'win32'" }, { name = "six" }, { name = "wcwidth" }, ] @@ -962,7 +962,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -3143,7 +3143,7 @@ name = "ipykernel" version = "6.29.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "appnope", marker = "platform_system == 'Darwin'" }, + { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, { name = "ipython" }, @@ -3234,7 +3234,7 @@ name = "jinxed" version = "1.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ansicon", marker = "platform_system == 'Windows'" }, + { name = "ansicon", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/20/d0/59b2b80e7a52d255f9e0ad040d2e826342d05580c4b1d7d7747cfb8db731/jinxed-1.3.0.tar.gz", hash = "sha256:1593124b18a41b7a3da3b078471442e51dbad3d77b4d4f2b0c26ab6f7d660dbf", size = 80981 } wheels = [ @@ -4198,7 +4198,7 @@ dev = [ { name = "pytest-xdist", specifier = ">=3.6.0" }, { name = "requests", specifier = ">=2.32.0" }, { name = "respx", specifier = ">=0.21.1" }, - { name = "ruff", specifier = ">=0.8.2,<0.9.0" }, + { name = "ruff", specifier = ">=0.8.4,<0.9.0" }, { name = "types-aiofiles", specifier = ">=24.1.0.20240626" }, { name = "types-google-cloud-ndb", specifier = ">=2.2.0.0" }, { name = "types-markdown", specifier = ">=3.7.0.20240822" }, @@ -5126,36 +5126,36 @@ wheels = [ [[package]] name = "mypy" -version = "1.13.0" +version = "1.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mypy-extensions" }, { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e8/21/7e9e523537991d145ab8a0a2fd98548d67646dc2aaaf6091c31ad883e7c1/mypy-1.13.0.tar.gz", hash = "sha256:0291a61b6fbf3e6673e3405cfcc0e7650bebc7939659fdca2702958038bd835e", size = 3152532 } +sdist = { url = "https://files.pythonhosted.org/packages/8c/7b/08046ef9330735f536a09a2e31b00f42bccdb2795dcd979636ba43bb2d63/mypy-1.14.0.tar.gz", hash = "sha256:822dbd184d4a9804df5a7d5335a68cf7662930e70b8c1bc976645d1509f9a9d6", size = 3215684 } wheels = [ - { url = "https://files.pythonhosted.org/packages/5e/8c/206de95a27722b5b5a8c85ba3100467bd86299d92a4f71c6b9aa448bfa2f/mypy-1.13.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6607e0f1dd1fb7f0aca14d936d13fd19eba5e17e1cd2a14f808fa5f8f6d8f60a", size = 11020731 }, - { url = "https://files.pythonhosted.org/packages/ab/bb/b31695a29eea76b1569fd28b4ab141a1adc9842edde080d1e8e1776862c7/mypy-1.13.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8a21be69bd26fa81b1f80a61ee7ab05b076c674d9b18fb56239d72e21d9f4c80", size = 10184276 }, - { url = "https://files.pythonhosted.org/packages/a5/2d/4a23849729bb27934a0e079c9c1aad912167d875c7b070382a408d459651/mypy-1.13.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b2353a44d2179846a096e25691d54d59904559f4232519d420d64da6828a3a7", size = 12587706 }, - { url = "https://files.pythonhosted.org/packages/5c/c3/d318e38ada50255e22e23353a469c791379825240e71b0ad03e76ca07ae6/mypy-1.13.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0730d1c6a2739d4511dc4253f8274cdd140c55c32dfb0a4cf8b7a43f40abfa6f", size = 13105586 }, - { url = "https://files.pythonhosted.org/packages/4a/25/3918bc64952370c3dbdbd8c82c363804678127815febd2925b7273d9482c/mypy-1.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:c5fc54dbb712ff5e5a0fca797e6e0aa25726c7e72c6a5850cfd2adbc1eb0a372", size = 9632318 }, - { url = "https://files.pythonhosted.org/packages/d0/19/de0822609e5b93d02579075248c7aa6ceaddcea92f00bf4ea8e4c22e3598/mypy-1.13.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:581665e6f3a8a9078f28d5502f4c334c0c8d802ef55ea0e7276a6e409bc0d82d", size = 10939027 }, - { url = "https://files.pythonhosted.org/packages/c8/71/6950fcc6ca84179137e4cbf7cf41e6b68b4a339a1f5d3e954f8c34e02d66/mypy-1.13.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3ddb5b9bf82e05cc9a627e84707b528e5c7caaa1c55c69e175abb15a761cec2d", size = 10108699 }, - { url = "https://files.pythonhosted.org/packages/26/50/29d3e7dd166e74dc13d46050b23f7d6d7533acf48f5217663a3719db024e/mypy-1.13.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:20c7ee0bc0d5a9595c46f38beb04201f2620065a93755704e141fcac9f59db2b", size = 12506263 }, - { url = "https://files.pythonhosted.org/packages/3f/1d/676e76f07f7d5ddcd4227af3938a9c9640f293b7d8a44dd4ff41d4db25c1/mypy-1.13.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3790ded76f0b34bc9c8ba4def8f919dd6a46db0f5a6610fb994fe8efdd447f73", size = 12984688 }, - { url = "https://files.pythonhosted.org/packages/9c/03/5a85a30ae5407b1d28fab51bd3e2103e52ad0918d1e68f02a7778669a307/mypy-1.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:51f869f4b6b538229c1d1bcc1dd7d119817206e2bc54e8e374b3dfa202defcca", size = 9626811 }, - { url = "https://files.pythonhosted.org/packages/fb/31/c526a7bd2e5c710ae47717c7a5f53f616db6d9097caf48ad650581e81748/mypy-1.13.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5c7051a3461ae84dfb5dd15eff5094640c61c5f22257c8b766794e6dd85e72d5", size = 11077900 }, - { url = "https://files.pythonhosted.org/packages/83/67/b7419c6b503679d10bd26fc67529bc6a1f7a5f220bbb9f292dc10d33352f/mypy-1.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:39bb21c69a5d6342f4ce526e4584bc5c197fd20a60d14a8624d8743fffb9472e", size = 10074818 }, - { url = "https://files.pythonhosted.org/packages/ba/07/37d67048786ae84e6612575e173d713c9a05d0ae495dde1e68d972207d98/mypy-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:164f28cb9d6367439031f4c81e84d3ccaa1e19232d9d05d37cb0bd880d3f93c2", size = 12589275 }, - { url = "https://files.pythonhosted.org/packages/1f/17/b1018c6bb3e9f1ce3956722b3bf91bff86c1cefccca71cec05eae49d6d41/mypy-1.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a4c1bfcdbce96ff5d96fc9b08e3831acb30dc44ab02671eca5953eadad07d6d0", size = 13037783 }, - { url = "https://files.pythonhosted.org/packages/cb/32/cd540755579e54a88099aee0287086d996f5a24281a673f78a0e14dba150/mypy-1.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:a0affb3a79a256b4183ba09811e3577c5163ed06685e4d4b46429a271ba174d2", size = 9726197 }, - { url = "https://files.pythonhosted.org/packages/11/bb/ab4cfdc562cad80418f077d8be9b4491ee4fb257440da951b85cbb0a639e/mypy-1.13.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a7b44178c9760ce1a43f544e595d35ed61ac2c3de306599fa59b38a6048e1aa7", size = 11069721 }, - { url = "https://files.pythonhosted.org/packages/59/3b/a393b1607cb749ea2c621def5ba8c58308ff05e30d9dbdc7c15028bca111/mypy-1.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5d5092efb8516d08440e36626f0153b5006d4088c1d663d88bf79625af3d1d62", size = 10063996 }, - { url = "https://files.pythonhosted.org/packages/d1/1f/6b76be289a5a521bb1caedc1f08e76ff17ab59061007f201a8a18cc514d1/mypy-1.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de2904956dac40ced10931ac967ae63c5089bd498542194b436eb097a9f77bc8", size = 12584043 }, - { url = "https://files.pythonhosted.org/packages/a6/83/5a85c9a5976c6f96e3a5a7591aa28b4a6ca3a07e9e5ba0cec090c8b596d6/mypy-1.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7bfd8836970d33c2105562650656b6846149374dc8ed77d98424b40b09340ba7", size = 13036996 }, - { url = "https://files.pythonhosted.org/packages/b4/59/c39a6f752f1f893fccbcf1bdd2aca67c79c842402b5283563d006a67cf76/mypy-1.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:9f73dba9ec77acb86457a8fc04b5239822df0c14a082564737833d2963677dbc", size = 9737709 }, - { url = "https://files.pythonhosted.org/packages/3b/86/72ce7f57431d87a7ff17d442f521146a6585019eb8f4f31b7c02801f78ad/mypy-1.13.0-py3-none-any.whl", hash = "sha256:9c250883f9fd81d212e0952c92dbfcc96fc237f4b7c92f56ac81fd48460b3e5a", size = 2647043 }, + { url = "https://files.pythonhosted.org/packages/ef/97/f00ded038482230e0beaaa08f9c5483a54530b362ad1b0d752d5d2b2f211/mypy-1.14.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e971c1c667007f9f2b397ffa80fa8e1e0adccff336e5e77e74cb5f22868bee87", size = 11207956 }, + { url = "https://files.pythonhosted.org/packages/68/67/8b4db0da19c9e3fa6264e948f1c135ab4dd45bede1809f4fdb613dc119f6/mypy-1.14.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e86aaeaa3221a278c66d3d673b297232947d873773d61ca3ee0e28b2ff027179", size = 10363681 }, + { url = "https://files.pythonhosted.org/packages/f5/00/56b1619ff1f3fcad2d411eccda60d74d20e73bda39c218d5ad2769980682/mypy-1.14.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1628c5c3ce823d296e41e2984ff88c5861499041cb416a8809615d0c1f41740e", size = 12832976 }, + { url = "https://files.pythonhosted.org/packages/e7/8b/9247838774b0bd865f190cc221822212091317f16310305ef924d9772532/mypy-1.14.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fadb29b77fc14a0dd81304ed73c828c3e5cde0016c7e668a86a3e0dfc9f3af3", size = 13013704 }, + { url = "https://files.pythonhosted.org/packages/b2/69/0c0868a6f3d9761d2f704d1fb6ef84d75998c27d342738a8b20f109a411f/mypy-1.14.0-cp310-cp310-win_amd64.whl", hash = "sha256:3fa76988dc760da377c1e5069200a50d9eaaccf34f4ea18428a3337034ab5a44", size = 9782230 }, + { url = "https://files.pythonhosted.org/packages/34/c1/b9dd3e955953aec1c728992545b7877c9f6fa742a623ce4c200da0f62540/mypy-1.14.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6e73c8a154eed31db3445fe28f63ad2d97b674b911c00191416cf7f6459fd49a", size = 11121032 }, + { url = "https://files.pythonhosted.org/packages/ee/96/c52d5d516819ab95bf41f4a1ada828a3decc302f8c152ff4fc5feb0e4529/mypy-1.14.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:273e70fcb2e38c5405a188425aa60b984ffdcef65d6c746ea5813024b68c73dc", size = 10286294 }, + { url = "https://files.pythonhosted.org/packages/69/2c/3dbe51877a24daa467f8d8631f9ffd1aabbf0f6d9367a01c44a59df81fe0/mypy-1.14.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1daca283d732943731a6a9f20fdbcaa927f160bc51602b1d4ef880a6fb252015", size = 12746528 }, + { url = "https://files.pythonhosted.org/packages/a1/a8/eb20cde4ba9c4c3e20d958918a7c5d92210f4d1a0200c27de9a641f70996/mypy-1.14.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7e68047bedb04c1c25bba9901ea46ff60d5eaac2d71b1f2161f33107e2b368eb", size = 12883489 }, + { url = "https://files.pythonhosted.org/packages/91/17/a1fc6c70f31d52c99299320cf81c3cb2c6b91ec7269414e0718a6d138e34/mypy-1.14.0-cp311-cp311-win_amd64.whl", hash = "sha256:7a52f26b9c9b1664a60d87675f3bae00b5c7f2806e0c2800545a32c325920bcc", size = 9780113 }, + { url = "https://files.pythonhosted.org/packages/fe/d8/0e72175ee0253217f5c44524f5e95251c02e95ba9749fb87b0e2074d203a/mypy-1.14.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d5326ab70a6db8e856d59ad4cb72741124950cbbf32e7b70e30166ba7bbf61dd", size = 11269011 }, + { url = "https://files.pythonhosted.org/packages/e9/6d/4ea13839dabe5db588dc6a1b766da16f420d33cf118a7b7172cdf6c7fcb2/mypy-1.14.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bf4ec4980bec1e0e24e5075f449d014011527ae0055884c7e3abc6a99cd2c7f1", size = 10253076 }, + { url = "https://files.pythonhosted.org/packages/3e/38/7db2c5d0f4d290e998f7a52b2e2616c7bbad96b8e04278ab09d11978a29e/mypy-1.14.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:390dfb898239c25289495500f12fa73aa7f24a4c6d90ccdc165762462b998d63", size = 12862786 }, + { url = "https://files.pythonhosted.org/packages/bf/4b/62d59c801b34141040989949c2b5c157d0408b45357335d3ec5b2845b0f6/mypy-1.14.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7e026d55ddcd76e29e87865c08cbe2d0104e2b3153a523c529de584759379d3d", size = 12971568 }, + { url = "https://files.pythonhosted.org/packages/f1/9c/e0f281b32d70c87b9e4d2939e302b1ff77ada4d7b0f2fb32890c144bc1d6/mypy-1.14.0-cp312-cp312-win_amd64.whl", hash = "sha256:585ed36031d0b3ee362e5107ef449a8b5dfd4e9c90ccbe36414ee405ee6b32ba", size = 9879477 }, + { url = "https://files.pythonhosted.org/packages/13/33/8380efd0ebdfdfac7fc0bf065f03a049800ca1e6c296ec1afc634340d992/mypy-1.14.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9f6f4c0b27401d14c483c622bc5105eff3911634d576bbdf6695b9a7c1ba741", size = 11251509 }, + { url = "https://files.pythonhosted.org/packages/15/6d/4e1c21c60fee11af7d8e4f2902a29886d1387d6a836be16229eb3982a963/mypy-1.14.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b2280cedcb312c7a79f5001ae5325582d0d339bce684e4a529069d0e7ca1e7", size = 10244282 }, + { url = "https://files.pythonhosted.org/packages/8b/cf/7a8ae5c0161edae15d25c2c67c68ce8b150cbdc45aefc13a8be271ee80b2/mypy-1.14.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:342de51c48bab326bfc77ce056ba08c076d82ce4f5a86621f972ed39970f94d8", size = 12867676 }, + { url = "https://files.pythonhosted.org/packages/9c/d0/71f7bbdcc7cfd0f2892db5b13b1e8857673f2cc9e0c30e3e4340523dc186/mypy-1.14.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:00df23b42e533e02a6f0055e54de9a6ed491cd8b7ea738647364fd3a39ea7efc", size = 12964189 }, + { url = "https://files.pythonhosted.org/packages/a7/40/fb4ad65d6d5f8c51396ecf6305ec0269b66013a5bf02d0e9528053640b4a/mypy-1.14.0-cp313-cp313-win_amd64.whl", hash = "sha256:e8c8387e5d9dff80e7daf961df357c80e694e942d9755f3ad77d69b0957b8e3f", size = 9888247 }, + { url = "https://files.pythonhosted.org/packages/39/32/0214608af400cdf8f5102144bb8af10d880675c65ed0b58f7e0e77175d50/mypy-1.14.0-py3-none-any.whl", hash = "sha256:2238d7f93fc4027ed1efc944507683df3ba406445a2b6c96e79666a045aadfab", size = 2752803 }, ] [[package]] @@ -6098,7 +6098,7 @@ name = "portalocker" version = "2.10.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "pywin32", marker = "platform_system == 'Windows'" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ed/d3/c6c64067759e87af98cc668c1cc75171347d0f1577fab7ca3749134e3cd4/portalocker-2.10.1.tar.gz", hash = "sha256:ef1bf844e878ab08aee7e40184156e1151f228f103aa5c6bd0724cc330960f8f", size = 40891 } wheels = [ @@ -7731,27 +7731,27 @@ wheels = [ [[package]] name = "ruff" -version = "0.8.2" +version = "0.8.4" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/2b/01245f4f3a727d60bebeacd7ee6d22586c7f62380a2597ddb22c2f45d018/ruff-0.8.2.tar.gz", hash = "sha256:b84f4f414dda8ac7f75075c1fa0b905ac0ff25361f42e6d5da681a465e0f78e5", size = 3349020 } +sdist = { url = "https://files.pythonhosted.org/packages/34/37/9c02181ef38d55b77d97c68b78e705fd14c0de0e5d085202bb2b52ce5be9/ruff-0.8.4.tar.gz", hash = "sha256:0d5f89f254836799af1615798caa5f80b7f935d7a670fad66c5007928e57ace8", size = 3402103 } wheels = [ - { url = "https://files.pythonhosted.org/packages/91/29/366be70216dba1731a00a41f2f030822b0c96c7c4f3b2c0cdce15cbace74/ruff-0.8.2-py3-none-linux_armv6l.whl", hash = "sha256:c49ab4da37e7c457105aadfd2725e24305ff9bc908487a9bf8d548c6dad8bb3d", size = 10530649 }, - { url = "https://files.pythonhosted.org/packages/63/82/a733956540bb388f00df5a3e6a02467b16c0e529132625fe44ce4c5fb9c7/ruff-0.8.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ec016beb69ac16be416c435828be702ee694c0d722505f9c1f35e1b9c0cc1bf5", size = 10274069 }, - { url = "https://files.pythonhosted.org/packages/3d/12/0b3aa14d1d71546c988a28e1b412981c1b80c8a1072e977a2f30c595cc4a/ruff-0.8.2-py3-none-macosx_11_0_arm64.whl", hash = "sha256:f05cdf8d050b30e2ba55c9b09330b51f9f97d36d4673213679b965d25a785f3c", size = 9909400 }, - { url = "https://files.pythonhosted.org/packages/23/08/f9f08cefb7921784c891c4151cce6ed357ff49e84b84978440cffbc87408/ruff-0.8.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60f578c11feb1d3d257b2fb043ddb47501ab4816e7e221fbb0077f0d5d4e7b6f", size = 10766782 }, - { url = "https://files.pythonhosted.org/packages/e4/71/bf50c321ec179aa420c8ec40adac5ae9cc408d4d37283a485b19a2331ceb/ruff-0.8.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cbd5cf9b0ae8f30eebc7b360171bd50f59ab29d39f06a670b3e4501a36ba5897", size = 10286316 }, - { url = "https://files.pythonhosted.org/packages/f2/83/c82688a2a6117539aea0ce63fdf6c08e60fe0202779361223bcd7f40bd74/ruff-0.8.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b402ddee3d777683de60ff76da801fa7e5e8a71038f57ee53e903afbcefdaa58", size = 11338270 }, - { url = "https://files.pythonhosted.org/packages/7f/d7/bc6a45e5a22e627640388e703160afb1d77c572b1d0fda8b4349f334fc66/ruff-0.8.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:705832cd7d85605cb7858d8a13d75993c8f3ef1397b0831289109e953d833d29", size = 12058579 }, - { url = "https://files.pythonhosted.org/packages/da/3b/64150c93946ec851e6f1707ff586bb460ca671581380c919698d6a9267dc/ruff-0.8.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:32096b41aaf7a5cc095fa45b4167b890e4c8d3fd217603f3634c92a541de7248", size = 11615172 }, - { url = "https://files.pythonhosted.org/packages/e4/9e/cf12b697ea83cfe92ec4509ae414dc4c9b38179cc681a497031f0d0d9a8e/ruff-0.8.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e769083da9439508833cfc7c23e351e1809e67f47c50248250ce1ac52c21fb93", size = 12882398 }, - { url = "https://files.pythonhosted.org/packages/a9/27/96d10863accf76a9c97baceac30b0a52d917eb985a8ac058bd4636aeede0/ruff-0.8.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fe716592ae8a376c2673fdfc1f5c0c193a6d0411f90a496863c99cd9e2ae25d", size = 11176094 }, - { url = "https://files.pythonhosted.org/packages/eb/10/cd2fd77d4a4e7f03c29351be0f53278a393186b540b99df68beb5304fddd/ruff-0.8.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:81c148825277e737493242b44c5388a300584d73d5774defa9245aaef55448b0", size = 10771884 }, - { url = "https://files.pythonhosted.org/packages/71/5d/beabb2ff18870fc4add05fa3a69a4cb1b1d2d6f83f3cf3ae5ab0d52f455d/ruff-0.8.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:d261d7850c8367704874847d95febc698a950bf061c9475d4a8b7689adc4f7fa", size = 10382535 }, - { url = "https://files.pythonhosted.org/packages/ae/29/6b3fdf3ad3e35b28d87c25a9ff4c8222ad72485ab783936b2b267250d7a7/ruff-0.8.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1ca4e3a87496dc07d2427b7dd7ffa88a1e597c28dad65ae6433ecb9f2e4f022f", size = 10886995 }, - { url = "https://files.pythonhosted.org/packages/e9/dc/859d889b4d9356a1a2cdbc1e4a0dda94052bc5b5300098647e51a58c430b/ruff-0.8.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:729850feed82ef2440aa27946ab39c18cb4a8889c1128a6d589ffa028ddcfc22", size = 11220750 }, - { url = "https://files.pythonhosted.org/packages/0b/08/e8f519f61f1d624264bfd6b8829e4c5f31c3c61193bc3cff1f19dbe7626a/ruff-0.8.2-py3-none-win32.whl", hash = "sha256:ac42caaa0411d6a7d9594363294416e0e48fc1279e1b0e948391695db2b3d5b1", size = 8729396 }, - { url = "https://files.pythonhosted.org/packages/f8/d4/ba1c7ab72aba37a2b71fe48ab95b80546dbad7a7f35ea28cf66fc5cea5f6/ruff-0.8.2-py3-none-win_amd64.whl", hash = "sha256:2aae99ec70abf43372612a838d97bfe77d45146254568d94926e8ed5bbb409ea", size = 9594729 }, - { url = "https://files.pythonhosted.org/packages/23/34/db20e12d3db11b8a2a8874258f0f6d96a9a4d631659d54575840557164c8/ruff-0.8.2-py3-none-win_arm64.whl", hash = "sha256:fb88e2a506b70cfbc2de6fae6681c4f944f7dd5f2fe87233a7233d888bad73e8", size = 9035131 }, + { url = "https://files.pythonhosted.org/packages/05/67/f480bf2f2723b2e49af38ed2be75ccdb2798fca7d56279b585c8f553aaab/ruff-0.8.4-py3-none-linux_armv6l.whl", hash = "sha256:58072f0c06080276804c6a4e21a9045a706584a958e644353603d36ca1eb8a60", size = 10546415 }, + { url = "https://files.pythonhosted.org/packages/eb/7a/5aba20312c73f1ce61814e520d1920edf68ca3b9c507bd84d8546a8ecaa8/ruff-0.8.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ffb60904651c00a1e0b8df594591770018a0f04587f7deeb3838344fe3adabac", size = 10346113 }, + { url = "https://files.pythonhosted.org/packages/76/f4/c41de22b3728486f0aa95383a44c42657b2db4062f3234ca36fc8cf52d8b/ruff-0.8.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:6ddf5d654ac0d44389f6bf05cee4caeefc3132a64b58ea46738111d687352296", size = 9943564 }, + { url = "https://files.pythonhosted.org/packages/0e/f0/afa0d2191af495ac82d4cbbfd7a94e3df6f62a04ca412033e073b871fc6d/ruff-0.8.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e248b1f0fa2749edd3350a2a342b67b43a2627434c059a063418e3d375cfe643", size = 10805522 }, + { url = "https://files.pythonhosted.org/packages/12/57/5d1e9a0fd0c228e663894e8e3a8e7063e5ee90f8e8e60cf2085f362bfa1a/ruff-0.8.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf197b98ed86e417412ee3b6c893f44c8864f816451441483253d5ff22c0e81e", size = 10306763 }, + { url = "https://files.pythonhosted.org/packages/04/df/f069fdb02e408be8aac6853583572a2873f87f866fe8515de65873caf6b8/ruff-0.8.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c41319b85faa3aadd4d30cb1cffdd9ac6b89704ff79f7664b853785b48eccdf3", size = 11359574 }, + { url = "https://files.pythonhosted.org/packages/d3/04/37c27494cd02e4a8315680debfc6dfabcb97e597c07cce0044db1f9dfbe2/ruff-0.8.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:9f8402b7c4f96463f135e936d9ab77b65711fcd5d72e5d67597b543bbb43cf3f", size = 12094851 }, + { url = "https://files.pythonhosted.org/packages/81/b1/c5d7fb68506cab9832d208d03ea4668da9a9887a4a392f4f328b1bf734ad/ruff-0.8.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4e56b3baa9c23d324ead112a4fdf20db9a3f8f29eeabff1355114dd96014604", size = 11655539 }, + { url = "https://files.pythonhosted.org/packages/ef/38/8f8f2c8898dc8a7a49bc340cf6f00226917f0f5cb489e37075bcb2ce3671/ruff-0.8.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:736272574e97157f7edbbb43b1d046125fce9e7d8d583d5d65d0c9bf2c15addf", size = 12912805 }, + { url = "https://files.pythonhosted.org/packages/06/dd/fa6660c279f4eb320788876d0cff4ea18d9af7d9ed7216d7bd66877468d0/ruff-0.8.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e5fe710ab6061592521f902fca7ebcb9fabd27bc7c57c764298b1c1f15fff720", size = 11205976 }, + { url = "https://files.pythonhosted.org/packages/a8/d7/de94cc89833b5de455750686c17c9e10f4e1ab7ccdc5521b8fe911d1477e/ruff-0.8.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:13e9ec6d6b55f6da412d59953d65d66e760d583dd3c1c72bf1f26435b5bfdbae", size = 10792039 }, + { url = "https://files.pythonhosted.org/packages/6d/15/3e4906559248bdbb74854af684314608297a05b996062c9d72e0ef7c7097/ruff-0.8.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:97d9aefef725348ad77d6db98b726cfdb075a40b936c7984088804dfd38268a7", size = 10400088 }, + { url = "https://files.pythonhosted.org/packages/a2/21/9ed4c0e8133cb4a87a18d470f534ad1a8a66d7bec493bcb8bda2d1a5d5be/ruff-0.8.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:ab78e33325a6f5374e04c2ab924a3367d69a0da36f8c9cb6b894a62017506111", size = 10900814 }, + { url = "https://files.pythonhosted.org/packages/0d/5d/122a65a18955bd9da2616b69bc839351f8baf23b2805b543aa2f0aed72b5/ruff-0.8.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:8ef06f66f4a05c3ddbc9121a8b0cecccd92c5bf3dd43b5472ffe40b8ca10f0f8", size = 11268828 }, + { url = "https://files.pythonhosted.org/packages/43/a9/1676ee9106995381e3d34bccac5bb28df70194167337ed4854c20f27c7ba/ruff-0.8.4-py3-none-win32.whl", hash = "sha256:552fb6d861320958ca5e15f28b20a3d071aa83b93caee33a87b471f99a6c0835", size = 8805621 }, + { url = "https://files.pythonhosted.org/packages/10/98/ed6b56a30ee76771c193ff7ceeaf1d2acc98d33a1a27b8479cbdb5c17a23/ruff-0.8.4-py3-none-win_amd64.whl", hash = "sha256:f21a1143776f8656d7f364bd264a9d60f01b7f52243fbe90e7670c0dfe0cf65d", size = 9660086 }, + { url = "https://files.pythonhosted.org/packages/13/9f/026e18ca7d7766783d779dae5e9c656746c6ede36ef73c6d934aaf4a6dec/ruff-0.8.4-py3-none-win_arm64.whl", hash = "sha256:9183dd615d8df50defa8b1d9a074053891ba39025cf5ae88e8bcb52edcc4bf08", size = 9074500 }, ] [[package]] @@ -8575,19 +8575,19 @@ dependencies = [ { name = "fsspec" }, { name = "jinja2" }, { name = "networkx" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "sympy" }, - { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and platform_system == 'Linux'" }, + { name = "triton", marker = "python_full_version < '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ @@ -8628,7 +8628,7 @@ name = "tqdm" version = "4.67.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 } wheels = [