Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

improved get icd11 diagnoses functions #2566

Open
wants to merge 10 commits into
base: develop
Choose a base branch
from

Conversation

DraKen0009
Copy link
Contributor

@DraKen0009 DraKen0009 commented Oct 28, 2024

Proposed Changes

  • - Improvised get get_icd11_diagnosis_object_by_id function for better errror handling.
  • - Added a parameter in settings.base.py to turn off logging when running tests

Associated Issue

Merge Checklist

  • Tests added/fixed
  • Linting Complete

Only PR's with test cases included and passing lint and test pipelines will be reviewed

@ohcnetwork/care-backend-maintainers @ohcnetwork/care-backend-admins

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced error handling for retrieving ICD-11 diagnoses, including specific exceptions for diagnosis not found and Redis connection issues.
    • New custom exceptions introduced for better clarity on error types.
  • Bug Fixes

    • Improved validation and error messages for invalid diagnosis IDs.
  • Tests

    • Added comprehensive tests for various error scenarios related to ICD-11 diagnosis retrieval.
  • Documentation

    • Updated docstrings for functions to clarify their purpose and behavior.
  • Chores

    • Adjusted logging configuration to reduce verbosity during testing.

@rithviknishad rithviknishad self-requested a review November 4, 2024 14:09
except ValueError as err:
raise ValidationError(detail="ID must be an integer.") from err

diagnosis, error = get_icd11_diagnosis_object_by_id(pk, as_dict=True)
Copy link
Member

Choose a reason for hiding this comment

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

@sainak should we switch to raising exceptions instead of returning tuple containing error?

Copy link
Member

Choose a reason for hiding this comment

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

yeah lets follow python way

Copy link
Contributor Author

@DraKen0009 DraKen0009 Nov 5, 2024

Choose a reason for hiding this comment

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

I was facing problem with raising exceptions directly from the function

  1. unable to provide a detailed error in api responses.
  2. For above issue we can solve it by raising APIExceptions from the function but since that is also used at other non views functions, we shouldn't use APIExceptions in the function

Copy link
Member

Choose a reason for hiding this comment

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

you'll have to extend the apiexception to give the specific method, that will also allow in better error handelling

Copy link

codecov bot commented Nov 5, 2024

Codecov Report

Attention: Patch coverage is 93.75000% with 3 lines in your changes missing coverage. Please review.

Project coverage is 69.63%. Comparing base (d23cbcb) to head (9149409).

Files with missing lines Patch % Lines
care/facility/static_data/icd11.py 84.21% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2566      +/-   ##
===========================================
+ Coverage    69.55%   69.63%   +0.07%     
===========================================
  Files          212      212              
  Lines        11966    12006      +40     
  Branches      1208     1207       -1     
===========================================
+ Hits          8323     8360      +37     
- Misses        3274     3277       +3     
  Partials       369      369              

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


🚨 Try these New Features:

Copy link

coderabbitai bot commented Nov 6, 2024

📝 Walkthrough
📝 Walkthrough
📝 Walkthrough
📝 Walkthrough

Walkthrough

The changes introduced in this pull request significantly enhance error handling and validation across multiple components of the application. The ICDViewSet class's retrieve method now ensures that the primary key is an integer and raises specific exceptions for various error conditions. The get_icd11_diagnosis_object_by_id function has been updated to handle Redis and database errors more effectively. Additionally, new test cases have been added to cover these scenarios, and two custom exception classes have been created to improve clarity in error reporting.

Changes

File Path Change Summary
care/facility/api/viewsets/icd.py Updated retrieve method to include enhanced error handling and validation for pk. Added specific exceptions.
care/facility/static_data/icd11.py Enhanced get_icd11_diagnosis_object_by_id function with specific error handling and added a detailed docstring.
care/facility/tests/test_icd11_api.py Added new tests for various retrieval scenarios, including Redis errors and invalid IDs. Updated existing tests.
care/utils/exceptions.py Added two custom exceptions: ICD11DiagnosisNotFoundError and ICD11RedisConnectionError.
config/settings/base.py Updated logging configuration to reduce verbosity during tests.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant ICDViewSet
    participant Database
    participant Redis

    Client->>ICDViewSet: Request diagnosis with ID
    ICDViewSet->>ICDViewSet: Validate ID
    alt ID is valid
        ICDViewSet->>Redis: Check diagnosis in Redis
        alt Diagnosis found
            Redis-->>ICDViewSet: Return diagnosis
        else Diagnosis not found
            ICDViewSet->>Database: Check diagnosis in DB
            alt Diagnosis found
                Database-->>ICDViewSet: Return diagnosis
            else Diagnosis not found
                ICDViewSet-->>Client: 404 Not Found
            end
        end
    else ID is invalid
        ICDViewSet-->>Client: 400 Bad Request
    end
