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
bpo-29615: SimpleXMLRPCDispatcher no longer chains KeyError (or any other exception) to exception(s) raised in the dispatched methods. #260
Conversation
Hello, and thanks for your contribution! I'm a bot set up to make sure that the project can legally accept your contribution by verifying you have signed the PSF contributor agreement (CLA). Unfortunately we couldn't find an account corresponding to your GitHub username on bugs.python.org (b.p.o) to verify you have signed the CLA. This is necessary for legal reasons before we can look at your contribution. Please follow these steps to help rectify the issue:
Thanks again to your contribution and we look forward to looking at it! |
Lib/xmlrpc/server.py
Outdated
else: | ||
raise Exception('method "%s" is not supported' % method) | ||
def func(*params): | ||
return dispatch_func(method=method, params=params) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not required that the dispatch function supports keyword arguments.
What is the purpose of this change? Why not call the dispatch function directly?
return func(method, params)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not required that the dispatch function supports keyword arguments.
Agreed, changed.
What is the purpose of this change? Why not call the dispatch function directly?
The purpose of this change is to make sure no exception handling is in progress when the function is called, thus I do attempt to first resolve the function (handling exceptions as I go) and then once the function is (hopefully) resolved, I attempt to call it
I did add @functools.wraps
around the wrapper (forgot to check that in earlier)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is why I suggest to move all code after except KeyError
out of exception handling block. This will allow to get rid of exception chaining and may make the code cleaner.
Lib/xmlrpc/server.py
Outdated
self.allow_dotted_names | ||
) | ||
except AttributeError: | ||
raise Exception('method "%s" is not supported' % method) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This chains two AttributeErrors and a KeyError to the raised exception.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know. But is that a problem, though? For me, this was the intended feature as it lets the caller know (using the exception chain) what steps were taken (and failed) to find the appropriate function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks a problem for me. These exceptions don't add useful information and are implementation details. For example current implementation uses hasattr()
rather than catching an AttributeError
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's leave the current implementation aside in terms of what's correct and incorrect :)
However, I will remove the chaining, as you suggest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chaining removed
Lib/xmlrpc/server.py
Outdated
except AttributeError: | ||
# call instance method directly | ||
try: | ||
func = resolve_dotted_attribute( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if resolve_dotted_attribute()
returns None
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @serhiy-storchaka , this is my 1st PR and 1st comment, so let me take this opportunity to thank you for the review :).
What about other (non-callable) objects, though? I mean, it's all the same and it doesn't really matter if whatever the function returns is NoneType
or, let's say, int(2)
--> it's still wrong. The original code does not check that either and it simply attempts to call the result.
I would let Python deal with the NoneType and other non-callable types and raise it's own TypeError and leave this part of the code untouched. It is still a subclass of Exception, so the signature, if you will, of this function is still the same
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None
is handled special in current code. Existing code can set an attribute to None
for disabling an inherited method. A user get a more informative exception 'method "{methodname}" is not supported' rather than "'NoneType' object is not callable". I don't know whether this is used in real code, but there is a risk of breaking existing code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, let me fix that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed, although the code is now a little bit more bloated than I would have liked, but it works.
Lib/xmlrpc/server.py
Outdated
self.allow_dotted_names | ||
) | ||
except AttributeError: | ||
raise Exception('method "%s" is not supported' % method) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks a problem for me. These exceptions don't add useful information and are implementation details. For example current implementation uses hasattr()
rather than catching an AttributeError
.
Lib/xmlrpc/server.py
Outdated
except AttributeError: | ||
# call instance method directly | ||
try: | ||
func = resolve_dotted_attribute( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
None
is handled special in current code. Existing code can set an attribute to None
for disabling an inherited method. A user get a more informative exception 'method "{methodname}" is not supported' rather than "'NoneType' object is not callable". I don't know whether this is used in real code, but there is a risk of breaking existing code.
Lib/xmlrpc/server.py
Outdated
else: | ||
raise Exception('method "%s" is not supported' % method) | ||
def func(*params): | ||
return dispatch_func(method=method, params=params) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is why I suggest to move all code after except KeyError
out of exception handling block. This will allow to get rid of exception chaining and may make the code cleaner.
…ts to invoke `None`
Lib/xmlrpc/server.py
Outdated
@@ -103,7 +103,7 @@ def export_add(self, x, y): | |||
|
|||
# Written by Brian Quinlan (brian@sweetapp.com). | |||
# Based on code written by Fredrik Lundh. | |||
|
|||
import functools |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not used.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, gone.
Lib/test/test_xmlrpc.py
Outdated
@@ -343,6 +343,24 @@ def run_server(): | |||
self.assertEqual(p.method(), 5) | |||
self.assertEqual(p.method(), 5) | |||
|
|||
def test_simple_xml_rpc_dispatcher_using_dispatch_func(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test is passed when leave Lib/xmlrpc/server.py
unchanged. Thus it doesn't test changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad. I forgot to check in the proper test earlier.
Lib/xmlrpc/server.py
Outdated
except AttributeError: | ||
pass | ||
|
||
if func is not None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For compatibility with current code it should be just else
if use try..except
.
Lib/xmlrpc/server.py
Outdated
if self.instance is not None: | ||
try: | ||
# check for a _dispatch method on the instance | ||
func = self.instance._dispatch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be better to use other name for local variable.
But I suggest to keep the current code, with hasattr()
. Catching exceptions is costly in Python, and if _dispatch
is not defined this causes regression.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok. Rewritten. I believe it is now in sync with the original code in every aspect, but the exception chaning issue I am attempting to solve.
…tting rid of the exception chaining
… no longer chained to exceptions raised within `_dispatch` function
Hi @serhiy-storchaka , in addition to the changes you requested directly in the code, I added a line to EDIT: Travis complained about test coverage, so I added tests that ensure that all 3 methods of locating the function to be dispatched work and neither of them chains any exceptions when the function gets called In order to get 100 % coverage of the Thanks |
…ace when calling dispatched funcs
Lib/test/test_xmlrpc.py
Outdated
try: | ||
dispatcher._dispatch(method='dispatched_func', params=exp_params) | ||
except DispatchExc as e: | ||
self.assertFalse(e.__cause__) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
assertIsNone()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Lib/test/test_xmlrpc.py
Outdated
dispatcher = xmlrpc.server.SimpleXMLRPCDispatcher() | ||
dispatcher.register_function(dispatched_func) | ||
try: | ||
dispatcher._dispatch(method='dispatched_func', params=exp_params) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to pass arguments as positional. It is shorter and more reliable. Parameter names is an implementation detail that we don't want to test here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. I am sorry about this, but in my code I tend to use keywords all the time, it makes it so much easier to understand what the code does when you see the keywords :). Removed.
Lib/test/test_xmlrpc.py
Outdated
dispatcher.register_function(dispatched_func) | ||
try: | ||
dispatcher._dispatch(method='dispatched_func', params=exp_params) | ||
except DispatchExc as e: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test is passed if DispatchExc
is not raised.
Use assertRaises()
as a context manager.
with self.assertRaises(DispatchExc) as cm:
dispatcher._dispatch('dispatched_func', exp_params)
self.assertIsNone(cm.exception.__cause__)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OH, snap. I always forget that context variables are visible outside the with
statement. Sure will do this, that's great.
Lib/test/test_xmlrpc.py
Outdated
@@ -343,6 +343,78 @@ def run_server(): | |||
self.assertEqual(p.method(), 5) | |||
self.assertEqual(p.method(), 5) | |||
|
|||
def test_simple_xml_rpc_dispatcher_resolves_registered_func_and_does_not_chain_excs_when_calling_it(self): | |||
exp_params = 1, 2, 3 | |||
dispatched = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking that dispatched
is set is redundant in these tests. It is obvious that it is set since the exception is raised after that. By removing dispatched
you can save few lines of code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
Lib/test/test_xmlrpc.py
Outdated
exp_params = 1, 2, 3 | ||
dispatched = False | ||
|
||
class DispatchExc(Exception): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DispatchExc
is used in several tests. It is worth to define it at the module level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope it's ok to define it at a test case level for now. (SimpleXMLRPCDispatcherTestCase.DispatcherExc
that is)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You will need to specify long qualified name (SimpleXMLRPCDispatcherTestCase.DispatcherExc
) or use outer self
to access it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know :)
Lib/xmlrpc/server.py
Outdated
raise Exception('method "%s" is not supported' % method) | ||
return func(*params) | ||
|
||
if self.instance is None: | ||
raise Exception('method "%s" is not supported' % method) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raise Exception()
is repeated multiple times. I think it is enough to repeat it only two times. Once when self.funcs[method]
is None
, and other time at the end of this method, as in the previous version of your code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, right. That's true. Done.
Lib/xmlrpc/server.py
Outdated
if hasattr(self.instance, '_dispatch'): | ||
# call the `_dispatch` method on the instance | ||
return self.instance._dispatch(method, params) | ||
else: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the other branch is ended by return
, else
is redundant.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
Misc/NEWS
Outdated
@@ -10,6 +10,10 @@ What's New in Python 3.7.0 alpha 1? | |||
Core and Builtins | |||
----------------- | |||
|
|||
- issue29615: xmlrpc.server.SimpleXMLRPCDispatcher.instance._dispatch function is no longer called in an except block |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The line is too long. Add periods after ends of sentences. Use "bpo-29615" instead of "issue29615". This entry should be in the "Library" section rather of "Core and Builtins".
I think it would be better to reformulate the entry as "SimpleXMLRPCDispatcher no longer adds KeyError exception in the traceback of exception raised in the method of the registered instance." Or something like. Describe the problem that is solved, not the solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, reworded
Misc/NEWS
Outdated
@@ -10,6 +10,10 @@ What's New in Python 3.7.0 alpha 1? | |||
Core and Builtins | |||
----------------- | |||
|
|||
- issue29615: xmlrpc.server.SimpleXMLRPCDispatcher.instance._dispatch function is no longer called in an except block | |||
Patch by Petr MOTEJLEK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Patch by Petr Motejlek.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
Misc/NEWS
Outdated
- issue29615: xmlrpc.server.SimpleXMLRPCDispatcher.instance._dispatch function is no longer called in an except block | ||
Patch by Petr MOTEJLEK | ||
|
||
- bpo-29607: Fix stack_effect computation for CALL_FUNCTION_EX. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be gone
…e and renamed tests in it to have shorter names
… uses self.assertRaises to ensure it was raised
…nd moved it higher
Hi @serhiy-storchaka , I think I addressed all your comments. Please check. There is still the coverage issue, though. What should I do about checking the raised exception when no methods can be located, please?
|
I think it is enough to catch the general |
…he behavior when no function can be located by the dispatcher
… attribute does not exist
Ok, let's see now. I believe all comments have been addressed and there should be 100 % coverage on the updated _dispatch function |
Lib/test/test_xmlrpc.py
Outdated
BinaryTestCase, FaultTestCase, UseBuiltinTypesTestCase, | ||
SimpleServerTestCase, SimpleServerEncodingTestCase, | ||
KeepaliveServerTestCase1, KeepaliveServerTestCase2, | ||
support.run_unittest(XMLRPCTestCase, SimpleXMLRPCDispatcherTestCase, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to add new test at the end of the list of tests or at separate line. This would make the diff smaller and easier fore review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
Misc/NEWS
Outdated
@@ -677,6 +677,10 @@ Library | |||
- Issue #24142: Reading a corrupt config file left configparser in an | |||
invalid state. Original patch by Florian Höch. | |||
|
|||
- bpo-29615: SimpleXMLRPCDispatcher no longer chains KeyError (or any other |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Entries usually are added at the start of corresponding section.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure
Lib/test/test_xmlrpc.py
Outdated
dispatcher.register_function(dispatched_func) | ||
with self.assertRaises(self.DispatchExc) as exc_ctx: | ||
dispatcher._dispatch('dispatched_func', exp_params) | ||
self.assertTupleEqual(exc_ctx.exception.args, (exp_params,)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just assertEqual()
can be used. It calls assertTupleEqual()
for tuples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Darn. And here I thought you would like this :D
Lib/test/test_xmlrpc.py
Outdated
chained exceptions""" | ||
|
||
def test_call_registered_func(self): | ||
"""Calls explicitly registered function |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comments and docstrings that consist of multiple sentences should have periods at the end of sentences.
If you want the first line of the docstring be without a period, turn the rest of the docstring into a comment.
"""Calls explicitly registered function"""
# Makes sure any exception raised inside the function has no other
# exception chained to it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will fix this, this seems reasonable
Ok, @serhiy-storchaka . I think once the sanity checks pass, we are golden. Thank you very much for the review and patience :) |
Lib/test/test_xmlrpc.py
Outdated
exception chained to it""" | ||
"""Calls explicitly registered function""" | ||
# Makes sure any exception raised inside the function has no other | ||
# exception chained to it""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"""
is not needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hasty typo. Removed.
I think it is worth to backport this to 3.6 and 3.5. Could you please do this @petr-motejlek? |
Will be my pleasure, @serhiy-storchaka. I wanted to do that in the first place, but did not know the proper procedure. What's that? Do I create separate PRs to the respective version branches? |
Yes, just create separate PRs. Add corresponding prefix ("[3.6]") and label ("cherry-pick for 3.6"). |
No description provided.