⚡️ Speed up method AuthService.verify_password by 17% in PR #10702 (pluggable-auth-service)#10703
⚡️ Speed up method AuthService.verify_password by 17% in PR #10702 (pluggable-auth-service)#10703codeflash-ai[bot] wants to merge 25 commits intomainfrom
AuthService.verify_password by 17% in PR #10702 (pluggable-auth-service)#10703Conversation
…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.
… 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.
…rieval method - Replaced the mock for retrieving the current user from JWT to access token in the TestSuperuserCommand. - Refactored unit tests for MCP encryption to utilize AuthService instead of a mock settings service, enhancing test reliability. - Updated patch decorators in tests to reflect the new method of obtaining the AuthService, ensuring consistency across test cases. These changes improve the accuracy and maintainability of the authentication tests.
The optimization eliminates redundant attribute traversal by caching the `verify` method reference during initialization. **Key Changes:** - Added `self._verify = settings_service.auth_settings.pwd_context.verify` in `__init__` to cache the bound method - Modified `verify_password` to use the cached `self._verify` instead of traversing `self.settings.auth_settings.pwd_context.verify` **Why It's Faster:** The original code performs 3 attribute lookups (`self.settings` → `auth_settings` → `pwd_context` → `verify`) on every call to `verify_password`. The optimization reduces this to a single attribute lookup (`self._verify`) by pre-computing the method reference once during initialization. **Performance Impact:** The line profiler shows a **34% reduction** in per-call overhead (from 2921.4ns to 1930.9ns per call), resulting in a **16% overall speedup**. This optimization is particularly effective because: - Password verification is typically called frequently in authentication flows - The attribute chain traversal overhead becomes significant when called thousands of times (2759 hits in the profile) - The cached method maintains the same behavior while eliminating repetitive lookups **Test Case Benefits:** The optimization shows consistent improvements across all test scenarios, from basic verification tests to large-scale tests with 500+ password verifications, making it especially valuable for high-throughput authentication workloads.
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the Tip 📝 Customizable high-level summaries are now available in beta!You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.
Example instruction:
Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later. Comment |
Codecov Report❌ Patch coverage is ❌ Your project status has failed because the head coverage (40.37%) 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 #10703 +/- ##
==========================================
+ Coverage 30.64% 30.86% +0.21%
==========================================
Files 1318 1323 +5
Lines 59710 59670 -40
Branches 8926 8959 +33
==========================================
+ Hits 18298 18415 +117
+ Misses 40565 40392 -173
- Partials 847 863 +16
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
3418a59 to
8077046
Compare
e0f63cf to
77320af
Compare
|
Closing automated codeflash PR. |
⚡️ This pull request contains optimizations for PR #10702
If you approve this dependent PR, these changes will be merged into the original PR branch
pluggable-auth-service.📄 17% (0.17x) speedup for
AuthService.verify_passwordinsrc/backend/base/langflow/services/auth/service.py⏱️ Runtime :
1.00 millisecond→862 microseconds(best of128runs)📝 Explanation and details
The optimization eliminates redundant attribute traversal by caching the
verifymethod reference during initialization.Key Changes:
self._verify = settings_service.auth_settings.pwd_context.verifyin__init__to cache the bound methodverify_passwordto use the cachedself._verifyinstead of traversingself.settings.auth_settings.pwd_context.verifyWhy It's Faster:
The original code performs 3 attribute lookups (
self.settings→auth_settings→pwd_context→verify) on every call toverify_password. The optimization reduces this to a single attribute lookup (self._verify) by pre-computing the method reference once during initialization.Performance Impact:
The line profiler shows a 34% reduction in per-call overhead (from 2921.4ns to 1930.9ns per call), resulting in a 16% overall speedup. This optimization is particularly effective because:
Test Case Benefits:
The optimization shows consistent improvements across all test scenarios, from basic verification tests to large-scale tests with 500+ password verifications, making it especially valuable for high-throughput authentication workloads.
✅ Correctness verification report:
🌀 Generated Regression Tests and Runtime
from unittest.mock import MagicMock
imports
import pytest
from langflow.services.auth.service import AuthService
--- Function to test and minimal dependencies ---
Minimal stub for pwd_context with verify method
class DummyPwdContext:
def verify(self, plain_password, hashed_password):
# Simulate a real password verification logic for testing
# For the sake of this test, let's assume:
# - hashed_password is always "hashed:<plain_password>"
# - returns True if hashed_password matches this scheme, else False
if not isinstance(plain_password, str) or not isinstance(hashed_password, str):
raise TypeError("Passwords must be strings")
if plain_password == "" or hashed_password == "":
# Simulate behavior: empty passwords never match
return False
if hashed_password == f"hashed:{plain_password}":
return True
return False
Minimal stub for auth_settings
class DummyAuthSettings:
def init(self):
self.pwd_context = DummyPwdContext()
Minimal stub for SettingsService
class SettingsService:
def init(self):
self.auth_settings = DummyAuthSettings()
ServiceType stub
class ServiceType:
class AUTH_SERVICE:
value = "auth_service"
AuthServiceBase stub
class AuthServiceBase:
pass
from langflow.services.auth.service import AuthService
--- Unit Tests ---
@pytest.fixture
def auth_service():
# Fixture to provide an AuthService instance with dummy settings
return AuthService(SettingsService())
1. Basic Test Cases
def test_correct_password_returns_true(auth_service):
# Test that correct password returns True
plain = "hunter2"
hashed = "hashed:hunter2"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_incorrect_password_returns_false(auth_service):
# Test that incorrect password returns False
plain = "hunter2"
hashed = "hashed:wrongpass"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_completely_wrong_hash_returns_false(auth_service):
# Test that a hash not matching the scheme returns False
plain = "hunter2"
hashed = "somegarbagehash"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_case_sensitivity(auth_service):
# Passwords are case-sensitive
plain = "Hunter2"
hashed = "hashed:hunter2"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_leading_trailing_whitespace(auth_service):
# Whitespace in password matters
plain = "hunter2 "
hashed = "hashed:hunter2"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_leading_trailing_whitespace_in_hash(auth_service):
# Whitespace in hash matters
plain = "hunter2"
hashed = "hashed:hunter2 "
codeflash_output = auth_service.verify_password(plain, hashed)
2. Edge Test Cases
def test_empty_password_and_hash(auth_service):
# Both empty should return False
codeflash_output = auth_service.verify_password("", "")
def test_empty_password_nonempty_hash(auth_service):
# Empty password, non-empty hash
codeflash_output = auth_service.verify_password("", "hashed:")
def test_nonempty_password_empty_hash(auth_service):
# Non-empty password, empty hash
codeflash_output = auth_service.verify_password("hunter2", "")
def test_password_with_special_characters(auth_service):
# Password with special characters
plain = "!@#$%^&*()_+-=~`"
hashed = f"hashed:{plain}"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_password_with_unicode(auth_service):
# Password with unicode characters
plain = "pässwörd😊"
hashed = f"hashed:{plain}"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_hash_with_unicode(auth_service):
# Hash with unicode, password ascii
plain = "password"
hashed = "hashed:pässwörd😊"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_password_is_none_raises(auth_service):
# None as password should raise TypeError
with pytest.raises(TypeError):
auth_service.verify_password(None, "hashed:None")
def test_hash_is_none_raises(auth_service):
# None as hash should raise TypeError
with pytest.raises(TypeError):
auth_service.verify_password("password", None)
def test_password_and_hash_are_none_raises(auth_service):
# Both None should raise TypeError
with pytest.raises(TypeError):
auth_service.verify_password(None, None)
def test_password_is_integer_raises(auth_service):
# Password as integer should raise TypeError
with pytest.raises(TypeError):
auth_service.verify_password(12345, "hashed:12345")
def test_hash_is_integer_raises(auth_service):
# Hash as integer should raise TypeError
with pytest.raises(TypeError):
auth_service.verify_password("password", 12345)
def test_very_long_password(auth_service):
# Test with a very long password string (edge of reasonable size)
plain = "a" * 512
hashed = f"hashed:{plain}"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_very_long_hash(auth_service):
# Test with a very long hash string (edge of reasonable size)
plain = "hunter2"
hashed = "hashed:" + "hunter2" * 100
codeflash_output = auth_service.verify_password(plain, hashed)
def test_password_with_newlines(auth_service):
# Password containing newlines
plain = "hunter2\n"
hashed = f"hashed:{plain}"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_hash_with_newlines(auth_service):
# Hash containing newlines
plain = "hunter2"
hashed = "hashed:hunter2\n"
codeflash_output = auth_service.verify_password(plain, hashed)
3. Large Scale Test Cases
def test_many_unique_passwords(auth_service):
# Test many unique passwords and hashes
for i in range(100):
plain = f"password{i}"
hashed = f"hashed:password{i}"
codeflash_output = auth_service.verify_password(plain, hashed)
# Incorrect hash
wrong_hashed = f"hashed:password{i+1}"
codeflash_output = auth_service.verify_password(plain, wrong_hashed)
def test_performance_with_large_password(auth_service):
# Test performance/handling with a very large password (1000 chars)
plain = "x" * 1000
hashed = f"hashed:{plain}"
codeflash_output = auth_service.verify_password(plain, hashed)
def test_performance_with_large_hash(auth_service):
# Test performance/handling with a very large hash (1000 chars)
plain = "hunter2"
hashed = "hashed:" + "a" * 994 # total length 1000
codeflash_output = auth_service.verify_password(plain, hashed)
def test_large_batch_of_passwords(auth_service):
# Test a batch of 500 password/hash pairs for correctness
pairs = [(f"user{i}", f"hashed:user{i}") for i in range(500)]
for plain, hashed in pairs:
codeflash_output = auth_service.verify_password(plain, hashed)
codeflash_output = auth_service.verify_password(plain, "hashed:wrong")
def test_large_batch_with_unicode(auth_service):
# Test a batch of 100 unicode password/hash pairs
for i in range(100):
plain = f"pässwörd{i}😊"
hashed = f"hashed:{plain}"
codeflash_output = auth_service.verify_password(plain, hashed)
codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
#------------------------------------------------
from abc import ABC
from unittest.mock import MagicMock
imports
import pytest
from langflow.services.auth.service import AuthService
--- Minimal stubs for dependencies to make the test self-contained ---
Simulate a password context similar to passlib's CryptContext
class DummyPwdContext:
def init(self):
# Store passwords as {plain: hashed}
self._hashes = {}
Simulate AuthSettings with a pwd_context
class DummyAuthSettings:
def init(self):
self.pwd_context = DummyPwdContext()
Simulate Settings with auth_settings
class DummySettings:
def init(self):
self.auth_settings = DummyAuthSettings()
Simulate SettingsService
class DummySettingsService:
def init(self):
self.settings = DummySettings()
class Service(ABC):
name: str
ready: bool = False
from langflow.services.auth.service import AuthService
--- Unit tests for AuthService.verify_password ---
@pytest.fixture
def auth_service():
# Provide a fresh AuthService with dummy settings for each test
return AuthService(DummySettingsService())
-------------------
1. BASIC TEST CASES
-------------------
def test_verify_password_correct(auth_service):
"""Test correct password returns True."""
pwd = "mysecret"
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
codeflash_output = auth_service.verify_password(pwd, hashed)
def test_verify_password_incorrect(auth_service):
"""Test incorrect password returns False."""
pwd = "mysecret"
wrong_pwd = "notmysecret"
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
codeflash_output = auth_service.verify_password(wrong_pwd, hashed)
def test_verify_password_empty_password(auth_service):
"""Test empty password string."""
pwd = ""
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
codeflash_output = auth_service.verify_password("", hashed)
codeflash_output = auth_service.verify_password("notempty", hashed)
def test_verify_password_empty_hash(auth_service):
"""Test with empty hash string (should fail)."""
codeflash_output = auth_service.verify_password("password", "")
def test_verify_password_both_empty(auth_service):
"""Test both password and hash are empty."""
# Hash the empty string to get the correct hash
hashed = auth_service.settings.auth_settings.pwd_context.hash("")
codeflash_output = auth_service.verify_password("", hashed)
codeflash_output = auth_service.verify_password("", "") # "" is not a valid hash
-------------------
2. EDGE TEST CASES
-------------------
def test_verify_password_special_characters(auth_service):
"""Test password with special characters."""
pwd = "!@# $%^&*()_+-=[]{};':,.<>/?`~"
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
codeflash_output = auth_service.verify_password(pwd, hashed)
codeflash_output = auth_service.verify_password(pwd + "x", hashed)
def test_verify_password_unicode(auth_service):
"""Test password with unicode characters."""
pwd = "pässwörd😊"
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
codeflash_output = auth_service.verify_password(pwd, hashed)
codeflash_output = auth_service.verify_password("pässwörd", hashed)
def test_verify_password_long_password(auth_service):
"""Test very long password."""
pwd = "a" * 512
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
codeflash_output = auth_service.verify_password(pwd, hashed)
# Off-by-one
codeflash_output = auth_service.verify_password(pwd + "b", hashed)
def test_verify_password_wrong_hash_format(auth_service):
"""Test with a hash that is not in expected format."""
# Our dummy expects 'hashed prefix
codeflash_output = auth_service.verify_password("password", "totallyinvalidhash")
def test_verify_password_similar_passwords(auth_service):
"""Test passwords that differ by case or whitespace."""
pwd = "Password"
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
codeflash_output = auth_service.verify_password("password", hashed)
codeflash_output = auth_service.verify_password("Password ", hashed)
def test_verify_password_non_string_inputs(auth_service):
"""Test non-string types as input (should handle gracefully)."""
hashed = auth_service.settings.auth_settings.pwd_context.hash("123")
# Integers
codeflash_output = auth_service.verify_password(123, hashed)
# None
codeflash_output = auth_service.verify_password(None, hashed)
# List
codeflash_output = auth_service.verify_password(['1','2','3'], hashed)
def test_verify_password_hash_for_different_password(auth_service):
"""Test using hash for one password with a different password."""
pwd1 = "first"
pwd2 = "second"
hashed1 = auth_service.settings.auth_settings.pwd_context.hash(pwd1)
hashed2 = auth_service.settings.auth_settings.pwd_context.hash(pwd2)
codeflash_output = auth_service.verify_password(pwd2, hashed1)
codeflash_output = auth_service.verify_password(pwd1, hashed2)
-------------------------
3. LARGE SCALE TEST CASES
-------------------------
def test_verify_password_many_passwords(auth_service):
"""Test verifying a large number of passwords (scalability)."""
# Generate 500 unique passwords and hashes
passwords = [f"userpassword{i}" for i in range(500)]
hashes = [auth_service.settings.auth_settings.pwd_context.hash(p) for p in passwords]
# All should verify with correct password
for p, h in zip(passwords, hashes):
codeflash_output = auth_service.verify_password(p, h)
# All should fail with wrong password
for i, (p, h) in enumerate(zip(passwords, hashes)):
wrong_p = passwords[(i+1)%len(passwords)]
codeflash_output = auth_service.verify_password(wrong_p, h)
def test_verify_password_performance_large_password(auth_service):
"""Test performance with a single very large password (near 1000 chars)."""
pwd = "x" * 999
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
codeflash_output = auth_service.verify_password(pwd, hashed)
# Slightly wrong password
codeflash_output = auth_service.verify_password(pwd[:-1], hashed)
def test_verify_password_bulk_wrong_hashes(auth_service):
"""Test all passwords with all mismatched hashes (should all fail except diagonal)."""
passwords = [f"pw{i}" for i in range(20)]
hashes = [auth_service.settings.auth_settings.pwd_context.hash(p) for p in passwords]
# Only correct password/hash pairs should pass
for i in range(len(passwords)):
for j in range(len(hashes)):
if i == j:
codeflash_output = auth_service.verify_password(passwords[i], hashes[j])
else:
codeflash_output = auth_service.verify_password(passwords[i], hashes[j])
-------------------------
4. ADDITIONAL EDGE CASES
-------------------------
def test_verify_password_hash_reuse(auth_service):
"""Test that reusing a hash for the same password always works."""
pwd = "reused"
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
for _ in range(10):
codeflash_output = auth_service.verify_password(pwd, hashed)
def test_verify_password_mutation_resistance(auth_service):
"""Mutation test: verify that any change to the verify logic breaks tests."""
# This test is a meta-test: if the logic is changed to e.g. always return True/False, most tests fail.
pwd = "mutation"
hashed = auth_service.settings.auth_settings.pwd_context.hash(pwd)
codeflash_output = auth_service.verify_password(pwd, hashed)
codeflash_output = auth_service.verify_password(pwd + "x", hashed)
codeflash_output = auth_service.verify_password("", hashed)
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-pr10702-2025-11-24T15.26.47and push.