Skip to content

⚡️ Speed up method AuthService.encrypt_api_key by 12% in PR #11639 (docs-chat-refactor-and-screenshots)#11643

Closed
codeflash-ai[bot] wants to merge 6 commits into
docs-1.8-releasefrom
codeflash/optimize-pr11639-2026-02-07T00.39.04
Closed

⚡️ Speed up method AuthService.encrypt_api_key by 12% in PR #11639 (docs-chat-refactor-and-screenshots)#11643
codeflash-ai[bot] wants to merge 6 commits into
docs-1.8-releasefrom
codeflash/optimize-pr11639-2026-02-07T00.39.04

Conversation

@codeflash-ai
Copy link
Copy Markdown
Contributor

@codeflash-ai codeflash-ai Bot commented Feb 7, 2026

⚡️ 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.

This PR will be automatically closed if the original PR is merged.


📄 12% (0.12x) speedup for AuthService.encrypt_api_key in src/backend/base/langflow/services/auth/service.py

⏱️ Runtime : 2.66 milliseconds 2.38 milliseconds (best of 57 runs)

📝 Explanation and details

The optimized code achieves an 11% speedup by introducing instance-level caching for the Fernet object used in encryption operations.

Key Optimization: Fernet Instance Caching

The original code creates a new Fernet instance on every call to _get_fernet(), which is invoked for each encrypt_api_key() operation. The line profiler shows that Fernet(valid_key) construction takes 60.7% of the time in _get_fernet() (1.12ms out of 1.85ms per call).

The optimized version caches both the secret key and the constructed Fernet instance as instance attributes (_cached_secret_key and _cached_fernet). When _get_fernet() is called, it checks if:

  1. A cached Fernet instance exists
  2. The current secret key matches the cached secret key

If both conditions are true (which happens in 104 out of 111 calls based on the profiler), it returns the cached instance, avoiding expensive re-initialization.

Performance Impact

From the line profiler data:

  • Original: _get_fernet() takes 1.85ms total per call
  • Optimized: _get_fernet() takes only 0.58ms total (68% reduction)
    • Cache hits (104/111 calls) execute only the fast path with string comparison and cached return
    • Cache misses (7/111 calls) still pay the full cost but also populate the cache

The optimization reduces _get_fernet() overhead in encrypt_api_key() from 20% to 10.4% of total runtime.

Test Case Performance

The optimization is most effective for workloads with:

  • Repeated encryption calls with the same secret key (e.g., test_encrypt_many_different_api_keys, test_encrypt_api_key_repeated_calls_consistency) - the cache persists across multiple API key encryptions
  • Large-scale operations (test_large_scale_encrypt_many_api_keys_performance_and_correctness with 100 keys) - amortizes the one-time initialization cost

The cache correctly invalidates when the secret key changes, maintaining correctness while capturing the common case where the secret key remains constant across multiple encryption operations.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 113 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Click to see Generated Regression Tests
from __future__ import annotations

import base64
import base64 as _base64_module  # avoid shadowing the base64 import above
import random
import random as _random_module
# imports
import sys
import types
from enum import Enum

import pytest  # used for our unit tests
from cryptography.fernet import Fernet
from cryptography.fernet import Fernet as _Fernet
from langflow.services.auth.service import AuthService
from langflow.services.schema import ServiceType as _ServiceType
from lfx.services.auth.base import BaseAuthService as _BaseAuthService
from lfx.services.settings.service import SettingsService as _SettingsService

# -----------------------------------------------------------------------------
# Helper test utilities (small, local helper classes - not domain class stubs)
# These are used to provide the minimal settings structure required by AuthService.
# -----------------------------------------------------------------------------

class _SecretContainer:
    """Simple container exposing get_secret_value() as required by AuthService."""
    def __init__(self, value: str):
        self._value = value

    def get_secret_value(self) -> str:
        # Return the secret exactly as provided
        return self._value

