Skip to content

Sync sqs client#160

Merged
felipao-mx merged 10 commits intomainfrom
sync-sqs-client
Mar 5, 2025
Merged

Sync sqs client#160
felipao-mx merged 10 commits intomainfrom
sync-sqs-client

Conversation

@felipao-mx
Copy link
Copy Markdown
Contributor

@felipao-mx felipao-mx commented Mar 4, 2025

incluye el cliente de sqs y el de celery en versión sync

Summary by CodeRabbit

  • New Features

    • Enhanced AWS SQS integration with support for asynchronous and synchronous task messaging.
  • Chores

    • Updated installation guidance and dependency management to streamline AWS tool usage.
    • Bumped the package version to 1.2.0.
  • Tests

    • Expanded and refined the test suite to ensure robust validation of AWS SQS and task messaging functionality.
    • Introduced new tests for SqsCeleryClient and SqsClient to validate message sending and receiving.
    • Updated existing tests to support new module structures and parameterized testing.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 4, 2025

Walkthrough

This pull request reorganizes and extends the project's AWS SQS integration. New modules introduce both asynchronous and synchronous SQS Celery clients. The asynchronous client in agave/tools/asyncio/sqs_celery_client.py adds methods to send tasks immediately and in the background, while the synchronous version in agave/tools/sync/sqs_celery_client.py includes a method to send tasks. Changes in the agave/tools/celery.py file expose the previously private message-building function and remove an older SQS Celery client implementation. Updates in the version file, requirements, and setup.py support enhanced AWS tooling with appropriate dependency groups for both synchronous and asynchronous operations. The testing suite is restructured with new fixtures, modified import paths, and updated tests to accommodate these changes and ensure proper operation with mocked AWS SQS services.

Suggested reviewers

  • rogelioLpz
  • pachCode

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b26cae2 and f4f6b15.

📒 Files selected for processing (1)
  • agave/version.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • agave/version.py
⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: pytest (3.13)
  • GitHub Check: pytest (3.12)
  • GitHub Check: pytest (3.11)
  • GitHub Check: pytest (3.10)
  • GitHub Check: pytest (3.9)
  • GitHub Check: coverage

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (20)
tests/tools/sync/test_sqs_client.py (2)

8-22: Test implementation is clear and verifies key functionality.

The test effectively verifies that:

  1. The synchronous SqsClient can send messages successfully
  2. Messages with and without message group IDs are handled correctly
  3. The message payloads are correctly serialized and deserialized

Using a context manager for the client ensures proper resource cleanup.

However, there's one potential improvement to consider:

- sqs_message = await sqs_client.receive_message()
- message_body = json.loads(sqs_message['Messages'][0]['Body'])
- assert message_body == data1
-
- sqs_message = await sqs_client.receive_message()
- message = json.loads(sqs_message['Messages'][0]['Body'])
- assert message == data2
+ # Extract message parsing to avoid code duplication
+ async def get_next_message_body():
+     sqs_message = await sqs_client.receive_message()
+     return json.loads(sqs_message['Messages'][0]['Body'])
+ 
+ message_body1 = await get_next_message_body()
+ assert message_body1 == data1
+ 
+ message_body2 = await get_next_message_body()
+ assert message_body2 == data2

21-21: Variable naming inconsistency.

The variable naming is inconsistent between message_body (line 17) and message (line 21) when they represent the same type of data. Consider standardizing the variable naming for better readability.

- message = json.loads(sqs_message['Messages'][0]['Body'])
+ message_body = json.loads(sqs_message['Messages'][0]['Body'])
tests/tools/sync/test_sqs_celery_client.py (3)

4-4: Consider using relative import for internal modules.

The import from agave.tools.sync.sqs_celery_client import SqsCeleryClient uses an absolute path. Consider using a relative import for internal modules to improve maintainability.

-from agave.tools.sync.sqs_celery_client import SqsCeleryClient
+from ...tools.sync.sqs_celery_client import SqsCeleryClient

9-14: Add test class with proper setup/teardown to handle queue lifecycle.

The test creates a SqsCeleryClient and calls start() but doesn't explicitly handle proper cleanup. Consider implementing this test within a test class with appropriate setup/teardown methods.

