[issue19172] selectors: add keys() method

Charles-François Natali report at bugs.python.org
Fri Oct 25 19:55:04 CEST 2013


Charles-François Natali added the comment:

> Antoine Pitrou added the comment:
>
> FWIW, I think the "ideal" solution would be for keys() (*) to return a
> read-only Mapping implementation, allowing for file object lookup (using
> __getitem__) as well as iteration on SelectorKeys (using __iter__) and
> fast emptiness checking (using __len__).

Thanks, I think that's a great idea.
I'm attaching a patch updating yours with the following:
- asyncio/test_asyncio update
- selectors' documentation update

IMO, it really offers both a compact, easy to use and performant API.

(Note: the mapping doesn't really have to be recreated upon each
get_map() call and could be kept as a private member, but IMO that's
not a performance issue).

----------
Added file: http://bugs.python.org/file32360/selectors_map-1.patch

_______________________________________
Python tracker <report at bugs.python.org>
<http://bugs.python.org/issue19172>
_______________________________________
-------------- next part --------------
diff -r eb1edc9e3722 Doc/library/selectors.rst
--- a/Doc/library/selectors.rst	Fri Oct 25 17:56:00 2013 +0200
+++ b/Doc/library/selectors.rst	Fri Oct 25 19:47:35 2013 +0200
@@ -157,12 +157,13 @@
       This must be called to make sure that any underlying resource is freed.
       The selector shall not be used once it has been closed.
 
-   .. method:: get_key(fileobj)
+   .. method:: get_map()
 
-      Return the key associated to a registered file object.
+      Return a mapping of file objects to selector keys.
 
-      This returns the :class:`SelectorKey` instance associated to this file
-      object, or raises :exc:`KeyError` if the file object is not registered.
+      This returns a :class:`~collections.abc.Mapping` instance mapping
+      registered file objects to their associated :class:`SelectorKey`
+      instance.
 
 
 .. class:: DefaultSelector()
diff -r eb1edc9e3722 Lib/asyncio/selector_events.py
--- a/Lib/asyncio/selector_events.py	Fri Oct 25 17:56:00 2013 +0200
+++ b/Lib/asyncio/selector_events.py	Fri Oct 25 19:47:35 2013 +0200
@@ -122,7 +122,7 @@
         """Add a reader callback."""
         handle = events.make_handle(callback, args)
         try:
-            key = self._selector.get_key(fd)
+            key = self._selector.get_map()[fd]
         except KeyError:
             self._selector.register(fd, selectors.EVENT_READ,
                                     (handle, None))
@@ -136,7 +136,7 @@
     def remove_reader(self, fd):
         """Remove a reader callback."""
         try:
-            key = self._selector.get_key(fd)
+            key = self._selector.get_map()[fd]
         except KeyError:
             return False
         else:
@@ -157,7 +157,7 @@
         """Add a writer callback.."""
         handle = events.make_handle(callback, args)
         try:
-            key = self._selector.get_key(fd)
+            key = self._selector.get_map()[fd]
         except KeyError:
             self._selector.register(fd, selectors.EVENT_WRITE,
                                     (None, handle))
@@ -171,7 +171,7 @@
     def remove_writer(self, fd):
         """Remove a writer callback."""
         try:
-            key = self._selector.get_key(fd)
+            key = self._selector.get_map()[fd]
         except KeyError:
             return False
         else:
diff -r eb1edc9e3722 Lib/selectors.py
--- a/Lib/selectors.py	Fri Oct 25 17:56:00 2013 +0200
+++ b/Lib/selectors.py	Fri Oct 25 19:47:35 2013 +0200
@@ -6,7 +6,7 @@
 
 
 from abc import ABCMeta, abstractmethod
-from collections import namedtuple
+from collections import namedtuple, Mapping
 import functools
 import select
 import sys
@@ -44,6 +44,25 @@
 selected event mask and attached data."""
 
 
+class _SelectorMapping(Mapping):
+    """Mapping of file objects to selector keys."""
+
+    def __init__(self, selector):
+        self._fd_to_key = selector._fd_to_key
+
+    def __len__(self):
+        return len(self._fd_to_key)
+
+    def __getitem__(self, fileobj):
+        try:
+            return self._fd_to_key[_fileobj_to_fd(fileobj)]
+        except KeyError:
+            raise KeyError("{!r} is not registered".format(fileobj)) from None
+
+    def __iter__(self):
+        return iter(self._fd_to_key)
+
+
 class BaseSelector(metaclass=ABCMeta):
     """Base selector class.
 
