This is an archive of the discontinued Mercurial Phabricator instance.

extensions: add wrappedfunction() context manager
ClosedPublic

Authored by martinvonz on Aug 22 2017, 2:29 AM.

Details

Summary

Several extensions exist that temporarily want to wrap a function (at
least narrowhg, any many of the extensions in hg-experimental). That's
why we have the unwrapfunction() that was introduced in 19578bb84731
(extensions: add unwrapfunction to undo wrapfunction, 2016-08-10).

This patch adds a simple wrappedfunction() that returns a context
manager.

Diff Detail

Repository
rHG Mercurial
Lint
Automatic diff as part of commit; lint not applicable.
Unit
Automatic diff as part of commit; unit tests not applicable.

Event Timeline

martinvonz created this revision.Aug 22 2017, 2:29 AM
quark accepted this revision.

cc python expert @mjpieters to see if he has additional comments. But I think this is good enough.

mjpieters added inline comments.Aug 23 2017, 9:32 AM
mercurial/extensions.py
408–412

Move this to __enter__. You want to be able to create the context manager separately from actually entering the context:

cmwrapper = wrapfunction(cont, 'name', wrapper)

# do other things, `cont.name` is *not* wrapped

with cmwrapper:
    # `cont.name` is wrapped
mjpieters requested changes to this revision.Aug 23 2017, 9:32 AM
This revision now requires changes to proceed.Aug 23 2017, 9:32 AM
quark added inline comments.Aug 23 2017, 11:44 AM
mercurial/extensions.py
408–412

The point is wrapfunction is also intended to work with non-context case:

wrapfunction(x, y, z) # works and should have immediate side effect

Maybe instead of replacing wrapfunction with a context manager, what about introducing it with a separate name? All users of wrapfunction will need to be updated anyway to use the context manager style

quark added a comment.EditedAug 23 2017, 11:57 AM

All users of wrapfunction will need to be updated anyway to use the context manager style

I think only those who use the return value of wrapfunction will need change (actually, new code is more correct regarding on nested wrapfunction handling) and expect that to be not too many. If wrapfunction is used to replace a function permanently, it does not need change.

Reusing name is more consistent with repo.transaction, repo.lock and open.

In D472#7758, @quark wrote:

All users of wrapfunction will need to be updated anyway to use the context manager style

I think only those who use the return value of wrapfunction will need change (actually, new code is more correct regarding on nested wrapfunction handling) and expect that to be not too many. If wrapfunction is used to replace a function permanently, it does not need change.
Reusing name is more consistent with repo.transaction, repo.lock and open.

I personally don't care much which way we go here. I originally did make it a separate function (wrappedfunction()) and did the wrapping in enter, but I'm fine either way.

mjpieters accepted this revision.Aug 24 2017, 6:14 AM

I was being too strict; like open() it's sometimes fine to apply the action in the constructor.

yuja added a subscriber: yuja.Aug 27 2017, 6:37 AM
yuja added inline comments.
mercurial/extensions.py
424

When will this __get__() be called?

wrapfunction() is generally used for wrapping a free function,
not an unbound method.

martinvonz added inline comments.Aug 27 2017, 12:41 PM
mercurial/extensions.py
424

See commit message. It's for compatibility with existing callers that bind the returned value.

yuja added inline comments.Aug 28 2017, 9:33 AM
mercurial/extensions.py
424

I see, but I don't think it's more common than wrapping a plain
function.

If the compatibility matters, we probably shouldn't replace
wrapfunction with a context manager.

quark added inline comments.Aug 28 2017, 1:02 PM
mercurial/extensions.py
424

I think using the return value of wrapfunction could lead to hidden bugs:

oldfunc = wrapfunction(x, 'y', newfunc)
...
# what if somebody else wraps x.y here?
...
x.y = oldfunc

So it seems reasonable to me to deprecate that and migrate users to unwrapfunction.

yuja added a comment.Aug 29 2017, 9:34 AM

This is off-topic, but if wrapfunction() is a context manager, I think
unwrapfunction() could also be a context manager.

mercurial/extensions.py
424

My point is why do we care only for classobj.method = oldfunc, which seems less likely than oldfunc().

In D472#8969, @yuja wrote:

This is off-topic, but if wrapfunction() is a context manager, I think
unwrapfunction() could also be a context manager.

I agree. I think it makes sense to have wrapfunction() and unwrapfunction() and a context manager that uses both.

I also think using the return value of wrapfunction() is risky, so we should try to reduce uses of it, but let's keep that discussion out of this review.

I failed with the update of this patch, so see D563 instead

quark added a comment.Aug 29 2017, 2:18 PM

I failed with the update of this patch, so see D563 instead

I guess it's because of the metaedit crash. This can be updated if you have Differential Revision: https://phab.mercurial-scm.org/D472 at the end of commit message.

martinvonz edited the summary of this revision. (Show Details)Aug 29 2017, 2:20 PM
martinvonz retitled this revision from extensions: make wrapfunction() return a context manager to extensions: add wrappedfunction() context manager.
martinvonz updated this revision to Diff 1409.
martinvonz updated this revision to Diff 1413.Aug 29 2017, 8:01 PM
In D472#9070, @quark wrote:

I failed with the update of this patch, so see D563 instead

I guess it's because of the metaedit crash. This can be updated if you have Differential Revision: https://phab.mercurial-scm.org/D472 at the end of commit message.

Thanks, I did that and it worked.

mercurial/extensions.py
415

I fixed this to pass the wrapper to unwrapfunction() so it doesn't unwrap unrelated functions. Also updated the test.

yuja accepted this revision.Aug 30 2017, 11:21 AM

Fixed weird indent and queued, thanks.

This revision is now accepted and ready to land.Aug 30 2017, 11:21 AM
This revision was automatically updated to reflect the committed changes.