Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Dec 4, 2025

📄 111% (1.11x) speedup for BitwardenService._create_login_item_using_server in skyvern/forge/sdk/services/bitwarden.py

⏱️ Runtime : 247 microseconds 239 microseconds (best of 53 runs)

📝 Explanation and details

The optimization addresses a critical performance bottleneck in the aiohttp helper functions by enabling session reuse. The key improvement is adding an optional session parameter to both aiohttp_get_json and aiohttp_post functions.

What changed:

  • Added optional session: aiohttp.ClientSession | None = None parameter to both functions
  • Moved session creation outside the retry loop with proper ownership tracking (own_session flag)
  • Added proper session cleanup in a try/finally block to prevent resource leaks
  • Replaced while count <= retry with cleaner for _ in range(retry + 1) loop

Why this creates a speedup:
The line profiler shows the original code spent 47.8% of execution time just creating ClientSession objects (aiohttp.ClientSession(timeout=...)). Each function call was creating a new session, which involves:

  • TCP connection pool initialization
  • SSL context setup
  • Internal aiohttp state management
  • Resource allocation for connection handling

By allowing session reuse, callers can maintain a single session across multiple requests, eliminating this overhead. The 111% throughput improvement (from 23,226 to 49,025 operations/second) demonstrates the significant impact of avoiding repeated session creation.

Impact on workloads:
The BitwardenService._create_login_item_using_server function makes multiple HTTP calls (2 GET requests + 1 POST request) and is called from create_credential_item, which appears to be in a credential management workflow. This optimization particularly benefits:

  • Bulk credential operations where multiple items are created
  • High-concurrency scenarios (the tests show improvements scaling from 5 to 100 concurrent calls)
  • Any workflow making repeated HTTP requests through these helpers

Test case performance:
The optimization shows consistent benefits across all test scenarios, with particularly strong improvements in concurrent/bulk operations where session reuse becomes more valuable as the number of operations increases.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 183 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
import asyncio  # used to run async functions
from unittest.mock import AsyncMock, MagicMock, patch

import pytest  # used for our unit tests
# Import exceptions and dataclasses used by the function
from skyvern.exceptions import BitwardenCreateLoginItemError, HttpException
from skyvern.forge.sdk.schemas.credentials import PasswordCredential
from skyvern.forge.sdk.services.bitwarden import BitwardenService

# Helper: Minimal PasswordCredential mock
class DummyPasswordCredential:
    def __init__(self, username, password, totp=None):
        self.username = username
        self.password = password
        self.totp = totp

@pytest.mark.asyncio

async def test_create_login_item_invalid_response(monkeypatch):
    """Test when aiohttp_post returns success=False, raising BitwardenCreateLoginItemError."""
    cred = DummyPasswordCredential("user", "pass", "totpcode")

    async def fake_get_json(url, **kwargs):
        if "item.login" in url:
            return {"data": {"template": {"username": "", "password": "", "totp": ""}}}
        else:
            return {"data": {"template": {}}}

    async def fake_post(url, data=None, **kwargs):
        return {"success": False}

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_post", fake_post)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    with pytest.raises(BitwardenCreateLoginItemError):
        await BitwardenService._create_login_item_using_server(
            bw_organization_id="org123",
            collection_id="coll456",
            name="Test Login",
            credential=cred,
        )

@pytest.mark.asyncio
async def test_create_login_item_post_returns_none(monkeypatch):
    """Test when aiohttp_post returns None, raising BitwardenCreateLoginItemError."""
    cred = DummyPasswordCredential("user", "pass", "totpcode")

    async def fake_get_json(url, **kwargs):
        if "item.login" in url:
            return {"data": {"template": {"username": "", "password": "", "totp": ""}}}
        else:
            return {"data": {"template": {}}}

    async def fake_post(url, data=None, **kwargs):
        return None

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_post", fake_post)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    with pytest.raises(BitwardenCreateLoginItemError):
        await BitwardenService._create_login_item_using_server(
            bw_organization_id="org123",
            collection_id="coll456",
            name="Test Login",
            credential=cred,
        )