class _AuthSettings:
    """Simple container holding a SECRET_KEY-like attribute expected by AuthService."""
    def __init__(self, secret_value: str):
        self.SECRET_KEY = _SecretContainer(secret_value)

class _Settings:
    """Container compatible with SettingsService annotation used by AuthService."""
    def __init__(self, secret_value: str):
        self.auth_settings = _AuthSettings(secret_value)


def test_encrypt_decrypt_with_valid_base64_key():
    """
    Basic functionality:
    - Use a valid Fernet key (generated by Fernet.generate_key()).
    - Ensure encrypt_api_key returns a string.
    - Ensure the returned encrypted token can be decrypted by the same service.
    """
    # Generate a correct Fernet key (urlsafe base64 of 32 bytes)
    valid_fernet_key = Fernet.generate_key().decode()  # str

    # Build a settings object that returns our valid key
    settings = _Settings(valid_fernet_key)

    # Instantiate the service with our settings
    service = AuthService(settings)

    # The API key to encrypt
    api_key = "my-plain-api-key"

    # Encrypt and assert type
    codeflash_output = service.encrypt_api_key(api_key); encrypted = codeflash_output

    # Decrypt using the service's fernet and verify content matches original
    f = service._get_fernet()
    decrypted = f.decrypt(encrypted.encode()).decode()


def test_encrypt_decrypt_with_short_secret_generates_deterministic_key_across_instances():
    """
    Edge case:
    - When SECRET_KEY is shorter than MINIMUM_KEY_LENGTH, the implementation uses
      random.seed(raw_key) to derive a deterministic key. Two separate AuthService
      instances with the same short secret should be able to decrypt one another's
      encrypted tokens.
    """
    short_secret = "short-secret"  # shorter than 32 chars -> triggers deterministic generation

    settings1 = _Settings(short_secret)
    settings2 = _Settings(short_secret)

    service1 = AuthService(settings1)
    service2 = AuthService(settings2)

    api_key = "edge-case-key"

    # Encrypt with first service
    codeflash_output = service1.encrypt_api_key(api_key); encrypted_by_1 = codeflash_output

    # Decrypt using second service instance: should succeed because seed-based key generation is deterministic
    decrypted_by_2 = service2._get_fernet().decrypt(encrypted_by_1.encode()).decode()


def test_invalid_long_non_base64_secret_raises_value_error():
    """
    Edge case:
    - When SECRET_KEY is long enough (>= MINIMUM_KEY_LENGTH) but not a valid
      base64-encoded 32-byte key, Fernet() constructor should raise an error.
    - The test asserts that encrypt_api_key raises ValueError in that situation.
    """
    # Create a non-base64, but long secret (e.g., 40 'x' characters)
    invalid_secret = "x" * 40  # >= 32 but not a valid Fernet key

    settings = _Settings(invalid_secret)
    service = AuthService(settings)

    with pytest.raises(Exception):
        # Attempting to encrypt should surface an error from Fernet initialization or use.
        # The exact exception from cryptography may be ValueError or similar; we accept any Exception.
        codeflash_output = service.encrypt_api_key("some-api-key"); _ = codeflash_output


def test_encrypt_empty_string_and_unicode_characters():
    """
    Edge cases:
    - Empty API key string should be acceptable and round-trip correctly.
    - API keys containing unicode (including emoji) should round-trip correctly.
    """
    valid_fernet_key = Fernet.generate_key().decode()
    settings = _Settings(valid_fernet_key)
    service = AuthService(settings)

    # Empty string case
    codeflash_output = service.encrypt_api_key(""); empty_encrypted = codeflash_output

    # Unicode string case
    unicode_key = "pāsswørd-🔒-测试"
    codeflash_output = service.encrypt_api_key(unicode_key); unicode_encrypted = codeflash_output
    decrypted_unicode = service._get_fernet().decrypt(unicode_encrypted.encode()).decode()


