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

feat(API): on exception, select most specific error handler available #1603

Merged

Conversation

csojinb
Copy link
Contributor

@csojinb csojinb commented Nov 2, 2019

Summary of Changes

Rather than selecting error handlers in LIFO order of registration, select the error handler corresponding to the nearest direct ancestor of the exception type raised. So, for example, if an app only adds a custom error handler for the Python Exception class, and an HTTPForbidden error is raised, then we use the default handler for HTTPError rather than the more general Exception handler (which is the pre-existing behavior).

This is implemented by storing error handlers on the API object as a dict rather than a list and looking them up using the method resolution order attribute (__mro__) on the raised exception class.

NOTE:

This commit only includes the actual implementation and does not address testing or documentation. I am seeking implementation feedback before completing those additional changes. Tests and documentation have been added. Thanks @vytas7 for implementation notes!

BREAKING CHANGE:

Registration of a new error handler for type E will no longer override previously-registered error handlers for subclasses of type E. Registration order will no longer matter except when multiple error handlers are registered for the exact same exception type, in which case the most recently registered error handler overrides the previous ones.

Related Issues

Addresses #1514.

I believe it would also allow reversion of #1599. Not true. I misremembered why #1599 was needed.

Pull Request Checklist

This is just a reminder about the most common mistakes. Please make sure that you tick all appropriate boxes. But please read our contribution guide at least once; it will save you a few review cycles!

If an item doesn't apply to your pull request, check it anyway to make it apparent that there's nothing to do.

  • Added tests for changed code.
  • Prefixed code comments with GitHub nick and an appropriate prefix.
  • Coding style is consistent with the rest of the framework.
  • Updated documentation for changed code.
    • Added docstrings for any new classes, functions, or modules.
    • Updated docstrings for any modifications to existing code.
    • Added references to new classes, functions, or modules to the relevant RST file under docs/.
    • Updated all relevant supporting documentation files under docs/.
    • A copyright notice is included at the top of any new modules (using your own name or the name of your organization).
    • Changed/added classes/methods/functions have appropriate versionadded, versionchanged, or deprecated directives.
  • Changes (and possible deprecations) have towncrier news fragments under docs/_newsfragments/. (Run towncrier --draft to ensure it renders correctly.)

If you have any questions to any of the points above, just submit and ask! This checklist is here to help you, not to deter you from contributing!

PR template inspired by the attrs project.

falcon/api.py Outdated Show resolved Hide resolved
falcon/api.py Outdated Show resolved Hide resolved
falcon/api.py Outdated Show resolved Hide resolved
@csojinb csojinb force-pushed the 1514-smart-exception-handler-precedence branch from 47951cb to a7398b7 Compare November 2, 2019 21:34
@csojinb
Copy link
Contributor Author

csojinb commented Nov 2, 2019

Would this change warrant a towncrier entry? Or a versionchanged in the docstring? Seems like probably a versionchanged would be good... should I just assume 3.0 for now? I see that #1514 is currently marked as version 3.0, so I'll go with that.

@csojinb csojinb force-pushed the 1514-smart-exception-handler-precedence branch from a7398b7 to 97e297e Compare November 2, 2019 21:50
@codecov
Copy link

codecov bot commented Nov 2, 2019

Codecov Report

Merging #1603 into master will not change coverage.
The diff coverage is n/a.

Impacted file tree graph

@@          Coverage Diff           @@
##           master   #1603   +/-   ##
======================================
  Coverage     100%    100%           
======================================
  Files          40      40           
  Lines        2692    2692           
  Branches      397     397           
======================================
  Hits         2692    2692

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update b68c4ff...2cee9c1. Read the comment docs.

falcon/api.py Outdated Show resolved Hide resolved
@csojinb csojinb marked this pull request as ready for review November 2, 2019 22:00
@vytas7
Copy link
Member

vytas7 commented Nov 3, 2019

@csojinb Re Towncrier I would think, yes, we need an entry (or actually two?) since it is both a breaking change, and a new feature.

falcon/api.py Outdated
@@ -269,7 +269,8 @@ def __call__(self, env, start_response): # noqa: C901
# next-hop child resource. In that case, the object
# being asked to dispatch to its child will raise an
# HTTP exception signalling the problem, e.g. a 404.
responder, params, resource, req.uri_template = self._get_responder(req)
responder, params, resource, req.uri_template = self._get_responder(
req)
Copy link
Contributor

Choose a reason for hiding this comment

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

Not a big deal, but do we need additional space for self._get_responder?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean, move it to another line because it's past 80 characters? Or, are you asking if this change shouldn't have occurred?

