Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-langfuse for openSUSE:Factory 
checked in at 2026-04-20 16:12:30
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-langfuse (Old)
 and      /work/SRC/openSUSE:Factory/.python-langfuse.new.11940 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-langfuse"

Mon Apr 20 16:12:30 2026 rev:7 rq:1348121 version:4.3.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-langfuse/python-langfuse.changes  
2026-04-14 17:49:09.025600360 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-langfuse.new.11940/python-langfuse.changes   
    2026-04-20 16:12:44.040705721 +0200
@@ -1,0 +2,14 @@
+Mon Apr 20 07:06:19 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 4.3.1:
+  * feat(api): update API spec from langfuse/langfuse 07cae52
+  * chore(deps): bump actions/github-script from 8.0.0 to 9.0.0
+    in the github-actions group
+  * refactor(tests): split suites by execution level and speed up
+    CI
+  * ci: add 7-day dependabot cooldown
+  * ci: harden GitHub Actions workflows with zizmor
+  * ci: make uv action tag explicit
+  * fix(langchain): propagate trace name metadata
+
+-------------------------------------------------------------------

Old:
----
  langfuse-4.2.0.tar.gz

New:
----
  langfuse-4.3.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-langfuse.spec ++++++
--- /var/tmp/diff_new_pack.v09ECO/_old  2026-04-20 16:12:44.840739130 +0200
+++ /var/tmp/diff_new_pack.v09ECO/_new  2026-04-20 16:12:44.840739130 +0200
@@ -18,7 +18,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-langfuse
-Version:        4.2.0
+Version:        4.3.1
 Release:        0
 Summary:        A client library for accessing langfuse
 License:        MIT

++++++ langfuse-4.2.0.tar.gz -> langfuse-4.3.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langfuse-4.2.0/PKG-INFO new/langfuse-4.3.1/PKG-INFO
--- old/langfuse-4.2.0/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/PKG-INFO 1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: langfuse
-Version: 4.2.0
+Version: 4.3.1
 Summary: A client library for accessing langfuse
 Author: langfuse
 Author-email: langfuse <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langfuse-4.2.0/langfuse/_client/client.py 
new/langfuse-4.3.1/langfuse/_client/client.py
--- old/langfuse-4.2.0/langfuse/_client/client.py       1970-01-01 
01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/langfuse/_client/client.py       1970-01-01 
01:00:00.000000000 +0100
@@ -3486,8 +3486,9 @@
                         fetch_timeout_seconds=fetch_timeout_seconds,
                     )
 
-                self._resources.prompt_cache.add_refresh_prompt_task(
+                
self._resources.prompt_cache.add_refresh_prompt_task_if_current(
                     cache_key,
+                    cached_prompt,
                     refresh_task,
                 )
                 langfuse_logger.debug(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langfuse-4.2.0/langfuse/_client/resource_manager.py 
new/langfuse-4.3.1/langfuse/_client/resource_manager.py
--- old/langfuse-4.2.0/langfuse/_client/resource_manager.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/langfuse/_client/resource_manager.py     1970-01-01 
01:00:00.000000000 +0100
@@ -405,6 +405,8 @@
         for media_upload_consumer in self._media_upload_consumers:
             media_upload_consumer.pause()
 
+        
self._media_manager.signal_shutdown(count=len(self._media_upload_consumers))
+
         for media_upload_consumer in self._media_upload_consumers:
             try:
                 media_upload_consumer.join()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langfuse-4.2.0/langfuse/_task_manager/media_manager.py 
new/langfuse-4.3.1/langfuse/_task_manager/media_manager.py
--- old/langfuse-4.2.0/langfuse/_task_manager/media_manager.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/langfuse/_task_manager/media_manager.py  1970-01-01 
01:00:00.000000000 +0100
@@ -18,6 +18,7 @@
 
 T = TypeVar("T")
 P = ParamSpec("P")
+_SHUTDOWN_SENTINEL = object()
 
 
 class MediaManager:
@@ -40,6 +41,11 @@
     def process_next_media_upload(self) -> None:
         try:
             upload_job = self._queue.get(block=True, timeout=1)
+
+            if upload_job is _SHUTDOWN_SENTINEL:
+                self._queue.task_done()
+                return
+
             logger.debug(
                 f"Media: Processing upload for 
media_id={upload_job['media_id']} in trace_id={upload_job['trace_id']}"
             )
@@ -54,6 +60,15 @@
             )
             self._queue.task_done()
 
+    def signal_shutdown(self, *, count: int = 1) -> None:
+        for _ in range(count):
+            try:
+                self._queue.put(_SHUTDOWN_SENTINEL, block=False)
+            except Full:
+                # If the queue is full, the consumer will keep draining work 
and
+                # observe the paused flag on the next loop iteration.
+                break
+
     def _find_and_process_media(
         self,
         *,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langfuse-4.2.0/langfuse/_task_manager/score_ingestion_consumer.py 
new/langfuse-4.3.1/langfuse/_task_manager/score_ingestion_consumer.py
--- old/langfuse-4.2.0/langfuse/_task_manager/score_ingestion_consumer.py       
1970-01-01 01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/langfuse/_task_manager/score_ingestion_consumer.py       
1970-01-01 01:00:00.000000000 +0100
@@ -2,7 +2,7 @@
 import os
 import threading
 import time
-from queue import Empty, Queue
+from queue import Empty, Full, Queue
 from typing import Any, List, Optional
 
 import backoff
@@ -17,6 +17,7 @@
 
 MAX_EVENT_SIZE_BYTES = int(os.environ.get("LANGFUSE_MAX_EVENT_SIZE_BYTES", 
1_000_000))
 MAX_BATCH_SIZE_BYTES = int(os.environ.get("LANGFUSE_MAX_BATCH_SIZE_BYTES", 
2_500_000))
+_SHUTDOWN_SENTINEL = object()
 
 
 class ScoreIngestionMetadata(BaseModel):
@@ -71,6 +72,10 @@
                     block=True, timeout=self._flush_interval - elapsed
                 )
 
+                if event is _SHUTDOWN_SENTINEL:
+                    self._ingestion_queue.task_done()
+                    break
+
                 # convert pydantic models to dicts
                 if "body" in event and isinstance(event["body"], BaseModel):
                     event["body"] = event["body"].model_dump(exclude_none=True)
@@ -139,6 +144,12 @@
     def pause(self) -> None:
         """Pause the consumer."""
         self.running = False
+        try:
+            self._ingestion_queue.put(_SHUTDOWN_SENTINEL, block=False)
+        except Full:
+            # If the queue is full, the consumer will wake up naturally while
+            # draining items, so a dedicated shutdown signal is not required.
+            pass
 
     def _upload_batch(self, batch: List[Any]) -> None:
         logger.debug(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langfuse-4.2.0/langfuse/_utils/prompt_cache.py 
new/langfuse-4.3.1/langfuse/_utils/prompt_cache.py
--- old/langfuse-4.2.0/langfuse/_utils/prompt_cache.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/langfuse/_utils/prompt_cache.py  1970-01-01 
01:00:00.000000000 +0100
@@ -3,8 +3,8 @@
 import atexit
 import os
 from datetime import datetime
-from queue import Empty, Queue
-from threading import Thread
+from queue import Queue
+from threading import RLock, Thread
 from typing import Callable, Dict, List, Optional, Set
 
 from langfuse._client.environment_variables import (
@@ -18,6 +18,7 @@
 )
 
 DEFAULT_PROMPT_CACHE_REFRESH_WORKERS = 1
+_SHUTDOWN_SENTINEL = object()
 
 
 class PromptCacheItem:
@@ -46,22 +47,24 @@
 
     def run(self) -> None:
         while self.running:
+            task = self._queue.get()
+
+            if task is _SHUTDOWN_SENTINEL:
+                self._queue.task_done()
+                break
+
+            logger.debug(
+                f"PromptCacheRefreshConsumer processing task, 
{self._identifier}"
+            )
             try:
-                task = self._queue.get(timeout=1)
-                logger.debug(
-                    f"PromptCacheRefreshConsumer processing task, 
{self._identifier}"
+                task()
+            # Task failed, but we still consider it processed
+            except Exception as e:
+                logger.warning(
+                    f"PromptCacheRefreshConsumer encountered an error, cache 
was not refreshed: {self._identifier}, {e}"
                 )
-                try:
-                    task()
-                # Task failed, but we still consider it processed
-                except Exception as e:
-                    logger.warning(
-                        f"PromptCacheRefreshConsumer encountered an error, 
cache was not refreshed: {self._identifier}, {e}"
-                    )
 
-                self._queue.task_done()
-            except Empty:
-                pass
+            self._queue.task_done()
 
     def pause(self) -> None:
         """Pause the consumer."""
@@ -73,12 +76,14 @@
     _threads: int
     _queue: Queue
     _processing_keys: Set[str]
+    _lock: RLock
 
     def __init__(self, threads: int = 1):
         self._queue = Queue()
         self._consumers = []
         self._threads = threads
         self._processing_keys = set()
+        self._lock = RLock()
 
         for i in range(self._threads):
             consumer = PromptCacheRefreshConsumer(self._queue, i)
@@ -88,16 +93,23 @@
         atexit.register(self.shutdown)
 
     def add_task(self, key: str, task: Callable[[], None]) -> None:
-        if key not in self._processing_keys:
-            logger.debug(f"Adding prompt cache refresh task for key: {key}")
-            self._processing_keys.add(key)
-            wrapped_task = self._wrap_task(key, task)
-            self._queue.put((wrapped_task))
-        else:
-            logger.debug(f"Prompt cache refresh task already submitted for 
key: {key}")
+        with self._lock:
+            if key not in self._processing_keys:
+                logger.debug(f"Adding prompt cache refresh task for key: 
{key}")
+                self._processing_keys.add(key)
+                wrapped_task = self._wrap_task(key, task)
+                self._queue.put((wrapped_task))
+            else:
+                logger.debug(
+                    f"Prompt cache refresh task already submitted for key: 
{key}"
+                )
 
     def active_tasks(self) -> int:
-        return len(self._processing_keys)
+        with self._lock:
+            return len(self._processing_keys)
+
+    def wait_for_idle(self) -> None:
+        self._queue.join()
 
     def _wrap_task(self, key: str, task: Callable[[], None]) -> Callable[[], 
None]:
         def wrapped() -> None:
@@ -105,7 +117,8 @@
             try:
                 task()
             finally:
-                self._processing_keys.remove(key)
+                with self._lock:
+                    self._processing_keys.remove(key)
                 logger.debug(f"Refreshed prompt cache for key: {key}")
 
         return wrapped
@@ -120,6 +133,9 @@
         for consumer in self._consumers:
             consumer.pause()
 
+        for _ in self._consumers:
+            self._queue.put(_SHUTDOWN_SENTINEL)
+
         for consumer in self._consumers:
             try:
                 consumer.join()
@@ -132,6 +148,7 @@
 
 class PromptCache:
     _cache: Dict[str, PromptCacheItem]
+    _lock: RLock
 
     _task_manager: PromptCacheTaskManager
     """Task manager for refreshing cache"""
@@ -140,34 +157,60 @@
         self, max_prompt_refresh_workers: int = 
DEFAULT_PROMPT_CACHE_REFRESH_WORKERS
     ):
         self._cache = {}
+        self._lock = RLock()
         self._task_manager = 
PromptCacheTaskManager(threads=max_prompt_refresh_workers)
         logger.debug("Prompt cache initialized.")
 
     def get(self, key: str) -> Optional[PromptCacheItem]:
-        return self._cache.get(key, None)
+        with self._lock:
+            return self._cache.get(key, None)
 
     def set(self, key: str, value: PromptClient, ttl_seconds: Optional[int]) 
-> None:
         if ttl_seconds is None:
             ttl_seconds = DEFAULT_PROMPT_CACHE_TTL_SECONDS
 
-        self._cache[key] = PromptCacheItem(value, ttl_seconds)
+        with self._lock:
+            self._cache[key] = PromptCacheItem(value, ttl_seconds)
 
     def delete(self, key: str) -> None:
-        self._cache.pop(key, None)
+        with self._lock:
+            self._cache.pop(key, None)
 
     def invalidate(self, prompt_name: str) -> None:
         """Invalidate all cached prompts with the given prompt name."""
-        for key in list(self._cache):
-            if key.startswith(prompt_name):
-                del self._cache[key]
+        with self._lock:
+            for key in list(self._cache):
+                if key.startswith(prompt_name):
+                    del self._cache[key]
 
     def add_refresh_prompt_task(self, key: str, fetch_func: Callable[[], 
None]) -> None:
         logger.debug(f"Submitting refresh task for key: {key}")
         self._task_manager.add_task(key, fetch_func)
 
+    def add_refresh_prompt_task_if_current(
+        self,
+        key: str,
+        expected_item: PromptCacheItem,
+        fetch_func: Callable[[], None],
+    ) -> None:
+        with self._lock:
+            current_item = self._cache.get(key)
+            if (
+                current_item is not None
+                and current_item is not expected_item
+                and not current_item.is_expired()
+            ):
+                logger.debug(
+                    f"Skipping refresh task for key: {key} because cache is 
already fresh."
+                )
+                return
+
+        self.add_refresh_prompt_task(key, fetch_func)
+
     def clear(self) -> None:
         """Clear the entire prompt cache, removing all cached prompts."""
-        self._cache.clear()
+        with self._lock:
+            self._cache.clear()
 
     @staticmethod
     def generate_cache_key(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langfuse-4.2.0/langfuse/api/blob_storage_integrations/client.py 
new/langfuse-4.3.1/langfuse/api/blob_storage_integrations/client.py
--- old/langfuse-4.2.0/langfuse/api/blob_storage_integrations/client.py 
1970-01-01 01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/langfuse/api/blob_storage_integrations/client.py 
1970-01-01 01:00:00.000000000 +0100
@@ -108,7 +108,7 @@
         type : BlobStorageIntegrationType
 
         bucket_name : str
-            Name of the storage bucket
+            Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a 
valid Azure container name (3-63 chars, lowercase letters, numbers, and hyphens 
only, must start and end with a letter or number, no consecutive hyphens).
 
         region : str
             Storage region
@@ -367,7 +367,7 @@
         type : BlobStorageIntegrationType
 
         bucket_name : str
-            Name of the storage bucket
+            Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a 
valid Azure container name (3-63 chars, lowercase letters, numbers, and hyphens 
only, must start and end with a letter or number, no consecutive hyphens).
 
         region : str
             Storage region
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langfuse-4.2.0/langfuse/api/blob_storage_integrations/raw_client.py 
new/langfuse-4.3.1/langfuse/api/blob_storage_integrations/raw_client.py
--- old/langfuse-4.2.0/langfuse/api/blob_storage_integrations/raw_client.py     
1970-01-01 01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/langfuse/api/blob_storage_integrations/raw_client.py     
1970-01-01 01:00:00.000000000 +0100
@@ -165,7 +165,7 @@
         type : BlobStorageIntegrationType
 
         bucket_name : str
-            Name of the storage bucket
+            Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a 
valid Azure container name (3-63 chars, lowercase letters, numbers, and hyphens 
only, must start and end with a letter or number, no consecutive hyphens).
 
         region : str
             Storage region
@@ -642,7 +642,7 @@
         type : BlobStorageIntegrationType
 
         bucket_name : str
-            Name of the storage bucket
+            Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a 
valid Azure container name (3-63 chars, lowercase letters, numbers, and hyphens 
only, must start and end with a letter or number, no consecutive hyphens).
 
         region : str
             Storage region
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/langfuse-4.2.0/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py
 
new/langfuse-4.3.1/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py
--- 
old/langfuse-4.2.0/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py
  1970-01-01 01:00:00.000000000 +0100
+++ 
new/langfuse-4.3.1/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py
  1970-01-01 01:00:00.000000000 +0100
@@ -26,7 +26,7 @@
         pydantic.Field()
     )
     """
-    Name of the storage bucket
+    Name of the storage bucket. For AZURE_BLOB_STORAGE, must be a valid Azure 
container name (3-63 chars, lowercase letters, numbers, and hyphens only, must 
start and end with a letter or number, no consecutive hyphens).
     """
 
     endpoint: typing.Optional[str] = pydantic.Field(default=None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langfuse-4.2.0/langfuse/langchain/CallbackHandler.py 
new/langfuse-4.3.1/langfuse/langchain/CallbackHandler.py
--- old/langfuse-4.2.0/langfuse/langchain/CallbackHandler.py    1970-01-01 
01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/langfuse/langchain/CallbackHandler.py    1970-01-01 
01:00:00.000000000 +0100
@@ -287,6 +287,11 @@
         ):
             attributes["user_id"] = metadata["langfuse_user_id"]
 
+        if "langfuse_trace_name" in metadata and isinstance(
+            metadata["langfuse_trace_name"], str
+        ):
+            attributes["trace_name"] = metadata["langfuse_trace_name"]
+
         if tags is not None or (
             "langfuse_tags" in metadata and 
isinstance(metadata["langfuse_tags"], list)
         ):
@@ -369,6 +374,7 @@
                     session_id=parsed_trace_attributes.get("session_id", None),
                     tags=parsed_trace_attributes.get("tags", None),
                     metadata=parsed_trace_attributes.get("metadata", None),
+                    trace_name=parsed_trace_attributes.get("trace_name", None),
                 )
 
                 self._propagation_context_manager.__enter__()
@@ -1403,6 +1409,7 @@
         "langfuse_session_id",
         "langfuse_user_id",
         "langfuse_tags",
+        "langfuse_trace_name",
     ]
 
     metadata_copy = metadata.copy()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/langfuse-4.2.0/pyproject.toml 
new/langfuse-4.3.1/pyproject.toml
--- old/langfuse-4.2.0/pyproject.toml   1970-01-01 01:00:00.000000000 +0100
+++ new/langfuse-4.3.1/pyproject.toml   1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 [project]
 name = "langfuse"
-version = "4.2.0"
+version = "4.3.1"
 description = "A client library for accessing langfuse"
 readme = "README.md"
 authors = [{ name = "langfuse", email = "[email protected]" }]
@@ -53,6 +53,12 @@
 
 [tool.pytest.ini_options]
 log_cli = true
+markers = [
+    "unit: deterministic tests that run without a Langfuse server",
+    "e2e: tests that require a real Langfuse server or persisted backend 
behaviour",
+    "serial_e2e: e2e tests that must not share server concurrency with the 
rest of the suite",
+    "live_provider: tests that call live model providers and run as a 
dedicated CI suite",
+]
 
 [tool.mypy]
 python_version = "3.12"

Reply via email to