def test_large_scale_encrypt_many_api_keys_performance_and_correctness():
    """
    Large scale test:
    - Encrypt and decrypt a moderate number of API keys (100 keys) to assess
      scalability and correctness under load.
    - We keep iterations under 1000 as required by the instructions.
    """
    valid_fernet_key = Fernet.generate_key().decode()
    settings = _Settings(valid_fernet_key)
    service = AuthService(settings)

    # Prepare 100 distinct API keys (keeps memory and loops within allowed bounds)
    api_keys = [f"bulk-key-{i}" for i in range(100)]

    # Encrypt all keys and store tokens
    encrypted_tokens = []
    for key in api_keys:
        codeflash_output = service.encrypt_api_key(key); token = codeflash_output
        encrypted_tokens.append(token)

    # Decrypt and assert each maps back to original
    fernet = service._get_fernet()
    for original, token in zip(api_keys, encrypted_tokens):
        pass


def test_consistency_between__get_fernet_and_encrypt_api_key_decryption():
    """
    Sanity test:
    - Ensure that encryption results from encrypt_api_key can be decrypted by the
      Fernet instance returned by service._get_fernet(), validating internal consistency.
    """
    valid_fernet_key = Fernet.generate_key().decode()
    settings = _Settings(valid_fernet_key)
    service = AuthService(settings)

    api_key = "consistent-key"
    codeflash_output = service.encrypt_api_key(api_key); encrypted = codeflash_output

    # Decrypt using separately retrieved Fernet instance
    fernet = service._get_fernet()
# 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 langflow.services.auth.service import AuthService


