Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pymongo for openSUSE:Factory 
checked in at 2021-06-01 10:33:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pymongo (Old)
 and      /work/SRC/openSUSE:Factory/.python-pymongo.new.1898 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pymongo"

Tue Jun  1 10:33:28 2021 rev:39 rq:895918 version:3.11.4

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pymongo/python-pymongo.changes    
2021-04-26 16:38:58.294013292 +0200
+++ /work/SRC/openSUSE:Factory/.python-pymongo.new.1898/python-pymongo.changes  
2021-06-01 10:33:43.348431073 +0200
@@ -1,0 +2,7 @@
+Fri May 28 07:38:51 UTC 2021 - pgaj...@suse.com
+
+- version update to 3.11.4
+  - Version 3.11.4 fixes a bug where a MongoClient would mistakenly attempt to
+    create minPoolSize connections to arbiter nodes (`PYTHON-2634`_).
+
+-------------------------------------------------------------------

Old:
----
  pymongo-3.11.3.tar.gz

New:
----
  pymongo-3.11.4.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pymongo.spec ++++++
--- /var/tmp/diff_new_pack.gGVwpa/_old  2021-06-01 10:33:43.944432088 +0200
+++ /var/tmp/diff_new_pack.gGVwpa/_new  2021-06-01 10:33:43.948432095 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-pymongo
-Version:        3.11.3
+Version:        3.11.4
 Release:        0
 Summary:        Python driver for MongoDB
 License:        Apache-2.0
@@ -56,7 +56,7 @@
 %python_expand %fdupes %{buildroot}%{$python_sitearch}
 
 %check
-%python_exec setup.py test -v
+%pyunittest discover -v
 
 %files %{python_files}
 %license LICENSE

++++++ pymongo-3.11.3.tar.gz -> pymongo-3.11.4.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/PKG-INFO new/pymongo-3.11.4/PKG-INFO
--- old/pymongo-3.11.3/PKG-INFO 2021-02-03 03:56:53.000000000 +0100
+++ new/pymongo-3.11.4/PKG-INFO 2021-05-05 00:15:41.111389600 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pymongo
-Version: 3.11.3
+Version: 3.11.4
 Summary: Python driver for MongoDB <http://www.mongodb.org>
 Home-page: http://github.com/mongodb/mongo-python-driver
 Author: Mike Dirolf
@@ -258,11 +258,11 @@
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Database
 Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
-Provides-Extra: tls
 Provides-Extra: encryption
-Provides-Extra: aws
-Provides-Extra: gssapi
+Provides-Extra: ocsp
 Provides-Extra: snappy
-Provides-Extra: srv
+Provides-Extra: tls
 Provides-Extra: zstd
