⚡️ Speed up method AuthService.decrypt_api_key by 16% in PR #11639 (docs-chat-refactor-and-screenshots)#11644
Closed
codeflash-ai[bot] wants to merge 6 commits into
Closed
Conversation
The optimized code achieves a **15% speedup** through two key optimizations: ## 1. Fernet Instance Caching (Primary Optimization) The original code recreates the `Fernet` object on every call to `_get_fernet()`, which involves expensive cryptographic key processing. The line profiler shows: - **Original**: `_ensure_valid_key()` takes 61.3% of `_get_fernet()` time (6.35ms), and `Fernet(valid_key)` takes 20.7% (2.14ms) - **Optimized**: These operations are cached, only executing on first call or secret key change The optimization adds `_fernet_cache` and `_cached_secret_key` instance variables in `__init__()`. In `_get_fernet()`, it checks if the cache is valid before recreating the Fernet object. With 212 calls to `_get_fernet()`, but only 13 cache misses (line profiler shows 13 hits for `_ensure_valid_key`), this means **199 calls avoided expensive key derivation**, reducing `_get_fernet()` total time from 10.37ms to 2.57ms (**~75% reduction**). ## 2. Fast-Path for Invalid Inputs The original code called `logger.debug()` for every invalid input, taking 31.8% of `decrypt_api_key()` time (14.69ms for 910 hits). The optimized version reorders the condition check (`if not encrypted_api_key or not isinstance(...)`) and removes the debug log entirely, reducing overhead from 16.1ms to 0.87ms per invalid input check. ## Impact on Test Cases - **Batch decryption tests** (e.g., `test_large_scale_decryption_batch` with 200 tokens, `test_decrypt_many_plaintext_keys` with 500 keys) benefit most since the Fernet instance is reused across all decryptions - **Invalid input tests** (e.g., `test_decrypt_empty_and_none_batch` with 500 empty/None values) see faster returns due to the optimized fast-path - **Single decryption tests** show minimal improvement since caching benefits are proportional to call frequency The optimizations are safe because the Fernet instance depends only on the secret key, which is checked on every call to detect configuration changes.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## docs-1.8-release #11644 +/- ##
===================================================
Coverage ? 35.21%
===================================================
Files ? 1521
Lines ? 72927
Branches ? 10936
===================================================
Hits ? 25681
Misses ? 45851
Partials ? 1395
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Base automatically changed from
docs-chat-refactor-and-screenshots
to
docs-1.8-release
February 10, 2026 16:03
Contributor
Author
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
⚡️ This pull request contains optimizations for PR #11639
If you approve this dependent PR, these changes will be merged into the original PR branch
docs-chat-refactor-and-screenshots.📄 16% (0.16x) speedup for
AuthService.decrypt_api_keyinsrc/backend/base/langflow/services/auth/service.py⏱️ Runtime :
240 microseconds→208 microseconds(best of26runs)📝 Explanation and details
The optimized code achieves a 15% speedup through two key optimizations:
1. Fernet Instance Caching (Primary Optimization)
The original code recreates the
Fernetobject on every call to_get_fernet(), which involves expensive cryptographic key processing. The line profiler shows:_ensure_valid_key()takes 61.3% of_get_fernet()time (6.35ms), andFernet(valid_key)takes 20.7% (2.14ms)The optimization adds
_fernet_cacheand_cached_secret_keyinstance variables in__init__(). In_get_fernet(), it checks if the cache is valid before recreating the Fernet object. With 212 calls to_get_fernet(), but only 13 cache misses (line profiler shows 13 hits for_ensure_valid_key), this means 199 calls avoided expensive key derivation, reducing_get_fernet()total time from 10.37ms to 2.57ms (~75% reduction).2. Fast-Path for Invalid Inputs
The original code called
logger.debug()for every invalid input, taking 31.8% ofdecrypt_api_key()time (14.69ms for 910 hits). The optimized version reorders the condition check (if not encrypted_api_key or not isinstance(...)) and removes the debug log entirely, reducing overhead from 16.1ms to 0.87ms per invalid input check.Impact on Test Cases
test_large_scale_decryption_batchwith 200 tokens,test_decrypt_many_plaintext_keyswith 500 keys) benefit most since the Fernet instance is reused across all decryptionstest_decrypt_empty_and_none_batchwith 500 empty/None values) see faster returns due to the optimized fast-pathThe optimizations are safe because the Fernet instance depends only on the secret key, which is checked on every call to detect configuration changes.
✅ Correctness verification report:
🌀 Click to see Generated Regression Tests
import base64
import random
import types
import pytest # used for our unit tests
from cryptography.fernet import Fernet
from langflow.services.auth.service import AuthService
Note:
The tests below import and use the real AuthService class from its module path.
In case the real package/module is not available in the test environment, these imports
will raise ImportError when running tests. The test suite assumes the codebase layout
matches the original source path: langflow.services.auth.service.AuthService
Helper to build a settings-like object expected by AuthService.init
def make_settings_with_secret(secret_value: str):
"""
Construct a minimal object graph that matches what AuthService expects:
settings.auth_settings.SECRET_KEY.get_secret_value() -> secret_value
Helper replicating the short-key branch of _ensure_valid_key to derive a Fernet key
This mirrors the implementation in AuthService._ensure_valid_key when raw_key length < 32
def derive_fernet_key_from_short_secret(raw_key: str) -> bytes:
# Seed the random module with the raw_key to get deterministic bytes (same as AuthService)
random.seed(raw_key)
key_bytes = bytes(random.getrandbits(8) for _ in range(32))
return base64.urlsafe_b64encode(key_bytes)
def test_decrypt_api_key_invalid_inputs():
# Create service instance with a deterministic short secret so it constructs a Fernet
settings = make_settings_with_secret("short_secret") # length < 32 -> deterministic key branch
service = AuthService(settings)
def test_decrypt_api_key_plaintext_returned_as_is():
# Plaintext strings not starting with 'gAAAAA' should be returned unchanged
settings = make_settings_with_secret("short_secret")
service = AuthService(settings)
def test_decrypt_api_key_successful_decryption_with_deterministic_short_secret():
# Use a short secret so AuthService will use the deterministic random-based key logic
raw_secret = "short_secret"
settings = make_settings_with_secret(raw_secret)
service = AuthService(settings)
def test_decrypt_api_key_wrong_secret_returns_empty():
# Create two different short secrets so that keys differ deterministically
encrypt_secret = "encrypt_me" # used to create token
service_secret = "service_has_different_secret" # used by the service to decrypt
def test_decrypt_api_key_retry_path_primary_fails_secondary_succeeds(monkeypatch):
"""
Force the primary decryption attempt to raise an exception while the secondary
(retry) call succeeds. We patch AuthService._get_fernet to return a custom object
whose decrypt method behaves differently depending on input type.
This verifies the retry logic inside decrypt_api_key.
"""
settings = make_settings_with_secret("short_secret")
service = AuthService(settings)
def test_decrypt_api_key_both_attempts_fail_logs_and_returns_empty(monkeypatch):
"""
Simulate both decrypt attempts failing to ensure decrypt_api_key returns an empty string.
We patch _get_fernet so decrypt always raises.
"""
settings = make_settings_with_secret("short_secret")
service = AuthService(settings)
def test_large_scale_decryption_batch():
"""
Create a moderate batch of encrypted tokens (below 1000 elements as required)
and decrypt them all to validate performance and correctness at scale.
We keep the batch size reasonably large (200) while staying within the constraint.
"""
raw_secret = "short_secret_batch"
settings = make_settings_with_secret(raw_secret)
service = AuthService(settings)
def test_non_fernet_but_prefix_present_returns_empty_if_decryption_fails(monkeypatch):
"""
If a string starts with gAAAAA but is not a valid token, the function will attempt to
decrypt it. If decryption fails, it should return an empty string. This test verifies that.
"""
settings = make_settings_with_secret("short_secret")
service = AuthService(settings)
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
import pytest
from cryptography.fernet import Fernet, InvalidToken
from langflow.services.auth.service import AuthService
def test_decrypt_plaintext_api_key():
"""Test that plaintext API keys (not starting with 'gAAAAA') are returned as-is."""
# Create a mock settings service
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_empty_string():
"""Test that empty string input returns empty string."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_none_input():
"""Test that None input returns empty string."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_non_string_input():
"""Test that non-string input returns empty string."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_whitespace_only_string():
"""Test that whitespace-only strings are treated as empty."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_very_long_plaintext_key():
"""Test decryption of very long plaintext API key."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_special_characters_plaintext():
"""Test decryption of plaintext key with special characters."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_unicode_characters_plaintext():
"""Test decryption of plaintext key with unicode characters."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_case_sensitive_gaaaaa_prefix():
"""Test that gAAAAA prefix check is case-sensitive."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_with_short_secret_key():
"""Test decryption when secret key is shorter than minimum length."""
mock_settings_service = Mock()
# Use a short secret key (less than 32 chars)
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "short_key"
def test_decrypt_many_plaintext_keys():
"""Test decryption of many plaintext API keys to assess performance."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_keys_with_varying_lengths():
"""Test decryption of plaintext keys with various lengths."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_empty_and_none_batch():
"""Test batch processing with empty strings and None values."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_consecutive_calls_same_key():
"""Test that consecutive decryption calls with same key are consistent."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_keys_with_common_prefixes():
"""Test decryption of plaintext keys with common prefixes."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_extreme_length_plaintext():
"""Test decryption with extremely long plaintext key (but reasonable for API keys)."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
def test_decrypt_performance_many_non_string_calls():
"""Test performance when handling many non-string inputs."""
mock_settings_service = Mock()
mock_settings_service.auth_settings.SECRET_KEY.get_secret_value.return_value = "test_secret_key_minimum_32_chars_"
To edit these changes
git checkout codeflash/optimize-pr11639-2026-02-07T00.49.34and push.