Loading

Poem

In the realm of code, where errors roam,
New exceptions arise, making bugs feel at home.
With tests that now cover each twist and turn,
Robustness is gained, and for knowledge, we yearn.
So here's to the changes, both big and small,
In the world of code, we shall conquer them all! 🎉


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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 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.

@DraKen0009 DraKen0009 marked this pull request as ready for review November 7, 2024 14:02
Copy link

@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: 3

🧹 Outside diff range and nitpick comments (10)
care/utils/exceptions.py (3)

3-4: I see we're being quite generous with vertical spacing here...

A single empty line between classes would suffice, but I suppose two empty lines make the code feel more... spacious? 🤔

class CeleryTaskError(Exception):
    pass
-

-
class ICD11DiagnosisNotFoundError(Exception):

5-11: The implementation looks fine, though it could be more... efficient.

While the implementation is correct, we could make it more concise by removing the redundant message assignment since Exception already handles this.

class ICD11DiagnosisNotFoundError(Exception):
    """Custom exception for ICD11 diagnosis not found."""

    def __init__(self, message):
-        self.message = message
-        super().__init__(self.message)
+        super().__init__(message)

12-18: Oh look, it's the same code again...

I see we're following the DRY principle... just kidding! 😉 This implementation is identical to the previous exception class. While that's not necessarily wrong, we could consider creating a base exception class if we plan to add more similar exceptions in the future.

Here's a more elegant approach:

+class BaseICD11Error(Exception):
+    """Base exception for ICD11-related errors."""
+    def __init__(self, message):
+        super().__init__(message)
+
+class ICD11DiagnosisNotFoundError(BaseICD11Error):
+    """Custom exception for ICD11 diagnosis not found."""
+
+class ICD11RedisConnectionError(BaseICD11Error):
+    """Custom exception for Redis connection issues."""
care/facility/tests/test_icd11_api.py (2)

61-68: Consider adding a test case for negative IDs... if you feel like it.

While the current test cases cover invalid string and zero IDs quite nicely, it might be worth adding a test for negative IDs. You know, just to be thorough... unless that's too much work.

# Additional test case suggestion
res = self.client.get("/api/v1/icd/-1/")
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
self.assertIn("Diagnosis with the specified ID not found.", res.json()["detail"])

95-101: The unexpected error test could use a bit more... personality.

While testing for generic exceptions is fine, it might be more helpful to test specific unexpected scenarios. You know, just in case someone wants to actually debug this someday.

Consider adding specific test cases for common unexpected errors:

@patch("care.facility.static_data.icd11.ICD11.get")
def test_retrieve_specific_unexpected_errors(self, mock_redis_get):
    # Test memory error
    mock_redis_get.side_effect = MemoryError("Out of memory")
    response = self.client.get("/api/v1/icd/123/")
    self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
    
    # Test timeout error
    mock_redis_get.side_effect = TimeoutError("Operation timed out")
    response = self.client.get("/api/v1/icd/123/")
    self.assertEqual(response.status_code, status.HTTP_500_INTERNAL_SERVER_ERROR)
config/settings/base.py (1)

357-360: Consider a more granular approach to log suppression.

While disabling logs during tests is reasonable, completely disabling CRITICAL logs might hide important issues. Perhaps we could:

  1. Only disable specific loggers
  2. Or maintain CRITICAL logs while disabling others
  3. Or make it configurable via an environment variable

After all, some of us might want to know when things go terribly wrong during tests... just saying.

Consider this more granular approach:

-# Disable logs when running tests
-if "test" in sys.argv:
-    logging.disable(logging.CRITICAL)
+# Configure logging for test environment
+if "test" in sys.argv:
+    LOGGING["root"]["level"] = env("TEST_LOG_LEVEL", default="CRITICAL")
+    # Optionally disable specific loggers while keeping CRITICAL logs enabled
+    for logger in LOGGING.get("loggers", {}):
+        LOGGING["loggers"][logger]["level"] = env("TEST_LOG_LEVEL", default="CRITICAL")
care/facility/api/viewsets/icd.py (1)

29-33: Refactor exception handling by creating a custom exception

Manually setting status_code on an APIException feels a bit clunky. Defining a custom exception class that inherits from APIException and sets the desired status_code would make the code cleaner and more maintainable.

Here's how you might implement it:

from rest_framework.exceptions import APIException

class ICD11RedisConnectionException(APIException):
    status_code = 400
    default_detail = "Redis connection error."

Then, update your exception handling:

-    except ICD11RedisConnectionError as e:
-        error_message = e.message
-        exception = APIException(error_message)
-        exception.status_code = 400
-        raise exception from e
+    except ICD11RedisConnectionError as e:
+        raise ICD11RedisConnectionException(detail=e.message) from e
care/facility/static_data/icd11.py (3)

