⚡️ Speed up function get_optional_user_store_api_key by 10% in PR #11565 (refactor/framework-agnostic-auth-service)#11575
Conversation
…ager for pluggable service discovery - Added `register_service` decorator to allow services to self-register with the ServiceManager. - Enhanced `ServiceManager` to support multiple service discovery mechanisms, including decorator-based registration, config files, and entry points. - Implemented methods for direct service class registration and plugin discovery from various sources, improving flexibility and extensibility of service management.
- Introduced VariableService class to handle environment variables with in-memory caching. - Added methods for getting, setting, deleting, and listing variables. - Included logging for service initialization and variable operations. - Created an __init__.py file to expose VariableService in the package namespace.
…teardown - Updated LocalStorageService to inherit from both StorageService and Service for improved functionality. - Added a name attribute for service identification. - Implemented an async teardown method for future extensibility, even though no cleanup is currently needed. - Refactored the constructor to ensure proper initialization of both parent classes.
…l logging functionality - Added `BaseTelemetryService` as an abstract base class defining the interface for telemetry services. - Introduced `TelemetryService`, a lightweight implementation that logs telemetry events without sending data. - Created `__init__.py` to expose the telemetry service in the package namespace. - Ensured robust async methods for logging various telemetry events and handling exceptions.
- Added `BaseTracingService` as an abstract base class defining the interface for tracing services. - Implemented `TracingService`, a lightweight version that logs trace events without external integrations. - Included async methods for starting and ending traces, tracing components, and managing logs and outputs. - Enhanced documentation for clarity on method usage and parameters.
- Introduced a new test suite for validating the functionality of the @register_service decorator. - Implemented tests for various service types including LocalStorageService, TelemetryService, and TracingService. - Verified behavior for service registration with and without overrides, ensuring correct service management. - Included tests for custom service implementations and preservation of class functionality. - Enhanced overall test coverage for the service registration mechanism.
- Introduced a suite of unit tests covering edge cases for service registration, lifecycle management, and dependency resolution. - Implemented integration tests to validate service loading from configuration files and environment variables. - Enhanced test coverage for various service types including LocalStorageService, TelemetryService, and VariableService. - Verified behavior for service registration with and without overrides, ensuring correct service management. - Ensured robust handling of error conditions and edge cases in service creation and configuration parsing.
- Introduced comprehensive unit tests for LocalStorageService, TelemetryService, TracingService, and VariableService. - Implemented integration tests to validate the interaction between minimal services. - Ensured robust coverage for file operations, service readiness, and exception handling. - Enhanced documentation within tests for clarity on functionality and expected behavior.
…ection - Revised the documentation to highlight the advantages of the pluggable service system. - Replaced the migration guide with a detailed overview of features such as automatic discovery, lazy instantiation, dependency injection, and lifecycle management. - Clarified examples of service registration and improved overall documentation for better understanding.
During rebase, the teardown method was added in two locations (lines 57 and 220). Removed the duplicate at line 57, keeping the one at the end of the class (line 220) which is the more appropriate location for cleanup methods.
…changes - Add MockSessionService fixtures to test files that use ServiceManager - Update LocalStorageService test instantiation to use mock session and settings services - Fix service count assertions to account for MockSessionService in fixtures - Remove duplicate class-level clean_manager fixtures in test_edge_cases.py These changes fix test failures caused by LocalStorageService requiring session_service and settings_service parameters instead of just data_dir.
- Fixed Diamond Inheritance in LocalStorageService - Added Circular Dependency Detection in _create_service_from_class - Fixed StorageService.teardown to Have Default Implementation
- The aiofile library uses native async I/O (libaio) which fails with EAGAIN (SystemError: 11, 'Resource temporarily unavailable') in containerized environments like GitHub Actions runners. - Switch to aiofiles which uses thread pool executors, providing reliable async file I/O across all environments including containers.
The discover_plugins() method had a TOCTOU (time-of-check to time-of-use) race condition. Since get() uses a keyed lock (per service name), multiple threads requesting different services could concurrently see _plugins_discovered=False and trigger duplicate plugin discovery. Wrap discover_plugins() with self._lock to ensure thread-safe access to the _plugins_discovered flag and prevent concurrent discovery execution.
…ager for pluggable service discovery - Added `register_service` decorator to allow services to self-register with the ServiceManager. - Enhanced `ServiceManager` to support multiple service discovery mechanisms, including decorator-based registration, config files, and entry points. - Implemented methods for direct service class registration and plugin discovery from various sources, improving flexibility and extensibility of service management.
…teardown - Updated LocalStorageService to inherit from both StorageService and Service for improved functionality. - Added a name attribute for service identification. - Implemented an async teardown method for future extensibility, even though no cleanup is currently needed. - Refactored the constructor to ensure proper initialization of both parent classes.
… and add auth service retrieval function
Consolidate all authentication methods into the AuthService class to
enable pluggable authentication implementations. The utils module now
contains thin wrappers that delegate to the registered auth service.
This allows alternative auth implementations (e.g., OIDC) to be
registered via the pluggable services system while maintaining
backward compatibility with existing code that imports from utils.
Changes:
- Move all auth logic (token creation, user validation, API key
security, password hashing, encryption) to AuthService
- Refactor utils.py to delegate to get_auth_service()
- Update function signatures to remove settings_service parameter
(now obtained from the service internally)
…vice parameter - Changed function to retrieve current user from access token instead of JWT. - Updated AuthServiceFactory to specify SettingsService type in create method. - Removed settings_service dependency from encryption and decryption functions, simplifying the code. This refactor enhances the clarity and maintainability of the authentication logic.
- Introduced comprehensive unit tests for AuthService, covering token creation, user validation, and authentication methods. - Added tests for pluggable authentication, ensuring correct delegation to registered services. - Enhanced test coverage for user authentication scenarios, including active/inactive user checks and token validation. These additions improve the reliability and maintainability of the authentication system.
…ai/langflow into pluggable-auth-service
…thub.com/langflow-ai/langflow into refactor/framework-agnostic-auth-service
The optimization achieves a **10% speedup** by introducing a **module-level cache** for the auth service instance, eliminating repeated service lookups. ## Key Changes **Before**: Each call to `decrypt_api_key()` invoked `_auth_service()` → `get_auth_service()`, performing a fresh service resolution every time. **After**: The `_resolve_auth_service()` function caches the auth service in `_cached_auth_service` on first access, returning the cached instance on subsequent calls. ## Why This Is Faster The line profiler reveals the core bottleneck: - **Original**: `decrypt_api_key()` spent **285ms** total, averaging **564μs per call** (506 hits) - **Optimized**: Same function spent **224ms** total, averaging **444μs per call** (506 hits) This **~120μs reduction per call** (~21% faster per invocation) comes from avoiding the service resolution overhead in `get_auth_service()`. Service resolution typically involves: - Dependency injection container lookups - Potential lazy initialization - Function call overhead through the indirection layer By caching the service instance, we pay this cost once per process lifetime instead of once per decryption operation. ## Impact on Workloads This optimization particularly benefits scenarios where: 1. **High-frequency decryption**: The `test_large_scale_many_users_decrypts_correctly` test (500 users) demonstrates consistent gains when decrypting multiple API keys in succession 2. **API request handling**: Each call to `get_optional_user_store_api_key()` that reaches the decryption path (98.7% of total time in the original) benefits from the cached lookup 3. **Concurrent requests**: Multiple threads/workers reuse the same cached auth service instance, multiplying the savings The optimization is **transparent** to callers—no API changes, no behavioral differences—making it safe for existing code paths while delivering measurable performance gains in decryption-heavy workloads.
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the
Comment |
Codecov Report❌ Patch coverage is ❌ Your project status has failed because the head coverage (41.91%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #11575 +/- ##
==========================================
+ Coverage 35.34% 35.47% +0.12%
==========================================
Files 1436 1438 +2
Lines 69241 69324 +83
Branches 10071 10063 -8
==========================================
+ Hits 24476 24591 +115
+ Misses 43515 43483 -32
Partials 1250 1250
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Closing automated codeflash PR. |
⚡️ This pull request contains optimizations for PR #11565
If you approve this dependent PR, these changes will be merged into the original PR branch
refactor/framework-agnostic-auth-service.📄 10% (0.10x) speedup for
get_optional_user_store_api_keyinsrc/backend/base/langflow/api/v1/store.py⏱️ Runtime :
8.14 milliseconds→7.38 milliseconds(best of11runs)📝 Explanation and details
The optimization achieves a 10% speedup by introducing a module-level cache for the auth service instance, eliminating repeated service lookups.
Key Changes
Before: Each call to
decrypt_api_key()invoked_auth_service()→get_auth_service(), performing a fresh service resolution every time.After: The
_resolve_auth_service()function caches the auth service in_cached_auth_serviceon first access, returning the cached instance on subsequent calls.Why This Is Faster
The line profiler reveals the core bottleneck:
decrypt_api_key()spent 285ms total, averaging 564μs per call (506 hits)This ~120μs reduction per call (~21% faster per invocation) comes from avoiding the service resolution overhead in
get_auth_service(). Service resolution typically involves:By caching the service instance, we pay this cost once per process lifetime instead of once per decryption operation.
Impact on Workloads
This optimization particularly benefits scenarios where:
test_large_scale_many_users_decrypts_correctlytest (500 users) demonstrates consistent gains when decrypting multiple API keys in successionget_optional_user_store_api_key()that reaches the decryption path (98.7% of total time in the original) benefits from the cached lookupThe optimization is transparent to callers—no API changes, no behavioral differences—making it safe for existing code paths while delivering measurable performance gains in decryption-heavy workloads.
✅ Correctness verification report:
🌀 Click to see Generated Regression Tests
from types import SimpleNamespace # simple container for user-like objects
from typing import List
from unittest.mock import ( # for patching external dependencies and spying
Mock, patch)
imports
import pytest # used for our unit tests
from langflow.api.v1.store import get_optional_user_store_api_key
def test_returns_none_when_store_api_key_is_none():
# Create a lightweight user-like object with store_api_key set to None
user = SimpleNamespace(store_api_key=None)
# When store_api_key is falsy (None), the function must return None immediately
codeflash_output = get_optional_user_store_api_key(user)
def test_returns_none_when_store_api_key_is_empty_string():
# Empty string is falsy in Python; function checks
if not user.store_api_keyuser = SimpleNamespace(store_api_key="")
# The function should treat empty string as absence and return None
codeflash_output = get_optional_user_store_api_key(user)
def test_successful_decryption_returns_decrypted_value():
# Prepare a user with a non-empty (truthy) store_api_key
encrypted = "enc-123"
user = SimpleNamespace(store_api_key=encrypted)
def test_decryption_raises_exception_returns_original_and_logs_exception():
# When the underlying decrypt path raises, get_optional_user_store_api_key must
# catch the exception and return the original stored API key.
encrypted = "enc-broken"
user = SimpleNamespace(store_api_key=encrypted)
def test_decrypt_returns_none_propagates_none():
# If decrypt_api_key returns None (unusual but possible), the function should
# return whatever decrypt_api_key returns (i.e., None).
user = SimpleNamespace(store_api_key="enc-none")
def test_whitespace_store_api_key_is_decrypted_not_treated_as_empty():
# A whitespace-only string is truthy and should be passed to decryption.
whitespace_key = " "
user = SimpleNamespace(store_api_key=whitespace_key)
def test_non_string_store_api_key_behaviour_numeric_zero_is_treated_as_absent():
# If store_api_key is numeric zero (0),
if not user.store_api_keywill treat it# as falsy and return None. This checks the function follows Python truthiness.
user = SimpleNamespace(store_api_key=0)
codeflash_output = get_optional_user_store_api_key(user)
def test_non_string_store_api_key_truthy_numeric_passes_to_decrypt():
# If store_api_key is a truthy non-string (e.g., 42), it will be passed to decrypt.
user = SimpleNamespace(store_api_key=42)
captured: List[object] = []
def test_large_scale_many_users_decrypts_correctly():
# Create a substantial list of users (500) to exercise repeated calls and ensure
# the function behaves consistently and scales reasonably for many invocations.
n = 500
users = [SimpleNamespace(store_api_key=f"key-{i}") for i in range(n)]
def test_function_does_not_mutate_user_object():
# The function should not change the user object passed in.
original_key = "immutable-key"
user = SimpleNamespace(store_api_key=original_key)
codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
from unittest.mock import MagicMock, Mock, patch
imports
import pytest
from langflow.api.utils import CurrentActiveUser
from langflow.api.v1.store import get_optional_user_store_api_key
class TestGetOptionalUserStoreApiKeyBasic:
"""Basic test cases for normal functionality."""
To edit these changes
git checkout codeflash/optimize-pr11565-2026-02-03T20.36.13and push.