summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjason kirtland <jek@discorporate.us>2013-07-03 11:01:53 +0200
committerjason kirtland <jek@discorporate.us>2013-07-03 11:01:53 +0200
commitb66e4e9acf6017b3af20fc56666461f01fd4e003 (patch)
treeb2cf14bb58d98f806097e3b48c5feaf0bfe9798d
parent60fc2947f0b208299c8dd7125431ba70d3b57e04 (diff)
downloadblinker-b66e4e9acf6017b3af20fc56666461f01fd4e003.tar.gz
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.
-rw-r--r--CHANGES8
-rw-r--r--blinker/__init__.py2
-rw-r--r--blinker/base.py23
-rw-r--r--tests/test_signals.py19
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):