optmanager: cope with bound methods in .subscribe

Fixes #2122
This commit is contained in:
Aldo Cortesi 2017-03-18 12:11:48 +13:00
parent 3f50d5fdbb
commit 4e24c95a61
3 changed files with 30 additions and 6 deletions

View File

@ -1,9 +1,9 @@
import contextlib import contextlib
import blinker import blinker
import blinker._saferef
import pprint import pprint
import copy import copy
import functools import functools
import weakref
import os import os
import typing import typing
import textwrap import textwrap
@ -127,15 +127,24 @@ class OptManager:
Subscribe a callable to the .changed signal, but only for a Subscribe a callable to the .changed signal, but only for a
specified list of options. The callable should accept arguments specified list of options. The callable should accept arguments
(options, updated), and may raise an OptionsError. (options, updated), and may raise an OptionsError.
The event will automatically be unsubscribed if the callable goes out of scope.
""" """
func = weakref.proxy(func) for i in opts:
if i not in self._options:
raise exceptions.OptionsError("No such option: %s" % i)
# We reuse blinker's safe reference functionality to cope with weakrefs
# to bound methods.
func = blinker._saferef.safe_ref(func)
@functools.wraps(func) @functools.wraps(func)
def _call(options, updated): def _call(options, updated):
if updated.intersection(set(opts)): if updated.intersection(set(opts)):
try: f = func()
func(options, updated) if f:
except ReferenceError: f(options, updated)
else:
self.changed.disconnect(_call) self.changed.disconnect(_call)
# Our wrapper function goes out of scope immediately, so we have to set # Our wrapper function goes out of scope immediately, so we have to set

View File

@ -276,7 +276,7 @@ class ConsoleMaster(master.Master):
self.set_palette(self.options, None) self.set_palette(self.options, None)
self.options.subscribe( self.options.subscribe(
self.set_palette, self.set_palette,
["palette", "palette_transparent"] ["console_palette", "console_palette_transparent"]
) )
self.loop = urwid.MainLoop( self.loop = urwid.MainLoop(
urwid.SolidFill("x"), urwid.SolidFill("x"),

View File

@ -151,6 +151,21 @@ def test_subscribe():
o.two = 4 o.two = 4
assert len(o.changed.receivers) == 0 assert len(o.changed.receivers) == 0
class binder:
def __init__(self):
self.o = TO()
self.called = False
self.o.subscribe(self.bound, ["two"])
def bound(self, *args, **kwargs):
self.called = True
t = binder()
t.o.one = 3
assert not t.called
t.o.two = 3
assert t.called
def test_rollback(): def test_rollback():
o = TO() o = TO()