⚡️ Speed up function is_encrypted by 148% in PR #11565 (refactor/framework-agnostic-auth-service)#11568
⚡️ Speed up function is_encrypted by 148% in PR #11565 (refactor/framework-agnostic-auth-service)#11568codeflash-ai[bot] wants to merge 153 commits into
is_encrypted by 148% in PR #11565 (refactor/framework-agnostic-auth-service)#11568Conversation
…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 optimized code achieves a **147% speedup** (958μs → 387μs) by introducing **memoization** via `lru_cache(maxsize=1024)` on the `decrypt_api_key` function. This optimization specifically targets repeated decryption calls with identical encrypted API keys. **Key optimization:** - **`@lru_cache(maxsize=1024)`** wraps the decryption logic in `_cached_decrypt()`. When the same encrypted API key is decrypted multiple times, subsequent calls return the cached result instantly instead of performing expensive cryptographic operations through the auth service. **Why this leads to speedup:** 1. **Cryptographic decryption is computationally expensive** - The line profiler shows `auth_utils.decrypt_api_key(value)` consumes ~99% of execution time (289-291ms out of ~292-294ms total in `is_encrypted`). 2. **High cache hit rate in test workloads** - The annotated tests reveal many scenarios with repeated decryption attempts: - `test_is_encrypted_repeated_calls_same_value`: 100 calls with identical value - `test_is_encrypted_performance_with_batch_of_values`: 500 batch operations - `test_is_encrypted_with_many_failed_decryptions`: 200 sequential attempts - `test_is_encrypted_stress_test_with_empty_decryption_results`: 300 calls with same pattern 3. **Cache effectiveness**: For workloads with duplicate encrypted keys (common in validation loops, batch processing, or configuration checks), the cache eliminates redundant decryption operations after the first call. **Additional improvement:** - **TYPE_CHECKING import guard** moves `SettingsService` import to type-check time only, reducing runtime import overhead (though this is a minor contributor compared to caching). **When this optimization excels:** - Scenarios with **repeated validation** of the same encrypted values (e.g., `test_is_encrypted_repeated_calls_same_value` processing 100 identical values) - **Batch processing** where the same API keys appear multiple times (`test_is_encrypted_with_mixed_encrypted_and_plain_values` with 250 mixed values) - Configuration validation where encrypted keys are checked multiple times during application lifetime **Potential impact on existing workloads:** - Without `function_references`, we cannot definitively determine if `decrypt_api_key` is in a hot path, but the test patterns (batch operations, stress tests with 200-500 iterations) suggest this function is called frequently in validation/processing pipelines. - The optimization assumes **encrypted keys have stable mappings** (same encrypted input always produces same decrypted output), which holds true for deterministic encryption schemes. - Memory footprint increases by up to 1024 cached entries, but this is negligible for typical API key sizes.
|
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.71%) 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 #11568 +/- ##
==========================================
+ Coverage 35.24% 35.38% +0.14%
==========================================
Files 1435 1437 +2
Lines 69091 69172 +81
Branches 10027 10019 -8
==========================================
+ Hits 24348 24479 +131
+ Misses 43507 43456 -51
- Partials 1236 1237 +1
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
Closing: removing CodeFlash integration. |
⚡️ 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.📄 148% (1.48x) speedup for
is_encryptedinsrc/backend/base/langflow/services/auth/mcp_encryption.py⏱️ Runtime :
958 microseconds→387 microseconds(best of122runs)📝 Explanation and details
The optimized code achieves a 147% speedup (958μs → 387μs) by introducing memoization via
lru_cache(maxsize=1024)on thedecrypt_api_keyfunction. This optimization specifically targets repeated decryption calls with identical encrypted API keys.Key optimization:
@lru_cache(maxsize=1024)wraps the decryption logic in_cached_decrypt(). When the same encrypted API key is decrypted multiple times, subsequent calls return the cached result instantly instead of performing expensive cryptographic operations through the auth service.Why this leads to speedup:
Cryptographic decryption is computationally expensive - The line profiler shows
auth_utils.decrypt_api_key(value)consumes ~99% of execution time (289-291ms out of ~292-294ms total inis_encrypted).High cache hit rate in test workloads - The annotated tests reveal many scenarios with repeated decryption attempts:
test_is_encrypted_repeated_calls_same_value: 100 calls with identical valuetest_is_encrypted_performance_with_batch_of_values: 500 batch operationstest_is_encrypted_with_many_failed_decryptions: 200 sequential attemptstest_is_encrypted_stress_test_with_empty_decryption_results: 300 calls with same patternCache effectiveness: For workloads with duplicate encrypted keys (common in validation loops, batch processing, or configuration checks), the cache eliminates redundant decryption operations after the first call.
Additional improvement:
SettingsServiceimport to type-check time only, reducing runtime import overhead (though this is a minor contributor compared to caching).When this optimization excels:
test_is_encrypted_repeated_calls_same_valueprocessing 100 identical values)test_is_encrypted_with_mixed_encrypted_and_plain_valueswith 250 mixed values)Potential impact on existing workloads:
function_references, we cannot definitively determine ifdecrypt_api_keyis in a hot path, but the test patterns (batch operations, stress tests with 200-500 iterations) suggest this function is called frequently in validation/processing pipelines.✅ Correctness verification report:
⚙️ Click to see Existing Unit Tests
🌀 Click to see Generated Regression Tests
import pytest # used for our unit tests
from cryptography.fernet import
InvalidToken # specific exception the code handles
from langflow.services.auth import mcp_encryption
from langflow.services.auth import utils as auth_utils # module under test
from langflow.services.auth.mcp_encryption import is_encrypted
def test_empty_string_returns_false_and_does_not_call_decrypt(monkeypatch):
# Ensure that for an empty string the function returns False and does NOT call decrypt_api_key.
# Patch decrypt_api_key to raise if it's called; if is_encrypted calls it, the test will fail.
def fail_if_called(_):
raise AssertionError("decrypt_api_key should not be called for empty input")
def test_decrypt_returns_same_value_not_encrypted(monkeypatch):
# If decrypt_api_key returns the exact same string, treat as NOT encrypted.
def echo(value):
# Return same value to simulate unexpected behavior where decrypt returns the input unchanged
return value
def test_decrypt_returns_different_value_encrypted(monkeypatch):
# If decrypt_api_key returns a different value, it's considered encrypted.
def decrypt_sim(value):
# Simulate successful decryption to a different plaintext
return "decrypted-" + value
def test_decrypt_returns_empty_string_assumed_encrypted(monkeypatch):
# If decrypt_api_key returns an empty string (falsy), the implementation treats it as encrypted.
def decrypt_empty(_):
return ""
def test_decrypt_returns_none_treated_as_encrypted(monkeypatch):
# If decrypt_api_key returns None (falsy), should be treated as encrypted via the
if not decryptedcheck.def decrypt_none(_):
return None
@pytest.mark.parametrize("exc", [ValueError, TypeError, KeyError, InvalidToken])
def test_decrypt_raises_known_exceptions_treated_as_encrypted(monkeypatch, exc):
# The function catches ValueError, TypeError, KeyError, and InvalidToken and returns True.
def raise_exc(_):
raise exc("simulated decryption failure")
def test_decrypt_raises_unexpected_exception_propagates(monkeypatch):
# Unexpected exceptions (not in the except list) should propagate.
def raise_runtime(_):
raise RuntimeError("unexpected failure")
def test_whitespace_string_is_handled(monkeypatch):
# A whitespace-only string is truthy so decrypt_api_key is invoked.
# If decrypt returns same whitespace, it's not considered encrypted.
def echo(value):
return value
def test_large_input_decrypted_to_different_value(monkeypatch):
# Large-scale input: string near 1000 characters (avoid huge loops per requirements).
# The function performs a single decrypt call, so it should handle long strings quickly.
large_token = "A" * 999 # 999 chars to stay within suggested limits
def test_large_input_decrypted_to_same_value(monkeypatch):
# Large-scale input that decrypts to the same (unlikely but possible), should be treated as NOT encrypted.
large_token = "B" * 999
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 cryptography.fernet import InvalidToken
from langflow.services.auth.mcp_encryption import is_encrypted
def test_is_encrypted_with_empty_string():
"""Test that an empty string is not considered encrypted."""
# Empty strings should return False immediately
codeflash_output = is_encrypted("")
def test_is_encrypted_with_none_like_empty_string():
"""Test that whitespace-only strings are not considered encrypted."""
# Single space should be treated as truthy but fail decryption
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.side_effect = InvalidToken()
# A string with only whitespace should attempt decryption and fail
codeflash_output = is_encrypted(" "); result = codeflash_output
def test_is_encrypted_with_valid_encrypted_string():
"""Test that a successfully decrypted value is recognized as encrypted."""
# When decryption succeeds and returns different value, it's encrypted
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.return_value = "decrypted_key_123"
# Original encrypted value
codeflash_output = is_encrypted("gAAAAABl1234567890..."); result = codeflash_output
def test_is_encrypted_with_unencrypted_string():
"""Test that a plain string that decrypts to itself is not considered encrypted."""
# When decryption returns the same value, it's not encrypted
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
plain_text = "my_plain_api_key"
mock_decrypt.return_value = plain_text
codeflash_output = is_encrypted(plain_text); result = codeflash_output
def test_is_encrypted_with_invalid_token_exception():
"""Test that InvalidToken exception is caught and treated as encrypted."""
# InvalidToken from Fernet means it looks like encrypted but can't decrypt
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.side_effect = InvalidToken()
codeflash_output = is_encrypted("invalid_fernet_token"); result = codeflash_output
def test_is_encrypted_with_value_error():
"""Test that ValueError during decryption is treated as encrypted."""
# ValueError can occur during base64 decoding
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.side_effect = ValueError("Invalid base64")
codeflash_output = is_encrypted("not_valid_base64!@#$"); result = codeflash_output
def test_is_encrypted_with_type_error():
"""Test that TypeError during decryption is treated as encrypted."""
# TypeError might occur with wrong types
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.side_effect = TypeError("Expected string")
codeflash_output = is_encrypted("some_value"); result = codeflash_output
def test_is_encrypted_with_key_error():
"""Test that KeyError during decryption is treated as encrypted."""
# KeyError might occur if encryption keys are missing
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.side_effect = KeyError("encryption_key")
codeflash_output = is_encrypted("encrypted_value"); result = codeflash_output
def test_is_encrypted_with_empty_string_from_decryption():
"""Test that empty string returned from decryption is treated as encrypted."""
# If decryption returns empty string, it's encrypted but corrupted/wrong key
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.return_value = ""
codeflash_output = is_encrypted("corrupted_encrypted_value"); result = codeflash_output
def test_is_encrypted_with_very_long_string():
"""Test that very long encrypted strings are handled correctly."""
# Long base64-like strings that represent encrypted data
long_value = "a" * 10000
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.return_value = "short_decrypted"
codeflash_output = is_encrypted(long_value); result = codeflash_output
def test_is_encrypted_with_special_characters():
"""Test that strings with special characters are handled correctly."""
# Encrypted values often contain special characters from base64 encoding
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.return_value = "decrypted_value"
special_value = "gAAAAABl12+/34==456=="
codeflash_output = is_encrypted(special_value); result = codeflash_output
def test_is_encrypted_with_unicode_characters():
"""Test that Unicode characters in encrypted strings are handled."""
# Unicode characters might appear in base64-like encoded strings
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.return_value = "decrypted"
unicode_value = "test_🔒_encrypted"
codeflash_output = is_encrypted(unicode_value); result = codeflash_output
def test_is_encrypted_with_single_character():
"""Test that single character strings are handled correctly."""
# Edge case: single character should attempt decryption
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.side_effect = InvalidToken()
codeflash_output = is_encrypted("a"); result = codeflash_output
def test_is_encrypted_with_whitespace_variations():
"""Test that strings with various whitespace are handled correctly."""
# Tab character
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.side_effect = InvalidToken()
codeflash_output = is_encrypted("\t"); result = codeflash_output
def test_is_encrypted_with_numeric_string():
"""Test that numeric strings are handled correctly."""
# Pure numbers that look like they could be encrypted
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.return_value = "123456789"
codeflash_output = is_encrypted("123456789"); result = codeflash_output
def test_is_encrypted_with_base64_like_string():
"""Test proper handling of valid-looking base64 encrypted strings."""
# Properly formatted base64 string
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.return_value = "decrypted_secret_key"
base64_value = "Z0FBQUFCTGwyNDU2MmE0Yy1kMmY2LTExZWMtOTM2OC04MjVmMzc2ZWU2Mzg="
codeflash_output = is_encrypted(base64_value); result = codeflash_output
def test_is_encrypted_with_all_exception_types_covered():
"""Test that all documented exception types are properly caught."""
# Test each exception type that is caught in the except clause
exceptions_to_test = [
(ValueError("test error"), True),
(TypeError("test error"), True),
(KeyError("test error"), True),
(InvalidToken(), True),
]
def test_is_encrypted_boundary_between_decrypted_and_unencrypted():
"""Test the boundary case where decrypted value is similar but not identical."""
# Value that decrypts to something slightly different
with patch('langflow.services.auth.utils.decrypt_api_key') as mock_decrypt:
mock_decrypt.return_value = "original_plus_extra"
original = "original"
codeflash_output = is_encrypted(original); result = codeflash_output
def test_is_encrypted_with_multiple_large_values():
"""Test that is_encrypted handles multiple large encrypted values correctly."""
# Simulate processing many large encrypted values
large_encrypted_values = [f"encrypted_value_{i:04d}" for i in range(100)]
def test_is_encrypted_with_large_data_payload():
"""Test is_encrypted with very large encrypted payload."""
# Simulate a large encrypted API key or token
large_payload = "a" * 100000
def test_is_encrypted_performance_with_batch_of_values():
"""Test that is_encrypted performs efficiently with a batch of values."""
# Create a batch of values to test performance
batch_size = 500
test_values = [f"value_{i}" for i in range(batch_size)]
def test_is_encrypted_with_many_failed_decryptions():
"""Test handling of many failed decryption attempts in sequence."""
# Simulate many failed decryption attempts
test_values = [f"invalid_encrypted_{i:03d}" for i in range(200)]
def test_is_encrypted_with_mixed_encrypted_and_plain_values():
"""Test is_encrypted with a large batch of mixed encrypted and plain values."""
# Mix of values - some encrypted, some plain
mixed_values = []
for i in range(250):
if i % 2 == 0:
mixed_values.append(f"encrypted_{i}")
else:
mixed_values.append(f"plain_{i}")
def test_is_encrypted_repeated_calls_same_value():
"""Test that repeated calls with the same value are consistent."""
# Call is_encrypted multiple times with the same value
test_value = "consistent_encrypted_value"
def test_is_encrypted_with_extreme_value_differences():
"""Test is_encrypted with extreme differences between encrypted and decrypted."""
# Test with very different length strings
test_cases = [
("x", "y" * 10000), # Short input, long output
("a" * 10000, "b"), # Long input, short output
("c" * 5000, "d" * 5000), # Same length, different content
]
def test_is_encrypted_stress_test_with_empty_decryption_results():
"""Test is_encrypted behavior with many empty decryption results."""
# Simulate 300 calls that result in empty decryption (wrong key scenario)
test_values = [f"encrypted_wrong_key_{i:03d}" for i in range(300)]
codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
To edit these changes
git checkout codeflash/optimize-pr11565-2026-02-03T18.48.26and push.