@@ -151,16 +170,9 @@
         """
         self._fd_to_key.clear()
 
-    def get_key(self, fileobj):
-        """Return the key associated to a registered file object.
-
-        Returns:
-        SelectorKey for this file object
-        """
-        try:
-            return self._fd_to_key[_fileobj_to_fd(fileobj)]
-        except KeyError:
-            raise KeyError("{!r} is not registered".format(fileobj)) from None
+    def get_map(self):
+        """Return a mapping of file objects to selector keys."""
+        return _SelectorMapping(self)
 
     def __enter__(self):
         return self
diff -r eb1edc9e3722 Lib/test/test_asyncio/test_selector_events.py
--- a/Lib/test/test_asyncio/test_selector_events.py	Fri Oct 25 17:56:00 2013 +0200
+++ b/Lib/test/test_asyncio/test_selector_events.py	Fri Oct 25 19:47:35 2013 +0200
@@ -396,7 +396,7 @@
         self.assertIs(err, f.exception())
 
     def test_add_reader(self):
-        self.loop._selector.get_key.side_effect = KeyError
+        self.loop._selector.get_map.return_value = {}
         cb = lambda: True
         self.loop.add_reader(1, cb)
 
@@ -410,8 +410,8 @@
     def test_add_reader_existing(self):
         reader = unittest.mock.Mock()
         writer = unittest.mock.Mock()
-        self.loop._selector.get_key.return_value = selectors.SelectorKey(
-            1, 1, selectors.EVENT_WRITE, (reader, writer))
+        self.loop._selector.get_map.return_value = {1: selectors.SelectorKey(
+            1, 1, selectors.EVENT_WRITE, (reader, writer))}
         cb = lambda: True
         self.loop.add_reader(1, cb)
 
@@ -426,8 +426,8 @@
 
     def test_add_reader_existing_writer(self):
         writer = unittest.mock.Mock()
-        self.loop._selector.get_key.return_value = selectors.SelectorKey(
-            1, 1, selectors.EVENT_WRITE, (None, writer))
+        self.loop._selector.get_map.return_value = {1: selectors.SelectorKey(
+            1, 1, selectors.EVENT_WRITE, (None, writer))}
         cb = lambda: True
         self.loop.add_reader(1, cb)
 
@@ -440,8 +440,8 @@
         self.assertEqual(writer, w)
 
     def test_remove_reader(self):
-        self.loop._selector.get_key.return_value = selectors.SelectorKey(
-            1, 1, selectors.EVENT_READ, (None, None))
+        self.loop._selector.get_map.return_value = {1: selectors.SelectorKey(
+            1, 1, selectors.EVENT_READ, (None, None))}
         self.assertFalse(self.loop.remove_reader(1))
 
         self.assertTrue(self.loop._selector.unregister.called)
@@ -449,9 +449,9 @@
     def test_remove_reader_read_write(self):
         reader = unittest.mock.Mock()
         writer = unittest.mock.Mock()
-        self.loop._selector.get_key.return_value = selectors.SelectorKey(
+        self.loop._selector.get_map.return_value = {1: selectors.SelectorKey(
             1, 1, selectors.EVENT_READ | selectors.EVENT_WRITE,
-            (reader, writer))
+            (reader, writer))}
         self.assertTrue(
             self.loop.remove_reader(1))
 
@@ -461,12 +461,12 @@
             self.loop._selector.modify.call_args[0])
 
     def test_remove_reader_unknown(self):
-        self.loop._selector.get_key.side_effect = KeyError
+        self.loop._selector.get_map.return_value = {}
         self.assertFalse(
             self.loop.remove_reader(1))
 
     def test_add_writer(self):
-        self.loop._selector.get_key.side_effect = KeyError
+        self.loop._selector.get_map.return_value = {}
         cb = lambda: True
         self.loop.add_writer(1, cb)
 
@@ -480,8 +480,8 @@
     def test_add_writer_existing(self):
         reader = unittest.mock.Mock()
         writer = unittest.mock.Mock()
-        self.loop._selector.get_key.return_value = selectors.SelectorKey(
-            1, 1, selectors.EVENT_READ, (reader, writer))
+        self.loop._selector.get_map.return_value = {1: selectors.SelectorKey(
+            1, 1, selectors.EVENT_READ, (reader, writer))}
         cb = lambda: True
         self.loop.add_writer(1, cb)
 
@@ -495,8 +495,8 @@
         self.assertEqual(cb, w._callback)
 
     def test_remove_writer(self):
-        self.loop._selector.get_key.return_value = selectors.SelectorKey(
-            1, 1, selectors.EVENT_WRITE, (None, None))
+        self.loop._selector.get_map.return_value = {1: selectors.SelectorKey(
+            1, 1, selectors.EVENT_WRITE, (None, None))}
         self.assertFalse(self.loop.remove_writer(1))
 
         self.assertTrue(self.loop._selector.unregister.called)
@@ -504,9 +504,9 @@
     def test_remove_writer_read_write(self):
         reader = unittest.mock.Mock()
         writer = unittest.mock.Mock()
-        self.loop._selector.get_key.return_value = selectors.SelectorKey(
+        self.loop._selector.get_map.return_value = {1: selectors.SelectorKey(
             1, 1, selectors.EVENT_READ | selectors.EVENT_WRITE,
-            (reader, writer))
+            (reader, writer))}
         self.assertTrue(
             self.loop.remove_writer(1))
 
@@ -516,7 +516,7 @@
             self.loop._selector.modify.call_args[0])
 
     def test_remove_writer_unknown(self):
-        self.loop._selector.get_key.side_effect = KeyError
+        self.loop._selector.get_map.return_value = {}
         self.assertFalse(
             self.loop.remove_writer(1))
 
diff -r eb1edc9e3722 Lib/test/test_asyncio/test_selectors.py
--- a/Lib/test/test_asyncio/test_selectors.py	Fri Oct 25 17:56:00 2013 +0200
+++ b/Lib/test/test_asyncio/test_selectors.py	Fri Oct 25 19:47:35 2013 +0200
@@ -85,7 +85,7 @@
         self.assertNotEqual(key.events, key2.events)
         self.assertEqual(
             selectors.SelectorKey(fobj, 10, selectors.EVENT_WRITE, None),
-            s.get_key(fobj))
+            s.get_map()[fobj])
 
     def test_modify_data(self):
         fobj = unittest.mock.Mock()
@@ -101,7 +101,7 @@
         self.assertNotEqual(key.data, key2.data)
         self.assertEqual(
             selectors.SelectorKey(fobj, 10, selectors.EVENT_READ, d2),
-            s.get_key(fobj))
+            s.get_map()[fobj])
 
     def test_modify_same(self):
         fobj = unittest.mock.Mock()
diff -r eb1edc9e3722 Lib/test/test_selectors.py
--- a/Lib/test/test_selectors.py	Fri Oct 25 17:56:00 2013 +0200
+++ b/Lib/test/test_selectors.py	Fri Oct 25 19:47:35 2013 +0200
@@ -106,7 +106,7 @@
         # modify events
         key2 = s.modify(rd, selectors.EVENT_WRITE)
         self.assertNotEqual(key.events, key2.events)
-        self.assertEqual(key2, s.get_key(rd))
+        self.assertEqual(key2, s.get_map()[rd])
 
         s.unregister(rd)
 
@@ -118,7 +118,7 @@
         key2 = s.modify(rd, selectors.EVENT_READ, d2)
         self.assertEqual(key.events, key2.events)
         self.assertNotEqual(key.data, key2.data)
-        self.assertEqual(key2, s.get_key(rd))
+        self.assertEqual(key2, s.get_map()[rd])
         self.assertEqual(key2.data, d2)
 
         # modify unknown file obj
@@ -136,10 +136,12 @@
         s.register(wr, selectors.EVENT_WRITE)
 
         s.close()
-        self.assertRaises(KeyError, s.get_key, rd)
-        self.assertRaises(KeyError, s.get_key, wr)
+        with self.assertRaises(KeyError):
+            s.get_map()[rd]
+        with self.assertRaises(KeyError):
+            s.get_map()[wr]
 
-    def test_get_key(self):
+    def test_get_map(self):
         s = self.SELECTOR()
         self.addCleanup(s.close)
 
@@ -147,11 +149,24 @@
         self.addCleanup(rd.close)
         self.addCleanup(wr.close)
 
+        keys = s.get_map()
+        self.assertFalse(keys)
+        self.assertEqual(len(keys), 0)
+        self.assertEqual(list(keys), [])
         key = s.register(rd, selectors.EVENT_READ, "data")
-        self.assertEqual(key, s.get_key(rd))
+        self.assertIn(rd, keys)
+        self.assertEqual(key, keys[rd])
+        self.assertEqual(len(keys), 1)
+        self.assertEqual(list(keys), [rd.fileno()])
+        self.assertEqual(list(keys.values()), [key])
 
         # unknown file obj
-        self.assertRaises(KeyError, s.get_key, 999999)
+        with self.assertRaises(KeyError):
+            keys[999999]
+
+        # Read-only mapping
+        with self.assertRaises(TypeError):
+            del keys[rd]
 
     def test_select(self):
         s = self.SELECTOR()
@@ -185,8 +200,8 @@
             sel.register(rd, selectors.EVENT_READ)
             sel.register(wr, selectors.EVENT_WRITE)
 
-        self.assertRaises(KeyError, s.get_key, rd)
-        self.assertRaises(KeyError, s.get_key, wr)
+        self.assertNotIn(rd, s.get_map())
+        self.assertNotIn(wr, s.get_map())
 
     def test_fileno(self):
         s = self.SELECTOR()


More information about the Python-bugs-list mailing list