I didn't intentionally make this change here, I think it must have been my auto-pep8. It looks like Falcon's pep8-checker rules allow 99-character lines, so probably I should revert this newline change. I guess my tool didn't pick up the config.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, falcon allows 99-characters lines, flake8 config.

In that case, I think would be better to revert that newline change

falcon/api.py Outdated
@@ -879,7 +898,8 @@ def _get_body(self, resp, wsgi_file_wrapper=None):
iterable = wsgi_file_wrapper(stream,
self._STREAM_BLOCK_SIZE)
else:
iterable = helpers.CloseableStreamIterator(stream, self._STREAM_BLOCK_SIZE)
iterable = helpers.CloseableStreamIterator(
stream, self._STREAM_BLOCK_SIZE)
Copy link
Contributor

Choose a reason for hiding this comment

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

And the same here:)

Copy link
Member

@kgriffs kgriffs left a comment

Choose a reason for hiding this comment

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

This looks good, do you think you can finish this up soon? I'd love to get this merged for the alpha release. AFAICT the remaining TODO items include:

  • Add towncrier news fragments
  • Rebase on master (fix conflicts)
  • Tweak docstrings (optional)
  • Tweak line wrapping (optional)

falcon/api.py Outdated Show resolved Hide resolved
@csojinb
Copy link
Contributor Author

csojinb commented Dec 12, 2019 via email

`_handle_error` returns false when the exception does not match a
custom handler and is not a subclass of ``HTTPError``, ``HTTPStatus``,
or ``Exception``. The update to include ``Exception`` was missed in
PR falconry#1527, which added a default handler for ``Exception``
Addresses falconry#1514

Rather than selecting error handlers in LIFO order of registration,
select the error handler corresponding to the nearest direct
ancestor of the exception type raised. So, for example, if an app
only adds a custom error handler for the Python ``Exception`` class,
and an ``HTTPForbidden`` error is raised, then we use the
default handler for ``HTTPError`` rather than the more general
``Exception`` handler (which is the pre-existing behavior).

This is implemented by storing error handlers on the API object
as a dict rather than a list and looking them up using the method
resolution order attribute (`__mro__`) on the raised exception
class.

NOTE: This commit only includes the actual implementation and does
not address testing or documentation. I am seeking implementation
feedback before completing those additional changes.

BREAKING CHANGE: Registration of a new error handler for type E
will no longer override previously-registered error handlers for
subclasses of type E. Registration order will no longer matter
*except* when multiple error handlers are registered for the
exact same exception type, in which case the most recently
registered error handler overrides the previous ones.
@csojinb csojinb force-pushed the 1514-smart-exception-handler-precedence branch 2 times, most recently from 0c7341d to 39d2d81 Compare December 15, 2019 22:59
@csojinb
Copy link
Contributor Author

csojinb commented Dec 15, 2019

Okay, I rebased, undid the overzealous pep8-ing, tweaked the add_error_handler docs, and added a towncrier fragment. I think that's everything. cc @kgriffs @vytas7

Copy link
Member

@vytas7 vytas7 left a comment

Choose a reason for hiding this comment

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

Looking great 👍

Just one thing, not sure if the GH PR interface is playing tricks on me, or did you happen to reintroduce falcon/api.py by manual master merge? This file is now removed in master.

@csojinb
Copy link
Contributor Author

csojinb commented Dec 15, 2019

Oh, so it is. I guess kdiff made that decision for me and I didn't look closely enough. I'll fix it and re-push

@csojinb csojinb force-pushed the 1514-smart-exception-handler-precedence branch from 39d2d81 to 347eed1 Compare December 15, 2019 23:23
@csojinb
Copy link
Contributor Author

csojinb commented Dec 15, 2019

@vytas7 fixed

@csojinb
Copy link
Contributor Author

csojinb commented Dec 15, 2019

Moving my copied TODO list out of the PR description into this comment:

TODOs:

  • Add towncrier news fragments
  • Rebase on master (fix conflicts)
  • Tweak docstrings (optional)
  • Tweak line wrapping (optional)

(Turns out you can't just check off checkboxes in someone else's comment.)

@csojinb csojinb mentioned this pull request Dec 16, 2019
11 tasks
Copy link
Member

@vytas7 vytas7 left a comment

Choose a reason for hiding this comment

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

👍

@vytas7 vytas7 requested a review from kgriffs December 16, 2019 22:18
@kgriffs
Copy link
Member

kgriffs commented Dec 16, 2019

🎉

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.

4 participants