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
Support freevars and methods that call mangled methods #68
Conversation
patchy/api.py
Outdated
|
||
|
||
def _indent(new_source): | ||
return '\n'.join([' ' + x for x in new_source.split('\n')]) |
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 is broken in so many ways. It will mess up docstrings and functions that use tabs in py3k.
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.
mess up docstrings only slightly?
Also yes there will be a TabError
, you could add a test for that
patchy/api.py
Outdated
# Fetch the actual function we are changing | ||
class_name = _class_name(func) | ||
if class_name: | ||
new_source = 'class {name}(object):\n{code}'.format( |
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.
might be better to parse the code and do a find and replace with identifiers.
Turns out mangling works everywhere:
def _Artist__foo():
return 'yo!'
class Artist(object):
def method(self):
return __foo()
def test_mangle():
assert Artist().method() == 'yo!'
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.
find/replace sounds even more hacky
also that test_mangle is a bit of a WAT🦆
tests/tabs_mangled.py
Outdated
@@ -0,0 +1,7 @@ | |||
class Artist: |
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.
pytest tmpdir instead to keep this inside the test function? you can always write \t
in the string. just wary tabs might get complained about by future lint rules
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's complained about by current lint rules
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.
yeah used the pytest tmpdir.
tests/test_patchy.py
Outdated
|
||
assert Artist().method() == "Cheese on toast" | ||
|
||
@pytest.mark.xfail |
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.
why does this fail? because the new method is compiled without the original _Artist__mangled_name
in scope?
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, needed to fix freevars/closures. Interestingly it doesn't need to be the original _Artist__mangled_name, just any var with the same name.
@@ -148,25 +149,57 @@ def _get_source(func): | |||
return source | |||
|
|||
|
|||
def _set_source(func, new_source): | |||
def _class_name(func): |
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.
there's nothing in the stnadard library for this? 😢
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.
func.im_class.__name__
but it was replaced with __qualname__
because __qualname__
is 'easier'
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.
Yeah I'm just sad so much parsing is needed on __qualname__
86aedaa
to
e84b599
Compare
@adamchainz worth splitting out the freevars stuff into a separate PR? |
c3cf28c
to
9087232
Compare
@adamchainz I think it's a good idea to release this as v2 just in case it breaks anyone |
036880f
to
9087232
Compare
tests/base.py
Outdated
|
||
import patchy.api | ||
|
||
|
||
class PatchyTestCase(unittest.TestCase): | ||
def setUp(self): | ||
class PatchyTestCase(object): |
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.
can you put this unittest -> pytest stuff in a separate PR please, it's adding noise to this commit
tests/test_patchy.py
Outdated
|
||
assert Artist().method() == "Cheese on toast" | ||
|
||
@unittest.skipUnless(six.PY3, "Python 3") |
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 explanation should point out that it's super()
that's Python 3 only
dont_inherit=True, | ||
) | ||
|
||
def _parse(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.
this doesn't need to be an inner 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.
It does to call _compile with the feature_flags freevar.
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.
so it does, I think I read it as calling compile
, my bad
patchy/api.py
Outdated
def _parse(code): | ||
return _compile(code, flags=ast.PyCF_ONLY_AST) | ||
|
||
class_name = _class_name(func) |
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.
there is so much going on here now, i think we need it splitting into more functions and some comments added
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've split it into more functions and added doc strings.
|
||
assert sample()() == "Cheese on toast" | ||
|
||
@pytest.mark.xfail(raises=NameError) |
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 would be surprised if this could ever be made to work, but I guess never say never...
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'm pretty sure it could be done by digging around in the garbage collector. But it would be unreliable
cc4743d
to
9087232
Compare
by adding empty statements that force the use of all freevars.
d98d403
to
fd02557
Compare
Merged in 3de5de6, thanks! |
Fixes #67