Hello community, here is the log from the commit of package python3-pymongo for openSUSE:Factory checked in at 2015-04-25 09:54:20 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python3-pymongo (Old) and /work/SRC/openSUSE:Factory/.python3-pymongo.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python3-pymongo" Changes: -------- --- /work/SRC/openSUSE:Factory/python3-pymongo/python3-pymongo.changes 2015-04-10 09:52:20.000000000 +0200 +++ /work/SRC/openSUSE:Factory/.python3-pymongo.new/python3-pymongo.changes 2015-04-25 11:26:29.000000000 +0200 @@ -1,0 +2,8 @@ +Fri Apr 24 16:35:07 UTC 2015 - [email protected] + +- update to version 3.0.1: + * OperationFailure iterating cursor with multiple mongoses + * GridFS.delete does not remove all chunks in PyMongo 3 + * AssertionError: Result batch started from 101, expected 0 + +------------------------------------------------------------------- Old: ---- pymongo-3.0.tar.gz New: ---- pymongo-3.0.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python3-pymongo.spec ++++++ --- /var/tmp/diff_new_pack.7OaZvQ/_old 2015-04-25 11:26:30.000000000 +0200 +++ /var/tmp/diff_new_pack.7OaZvQ/_new 2015-04-25 11:26:30.000000000 +0200 @@ -17,7 +17,7 @@ Name: python3-pymongo -Version: 3.0 +Version: 3.0.1 Release: 0 Url: http://github.com/mongodb/mongo-python-driver Summary: Python driver for MongoDB ++++++ pymongo-3.0.tar.gz -> pymongo-3.0.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/MANIFEST.in new/pymongo-3.0.1/MANIFEST.in --- old/pymongo-3.0/MANIFEST.in 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/MANIFEST.in 2015-04-21 17:58:36.000000000 +0200 @@ -3,6 +3,9 @@ include ez_setup.py recursive-include doc *.rst recursive-include doc *.py +recursive-include doc *.conf +recursive-include doc *.css +recursive-include doc *.js recursive-include tools *.py include tools/README.rst recursive-include test *.pem diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/PKG-INFO new/pymongo-3.0.1/PKG-INFO --- old/pymongo-3.0/PKG-INFO 2015-04-08 00:10:46.000000000 +0200 +++ new/pymongo-3.0.1/PKG-INFO 2015-04-21 22:37:57.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pymongo -Version: 3.0 +Version: 3.0.1 Summary: Python driver for MongoDB <http://www.mongodb.org> Home-page: http://github.com/mongodb/mongo-python-driver Author: Bernie Hackett @@ -102,6 +102,18 @@ - `Monotime <https://pypi.python.org/pypi/Monotime>`_ adds support for a monotonic clock, which improves reliability in environments where clock adjustments are frequent. Not needed in Python 3.3+. + - `wincertstore <https://pypi.python.org/pypi/wincertstore>`_ adds support + for verifying server SSL certificates using Windows provided CA + certificates on older versions of python. Not needed or used with versions + of Python 2 beginning with 2.7.9, or versions of Python 3 beginning with + 3.4.0. + - `certifi <https://pypi.python.org/pypi/certifi>`_ adds support for + using the Mozilla CA bundle with SSL to verify server certificates. Not + needed or used with versions of Python 2 beginning with 2.7.9 on any OS, + versions of Python 3 beginning with Python 3.4.0 on Windows, or versions + of Python 3 beginning with Python 3.2.0 on operating systems other than + Windows. + Additional dependencies are: @@ -165,7 +177,7 @@ $ python green_framework_test.py gevent - Or with Eventlet's: + Or with Eventlet's:: $ python green_framework_test.py eventlet diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/README.rst new/pymongo-3.0.1/README.rst --- old/pymongo-3.0/README.rst 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/README.rst 2015-04-21 01:03:45.000000000 +0200 @@ -94,6 +94,18 @@ - `Monotime <https://pypi.python.org/pypi/Monotime>`_ adds support for a monotonic clock, which improves reliability in environments where clock adjustments are frequent. Not needed in Python 3.3+. +- `wincertstore <https://pypi.python.org/pypi/wincertstore>`_ adds support + for verifying server SSL certificates using Windows provided CA + certificates on older versions of python. Not needed or used with versions + of Python 2 beginning with 2.7.9, or versions of Python 3 beginning with + 3.4.0. +- `certifi <https://pypi.python.org/pypi/certifi>`_ adds support for + using the Mozilla CA bundle with SSL to verify server certificates. Not + needed or used with versions of Python 2 beginning with 2.7.9 on any OS, + versions of Python 3 beginning with Python 3.4.0 on Windows, or versions + of Python 3 beginning with Python 3.2.0 on operating systems other than + Windows. + Additional dependencies are: @@ -157,7 +169,7 @@ $ python green_framework_test.py gevent -Or with Eventlet's: +Or with Eventlet's:: $ python green_framework_test.py eventlet diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/doc/changelog.rst new/pymongo-3.0.1/doc/changelog.rst --- old/pymongo-3.0/doc/changelog.rst 2015-04-07 23:16:42.000000000 +0200 +++ new/pymongo-3.0.1/doc/changelog.rst 2015-04-21 01:22:42.000000000 +0200 @@ -1,6 +1,21 @@ Changelog ========= +Changes in Version 3.0.1 +------------------------ + +Version 3.0.1 fixes issues reported since the release of 3.0, most +importantly a bug in GridFS.delete that could prevent file chunks from +actually being deleted. + +Issues Resolved +............... + +See the `PyMongo 3.0.1 release notes in JIRA`_ for the list of resolved issues +in this release. + +.. _PyMongo 3.0.1 release notes in JIRA: https://jira.mongodb.org/browse/PYTHON/fixforversion/15322 + Changes in Version 3.0 ---------------------- @@ -485,6 +500,38 @@ .. _PyMongo 2.8 release notes in JIRA: https://jira.mongodb.org/browse/PYTHON/fixforversion/14223 +Changes in Version 2.7.2 +------------------------ + +Version 2.7.2 includes fixes for upsert reporting in the bulk API for MongoDB +versions previous to 2.6, a regression in how son manipulators are applied in +:meth:`~pymongo.collection.Collection.insert`, a few obscure connection pool +semaphore leaks, and a few other minor issues. See the list of issues resolved +for full details. + +Issues Resolved +............... + +See the `PyMongo 2.7.2 release notes in JIRA`_ for the list of resolved issues +in this release. + +.. _PyMongo 2.7.2 release notes in JIRA: https://jira.mongodb.org/browse/PYTHON/fixforversion/14005 + +Changes in Version 2.7.1 +------------------------ + +Version 2.7.1 fixes a number of issues reported since the release of 2.7, +most importantly a fix for creating indexes and manipulating users through +mongos versions older than 2.4.0. + +Issues Resolved +............... + +See the `PyMongo 2.7.1 release notes in JIRA`_ for the list of resolved issues +in this release. + +.. _PyMongo 2.7.1 release notes in JIRA: https://jira.mongodb.org/browse/PYTHON/fixforversion/13823 + Changes in Version 2.7 ---------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/doc/examples/aggregation.rst new/pymongo-3.0.1/doc/examples/aggregation.rst --- old/pymongo-3.0/doc/examples/aggregation.rst 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/doc/examples/aggregation.rst 2015-04-20 22:03:23.000000000 +0200 @@ -178,13 +178,13 @@ .. doctest:: + >>> from bson.code import Code >>> reducer = Code(""" ... function(obj, prev){ ... prev.count++; ... } ... """) ... - >>> from bson.son import SON >>> results = db.things.group(key={"x":1}, condition={}, initial={"count": 0}, reduce=reducer) >>> for doc in results: ... print doc diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/doc/pydoctheme/static/pydoctheme.css new/pymongo-3.0.1/doc/pydoctheme/static/pydoctheme.css --- old/pymongo-3.0/doc/pydoctheme/static/pydoctheme.css 1970-01-01 01:00:00.000000000 +0100 +++ new/pymongo-3.0.1/doc/pydoctheme/static/pydoctheme.css 2015-04-14 20:25:10.000000000 +0200 @@ -0,0 +1,178 @@ +@import url("default.css"); + +body { + background-color: white; + margin-left: 1em; + margin-right: 1em; +} + +div.related { + margin-bottom: 1.2em; + padding: 0.5em 0; + border-top: 1px solid #ccc; + margin-top: 0.5em; +} + +div.related a:hover { + color: #0095C4; +} + +div.related:first-child { + border-top: 0; + border-bottom: 1px solid #ccc; +} + +div.sphinxsidebar { + background-color: #eeeeee; + border-radius: 5px; + line-height: 130%; + font-size: smaller; +} + +div.sphinxsidebar h3, div.sphinxsidebar h4 { + margin-top: 1.5em; +} + +div.sphinxsidebarwrapper > h3:first-child { + margin-top: 0.2em; +} + +div.sphinxsidebarwrapper > ul > li > ul > li { + margin-bottom: 0.4em; +} + +div.sphinxsidebar a:hover { + color: #0095C4; +} + +div.sphinxsidebar input { + font-family: 'Lucida Grande',Arial,sans-serif; + border: 1px solid #999999; + font-size: smaller; + border-radius: 3px; +} + +div.sphinxsidebar input[type=text] { + max-width: 150px; +} + +div.body { + padding: 0 0 0 1.2em; +} + +div.body p { + line-height: 140%; +} + +div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { + margin: 0; + border: 0; + padding: 0.3em 0; +} + +div.body hr { + border: 0; + background-color: #ccc; + height: 1px; +} + +div.body pre { + border-radius: 3px; + border: 1px solid #ac9; +} + +div.body div.admonition, div.body div.impl-detail { + border-radius: 3px; +} + +div.body div.impl-detail > p { + margin: 0; +} + +div.body div.seealso { + border: 1px solid #dddd66; +} + +div.body a { + color: #0072aa; +} + +div.body a:visited { + color: #6363bb; +} + +div.body a:hover { + color: #00B0E4; +} + +tt, code, pre { + font-family: monospace, sans-serif; + font-size: 96.5%; +} + +div.body tt, div.body code { + border-radius: 3px; +} + +div.body tt.descname, div.body code.descname { + font-size: 120%; +} + +div.body tt.xref, div.body a tt, div.body code.xref, div.body a code { + font-weight: normal; +} + +.deprecated { + border-radius: 3px; +} + +table.docutils { + border: 1px solid #ddd; + min-width: 20%; + border-radius: 3px; + margin-top: 10px; + margin-bottom: 10px; +} + +table.docutils td, table.docutils th { + border: 1px solid #ddd !important; + border-radius: 3px; +} + +table p, table li { + text-align: left !important; +} + +table.docutils th { + background-color: #eee; + padding: 0.3em 0.5em; +} + +table.docutils td { + background-color: white; + padding: 0.3em 0.5em; +} + +table.footnote, table.footnote td { + border: 0 !important; +} + +div.footer { + line-height: 150%; + margin-top: -2em; + text-align: right; + width: auto; + margin-right: 10px; +} + +div.footer a:hover { + color: #0095C4; +} + +.refcount { + color: #060; +} + +.stableabi { + color: #229; +} diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/doc/pydoctheme/theme.conf new/pymongo-3.0.1/doc/pydoctheme/theme.conf --- old/pymongo-3.0/doc/pydoctheme/theme.conf 1970-01-01 01:00:00.000000000 +0100 +++ new/pymongo-3.0.1/doc/pydoctheme/theme.conf 2015-04-14 20:25:10.000000000 +0200 @@ -0,0 +1,23 @@ +[theme] +inherit = default +stylesheet = pydoctheme.css +pygments_style = sphinx + +[options] +bodyfont = 'Lucida Grande', Arial, sans-serif +headfont = 'Lucida Grande', Arial, sans-serif +footerbgcolor = white +footertextcolor = #555555 +relbarbgcolor = white +relbartextcolor = #666666 +relbarlinkcolor = #444444 +sidebarbgcolor = white +sidebartextcolor = #444444 +sidebarlinkcolor = #444444 +bgcolor = white +textcolor = #222222 +linkcolor = #0090c0 +visitedlinkcolor = #00608f +headtextcolor = #1a1a1a +headbgcolor = white +headlinkcolor = #aaaaaa diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/doc/static/sidebar.js new/pymongo-3.0.1/doc/static/sidebar.js --- old/pymongo-3.0/doc/static/sidebar.js 1970-01-01 01:00:00.000000000 +0100 +++ new/pymongo-3.0.1/doc/static/sidebar.js 2015-04-14 20:25:10.000000000 +0200 @@ -0,0 +1,193 @@ +/* + * sidebar.js + * ~~~~~~~~~~ + * + * This script makes the Sphinx sidebar collapsible and implements intelligent + * scrolling. + * + * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds in + * .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton used to + * collapse and expand the sidebar. + * + * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden and the + * width of the sidebar and the margin-left of the document are decreased. + * When the sidebar is expanded the opposite happens. This script saves a + * per-browser/per-session cookie used to remember the position of the sidebar + * among the pages. Once the browser is closed the cookie is deleted and the + * position reset to the default (expanded). + * + * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +$(function() { + // global elements used by the functions. + // the 'sidebarbutton' element is defined as global after its + // creation, in the add_sidebar_button function + var jwindow = $(window); + var jdocument = $(document); + var bodywrapper = $('.bodywrapper'); + var sidebar = $('.sphinxsidebar'); + var sidebarwrapper = $('.sphinxsidebarwrapper'); + + // original margin-left of the bodywrapper and width of the sidebar + // with the sidebar expanded + var bw_margin_expanded = bodywrapper.css('margin-left'); + var ssb_width_expanded = sidebar.width(); + + // margin-left of the bodywrapper and width of the sidebar + // with the sidebar collapsed + var bw_margin_collapsed = '.8em'; + var ssb_width_collapsed = '.8em'; + + // colors used by the current theme + var dark_color = '#AAAAAA'; + var light_color = '#CCCCCC'; + + function get_viewport_height() { + if (window.innerHeight) + return window.innerHeight; + else + return jwindow.height(); + } + + function sidebar_is_collapsed() { + return sidebarwrapper.is(':not(:visible)'); + } + + function toggle_sidebar() { + if (sidebar_is_collapsed()) + expand_sidebar(); + else + collapse_sidebar(); + // adjust the scrolling of the sidebar + scroll_sidebar(); + } + + function collapse_sidebar() { + sidebarwrapper.hide(); + sidebar.css('width', ssb_width_collapsed); + bodywrapper.css('margin-left', bw_margin_collapsed); + sidebarbutton.css({ + 'margin-left': '0', + 'height': bodywrapper.height(), + 'border-radius': '5px' + }); + sidebarbutton.find('span').text('»'); + sidebarbutton.attr('title', _('Expand sidebar')); + document.cookie = 'sidebar=collapsed'; + } + + function expand_sidebar() { + bodywrapper.css('margin-left', bw_margin_expanded); + sidebar.css('width', ssb_width_expanded); + sidebarwrapper.show(); + sidebarbutton.css({ + 'margin-left': ssb_width_expanded-12, + 'height': bodywrapper.height(), + 'border-radius': '0 5px 5px 0' + }); + sidebarbutton.find('span').text('«'); + sidebarbutton.attr('title', _('Collapse sidebar')); + //sidebarwrapper.css({'padding-top': + // Math.max(window.pageYOffset - sidebarwrapper.offset().top, 10)}); + document.cookie = 'sidebar=expanded'; + } + + function add_sidebar_button() { + sidebarwrapper.css({ + 'float': 'left', + 'margin-right': '0', + 'width': ssb_width_expanded - 28 + }); + // create the button + sidebar.append( + '<div id="sidebarbutton"><span>«</span></div>' + ); + var sidebarbutton = $('#sidebarbutton'); + // find the height of the viewport to center the '<<' in the page + var viewport_height = get_viewport_height(); + var sidebar_offset = sidebar.offset().top; + var sidebar_height = Math.max(bodywrapper.height(), sidebar.height()); + sidebarbutton.find('span').css({ + 'display': 'block', + 'position': 'fixed', + 'top': Math.min(viewport_height/2, sidebar_height/2 + sidebar_offset) - 10 + }); + + sidebarbutton.click(toggle_sidebar); + sidebarbutton.attr('title', _('Collapse sidebar')); + sidebarbutton.css({ + 'border-radius': '0 5px 5px 0', + 'color': '#444444', + 'background-color': '#CCCCCC', + 'font-size': '1.2em', + 'cursor': 'pointer', + 'height': sidebar_height, + 'padding-top': '1px', + 'padding-left': '1px', + 'margin-left': ssb_width_expanded - 12 + }); + + sidebarbutton.hover( + function () { + $(this).css('background-color', dark_color); + }, + function () { + $(this).css('background-color', light_color); + } + ); + } + + function set_position_from_cookie() { + if (!document.cookie) + return; + var items = document.cookie.split(';'); + for(var k=0; k<items.length; k++) { + var key_val = items[k].split('='); + var key = key_val[0]; + if (key == 'sidebar') { + var value = key_val[1]; + if ((value == 'collapsed') && (!sidebar_is_collapsed())) + collapse_sidebar(); + else if ((value == 'expanded') && (sidebar_is_collapsed())) + expand_sidebar(); + } + } + } + + add_sidebar_button(); + var sidebarbutton = $('#sidebarbutton'); + set_position_from_cookie(); + + + /* intelligent scrolling */ + function scroll_sidebar() { + var sidebar_height = sidebarwrapper.height(); + var viewport_height = get_viewport_height(); + var offset = sidebar.position()['top']; + var wintop = jwindow.scrollTop(); + var winbot = wintop + viewport_height; + var curtop = sidebarwrapper.position()['top']; + var curbot = curtop + sidebar_height; + // does sidebar fit in window? + if (sidebar_height < viewport_height) { + // yes: easy case -- always keep at the top + sidebarwrapper.css('top', $u.min([$u.max([0, wintop - offset - 10]), + jdocument.height() - sidebar_height - 200])); + } + else { + // no: only scroll if top/bottom edge of sidebar is at + // top/bottom edge of window + if (curtop > wintop && curbot > winbot) { + sidebarwrapper.css('top', $u.max([wintop - offset - 10, 0])); + } + else if (curtop < wintop && curbot < winbot) { + sidebarwrapper.css('top', $u.min([winbot - sidebar_height - offset - 20, + jdocument.height() - sidebar_height - 200])); + } + } + } + jwindow.scroll(scroll_sidebar); +}); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/gridfs/__init__.py new/pymongo-3.0.1/gridfs/__init__.py --- old/pymongo-3.0/gridfs/__init__.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/gridfs/__init__.py 2015-04-20 22:03:23.000000000 +0200 @@ -41,15 +41,9 @@ Raises :class:`TypeError` if `database` is not an instance of :class:`~pymongo.database.Database`. - The `connect` parameter ensures that the underlying - :class:`~pymongo.mongo_client.MongoClient` is connected to a server, - and creates an index on the "chunks" collection if needed. - :Parameters: - `database`: database to use - `collection` (optional): root collection to use - - `connect` (optional): whether to begin connecting the client in - the background .. versionchanged:: 3.0 `database` must use an acknowledged @@ -235,8 +229,8 @@ - `file_id`: ``"_id"`` of the file to delete """ self.__ensure_index_files_id() - self.__files.delete_many({"_id": file_id}) - self.__chunks.delete_one({"files_id": file_id}) + self.__files.delete_one({"_id": file_id}) + self.__chunks.delete_many({"files_id": file_id}) def list(self): """List the names of all files stored in this instance of diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo/__init__.py new/pymongo-3.0.1/pymongo/__init__.py --- old/pymongo-3.0/pymongo/__init__.py 2015-04-07 23:24:48.000000000 +0200 +++ new/pymongo-3.0.1/pymongo/__init__.py 2015-04-21 22:28:15.000000000 +0200 @@ -70,7 +70,7 @@ ALL = 2 """Profile all operations.""" -version_tuple = (3, 0) +version_tuple = (3, 0, 1) def get_version_string(): if isinstance(version_tuple[-1], str): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo/collection.py new/pymongo-3.0.1/pymongo/collection.py --- old/pymongo-3.0/pymongo/collection.py 2015-04-06 23:54:56.000000000 +0200 +++ new/pymongo-3.0.1/pymongo/collection.py 2015-04-14 20:47:22.000000000 +0200 @@ -870,8 +870,11 @@ error. - `oplog_replay` (optional): If True, set the oplogReplay query flag. - - `modifiers` (optional): A dict specifying the MongoDB query - modifiers that should be used for this query. + - `modifiers` (optional): A dict specifying the MongoDB `query + modifiers`_ that should be used for this query. For example:: + + >>> db.test.find(modifiers={"$maxTimeMS": 500}) + - `batch_size` (optional): Limits the number of documents returned in a single batch. - `manipulate` (optional): **DEPRECATED** - If True (the default), @@ -918,6 +921,8 @@ The `tag_sets` and `secondary_acceptable_latency_ms` parameters. .. _PYTHON-500: https://jira.mongodb.org/browse/PYTHON-500 + .. _query modifiers: + http://docs.mongodb.org/manual/reference/operator/query-modifier/ .. mongodoc:: find """ @@ -975,6 +980,15 @@ return [CommandCursor(self, cursor['cursor'], sock_info.address) for cursor in result['cursors']] + def _count(self, cmd): + """Internal count helper.""" + with self._socket_for_reads() as (sock_info, slave_ok): + res = self._command(sock_info, cmd, slave_ok, + allowable_errors=["ns missing"]) + if res.get("errmsg", "") == "ns missing": + return 0 + return int(res["n"]) + def count(self, filter=None, **kwargs): """Get the number of documents in this collection. @@ -1006,12 +1020,7 @@ if "hint" in kwargs and not isinstance(kwargs["hint"], string_type): kwargs["hint"] = helpers._index_document(kwargs["hint"]) cmd.update(kwargs) - with self._socket_for_reads() as (sock_info, slave_ok): - res = self._command(sock_info, cmd, slave_ok, - allowable_errors=["ns missing"]) - if res.get("errmsg", "") == "ns missing": - return 0 - return int(res["n"]) + return self._count(cmd) def create_indexes(self, indexes): """Create one or more indexes on this collection. @@ -1255,23 +1264,31 @@ .. versionadded:: 3.0 """ + codec_options = CodecOptions(SON) + coll = self.with_options(codec_options) with self._socket_for_primary_reads() as (sock_info, slave_ok): if sock_info.max_wire_version > 2: cmd = SON([("listIndexes", self.__name), ("cursor", {})]) cursor = self._command(sock_info, cmd, slave_ok, ReadPreference.PRIMARY, - CodecOptions(SON))["cursor"] + codec_options)["cursor"] + return CommandCursor(coll, cursor, sock_info.address) else: namespace = _UJOIN % (self.__database.name, "system.indexes") res = helpers._first_batch( sock_info, namespace, {"ns": self.__full_name}, - 0, slave_ok, CodecOptions(SON), ReadPreference.PRIMARY) + 0, slave_ok, codec_options, ReadPreference.PRIMARY) + data = res["data"] cursor = { "id": res["cursor_id"], - "firstBatch": res["data"], + "firstBatch": data, "ns": namespace, } - return CommandCursor(self, cursor, sock_info.address) + # Note that a collection can only have 64 indexes, so we don't + # technically have to pass len(data) here. There will never be + # an OP_GET_MORE call. + return CommandCursor( + coll, cursor, sock_info.address, len(data)) def index_information(self): """Get information on this collection's indexes. @@ -1887,6 +1904,7 @@ else: self._update(sock_info, {"_id": to_save["_id"]}, to_save, True, check_keys, False, manipulate, write_concern) + return to_save.get("_id") def insert(self, doc_or_docs, manipulate=True, check_keys=True, continue_on_error=False, **kwargs): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo/command_cursor.py new/pymongo-3.0.1/pymongo/command_cursor.py --- old/pymongo-3.0/pymongo/command_cursor.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/pymongo/command_cursor.py 2015-04-20 22:03:23.000000000 +0200 @@ -35,7 +35,7 @@ self.__data = deque(cursor_info['firstBatch']) self.__retrieved = retrieved self.__batch_size = 0 - self.__killed = False + self.__killed = (self.__id == 0) if "ns" in cursor_info: self.__ns = cursor_info["ns"] @@ -114,6 +114,8 @@ client._reset_server_and_request_check(self.address) raise self.__id = doc["cursor_id"] + if self.__id == 0: + self.__killed = True assert doc["starting_from"] == self.__retrieved, ( "Result batch started from %s, expected %s" % ( @@ -143,7 +145,14 @@ @property def alive(self): - """Does this cursor have the potential to return more data?""" + """Does this cursor have the potential to return more data? + + Even if :attr:`alive` is ``True``, :meth:`.next` can raise + :exc:`StopIteration`. Best to use a for loop:: + + for doc in collection.aggregate(pipeline): + print(doc) + """ return bool(len(self.__data) or (not self.__killed)) @property @@ -167,7 +176,7 @@ """ if len(self.__data) or self._refresh(): coll = self.__collection - return coll.database._fix_incoming(self.__data.popleft(), coll) + return coll.database._fix_outgoing(self.__data.popleft(), coll) else: raise StopIteration diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo/cursor.py new/pymongo-3.0.1/pymongo/cursor.py --- old/pymongo-3.0/pymongo/cursor.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/pymongo/cursor.py 2015-04-20 22:03:23.000000000 +0200 @@ -655,22 +655,23 @@ The :meth:`~count` method now supports :meth:`~hint`. """ validate_boolean("with_limit_and_skip", with_limit_and_skip) - options = {"query": self.__spec} + cmd = SON([("count", self.__collection.name), + ("query", self.__spec)]) if self.__max_time_ms is not None: - options["maxTimeMS"] = self.__max_time_ms + cmd["maxTimeMS"] = self.__max_time_ms if self.__comment: - options["$comment"] = self.__comment + cmd["$comment"] = self.__comment if self.__hint is not None: - options["hint"] = self.__hint + cmd["hint"] = self.__hint if with_limit_and_skip: if self.__limit: - options["limit"] = self.__limit + cmd["limit"] = self.__limit if self.__skip: - options["skip"] = self.__skip + cmd["skip"] = self.__skip - return self.__collection.count(**options) + return self.__collection._count(cmd) def distinct(self, key): """Get a list of distinct values for `key` among all documents @@ -860,6 +861,8 @@ client._reset_server_and_request_check(self.__address) raise self.__id = doc["cursor_id"] + if self.__id == 0: + self.__killed = True # starting from doesn't get set on getmore's for tailable cursors if not self.__query_flags & _QUERY_OPTIONS["tailable_cursor"]: @@ -934,6 +937,11 @@ <http://www.mongodb.org/display/DOCS/Tailable+Cursors>`_ since they will stop iterating even though they *may* return more results in the future. + + With regular cursors, simply use a for loop instead of :attr:`alive`:: + + for doc in collection.find(): + print(doc) """ return bool(len(self.__data) or (not self.__killed)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo/database.py new/pymongo-3.0.1/pymongo/database.py --- old/pymongo-3.0/pymongo/database.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/pymongo/database.py 2015-04-14 20:43:22.000000000 +0200 @@ -462,17 +462,20 @@ cmd["filter"] = criteria coll = self["$cmd"] cursor = self._command(sock_info, cmd, slave_okay)["cursor"] + return CommandCursor(coll, cursor, sock_info.address) else: coll = self["system.namespaces"] res = _first_batch(sock_info, coll.full_name, criteria, 0, slave_okay, CodecOptions(), ReadPreference.PRIMARY) + data = res["data"] cursor = { "id": res["cursor_id"], - "firstBatch": res["data"], + "firstBatch": data, "ns": coll.full_name, } - return CommandCursor(coll, cursor, sock_info.address) + # Need to tell the cursor how many docs were in the first batch. + return CommandCursor(coll, cursor, sock_info.address, len(data)) def collection_names(self, include_system_collections=True): """Get a list of all the collection names in this database. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo/message.py new/pymongo-3.0.1/pymongo/message.py --- old/pymongo-3.0/pymongo/message.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/pymongo/message.py 2015-04-20 22:07:00.000000000 +0200 @@ -69,7 +69,7 @@ if mode and ( mode != ReadPreference.SECONDARY_PREFERRED.mode or tag_sets != [{}]): - if "query" not in spec: + if "$query" not in spec: spec = SON([("$query", spec)]) spec["$readPreference"] = read_preference.document return spec diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo/mongo_client.py new/pymongo-3.0.1/pymongo/mongo_client.py --- old/pymongo-3.0/pymongo/mongo_client.py 2015-04-02 20:32:45.000000000 +0200 +++ new/pymongo-3.0.1/pymongo/mongo_client.py 2015-04-21 21:56:43.000000000 +0200 @@ -118,12 +118,20 @@ that the pool will open simultaneously. If this is set, operations will block if there are `maxPoolSize` outstanding connections from the pool. Defaults to 100. - - `socketTimeoutMS`: (integer or None) How long (in milliseconds) a - send or receive on a socket can take before timing out. Defaults to - ``None`` (no timeout). - - `connectTimeoutMS`: (integer or None) How long (in milliseconds) a - connection can take to be opened before timing out. Defaults to - ``20000``. + - `socketTimeoutMS`: (integer or None) Controls how long (in + milliseconds) the driver will wait for a response after sending an + ordinary (non-monitoring) database operation before concluding that + a network error has occurred. Defaults to ``None`` (no timeout). + - `connectTimeoutMS`: (integer or None) Controls how long (in + milliseconds) the driver will wait during server monitoring when + connecting a new socket to a server before concluding the server + is unavailable. Defaults to ``20000`` (20 seconds). + - `serverSelectionTimeoutMS`: (integer) Controls how long (in + milliseconds) the driver will wait to find an available, + appropriate server to carry out a database operation; while it is + waiting, multiple server monitoring operations may be carried out, + each controlled by `connectTimeoutMS`. Defaults to ``30000`` (30 + seconds). - `waitQueueTimeoutMS`: (integer or None) How long (in milliseconds) a thread will wait for a socket from the pool if the pool has no free sockets. Defaults to ``None`` (no timeout). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo/server_selectors.py new/pymongo-3.0.1/pymongo/server_selectors.py --- old/pymongo-3.0/pymongo/server_selectors.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/pymongo/server_selectors.py 2015-04-20 22:03:23.000000000 +0200 @@ -21,10 +21,6 @@ return server_descriptions -def address_server_selector(address, server_descriptions): - return [s for s in server_descriptions if s.address == address] - - def writable_server_selector(server_descriptions): return [s for s in server_descriptions if s.is_writable] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo/topology.py new/pymongo-3.0.1/pymongo/topology.py --- old/pymongo-3.0/pymongo/topology.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/pymongo/topology.py 2015-04-20 22:03:23.000000000 +0200 @@ -16,7 +16,6 @@ import random import threading -from functools import partial from bson.py3compat import itervalues from pymongo import common @@ -27,7 +26,7 @@ from pymongo.errors import ServerSelectionTimeoutError, InvalidOperation from pymongo.monotonic import time as _time from pymongo.server import Server -from pymongo.server_selectors import (address_server_selector, +from pymongo.server_selectors import (any_server_selector, apply_local_threshold, arbiter_server_selector, secondary_server_selector, @@ -59,7 +58,10 @@ with self._lock: self._ensure_opened() - def select_servers(self, selector, server_selection_timeout=None): + def select_servers(self, + selector, + server_selection_timeout=None, + address=None): """Return a list of Servers matching selector, or time out. :Parameters: @@ -68,6 +70,7 @@ - `server_selection_timeout` (optional): maximum seconds to wait. If not provided, the default value common.SERVER_SELECTION_TIMEOUT is used. + - `address`: optional server address to select. Calls self.open() if needed. @@ -84,7 +87,7 @@ now = _time() end_time = now + server_timeout - server_descriptions = self._apply_selector(selector) + server_descriptions = self._apply_selector(selector, address) while not server_descriptions: # No suitable servers. @@ -102,15 +105,19 @@ self._condition.wait(common.MIN_HEARTBEAT_INTERVAL) self._description.check_compatible() now = _time() - server_descriptions = self._apply_selector(selector) + server_descriptions = self._apply_selector(selector, address) return [self.get_server_by_address(sd.address) for sd in server_descriptions] - def select_server(self, selector, server_selection_timeout=None): + def select_server(self, + selector, + server_selection_timeout=None, + address=None): """Like select_servers, but choose a random server if several match.""" return random.choice(self.select_servers(selector, - server_selection_timeout)) + server_selection_timeout, + address)) def select_server_by_address(self, address, server_selection_timeout=None): @@ -131,8 +138,9 @@ Raises exc:`ServerSelectionTimeoutError` after `server_selection_timeout` if no matching servers are found. """ - selector = partial(address_server_selector, address) - return self.select_server(selector, server_selection_timeout) + return self.select_server(any_server_selector, + server_selection_timeout, + address) def on_change(self, server_description): """Process a new ServerDescription after an ismaster call completes.""" @@ -295,10 +303,13 @@ for server in self._servers.values(): server.request_check() - def _apply_selector(self, selector): + def _apply_selector(self, selector, address): if self._description.topology_type == TOPOLOGY_TYPE.Single: # Ignore the selector. return self._description.known_servers + elif address: + sd = self._description.server_descriptions().get(address) + return [sd] if sd else [] elif self._description.topology_type == TOPOLOGY_TYPE.Sharded: return apply_local_threshold(self._settings.local_threshold_ms, self._description.known_servers) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo.egg-info/PKG-INFO new/pymongo-3.0.1/pymongo.egg-info/PKG-INFO --- old/pymongo-3.0/pymongo.egg-info/PKG-INFO 2015-04-08 00:10:46.000000000 +0200 +++ new/pymongo-3.0.1/pymongo.egg-info/PKG-INFO 2015-04-21 22:37:57.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: pymongo -Version: 3.0 +Version: 3.0.1 Summary: Python driver for MongoDB <http://www.mongodb.org> Home-page: http://github.com/mongodb/mongo-python-driver Author: Bernie Hackett @@ -102,6 +102,18 @@ - `Monotime <https://pypi.python.org/pypi/Monotime>`_ adds support for a monotonic clock, which improves reliability in environments where clock adjustments are frequent. Not needed in Python 3.3+. + - `wincertstore <https://pypi.python.org/pypi/wincertstore>`_ adds support + for verifying server SSL certificates using Windows provided CA + certificates on older versions of python. Not needed or used with versions + of Python 2 beginning with 2.7.9, or versions of Python 3 beginning with + 3.4.0. + - `certifi <https://pypi.python.org/pypi/certifi>`_ adds support for + using the Mozilla CA bundle with SSL to verify server certificates. Not + needed or used with versions of Python 2 beginning with 2.7.9 on any OS, + versions of Python 3 beginning with Python 3.4.0 on Windows, or versions + of Python 3 beginning with Python 3.2.0 on operating systems other than + Windows. + Additional dependencies are: @@ -165,7 +177,7 @@ $ python green_framework_test.py gevent - Or with Eventlet's: + Or with Eventlet's:: $ python green_framework_test.py eventlet diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/pymongo.egg-info/SOURCES.txt new/pymongo-3.0.1/pymongo.egg-info/SOURCES.txt --- old/pymongo-3.0/pymongo.egg-info/SOURCES.txt 2015-04-08 00:10:46.000000000 +0200 +++ new/pymongo-3.0.1/pymongo.egg-info/SOURCES.txt 2015-04-21 22:37:57.000000000 +0200 @@ -88,6 +88,9 @@ doc/examples/index.rst doc/examples/mod_wsgi.rst doc/examples/tls.rst +doc/pydoctheme/theme.conf +doc/pydoctheme/static/pydoctheme.css +doc/static/sidebar.js gridfs/__init__.py gridfs/errors.py gridfs/grid_file.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/setup.py new/pymongo-3.0.1/setup.py --- old/pymongo-3.0/setup.py 2015-04-07 23:24:36.000000000 +0200 +++ new/pymongo-3.0.1/setup.py 2015-04-21 22:28:25.000000000 +0200 @@ -26,7 +26,7 @@ from distutils.errors import DistutilsPlatformError, DistutilsExecError from distutils.core import Extension -version = "3.0" +version = "3.0.1" f = open("README.rst") try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/test/__init__.py new/pymongo-3.0.1/test/__init__.py --- old/pymongo-3.0/test/__init__.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/test/__init__.py 2015-04-21 00:04:45.000000000 +0200 @@ -297,6 +297,12 @@ "Must be connected to a mongod, not a mongos", func=func) + def require_mongos(self, func): + """Run a test only if the client is connected to a mongos.""" + return self._require(self.is_mongos, + "Must be connected to a mongos", + func=func) + def check_auth_with_sharding(self, func): """Skip a test when connected to mongos < 2.0 and running with auth.""" condition = not (self.auth_enabled and diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/test/test_collection.py new/pymongo-3.0.1/test/test_collection.py --- old/pymongo-3.0/test/test_collection.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/test/test_collection.py 2015-04-21 00:10:33.000000000 +0200 @@ -1110,6 +1110,22 @@ for doc in cursor: pass + @client_context.require_version_min(2, 5, 1) + def test_aggregation_cursor_alive(self): + self.db.test.delete_many({}) + self.db.test.insert_many([{} for _ in range(3)]) + self.addCleanup(self.db.test.delete_many, {}) + cursor = self.db.test.aggregate(pipeline=[], cursor={'batchSize': 2}) + n = 0 + while True: + cursor.next() + n += 1 + if 3 == n: + self.assertFalse(cursor.alive) + break + + self.assertTrue(cursor.alive) + @client_context.require_version_min(2, 5, 5) @client_context.require_no_mongos def test_parallel_scan(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/test/test_cursor.py new/pymongo-3.0.1/test/test_cursor.py --- old/pymongo-3.0/test/test_cursor.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/test/test_cursor.py 2015-04-21 00:10:46.000000000 +0200 @@ -634,11 +634,16 @@ if client_context.version.at_least(2, 6, 0): # Count supports hint self.assertEqual(0, collection.find({'i': 1}).hint("x_1").count()) + self.assertEqual( + 0, collection.find({'i': 1}).hint([("x", 1)]).count()) else: # Hint is ignored self.assertEqual(1, collection.find({'i': 1}).hint("x_1").count()) + self.assertEqual( + 1, collection.find({'i': 1}).hint([("x", 1)]).count()) self.assertEqual(2, collection.find().hint("x_1").count()) + self.assertEqual(2, collection.find().hint([("x", 1)]).count()) def test_where(self): db = self.db @@ -1106,5 +1111,34 @@ docs.extend(ccursor) self.assertEqual(len(docs), 200) + def test_modifiers(self): + cur = self.db.test.find() + self.assertTrue('$query' not in cur._Cursor__query_spec()) + cur = self.db.test.find().comment("testing").max_time_ms(500) + self.assertTrue('$query' in cur._Cursor__query_spec()) + self.assertEqual(cur._Cursor__query_spec()["$comment"], "testing") + self.assertEqual(cur._Cursor__query_spec()["$maxTimeMS"], 500) + cur = self.db.test.find( + modifiers={"$maxTimeMS": 500, "$comment": "testing"}) + self.assertTrue('$query' in cur._Cursor__query_spec()) + self.assertEqual(cur._Cursor__query_spec()["$comment"], "testing") + self.assertEqual(cur._Cursor__query_spec()["$maxTimeMS"], 500) + + def test_alive(self): + self.db.test.delete_many({}) + self.db.test.insert_many([{} for _ in range(3)]) + self.addCleanup(self.db.test.delete_many, {}) + cursor = self.db.test.find().batch_size(2) + n = 0 + while True: + cursor.next() + n += 1 + if 3 == n: + self.assertFalse(cursor.alive) + break + + self.assertTrue(cursor.alive) + + if __name__ == "__main__": unittest.main() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/test/test_database.py new/pymongo-3.0.1/test/test_database.py --- old/pymongo-3.0/test/test_database.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/test/test_database.py 2015-04-14 20:43:22.000000000 +0200 @@ -163,6 +163,16 @@ for coll in colls_without_systems: self.assertTrue(not coll.startswith("system.")) + # Force more than one batch. + db = self.client.many_collections + for i in range(101): + db["coll" + str(i)].insert_one({}) + # No Error + try: + db.collection_names() + finally: + self.client.drop_database("many_collections") + def test_drop_collection(self): db = Database(self.client, "pymongo_test") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/test/test_gridfs.py new/pymongo-3.0.1/test/test_gridfs.py --- old/pymongo-3.0/test/test_gridfs.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/test/test_gridfs.py 2015-04-20 22:03:23.000000000 +0200 @@ -117,6 +117,18 @@ self.assertEqual("foo", oid) self.assertEqual(b"hello world", self.fs.get("foo").read()) + def test_multi_chunk_delete(self): + self.db.fs.drop() + self.assertEqual(0, self.db.fs.files.count()) + self.assertEqual(0, self.db.fs.chunks.count()) + gfs = gridfs.GridFS(self.db) + oid = gfs.put(b"hello", chunkSize=1) + self.assertEqual(1, self.db.fs.files.count()) + self.assertEqual(5, self.db.fs.chunks.count()) + gfs.delete(oid) + self.assertEqual(0, self.db.fs.files.count()) + self.assertEqual(0, self.db.fs.chunks.count()) + def test_list(self): self.assertEqual([], self.fs.list()) self.fs.put(b"hello world") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/test/test_legacy_api.py new/pymongo-3.0.1/test/test_legacy_api.py --- old/pymongo-3.0/test/test_legacy_api.py 2015-04-02 00:43:39.000000000 +0200 +++ new/pymongo-3.0.1/test/test_legacy_api.py 2015-04-14 20:33:36.000000000 +0200 @@ -615,6 +615,16 @@ self.db.test.save(doc) self.assertTrue("_id" in doc) + def test_save_returns_id(self): + doc = {"hello": "jesse"} + _id = self.db.test.save(doc) + self.assertTrue(isinstance(_id, ObjectId)) + self.assertEqual(_id, doc["_id"]) + doc["hi"] = "bernie" + _id = self.db.test.save(doc) + self.assertTrue(isinstance(_id, ObjectId)) + self.assertEqual(_id, doc["_id"]) + def test_remove_one(self): # Tests legacy remove. self.db.test.remove() @@ -967,6 +977,33 @@ out = db.test.find_one() self.assertEqual('value', out.get('value')) + def test_son_manipulator_outgoing(self): + class Thing(object): + def __init__(self, value): + self.value = value + + class ThingTransformer(SONManipulator): + def transform_outgoing(self, doc, collection): + # We don't want this applied to the command return + # value in pymongo.cursor.Cursor. + if 'value' in doc: + return Thing(doc['value']) + return doc + + db = self.client.foo + db.add_son_manipulator(ThingTransformer()) + + db.test.delete_many({}) + db.test.insert_one({'value': 'value'}) + out = db.test.find_one() + self.assertTrue(isinstance(out, Thing)) + self.assertEqual('value', out.value) + + if client_context.version.at_least(2, 6): + out = next(db.test.aggregate([], cursor={})) + self.assertTrue(isinstance(out, Thing)) + self.assertEqual('value', out.value) + def test_son_manipulator_inheritance(self): # Tests legacy API elements. class Thing(object): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pymongo-3.0/test/test_read_preferences.py new/pymongo-3.0.1/test/test_read_preferences.py --- old/pymongo-3.0/test/test_read_preferences.py 2015-04-02 00:41:30.000000000 +0200 +++ new/pymongo-3.0.1/test/test_read_preferences.py 2015-04-21 01:17:13.000000000 +0200 @@ -21,8 +21,9 @@ sys.path[0:0] = [""] from bson.py3compat import MAXSIZE -from pymongo.cursor import _QUERY_OPTIONS +from bson.son import SON from pymongo.errors import ConfigurationError +from pymongo.message import _maybe_add_read_preference from pymongo.mongo_client import MongoClient from pymongo.read_preferences import (ReadPreference, MovingAverage, Primary, PrimaryPreferred, @@ -33,15 +34,14 @@ from pymongo.write_concern import WriteConcern from test.test_replica_set_client import TestReplicaSetClientBase -from test import (client_context, +from test import (SkipTest, + client_context, host, port, unittest, - utils, - IntegrationTest, db_user, db_pwd) -from test.utils import connected, single_client, one, wait_until, rs_client +from test.utils import single_client, one, wait_until, rs_client from test.version import Version @@ -400,6 +400,95 @@ avg.add_sample(30) self.assertAlmostEqual(15.6, avg.get()) +class TestMongosAndReadPreference(unittest.TestCase): + + def test_maybe_add_read_preference(self): + + # Primary doesn't add $readPreference + out = _maybe_add_read_preference({}, Primary()) + self.assertEqual(out, {}) + + pref = PrimaryPreferred() + out = _maybe_add_read_preference({}, pref) + self.assertEqual( + out, SON([("$query", {}), ("$readPreference", pref.document)])) + pref = PrimaryPreferred(tag_sets=[{'dc': 'nyc'}]) + out = _maybe_add_read_preference({}, pref) + self.assertEqual( + out, SON([("$query", {}), ("$readPreference", pref.document)])) + + pref = Secondary() + out = _maybe_add_read_preference({}, pref) + self.assertEqual( + out, SON([("$query", {}), ("$readPreference", pref.document)])) + pref = Secondary(tag_sets=[{'dc': 'nyc'}]) + out = _maybe_add_read_preference({}, pref) + self.assertEqual( + out, SON([("$query", {}), ("$readPreference", pref.document)])) + + # SecondaryPreferred without tag_sets doesn't add $readPreference + pref = SecondaryPreferred() + out = _maybe_add_read_preference({}, pref) + self.assertEqual(out, {}) + pref = SecondaryPreferred(tag_sets=[{'dc': 'nyc'}]) + out = _maybe_add_read_preference({}, pref) + self.assertEqual( + out, SON([("$query", {}), ("$readPreference", pref.document)])) + + pref = Nearest() + out = _maybe_add_read_preference({}, pref) + self.assertEqual( + out, SON([("$query", {}), ("$readPreference", pref.document)])) + pref = Nearest(tag_sets=[{'dc': 'nyc'}]) + out = _maybe_add_read_preference({}, pref) + self.assertEqual( + out, SON([("$query", {}), ("$readPreference", pref.document)])) + + criteria = SON([("$query", {}), ("$orderby", SON([("_id", 1)]))]) + pref = Nearest() + out = _maybe_add_read_preference(criteria, pref) + self.assertEqual( + out, + SON([("$query", {}), + ("$orderby", SON([("_id", 1)])), + ("$readPreference", pref.document)])) + pref = Nearest(tag_sets=[{'dc': 'nyc'}]) + out = _maybe_add_read_preference(criteria, pref) + self.assertEqual( + out, + SON([("$query", {}), + ("$orderby", SON([("_id", 1)])), + ("$readPreference", pref.document)])) + + @client_context.require_mongos + def test_mongos(self): + shard = client_context.client.config.shards.find_one()['host'] + num_members = shard.count(',') + 1 + if num_members == 1: + raise SkipTest("Need a replica set shard to test.") + coll = client_context.client.pymongo_test.get_collection( + "test", + write_concern=WriteConcern(w=num_members)) + coll.drop() + res = coll.insert_many([{} for _ in range(5)]) + first_id = res.inserted_ids[0] + last_id = res.inserted_ids[-1] + + # Note - this isn't a perfect test since there's no way to + # tell what shard member a query ran on. + for pref in (Primary(), + PrimaryPreferred(), + Secondary(), + SecondaryPreferred(), + Nearest()): + qcoll = coll.with_options(read_preference=pref) + results = list(qcoll.find().sort([("_id", 1)])) + self.assertEqual(first_id, results[0]["_id"]) + self.assertEqual(last_id, results[-1]["_id"]) + results = list(qcoll.find().sort([("_id", -1)])) + self.assertEqual(first_id, results[-1]["_id"]) + self.assertEqual(last_id, results[0]["_id"]) + if __name__ == "__main__": unittest.main()
