lfx 1.14.0.dev2__py3-none-any.whl → 1.14.0.dev4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lfx/_assets/component_index.json +4 -4
- lfx/base/models/model_remediation.py +14 -3
- lfx/components/files_and_knowledge/directory.py +10 -2
- lfx/extension/migration/rewrite.py +5 -5
- lfx/graph/graph/base.py +2 -0
- lfx/load/load.py +2 -1
- lfx/schema/workflow.py +9 -15
- lfx/services/settings/groups/runtime.py +2 -0
- lfx/upgrade/checker.py +3 -0
- lfx/utils/flow_code.py +46 -0
- lfx/utils/flow_requirements.py +3 -1
- lfx/utils/flow_validation.py +21 -7
- {lfx-1.14.0.dev2.dist-info → lfx-1.14.0.dev4.dist-info}/METADATA +5 -4
- {lfx-1.14.0.dev2.dist-info → lfx-1.14.0.dev4.dist-info}/RECORD +16 -15
- {lfx-1.14.0.dev2.dist-info → lfx-1.14.0.dev4.dist-info}/WHEEL +0 -0
- {lfx-1.14.0.dev2.dist-info → lfx-1.14.0.dev4.dist-info}/entry_points.txt +0 -0
lfx/_assets/component_index.json
CHANGED
|
@@ -5469,7 +5469,7 @@
|
|
|
5469
5469
|
"icon": "folder",
|
|
5470
5470
|
"legacy": true,
|
|
5471
5471
|
"metadata": {
|
|
5472
|
-
"code_hash": "
|
|
5472
|
+
"code_hash": "f34e352b2cbe",
|
|
5473
5473
|
"component_class_name": "DirectoryComponent",
|
|
5474
5474
|
"dependencies": {
|
|
5475
5475
|
"dependencies": [
|
|
@@ -5523,7 +5523,7 @@
|
|
|
5523
5523
|
"show": true,
|
|
5524
5524
|
"title_case": false,
|
|
5525
5525
|
"type": "code",
|
|
5526
|
-
"value": "from pathlib import Path, PurePath, PureWindowsPath\n\nfrom lfx.base.data.utils import TEXT_FILE_TYPES, parallel_load_data, parse_text_file_to_data, retrieve_file_paths\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, IntInput, MessageTextInput, MultiselectInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.services.deps import get_settings_service\nfrom lfx.template.field.base import Output\nfrom lfx.utils.file_path_security import component_file_access_scopes, enforce_local_file_access\n\n\nclass DirectoryComponent(Component):\n display_name = \"Directory\"\n description = \"Recursively load files from a directory.\"\n documentation: str = \"https://docs.langflow.org/directory\"\n icon = \"folder\"\n name = \"Directory\"\n legacy = True\n replacement = [\"files_and_knowledge.File\"]\n\n inputs = [\n MessageTextInput(\n name=\"path\",\n display_name=\"Path\",\n info=\"Path to the directory to load files from. Defaults to current directory ('.')\",\n value=\".\",\n tool_mode=True,\n ),\n MultiselectInput(\n name=\"types\",\n display_name=\"File Types\",\n info=\"File types to load. Select one or more types or leave empty to load all supported types.\",\n options=TEXT_FILE_TYPES,\n value=[],\n ),\n IntInput(\n name=\"depth\",\n display_name=\"Depth\",\n info=\"Depth to search for files.\",\n value=0,\n ),\n IntInput(\n name=\"max_concurrency\",\n display_name=\"Max Concurrency\",\n advanced=True,\n info=\"Maximum concurrency for loading files.\",\n value=2,\n ),\n BoolInput(\n name=\"load_hidden\",\n display_name=\"Load Hidden\",\n advanced=True,\n info=\"If true, hidden files will be loaded.\",\n ),\n BoolInput(\n name=\"recursive\",\n display_name=\"Recursive\",\n advanced=True,\n info=\"If true, the search will be recursive.\",\n ),\n BoolInput(\n name=\"silent_errors\",\n display_name=\"Silent Errors\",\n advanced=True,\n info=\"If true, errors will not raise an exception.\",\n ),\n BoolInput(\n name=\"use_multithreading\",\n display_name=\"Use Multithreading\",\n advanced=True,\n info=\"If true, multithreading will be used.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Loaded Files\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n @staticmethod\n def _has_parent_reference(path: str) -> bool:\n path_parts = (*PurePath(path).parts, *PureWindowsPath(path).parts)\n return any(part == \"..\" for part in path_parts)\n\n def _allowed_roots(self) -> list[Path]:\n roots = {Path.cwd().resolve()}\n configured_roots = getattr(get_settings_service().settings, \"directory_component_allowed_roots\", []) or []\n for root in configured_roots:\n if root:\n roots.add(Path(root).expanduser().resolve())\n return list(roots)\n\n def _resolve_directory_path(self, path: str) -> str:\n path = str(path or \".\").strip()\n # Reject null bytes and parent references outright. A drive-absolute or UNC\n # path (e.g. ``D:\\\\shared\\\\docs``) is NOT blanket-rejected here: canonicalization\n # plus the ``_allowed_roots()`` containment check below decides whether it is\n # allowed, so operator-configured roots on another drive remain reachable.\n if \"\\x00\" in path or self._has_parent_reference(path):\n msg = \"Directory path escapes the allowed root.\"\n raise ValueError(msg)\n\n resolved_path = Path(self.resolve_path(path)).expanduser().resolve()\n if not any(resolved_path == root or resolved_path.is_relative_to(root) for root in self._allowed_roots()):\n msg = \"Directory path escapes the allowed root.\"\n raise ValueError(msg)\n return str(resolved_path)\n\n def load_directory(self) -> list[Data]:\n path = self.path\n types = self.types\n depth = self.depth\n max_concurrency = self.max_concurrency\n load_hidden = self.load_hidden\n recursive = self.recursive\n silent_errors = self.silent_errors\n use_multithreading = self.use_multithreading\n\n resolved_path = self._resolve_directory_path(path)\n\n # Security: confine directory reads to the storage dir in restricted (multi-tenant)\n # mode so a tenant cannot recursively read arbitrary server directories.\n resolved_path = str(enforce_local_file_access(resolved_path, scope_ids=component_file_access_scopes(self)))\n\n # If no types are specified, use all supported types\n if not types:\n types = TEXT_FILE_TYPES\n\n # Check if all specified types are valid\n invalid_types = [t for t in types if t not in TEXT_FILE_TYPES]\n if invalid_types:\n msg = f\"Invalid file types specified: {invalid_types}. Valid types are: {TEXT_FILE_TYPES}\"\n raise ValueError(msg)\n\n valid_types = types\n\n file_paths = retrieve_file_paths(\n resolved_path, load_hidden=load_hidden, recursive=recursive, depth=depth, types=valid_types\n )\n\n loaded_data = []\n if use_multithreading:\n loaded_data = parallel_load_data(file_paths, silent_errors=silent_errors, max_concurrency=max_concurrency)\n else:\n loaded_data = [parse_text_file_to_data(file_path, silent_errors=silent_errors) for file_path in file_paths]\n\n valid_data = [x for x in loaded_data if x is not None and isinstance(x, Data)]\n self.status = valid_data\n return valid_data\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.load_directory())\n"
|
|
5526
|
+
"value": "from pathlib import Path, PurePath, PureWindowsPath\n\nimport lfx\nfrom lfx.base.data.utils import TEXT_FILE_TYPES, parallel_load_data, parse_text_file_to_data, retrieve_file_paths\nfrom lfx.custom.custom_component.component import Component\nfrom lfx.io import BoolInput, IntInput, MessageTextInput, MultiselectInput\nfrom lfx.schema.data import Data\nfrom lfx.schema.dataframe import DataFrame\nfrom lfx.services.deps import get_settings_service\nfrom lfx.template.field.base import Output\nfrom lfx.utils.file_path_security import component_file_access_scopes, enforce_local_file_access\n\n\nclass DirectoryComponent(Component):\n display_name = \"Directory\"\n description = \"Recursively load files from a directory.\"\n documentation: str = \"https://docs.langflow.org/directory\"\n icon = \"folder\"\n name = \"Directory\"\n legacy = True\n replacement = [\"files_and_knowledge.File\"]\n\n inputs = [\n MessageTextInput(\n name=\"path\",\n display_name=\"Path\",\n info=\"Path to the directory to load files from. Defaults to current directory ('.')\",\n value=\".\",\n tool_mode=True,\n ),\n MultiselectInput(\n name=\"types\",\n display_name=\"File Types\",\n info=\"File types to load. Select one or more types or leave empty to load all supported types.\",\n options=TEXT_FILE_TYPES,\n value=[],\n ),\n IntInput(\n name=\"depth\",\n display_name=\"Depth\",\n info=\"Depth to search for files.\",\n value=0,\n ),\n IntInput(\n name=\"max_concurrency\",\n display_name=\"Max Concurrency\",\n advanced=True,\n info=\"Maximum concurrency for loading files.\",\n value=2,\n ),\n BoolInput(\n name=\"load_hidden\",\n display_name=\"Load Hidden\",\n advanced=True,\n info=\"If true, hidden files will be loaded.\",\n ),\n BoolInput(\n name=\"recursive\",\n display_name=\"Recursive\",\n advanced=True,\n info=\"If true, the search will be recursive.\",\n ),\n BoolInput(\n name=\"silent_errors\",\n display_name=\"Silent Errors\",\n advanced=True,\n info=\"If true, errors will not raise an exception.\",\n ),\n BoolInput(\n name=\"use_multithreading\",\n display_name=\"Use Multithreading\",\n advanced=True,\n info=\"If true, multithreading will be used.\",\n ),\n ]\n\n outputs = [\n Output(display_name=\"Loaded Files\", name=\"dataframe\", method=\"as_dataframe\"),\n ]\n\n @staticmethod\n def _has_parent_reference(path: str) -> bool:\n path_parts = (*PurePath(path).parts, *PureWindowsPath(path).parts)\n return any(part == \"..\" for part in path_parts)\n\n @staticmethod\n def _bundled_components_root() -> Path:\n # The component sources shipped with lfx are public, read-only package files that the\n # Langflow Assistant reads wherever lfx is installed, whatever the working directory.\n return (Path(lfx.__file__).parent / \"components\").resolve()\n\n def _allowed_roots(self) -> list[Path]:\n roots = {Path.cwd().resolve(), self._bundled_components_root()}\n configured_roots = getattr(get_settings_service().settings, \"directory_component_allowed_roots\", []) or []\n for root in configured_roots:\n if root:\n roots.add(Path(root).expanduser().resolve())\n return list(roots)\n\n def _resolve_directory_path(self, path: str) -> str:\n path = str(path or \".\").strip()\n # Reject null bytes and parent references outright. A drive-absolute or UNC\n # path (e.g. ``D:\\\\shared\\\\docs``) is NOT blanket-rejected here: canonicalization\n # plus the ``_allowed_roots()`` containment check below decides whether it is\n # allowed, so operator-configured roots on another drive remain reachable.\n if \"\\x00\" in path or self._has_parent_reference(path):\n msg = \"Directory path escapes the allowed root.\"\n raise ValueError(msg)\n\n resolved_path = Path(self.resolve_path(path)).expanduser().resolve()\n if not any(resolved_path == root or resolved_path.is_relative_to(root) for root in self._allowed_roots()):\n msg = \"Directory path escapes the allowed root.\"\n raise ValueError(msg)\n return str(resolved_path)\n\n def load_directory(self) -> list[Data]:\n path = self.path\n types = self.types\n depth = self.depth\n max_concurrency = self.max_concurrency\n load_hidden = self.load_hidden\n recursive = self.recursive\n silent_errors = self.silent_errors\n use_multithreading = self.use_multithreading\n\n resolved_path = self._resolve_directory_path(path)\n\n # Security: confine directory reads to the storage dir in restricted (multi-tenant)\n # mode so a tenant cannot recursively read arbitrary server directories.\n if not Path(resolved_path).is_relative_to(self._bundled_components_root()):\n resolved_path = str(enforce_local_file_access(resolved_path, scope_ids=component_file_access_scopes(self)))\n\n # If no types are specified, use all supported types\n if not types:\n types = TEXT_FILE_TYPES\n\n # Check if all specified types are valid\n invalid_types = [t for t in types if t not in TEXT_FILE_TYPES]\n if invalid_types:\n msg = f\"Invalid file types specified: {invalid_types}. Valid types are: {TEXT_FILE_TYPES}\"\n raise ValueError(msg)\n\n valid_types = types\n\n file_paths = retrieve_file_paths(\n resolved_path, load_hidden=load_hidden, recursive=recursive, depth=depth, types=valid_types\n )\n\n loaded_data = []\n if use_multithreading:\n loaded_data = parallel_load_data(file_paths, silent_errors=silent_errors, max_concurrency=max_concurrency)\n else:\n loaded_data = [parse_text_file_to_data(file_path, silent_errors=silent_errors) for file_path in file_paths]\n\n valid_data = [x for x in loaded_data if x is not None and isinstance(x, Data)]\n self.status = valid_data\n return valid_data\n\n def as_dataframe(self) -> DataFrame:\n return DataFrame(self.load_directory())\n"
|
|
5527
5527
|
},
|
|
5528
5528
|
"depth": {
|
|
5529
5529
|
"_input_type": "IntInput",
|
|
@@ -32518,6 +32518,6 @@
|
|
|
32518
32518
|
"num_components": 130,
|
|
32519
32519
|
"num_modules": 17
|
|
32520
32520
|
},
|
|
32521
|
-
"sha256": "
|
|
32522
|
-
"version": "1.14.0.
|
|
32521
|
+
"sha256": "d6908e8a66b45d5c246e965258c469a724f6b114a7de55827005e0b6f8fab4e2",
|
|
32522
|
+
"version": "1.14.0.dev4"
|
|
32523
32523
|
}
|
|
@@ -58,10 +58,21 @@ REMEDIATIONS: tuple[Remediation, ...] = (
|
|
|
58
58
|
providers=("OpenAI",),
|
|
59
59
|
),
|
|
60
60
|
Remediation(
|
|
61
|
-
#
|
|
62
|
-
#
|
|
61
|
+
# Models that only accept the default temperature reject an explicitly set
|
|
62
|
+
# one at call time: Claude 5 calls it "deprecated" (GH-14291), while OpenAI
|
|
63
|
+
# and Azure reasoning models (o-series, gpt-5*) answer with "'temperature'
|
|
64
|
+
# is not supported with this model" or "'temperature' does not support
|
|
65
|
+
# <value> ... Only the default (1) value is supported". Dropping temperature
|
|
66
|
+
# (→ provider default) is the fix for all of them. Markers stay narrow so a
|
|
67
|
+
# genuine out-of-range value ("temperature must be between 0 and 2"), which
|
|
68
|
+
# only the user can fix, still surfaces.
|
|
63
69
|
name="temperature-unsupported",
|
|
64
|
-
markers=(
|
|
70
|
+
markers=(
|
|
71
|
+
"`temperature` is deprecated",
|
|
72
|
+
"temperature is deprecated",
|
|
73
|
+
"'temperature' is not supported",
|
|
74
|
+
"'temperature' does not support",
|
|
75
|
+
),
|
|
65
76
|
overrides={"temperature": None},
|
|
66
77
|
),
|
|
67
78
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from pathlib import Path, PurePath, PureWindowsPath
|
|
2
2
|
|
|
3
|
+
import lfx
|
|
3
4
|
from lfx.base.data.utils import TEXT_FILE_TYPES, parallel_load_data, parse_text_file_to_data, retrieve_file_paths
|
|
4
5
|
from lfx.custom.custom_component.component import Component
|
|
5
6
|
from lfx.io import BoolInput, IntInput, MessageTextInput, MultiselectInput
|
|
@@ -82,8 +83,14 @@ class DirectoryComponent(Component):
|
|
|
82
83
|
path_parts = (*PurePath(path).parts, *PureWindowsPath(path).parts)
|
|
83
84
|
return any(part == ".." for part in path_parts)
|
|
84
85
|
|
|
86
|
+
@staticmethod
|
|
87
|
+
def _bundled_components_root() -> Path:
|
|
88
|
+
# The component sources shipped with lfx are public, read-only package files that the
|
|
89
|
+
# Langflow Assistant reads wherever lfx is installed, whatever the working directory.
|
|
90
|
+
return (Path(lfx.__file__).parent / "components").resolve()
|
|
91
|
+
|
|
85
92
|
def _allowed_roots(self) -> list[Path]:
|
|
86
|
-
roots = {Path.cwd().resolve()}
|
|
93
|
+
roots = {Path.cwd().resolve(), self._bundled_components_root()}
|
|
87
94
|
configured_roots = getattr(get_settings_service().settings, "directory_component_allowed_roots", []) or []
|
|
88
95
|
for root in configured_roots:
|
|
89
96
|
if root:
|
|
@@ -120,7 +127,8 @@ class DirectoryComponent(Component):
|
|
|
120
127
|
|
|
121
128
|
# Security: confine directory reads to the storage dir in restricted (multi-tenant)
|
|
122
129
|
# mode so a tenant cannot recursively read arbitrary server directories.
|
|
123
|
-
|
|
130
|
+
if not Path(resolved_path).is_relative_to(self._bundled_components_root()):
|
|
131
|
+
resolved_path = str(enforce_local_file_access(resolved_path, scope_ids=component_file_access_scopes(self)))
|
|
124
132
|
|
|
125
133
|
# If no types are specified, use all supported types
|
|
126
134
|
if not types:
|
|
@@ -13,11 +13,11 @@ before constructing the graph. The rewrite is intentionally tolerant:
|
|
|
13
13
|
* Unknown shape (non-dict node, missing 'data' field) -> skipped, no
|
|
14
14
|
error. The caller's own validation handles malformed payloads.
|
|
15
15
|
|
|
16
|
-
The rewriter mutates ``payload`` in place
|
|
17
|
-
|
|
18
|
-
just to swap one string per node
|
|
19
|
-
re-bind their reference. This is the one mutation
|
|
20
|
-
everything else is frozen Pydantic models.
|
|
16
|
+
The rewriter mutates ``payload`` in place. ``Graph.from_payload`` passes the
|
|
17
|
+
normalized copy it builds from, so the caller's saved flow is never rewritten.
|
|
18
|
+
Copying every node again just to swap one string per node would add a second
|
|
19
|
+
copy and force callers to re-bind their reference. This is the one mutation
|
|
20
|
+
in the migration system, and everything else is frozen Pydantic models.
|
|
21
21
|
|
|
22
22
|
The function returns a :class:`MigrationReport` with one record per node it
|
|
23
23
|
visited so the caller (today: nothing; tomorrow: ``ExtensionEventsService``)
|
lfx/graph/graph/base.py
CHANGED
|
@@ -52,6 +52,7 @@ from lfx.services.authorization.base import ExecutionPrincipal
|
|
|
52
52
|
from lfx.services.cache.utils import CacheMiss
|
|
53
53
|
from lfx.services.deps import get_chat_service, get_tracing_service
|
|
54
54
|
from lfx.utils.async_helpers import run_until_complete
|
|
55
|
+
from lfx.utils.flow_code import normalize_code_for_import
|
|
55
56
|
|
|
56
57
|
try:
|
|
57
58
|
from opentelemetry import trace as otel_trace
|
|
@@ -1848,6 +1849,7 @@ class Graph:
|
|
|
1848
1849
|
validate_flow_for_current_settings,
|
|
1849
1850
|
)
|
|
1850
1851
|
|
|
1852
|
+
payload = normalize_code_for_import(payload)
|
|
1851
1853
|
if "data" in payload:
|
|
1852
1854
|
payload = payload["data"]
|
|
1853
1855
|
# Catalog identity is the exact, case-sensitive stored node.data.type.
|
lfx/load/load.py
CHANGED
|
@@ -15,6 +15,7 @@ from lfx.load.utils import replace_tweaks_with_env
|
|
|
15
15
|
from lfx.log.logger import configure
|
|
16
16
|
from lfx.processing.process import process_tweaks, run_graph
|
|
17
17
|
from lfx.utils.async_helpers import run_until_complete
|
|
18
|
+
from lfx.utils.flow_code import normalize_code_for_import
|
|
18
19
|
from lfx.utils.flow_validation import CustomComponentValidationError, ensure_component_hash_lookups_loaded
|
|
19
20
|
from lfx.utils.util import update_settings
|
|
20
21
|
|
|
@@ -76,7 +77,7 @@ async def aload_flow_from_json(
|
|
|
76
77
|
msg = "Input must be either a file path (str) or a JSON object (dict)"
|
|
77
78
|
raise TypeError(msg)
|
|
78
79
|
|
|
79
|
-
graph_data = flow_graph["data"]
|
|
80
|
+
graph_data = normalize_code_for_import(flow_graph)["data"]
|
|
80
81
|
if tweaks is not None:
|
|
81
82
|
graph_data = process_tweaks(graph_data, tweaks)
|
|
82
83
|
|
lfx/schema/workflow.py
CHANGED
|
@@ -511,26 +511,20 @@ class WorkflowResumeResponse(BaseModel):
|
|
|
511
511
|
message: str | None = None
|
|
512
512
|
|
|
513
513
|
|
|
514
|
+
# FastAPI registers ``model`` in components.schemas and emits oneOf $refs.
|
|
515
|
+
# Inlining model_json_schema() leaves the discriminator mapping pointing at names
|
|
516
|
+
# that are never created, which Redoc cannot resolve.
|
|
517
|
+
WorkflowExecutionResult = Annotated[
|
|
518
|
+
WorkflowExecutionResponse | WorkflowJobResponse,
|
|
519
|
+
Field(discriminator="object"),
|
|
520
|
+
]
|
|
521
|
+
|
|
514
522
|
# OpenAPI response definitions
|
|
515
523
|
WORKFLOW_EXECUTION_RESPONSES = {
|
|
516
524
|
200: {
|
|
525
|
+
"model": WorkflowExecutionResult,
|
|
517
526
|
"description": "Workflow execution response",
|
|
518
527
|
"content": {
|
|
519
|
-
"application/json": {
|
|
520
|
-
"schema": {
|
|
521
|
-
"oneOf": [
|
|
522
|
-
WorkflowExecutionResponse.model_json_schema(),
|
|
523
|
-
WorkflowJobResponse.model_json_schema(),
|
|
524
|
-
],
|
|
525
|
-
"discriminator": {
|
|
526
|
-
"propertyName": "object",
|
|
527
|
-
"mapping": {
|
|
528
|
-
"response": "#/components/schemas/WorkflowExecutionResponse",
|
|
529
|
-
"job": "#/components/schemas/WorkflowJobResponse",
|
|
530
|
-
},
|
|
531
|
-
},
|
|
532
|
-
}
|
|
533
|
-
},
|
|
534
528
|
"text/event-stream": {
|
|
535
529
|
"schema": WorkflowStreamEvent.model_json_schema(),
|
|
536
530
|
"description": "Server-sent events for streaming execution",
|
|
@@ -250,6 +250,8 @@ class RuntimeSettings(BaseModel):
|
|
|
250
250
|
no inbound provider deliveries at all (a firewalled install using Track B
|
|
251
251
|
listeners only); every ingress request then answers 404 exactly as an
|
|
252
252
|
unknown trigger id does, so disabling it leaks nothing either."""
|
|
253
|
+
trigger_ingress_origin: str | None = None
|
|
254
|
+
"""Operator-advertised HTTPS origin for push providers without OAuth callbacks."""
|
|
253
255
|
trigger_ingress_max_body_bytes: int = Field(default=1_048_576, gt=0)
|
|
254
256
|
"""Per-route body cap for an ingress delivery, independent of the global
|
|
255
257
|
request limit. Provider notifications are small - Graph basic notifications
|
lfx/upgrade/checker.py
CHANGED
|
@@ -6,6 +6,8 @@ from collections.abc import Mapping
|
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
7
|
from typing import Any, Literal
|
|
8
8
|
|
|
9
|
+
from lfx.utils.flow_code import normalize_code_for_import
|
|
10
|
+
|
|
9
11
|
COMPONENTS_TO_IGNORE_UPDATE: frozenset[str] = frozenset({"CustomComponent"})
|
|
10
12
|
TRANSIENT_TEMPLATE_KEYS: frozenset[str] = frozenset({"is_refresh", "tools_metadata"})
|
|
11
13
|
|
|
@@ -278,6 +280,7 @@ def check_flow_compatibility(
|
|
|
278
280
|
rebuilding the lookup when the caller also runs ``apply_safe_upgrades`` on the same
|
|
279
281
|
registry.
|
|
280
282
|
"""
|
|
283
|
+
flow_data = normalize_code_for_import(flow_data)
|
|
281
284
|
if registry is None:
|
|
282
285
|
registry = build_registry_lookup(all_types_dict)
|
|
283
286
|
statuses: list[NodeStatus] = []
|
lfx/utils/flow_code.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Normalize source code in exported flow payloads."""
|
|
2
|
+
|
|
3
|
+
from collections.abc import Iterator
|
|
4
|
+
from copy import deepcopy
|
|
5
|
+
|
|
6
|
+
from lfx.utils.flow_envelope import split_flow_envelope
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _code_fields(flow: dict) -> Iterator[dict]:
|
|
10
|
+
_, graph = split_flow_envelope(flow)
|
|
11
|
+
nodes = graph.get("nodes")
|
|
12
|
+
if not isinstance(nodes, list):
|
|
13
|
+
return
|
|
14
|
+
for node in nodes:
|
|
15
|
+
if not isinstance(node, dict):
|
|
16
|
+
continue
|
|
17
|
+
node_data = node.get("data")
|
|
18
|
+
if not isinstance(node_data, dict):
|
|
19
|
+
continue
|
|
20
|
+
component = node_data.get("node")
|
|
21
|
+
if not isinstance(component, dict):
|
|
22
|
+
continue
|
|
23
|
+
template = component.get("template")
|
|
24
|
+
if not isinstance(template, dict):
|
|
25
|
+
continue
|
|
26
|
+
for field_data in template.values():
|
|
27
|
+
if isinstance(field_data, dict) and field_data.get("type") == "code":
|
|
28
|
+
yield field_data
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def has_code_lines(flow: dict) -> bool:
|
|
32
|
+
"""Return whether any code field still stores exported code as a list of lines."""
|
|
33
|
+
return any(isinstance(field_data.get("value"), list) for field_data in _code_fields(flow))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def normalize_code_for_import(flow: dict) -> dict:
|
|
37
|
+
"""Return a copy with exported code lines joined without losing trailing newlines."""
|
|
38
|
+
normalized = deepcopy(flow)
|
|
39
|
+
for field_data in _code_fields(normalized):
|
|
40
|
+
value = field_data.get("value")
|
|
41
|
+
if isinstance(value, list):
|
|
42
|
+
if not all(isinstance(line, str) for line in value):
|
|
43
|
+
msg = "Code lines must all be strings"
|
|
44
|
+
raise ValueError(msg)
|
|
45
|
+
field_data["value"] = "\n".join(value)
|
|
46
|
+
return normalized
|
lfx/utils/flow_requirements.py
CHANGED
|
@@ -42,6 +42,8 @@ from functools import lru_cache
|
|
|
42
42
|
from pathlib import Path
|
|
43
43
|
from types import MappingProxyType
|
|
44
44
|
|
|
45
|
+
from lfx.utils.flow_code import normalize_code_for_import
|
|
46
|
+
|
|
45
47
|
# ---------------------------------------------------------------------------
|
|
46
48
|
# Standard-library module names (3.10+)
|
|
47
49
|
# ---------------------------------------------------------------------------
|
|
@@ -548,7 +550,7 @@ def generate_requirements_from_flow(
|
|
|
548
550
|
all_packages: set[str] = set()
|
|
549
551
|
all_providers: set[str] = set()
|
|
550
552
|
|
|
551
|
-
data = flow.get("data", {})
|
|
553
|
+
data = normalize_code_for_import(flow).get("data", {})
|
|
552
554
|
nodes = data.get("nodes", [])
|
|
553
555
|
|
|
554
556
|
for node in nodes:
|
lfx/utils/flow_validation.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
9
9
|
|
|
10
10
|
from lfx.log.logger import logger
|
|
11
11
|
from lfx.utils.component_aliases import ComponentIdentityIndex, get_component_type_aliases
|
|
12
|
+
from lfx.utils.flow_code import has_code_lines, normalize_code_for_import
|
|
12
13
|
|
|
13
14
|
if TYPE_CHECKING:
|
|
14
15
|
from lfx.services.catalog_policy import CatalogPolicySnapshot
|
|
@@ -276,11 +277,24 @@ def _extract_graph_payload(graph: Any) -> Mapping[str, Any] | None:
|
|
|
276
277
|
|
|
277
278
|
|
|
278
279
|
def _extract_flow_data(target: Mapping[str, Any] | Any | None) -> dict[str, Any] | None:
|
|
279
|
-
"""Normalize a flow payload or graph-like object to raw graph data.
|
|
280
|
+
"""Normalize a flow payload or graph-like object to raw graph data.
|
|
281
|
+
|
|
282
|
+
Downloaded flows store component code as lists of lines, and ``Graph.from_payload`` joins
|
|
283
|
+
them before it builds. Join them here as well, on a copy, so policy checks hash and sanitize
|
|
284
|
+
the source that will run. Lines that cannot be joined stay as they are, and the code checks
|
|
285
|
+
still block them.
|
|
286
|
+
"""
|
|
280
287
|
if isinstance(target, Mapping) or target is None:
|
|
281
|
-
|
|
288
|
+
flow_data = _normalize_flow_data(target)
|
|
289
|
+
else:
|
|
290
|
+
flow_data = _normalize_flow_data(_extract_graph_payload(target))
|
|
282
291
|
|
|
283
|
-
|
|
292
|
+
if flow_data is None or not has_code_lines(flow_data):
|
|
293
|
+
return flow_data
|
|
294
|
+
try:
|
|
295
|
+
return normalize_code_for_import(flow_data)
|
|
296
|
+
except ValueError:
|
|
297
|
+
return flow_data
|
|
284
298
|
|
|
285
299
|
|
|
286
300
|
def _collect_catalog_component_keys(nodes: Any) -> set[str]:
|
|
@@ -1317,10 +1331,10 @@ def substitute_outdated_component_code_in_place(
|
|
|
1317
1331
|
claim is likewise left untouched, so drift is never resolved into the *wrong* component —
|
|
1318
1332
|
see :class:`SubstitutableComponentTypes`.
|
|
1319
1333
|
|
|
1320
|
-
Mutates ``flow_data`` in place
|
|
1321
|
-
|
|
1322
|
-
flagging the node as outdated
|
|
1323
|
-
nodes that were swapped.
|
|
1334
|
+
Mutates ``flow_data`` in place. ``Graph.from_payload`` passes the normalized copy it builds
|
|
1335
|
+
from, so a stored flow is never changed there. Other callers that must preserve the stored
|
|
1336
|
+
flow, so the editor keeps flagging the node as outdated, pass a copy. Returns
|
|
1337
|
+
``display_name (id)`` labels for the nodes that were swapped.
|
|
1324
1338
|
|
|
1325
1339
|
When ``validate_public_execution`` is true, the exact post-substitution bytes are checked
|
|
1326
1340
|
against the public code-execution policy using the same registry snapshot that supplied
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: lfx
|
|
3
|
-
Version: 1.14.0.
|
|
3
|
+
Version: 1.14.0.dev4
|
|
4
4
|
Summary: Langflow Executor - A lightweight CLI tool for executing and serving Langflow AI flows
|
|
5
5
|
Author-email: Gabriel Luiz Freitas Almeida <gabriel@langflow.org>
|
|
6
6
|
Requires-Python: <3.16,>=3.10
|
|
@@ -18,7 +18,8 @@ Requires-Dist: defusedxml<1.0.0,>=0.7.1
|
|
|
18
18
|
Requires-Dist: docstring-parser<1.0.0,>=0.16
|
|
19
19
|
Requires-Dist: emoji<3.0.0,>=2.14.1
|
|
20
20
|
Requires-Dist: fastapi<1.0.0,>=0.135.0
|
|
21
|
-
Requires-Dist: filelock<4.0.0
|
|
21
|
+
Requires-Dist: filelock<4.2.0,>=4.1.0; python_version < '3.11'
|
|
22
|
+
Requires-Dist: filelock<5.0.0,>=4.2.1; python_version >= '3.11'
|
|
22
23
|
Requires-Dist: gunicorn>=22.0; sys_platform != 'win32'
|
|
23
24
|
Requires-Dist: h2>=4.4.1
|
|
24
25
|
Requires-Dist: httpx[http2]<1.0.0,>=0.24.0
|
|
@@ -27,7 +28,7 @@ Requires-Dist: jsonschema<5.0.0,>=4.20.0
|
|
|
27
28
|
Requires-Dist: langchain-classic~=1.0.7
|
|
28
29
|
Requires-Dist: langchain-core<2.0.0,>=1.2.28
|
|
29
30
|
Requires-Dist: langchain~=1.3.0
|
|
30
|
-
Requires-Dist: langflow-sdk==0.4.0.
|
|
31
|
+
Requires-Dist: langflow-sdk==0.4.0.dev98
|
|
31
32
|
Requires-Dist: loguru<1.0.0,>=0.7.3
|
|
32
33
|
Requires-Dist: markitdown<2.0.0,>=0.1.4; python_version < '3.15'
|
|
33
34
|
Requires-Dist: mcp<2.0.0,>=1.30.0
|
|
@@ -77,7 +78,7 @@ Provides-Extra: sqlite
|
|
|
77
78
|
Requires-Dist: apsw==3.53.4.0; extra == 'sqlite'
|
|
78
79
|
Requires-Dist: sqlite-vec==0.1.9; extra == 'sqlite'
|
|
79
80
|
Provides-Extra: toolguard
|
|
80
|
-
Requires-Dist: lfx-toolguard<1.0.0,>=0.
|
|
81
|
+
Requires-Dist: lfx-toolguard<1.0.0,>=0.3.0; extra == 'toolguard'
|
|
81
82
|
Description-Content-Type: text/markdown
|
|
82
83
|
|
|
83
84
|
# LFX - Langflow Executor
|
|
@@ -10,7 +10,7 @@ lfx/preload.py,sha256=5YciPjPCRAZhiYKQYsLKcNO-phS9vBdo7VF46_khfms,13737
|
|
|
10
10
|
lfx/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
11
|
lfx/settings.py,sha256=wnx4zkOLQ8mvampYsnnvVV9GvEnRUuWQpKFSbFTCIp4,181
|
|
12
12
|
lfx/type_extraction.py,sha256=3gPIl9AMA8UquNG7E_Oh4_9nk5BGlUVuB97qjSuu5zg,2792
|
|
13
|
-
lfx/_assets/component_index.json,sha256=
|
|
13
|
+
lfx/_assets/component_index.json,sha256=wxZAs85Loiu3Af6lq1dA2AQ1XI7eWJGnisWeYwnpe6c,2476122
|
|
14
14
|
lfx/_assets/legacy_storage_sources.json,sha256=3OWkuQ30fWqBeuctyYOQUBZqEreC5kJhS9B6NfSGm8I,183917
|
|
15
15
|
lfx/_assets/stable_hash_history.json,sha256=WZjooLc3zQgc1kUWF5aoWtYyOuTHT-qmh7ZtiFcEsUg,41694
|
|
16
16
|
lfx/base/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -127,7 +127,7 @@ lfx/base/models/llm_callback_utils.py,sha256=0GLzdlUDxLOfF-oQsUykUDB1iaVpA2zOiAT
|
|
|
127
127
|
lfx/base/models/model.py,sha256=WuQkDrq9RY7yU2SziiRj0UjIxTO46_ZXq8yXFNCeuo4,21896
|
|
128
128
|
lfx/base/models/model_input_constants.py,sha256=5XUR9OLmukyYWGXem9o5suFYvhJNAWiEdNUPoR6COxk,12882
|
|
129
129
|
lfx/base/models/model_metadata.py,sha256=OHuwAqav-ay77rtlfYqbzJ2ZIq--3fGZW3CJxQh3oFI,18934
|
|
130
|
-
lfx/base/models/model_remediation.py,sha256=
|
|
130
|
+
lfx/base/models/model_remediation.py,sha256=ONHZ_E35ZNS4n6ncb1yK4_fuT4f4zRKjdA91k7rkU_U,6585
|
|
131
131
|
lfx/base/models/model_utils.py,sha256=-WXD2t9oBVk-t0oNNOon6GjHLSMpZE_8Zy1ShGVwbtA,58743
|
|
132
132
|
lfx/base/models/models_dev_catalog.py,sha256=7OIOxizp_jcxh4INJnBD8uyOZDE7KGS_29EoD019b88,21839
|
|
133
133
|
lfx/base/models/novita_constants.py,sha256=_mgBYGwpddUw4CLhLKJl-psOUzA_SQGHrfZJUNes6aI,1247
|
|
@@ -301,7 +301,7 @@ lfx/components/files_and_knowledge/__init__.py,sha256=uqO6FlF-J_cbv0w53e-6rc4nFI
|
|
|
301
301
|
lfx/components/files_and_knowledge/_filesystem_isolation.py,sha256=dBSJSZUW0DAqtc3nbdFz2ElTuSWWXDQSk02x-eQNtlc,1669
|
|
302
302
|
lfx/components/files_and_knowledge/_filesystem_namespace.py,sha256=9HOFQIZ75jqzIQ-73yapsMsGkilgRD6L-Ur2t2cLhlM,3022
|
|
303
303
|
lfx/components/files_and_knowledge/_kb_paths.py,sha256=zUEQYM4kTexATtj1tZpdbGLwOmHJDbl1d_-G0jFNTPI,3719
|
|
304
|
-
lfx/components/files_and_knowledge/directory.py,sha256=
|
|
304
|
+
lfx/components/files_and_knowledge/directory.py,sha256=8041Kyy-EeDSdKA8YTuJcz_Xscte2OUUBolrn14SgFg,6405
|
|
305
305
|
lfx/components/files_and_knowledge/file.py,sha256=GWu8GmX4aqjt_cDeH9f23QKt4oLUCmsKK4NZCQW_4ws,70528
|
|
306
306
|
lfx/components/files_and_knowledge/filesystem.py,sha256=jwRPBZoe92ZZyGLOzg5h063uR0fOYH-OszsDBuQvGqQ,56709
|
|
307
307
|
lfx/components/files_and_knowledge/ingestion.py,sha256=dabhpFeXDDMkgk9IdlUSriV4sncJwpyKsK4yaIrYiJU,1954
|
|
@@ -565,7 +565,7 @@ lfx/extension/loader/_types.py,sha256=FH6pEgb5zpIVXIIQg0XDPpVWDGWzb1vil0YXEzhYzR
|
|
|
565
565
|
lfx/extension/migration/__init__.py,sha256=CwRSy0t3Bz2usgrKuuSGEr9iRBInoFFiQEJaiZOhY6U,1754
|
|
566
566
|
lfx/extension/migration/loader.py,sha256=enDNoi7JMEmffGL4HmydYW40govunBMC2R3hcJsLlho,6081
|
|
567
567
|
lfx/extension/migration/migration_table.json,sha256=V29p8LeSHX-YXZCJlnT2LHrZ-py3r3Q94DSHyMeR2f0,188056
|
|
568
|
-
lfx/extension/migration/rewrite.py,sha256=
|
|
568
|
+
lfx/extension/migration/rewrite.py,sha256=O9T_Ggzk3tzGX4PgQ18fmwUkzGDaD1jvcP_2m3Ui8CM,19913
|
|
569
569
|
lfx/extension/migration/schema.py,sha256=meM64U7p5IQHE2VIfsvLoO7Ue0AwvH716Sb4NQkvEkQ,16717
|
|
570
570
|
lfx/field_typing/__init__.py,sha256=1SIlVWBB9WTTLUNSmiR0LdMNY2pGg_-jnCF4Ll6FlG4,1808
|
|
571
571
|
lfx/field_typing/conditional_options.py,sha256=RmJXXv_Bxq_aavF9t9Daa7OsWrprZXL-BAAVJgizyzo,770
|
|
@@ -595,7 +595,7 @@ lfx/graph/flow_builder/layout.py,sha256=ZCdVrmJ-Fv2LMBq35Dd2LPx1sbncr9M0zGncTgyx
|
|
|
595
595
|
lfx/graph/flow_builder/spec.py,sha256=9f8L3mlOtHgazqu1KR3vvQml0UjQleRdApzV-PIij3M,8264
|
|
596
596
|
lfx/graph/graph/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
597
597
|
lfx/graph/graph/ascii.py,sha256=-jYWI_Zmz24mfLOxJrzIINZ0dbQgd9PXJfqwZa03xas,6211
|
|
598
|
-
lfx/graph/graph/base.py,sha256=
|
|
598
|
+
lfx/graph/graph/base.py,sha256=oP1pQHi5KdCl0gGGtENkE0getc1EXwLPE35Zui-JJOU,152565
|
|
599
599
|
lfx/graph/graph/constants.py,sha256=jwjl4RydV_k_zawbI8FIgiLHeBBgH-cStVitxxSyXQs,1641
|
|
600
600
|
lfx/graph/graph/runnable_vertices_manager.py,sha256=c-qQP3koKyAsIADDSONiiz4FIRIn6q5kAMX6EQIBBfA,6148
|
|
601
601
|
lfx/graph/graph/schema.py,sha256=1a8pictdRVN6ByqpvhOYu7heDeVs7A3eGpZuYIF7kaY,1086
|
|
@@ -638,7 +638,7 @@ lfx/interface/initialize/loading.py,sha256=mRB7_N4xnS2e0oe5qiQ00SaDo0g-nzkC9CDmC
|
|
|
638
638
|
lfx/io/__init__.py,sha256=Yihh5qVCVYJRDG3AjVpX2IQqEy-6FS7zbgHqlgKbjy4,1337
|
|
639
639
|
lfx/io/schema.py,sha256=L6hE9SAJqbx9BH9wQU8zAjDSfa9XCni8C2S-oTyAgsw,15396
|
|
640
640
|
lfx/load/__init__.py,sha256=y35GBUhVTOsG3GzL5UVL-RNAsu0D7T8MVPrNXoDMx7U,224
|
|
641
|
-
lfx/load/load.py,sha256=
|
|
641
|
+
lfx/load/load.py,sha256=KGjG1oTECRZGe1kTwhxTMfmatf3ep_8QJtiO1fY4yzA,11009
|
|
642
642
|
lfx/load/utils.py,sha256=4mJ3_sAt8DdBVIiOHV8aGJchglF2IYTbLAQfwUkcN8k,4543
|
|
643
643
|
lfx/log/__init__.py,sha256=gJLNG2h6ptId2U9EyXHYmTlYNHESsSibmHhd3hPetRU,197
|
|
644
644
|
lfx/log/_streams.py,sha256=UAX_39RvJslajjE9HwadPuUnbfjMUnglOKjVJlFFauw,1195
|
|
@@ -691,7 +691,7 @@ lfx/schema/serialize.py,sha256=Y7aL91w3BW4ZYkgdIHosUYdpIJUDku-SoqYoIQtwtGM,252
|
|
|
691
691
|
lfx/schema/table.py,sha256=1RdMmk1vbzmPFW0JEGhP7WDDRtvUKH9lkDw5v-JWhSw,4668
|
|
692
692
|
lfx/schema/token_usage.py,sha256=6FU_v0xc5Gd-AA7lv67Yvx2mEzN6GCRXLH8ik06VbJo,8166
|
|
693
693
|
lfx/schema/validators.py,sha256=1SwbWr44uoIOsoWb1BxqvHq104SFh_jhLJVOKGTw4sE,4769
|
|
694
|
-
lfx/schema/workflow.py,sha256=
|
|
694
|
+
lfx/schema/workflow.py,sha256=C2aVEx9fztyyD1ycIef1bb_NQGK-qvqvQXmZoEpiRKU,21002
|
|
695
695
|
lfx/serialization/__init__.py,sha256=g-CG7Y--Eog6mSPMI7wqXh6J8lYygFthqmJWrCa3P7k,145
|
|
696
696
|
lfx/serialization/constants.py,sha256=mAYNbapEPu9-KixSHVPCXUyM-bsFMjSoCc4ohkQGvQY,47
|
|
697
697
|
lfx/serialization/serialization.py,sha256=NdcX7KXeP69tF4R1ABB2w4IFRKxrUFotyZVg04k78aU,12532
|
|
@@ -779,7 +779,7 @@ lfx/services/settings/groups/database.py,sha256=pkMJYWzeZjWTPkIRLvBz5Cf0m9sBhSnR
|
|
|
779
779
|
lfx/services/settings/groups/mcp.py,sha256=hL0Id8I-w6NEtlCQxYuqUBkkukaJECvzxS4ooRX3tFo,10434
|
|
780
780
|
lfx/services/settings/groups/observability.py,sha256=Q09KEmP0FUeD6gMacIspcbcNa8VTYQJafCxe1PqFTPs,2486
|
|
781
781
|
lfx/services/settings/groups/paths.py,sha256=IwDpt28fCXyUPrS1HJs6ebELaAcaqLBMP04JtL7doWI,3796
|
|
782
|
-
lfx/services/settings/groups/runtime.py,sha256=
|
|
782
|
+
lfx/services/settings/groups/runtime.py,sha256=JxVFU3pmt8fEH1Cg_eQQxMzAhmeG1zizFSqjH5BP21I,28188
|
|
783
783
|
lfx/services/settings/groups/security.py,sha256=Gu4yAdjmSyro0m5BX4WZAOwnmR8V8gTCMRcPpWEBkB8,28786
|
|
784
784
|
lfx/services/settings/groups/server.py,sha256=k0qcRAD-mK11MDpjLDvL27-i2ltZafPPcb0MAkzwBVg,5281
|
|
785
785
|
lfx/services/settings/groups/storage.py,sha256=uZgVWRFkI2wxZ5g3uoWyNKB2fclrGJ64OZ9bMrISWVM,628
|
|
@@ -837,7 +837,7 @@ lfx/type_extraction/__init__.py,sha256=8kKUjudbOHkYHVEOA14jD4A-9UUlG1lj1rznKuWoP
|
|
|
837
837
|
lfx/type_extraction/type_extraction.py,sha256=B4P-eAfUD9i0NqohdntgU3NBLU_0jDc18L36lQsYhrY,2718
|
|
838
838
|
lfx/upgrade/__init__.py,sha256=f4yVhS7B-nHW_tvUi3hzr4ZDSEwqRQRukCH_uniecDE,277
|
|
839
839
|
lfx/upgrade/applier.py,sha256=xkbA-digcmmpdwVd1hYz0D7RLNmQIs6V53QWX1y_Z0M,3871
|
|
840
|
-
lfx/upgrade/checker.py,sha256=
|
|
840
|
+
lfx/upgrade/checker.py,sha256=4K-QRaj9W-0aMPoWKstsiZBxWHmA2IJ0eviY0tw3zWw,13547
|
|
841
841
|
lfx/upgrade/cli_gate.py,sha256=SRfRleATX6klHzCTUu0kiF38OmEgof8UkBfDDo5sK1Y,4878
|
|
842
842
|
lfx/upgrade/registry.py,sha256=rqOl9G3ucamM4bxSZY4WHR10Ivt6T-mDPNaQAhSvJKU,1975
|
|
843
843
|
lfx/utils/__init__.py,sha256=X5mPJ2NuCxF0hobaFDBq4IGqpZFonPaIokeoFSHuX2E,881
|
|
@@ -852,9 +852,10 @@ lfx/utils/end_user_storage.py,sha256=M83ec39bI54yI14IW5dA-laysrLTcdxLhQyAXEjFSlE
|
|
|
852
852
|
lfx/utils/env_var_security.py,sha256=cngMWWB-Wl3U1ypxHd_pvS_uHaNyKXgzOB3hcrOcEwA,4365
|
|
853
853
|
lfx/utils/exceptions.py,sha256=xLkPFy5CgMiw8QyZc5UXgUjmBNY9MdJ4XXzaYAVcavQ,3190
|
|
854
854
|
lfx/utils/file_path_security.py,sha256=8V-WYsQRFlgGvToeoUdCj1K-5-w_AMORaQY4LUlD23c,17616
|
|
855
|
+
lfx/utils/flow_code.py,sha256=4R7UlXKHApJ4zPUIJt9qRAWOl2hWMlX5jK2MGKnX4ss,1664
|
|
855
856
|
lfx/utils/flow_envelope.py,sha256=D0eCjaOyvQ5gWSqdsoL7hQwNKf_6fJTToC9yZNHNq8w,2302
|
|
856
|
-
lfx/utils/flow_requirements.py,sha256=
|
|
857
|
-
lfx/utils/flow_validation.py,sha256=
|
|
857
|
+
lfx/utils/flow_requirements.py,sha256=AsioxS7ZlN7tB7H6Pbylt-eXByclVjS7MbMiN3AJXAU,28757
|
|
858
|
+
lfx/utils/flow_validation.py,sha256=pHPygVg3-fer3HKgyarHiye5cPAUEW5BolIvtCBego0,105258
|
|
858
859
|
lfx/utils/helpers.py,sha256=0LE0barnVp-8Y5cCoDRzhDzesvXqgiT7IXP6vtTSyGE,889
|
|
859
860
|
lfx/utils/image.py,sha256=CaMGwpCCKxcu_pWmP7FZXwZ3kr1-ymGKjN92Bv_yr5k,3160
|
|
860
861
|
lfx/utils/jq_security.py,sha256=mADEtPPbQvoQw88JOXB1Ig9Eh5uslijYw8U_ZPho7_U,8571
|
|
@@ -889,7 +890,7 @@ lfx/workflow/router.py,sha256=lrIDbrrE0XwI2fbfxpt8jb2jPSyxTpthUqUY1HXVVCo,24620
|
|
|
889
890
|
lfx/workflow/adapters/__init__.py,sha256=8WyVggf_LacLJRJEUM6_4JLqM2EN-KogrbUEp1E8w00,5166
|
|
890
891
|
lfx/workflow/adapters/agui.py,sha256=ZLLJJ-CQgB1ce8HhqjQt4AtR3a70MSweC5F5StUgW0c,3479
|
|
891
892
|
lfx/workflow/adapters/langflow.py,sha256=0RT1uZWBz7O1JmOlfiQVruvXBIX8FPhXwGdLQchvJ5I,8845
|
|
892
|
-
lfx-1.14.0.
|
|
893
|
-
lfx-1.14.0.
|
|
894
|
-
lfx-1.14.0.
|
|
895
|
-
lfx-1.14.0.
|
|
893
|
+
lfx-1.14.0.dev4.dist-info/METADATA,sha256=xbk4nzHr3j80nfYOVWkWrFXUthpg-rXzPDNw28ZDgFY,35385
|
|
894
|
+
lfx-1.14.0.dev4.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
895
|
+
lfx-1.14.0.dev4.dist-info/entry_points.txt,sha256=jKPaACW4536e5puzF8Po_sPx7_GvktOBvttxOwJiMMs,104
|
|
896
|
+
lfx-1.14.0.dev4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|