class TestAuthServiceEncryptApiKey:
    """Test suite for AuthService.encrypt_api_key method"""

    @pytest.fixture
    def mock_settings_service(self):
        """Create a mock SettingsService with proper auth_settings configuration"""
        mock_service = Mock()
        mock_auth_settings = Mock()
        mock_secret = Mock()
        mock_secret.get_secret_value.return_value = "test_secret_key_for_encryption_purposes"
        mock_auth_settings.SECRET_KEY = mock_secret
        mock_service.auth_settings = mock_auth_settings
        return mock_service

    @pytest.fixture
    def auth_service(self, mock_settings_service):
        """Create an AuthService instance with mocked dependencies"""
        service = AuthService(mock_settings_service)
        # Replace settings property to use our mock
        service.settings = mock_settings_service
        return service

    # ===================== BASIC TEST CASES =====================

    def test_encrypt_api_key_basic_string(self, auth_service):
        """Test encryption of a simple API key string"""
        api_key = "test_api_key_12345"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_api_key_returns_string(self, auth_service):
        """Test that encrypt_api_key returns a string type"""
        api_key = "simple_key"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_api_key_non_empty_output(self, auth_service):
        """Test that encryption produces non-empty output"""
        api_key = "my_api_key"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_different_keys_produce_different_output(self, auth_service):
        """Test that different API keys produce different encrypted outputs"""
        api_key1 = "key_one"
        api_key2 = "key_two"
        
        codeflash_output = auth_service.encrypt_api_key(api_key1); result1 = codeflash_output
        codeflash_output = auth_service.encrypt_api_key(api_key2); result2 = codeflash_output

    def test_encrypt_same_key_produces_same_output(self, auth_service):
        """Test that encrypting the same key twice produces the same output"""
        api_key = "consistent_key"
        
        codeflash_output = auth_service.encrypt_api_key(api_key); result1 = codeflash_output
        codeflash_output = auth_service.encrypt_api_key(api_key); result2 = codeflash_output

    # ===================== EDGE CASE TEST CASES =====================

    def test_encrypt_empty_api_key(self, auth_service):
        """Test encryption of an empty string API key"""
        api_key = ""
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_very_short_api_key(self, auth_service):
        """Test encryption of a very short API key (single character)"""
        api_key = "a"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_api_key_with_special_characters(self, auth_service):
        """Test encryption of API keys containing special characters"""
        api_key = "key!@#$%^&*()_+-=[]{}|;:',.<>?/"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_api_key_with_unicode_characters(self, auth_service):
        """Test encryption of API keys containing unicode characters"""
        api_key = "key_with_unicode_\u00e9\u00f1\u00fc"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_api_key_with_whitespace(self, auth_service):
        """Test encryption of API keys with various whitespace characters"""
        api_key = "key with spaces\tand\ttabs\nand\nnewlines"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_api_key_with_only_whitespace(self, auth_service):
        """Test encryption of API key containing only whitespace"""
        api_key = "   \t\n   "
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_very_long_api_key(self, auth_service):
        """Test encryption of a very long API key"""
        api_key = "x" * 10000
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_api_key_with_null_character(self, auth_service):
        """Test encryption of API key containing null characters"""
        api_key = "key\x00with\x00nulls"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_api_key_numeric_string(self, auth_service):
        """Test encryption of numeric string API key"""
        api_key = "123456789"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_api_key_all_special_chars_numeric(self, auth_service):
        """Test encryption of API key with mixed alphanumeric and special characters"""
        api_key = "abc123!@#XYZ789$%^"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    # ===================== LARGE SCALE TEST CASES =====================

    def test_encrypt_many_different_api_keys(self, auth_service):
        """Test encryption of many different API keys to ensure consistency"""
        api_keys = [f"api_key_{i}" for i in range(100)]
        results = []
        
        # Encrypt multiple keys
        for api_key in api_keys:
            codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output
            results.append(result)

    def test_encrypt_api_key_repeated_calls_consistency(self, auth_service):
        """Test that repeated encryption calls on the same key produce consistent results"""
        api_key = "test_consistency_key"
        results = []
        
        # Encrypt the same key 50 times
        for _ in range(50):
            codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output
            results.append(result)

    def test_encrypt_keys_with_varying_lengths(self, auth_service):
        """Test encryption of keys with varying lengths from 1 to 1000"""
        results = {}
        
        # Test keys of increasing length
        for length in [1, 10, 50, 100, 250, 500, 1000]:
            api_key = "k" * length
            codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output
            results[length] = result

    def test_encrypt_api_keys_with_repeated_patterns(self, auth_service):
        """Test encryption of API keys with repeated character patterns"""
        patterns = [
            "a" * 500,
            "ab" * 250,
            "abc" * 166,
            "12345" * 100,
            "!@#$%" * 100
        ]
        results = []
        
        for pattern in patterns:
            codeflash_output = auth_service.encrypt_api_key(pattern); result = codeflash_output
            results.append(result)

    def test_encrypt_sequential_similar_keys(self, auth_service):
        """Test encryption of similar keys with slight variations"""
        base_key = "base_api_key_"
        results = {}
        
        # Create similar keys by appending different suffixes
        for i in range(100):
            api_key = base_key + str(i)
            codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output
            results[i] = result

    def test_encrypt_api_key_output_is_valid_base64_like(self, auth_service):
        """Test that encrypted output looks like valid Fernet token (base64-like)"""
        api_key = "test_token_validity"
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_keys_from_different_character_sets(self, auth_service):
        """Test encryption with keys from different character sets"""
        character_sets = {
            "lowercase": "abcdefghijklmnopqrstuvwxyz",
            "uppercase": "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
            "digits": "0123456789",
            "special": "!@#$%^&*()-_=+[]{}|;:,.<>?",
            "mixed": "aB1!cD2@eF3#"
        }
        results = {}
        
        for charset_name, charset_chars in character_sets.items():
            api_key = charset_chars * 10
            codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output
            results[charset_name] = result

    def test_encrypt_api_key_with_large_unicode_content(self, auth_service):
        """Test encryption with large content containing various unicode characters"""
        unicode_chars = "abcdefghijklmnopqrstuvwxyz"
        unicode_chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
        unicode_chars += "0123456789"
        unicode_chars += "\u00e9\u00e8\u00ea\u00eb\u00fc\u00f9\u00f1"  # Various accented chars
        
        api_key = unicode_chars * 50
        codeflash_output = auth_service.encrypt_api_key(api_key); result = codeflash_output

    def test_encrypt_alternating_pattern_keys(self, auth_service):
        """Test encryption of keys with alternating patterns"""
        patterns = [
            "01" * 500,
            "ab" * 500,
            "aAbB" * 250,
            "1a2b3c" * 166
        ]
        results = []
        
        for pattern in patterns:
            codeflash_output = auth_service.encrypt_api_key(pattern); result = codeflash_output
            results.append(result)