64-67: Consider enhancing the docstring to provide more detailed information.

Adding parameter and return value descriptions can improve code readability and maintenance.

You might update the docstring as follows:

     def get_icd11_diagnosis_object_by_id(
         diagnosis_id: int, as_dict=False
     ) -> ICD11 | ICD11Object | None:
+        """
+        Retrieves ICD11 diagnosis by ID with Redis lookup and database fallback.
+        
+        Parameters:
+            diagnosis_id (int): The ID of the diagnosis to retrieve.
+            as_dict (bool): Whether to return the diagnosis as a dictionary representation.
+        
+        Returns:
+            ICD11 | ICD11Object | None: The diagnosis object or its dictionary representation.
+        
+        Raises:
+            ICD11DiagnosisNotFoundError: If the diagnosis with the specified ID is not found.
+            ICD11RedisConnectionError: If there is a Redis connection issue.
+            Exception: If an unexpected error occurs.
+        """

86-88: Consider logging the exception before raising ICD11DiagnosisNotFoundError.

Adding a log entry can assist in debugging when a diagnosis is not found.

You might update the code as follows:

     except ICD11Diagnosis.DoesNotExist as e:
+        logger.warning(
+            "Diagnosis with ID %s not found in the database.",
+            diagnosis_id,
+        )
         error_message = "Diagnosis with the specified ID not found."
         raise ICD11DiagnosisNotFoundError(error_message) from e

90-93: Consider logging the RedisError before raising ICD11RedisConnectionError.

This can help in diagnosing Redis connection issues.

You might update the code as follows:

     except RedisError as e:
+        logger.error(
+            "Redis connection issue encountered.",
+            exc_info=True,
+        )
         error_message = "Redis connection issue encountered."
         raise ICD11RedisConnectionError(error_message) from e
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between d6d069e and 15b0d4f.

📒 Files selected for processing (5)
  • care/facility/api/viewsets/icd.py (1 hunks)
  • care/facility/static_data/icd11.py (2 hunks)
  • care/facility/tests/test_icd11_api.py (2 hunks)
  • care/utils/exceptions.py (1 hunks)
  • config/settings/base.py (2 hunks)
🔇 Additional comments (4)
care/utils/exceptions.py (1)

5-18: The new exception classes align well with the PR objectives.

The addition of these custom exceptions improves error handling specificity for ICD11-related operations, which was a key objective of this PR. The docstrings are clear and the inheritance hierarchy is appropriate.

Let's verify these exceptions are being used consistently:

✅ Verification successful

The exceptions are used appropriately, though they could use a bit more coverage

The exceptions are properly imported and handled in the ICD11-related operations, specifically in:

  • care/facility/static_data/icd11.py: Raises both exceptions with proper error propagation
  • care/facility/api/viewsets/icd.py: Catches and transforms them into appropriate API responses

The error handling follows a clean pattern where:

  • ICD11DiagnosisNotFoundError maps to HTTP 404 (NotFound)
  • ICD11RedisConnectionError maps to a generic APIException

Though it would have been nice to see these exceptions used in more places, the current implementation is technically sound and follows good practices.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for consistent usage of the new exception classes
# Expected: These exceptions should be used in ICD11-related operations

# Search for exception usage
echo "Checking exception usage patterns..."
rg -A 2 "ICD11DiagnosisNotFoundError|ICD11RedisConnectionError"

# Check for any other ICD11-related error handling we might have missed
echo "Checking for other potential ICD11 error handling..."
rg -i "icd.*error|icd.*exception"

Length of output: 2814

care/facility/tests/test_icd11_api.py (2)

1-4: LGTM! The imports are properly organized.

The new Redis-related exception imports align perfectly with the PR's objective of improving error handling.


70-94: Excellent error scenario coverage!

The test cases for Redis/DB not found and connection errors are well-structured and comprehensive. The use of mocking is particularly elegant.

config/settings/base.py (1)

6-6: LGTM: Clean import addition.

The sys module import is properly placed with other standard library imports.

care/facility/api/viewsets/icd.py Show resolved Hide resolved
care/facility/api/viewsets/icd.py Show resolved Hide resolved
care/facility/static_data/icd11.py Show resolved Hide resolved
@DraKen0009 DraKen0009 requested a review from a team as a code owner November 23, 2024 13:35
@DraKen0009
Copy link
Contributor Author

@sainak can you please review it

@DraKen0009
Copy link
Contributor Author

the exception handling comments by code rabbit are not needed .I've kept the generic Exception catching at the highest level, so it first check through all the named exceptions , but if error something completely different then only last Exception block is triggered

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants