mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-23 20:49:48 +08:00
fix: More python 3.14 compatibility cleanup
This commit is contained in:
70
.github/workflows/cross-platform-test.yml
vendored
70
.github/workflows/cross-platform-test.yml
vendored
@ -584,9 +584,26 @@ jobs:
|
||||
uv venv test-env --seed
|
||||
shell: bash
|
||||
|
||||
# Wheel installation steps — install langflow-sdk before LFX (sdk is not yet on PyPI)
|
||||
- name: Install langflow-sdk from wheel (Windows)
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows' && (inputs.sdk-artifact-name != '' || needs.build-if-needed.outputs.sdk-artifact-name != '')
|
||||
# Wheel installation steps — install SDK and LFX together when both are present.
|
||||
- name: Install SDK and LFX packages from wheel (Windows)
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows' && (inputs.sdk-artifact-name != '' || needs.build-if-needed.outputs.sdk-artifact-name != '') && (inputs.lfx-artifact-name != '' || needs.build-if-needed.outputs.lfx-artifact-name != '')
|
||||
run: |
|
||||
ls -la ./sdk-dist/
|
||||
find ./sdk-dist -name "*.whl" -type f
|
||||
ls -la ./lfx-dist/
|
||||
find ./lfx-dist -name "*.whl" -type f
|
||||
SDK_WHEEL_FILE=$(find ./sdk-dist -name "*.whl" -type f | head -1)
|
||||
LFX_WHEEL_FILE=$(find ./lfx-dist -name "*.whl" -type f | head -1)
|
||||
if [ -n "$SDK_WHEEL_FILE" ] && [ -n "$LFX_WHEEL_FILE" ]; then
|
||||
uv pip install --prerelease=allow --python ./test-env/Scripts/python.exe "$SDK_WHEEL_FILE" "$LFX_WHEEL_FILE"
|
||||
else
|
||||
echo "Missing wheel file in ./sdk-dist/ or ./lfx-dist/"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Install SDK package from wheel (Windows)
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows' && (inputs.sdk-artifact-name != '' || needs.build-if-needed.outputs.sdk-artifact-name != '') && (inputs.lfx-artifact-name == '' && needs.build-if-needed.outputs.lfx-artifact-name == '')
|
||||
run: |
|
||||
WHEEL_FILE=$(find ./sdk-dist -name "*.whl" -type f | head -1)
|
||||
if [ -n "$WHEEL_FILE" ]; then
|
||||
@ -597,8 +614,25 @@ jobs:
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Install langflow-sdk from wheel (Unix)
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows' && (inputs.sdk-artifact-name != '' || needs.build-if-needed.outputs.sdk-artifact-name != '')
|
||||
- name: Install SDK and LFX packages from wheel (Unix)
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows' && (inputs.sdk-artifact-name != '' || needs.build-if-needed.outputs.sdk-artifact-name != '') && (inputs.lfx-artifact-name != '' || needs.build-if-needed.outputs.lfx-artifact-name != '')
|
||||
run: |
|
||||
ls -la ./sdk-dist/
|
||||
find ./sdk-dist -name "*.whl" -type f
|
||||
ls -la ./lfx-dist/
|
||||
find ./lfx-dist -name "*.whl" -type f
|
||||
SDK_WHEEL_FILE=$(find ./sdk-dist -name "*.whl" -type f | head -1)
|
||||
LFX_WHEEL_FILE=$(find ./lfx-dist -name "*.whl" -type f | head -1)
|
||||
if [ -n "$SDK_WHEEL_FILE" ] && [ -n "$LFX_WHEEL_FILE" ]; then
|
||||
uv pip install --prerelease=allow --python ./test-env/bin/python "$SDK_WHEEL_FILE" "$LFX_WHEEL_FILE"
|
||||
else
|
||||
echo "Missing wheel file in ./sdk-dist/ or ./lfx-dist/"
|
||||
exit 1
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Install SDK package from wheel (Unix)
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows' && (inputs.sdk-artifact-name != '' || needs.build-if-needed.outputs.sdk-artifact-name != '') && (inputs.lfx-artifact-name == '' && needs.build-if-needed.outputs.lfx-artifact-name == '')
|
||||
run: |
|
||||
WHEEL_FILE=$(find ./sdk-dist -name "*.whl" -type f | head -1)
|
||||
if [ -n "$WHEEL_FILE" ]; then
|
||||
@ -610,7 +644,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Install LFX package from wheel (Windows)
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows' && (inputs.lfx-artifact-name != '' || needs.build-if-needed.outputs.lfx-artifact-name != '')
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os == 'windows' && (inputs.lfx-artifact-name != '' || needs.build-if-needed.outputs.lfx-artifact-name != '') && (inputs.sdk-artifact-name == '' && needs.build-if-needed.outputs.sdk-artifact-name == '')
|
||||
run: |
|
||||
ls -la ./lfx-dist/
|
||||
find ./lfx-dist -name "*.whl" -type f
|
||||
@ -652,7 +686,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Install LFX package from wheel (Unix)
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows' && (inputs.lfx-artifact-name != '' || needs.build-if-needed.outputs.lfx-artifact-name != '')
|
||||
if: steps.install-method.outputs.method == 'wheel' && matrix.os != 'windows' && (inputs.lfx-artifact-name != '' || needs.build-if-needed.outputs.lfx-artifact-name != '') && (inputs.sdk-artifact-name == '' && needs.build-if-needed.outputs.sdk-artifact-name == '')
|
||||
run: |
|
||||
ls -la ./lfx-dist/
|
||||
find ./lfx-dist -name "*.whl" -type f
|
||||
@ -701,11 +735,20 @@ jobs:
|
||||
LFX_WHEEL=$(find ./lfx-dist -name "*.whl" -type f | head -1)
|
||||
if [ -n "$LFX_WHEEL" ]; then
|
||||
echo "Force reinstalling LFX: $LFX_WHEEL"
|
||||
INSTALL_WHEELS=("$LFX_WHEEL")
|
||||
SDK_WHEEL=""
|
||||
if [ -d ./sdk-dist ]; then
|
||||
SDK_WHEEL=$(find ./sdk-dist -name "*.whl" -type f | head -1)
|
||||
fi
|
||||
if [ -n "$SDK_WHEEL" ]; then
|
||||
echo "Force reinstalling langflow-sdk with LFX: $SDK_WHEEL"
|
||||
INSTALL_WHEELS=("$SDK_WHEEL" "$LFX_WHEEL")
|
||||
fi
|
||||
NO_DEPS="--no-deps"
|
||||
if [ "${{ inputs.pre_release }}" = "true" ]; then
|
||||
NO_DEPS=""
|
||||
fi
|
||||
uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/Scripts/python.exe "$LFX_WHEEL"
|
||||
uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/Scripts/python.exe "${INSTALL_WHEELS[@]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
@ -729,11 +772,20 @@ jobs:
|
||||
LFX_WHEEL=$(find ./lfx-dist -name "*.whl" -type f | head -1)
|
||||
if [ -n "$LFX_WHEEL" ]; then
|
||||
echo "Force reinstalling LFX: $LFX_WHEEL"
|
||||
INSTALL_WHEELS=("$LFX_WHEEL")
|
||||
SDK_WHEEL=""
|
||||
if [ -d ./sdk-dist ]; then
|
||||
SDK_WHEEL=$(find ./sdk-dist -name "*.whl" -type f | head -1)
|
||||
fi
|
||||
if [ -n "$SDK_WHEEL" ]; then
|
||||
echo "Force reinstalling langflow-sdk with LFX: $SDK_WHEEL"
|
||||
INSTALL_WHEELS=("$SDK_WHEEL" "$LFX_WHEEL")
|
||||
fi
|
||||
NO_DEPS="--no-deps"
|
||||
if [ "${{ inputs.pre_release }}" = "true" ]; then
|
||||
NO_DEPS=""
|
||||
fi
|
||||
uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/bin/python "$LFX_WHEEL"
|
||||
uv pip install --force-reinstall $NO_DEPS --prerelease=allow --python ./test-env/bin/python "${INSTALL_WHEELS[@]}"
|
||||
fi
|
||||
fi
|
||||
|
||||
|
||||
@ -234,7 +234,7 @@ sentence-transformers = ["sentence-transformers>=2.3.1"]
|
||||
ctransformers = ["ctransformers>=0.2.10"]
|
||||
|
||||
# Individual monitoring providers
|
||||
langfuse = ["langfuse~=3.8"]
|
||||
langfuse = ["langfuse~=3.8; python_version < '3.14'"]
|
||||
langwatch = ["langwatch~=0.10.0; python_version < '3.14'"]
|
||||
langsmith = ["langsmith>=0.3.42,<1.0.0"]
|
||||
arize = ["arize-phoenix-otel>=0.6.1"]
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
@ -8,6 +8,8 @@ See: https://langfuse.com/docs/observability/sdk/upgrade-path
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
import uuid
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
@ -28,41 +30,66 @@ def langfuse_env_vars():
|
||||
yield
|
||||
|
||||
|
||||
def _clear_failed_langfuse_import() -> None:
|
||||
"""Remove partially imported Langfuse modules after SDK import failures."""
|
||||
for module_name in list(sys.modules):
|
||||
if module_name == "langfuse" or module_name.startswith("langfuse."):
|
||||
sys.modules.pop(module_name, None)
|
||||
|
||||
|
||||
def _import_langfuse_or_skip():
|
||||
try:
|
||||
from langfuse import Langfuse
|
||||
except Exception as exc:
|
||||
_clear_failed_langfuse_import()
|
||||
pytest.skip(f"langfuse SDK is not importable: {exc}")
|
||||
return Langfuse
|
||||
|
||||
|
||||
def _import_callback_handler_or_skip():
|
||||
try:
|
||||
from langfuse.langchain import CallbackHandler
|
||||
except Exception as exc:
|
||||
_clear_failed_langfuse_import()
|
||||
pytest.skip(f"langfuse LangChain callback handler is not importable: {exc}")
|
||||
return CallbackHandler
|
||||
|
||||
|
||||
class TestLangfuseV3ApiExists:
|
||||
"""Verify that the langfuse v3 API methods we need actually exist."""
|
||||
|
||||
def test_langfuse_client_has_start_span(self):
|
||||
"""Verify start_span method exists (v3 API)."""
|
||||
from langfuse import Langfuse
|
||||
langfuse_class = _import_langfuse_or_skip()
|
||||
|
||||
assert hasattr(Langfuse, "start_span"), "Langfuse.start_span() should exist in v3"
|
||||
assert hasattr(langfuse_class, "start_span"), "Langfuse.start_span() should exist in v3"
|
||||
|
||||
def test_langfuse_client_has_start_as_current_span(self):
|
||||
"""Verify start_as_current_span method exists (v3 API)."""
|
||||
from langfuse import Langfuse
|
||||
langfuse_class = _import_langfuse_or_skip()
|
||||
|
||||
assert hasattr(Langfuse, "start_as_current_span"), "Langfuse.start_as_current_span() should exist in v3"
|
||||
assert hasattr(langfuse_class, "start_as_current_span"), "Langfuse.start_as_current_span() should exist in v3"
|
||||
|
||||
def test_langfuse_client_has_create_trace_id(self):
|
||||
"""Verify create_trace_id method exists (v3 API)."""
|
||||
from langfuse import Langfuse
|
||||
langfuse_class = _import_langfuse_or_skip()
|
||||
|
||||
assert hasattr(Langfuse, "create_trace_id"), "Langfuse.create_trace_id() should exist in v3"
|
||||
assert hasattr(langfuse_class, "create_trace_id"), "Langfuse.create_trace_id() should exist in v3"
|
||||
|
||||
def test_langfuse_client_does_not_have_trace(self):
|
||||
"""Verify trace() method was removed in v3."""
|
||||
from langfuse import Langfuse
|
||||
langfuse_class = _import_langfuse_or_skip()
|
||||
|
||||
# This test documents that trace() no longer exists
|
||||
# If this fails, langfuse may have restored backward compatibility
|
||||
assert not hasattr(Langfuse, "trace"), "Langfuse.trace() should NOT exist in v3 (removed)"
|
||||
assert not hasattr(langfuse_class, "trace"), "Langfuse.trace() should NOT exist in v3 (removed)"
|
||||
|
||||
def test_callback_handler_import_path(self):
|
||||
"""Verify the v3 callback handler import path works."""
|
||||
# v3 path
|
||||
from langfuse.langchain import CallbackHandler
|
||||
callback_handler = _import_callback_handler_or_skip()
|
||||
|
||||
assert CallbackHandler is not None
|
||||
assert callback_handler is not None
|
||||
|
||||
|
||||
class TestLangfuseTracerV3Compatibility:
|
||||
@ -91,7 +118,29 @@ class TestLangfuseTracerFunctionality:
|
||||
@pytest.fixture
|
||||
def mock_langfuse(self):
|
||||
"""Create a mock langfuse client that simulates v3 API."""
|
||||
with patch("langfuse.Langfuse") as mock_langfuse_class:
|
||||
|
||||
class TraceContext(dict):
|
||||
pass
|
||||
|
||||
mock_langfuse_module = types.ModuleType("langfuse")
|
||||
mock_langfuse_types_module = types.ModuleType("langfuse.types")
|
||||
mock_langfuse_langchain_module = types.ModuleType("langfuse.langchain")
|
||||
|
||||
mock_langfuse_class = MagicMock()
|
||||
mock_langfuse_types_module.TraceContext = TraceContext
|
||||
mock_langfuse_langchain_module.CallbackHandler = MagicMock()
|
||||
mock_langfuse_module.Langfuse = mock_langfuse_class
|
||||
mock_langfuse_module.types = mock_langfuse_types_module
|
||||
mock_langfuse_module.langchain = mock_langfuse_langchain_module
|
||||
|
||||
with patch.dict(
|
||||
sys.modules,
|
||||
{
|
||||
"langfuse": mock_langfuse_module,
|
||||
"langfuse.types": mock_langfuse_types_module,
|
||||
"langfuse.langchain": mock_langfuse_langchain_module,
|
||||
},
|
||||
):
|
||||
mock_client = MagicMock()
|
||||
mock_langfuse_class.return_value = mock_client
|
||||
mock_langfuse_class.create_trace_id = MagicMock(return_value="a" * 32)
|
||||
|
||||
26
uv.lock
generated
26
uv.lock
generated
@ -7140,7 +7140,7 @@ all = [
|
||||
{ name = "langchain-pinecone", marker = "python_full_version < '3.14'" },
|
||||
{ name = "langchain-sambanova" },
|
||||
{ name = "langchain-unstructured" },
|
||||
{ name = "langfuse" },
|
||||
{ name = "langfuse", marker = "python_full_version < '3.14'" },
|
||||
{ name = "langsmith" },
|
||||
{ name = "langwatch", marker = "python_full_version < '3.14'" },
|
||||
{ name = "lark" },
|
||||
@ -7307,7 +7307,7 @@ complete = [
|
||||
{ name = "langchain-pinecone", marker = "python_full_version < '3.14'" },
|
||||
{ name = "langchain-sambanova" },
|
||||
{ name = "langchain-unstructured" },
|
||||
{ name = "langfuse" },
|
||||
{ name = "langfuse", marker = "python_full_version < '3.14'" },
|
||||
{ name = "langsmith" },
|
||||
{ name = "langwatch", marker = "python_full_version < '3.14'" },
|
||||
{ name = "lark" },
|
||||
@ -7459,7 +7459,7 @@ langchain-unstructured = [
|
||||
{ name = "langchain-unstructured" },
|
||||
]
|
||||
langfuse = [
|
||||
{ name = "langfuse" },
|
||||
{ name = "langfuse", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
langsmith = [
|
||||
{ name = "langsmith" },
|
||||
@ -7875,7 +7875,7 @@ requires-dist = [
|
||||
{ name = "langflow-base", extras = ["yfinance"], marker = "extra == 'complete'", editable = "src/backend/base" },
|
||||
{ name = "langflow-base", extras = ["youtube"], marker = "extra == 'complete'", editable = "src/backend/base" },
|
||||
{ name = "langflow-base", extras = ["zep"], marker = "extra == 'complete'", editable = "src/backend/base" },
|
||||
{ name = "langfuse", marker = "extra == 'langfuse'", specifier = "~=3.8" },
|
||||
{ name = "langfuse", marker = "python_full_version < '3.14' and extra == 'langfuse'", specifier = "~=3.8" },
|
||||
{ name = "langgraph-checkpoint", specifier = ">4.0.0,<5.0.0" },
|
||||
{ name = "langsmith", marker = "extra == 'langsmith'", specifier = ">=0.3.42,<1.0.0" },
|
||||
{ name = "langwatch", marker = "python_full_version < '3.14' and extra == 'langwatch'", specifier = "~=0.10.0" },
|
||||
@ -8058,15 +8058,15 @@ name = "langfuse"
|
||||
version = "3.14.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "backoff" },
|
||||
{ name = "httpx" },
|
||||
{ name = "openai" },
|
||||
{ name = "opentelemetry-api" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-http" },
|
||||
{ name = "opentelemetry-sdk" },
|
||||
{ name = "packaging" },
|
||||
{ name = "pydantic" },
|
||||
{ name = "wrapt" },
|
||||
{ name = "backoff", marker = "python_full_version < '3.14'" },
|
||||
{ name = "httpx", marker = "python_full_version < '3.14'" },
|
||||
{ name = "openai", marker = "python_full_version < '3.14'" },
|
||||
{ name = "opentelemetry-api", marker = "python_full_version < '3.14'" },
|
||||
{ name = "opentelemetry-exporter-otlp-proto-http", marker = "python_full_version < '3.14'" },
|
||||
{ name = "opentelemetry-sdk", marker = "python_full_version < '3.14'" },
|
||||
{ name = "packaging", marker = "python_full_version < '3.14'" },
|
||||
{ name = "pydantic", marker = "python_full_version < '3.14'" },
|
||||
{ name = "wrapt", marker = "python_full_version < '3.14'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e5/e4/5bb96bc5f95f476c8f7c4e654a6420250938cc21645b914a1309c2dc3d49/langfuse-3.14.6.tar.gz", hash = "sha256:058f7b7caa9284b964feaee81c223542d78e9fb4daabf312269b9f903471b9c4", size = 236530, upload-time = "2026-04-01T17:27:55.057Z" }
|
||||
wheels = [
|
||||
|
||||
Reference in New Issue
Block a user