fix: More python 3.14 compatibility cleanup

This commit is contained in:
Eric Hare
2026-05-14 17:49:56 -07:00
parent cf488e545c
commit b695f97cd3
5 changed files with 137 additions and 34 deletions

View File

@ -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

View File

@ -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"]

View File

@ -1,3 +1,5 @@
from __future__ import annotations
import math
from datetime import datetime, timezone
from typing import Any

View File

@ -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
View File

@ -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 = [