-Provides-Extra: ocsp
+Provides-Extra: aws
+Provides-Extra: srv
+Provides-Extra: gssapi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/bson/_cbsonmodule.c 
new/pymongo-3.11.4/bson/_cbsonmodule.c
--- old/pymongo-3.11.3/bson/_cbsonmodule.c      2021-02-03 01:53:34.000000000 
+0100
+++ new/pymongo-3.11.4/bson/_cbsonmodule.c      2021-05-05 00:15:21.000000000 
+0200
@@ -2621,7 +2621,7 @@
     if (name_length > BSON_MAX_SIZE || position + name_length >= max) {
         PyObject* InvalidBSON = _error("InvalidBSON");
         if (InvalidBSON) {
-            PyErr_SetNone(InvalidBSON);
+            PyErr_SetString(InvalidBSON, "field name too large");
             Py_DECREF(InvalidBSON);
         }
         return -1;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/doc/changelog.rst 
new/pymongo-3.11.4/doc/changelog.rst
--- old/pymongo-3.11.3/doc/changelog.rst        2021-02-03 02:55:08.000000000 
+0100
+++ new/pymongo-3.11.4/doc/changelog.rst        2021-05-05 00:15:21.000000000 
+0200
@@ -1,6 +1,21 @@
 Changelog
 =========
 
+Changes in Version 3.11.4
+-------------------------
+
+Issues Resolved
+...............
+
+Version 3.11.4 fixes a bug where a MongoClient would mistakenly attempt to
+create minPoolSize connections to arbiter nodes (`PYTHON-2634`_).
+
+See the `PyMongo 3.11.4 release notes in JIRA`_ for the list of resolved issues
+in this release.
+
+.. _PYTHON-2634: https://jira.mongodb.org/browse/PYTHON-2452
+.. _PyMongo 3.11.4 release notes in JIRA: 
https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=30902
+
 Changes in Version 3.11.3
 -------------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/doc/conf.py 
new/pymongo-3.11.4/doc/conf.py
--- old/pymongo-3.11.3/doc/conf.py      2021-02-03 01:53:34.000000000 +0100
+++ new/pymongo-3.11.4/doc/conf.py      2021-05-05 00:15:21.000000000 +0200
@@ -91,13 +91,6 @@
 # Additional static files.
 html_static_path = ['static']
 
-# These paths are either relative to html_static_path
-# or fully qualified paths (eg. https://...)
-# Note: html_js_files was added in Sphinx 1.8.
-html_js_files = [
-    'delighted.js',
-]
-
 # The name for this set of Sphinx documents.  If None, it defaults to
 # "<project> v<release> documentation".
 #html_title = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/doc/static/delighted.js 
new/pymongo-3.11.4/doc/static/delighted.js
--- old/pymongo-3.11.3/doc/static/delighted.js  2021-01-16 00:06:11.000000000 
+0100
+++ new/pymongo-3.11.4/doc/static/delighted.js  1970-01-01 01:00:00.000000000 
+0100
@@ -1,22 +0,0 @@
-/* eslint-disable */
-// Delighted
-!function(e,t,r,n,a){if(!e[a]){for(var i=e[a]=[],s=0;s<r.length;s++){var 
c=r[s];i[c]=i[c]||function(e){return function(){var 
t=Array.prototype.slice.call(arguments);i.push([e,t])}}(c)}i.SNIPPET_VERSION="1.0.1";var
 
o=t.createElement("script");o.type="text/javascript",o.async=!0,o.src="https://d2yyd1h5u9mauk.cloudfront.net/integrations/web/v1/library/"+n+"/"+a+".js";var
 
l=t.getElementsByTagName("script")[0];l.parentNode.insertBefore(o,l)}}(window,document,["survey","reset","config","init","set","get","event","identify","track","page","screen","group","alias"],"Dk30CC86ba0nATlK","delighted");
-// Segment
-!function(){var 
analytics=window.analytics=window.analytics||[];if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Segment
 snippet included 
twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on"];analytics.factory=function(t){return
 function(){var 
e=Array.prototype.slice.call(arguments);e.unshift(t);analytics.push(e);return 
analytics}};for(var t=0;t<analytics.methods.length;t++){var 
e=analytics.methods[t];analytics[e]=analytics.factory(e)}analytics.load=function(t,e){var
 
n=document.createElement("script");n.type="text/javascript";n.async=!0;n.src="https://cdn.segment.com/analytics.js/v1/"+t+"/analytics.min.js";var
 
a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(n,a);analytics._loadOptions=e};analytics.SNIPPET_VERSION="4.1.0";
-        analytics.load("aGhVvyxnPWlyP71vVl9ZjGWxAtoVGLXX");
-    }}();
-
-delighted.survey({
-    minTimeOnPage: 180,
-    sampleFactor: 0.1,
-    properties: {
-        project: 'pymongo'
-    }
-});
-
-// Update Segment
-analytics.page({
-    path: location.pathname,
-    url: location.href,
-    project: 'pymongo'
-});
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/pymongo/__init__.py 
new/pymongo-3.11.4/pymongo/__init__.py
--- old/pymongo-3.11.3/pymongo/__init__.py      2021-02-03 02:55:08.000000000 
+0100
+++ new/pymongo-3.11.4/pymongo/__init__.py      2021-05-05 00:15:21.000000000 
+0200
@@ -74,7 +74,7 @@
 ALL = 2
 """Profile all operations."""
 