# 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-pr11639-2026-02-07T00.39.04 and push.

Codeflash

mendonk and others added 6 commits February 6, 2026 17:02
The optimized code achieves an **11% speedup** by introducing **instance-level caching** for the `Fernet` object used in encryption operations.

## Key Optimization: Fernet Instance Caching

The original code creates a new `Fernet` instance on every call to `_get_fernet()`, which is invoked for each `encrypt_api_key()` operation. The line profiler shows that `Fernet(valid_key)` construction takes **60.7% of the time** in `_get_fernet()` (1.12ms out of 1.85ms per call).

The optimized version caches both the secret key and the constructed `Fernet` instance as instance attributes (`_cached_secret_key` and `_cached_fernet`). When `_get_fernet()` is called, it checks if:
1. A cached Fernet instance exists
2. The current secret key matches the cached secret key

If both conditions are true (which happens in 104 out of 111 calls based on the profiler), it returns the cached instance, avoiding expensive re-initialization.

## Performance Impact

From the line profiler data:
- **Original**: `_get_fernet()` takes 1.85ms total per call
- **Optimized**: `_get_fernet()` takes only 0.58ms total (68% reduction)
  - Cache hits (104/111 calls) execute only the fast path with string comparison and cached return
  - Cache misses (7/111 calls) still pay the full cost but also populate the cache

The optimization reduces `_get_fernet()` overhead in `encrypt_api_key()` from **20%** to **10.4%** of total runtime.

## Test Case Performance

The optimization is most effective for workloads with:
- **Repeated encryption calls** with the same secret key (e.g., `test_encrypt_many_different_api_keys`, `test_encrypt_api_key_repeated_calls_consistency`) - the cache persists across multiple API key encryptions
- **Large-scale operations** (`test_large_scale_encrypt_many_api_keys_performance_and_correctness` with 100 keys) - amortizes the one-time initialization cost

The cache correctly invalidates when the secret key changes, maintaining correctness while capturing the common case where the secret key remains constant across multiple encryption operations.
@codeflash-ai codeflash-ai Bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Feb 7, 2026
@github-actions github-actions Bot added the community Pull Request from an external contributor label Feb 7, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 7, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (docs-1.8-release@cdacc17). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@                 Coverage Diff                 @@
##             docs-1.8-release   #11643   +/-   ##
===================================================
  Coverage                    ?   35.21%           
===================================================
  Files                       ?     1521           
  Lines                       ?    72930           
  Branches                    ?    10936           
===================================================
  Hits                        ?    25679           
  Misses                      ?    45856           
  Partials                    ?     1395           
Flag Coverage Δ
backend 55.66% <100.00%> (?)
lfx 42.10% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/backend/base/langflow/services/auth/service.py 67.06% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Base automatically changed from docs-chat-refactor-and-screenshots to docs-1.8-release February 10, 2026 16:03
@codeflash-ai codeflash-ai Bot closed this Feb 10, 2026
@codeflash-ai
Copy link
Copy Markdown
Contributor Author

codeflash-ai Bot commented Feb 10, 2026

This PR has been automatically closed because the original PR #11639 by mendonk was closed.

@codeflash-ai codeflash-ai Bot deleted the codeflash/optimize-pr11639-2026-02-07T00.39.04 branch February 10, 2026 16:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI community Pull Request from an external contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant