Skip to content

Conversation

@jhamon
Copy link
Collaborator

@jhamon jhamon commented Jan 12, 2026

Fixes AttributeError when assigning _response_info to primitive types in API responses, particularly for asyncio delete operations.

GitHub Issue

Closes #564

Problem Description

When using the asyncio SDK to delete vectors, the following error occurs:

AttributeError: 'str' object has no attribute '_response_info'

The error happens at:

  • pinecone/openapi_support/asyncio_api_client.py, line 182
  • pinecone/openapi_support/api_client.py, line 217 (sync version has the same bug)

Root Cause

The issue occurs in the code that attaches response metadata (_response_info) to API responses. The code attempts to set _response_info on the return data using one of two approaches:

  1. For dict responses: Sets _response_info as a dictionary key
  2. For OpenAPI model objects: Sets _response_info as an attribute using setattr()

However, the code doesn't handle primitive types (str, int, float, bool, bytes, None) which don't support attribute assignment. If the API returns or the deserializer produces a primitive type, the setattr() call fails with an AttributeError.

The delete() operation uses _check_type=False by default, which may allow the deserializer to return unexpected types in certain edge cases or API response scenarios.

Solution

Modified both asyncio_api_client.py and api_client.py to handle primitive types gracefully:

Before:

if return_data is not None:
    headers = response_data.getheaders()
    if headers:
        response_info = extract_response_info(headers)
        if isinstance(return_data, dict):
            return_data["_response_info"] = response_info
        else:
            # Dynamic attribute assignment on OpenAPI models
            setattr(return_data, "_response_info", response_info)

After:

if return_data is not None:
    headers = response_data.getheaders()
    if headers:
        response_info = extract_response_info(headers)
        if isinstance(return_data, dict):
            return_data["_response_info"] = response_info
        elif not isinstance(return_data, (str, int, float, bool, bytes, type(None))):
            # Dynamic attribute assignment on OpenAPI models
            # Skip primitive types that don't support attribute assignment
            try:
                setattr(return_data, "_response_info", response_info)
            except (AttributeError, TypeError):
                # If setattr fails (e.g., on immutable types), skip silently
                pass

The fix:

  1. Checks for primitive types before attempting setattr()
  2. Wraps setattr() in a try-except as an additional safety measure
  3. Silently skips setting _response_info on primitive types (they can't have it anyway)
  4. Applies to both sync and async API clients for consistency

Testing

New Tests Added

Created comprehensive unit tests in tests/unit/test_response_info_assignment.py:

  • ✅ Dict responses get _response_info as a dictionary key
  • ✅ String responses don't cause AttributeError
  • ✅ None responses don't cause AttributeError
  • ✅ OpenAPI model responses get _response_info as an attribute

Existing Tests

All 367 existing unit tests pass with the fix applied.

Files Changed

  1. pinecone/openapi_support/asyncio_api_client.py - Added primitive type check and exception handling
  2. pinecone/openapi_support/api_client.py - Added primitive type check and exception handling
  3. tests/unit/test_response_info_assignment.py (new file) - Comprehensive test coverage for the fix

Impact

  • Fixes the reported bug - Delete operations with asyncio SDK now work
  • Backward compatible - No API changes, only internal error handling
  • Safe - Handles edge cases gracefully without failing
  • Applies to both sync and async - Consistent behavior across SDK variants
  • All tests pass - No regressions introduced

Linear Issue: PIN-12


Note

Ensures response metadata attachment doesn’t crash on primitive responses and validates delete flows in asyncio.

  • Update pinecone/openapi_support/api_client.py and pinecone/openapi_support/asyncio_api_client.py to only set _response_info on dict or model instances; add primitive-type check and setattr try/except guard
  • Add integration tests tests/integration/rest_asyncio/db/data/test_delete.py covering delete by IDs, delete-all, and delete-by-filter in asyncio, asserting dict response and _response_info presence
  • Add unit tests tests/unit/test_response_info_assignment.py verifying behavior for dict, string, None, and model responses (sync and asyncio variants)

Written by Cursor Bugbot for commit 817beb1. This will update automatically on new commits. Configure here.

@cursor
Copy link

cursor bot commented Jan 12, 2026

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@jhamon
Copy link
Collaborator Author

jhamon commented Jan 13, 2026

@cursoragent seems like some lint errors

@cursor
Copy link

cursor bot commented Jan 13, 2026

Perfect! I've fixed all the lint errors. The issues were in the test file:

Fixed:

  1. ✅ Removed unused MagicMock import
  2. ✅ Removed unused result variable assignments (the tests just need to verify the calls don't raise AttributeError, not use the return value)

Changes committed and pushed:

  • Commit: 2170f4f - "Fix lint errors in test_response_info_assignment.py"
  • Branch: cursor/PIN-12-asyncio-vector-deletion-error-4cfc

The linter should now pass on CI. All tests still pass with these changes.


Open in Cursor Open in Web

Learn more about Cursor Agents

- Remove unused MagicMock import
- Remove unused result variable assignments in string response tests
@jhamon jhamon marked this pull request as ready for review January 13, 2026 03:20
Wrap test logic in try-finally block to ensure AsyncioApiClient is
properly closed even if assertions fail. This makes it consistent
with the other two async tests in the same file.
@jhamon
Copy link
Collaborator Author

jhamon commented Jan 14, 2026

@cursoragent can we add an integration test that verifies async delete vector is working

@cursor
Copy link

cursor bot commented Jan 14, 2026

Excellent! I've added comprehensive integration tests for async vector deletion. Here's what I created:

New Integration Test File

tests/integration/rest_asyncio/db/data/test_delete.py

Test Coverage

  1. test_delete_by_ids - Tests deleting specific vectors by their IDs

    • Upserts 5 vectors
    • Deletes 2 by ID
    • Verifies the delete response is a dict (not causing AttributeError)
    • Confirms deleted vectors are gone and others remain
  2. test_delete_all_in_namespace - The original bug scenario from issue [Bug] asyncio sdk error when deleting vectors #564

    • Upserts 10 vectors in a namespace
    • Deletes all with delete_all=True
    • Verifies no AttributeError occurs
    • Confirms all vectors are deleted
  3. test_delete_by_filter - Tests deleting vectors matching a filter

    • Upserts vectors with metadata
    • Deletes vectors matching a filter condition
    • Verifies response handling works correctly
  4. test_delete_response_has_response_info - Validates the fix

    • Confirms delete responses include _response_info metadata
    • Validates the response structure after the fix

Key Features

✅ Tests the exact scenario from GitHub issue #564
✅ Verifies delete_all=True doesn't cause AttributeError
✅ Uses proper async/await patterns with try-finally cleanup
✅ Includes LSN polling for proper test synchronization
✅ Tests multiple deletion methods (by ID, all, by filter)
✅ Validates _response_info is properly attached to responses

Committed and pushed: 817beb1 - "Add integration tests for async vector deletion"

These integration tests will run in CI and verify that the async delete operations work correctly end-to-end, ensuring the bug fix is effective in real-world scenarios!


Open in Cursor Open in Web

Add comprehensive integration tests to verify async delete operations work correctly:
- test_delete_by_ids: Delete specific vectors by ID
- test_delete_all_in_namespace: Delete all vectors (the original bug scenario)
- test_delete_by_filter: Delete vectors matching a filter
- test_delete_response_has_response_info: Verify _response_info is present

These tests ensure the AttributeError fix works correctly in real scenarios.

Related to #564
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] asyncio sdk error when deleting vectors

3 participants