From b66e4e9acf6017b3af20fc56666461f01fd4e003 Mon Sep 17 00:00:00 2001 From: jason kirtland Date: Wed, 3 Jul 2013 11:01:53 +0200 Subject: blinker.signal() and blinker.Namespace no longer use weak references. In the original implementation, I was uneasy about releasing a library that held an unbounded, module-level cache, so it was built using a weak value mapping. In practice, users making tons of signals are using Signal() directly, and the weak referencing violates the principle of least surprise in code like `signal('foo').connect(...)`. Previous code is available as WeakNamespace for the time being. The implementation is trivial and will likely be dropped in the future unless use cases are voiced. --- CHANGES | 8 +++++++- blinker/__init__.py | 2 ++ blinker/base.py | 23 ++++++++++++++++++++++- tests/test_signals.py | 19 +++++++++++++++++-- 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index fce8ca6..c7e6979 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,13 @@ Blinker Changelog Version 1.3 ----------- -Unreleased +- The global signal stash behind blinker.signal() is now backed by a + regular name-to-Signal dictionary. Previously, weak references were + held in the mapping and ephermal usage in code like + ``signal('foo').connect(...)`` could have surprising program behavior + depending on import order of modules. +- blinker.Namespace is now built on a regular dict. Use + blinker.WeakNamespace for the older, weak-referencing behavior. Version 1.2 diff --git a/blinker/__init__.py b/blinker/__init__.py index 2016746..a123360 100644 --- a/blinker/__init__.py +++ b/blinker/__init__.py @@ -3,6 +3,7 @@ from blinker.base import ( NamedSignal, Namespace, Signal, + WeakNamespace, receiver_connected, signal, ) @@ -12,6 +13,7 @@ __all__ = [ 'NamedSignal', 'Namespace', 'Signal', + 'WeakNamespace', 'receiver_connected', 'signal', ] diff --git a/blinker/base.py b/blinker/base.py index 615ef77..d8fe9f7 100644 --- a/blinker/base.py +++ b/blinker/base.py @@ -387,7 +387,7 @@ class NamedSignal(Signal): return "%s; %r>" % (base[:-1], self.name) -class Namespace(WeakValueDictionary): +class Namespace(dict): """A mapping of signal names to signals.""" def signal(self, name, doc=None): @@ -402,4 +402,25 @@ class Namespace(WeakValueDictionary): return self.setdefault(name, NamedSignal(name, doc)) +class WeakNamespace(WeakValueDictionary): + """A weak mapping of signal names to signals. + + Automatically cleans up unused Signals when the last reference goes out + of scope. This namespace implementation exists for a measure of legacy + compatibility with Blinker <= 1.2, and may be dropped in the future. + + """ + + def signal(self, name, doc=None): + """Return the :class:`NamedSignal` *name*, creating it if required. + + Repeated calls to this function will return the same signal object. + + """ + try: + return self[name] + except KeyError: + return self.setdefault(name, NamedSignal(name, doc)) + + signal = Namespace().signal diff --git a/tests/test_signals.py b/tests/test_signals.py index 094ee94..e6142fb 100644 --- a/tests/test_signals.py +++ b/tests/test_signals.py @@ -199,8 +199,8 @@ def test_meta_connect_failure(): blinker.receiver_connected._clear_state() -def test_singletons(): - ns = blinker.Namespace() +def test_weak_namespace(): + ns = blinker.WeakNamespace() assert not ns s1 = ns.signal('abc') assert s1 is ns.signal('abc') @@ -216,6 +216,21 @@ def test_singletons(): assert 'abc' not in ns +def test_namespace(): + ns = blinker.Namespace() + assert not ns + s1 = ns.signal('abc') + assert s1 is ns.signal('abc') + assert s1 is not ns.signal('def') + assert 'abc' in ns + + del s1 + collect_acyclic_refs() + + assert 'def' in ns + assert 'abc' in ns + + def test_weak_receiver(): sentinel = [] def received(sender, **kw): -- cgit v1.2.1