mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-23 22:44:41 +08:00
fix(custom): honor asname in from X import Y as Z for custom components (#12813)
* fix(custom): honor asname in `from X import Y as Z` for custom components `_handle_module_attributes` was keying `exec_globals` on `alias.name`, so `from pkg import Foo as Bar` bound `Foo` instead of `Bar` in a custom component's exec scope. Any reference to `Bar` then raised NameError and the component failed to load. Use `alias.asname or alias.name`, matching the `import X as Y` branch in the same file and standard Python import semantics. Adds regression tests against `prepare_global_scope` and end-to-end through `create_class`. * fix: Make sdk env flag idempotent (release-1.9.1) (#12815) fix: Make sdk env flag idempotent Refactor pytest_addoption to register CLI options via a loop with contextlib.suppress(ValueError), allowing langflow-sdk and lfx to coexist without plugin registration conflicts. Add test_pytest_addoption_is_idempotent to verify behavior.
This commit is contained in:
@ -341,7 +341,8 @@ def _resolve_attribute(imported_module, module_name, attr_name):
|
||||
def _handle_module_attributes(imported_module, node, module_name, exec_globals):
|
||||
"""Handle importing specific attributes from a module."""
|
||||
for alias in node.names:
|
||||
exec_globals[alias.name] = _resolve_attribute(imported_module, module_name, alias.name)
|
||||
key = alias.asname or alias.name
|
||||
exec_globals[key] = _resolve_attribute(imported_module, module_name, alias.name)
|
||||
|
||||
|
||||
class _MissingModulePlaceholder:
|
||||
|
||||
@ -97,6 +97,38 @@ def to_url(path):
|
||||
assert scope["urllib"].request.pathname2url("folder name/file.txt") == "folder%20name/file.txt"
|
||||
|
||||
|
||||
def test_prepare_global_scope_supports_aliased_from_imports():
|
||||
"""Regression test: `from X import Y as Z` must bind Z in scope, not Y."""
|
||||
module = ast.parse(
|
||||
dedent("""
|
||||
from urllib.request import pathname2url as to_url_path
|
||||
|
||||
def to_url(path):
|
||||
return to_url_path(path)
|
||||
""")
|
||||
)
|
||||
scope = prepare_global_scope(module)
|
||||
|
||||
assert "to_url_path" in scope
|
||||
assert "pathname2url" not in scope
|
||||
assert scope["to_url_path"]("folder name/file.txt") == "folder%20name/file.txt"
|
||||
|
||||
|
||||
def test_create_class_supports_aliased_from_imports():
|
||||
"""End-to-end: a component using `from X import Y as Z` should load and Z is usable."""
|
||||
code = dedent("""
|
||||
from urllib.request import pathname2url as to_url_path
|
||||
from lfx.custom import Component
|
||||
|
||||
class AliasedImportComponent(Component):
|
||||
def to_url(self, path):
|
||||
return to_url_path(path)
|
||||
""")
|
||||
cls = create_class(code, "AliasedImportComponent")
|
||||
assert cls.__name__ == "AliasedImportComponent"
|
||||
assert cls().to_url("folder name/file.txt") == "folder%20name/file.txt"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _get_module_fallbacks
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -30,6 +30,7 @@ Usage inside a test file::
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
@ -54,34 +55,39 @@ if TYPE_CHECKING:
|
||||
def pytest_addoption(parser: pytest.Parser) -> None:
|
||||
"""Register Langflow-specific CLI options."""
|
||||
group = parser.getgroup("langflow", "Langflow integration testing options")
|
||||
group.addoption(
|
||||
"--langflow-env",
|
||||
dest="langflow_env",
|
||||
default=None,
|
||||
metavar="NAME",
|
||||
help="Environment name from langflow-environments.toml to use for integration tests.",
|
||||
)
|
||||
group.addoption(
|
||||
"--langflow-url",
|
||||
dest="langflow_url",
|
||||
default=None,
|
||||
metavar="URL",
|
||||
help="Base URL of the Langflow instance (overrides --langflow-env).",
|
||||
)
|
||||
group.addoption(
|
||||
"--langflow-api-key",
|
||||
dest="langflow_api_key",
|
||||
default=None,
|
||||
metavar="KEY",
|
||||
help="API key for the Langflow instance (overrides environment config).",
|
||||
)
|
||||
group.addoption(
|
||||
"--langflow-environments-file",
|
||||
dest="langflow_environments_file",
|
||||
default=None,
|
||||
metavar="PATH",
|
||||
help="Path to langflow-environments.toml (overrides default discovery).",
|
||||
)
|
||||
options = {
|
||||
"--langflow-env": {
|
||||
"dest": "langflow_env",
|
||||
"default": None,
|
||||
"metavar": "NAME",
|
||||
"help": "Environment name from langflow-environments.toml to use for integration tests.",
|
||||
},
|
||||
"--langflow-url": {
|
||||
"dest": "langflow_url",
|
||||
"default": None,
|
||||
"metavar": "URL",
|
||||
"help": "Base URL of the Langflow instance (overrides --langflow-env).",
|
||||
},
|
||||
"--langflow-api-key": {
|
||||
"dest": "langflow_api_key",
|
||||
"default": None,
|
||||
"metavar": "KEY",
|
||||
"help": "API key for the Langflow instance (overrides environment config).",
|
||||
},
|
||||
"--langflow-environments-file": {
|
||||
"dest": "langflow_environments_file",
|
||||
"default": None,
|
||||
"metavar": "PATH",
|
||||
"help": "Path to langflow-environments.toml (overrides default discovery).",
|
||||
},
|
||||
}
|
||||
|
||||
# langflow-sdk and lfx can both be installed in the same environment and
|
||||
# expose the same remote-testing flags. Keep registration idempotent so
|
||||
# pytest plugin auto-discovery can load both entry points safely.
|
||||
for flag, kwargs in options.items():
|
||||
with contextlib.suppress(ValueError):
|
||||
group.addoption(flag, **kwargs)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@ -8,9 +8,10 @@ from __future__ import annotations
|
||||
|
||||
import httpx
|
||||
import respx
|
||||
from _pytest.config.argparsing import Parser
|
||||
from langflow_sdk.client import AsyncLangflowClient, LangflowClient
|
||||
from langflow_sdk.models import RunOutput, RunResponse
|
||||
from langflow_sdk.testing import AsyncFlowRunner, FlowRunner
|
||||
from langflow_sdk.testing import AsyncFlowRunner, FlowRunner, pytest_addoption
|
||||
|
||||
_BASE = "http://langflow.test"
|
||||
|
||||
@ -307,3 +308,11 @@ def test_pytest_addoption_registers_options(pytestconfig):
|
||||
"--langflow-environments-file",
|
||||
):
|
||||
assert pytestconfig.getoption(name) is None, f"Option {name!r} missing or has unexpected default"
|
||||
|
||||
|
||||
def test_pytest_addoption_is_idempotent():
|
||||
"""Registering the plugin twice should not fail when another plugin owns the same flags."""
|
||||
parser = Parser(_ispytest=True)
|
||||
|
||||
pytest_addoption(parser)
|
||||
pytest_addoption(parser)
|
||||
|
||||
Reference in New Issue
Block a user