-version_tuple = (3, 11, 3)
+version_tuple = (3, 11, 4)
 
 def get_version_string():
     if isinstance(version_tuple[-1], str):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/pymongo/topology.py 
new/pymongo-3.11.4/pymongo/topology.py
--- old/pymongo-3.11.3/pymongo/topology.py      2021-02-03 02:55:08.000000000 
+0100
+++ new/pymongo-3.11.4/pymongo/topology.py      2021-05-05 00:15:21.000000000 
+0200
@@ -430,15 +430,26 @@
                     ServerDescription(address, error=error), True)
                 server.request_check()
 
+    def data_bearing_servers(self):
+        """Return a list of all data-bearing servers.
+
+        This includes any server that might be selected for an operation.
+        """
+        if self._description.topology_type == TOPOLOGY_TYPE.Single:
+            return self._description.known_servers
+        return self._description.readable_servers
+
     def update_pool(self, all_credentials):
         # Remove any stale sockets and add new sockets if pool is too small.
         servers = []
         with self._lock:
-            for server in self._servers.values():
-                servers.append((server, server._pool.generation))
+            # Only update pools for data-bearing servers.
+            for sd in self.data_bearing_servers():
+                server = self._servers[sd.address]
+                servers.append((server, server.pool.generation))
 
         for server, generation in servers:
-            server._pool.remove_stale_sockets(generation, all_credentials)
+            server.pool.remove_stale_sockets(generation, all_credentials)
 
     def close(self):
         """Clear pools and terminate monitors. Topology reopens on demand."""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/pymongo.egg-info/PKG-INFO 
new/pymongo-3.11.4/pymongo.egg-info/PKG-INFO
--- old/pymongo-3.11.3/pymongo.egg-info/PKG-INFO        2021-02-03 
03:56:52.000000000 +0100
+++ new/pymongo-3.11.4/pymongo.egg-info/PKG-INFO        2021-05-05 
00:15:40.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: pymongo
-Version: 3.11.3
+Version: 3.11.4
 Summary: Python driver for MongoDB <http://www.mongodb.org>
 Home-page: http://github.com/mongodb/mongo-python-driver
 Author: Mike Dirolf
@@ -258,11 +258,11 @@
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Topic :: Database
 Requires-Python: >=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*
-Provides-Extra: tls
 Provides-Extra: encryption
-Provides-Extra: aws
-Provides-Extra: gssapi
+Provides-Extra: ocsp
 Provides-Extra: snappy
-Provides-Extra: srv
+Provides-Extra: tls
 Provides-Extra: zstd
-Provides-Extra: ocsp
+Provides-Extra: aws
+Provides-Extra: srv
+Provides-Extra: gssapi
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/pymongo.egg-info/SOURCES.txt 
new/pymongo-3.11.4/pymongo.egg-info/SOURCES.txt
--- old/pymongo-3.11.3/pymongo.egg-info/SOURCES.txt     2021-02-03 
03:56:52.000000000 +0100
+++ new/pymongo-3.11.4/pymongo.egg-info/SOURCES.txt     2021-05-05 
00:15:40.000000000 +0200
@@ -121,7 +121,6 @@
 doc/examples/uuid.rst
 doc/pydoctheme/theme.conf
 doc/pydoctheme/static/pydoctheme.css
-doc/static/delighted.js
 doc/static/periodic-executor-refs.png
 doc/static/sidebar.js
 gridfs/__init__.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/pymongo.egg-info/requires.txt 
new/pymongo-3.11.4/pymongo.egg-info/requires.txt
--- old/pymongo-3.11.3/pymongo.egg-info/requires.txt    2021-02-03 
03:56:52.000000000 +0100
+++ new/pymongo-3.11.4/pymongo.egg-info/requires.txt    2021-05-05 
00:15:40.000000000 +0200
@@ -17,10 +17,9 @@
 python-snappy
 
 [srv]
-dnspython<1.17.0,>=1.16.0
+dnspython<2.0.0,>=1.16.0
 
 [tls]