-async def test_send_task(sqs_client) -> None:
-    args = [10, 'foo']
-    kwargs = dict(hola='mundo')
-    queue = SqsCeleryClient(sqs_client.queue_url, CORE_QUEUE_REGION)
-    queue.start()
+class TestSqsCeleryClient:
+    async def setup_method(self):
+        self.args = [10, 'foo']
+        self.kwargs = dict(hola='mundo')
+        self.queue = SqsCeleryClient(self.sqs_client.queue_url, CORE_QUEUE_REGION)
+        self.queue.start()
+
+    async def teardown_method(self):
+        # Ensure proper cleanup of resources
+        if hasattr(self, 'queue'):
+            self.queue.stop()
+
+    async def test_send_task(self, sqs_client) -> None:
+        self.sqs_client = sqs_client
+        # Test implementation...

15-29: Add additional test cases to verify error handling.

The test only covers the happy path for sending a task. Consider adding tests for edge cases such as:

  • Empty args/kwargs
  • Invalid task names
  • Queue failures
setup.py (1)

46-49: Duplicated dependencies between tasks and asyncio_aws_tools extras.

The same aiobotocore and types-aiobotocore-sqs dependencies are defined in both 'tasks' and 'asyncio_aws_tools' extras groups with the same version constraints.

Consider consolidating these dependencies to avoid duplication, perhaps by having one group depend on the other:

'tasks': [
    'aiobotocore>=2.0.0,<3.0.0',
    'types-aiobotocore-sqs>=2.1.0,<3.0.0',
],
'sync_aws_tools': [
    'boto3>=1.34.106,<2.0.0',
    'types-boto3[sqs]>=1.34.106,<2.0.0',
],
'asyncio_aws_tools': [
-    'aiobotocore>=2.0.0,<3.0.0',
-    'types-aiobotocore-sqs>=2.1.0,<3.0.0',
],

Then you could update the asyncio_aws_tools section to use the tasks group:

'asyncio_aws_tools': ['agave[tasks]'],
agave/tools/sync/sqs_celery_client.py (3)

4-5: Consider using relative imports for internal modules.

The imports from agave.tools.celery import build_celery_message and from agave.tools.sync.sqs_client import SqsClient use absolute paths. Consider using relative imports for internal modules.

-from agave.tools.celery import build_celery_message
-from agave.tools.sync.sqs_client import SqsClient
+from ...tools.celery import build_celery_message
+from .sqs_client import SqsClient

8-9: Document the purpose and usage of the SqsCeleryClient class.

The SqsCeleryClient class lacks class-level documentation explaining its purpose and how to use it.

@dataclass
+"""
+A synchronous client for sending Celery tasks to SQS.
+
+This client extends the SqsClient to provide specialized methods for sending
+Celery-formatted messages to SQS queues.
+
+Example:
+    client = SqsCeleryClient(queue_url, region)
+    client.start()
+    client.send_task('task.name', args=[1, 2], kwargs={'param': 'value'})
+    client.stop()
+"""
class SqsCeleryClient(SqsClient):

10-17: Add method documentation and add error handling.

The send_task method lacks documentation and doesn't handle potential errors from message building or sending operations.

def send_task(
    self,
    name: str,
    args: Optional[Iterable] = None,
    kwargs: Optional[dict] = None,
) -> None:
+    """
+    Send a Celery task to the SQS queue.
+    
+    Args:
+        name: The name of the task to execute
+        args: Optional positional arguments for the task
+        kwargs: Optional keyword arguments for the task
+        
+    Raises:
+        Exception: If there's an error building or sending the message
+    """
    celery_message = build_celery_message(name, args or (), kwargs or {})
    self.send_message(celery_message)
agave/tools/asyncio/sqs_celery_client.py (4)

5-7: Consistent import style.

The import style is inconsistent - using absolute import for the celery module but relative import for sqs_client. Consider using relative imports consistently for internal modules.

-from agave.tools.celery import build_celery_message
+from ...tools.celery import build_celery_message

from .sqs_client import SqsClient

10-11: Document the purpose and usage of the SqsCeleryClient class.

The SqsCeleryClient class lacks class-level documentation explaining its purpose and how to use it. This is particularly important for an async class with multiple methods.

@dataclass
+"""
+An asynchronous client for sending Celery tasks to SQS.
+
+This client extends the SqsClient to provide specialized methods for sending
+Celery-formatted messages to SQS queues, with both awaitable and background task options.
+
+Example:
+    client = SqsCeleryClient(queue_url, region)
+    await client.start()
+    await client.send_task('task.name', args=[1, 2], kwargs={'param': 'value'})
+    # Or as a background task:
+    task = client.send_background_task('task.name', args=[1, 2], kwargs={'param': 'value'})
+    await task  # Optionally wait for completion
+    await client.stop()
+"""
class SqsCeleryClient(SqsClient):

12-20: Add method documentation and error handling to send_task.

The send_task method lacks documentation and doesn't handle potential errors from message building or sending operations.

async def send_task(
    self,
    name: str,
    args: Optional[Iterable] = None,
    kwargs: Optional[dict] = None,
) -> None:
+    """
+    Send a Celery task to the SQS queue and wait for completion.
+    
+    Args:
+        name: The name of the task to execute
+        args: Optional positional arguments for the task
+        kwargs: Optional keyword arguments for the task
+        
+    Raises:
+        Exception: If there's an error building or sending the message
+    """
    celery_message = build_celery_message(name, args or (), kwargs or {})
    await super().send_message(celery_message)

21-28: Add method documentation and error handling to send_background_task.

The send_background_task method lacks documentation and doesn't provide guidance on error handling for the returned task.

def send_background_task(
    self,
    name: str,
    args: Optional[Iterable] = None,
    kwargs: Optional[dict] = None,
) -> asyncio.Task:
+    """
+    Send a Celery task to the SQS queue as a background task.
+    
+    This method returns immediately with an asyncio Task that can be awaited
+    or added to an event loop for later completion.
+    
+    Args:
+        name: The name of the task to execute
+        args: Optional positional arguments for the task
+        kwargs: Optional keyword arguments for the task
+        
+    Returns:
+        asyncio.Task: A task representing the asynchronous send operation
+        
+    Note: 
+        Errors in the background task should be handled by attaching
+        error callbacks to the returned task.
+    """
    celery_message = build_celery_message(name, args or (), kwargs or {})
    return super().send_message_async(celery_message)
agave/tools/sync/sqs_client.py (2)

6-13: Use exception chaining to preserve traceback.

When raising an ImportError in the except clause, linking the original exception can aid debugging.

Consider applying this diff:

- except ImportError:
-     raise ImportError(
-         "You must install agave with [sync_aws_tools] option.\n"
-         "You can install it with: pip install agave[sync_aws_tools]"
-     )
+ except ImportError as e:
+     raise ImportError(
+         "You must install agave with [sync_aws_tools] option.\n"
+         "You can install it with: pip install agave[sync_aws_tools]"
+     ) from e
🧰 Tools
🪛 Ruff (0.8.2)

10-13: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


22-29: Release resources within __exit__ if needed.

__exit__ is currently empty, which is fine if there are no resources to clean up. However, if you anticipate needing to close or finalize your SQS client usage, consider handling that here to avoid resource leaks.

agave/tools/celery.py (1)

7-9: Optional docstring for _b64_encode.

While the private _b64_encode function is self-explanatory, adding a brief docstring can help future maintainers understand its purpose.

tests/conftest.py (4)

229-238: Properly deprecated AWS credentials mock fixture.

The fixture appropriately sets up mock AWS credentials for testing purposes. The deprecation warning is well-documented, directing users to the preferred alternative in cuenca-test-fixtures.

However, consider adding more detail about the specific fixture name from cuenca-test-fixtures that should be used instead to make the migration path clearer for developers.


258-266: Task count patching implementation is effective but has a Spanish comment.

The fixture successfully limits the iteration count for tests, but contains a comment in Spanish which should be translated to English for consistency.

- # Para pruebas solo unos cuantos ciclos
+ # Only a few cycles for testing purposes

268-282: Aiobotocore client creation patch has potential error handling issue.

The implementation uses next() to extract the first string argument without a fallback, which could lead to a StopIteration error if no string argument is provided.

-    def mock_create_client(*args, **kwargs):
-        service_name = next(a for a in args if type(a) is str)
-        kwargs['endpoint_url'] = aws_endpoint_urls[service_name]
-
-        return create_client(*args, **kwargs)

+    def mock_create_client(*args, **kwargs):
+        try:
+            service_name = next(a for a in args if type(a) is str)
+            if service_name in aws_endpoint_urls:
+                kwargs['endpoint_url'] = aws_endpoint_urls[service_name]
+        except StopIteration:
+            pass  # No string argument found, proceed with default
+
+        return create_client(*args, **kwargs)

284-298: Similar error handling concern in boto3 client patch.

The implementation has better error handling than the aiobotocore version since it checks if the service_name is in aws_endpoint_urls, but it still uses next() without a fallback.

-    def mock_client(*args, **kwargs):
-        service_name = next(a for a in args if type(a) is str)
-        if service_name in aws_endpoint_urls:
-            kwargs['endpoint_url'] = aws_endpoint_urls[service_name]
-        return create_client(*args, **kwargs)

+    def mock_client(*args, **kwargs):
+        try:
+            service_name = next(a for a in args if type(a) is str)
+            if service_name in aws_endpoint_urls:
+                kwargs['endpoint_url'] = aws_endpoint_urls[service_name]
+        except StopIteration:
+            pass  # No string argument found, proceed with default
+
+        return create_client(*args, **kwargs)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57bfaec and be01401.

📒 Files selected for processing (16)
  • agave/tools/asyncio/sqs_celery_client.py (1 hunks)
  • agave/tools/asyncio/sqs_client.py (1 hunks)
  • agave/tools/celery.py (1 hunks)
  • agave/tools/sync/sqs_celery_client.py (1 hunks)
  • agave/tools/sync/sqs_client.py (1 hunks)
  • agave/version.py (1 hunks)
  • requirements-test.txt (1 hunks)
  • requirements.txt (1 hunks)
  • setup.py (1 hunks)
  • tests/conftest.py (2 hunks)
  • tests/tasks/conftest.py (0 hunks)
  • tests/tasks/test_imports.py (1 hunks)
  • tests/tools/asyncio/test_sqs_celery_client.py (1 hunks)
  • tests/tools/asyncio/test_sqs_client.py (1 hunks)
  • tests/tools/sync/test_sqs_celery_client.py (1 hunks)
  • tests/tools/sync/test_sqs_client.py (1 hunks)
💤 Files with no reviewable changes (1)
  • tests/tasks/conftest.py
✅ Files skipped from review due to trivial changes (3)
  • tests/tools/asyncio/test_sqs_client.py
  • agave/version.py
  • tests/tools/asyncio/test_sqs_celery_client.py
🧰 Additional context used
📓 Path-based instructions (2)
`**/*.py`: Enforce Relative Imports for Internal Modules

En...

**/*.py: Enforce Relative Imports for Internal Modules

Ensure that any imports referencing internal modules use relative paths. However, if modules reside in the main module directories (for example /src or /library_or_app_name) —and relative imports are not feasible—absolute imports are acceptable. Additionally, if a module is located outside the main module structure (for example, in /tests or /scripts at a similar level), absolute imports are also valid.

Examples and Guidelines:

  1. If a module is in the same folder or a subfolder of the current file, use relative imports. For instance: from .some_module import SomeClass
  2. If the module is located under /src or /library_or_app_name and cannot be imported relatively, absolute imports are allowed (e.g., from library_or_app_name.utilities import helper_method).
  3. If a module is outside the main module directories (for example, in /tests, /scripts, or any similarly placed directory), absolute imports are valid.
  4. External (third-party) libraries should be imported absolutely (e.g., import requests).
  • agave/tools/asyncio/sqs_client.py
  • tests/tools/sync/test_sqs_celery_client.py
  • agave/tools/sync/sqs_celery_client.py
  • agave/tools/sync/sqs_client.py
  • agave/tools/asyncio/sqs_celery_client.py
  • tests/conftest.py
  • tests/tools/sync/test_sqs_client.py
  • agave/tools/celery.py
  • setup.py
  • tests/tasks/test_imports.py
`**/*.py`: Rule: Enforce Snake Case in Python Backend
  1. N...

**/*.py:
Rule: Enforce Snake Case in Python Backend

  1. New or Modified Code: Use snake_case for all variables, functions, methods, and class attributes.
  2. Exceptions (Pydantic models for API responses):
    • Primary fields must be snake_case.
    • If older clients expect camelCase, create a computed or alias field that references the snake_case field.
    • Mark any camelCase fields as deprecated or transitional.

Examples

Invalid:

class CardConfiguration(BaseModel):
    title: str
    subTitle: str  # ❌ Modified or new field in camelCase

Valid:

class CardConfiguration(BaseModel):
    title: str
    subtitle: str  # ✅ snake_case for new/modified field

    @computed_field
    def subTitle(self) -> str:  # camelCase allowed only for compatibility
        return self.subtitle

Any direct use of camelCase in new or updated code outside of these exceptions should be flagged.

  • agave/tools/asyncio/sqs_client.py
  • tests/tools/sync/test_sqs_celery_client.py
  • agave/tools/sync/sqs_celery_client.py
  • agave/tools/sync/sqs_client.py
  • agave/tools/asyncio/sqs_celery_client.py
  • tests/conftest.py
  • tests/tools/sync/test_sqs_client.py
  • agave/tools/celery.py
  • setup.py
  • tests/tasks/test_imports.py
🪛 Ruff (0.8.2)
agave/tools/sync/sqs_client.py

10-13: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (13)
requirements-test.txt (1)

16-16: Dependency addition looks good.

The addition of typing_extensions==4.12.2 is appropriate for providing backwards compatibility for Python typing features, which aligns well with the project's type safety focus evidenced by other testing dependencies.

agave/tools/asyncio/sqs_client.py (1)

12-13: Updated installation instructions are clear and accurate.

The error message now correctly instructs users to install the package with the [asyncio_aws_tools] option, which is more specific and aligns with the package's actual requirements structure.

requirements.txt (1)

2-3:

Details

✅ Verification successful

AWS dependencies are properly defined with pinned versions.

The addition of boto3 and types-boto3[sqs] with pinned versions is appropriate for the SQS functionality being implemented. Including type hints for boto3 shows good attention to type safety.


🏁 Script executed:

#!/bin/bash
# Verify the versions of boto3 and types-boto3 are compatible and the latest available stable versions

echo "Checking latest boto3 version..."
LATEST_BOTO3=$(pip index versions boto3 | grep -o "Available versions: .*" | cut -d' ' -f3 | tr ',' '\n' | sort -V | tail -n1)
echo "Latest boto3 version: $LATEST_BOTO3"
echo "Required boto3 version: 1.35.74"

echo "Checking latest types-boto3 version..."
LATEST_TYPES_BOTO3=$(pip index versions types-boto3 | grep -o "Available versions: .*" | cut -d' ' -f3 | tr ',' '\n' | sort -V | tail -n1)
echo "Latest types-boto3 version: $LATEST_TYPES_BOTO3"
echo "Required types-boto3 version: 1.35.74"

Length of output: 1007


AWS Dependency Versions Verified and Approved
The pinned versions in requirements.txt for boto3==1.35.74 and types-boto3[sqs]==1.35.74 are intentionally fixed for stability and compatibility with the implemented SQS functionality. Although the latest available versions (1.37.5) are a bit newer, sticking with a verified version can help avoid unexpected issues.

  • File Affected: requirements.txt (lines 2-3)
  • Observation: The deliberate pinning of versions ensures type safety and consistent behavior without automatically chasing the latest revisions.
tests/tools/sync/test_sqs_client.py (1)

3-3: Import follows project guidelines for test files.

The absolute import of SqsClient follows the project guidelines since this is a test file outside the main module directories.

setup.py (1)

42-45:

Details

❓ Verification inconclusive

Ensure boto3 version compatibility.

The version constraint for boto3 (>=1.34.106,<2.0.0) and types-boto3 allows for a wide range of versions. Verify that all supported versions work correctly with your implementation.


🏁 Script executed:

#!/bin/bash
# Check for the latest boto3 version
pip index versions boto3 | head -n 5

Length of output: 15409


Verify boto3 Version Compatibility

The dependency specification in setup.py for boto3 (>=1.34.106,<2.0.0) spans a wide range—including the latest release (currently 1.37.5 as confirmed by the pip index output). Please ensure that your implementation has been tested against this full range. If discrepancies arise between minor versions, consider adding targeted tests or narrowing the version constraint.

agave/tools/sync/sqs_client.py (2)

30-33: Initialization logic seems clear.

No issues found in the start method. It correctly initializes the SQS client using the given region.


34-44: Confirm SQS exception handling.

The send_message method directly calls send_message on the SQS client. Ensure that any ClientError or related exceptions thrown by boto3 are appropriately handled or propagated if needed.

agave/tools/celery.py (1)

12-12: Public function rename is consistent.

Renaming _build_celery_message to build_celery_message is a sensible approach to make it publicly accessible. The signature and usage appear correct.

tests/tasks/test_imports.py (2)

7-21: Good use of parametrized testing.

Parametrizing modules and installation options under test is an efficient strategy to ensure coverage for both async and sync clients.


22-42: Robust assertions for import errors.

These checks thoroughly validate that the raised ImportError instructs users to install the correct extras. This ensures clarity and reduces runtime confusion.

tests/conftest.py (3)

4-5: Imports are properly organized and follow best practices.

The new imports are logically grouped and alphabetically ordered. The absolute import for agave.tasks is appropriate as it's importing from a main module path outside the test directory.

Also applies to: 8-9, 11-12, 16-18


240-256: Well-structured AWS endpoint URLs fixture with proper teardown.

The fixture correctly sets up a ThreadedMotoServer and properly tears it down after tests are complete. This ensures clean test isolation.


300-322: Well-implemented SQS client fixture with appropriate queue configuration.

The fixture properly sets up an SQS FIFO queue with content-based deduplication and correctly configures partial functions for common operations. The cleanup with purge_queue ensures tests don't interfere with each other.

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 4, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (57bfaec) to head (f4f6b15).
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main      #160   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           15        18    +3     
  Lines          623       652   +29     
  Branches        63        63           
=========================================
+ Hits           623       652   +29     
Flag Coverage Δ
unittests 100.00% <100.00%> (ø)

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

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🔭 Outside diff range comments (1)
agave/tools/sync/sqs_client.py (1)

39-49: 🛠️ Refactor suggestion

Add error handling and improve the send_message method.

The method should handle AWS SQS exceptions and provide helpful error messages. Also, consider adding a docstring explaining the parameters and functionality.

def send_message(
    self,
    data: Union[str, dict],
    message_group_id: Optional[str] = None,
) -> None:
+    """Send a message to the configured SQS queue.
+    
+    Args:
+        data: Message content as string or dict (will be JSON serialized)
+        message_group_id: Optional group ID for FIFO queues, defaults to UUID4
+    
+    Raises:
+        RuntimeError: If there's an error sending the message to SQS
+    """
+    try:
        self._sqs.send_message(
            QueueUrl=self.queue_url,
            MessageBody=data if isinstance(data, str) else json.dumps(data),
            MessageGroupId=message_group_id or str(uuid4()),
        )
+    except Exception as e:
+        raise RuntimeError(f"Failed to send message to SQS: {str(e)}") from e
🧹 Nitpick comments (1)
agave/tools/sync/sqs_client.py (1)

22-34: Consider adding documentation for context manager usage.

The context manager implementation is functional but lacks documentation about its purpose. Also, the __exit__ method doesn't perform any cleanup, which should be explained or addressed.

def __enter__(self) -> "SqsClient":
+    """Initialize the SQS client and return self for context manager usage."""
    self.start()
    return self

def __exit__(
    self,
    exc_type: Optional[type],
    exc_value: Optional[Exception],
    traceback: Optional[object],
) -> None:
-    # allow this class to support context manager
+    """Exit the context manager.
+    
+    This method doesn't close any resources as the boto3 SQS client
+    doesn't require explicit cleanup.
+    """
    ...
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c9851c and bb3de88.

📒 Files selected for processing (2)
  • agave/tools/asyncio/sqs_client.py (2 hunks)
  • agave/tools/sync/sqs_client.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • agave/tools/asyncio/sqs_client.py
🧰 Additional context used
📓 Path-based instructions (2)
`**/*.py`: Enforce Relative Imports for Internal Modules

En...

**/*.py: Enforce Relative Imports for Internal Modules

Ensure that any imports referencing internal modules use relative paths. However, if modules reside in the main module directories (for example /src or /library_or_app_name) —and relative imports are not feasible—absolute imports are acceptable. Additionally, if a module is located outside the main module structure (for example, in /tests or /scripts at a similar level), absolute imports are also valid.

Examples and Guidelines:

  1. If a module is in the same folder or a subfolder of the current file, use relative imports. For instance: from .some_module import SomeClass
  2. If the module is located under /src or /library_or_app_name and cannot be imported relatively, absolute imports are allowed (e.g., from library_or_app_name.utilities import helper_method).
  3. If a module is outside the main module directories (for example, in /tests, /scripts, or any similarly placed directory), absolute imports are valid.
  4. External (third-party) libraries should be imported absolutely (e.g., import requests).
  • agave/tools/sync/sqs_client.py
`**/*.py`: Rule: Enforce Snake Case in Python Backend
  1. N...

**/*.py:
Rule: Enforce Snake Case in Python Backend

  1. New or Modified Code: Use snake_case for all variables, functions, methods, and class attributes.
  2. Exceptions (Pydantic models for API responses):
    • Primary fields must be snake_case.
    • If older clients expect camelCase, create a computed or alias field that references the snake_case field.
    • Mark any camelCase fields as deprecated or transitional.

Examples

Invalid:

class CardConfiguration(BaseModel):
    title: str
    subTitle: str  # ❌ Modified or new field in camelCase

Valid:

class CardConfiguration(BaseModel):
    title: str
    subtitle: str  # ✅ snake_case for new/modified field

    @computed_field
    def subTitle(self) -> str:  # camelCase allowed only for compatibility
        return self.subtitle

Any direct use of camelCase in new or updated code outside of these exceptions should be flagged.

  • agave/tools/sync/sqs_client.py
🪛 Ruff (0.8.2)
agave/tools/sync/sqs_client.py

10-13: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🔇 Additional comments (2)
agave/tools/sync/sqs_client.py (2)

16-20: LGTM! The dataclass implementation is clean and well-typed.

The dataclass approach with proper type hints and the field(init=False) for the internal _sqs client is a good design choice.


1-5: LGTM! The imports are appropriate and well-organized.

The imported modules are relevant to the functionality being implemented and follow standard import organization (standard library first).

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
agave/tools/sync/sqs_client.py (3)

22-23: Add error handling to the __post_init__ method.

The method initializes the boto3 SQS client but doesn't handle potential AWS connection errors, which could lead to cryptic error messages for users.

def __post_init__(self) -> None:
+    """Initialize the boto3 SQS client with the configured region."""
+    try:
        self._sqs = boto3.client('sqs', region_name=self.region_name)
+    except Exception as e:
+        raise RuntimeError(f"Failed to initialize SQS client: {str(e)}") from e

25-34: Add error handling and documentation to the send_message method.

The method lacks error handling for potential AWS errors during message sending, and would benefit from documentation explaining its purpose and behavior.

def send_message(
    self,
    data: Union[str, dict],
    message_group_id: Optional[str] = None,
) -> None:
+    """
+    Send a message to the configured SQS queue.
+    
+    Args:
+        data: The message body as either a string or a dictionary (will be JSON serialized)
+        message_group_id: Optional group ID for FIFO queues, generates a UUID if not provided
+    
+    Raises:
+        RuntimeError: If there's an error sending the message to SQS
+    """
+    try:
        self._sqs.send_message(
            QueueUrl=self.queue_url,
            MessageBody=data if isinstance(data, str) else json.dumps(data),
            MessageGroupId=message_group_id or str(uuid4()),
        )
+    except Exception as e:
+        raise RuntimeError(f"Failed to send message to SQS: {str(e)}") from e

16-35: Add class-level docstring for better code documentation.

The class would benefit from a docstring explaining its purpose, usage, and how it fits into the overall architecture.

@dataclass
class SqsClient:
+    """
+    A synchronous client for interacting with AWS SQS (Simple Queue Service).
+    
+    This class provides methods to send messages to an SQS queue using boto3.
+    It requires the 'sync_aws_tools' extra dependencies to be installed.
+    
+    Attributes:
+        queue_url: The URL of the SQS queue to interact with
+        region_name: The AWS region where the queue is located
+    """
    queue_url: str
    region_name: str
    _sqs: Boto3SQSClient = field(init=False)
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bb3de88 and b26cae2.

📒 Files selected for processing (3)
  • agave/tools/sync/sqs_client.py (1 hunks)
  • tests/tools/sync/test_sqs_celery_client.py (1 hunks)
  • tests/tools/sync/test_sqs_client.py (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/tools/sync/test_sqs_celery_client.py
  • tests/tools/sync/test_sqs_client.py
🧰 Additional context used
📓 Path-based instructions (2)
`**/*.py`: Enforce Relative Imports for Internal Modules

En...

**/*.py: Enforce Relative Imports for Internal Modules

Ensure that any imports referencing internal modules use relative paths. However, if modules reside in the main module directories (for example /src or /library_or_app_name) —and relative imports are not feasible—absolute imports are acceptable. Additionally, if a module is located outside the main module structure (for example, in /tests or /scripts at a similar level), absolute imports are also valid.

Examples and Guidelines:

  1. If a module is in the same folder or a subfolder of the current file, use relative imports. For instance: from .some_module import SomeClass
  2. If the module is located under /src or /library_or_app_name and cannot be imported relatively, absolute imports are allowed (e.g., from library_or_app_name.utilities import helper_method).
  3. If a module is outside the main module directories (for example, in /tests, /scripts, or any similarly placed directory), absolute imports are valid.
  4. External (third-party) libraries should be imported absolutely (e.g., import requests).
  • agave/tools/sync/sqs_client.py
`**/*.py`: Rule: Enforce Snake Case in Python Backend
  1. N...

**/*.py:
Rule: Enforce Snake Case in Python Backend

  1. New or Modified Code: Use snake_case for all variables, functions, methods, and class attributes.
  2. Exceptions (Pydantic models for API responses):
    • Primary fields must be snake_case.
    • If older clients expect camelCase, create a computed or alias field that references the snake_case field.
    • Mark any camelCase fields as deprecated or transitional.

Examples

Invalid:

class CardConfiguration(BaseModel):
    title: str
    subTitle: str  # ❌ Modified or new field in camelCase

Valid:

class CardConfiguration(BaseModel):
    title: str
    subtitle: str  # ✅ snake_case for new/modified field

    @computed_field
    def subTitle(self) -> str:  # camelCase allowed only for compatibility
        return self.subtitle

Any direct use of camelCase in new or updated code outside of these exceptions should be flagged.

  • agave/tools/sync/sqs_client.py
🪛 Ruff (0.8.2)
agave/tools/sync/sqs_client.py

10-13: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

⏰ Context from checks skipped due to timeout of 90000ms (6)
  • GitHub Check: pytest (3.13)
  • GitHub Check: pytest (3.12)
  • GitHub Check: pytest (3.11)
  • GitHub Check: pytest (3.10)
  • GitHub Check: pytest (3.9)
  • GitHub Check: coverage
🔇 Additional comments (2)
agave/tools/sync/sqs_client.py (2)

6-13: Improve exception handling to preserve the original traceback.

The ImportError is correctly raised with a helpful message, but you should use the from clause to preserve the original exception context. This makes debugging easier by showing the complete chain of errors.

try:
    import boto3
    from types_boto3_sqs import SQSClient as Boto3SQSClient
-except ImportError:
+except ImportError as err:
    raise ImportError(
        "You must install agave with [sync_aws_tools] option.\n"
        "You can install it with: pip install agave[sync_aws_tools]"
-    )
+    ) from err
🧰 Tools
🪛 Ruff (0.8.2)

10-13: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


16-20: LGTM: Class definition with proper type annotations.

The dataclass structure with clear type annotations provides a clean interface for the SQS client.

@felipao-mx felipao-mx merged commit de7a3c4 into main Mar 5, 2025
12 checks passed
@felipao-mx felipao-mx deleted the sync-sqs-client branch March 5, 2025 00:05
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.

2 participants