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

fix: on_operation hooks unable to modify result on error. #3629

Merged
merged 13 commits into from
Sep 12, 2024

Conversation

nrbnlulu
Copy link
Member

@nrbnlulu nrbnlulu commented Sep 12, 2024

Description

Types of Changes

  • Core
  • Bugfix
  • New feature
  • Enhancement/optimization
  • Documentation

Issues Fixed or Closed by This PR

fix: #3625

Checklist

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • I have tested the changes and verified that they work and don't break anything (as well as I can manage).

Summary by Sourcery

Refactor the execute function to ensure on_operation hooks can modify the result on error and add a test to verify error masking in asynchronous operations.

Bug Fixes:

  • Fix the issue where on_operation hooks were unable to modify the result on error by restructuring the error handling logic in the execute function.

Tests:

  • Add an asynchronous test to verify that errors are correctly masked when executing a query with hidden errors.

Copy link
Contributor

sourcery-ai bot commented Sep 12, 2024

Reviewer's Guide by Sourcery

This pull request addresses an issue where on_operation hooks were unable to modify the result when an error occurred. The main changes involve restructuring the error handling logic in the execute function to allow for proper error processing and result modification.

File-Level Changes

Change Details Files
Restructured error handling in the execute function
  • Moved the try-except block inside the extensions_runner.operation() context
  • Rearranged the code to handle exceptions within the operation context
  • Changed variable name from 'res' to 'result' for clarity
  • Moved the final return statement inside the try block
strawberry/schema/execute.py
Added a new asynchronous test for error masking
  • Created a new test function 'test_mask_all_errors_async'
  • Implemented an async query with a hidden error
  • Verified that the error is properly masked in the result
tests/schema/extensions/test_mask_errors.py

Tips
  • Trigger a new Sourcery review by commenting @sourcery-ai review on the pull request.
  • Continue your discussion with Sourcery by replying directly to review comments.
  • You can change your review settings at any time by accessing your dashboard:
    • Enable or disable the Sourcery-generated pull request summary or reviewer's guide;
    • Change the review language;
  • You can always contact us if you have any questions or feedback.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @nrbnlulu - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟢 General issues: all looks good
  • 🟢 Security: all looks good
  • 🟡 Testing: 2 issues found
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment to tell me if it was helpful.

tests/schema/extensions/test_mask_errors.py Show resolved Hide resolved
"message": "Unexpected error.",
"path": ["hiddenError"],
}
]
def test_mask_some_errors():
Copy link
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Consider adding an async version of test_mask_some_errors

To maintain consistency and ensure both sync and async paths are tested equally, it would be beneficial to add an async version of the test_mask_some_errors test case.

def test_mask_some_errors():
    # ... existing code ...

import pytest

@pytest.mark.asyncio
async def test_mask_some_errors_async():
    # ... async version of the test ...

async with extensions_runner.operation():
# Note: In graphql-core the schema would be validated here but in
# Strawberry we are validating it at initialisation time instead
try:
Copy link
Contributor

Choose a reason for hiding this comment

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

issue (complexity): Consider restructuring the code to reduce nesting and improve readability.

The restructuring has increased code complexity by adding an extra level of nesting. While this change might have been intended to ensure proper closure of the context manager, it can be achieved without increasing the nesting level. Consider using a try-finally block to maintain the original structure while ensuring the context manager is always exited properly:

async def execute(...) -> ExecutionResult | PreExecutionError:
    try:
        async with extensions_runner.operation():
            # Note: In graphql-core the schema would be validated here but in
            # Strawberry we are validating it at initialisation time instead

            if errors := await _parse_and_validate_async(
                execution_context, extensions_runner
            ):
                return await _handle_execution_result(
                    execution_context, errors, extensions_runner, process_errors
                )

            assert execution_context.graphql_document
            result = None
            async with extensions_runner.executing():
                if not execution_context.result:
                    result = await await_maybe(
                        original_execute(
                            schema,
                            execution_context.graphql_document,
                            root_value=execution_context.root_value,
                            middleware=middleware_manager,
                            variable_values=execution_context.variables,
                            operation_name=execution_context.operation_name,
                            context_value=execution_context.context,
                            execution_context_class=execution_context_class,
                        )
                    )
                else:
                    result = execution_context.result

            # return results after all the operation completed.
            return await _handle_execution_result(
                execution_context, result, extensions_runner, process_errors
            )
    except (MissingQueryError, InvalidOperationTypeError) as e:
        raise e
    except Exception as exc:
        return await _handle_execution_result(
            execution_context,
            PreExecutionError(data=None, errors=[_coerce_error(exc)]),
            extensions_runner,
            process_errors,
        )
    finally:
        await extensions_runner.operation().__aexit__(None, None, None)

This approach maintains the original structure, reduces nesting, and ensures proper closure of the context manager in all cases, including when exceptions occur.

@botberry
Copy link
Member

botberry commented Sep 12, 2024

Thanks for adding the RELEASE.md file!

Here's a preview of the changelog:


This release fixes an issue that prevented extensions to receive the result from
the execution context when executing operations in async.

Here's the tweet text:

🆕 Release (next) is out! Thanks to ניר for the PR 👏

Get it here 👉 https://strawberry.rocks/release/(next)

Copy link
Member

@patrick91 patrick91 left a comment

Choose a reason for hiding this comment

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

Thank you so much!

Copy link

codecov bot commented Sep 12, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.76%. Comparing base (eb4f558) to head (951f88e).
Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3629   +/-   ##
=======================================
  Coverage   96.75%   96.76%           
=======================================
  Files         521      521           
  Lines       33715    33732   +17     
  Branches     5622     5627    +5     
=======================================
+ Hits        32622    32640   +18     
+ Misses        861      860    -1     
  Partials      232      232           

Copy link

codspeed-hq bot commented Sep 12, 2024

CodSpeed Performance Report

Merging #3629 will not alter performance

Comparing nrbnlulu:fix-mask-error (951f88e) with main (eb4f558)

Summary

✅ 15 untouched benchmarks

@patrick91 patrick91 merged commit 84c5319 into strawberry-graphql:main Sep 12, 2024
117 checks passed
@nrbnlulu nrbnlulu deleted the fix-mask-error branch September 13, 2024 06:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

MaskErrors does not work since version 0.240.0
3 participants