-ipaddress
 
 [zstd]
 zstandard
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/setup.py new/pymongo-3.11.4/setup.py
--- old/pymongo-3.11.3/setup.py 2021-02-03 02:55:08.000000000 +0100
+++ new/pymongo-3.11.4/setup.py 2021-05-05 00:15:21.000000000 +0200
@@ -39,7 +39,7 @@
     except ImportError:
         _HAVE_SPHINX = False
 
-version = "3.11.3"
+version = "3.11.4"
 
 f = open("README.rst")
 try:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/test/pymongo_mocks.py 
new/pymongo-3.11.4/test/pymongo_mocks.py
--- old/pymongo-3.11.3/test/pymongo_mocks.py    2021-02-03 01:53:34.000000000 
+0100
+++ new/pymongo-3.11.4/test/pymongo_mocks.py    2021-05-05 00:15:21.000000000 
+0200
@@ -85,13 +85,13 @@
 class MockClient(MongoClient):
     def __init__(
             self, standalones, members, mongoses, ismaster_hosts=None,
-            *args, **kwargs):
+            arbiters=None, down_hosts=None, *args, **kwargs):
         """A MongoClient connected to the default server, with a mock topology.
 
-        standalones, members, mongoses determine the configuration of the
-        topology. They are formatted like ['a:1', 'b:2']. ismaster_hosts
-        provides an alternative host list for the server's mocked ismaster
-        response; see test_connect_with_internal_ips.
+        standalones, members, mongoses, arbiters, and down_hosts determine the
+        configuration of the topology. They are formatted like ['a:1', 'b:2'].
+        ismaster_hosts provides an alternative host list for the server's
+        mocked ismaster response; see test_connect_with_internal_ips.
         """
         self.mock_standalones = standalones[:]
         self.mock_members = members[:]
@@ -101,6 +101,9 @@
         else:
             self.mock_primary = None
 
+        # Hosts that should be considered an arbiter.
+        self.mock_arbiters = arbiters[:] if arbiters else []
+
         if ismaster_hosts is not None:
             self.mock_ismaster_hosts = ismaster_hosts
         else:
@@ -109,7 +112,7 @@
         self.mock_mongoses = mongoses[:]
 
         # Hosts that should raise socket errors.
-        self.mock_down_hosts = []
+        self.mock_down_hosts = down_hosts[:] if down_hosts else []
 
         # Hostname -> (min wire version, max wire version)
         self.mock_wire_versions = {}
@@ -182,6 +185,10 @@
 
             if self.mock_primary:
                 response['primary'] = self.mock_primary
+
+            if host in self.mock_arbiters:
+                response['arbiterOnly'] = True
+                response['secondary'] = False
         elif host in self.mock_mongoses:
             response = {
                 'ok': 1,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/test/test_bson.py 
new/pymongo-3.11.4/test/test_bson.py
--- old/pymongo-3.11.3/test/test_bson.py        2021-02-03 01:53:34.000000000 
+0100
+++ new/pymongo-3.11.4/test/test_bson.py        2021-05-05 00:15:21.000000000 
+0200
@@ -373,6 +373,13 @@
                 with self.assertRaises(InvalidBSON, msg=msg):
                     list(decode_file_iter(scratch))
 
+    def test_invalid_field_name(self):
+        # Decode a truncated field
+        with self.assertRaises(InvalidBSON) as ctx:
+            decode(b'\x0b\x00\x00\x00\x02field\x00')
+        # Assert that the InvalidBSON error message is not empty.
+        self.assertTrue(str(ctx.exception))
+
     def test_data_timestamp(self):
         self.assertEqual({"test": Timestamp(4, 20)},
                          decode(b"\x13\x00\x00\x00\x11\x74\x65\x73\x74\x00\x14"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/test/test_client.py 
new/pymongo-3.11.4/test/test_client.py
--- old/pymongo-3.11.3/test/test_client.py      2021-02-03 02:55:08.000000000 
+0100
+++ new/pymongo-3.11.4/test/test_client.py      2021-05-05 00:15:21.000000000 
+0200
@@ -35,7 +35,7 @@
 from bson.son import SON
 from bson.tz_util import utc
 import pymongo
-from pymongo import auth, message
+from pymongo import auth, message, monitoring
 from pymongo.common import CONNECT_TIMEOUT, _UUID_REPRESENTATIONS
 from pymongo.command_cursor import CommandCursor
 from pymongo.compression_support import _HAVE_SNAPPY, _HAVE_ZSTD
@@ -58,7 +58,7 @@
 from pymongo.pool import SocketInfo, _METADATA
 from pymongo.read_preferences import ReadPreference
 from pymongo.server_description import ServerDescription
-from pymongo.server_selectors import (any_server_selector,
+from pymongo.server_selectors import (readable_server_selector,
                                       writable_server_selector)
 from pymongo.server_type import SERVER_TYPE
 from pymongo.settings import TOPOLOGY_TYPE
@@ -76,6 +76,7 @@
 from test.pymongo_mocks import MockClient
 from test.utils import (assertRaisesExactly,
                         connected,
+                        CMAPListener,
                         delay,
                         FunctionCallRecorder,
                         get_pool,
@@ -452,21 +453,25 @@
 
 class TestClient(IntegrationTest):
 
-    def test_max_idle_time_reaper(self):
+    def test_max_idle_time_reaper_default(self):
         with client_knobs(kill_cursor_frequency=0.1):
             # Assert reaper doesn't remove sockets when maxIdleTimeMS not set
             client = rs_or_single_client()
-            server = client._get_topology().select_server(any_server_selector)
+            server = client._get_topology().select_server(
+                readable_server_selector)
             with server._pool.get_socket({}) as sock_info:
                 pass
             self.assertEqual(1, len(server._pool.sockets))
             self.assertTrue(sock_info in server._pool.sockets)
             client.close()
 
+    def test_max_idle_time_reaper_removes_stale_minPoolSize(self):
+        with client_knobs(kill_cursor_frequency=0.1):
             # Assert reaper removes idle socket and replaces it with a new one
             client = rs_or_single_client(maxIdleTimeMS=500,
                                          minPoolSize=1)
-            server = client._get_topology().select_server(any_server_selector)
+            server = client._get_topology().select_server(
+                readable_server_selector)
             with server._pool.get_socket({}) as sock_info:
                 pass
             # When the reaper runs at the same time as the get_socket, two
@@ -478,11 +483,14 @@
                        "replace stale socket")
             client.close()
 
+    def test_max_idle_time_reaper_does_not_exceed_maxPoolSize(self):
+        with client_knobs(kill_cursor_frequency=0.1):
             # Assert reaper respects maxPoolSize when adding new sockets.
             client = rs_or_single_client(maxIdleTimeMS=500,
                                          minPoolSize=1,
                                          maxPoolSize=1)
-            server = client._get_topology().select_server(any_server_selector)
+            server = client._get_topology().select_server(
+                readable_server_selector)
             with server._pool.get_socket({}) as sock_info:
                 pass
             # When the reaper runs at the same time as the get_socket,
@@ -494,9 +502,12 @@
                        "replace stale socket")
             client.close()
 
+    def test_max_idle_time_reaper_removes_stale(self):
+        with client_knobs(kill_cursor_frequency=0.1):
             # Assert reaper has removed idle socket and NOT replaced it
             client = rs_or_single_client(maxIdleTimeMS=500)
-            server = client._get_topology().select_server(any_server_selector)
+            server = client._get_topology().select_server(
+                readable_server_selector)
             with server._pool.get_socket({}) as sock_info_one:
                 pass
             # Assert that the pool does not close sockets prematurely.
@@ -512,12 +523,14 @@
     def test_min_pool_size(self):
         with client_knobs(kill_cursor_frequency=.1):
             client = rs_or_single_client()
-            server = client._get_topology().select_server(any_server_selector)
+            server = client._get_topology().select_server(
+                readable_server_selector)
             self.assertEqual(0, len(server._pool.sockets))
 
             # Assert that pool started up at minPoolSize
             client = rs_or_single_client(minPoolSize=10)
-            server = client._get_topology().select_server(any_server_selector)
+            server = client._get_topology().select_server(
+                readable_server_selector)
             wait_until(lambda: 10 == len(server._pool.sockets),
                        "pool initialized with 10 sockets")
 
@@ -532,7 +545,8 @@
         # Use high frequency to test _get_socket_no_auth.
         with client_knobs(kill_cursor_frequency=99999999):
             client = rs_or_single_client(maxIdleTimeMS=500)
-            server = client._get_topology().select_server(any_server_selector)
+            server = client._get_topology().select_server(
+                readable_server_selector)
             with server._pool.get_socket({}) as sock_info:
                 pass
             self.assertEqual(1, len(server._pool.sockets))
@@ -546,7 +560,8 @@
 
             # Test that sockets are reused if maxIdleTimeMS is not set.
             client = rs_or_single_client()
-            server = client._get_topology().select_server(any_server_selector)
+            server = client._get_topology().select_server(
+                readable_server_selector)
             with server._pool.get_socket({}) as sock_info:
                 pass
             self.assertEqual(1, len(server._pool.sockets))
@@ -2008,5 +2023,62 @@
         self.assertIsNone(ct.get())
 
 
+class TestClientPool(MockClientTest):
+
+    @client_context.require_connection
+    def test_rs_client_does_not_maintain_pool_to_arbiters(self):
+        listener = CMAPListener()
+        c = MockClient(
+            standalones=[],
+            members=['a:1', 'b:2', 'c:3', 'd:4'],
+            mongoses=[],
+            arbiters=['c:3'],  # c:3 is an arbiter.
+            down_hosts=['d:4'],  # d:4 is unreachable.
+            host=['a:1', 'b:2', 'c:3', 'd:4'],
+            replicaSet='rs',
+            minPoolSize=1,  # minPoolSize
+            event_listeners=[listener],
+        )
+        self.addCleanup(c.close)
+
+        wait_until(lambda: len(c.nodes) == 3, 'connect')
+        self.assertEqual(c.address, ('a', 1))
+        self.assertEqual(c.arbiters, set([('c', 3)]))
+        # Assert that we create 2 and only 2 pooled connections.
+        listener.wait_for_event(monitoring.ConnectionReadyEvent, 2)
+        self.assertEqual(
+            listener.event_count(monitoring.ConnectionCreatedEvent), 2)
+        # Assert that we do not create connections to arbiters.
+        arbiter = c._topology.get_server_by_address(('c', 3))
+        self.assertFalse(arbiter.pool.sockets)
+        # Assert that we do not create connections to unknown servers.
+        arbiter = c._topology.get_server_by_address(('d', 4))
+        self.assertFalse(arbiter.pool.sockets)
+
+    @client_context.require_connection
+    def test_direct_client_maintains_pool_to_arbiter(self):
+        listener = CMAPListener()
+        c = MockClient(
+            standalones=[],
+            members=['a:1', 'b:2', 'c:3'],
+            mongoses=[],
+            arbiters=['c:3'],  # c:3 is an arbiter.
+            host='c:3',
+            directConnection=True,
+            minPoolSize=1,  # minPoolSize
+            event_listeners=[listener],
+        )
+        self.addCleanup(c.close)
+
+        wait_until(lambda: len(c.nodes) == 1, 'connect')
+        self.assertEqual(c.address, ('c', 3))
+        # Assert that we create 1 pooled connection.
+        listener.wait_for_event(monitoring.ConnectionReadyEvent, 1)
+        self.assertEqual(
+            listener.event_count(monitoring.ConnectionCreatedEvent), 1)
+        arbiter = c._topology.get_server_by_address(('c', 3))
+        self.assertEqual(len(arbiter.pool.sockets), 1)
+
+
 if __name__ == "__main__":
     unittest.main()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/test/test_heartbeat_monitoring.py 
new/pymongo-3.11.4/test/test_heartbeat_monitoring.py
--- old/pymongo-3.11.3/test/test_heartbeat_monitoring.py        2021-02-03 
01:53:34.000000000 +0100
+++ new/pymongo-3.11.4/test/test_heartbeat_monitoring.py        2021-05-05 
00:15:14.000000000 +0200
@@ -51,12 +51,12 @@
             # monitor thread may run multiple times during the execution
             # of this test.
             wait_until(
-                lambda: len(listener.results) >= expected_len,
+                lambda: len(listener.events) >= expected_len,
                 "publish all events")
 
         try:
             # zip gives us len(expected_results) pairs.
-            for expected, actual in zip(expected_results, listener.results):
+            for expected, actual in zip(expected_results, listener.events):
                 self.assertEqual(expected,
                                  actual.__class__.__name__)
                 self.assertEqual(actual.connection_id,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/test/test_streaming_protocol.py 
new/pymongo-3.11.4/test/test_streaming_protocol.py
--- old/pymongo-3.11.3/test/test_streaming_protocol.py  2021-02-03 
02:55:08.000000000 +0100
+++ new/pymongo-3.11.4/test/test_streaming_protocol.py  2021-05-05 
00:15:21.000000000 +0200
@@ -215,7 +215,7 @@
         self.assertTrue(hb_failed_events[0].awaited)
         # Depending on thread scheduling, the failed heartbeat could occur on
         # the second or third check.
-        events = [type(e) for e in hb_listener.results[:4]]
+        events = [type(e) for e in hb_listener.events[:4]]
         if events == [monitoring.ServerHeartbeatStartedEvent,
                       monitoring.ServerHeartbeatSucceededEvent,
                       monitoring.ServerHeartbeatStartedEvent,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pymongo-3.11.3/test/utils.py 
new/pymongo-3.11.4/test/utils.py
--- old/pymongo-3.11.3/test/utils.py    2021-02-03 02:55:08.000000000 +0100
+++ new/pymongo-3.11.4/test/utils.py    2021-05-05 00:15:21.000000000 +0200
@@ -36,7 +36,7 @@
 from pymongo import (MongoClient,
                      monitoring, read_preferences)
 from pymongo.errors import ConfigurationError, OperationFailure
-from pymongo.monitoring import _SENSITIVE_COMMANDS, ConnectionPoolListener
+from pymongo.monitoring import _SENSITIVE_COMMANDS
 from pymongo.pool import (_CancellationContext,
                           PoolOptions)
 from pymongo.read_concern import ReadConcern
@@ -60,7 +60,7 @@
 IMPOSSIBLE_WRITE_CONCERN = WriteConcern(w=50)
 
 
-class CMAPListener(ConnectionPoolListener):
+class BaseListener(object):
     def __init__(self):
         self.events = []
 
@@ -71,8 +71,26 @@
         self.events.append(event)
 
     def event_count(self, event_type):
-        return len([event for event in self.events[:]
-                    if isinstance(event, event_type)])
+        return len(self.events_by_type(event_type))
+
+    def events_by_type(self, event_type):
+        """Return the matching events by event class.
+
+        event_type can be a single class or a tuple of classes.
+        """
+        return self.matching(lambda e: isinstance(e, event_type))
+
+    def matching(self, matcher):
+        """Return the matching events."""
+        return [event for event in self.events[:] if matcher(event)]
+
+    def wait_for_event(self, event, count):
+        """Wait for a number of events to be published, or fail."""
+        wait_until(lambda: self.event_count(event) >= count,
+                   'find %s %s event(s)' % (count, event))
+
+
+class CMAPListener(BaseListener, monitoring.ConnectionPoolListener):
 
     def connection_created(self, event):
         self.add_event(event)
@@ -196,25 +214,17 @@
     """Listens to Server and Topology events."""
 
 
-class HeartbeatEventListener(monitoring.ServerHeartbeatListener):
+class HeartbeatEventListener(BaseListener, monitoring.ServerHeartbeatListener):
     """Listens to only server heartbeat events."""
 
-    def __init__(self):
-        self.results = []
-
     def started(self, event):
-        self.results.append(event)
+        self.add_event(event)
 
     def succeeded(self, event):
-        self.results.append(event)
+        self.add_event(event)
 
     def failed(self, event):
-        self.results.append(event)
-
-    def matching(self, matcher):
-        """Return the matching events."""
-        results = self.results[:]
-        return [event for event in results if matcher(event)]
+        self.add_event(event)
 
 
 class MockSocketInfo(object):

Reply via email to