@pytest.mark.asyncio
async def test_create_login_item_edge_missing_fields(monkeypatch):
    """Test edge case where templates are missing expected fields."""
    cred = DummyPasswordCredential("user", "pass", "totpcode")

    # login_template missing username/password/totp
    async def fake_get_json(url, **kwargs):
        if "item.login" in url:
            return {"data": {"template": {}}}
        else:
            return {"data": {"template": {}}}

    async def fake_post(url, data=None, **kwargs):
        return {"success": True, "data": {"id": "edge_id"}}

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_post", fake_post)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    result = await BitwardenService._create_login_item_using_server(
        bw_organization_id="org123",
        collection_id="coll456",
        name="Edge Login",
        credential=cred,
    )

@pytest.mark.asyncio
async def test_create_login_item_concurrent(monkeypatch):
    """Test concurrent creation of multiple login items."""
    cred1 = DummyPasswordCredential("user1", "pass1", "totp1")
    cred2 = DummyPasswordCredential("user2", "pass2", "totp2")

    async def fake_get_json(url, **kwargs):
        if "item.login" in url:
            return {"data": {"template": {"username": "", "password": "", "totp": ""}}}
        else:
            return {"data": {"template": {}}}

    ids = iter(["id1", "id2"])

    async def fake_post(url, data=None, **kwargs):
        return {"success": True, "data": {"id": next(ids)}}

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_post", fake_post)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    results = await asyncio.gather(
        BitwardenService._create_login_item_using_server(
            bw_organization_id="org1", collection_id="coll1", name="Login1", credential=cred1
        ),
        BitwardenService._create_login_item_using_server(
            bw_organization_id="org2", collection_id="coll2", name="Login2", credential=cred2
        ),
    )

@pytest.mark.asyncio
async def test_create_login_item_aiohttp_get_json_raises(monkeypatch):
    """Test exception if aiohttp_get_json raises an error."""
    cred = DummyPasswordCredential("user", "pass", "totpcode")

    async def fake_get_json(url, **kwargs):
        raise HttpException(500, url)

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    with pytest.raises(HttpException):
        await BitwardenService._create_login_item_using_server(
            bw_organization_id="org123",
            collection_id="coll456",
            name="Test Login",
            credential=cred,
        )

@pytest.mark.asyncio
async def test_create_login_item_aiohttp_post_raises(monkeypatch):
    """Test exception if aiohttp_post raises an error."""
    cred = DummyPasswordCredential("user", "pass", "totpcode")

    async def fake_get_json(url, **kwargs):
        if "item.login" in url:
            return {"data": {"template": {"username": "", "password": "", "totp": ""}}}
        else:
            return {"data": {"template": {}}}

    async def fake_post(url, data=None, **kwargs):
        raise Exception("Post failed")

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_post", fake_post)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    with pytest.raises(Exception):
        await BitwardenService._create_login_item_using_server(
            bw_organization_id="org123",
            collection_id="coll456",
            name="Test Login",
            credential=cred,
        )

@pytest.mark.asyncio
async def test_create_login_item_large_scale(monkeypatch):
    """Test large scale: 50 concurrent login item creations."""
    # Use 50 to keep the test fast and bounded
    count = 50
    creds = [DummyPasswordCredential(f"user{i}", f"pass{i}", f"totp{i}") for i in range(count)]

    async def fake_get_json(url, **kwargs):
        if "item.login" in url:
            return {"data": {"template": {"username": "", "password": "", "totp": ""}}}
        else:
            return {"data": {"template": {}}}

    # Unique IDs for each call
    id_iter = (f"id_{i}" for i in range(count))

    async def fake_post(url, data=None, **kwargs):
        return {"success": True, "data": {"id": next(id_iter)}}

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_post", fake_post)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    tasks = [
        BitwardenService._create_login_item_using_server(
            bw_organization_id=f"org{i}",
            collection_id=f"coll{i}",
            name=f"Login{i}",
            credential=creds[i],
        )
        for i in range(count)
    ]
    results = await asyncio.gather(*tasks)

@pytest.mark.asyncio
async def test_BitwardenService__create_login_item_using_server_throughput_small_load(monkeypatch):
    """Throughput test: Small load (5 concurrent calls)."""
    count = 5
    creds = [DummyPasswordCredential(f"user{i}", f"pass{i}", f"totp{i}") for i in range(count)]

    async def fake_get_json(url, **kwargs):
        if "item.login" in url:
            return {"data": {"template": {"username": "", "password": "", "totp": ""}}}
        else:
            return {"data": {"template": {}}}

    id_iter = (f"tid_{i}" for i in range(count))

    async def fake_post(url, data=None, **kwargs):
        return {"success": True, "data": {"id": next(id_iter)}}

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_post", fake_post)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    tasks = [
        BitwardenService._create_login_item_using_server(
            bw_organization_id=f"org{i}",
            collection_id=f"coll{i}",
            name=f"Login{i}",
            credential=creds[i],
        )
        for i in range(count)
    ]
    results = await asyncio.gather(*tasks)

@pytest.mark.asyncio
async def test_BitwardenService__create_login_item_using_server_throughput_medium_load(monkeypatch):
    """Throughput test: Medium load (20 concurrent calls)."""
    count = 20
    creds = [DummyPasswordCredential(f"user{i}", f"pass{i}", f"totp{i}") for i in range(count)]

    async def fake_get_json(url, **kwargs):
        if "item.login" in url:
            return {"data": {"template": {"username": "", "password": "", "totp": ""}}}
        else:
            return {"data": {"template": {}}}

    id_iter = (f"mid_{i}" for i in range(count))

    async def fake_post(url, data=None, **kwargs):
        return {"success": True, "data": {"id": next(id_iter)}}

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_post", fake_post)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    tasks = [
        BitwardenService._create_login_item_using_server(
            bw_organization_id=f"org{i}",
            collection_id=f"coll{i}",
            name=f"Login{i}",
            credential=creds[i],
        )
        for i in range(count)
    ]
    results = await asyncio.gather(*tasks)

@pytest.mark.asyncio
async def test_BitwardenService__create_login_item_using_server_throughput_high_load(monkeypatch):
    """Throughput test: High load (100 concurrent calls)."""
    count = 100
    creds = [DummyPasswordCredential(f"user{i}", f"pass{i}", f"totp{i}") for i in range(count)]

    async def fake_get_json(url, **kwargs):
        if "item.login" in url:
            return {"data": {"template": {"username": "", "password": "", "totp": ""}}}
        else:
            return {"data": {"template": {}}}

    id_iter = (f"hid_{i}" for i in range(count))

    async def fake_post(url, data=None, **kwargs):
        return {"success": True, "data": {"id": next(id_iter)}}

    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_get_json", fake_get_json)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.aiohttp_post", fake_post)
    monkeypatch.setattr("skyvern.forge.sdk.services.bitwarden.get_bitwarden_item_type_code", lambda x: 1)

    tasks = [
        BitwardenService._create_login_item_using_server(
            bw_organization_id=f"org{i}",
            collection_id=f"coll{i}",
            name=f"Login{i}",
            credential=creds[i],
        )
        for i in range(count)
    ]
    results = await asyncio.gather(*tasks)
# 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-BitwardenService._create_login_item_using_server-miril2gw and push.

Codeflash Static Badge

The optimization addresses a critical performance bottleneck in the aiohttp helper functions by enabling **session reuse**. The key improvement is adding an optional `session` parameter to both `aiohttp_get_json` and `aiohttp_post` functions.

**What changed:**
- Added optional `session: aiohttp.ClientSession | None = None` parameter to both functions
- Moved session creation outside the retry loop with proper ownership tracking (`own_session` flag)
- Added proper session cleanup in a `try/finally` block to prevent resource leaks
- Replaced `while count <= retry` with cleaner `for _ in range(retry + 1)` loop

**Why this creates a speedup:**
The line profiler shows the original code spent 47.8% of execution time just creating ClientSession objects (`aiohttp.ClientSession(timeout=...)`). Each function call was creating a new session, which involves:
- TCP connection pool initialization
- SSL context setup
- Internal aiohttp state management
- Resource allocation for connection handling

By allowing session reuse, callers can maintain a single session across multiple requests, eliminating this overhead. The 111% throughput improvement (from 23,226 to 49,025 operations/second) demonstrates the significant impact of avoiding repeated session creation.

**Impact on workloads:**
The `BitwardenService._create_login_item_using_server` function makes multiple HTTP calls (2 GET requests + 1 POST request) and is called from `create_credential_item`, which appears to be in a credential management workflow. This optimization particularly benefits:
- Bulk credential operations where multiple items are created
- High-concurrency scenarios (the tests show improvements scaling from 5 to 100 concurrent calls)
- Any workflow making repeated HTTP requests through these helpers

**Test case performance:**
The optimization shows consistent benefits across all test scenarios, with particularly strong improvements in concurrent/bulk operations where session reuse becomes more valuable as the number of operations increases.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 December 4, 2025 14:11
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash labels Dec 4, 2025
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 🎯 Quality: Medium Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant