PYTHON-2133 Fix up docs

And finish deleting python 2 specific code.
This commit is contained in:
Bernie Hackett 2021-01-20 16:08:31 -08:00
parent c65b89d8a1
commit 521f7b9af4
44 changed files with 259 additions and 504 deletions

View File

@ -19,7 +19,7 @@ that might not be of interest or that has already been addressed.
Supported Interpreters
----------------------
PyMongo supports CPython 2.7, 3.4+, PyPy, and PyPy3.5+. Language
PyMongo supports CPython 3.4+ and PyPy3.5+. Language
features not supported by all interpreters can not be used.
Style Guide

View File

@ -160,9 +160,9 @@ Here's a basic example (for more see the *examples* section of the docs):
>>> client = pymongo.MongoClient("localhost", 27017)
>>> db = client.test
>>> db.name
u'test'
'test'
>>> db.my_collection
Collection(Database(MongoClient('localhost', 27017), u'test'), u'my_collection')
Collection(Database(MongoClient('localhost', 27017), 'test'), 'my_collection')
>>> db.my_collection.insert_one({"x": 10}).inserted_id
ObjectId('4aba15ebe23f6b53b0000000')
>>> db.my_collection.insert_one({"x": 8}).inserted_id
@ -170,7 +170,7 @@ Here's a basic example (for more see the *examples* section of the docs):
>>> db.my_collection.insert_one({"x": 11}).inserted_id
ObjectId('4aba160ee23f6b543e000002')
>>> db.my_collection.find_one()
{u'x': 10, u'_id': ObjectId('4aba15ebe23f6b53b0000000')}
{'x': 10, '_id': ObjectId('4aba15ebe23f6b53b0000000')}
>>> for item in db.my_collection.find():
... print(item["x"])
...
@ -178,7 +178,7 @@ Here's a basic example (for more see the *examples* section of the docs):
8
11
>>> db.my_collection.create_index("x")
u'x_1'
'x_1'
>>> for item in db.my_collection.find().sort("x", pymongo.ASCENDING):
... print(item["x"])
...

View File

@ -22,11 +22,9 @@ Python Type BSON Type Supported Direction
None null both
bool boolean both
int [#int]_ int32 / int64 py -> bson
long int64 py -> bson
`bson.int64.Int64` int64 both
float number (real) both
string string py -> bson
unicode string both
str string both
list array both
dict / `SON` object both
datetime.datetime [#dt]_ [#dt2]_ date both
@ -36,17 +34,11 @@ compiled re [#re]_ regex py -> bson
`bson.objectid.ObjectId` oid both
`bson.dbref.DBRef` dbref both
None undefined bson -> py
unicode code bson -> py
`bson.code.Code` code py -> bson
unicode symbol bson -> py
bytes (Python 3) [#bytes]_ binary both
`bson.code.Code` code both
str symbol bson -> py
bytes [#bytes]_ binary both
======================================= ============= ===================
Note that, when using Python 2.x, to save binary data it must be wrapped as
an instance of `bson.binary.Binary`. Otherwise it will be saved as a BSON
string and retrieved as unicode. Users of Python 3.x can use the Python bytes
type.
.. [#int] A Python int will be saved as a BSON int32 or BSON int64 depending
on its size. A BSON int32 will always decode to a Python int. A BSON
int64 will always decode to a :class:`~bson.int64.Int64`.
@ -58,10 +50,8 @@ type.
objects from ``re.compile()`` are both saved as BSON regular expressions.
BSON regular expressions are decoded as :class:`~bson.regex.Regex`
instances.
.. [#bytes] The bytes type from Python 3.x is encoded as BSON binary with
subtype 0. In Python 3.x it will be decoded back to bytes. In Python 2.x
it will be decoded to an instance of :class:`~bson.binary.Binary` with
subtype 0.
.. [#bytes] The bytes type is encoded as BSON binary with
subtype 0. It will be decoded back to bytes.
"""
import calendar
@ -161,7 +151,7 @@ def _get_int(data, view, position, dummy0, dummy1, dummy2):
def _get_c_string(data, view, position, opts):
"""Decode a BSON 'C' string to python unicode string."""
"""Decode a BSON 'C' string to python str."""
end = data.index(b"\x00", position)
return _utf_8_decode(view[position:end],
opts.unicode_decode_error_handler, True)[0], end + 1
@ -173,7 +163,7 @@ def _get_float(data, view, position, dummy0, dummy1, dummy2):
def _get_string(data, view, position, obj_end, opts, dummy):
"""Decode a BSON string to python unicode string."""
"""Decode a BSON string to python str."""
length = _UNPACK_INT_FROM(data, position)[0]
position += 4
if length < 1 or obj_end - position < length:
@ -573,7 +563,7 @@ def _encode_list(name, value, check_keys, opts):
def _encode_text(name, value, dummy0, dummy1):
"""Encode a python unicode (python 2.x) / str (python 3.x)."""
"""Encode a python str."""
value = _utf_8_encode(value)[0]
return b"\x02" + name + _PACK_INT(len(value) + 1) + value + b"\x00"
@ -616,12 +606,11 @@ def _encode_none(name, dummy0, dummy1, dummy2):
def _encode_regex(name, value, dummy0, dummy1):
"""Encode a python regex or bson.regex.Regex."""
flags = value.flags
# Python 2 common case
if flags == 0:
return b"\x0B" + name + _make_c_string_check(value.pattern) + b"\x00"
# Python 3 common case
elif flags == re.UNICODE:
if flags == re.UNICODE:
return b"\x0B" + name + _make_c_string_check(value.pattern) + b"u\x00"
elif flags == 0:
return b"\x0B" + name + _make_c_string_check(value.pattern) + b"\x00"
else:
sflags = b""
if flags & re.IGNORECASE:

View File

@ -57,7 +57,7 @@ class DBRef(object):
@property
def collection(self):
"""Get the name of this DBRef's collection as unicode.
"""Get the name of this DBRef's collection.
"""
return self.__collection

View File

@ -30,7 +30,7 @@ Example usage (deserialization):
>>> from bson.json_util import loads
>>> loads('[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$scope": {}, "$code": "function x() { return 1; }"}}, {"bin": {"$type": "80", "$binary": "AQIDBA=="}}]')
[{u'foo': [1, 2]}, {u'bar': {u'hello': u'world'}}, {u'code': Code('function x() { return 1; }', {})}, {u'bin': Binary('...', 128)}]
[{'foo': [1, 2]}, {'bar': {'hello': 'world'}}, {'code': Code('function x() { return 1; }', {})}, {'bin': Binary(b'...', 128)}]
Example usage (serialization):
@ -855,7 +855,6 @@ def default(obj, json_options=DEFAULT_JSON_OPTIONS):
return {'$numberDouble': representation}
elif json_options.json_mode == JSONMode.CANONICAL:
# repr() will return the shortest string guaranteed to produce the
# original value, when float() is called on it. str produces a
# shorter string in Python 2.
# original value, when float() is called on it.
return {'$numberDouble': str(repr(obj))}
raise TypeError("%r is not JSON serializable" % obj)

View File

@ -70,7 +70,7 @@ class ObjectId(object):
By default, ``ObjectId()`` creates a new unique identifier. The
optional parameter `oid` can be an :class:`ObjectId`, or any 12
:class:`bytes` or, in Python 2, any 12-character :class:`str`.
:class:`bytes`.
For example, the 12 bytes b'foo-bar-quux' do not follow the ObjectId
specification but they are acceptable input::
@ -78,14 +78,10 @@ class ObjectId(object):
>>> ObjectId(b'foo-bar-quux')
ObjectId('666f6f2d6261722d71757578')
`oid` can also be a :class:`unicode` or :class:`str` of 24 hex digits::
`oid` can also be a :class:`str` of 24 hex digits::
>>> ObjectId('0123456789ab0123456789ab')
ObjectId('0123456789ab0123456789ab')
>>>
>>> # A u-prefixed unicode literal:
>>> ObjectId(u'0123456789ab0123456789ab')
ObjectId('0123456789ab0123456789ab')
Raises :class:`~bson.errors.InvalidId` if `oid` is not 12 bytes nor
24 hex digits, or :class:`TypeError` if `oid` is not an accepted type.
@ -201,7 +197,6 @@ class ObjectId(object):
"""
if isinstance(oid, ObjectId):
self.__id = oid.binary
# bytes or unicode in python 2, str in python 3
elif isinstance(oid, str):
if len(oid) == 24:
try:

View File

@ -33,7 +33,7 @@ class SON(dict):
A subclass of dict that maintains ordering of keys and provides a
few extra niceties for dealing with SON. SON provides an API
similar to collections.OrderedDict from Python 2.7+.
similar to collections.OrderedDict.
"""
def __init__(self, data=None, **kwargs):

View File

@ -27,8 +27,8 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'PyMongo'
copyright = u'MongoDB, Inc. 2008-present. MongoDB, Mongo, and the leaf logo are registered trademarks of MongoDB, Inc'
project = 'PyMongo'
copyright = 'MongoDB, Inc. 2008-present. MongoDB, Mongo, and the leaf logo are registered trademarks of MongoDB, Inc'
html_show_sphinx = False
# The version info for the project you're documenting, acts as replacement for
@ -152,8 +152,8 @@ htmlhelp_basename = 'PyMongo' + release.replace('.', '_')
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'PyMongo.tex', u'PyMongo Documentation',
u'Michael Dirolf', 'manual'),
('index', 'PyMongo.tex', 'PyMongo Documentation',
'Michael Dirolf', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of

View File

@ -58,15 +58,15 @@ eg "$sort":
... ]
>>> import pprint
>>> pprint.pprint(list(db.things.aggregate(pipeline)))
[{u'_id': u'cat', u'count': 3},
{u'_id': u'dog', u'count': 2},
{u'_id': u'mouse', u'count': 1}]
[{'_id': 'cat', 'count': 3},
{'_id': 'dog', 'count': 2},
{'_id': 'mouse', 'count': 1}]
To run an explain plan for this aggregation use the
:meth:`~pymongo.database.Database.command` method::
>>> db.command('aggregate', 'things', pipeline=pipeline, explain=True)
{u'ok': 1.0, u'stages': [...]}
{'ok': 1.0, 'stages': [...]}
As well as simple aggregations the aggregation framework provides projection
capabilities to reshape the returned data. Using projections and aggregation,
@ -123,9 +123,9 @@ iterate over the result collection:
>>> for doc in result.find().sort("_id"):
... pprint.pprint(doc)
...
{u'_id': u'cat', u'value': 3.0}
{u'_id': u'dog', u'value': 2.0}
{u'_id': u'mouse', u'value': 1.0}
{'_id': 'cat', 'value': 3.0}
{'_id': 'dog', 'value': 2.0}
{'_id': 'mouse', 'value': 1.0}
Advanced Map/Reduce
-------------------
@ -140,7 +140,7 @@ response to the map/reduce command, rather than just the result collection:
>>> pprint.pprint(
... db.things.map_reduce(mapper, reducer, "myresults", full_response=True))
{...u'ok': 1.0,... u'result': u'myresults'...}
{...'ok': 1.0,... 'result': 'myresults'...}
All of the optional map/reduce parameters are also supported, simply pass them
as keyword arguments. In this example we use the `query` parameter to limit the
@ -153,8 +153,8 @@ documents that will be mapped over:
>>> for doc in results.find().sort("_id"):
... pprint.pprint(doc)
...
{u'_id': u'cat', u'value': 1.0}
{u'_id': u'dog', u'value': 1.0}
{'_id': 'cat', 'value': 1.0}
{'_id': 'dog', 'value': 1.0}
You can use :class:`~bson.son.SON` or :class:`collections.OrderedDict` to
specify a different database to store the result collection:
@ -168,6 +168,6 @@ specify a different database to store the result collection:
... reducer,
... out=SON([("replace", "results"), ("db", "outdb")]),
... full_response=True))
{...u'ok': 1.0,... u'result': {u'collection': u'results', u'db': u'outdb'}...}
{...'ok': 1.0,... 'result': {'collection': 'results', 'db': 'outdb'}...}
.. seealso:: The full list of options for MongoDB's `map reduce engine <http://www.mongodb.org/display/DOCS/MapReduce>`_

View File

@ -11,8 +11,7 @@ Percent-Escaping Username and Password
--------------------------------------
Username and password must be percent-escaped with
:meth:`urllib.parse.quote_plus` in Python 3, or :meth:`urllib.quote_plus` in
Python 2, to be used in a MongoDB URI. For example, in Python 3::
:meth:`urllib.parse.quote_plus`, to be used in a MongoDB URI. For example::
>>> from pymongo import MongoClient
>>> import urllib.parse

View File

@ -70,7 +70,7 @@ of operations performed.
'nModified': 2,
'nRemoved': 10000,
'nUpserted': 1,
'upserted': [{u'_id': 4, u'index': 5}],
'upserted': [{'_id': 4, 'index': 5}],
'writeConcernErrors': [],
'writeErrors': []}
@ -107,10 +107,10 @@ the failure.
'nUpserted': 0,
'upserted': [],
'writeConcernErrors': [],
'writeErrors': [{u'code': 11000,
u'errmsg': u'...E11000...duplicate key error...',
u'index': 1,...
u'op': {'_id': 4}}]}
'writeErrors': [{'code': 11000,
'errmsg': '...E11000...duplicate key error...',
'index': 1,...
'op': {'_id': 4}}]}
.. _unordered_bulk:
@ -145,14 +145,14 @@ and fourth operations succeed.
'nUpserted': 0,
'upserted': [],
'writeConcernErrors': [],
'writeErrors': [{u'code': 11000,
u'errmsg': u'...E11000...duplicate key error...',
u'index': 0,...
u'op': {'_id': 1}},
{u'code': 11000,
u'errmsg': u'...E11000...duplicate key error...',
u'index': 2,...
u'op': {'_id': 3}}]}
'writeErrors': [{'code': 11000,
'errmsg': '...E11000...duplicate key error...',
'index': 0,...
'op': {'_id': 1}},
{'code': 11000,
'errmsg': '...E11000...duplicate key error...',
'index': 2,...
'op': {'_id': 3}}]}
Write Concern
.............
@ -177,7 +177,7 @@ after all operations are attempted, regardless of execution order.
'nRemoved': 0,
'nUpserted': 0,
'upserted': [],
'writeConcernErrors': [{u'code': 64...
u'errInfo': {u'wtimeout': True},
u'errmsg': u'waiting for replication timed out'}],
'writeConcernErrors': [{'code': 64...
'errInfo': {'wtimeout': True},
'errmsg': 'waiting for replication timed out'}],
'writeErrors': []}

View File

@ -138,7 +138,7 @@ Now, we can seamlessly encode and decode instances of
>>> mydoc = collection.find_one()
>>> import pprint
>>> pprint.pprint(mydoc)
{u'_id': ObjectId('...'), u'num': Decimal('45.321')}
{'_id': ObjectId('...'), 'num': Decimal('45.321')}
We can see what's actually being saved to the database by creating a fresh
@ -149,7 +149,7 @@ MongoDB:
>>> vanilla_collection = db.get_collection('test')
>>> pprint.pprint(vanilla_collection.find_one())
{u'_id': ObjectId('...'), u'num': Decimal128('45.321')}
{'_id': ObjectId('...'), 'num': Decimal128('45.321')}
Encoding Subtypes
@ -217,7 +217,7 @@ object, we can seamlessly encode instances of ``DecimalInt``:
<pymongo.results.InsertOneResult object at ...>
>>> mydoc = collection.find_one()
>>> pprint.pprint(mydoc)
{u'_id': ObjectId('...'), u'num': Decimal('45.321')}
{'_id': ObjectId('...'), 'num': Decimal('45.321')}
Note that the ``transform_bson`` method of the base codec class results in
these values being decoded as ``Decimal`` (and not ``DecimalInt``).
@ -310,7 +310,7 @@ We can now seamlessly encode instances of :py:class:`~decimal.Decimal`:
<pymongo.results.InsertOneResult object at ...>
>>> mydoc = collection.find_one()
>>> pprint.pprint(mydoc)
{u'_id': ObjectId('...'), u'num': Decimal128('45.321')}
{'_id': ObjectId('...'), 'num': Decimal128('45.321')}
.. note::

View File

@ -22,7 +22,7 @@ Creating a geospatial index in pymongo is easy:
>>> from pymongo import MongoClient, GEO2D
>>> db = MongoClient().geo_example
>>> db.places.create_index([("loc", GEO2D)])
u'loc_2d'
'loc_2d'
Inserting Places
----------------
@ -53,9 +53,9 @@ Using the geospatial index we can find documents near another point:
>>> for doc in db.places.find({"loc": {"$near": [3, 6]}}).limit(3):
... pprint.pprint(doc)
...
{u'_id': ObjectId('...'), u'loc': [2, 5]}
{u'_id': ObjectId('...'), u'loc': [4, 4]}
{u'_id': ObjectId('...'), u'loc': [1, 2]}
{'_id': ObjectId('...'), 'loc': [2, 5]}
{'_id': ObjectId('...'), 'loc': [4, 4]}
{'_id': ObjectId('...'), 'loc': [1, 2]}
.. note:: If using :data:`pymongo.GEOSPHERE`, using $nearSphere is recommended.
@ -68,9 +68,9 @@ The $maxDistance operator requires the use of :class:`~bson.son.SON`:
>>> for doc in db.places.find(query).limit(3):
... pprint.pprint(doc)
...
{u'_id': ObjectId('...'), u'loc': [2, 5]}
{u'_id': ObjectId('...'), u'loc': [4, 4]}
{u'_id': ObjectId('...'), u'loc': [1, 2]}
{'_id': ObjectId('...'), 'loc': [2, 5]}
{'_id': ObjectId('...'), 'loc': [4, 4]}
{'_id': ObjectId('...'), 'loc': [1, 2]}
It's also possible to query for all items within a given rectangle
(specified by lower-left and upper-right coordinates):
@ -80,8 +80,8 @@ It's also possible to query for all items within a given rectangle
>>> query = {"loc": {"$within": {"$box": [[2, 2], [5, 6]]}}}
>>> for doc in db.places.find(query).sort('_id'):
... pprint.pprint(doc)
{u'_id': ObjectId('...'), u'loc': [2, 5]}
{u'_id': ObjectId('...'), u'loc': [4, 4]}
{'_id': ObjectId('...'), 'loc': [2, 5]}
{'_id': ObjectId('...'), 'loc': [4, 4]}
Or circle (specified by center point and radius):
@ -91,15 +91,15 @@ Or circle (specified by center point and radius):
>>> for doc in db.places.find(query).sort('_id'):
... pprint.pprint(doc)
...
{u'_id': ObjectId('...'), u'loc': [2, 5]}
{u'_id': ObjectId('...'), u'loc': [1, 2]}
{u'_id': ObjectId('...'), u'loc': [4, 4]}
{'_id': ObjectId('...'), 'loc': [2, 5]}
{'_id': ObjectId('...'), 'loc': [1, 2]}
{'_id': ObjectId('...'), 'loc': [4, 4]}
geoNear queries are also supported using :class:`~bson.son.SON`::
>>> from bson.son import SON
>>> db.command(SON([('geoNear', 'places'), ('near', [1, 2])]))
{u'ok': 1.0, u'stats': ...}
{'ok': 1.0, 'stats': ...}
.. warning:: Starting in MongoDB version 4.0, MongoDB deprecates the **geoNear** command. Use one of the following operations instead.

View File

@ -52,7 +52,7 @@ file:
.. doctest::
>>> fs.get(a).read()
'hello world'
b'hello world'
:meth:`~gridfs.GridFS.get` returns a file-like object, so we get the
file's contents by calling :meth:`~gridfs.grid_file.GridOut.read`.
@ -68,11 +68,11 @@ keyword arguments:
>>> b = fs.put(fs.get(a), filename="foo", bar="baz")
>>> out = fs.get(b)
>>> out.read()
'hello world'
b'hello world'
>>> out.filename
u'foo'
'foo'
>>> out.bar
u'baz'
'baz'
>>> out.upload_date
datetime.datetime(...)

View File

@ -111,7 +111,7 @@ set::
>>> from time import sleep
>>> c = MongoClient(replicaset='foo'); print(c.nodes); sleep(0.1); print(c.nodes)
frozenset([])
frozenset([(u'localhost', 27019), (u'localhost', 27017), (u'localhost', 27018)])
frozenset([('localhost', 27019), ('localhost', 27017), ('localhost', 27018)])
You need not wait for replica set discovery in your application, however.
If you need to do any operation with a MongoClient, such as a
@ -132,7 +132,7 @@ connect to the replica set and perform a couple of basic operations::
>>> db.test.insert_one({"x": 1}).inserted_id
ObjectId('...')
>>> db.test.find_one()
{u'x': 1, u'_id': ObjectId('...')}
{'x': 1, '_id': ObjectId('...')}
By checking the host and port, we can see that we're connected to
*localhost:27017*, which is the current primary::
@ -162,7 +162,7 @@ general). At that point the driver will connect to the new primary and
the operation will succeed::
>>> db.test.find_one()
{u'x': 1, u'_id': ObjectId('...')}
{'x': 1, '_id': ObjectId('...')}
>>> db.client.address
('localhost', 27018)

View File

@ -16,30 +16,6 @@ command::
$ python -m pip install pymongo[tls]
Starting with PyMongo 3.11 this installs `PyOpenSSL
<https://pypi.org/project/pyOpenSSL/>`_, `requests`_
and `service_identity
<https://pypi.org/project/service_identity/>`_
for users of Python versions older than 2.7.9. PyOpenSSL supports SNI for these
old Python versions allowing applictions to connect to Altas free and shared
tier instances.
Earlier versions of PyMongo require you to manually install the dependencies
listed below.
Python 2.x
``````````
The `ipaddress`_ module is required on all platforms.
When using CPython < 2.7.9 or PyPy < 2.5.1:
- On Windows, the `wincertstore`_ module is required.
- On all other platforms, the `certifi`_ module is required.
.. _ipaddress: https://pypi.python.org/pypi/ipaddress
.. _wincertstore: https://pypi.python.org/pypi/wincertstore
.. _certifi: https://pypi.python.org/pypi/certifi
.. warning:: Industry best practices recommend, and some regulations require,
the use of TLS 1.1 or newer. Though no application changes are required for
PyMongo to make use of the newest protocols, some operating systems or
@ -128,8 +104,7 @@ Or, in the URI::
Specifying a certificate revocation list
........................................
Python 2.7.9+ (pypy 2.5.1+) and 3.4+ provide support for certificate revocation
lists. The ``tlsCRLFile`` option takes a path to a CRL file. It can be passed
The ``tlsCRLFile`` option takes a path to a CRL file. It can be passed
as a keyword argument::
>>> client = pymongo.MongoClient('example.com',
@ -161,9 +136,8 @@ the ``ssl_keyfile`` option::
... tlsCertificateKeyFile='/path/to/client.pem',
... ssl_keyfile='/path/to/key.pem')
Python 2.7.9+ (pypy 2.5.1+) and 3.3+ support providing a password or passphrase
to decrypt encrypted private keys. Use the ``tlsCertificateKeyFilePassword``
option::
Python supports providing a password or passphrase to decrypt encrypted
private keys. Use the ``tlsCertificateKeyFilePassword`` option::
>>> client = pymongo.MongoClient('example.com',
... tls=True,

View File

@ -238,7 +238,7 @@ Therefore, Python dicts are not guaranteed to show keys in the order they are
stored in BSON. Here, "a" is shown before "b":
>>> print(collection.find_one())
{u'_id': 1.0, u'subdocument': {u'a': 1.0, u'b': 1.0}}
{'_id': 1.0, 'subdocument': {'a': 1.0, 'b': 1.0}}
To preserve order when reading BSON, use the :class:`~bson.son.SON` class,
which is a dict that remembers its key order. First, get a handle to the
@ -264,7 +264,7 @@ Now, documents and subdocuments in query results are represented with
.. doctest:: key-order
>>> print(collection_son.find_one())
SON([(u'_id', 1.0), (u'subdocument', SON([(u'b', 1.0), (u'a', 1.0)]))])
SON([('_id', 1.0), ('subdocument', SON([('b', 1.0), ('a', 1.0)]))])
The subdocument's actual storage layout is now visible: "b" is before "a".
@ -287,7 +287,7 @@ There are two solutions. First, you can match the subdocument field-by-field:
>>> collection.find_one({'subdocument.a': 1.0,
... 'subdocument.b': 1.0})
{u'_id': 1.0, u'subdocument': {u'a': 1.0, u'b': 1.0}}
{'_id': 1.0, 'subdocument': {'a': 1.0, 'b': 1.0}}
The query matches any subdocument with an "a" of 1.0 and a "b" of 1.0,
regardless of the order you specify them in Python or the order they are stored
@ -298,7 +298,7 @@ The second solution is to use a :class:`~bson.son.SON` to specify the key order:
>>> query = {'subdocument': SON([('b', 1.0), ('a', 1.0)])}
>>> collection.find_one(query)
{u'_id': 1.0, u'subdocument': {u'a': 1.0, u'b': 1.0}}
{'_id': 1.0, 'subdocument': {'a': 1.0, 'b': 1.0}}
The key order you use when you create a :class:`~bson.son.SON` is preserved
when it is serialized to BSON and used as a query. Thus you can create a

View File

@ -143,7 +143,7 @@ of the collections in our database:
.. doctest::
>>> db.list_collection_names()
[u'posts']
['posts']
Getting a Single Document With :meth:`~pymongo.collection.Collection.find_one`
------------------------------------------------------------------------------
@ -159,11 +159,11 @@ document from the posts collection:
>>> import pprint
>>> pprint.pprint(posts.find_one())
{u'_id': ObjectId('...'),
u'author': u'Mike',
u'date': datetime.datetime(...),
u'tags': [u'mongodb', u'python', u'pymongo'],
u'text': u'My first blog post!'}
{'_id': ObjectId('...'),
'author': 'Mike',
'date': datetime.datetime(...),
'tags': ['mongodb', 'python', 'pymongo'],
'text': 'My first blog post!'}
The result is a dictionary matching the one that we inserted previously.
@ -177,11 +177,11 @@ our results to a document with author "Mike" we do:
.. doctest::
>>> pprint.pprint(posts.find_one({"author": "Mike"}))
{u'_id': ObjectId('...'),
u'author': u'Mike',
u'date': datetime.datetime(...),
u'tags': [u'mongodb', u'python', u'pymongo'],
u'text': u'My first blog post!'}
{'_id': ObjectId('...'),
'author': 'Mike',
'date': datetime.datetime(...),
'tags': ['mongodb', 'python', 'pymongo'],
'text': 'My first blog post!'}
If we try with a different author, like "Eliot", we'll get no result:
@ -201,11 +201,11 @@ We can also find a post by its ``_id``, which in our example is an ObjectId:
>>> post_id
ObjectId(...)
>>> pprint.pprint(posts.find_one({"_id": post_id}))
{u'_id': ObjectId('...'),
u'author': u'Mike',
u'date': datetime.datetime(...),
u'tags': [u'mongodb', u'python', u'pymongo'],
u'text': u'My first blog post!'}
{'_id': ObjectId('...'),
'author': 'Mike',
'date': datetime.datetime(...),
'tags': ['mongodb', 'python', 'pymongo'],
'text': 'My first blog post!'}
Note that an ObjectId is not the same as its string representation:
@ -229,23 +229,6 @@ case to **convert the ObjectId from a string** before passing it to
.. seealso:: :ref:`web-application-querying-by-objectid`
A Note On Unicode Strings
-------------------------
You probably noticed that the regular Python strings we stored earlier look
different when retrieved from the server (e.g. u'Mike' instead of 'Mike').
A short explanation is in order.
MongoDB stores data in `BSON format <http://bsonspec.org>`_. BSON strings are
UTF-8 encoded so PyMongo must ensure that any strings it stores contain only
valid UTF-8 data. Regular strings (<type 'str'>) are validated and stored
unaltered. Unicode strings (<type 'unicode'>) are encoded UTF-8 first. The
reason our example string is represented in the Python shell as u'Mike' instead
of 'Mike' is that PyMongo decodes each BSON string to a Python unicode string,
not a regular str.
`You can read more about Python unicode strings here
<http://docs.python.org/howto/unicode.html>`_.
Bulk Inserts
------------
In order to make querying a little more interesting, let's insert a
@ -293,21 +276,21 @@ document in the ``posts`` collection:
>>> for post in posts.find():
... pprint.pprint(post)
...
{u'_id': ObjectId('...'),
u'author': u'Mike',
u'date': datetime.datetime(...),
u'tags': [u'mongodb', u'python', u'pymongo'],
u'text': u'My first blog post!'}
{u'_id': ObjectId('...'),
u'author': u'Mike',
u'date': datetime.datetime(...),
u'tags': [u'bulk', u'insert'],
u'text': u'Another post!'}
{u'_id': ObjectId('...'),
u'author': u'Eliot',
u'date': datetime.datetime(...),
u'text': u'and pretty easy too!',
u'title': u'MongoDB is fun'}
{'_id': ObjectId('...'),
'author': 'Mike',
'date': datetime.datetime(...),
'tags': ['mongodb', 'python', 'pymongo'],
'text': 'My first blog post!'}
{'_id': ObjectId('...'),
'author': 'Mike',
'date': datetime.datetime(...),
'tags': ['bulk', 'insert'],
'text': 'Another post!'}
{'_id': ObjectId('...'),
'author': 'Eliot',
'date': datetime.datetime(...),
'text': 'and pretty easy too!',
'title': 'MongoDB is fun'}
Just like we did with :meth:`~pymongo.collection.Collection.find_one`,
we can pass a document to :meth:`~pymongo.collection.Collection.find`
@ -319,16 +302,16 @@ author is "Mike":
>>> for post in posts.find({"author": "Mike"}):
... pprint.pprint(post)
...
{u'_id': ObjectId('...'),
u'author': u'Mike',
u'date': datetime.datetime(...),
u'tags': [u'mongodb', u'python', u'pymongo'],
u'text': u'My first blog post!'}
{u'_id': ObjectId('...'),
u'author': u'Mike',
u'date': datetime.datetime(...),
u'tags': [u'bulk', u'insert'],
u'text': u'Another post!'}
{'_id': ObjectId('...'),
'author': 'Mike',
'date': datetime.datetime(...),
'tags': ['mongodb', 'python', 'pymongo'],
'text': 'My first blog post!'}
{'_id': ObjectId('...'),
'author': 'Mike',
'date': datetime.datetime(...),
'tags': ['bulk', 'insert'],
'text': 'Another post!'}
Counting
--------
@ -362,16 +345,16 @@ than a certain date, but also sort the results by author:
>>> for post in posts.find({"date": {"$lt": d}}).sort("author"):
... pprint.pprint(post)
...
{u'_id': ObjectId('...'),
u'author': u'Eliot',
u'date': datetime.datetime(...),
u'text': u'and pretty easy too!',
u'title': u'MongoDB is fun'}
{u'_id': ObjectId('...'),
u'author': u'Mike',
u'date': datetime.datetime(...),
u'tags': [u'bulk', u'insert'],
u'text': u'Another post!'}
{'_id': ObjectId('...'),
'author': 'Eliot',
'date': datetime.datetime(...),
'text': 'and pretty easy too!',
'title': 'MongoDB is fun'}
{'_id': ObjectId('...'),
'author': 'Mike',
'date': datetime.datetime(...),
'tags': ['bulk', 'insert'],
'text': 'Another post!'}
Here we use the special ``"$lt"`` operator to do a range query, and
also call :meth:`~pymongo.cursor.Cursor.sort` to sort the results
@ -393,7 +376,7 @@ First, we'll need to create the index:
>>> result = db.profiles.create_index([('user_id', pymongo.ASCENDING)],
... unique=True)
>>> sorted(list(db.profiles.index_information()))
[u'_id_', u'user_id_1']
['_id_', 'user_id_1']
Notice that we have two indexes now: one is the index on ``_id`` that MongoDB
creates automatically, and the other is the index on ``user_id`` we just

View File

@ -107,12 +107,11 @@ class GridFS(object):
finally:
f.close()
`data` can be either an instance of :class:`str` (:class:`bytes`
in python 3) or a file-like object providing a :meth:`read` method.
If an `encoding` keyword argument is passed, `data` can also be a
:class:`unicode` (:class:`str` in python 3) instance, which will
be encoded as `encoding` before being written. Any keyword arguments
will be passed through to the created file - see
`data` can be either an instance of :class:`bytes` or a file-like
object providing a :meth:`read` method. If an `encoding` keyword
argument is passed, `data` can also be a :class:`str` instance, which
will be encoded as `encoding` before being written. Any keyword
arguments will be passed through to the created file - see
:meth:`~gridfs.grid_file.GridIn` for possible arguments. Returns the
``"_id"`` of the created file.

View File

@ -144,11 +144,8 @@ class GridIn(object):
- ``"chunkSize"`` or ``"chunk_size"``: size of each of the
chunks, in bytes (default: 255 kb)
- ``"encoding"``: encoding used for this file. In Python 2,
any :class:`unicode` that is written to the file will be
converted to a :class:`str`. In Python 3, any :class:`str`
that is written to the file will be converted to
:class:`bytes`.
- ``"encoding"``: encoding used for this file. Any :class:`str`
that is written to the file will be converted to :class:`bytes`.
:Parameters:
- `root_collection`: root collection to write to
@ -345,15 +342,14 @@ class GridIn(object):
`data` can be either a string of bytes or a file-like object
(implementing :meth:`read`). If the file has an
:attr:`encoding` attribute, `data` can also be a
:class:`unicode` (:class:`str` in python 3) instance, which
will be encoded as :attr:`encoding` before being written.
:class:`str` instance, which will be encoded as
:attr:`encoding` before being written.
Due to buffering, the data may not actually be written to the
database until the :meth:`close` method is called. Raises
:class:`ValueError` if this file is already closed. Raises
:class:`TypeError` if `data` is not an instance of
:class:`str` (:class:`bytes` in python 3), a file-like object,
or an instance of :class:`unicode` (:class:`str` in python 3).
:class:`bytes`, a file-like object, or an instance of :class:`str`.
Unicode data is only allowed if the file has an :attr:`encoding`
attribute.

View File

@ -439,8 +439,8 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find({}):
... print(doc)
...
{u'x': 1, u'_id': ObjectId('54f62e60fba5226811f634ef')}
{u'x': 1, u'_id': ObjectId('54f62e60fba5226811f634f0')}
{'x': 1, '_id': ObjectId('54f62e60fba5226811f634ef')}
{'x': 1, '_id': ObjectId('54f62e60fba5226811f634f0')}
>>> # DeleteMany, UpdateOne, and UpdateMany are also available.
...
>>> from pymongo import InsertOne, DeleteOne, ReplaceOne
@ -458,9 +458,9 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find({}):
... print(doc)
...
{u'x': 1, u'_id': ObjectId('54f62e60fba5226811f634f0')}
{u'y': 1, u'_id': ObjectId('54f62ee2fba5226811f634f1')}
{u'z': 1, u'_id': ObjectId('54f62ee28891e756a6e1abd5')}
{'x': 1, '_id': ObjectId('54f62e60fba5226811f634f0')}
{'y': 1, '_id': ObjectId('54f62ee2fba5226811f634f1')}
{'z': 1, '_id': ObjectId('54f62ee28891e756a6e1abd5')}
:Parameters:
- `requests`: A list of write operations (see examples above).
@ -660,7 +660,7 @@ class Collection(common.BaseObject):
>>> result.inserted_id
ObjectId('54f112defba522406c9cc208')
>>> db.test.find_one({'x': 1})
{u'x': 1, u'_id': ObjectId('54f112defba522406c9cc208')}
{'x': 1, '_id': ObjectId('54f112defba522406c9cc208')}
:Parameters:
- `document`: The document to insert. Must be a mutable mapping
@ -876,7 +876,7 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find({}):
... print(doc)
...
{u'x': 1, u'_id': ObjectId('54f4c5befba5220aa4d6dee7')}
{'x': 1, '_id': ObjectId('54f4c5befba5220aa4d6dee7')}
>>> result = db.test.replace_one({'x': 1}, {'y': 1})
>>> result.matched_count
1
@ -885,7 +885,7 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find({}):
... print(doc)
...
{u'y': 1, u'_id': ObjectId('54f4c5befba5220aa4d6dee7')}
{'y': 1, '_id': ObjectId('54f4c5befba5220aa4d6dee7')}
The *upsert* option can be used to insert a new document if a matching
document does not exist.
@ -898,7 +898,7 @@ class Collection(common.BaseObject):
>>> result.upserted_id
ObjectId('54f11e5c8891e756a6e1abd4')
>>> db.test.find_one({'x': 1})
{u'x': 1, u'_id': ObjectId('54f11e5c8891e756a6e1abd4')}
{'x': 1, '_id': ObjectId('54f11e5c8891e756a6e1abd4')}
:Parameters:
- `filter`: A query that matches the document to replace.
@ -955,9 +955,9 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find():
... print(doc)
...
{u'x': 1, u'_id': 0}
{u'x': 1, u'_id': 1}
{u'x': 1, u'_id': 2}
{'x': 1, '_id': 0}
{'x': 1, '_id': 1}
{'x': 1, '_id': 2}
>>> result = db.test.update_one({'x': 1}, {'$inc': {'x': 3}})
>>> result.matched_count
1
@ -966,9 +966,9 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find():
... print(doc)
...
{u'x': 4, u'_id': 0}
{u'x': 1, u'_id': 1}
{u'x': 1, u'_id': 2}
{'x': 4, '_id': 0}
{'x': 1, '_id': 1}
{'x': 1, '_id': 2}
:Parameters:
- `filter`: A query that matches the document to update.
@ -1031,9 +1031,9 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find():
... print(doc)
...
{u'x': 1, u'_id': 0}
{u'x': 1, u'_id': 1}
{u'x': 1, u'_id': 2}
{'x': 1, '_id': 0}
{'x': 1, '_id': 1}
{'x': 1, '_id': 2}
>>> result = db.test.update_many({'x': 1}, {'$inc': {'x': 3}})
>>> result.matched_count
3
@ -1042,9 +1042,9 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find():
... print(doc)
...
{u'x': 4, u'_id': 0}
{u'x': 4, u'_id': 1}
{u'x': 4, u'_id': 2}
{'x': 4, '_id': 0}
{'x': 4, '_id': 1}
{'x': 4, '_id': 2}
:Parameters:
- `filter`: A query that matches the documents to update.
@ -2123,10 +2123,10 @@ class Collection(common.BaseObject):
like this:
>>> db.test.create_index("x", unique=True)
u'x_1'
'x_1'
>>> db.test.index_information()
{u'_id_': {u'key': [(u'_id', 1)]},
u'x_1': {u'unique': True, u'key': [(u'x', 1)]}}
{'_id_': {'key': [('_id', 1)]},
'x_1': {'unique': True, 'key': [('x', 1)]}}
:Parameters:
- `session` (optional): a
@ -2800,7 +2800,7 @@ class Collection(common.BaseObject):
>>> db.test.count_documents({'x': 1})
2
>>> db.test.find_one_and_delete({'x': 1})
{u'x': 1, u'_id': ObjectId('54f4e12bfba5220aa4d6dee8')}
{'x': 1, '_id': ObjectId('54f4e12bfba5220aa4d6dee8')}
>>> db.test.count_documents({'x': 1})
1
@ -2809,17 +2809,17 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find({'x': 1}):
... print(doc)
...
{u'x': 1, u'_id': 0}
{u'x': 1, u'_id': 1}
{u'x': 1, u'_id': 2}
{'x': 1, '_id': 0}
{'x': 1, '_id': 1}
{'x': 1, '_id': 2}
>>> db.test.find_one_and_delete(
... {'x': 1}, sort=[('_id', pymongo.DESCENDING)])
{u'x': 1, u'_id': 2}
{'x': 1, '_id': 2}
The *projection* option can be used to limit the fields returned.
>>> db.test.find_one_and_delete({'x': 1}, projection={'_id': False})
{u'x': 1}
{'x': 1}
:Parameters:
- `filter`: A query that matches the document to delete.
@ -2877,17 +2877,17 @@ class Collection(common.BaseObject):
>>> for doc in db.test.find({}):
... print(doc)
...
{u'x': 1, u'_id': 0}
{u'x': 1, u'_id': 1}
{u'x': 1, u'_id': 2}
{'x': 1, '_id': 0}
{'x': 1, '_id': 1}
{'x': 1, '_id': 2}
>>> db.test.find_one_and_replace({'x': 1}, {'y': 1})
{u'x': 1, u'_id': 0}
{'x': 1, '_id': 0}
>>> for doc in db.test.find({}):
... print(doc)
...
{u'y': 1, u'_id': 0}
{u'x': 1, u'_id': 1}
{u'x': 1, u'_id': 2}
{'y': 1, '_id': 0}
{'x': 1, '_id': 1}
{'x': 1, '_id': 2}
:Parameters:
- `filter`: A query that matches the document to replace.
@ -2953,7 +2953,7 @@ class Collection(common.BaseObject):
>>> db.test.find_one_and_update(
... {'_id': 665}, {'$inc': {'count': 1}, '$set': {'done': True}})
{u'_id': 665, u'done': False, u'count': 25}}
{'_id': 665, 'done': False, 'count': 25}}
Returns ``None`` if no document matches the filter.
@ -2971,7 +2971,7 @@ class Collection(common.BaseObject):
... {'_id': 'userid'},
... {'$inc': {'seq': 1}},
... return_document=ReturnDocument.AFTER)
{u'_id': u'userid', u'seq': 1}
{'_id': 'userid', 'seq': 1}
You can limit the fields returned with the *projection* option.
@ -2980,7 +2980,7 @@ class Collection(common.BaseObject):
... {'$inc': {'seq': 1}},
... projection={'seq': True, '_id': False},
... return_document=ReturnDocument.AFTER)
{u'seq': 2}
{'seq': 2}
The *upsert* option can be used to create the document if it doesn't
already exist.
@ -2993,20 +2993,20 @@ class Collection(common.BaseObject):
... projection={'seq': True, '_id': False},
... upsert=True,
... return_document=ReturnDocument.AFTER)
{u'seq': 1}
{'seq': 1}
If multiple documents match *filter*, a *sort* can be applied.
>>> for doc in db.test.find({'done': True}):
... print(doc)
...
{u'_id': 665, u'done': True, u'result': {u'count': 26}}
{u'_id': 701, u'done': True, u'result': {u'count': 17}}
{'_id': 665, 'done': True, 'result': {'count': 26}}
{'_id': 701, 'done': True, 'result': {'count': 17}}
>>> db.test.find_one_and_update(
... {'done': True},
... {'$set': {'final': True}},
... sort=[('_id', pymongo.DESCENDING)])
{u'_id': 701, u'done': True, u'result': {u'count': 17}}
{'_id': 701, 'done': True, 'result': {'count': 17}}
:Parameters:
- `filter`: A query that matches the document to update.

View File

@ -234,8 +234,7 @@ def validate_non_negative_integer_or_none(option, value):
def validate_string(option, value):
"""Validates that 'value' is an instance of `basestring` for Python 2
or `str` for Python 3.
"""Validates that 'value' is an instance of `str`.
"""
if isinstance(value, str):
return value

View File

@ -142,10 +142,6 @@ def decompress(data, compressor_id):
# https://github.com/andrix/python-snappy/issues/65
# This only matters when data is a memoryview since
# id(bytes(data)) == id(data) when data is a bytes.
# NOTE: bytes(memoryview) returns the memoryview repr
# in Python 2.7. The right thing to do in 2.7 is call
# memoryview.tobytes(), but we currently only use
# memoryview in Python 3.x.
return snappy.uncompress(bytes(data))
elif compressor_id == ZlibContext.compressor_id:
return zlib.decompress(data)

View File

@ -364,12 +364,12 @@ class ClientEncryption(object):
These are the Azure Active Directory credentials used to
generate Azure Key Vault messages.
- `gcp`: Map with "email" as a string and "privateKey"
as `bytes` or a base64 encoded string (unicode on Python 2).
as `bytes` or a base64 encoded string.
Additionally, "endpoint" may also be specified as a string
(defaults to 'oauth2.googleapis.com'). These are the
credentials used to generate Google Cloud KMS messages.
- `local`: Map with "key" as `bytes` (96 bytes in length) or
a base64 encoded string (unicode on Python 2) which decodes
a base64 encoded string which decodes
to 96 bytes. "key" is the master key used to encrypt/decrypt
data keys. This key should be generated and stored as securely
as possible.

View File

@ -65,12 +65,12 @@ class AutoEncryptionOpts(object):
These are the Azure Active Directory credentials used to
generate Azure Key Vault messages.
- `gcp`: Map with "email" as a string and "privateKey"
as `bytes` or a base64 encoded string (unicode on Python 2).
as `bytes` or a base64 encoded string.
Additionally, "endpoint" may also be specified as a string
(defaults to 'oauth2.googleapis.com'). These are the
credentials used to generate Google Cloud KMS messages.
- `local`: Map with "key" as `bytes` (96 bytes in length) or
a base64 encoded string (unicode on Python 2) which decodes
a base64 encoded string which decodes
to 96 bytes. "key" is the master key used to encrypt/decrypt
data keys. This key should be generated and stored as securely
as possible.

View File

@ -1574,8 +1574,6 @@ class _OpReply(object):
# PYTHON-945: ignore starting_from field.
flags, cursor_id, _, number_returned = cls.UNPACK_FROM(msg)
# Convert Python 3 memoryview to bytes. Note we should call
# memoryview.tobytes() if we start using memoryview in Python 2.7.
documents = bytes(msg[20:])
return cls(flags, cursor_id, number_returned, documents)
@ -1649,8 +1647,6 @@ class _OpMsg(object):
if len(msg) != first_payload_size + 5:
raise ProtocolError("Unsupported OP_MSG reply: >1 section")
# Convert Python 3 memoryview to bytes. Note we should call
# memoryview.tobytes() if we start using memoryview in Python 2.7.
payload_document = bytes(msg[5:])
return cls(flags, payload_document)
@ -1699,17 +1695,17 @@ def _first_batch(sock_info, db, coll, query, ntoreturn,
# listIndexes
if 'cursor' in cmd:
result = {
u'cursor': {
u'firstBatch': docs,
u'id': reply.cursor_id,
u'ns': u'%s.%s' % (db, coll)
'cursor': {
'firstBatch': docs,
'id': reply.cursor_id,
'ns': '%s.%s' % (db, coll)
},
u'ok': 1.0
'ok': 1.0
}
# fsyncUnlock, currentOp
else:
result = docs[0] if docs else {}
result[u'ok'] = 1.0
result['ok'] = 1.0
if publish:
duration = (datetime.datetime.now() - start) + encoding_duration
listeners.publish_command_success(

View File

@ -26,9 +26,9 @@ access:
>>> from pymongo import MongoClient
>>> c = MongoClient()
>>> c.test_database
Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), u'test_database')
Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'test_database')
>>> c['test-database']
Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), u'test-database')
Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'test-database')
"""
import contextlib
@ -118,12 +118,7 @@ class MongoClient(common.BaseObject):
passwords reserved characters like ':', '/', '+' and '@' must be
percent encoded following RFC 2396::
try:
# Python 3.x
from urllib.parse import quote_plus
except ImportError:
# Python 2.x
from urllib import quote_plus
from urllib.parse import quote_plus
uri = "mongodb://%s:%s@%s" % (
quote_plus(user), quote_plus(password), host)

View File

@ -225,7 +225,7 @@ else:
# while the main thread holds the import lock, getaddrinfo deadlocks trying
# to import the IDNA codec. Import it here, where presumably we're on the
# main thread, to avoid the deadlock. See PYTHON-607.
u'foo'.encode('idna')
'foo'.encode('idna')
def _raise_connection_failure(address, error, msg_prefix=None):

View File

@ -49,8 +49,8 @@ else:
:Parameters:
- `data`: The string to SASLprep. Unicode strings
(python 2.x unicode, 3.x str) are supported. Byte strings
(python 2.x str, 3.x bytes) are ignored.
(:class:`str`) are supported. Byte strings
(:class:`bytes`) are ignored.
- `prohibit_unassigned_code_points`: True / False. RFC 3454
and RFCs for various SASL mechanisms distinguish between
`queries` (unassigned code points allowed) and

View File

@ -72,8 +72,6 @@ class Selection(object):
def __bool__(self):
return bool(self.server_descriptions)
__nonzero__ = __bool__ # Python 2.
def __getitem__(self, item):
return self.server_descriptions[item]

View File

@ -19,114 +19,26 @@ import sys as _sys
# PROTOCOL_TLS_CLIENT is Python 3.6+
PROTOCOL_SSLv23 = getattr(_ssl, "PROTOCOL_TLS_CLIENT", _ssl.PROTOCOL_SSLv23)
# Python 2.7.9+
OP_NO_SSLv2 = getattr(_ssl, "OP_NO_SSLv2", 0)
# Python 2.7.9+
OP_NO_SSLv3 = getattr(_ssl, "OP_NO_SSLv3", 0)
# Python 2.7.9+, OpenSSL 1.0.0+
OP_NO_COMPRESSION = getattr(_ssl, "OP_NO_COMPRESSION", 0)
# Python 3.7+, OpenSSL 1.1.0h+
OP_NO_RENEGOTIATION = getattr(_ssl, "OP_NO_RENEGOTIATION", 0)
# Python 2.7.9+
HAS_SNI = getattr(_ssl, "HAS_SNI", False)
IS_PYOPENSSL = False
# Base Exception class
SSLError = _ssl.SSLError
try:
# CPython 2.7.9+
from ssl import SSLContext
if hasattr(_ssl, "VERIFY_CRL_CHECK_LEAF"):
from ssl import VERIFY_CRL_CHECK_LEAF
# Python 3.7 uses OpenSSL's hostname matching implementation
# making it the obvious version to start using SSLConext.check_hostname.
# Python 3.6 might have been a good version, but it suffers
# from https://bugs.python.org/issue32185.
# We'll use our bundled match_hostname for older Python
# versions, which also supports IP address matching
# with Python < 3.5.
CHECK_HOSTNAME_SAFE = _sys.version_info[:2] >= (3, 7)
except ImportError:
from pymongo.errors import ConfigurationError
class SSLContext(object):
"""A fake SSLContext.
This implements an API similar to ssl.SSLContext from python 3.2
but does not implement methods or properties that would be
incompatible with ssl.wrap_socket from python 2.7 < 2.7.9.
You must pass protocol which must be one of the PROTOCOL_* constants
defined in the ssl module. ssl.PROTOCOL_SSLv23 is recommended for maximum
interoperability.
"""
__slots__ = ('_cafile', '_certfile',
'_keyfile', '_protocol', '_verify_mode')
def __init__(self, protocol):
self._cafile = None
self._certfile = None
self._keyfile = None
self._protocol = protocol
self._verify_mode = _ssl.CERT_NONE
@property
def protocol(self):
"""The protocol version chosen when constructing the context.
This attribute is read-only.
"""
return self._protocol
def __get_verify_mode(self):
"""Whether to try to verify other peers' certificates and how to
behave if verification fails. This attribute must be one of
ssl.CERT_NONE, ssl.CERT_OPTIONAL or ssl.CERT_REQUIRED.
"""
return self._verify_mode
def __set_verify_mode(self, value):
"""Setter for verify_mode."""
self._verify_mode = value
verify_mode = property(__get_verify_mode, __set_verify_mode)
def load_cert_chain(self, certfile, keyfile=None, password=None):
"""Load a private key and the corresponding certificate. The certfile
string must be the path to a single file in PEM format containing the
certificate as well as any number of CA certificates needed to
establish the certificate's authenticity. The keyfile string, if
present, must point to a file containing the private key. Otherwise
the private key will be taken from certfile as well.
"""
if password is not None:
raise ConfigurationError(
"Support for ssl_pem_passphrase requires "
"python 2.7.9+ (pypy 2.5.1+), python 3 or "
"PyOpenSSL")
self._certfile = certfile
self._keyfile = keyfile
def load_verify_locations(self, cafile=None, dummy=None):
"""Load a set of "certification authority"(CA) certificates used to
validate other peers' certificates when `~verify_mode` is other than
ssl.CERT_NONE.
"""
self._cafile = cafile
def wrap_socket(self, sock, server_side=False,
do_handshake_on_connect=True,
suppress_ragged_eofs=True, dummy=None):
"""Wrap an existing Python socket sock and return an ssl.SSLSocket
object.
"""
return _ssl.wrap_socket(sock, keyfile=self._keyfile,
certfile=self._certfile,
server_side=server_side,
cert_reqs=self._verify_mode,
ssl_version=self._protocol,
ca_certs=self._cafile,
do_handshake_on_connect=do_handshake_on_connect,
suppress_ragged_eofs=suppress_ragged_eofs)
from ssl import SSLContext
if hasattr(_ssl, "VERIFY_CRL_CHECK_LEAF"):
from ssl import VERIFY_CRL_CHECK_LEAF
# Python 3.7 uses OpenSSL's hostname matching implementation
# making it the obvious version to start using SSLConext.check_hostname.
# Python 3.6 might have been a good version, but it suffers
# from https://bugs.python.org/issue32185.
# We'll use our bundled match_hostname for older Python
# versions, which also supports IP address matching
# with Python < 3.5.
CHECK_HOSTNAME_SAFE = _sys.version_info[:2] >= (3, 7)

View File

@ -145,46 +145,6 @@ class doc(Command):
raise RuntimeError(
"You must install Sphinx to build or test the documentation.")
# TODO: Convert all the docs to Python 3 and delete all this.
import doctest
from doctest import OutputChecker as _OutputChecker
# Match u or U (possibly followed by r or R), removing it.
# r/R can follow u/U but not precede it. Don't match the
# single character string 'u' or 'U'.
_u_literal_re = re.compile(
r"(\W|^)(?<![\'\"])[uU]([rR]?[\'\"])", re.UNICODE)
# Match b or B (possibly followed by r or R), removing.
# r/R can follow b/B but not precede it. Don't match the
# single character string 'b' or 'B'.
_b_literal_re = re.compile(
r"(\W|^)(?<![\'\"])[bB]([rR]?[\'\"])", re.UNICODE)
class _StringPrefixFixer(_OutputChecker):
def check_output(self, want, got, optionflags):
# The docstrings are written with python 2.x in mind.
# To make the doctests pass in python 3 we have to
# strip the 'u' prefix from the expected results. The
# actual results won't have that prefix.
want = re.sub(_u_literal_re, r'\1\2', want)
# We also have to strip the 'b' prefix from the actual
# results since python 2.x expected results won't have
# that prefix.
got = re.sub(_b_literal_re, r'\1\2', got)
return super(
_StringPrefixFixer, self).check_output(
want, got, optionflags)
def output_difference(self, example, got, optionflags):
example.want = re.sub(_u_literal_re, r'\1\2', example.want)
got = re.sub(_b_literal_re, r'\1\2', got)
return super(
_StringPrefixFixer, self).output_difference(
example, got, optionflags)
doctest.OutputChecker = _StringPrefixFixer
if self.test:
path = os.path.join(
os.path.abspath('.'), "doc", "_build", "doctest")

View File

@ -18,11 +18,7 @@ import os
import sys
import threading
try:
from urllib.parse import quote_plus
except ImportError:
# Python 2
from urllib import quote_plus
from urllib.parse import quote_plus
sys.path[0:0] = [""]
@ -516,63 +512,63 @@ class TestSCRAM(unittest.TestCase):
if HAVE_STRINGPREP:
# Test the use of SASLprep on passwords. For example,
# saslprep(u'\u2136') becomes u'IV' and saslprep(u'I\u00ADX')
# becomes u'IX'. SASLprep is only supported when the standard
# saslprep('\u2136') becomes 'IV' and saslprep('I\u00ADX')
# becomes 'IX'. SASLprep is only supported when the standard
# library provides stringprep.
client_context.create_user(
'testscram',
u'\u2168',
u'\u2163',
'\u2168',
'\u2163',
roles=['dbOwner'],
mechanisms=['SCRAM-SHA-256'])
client_context.create_user(
'testscram',
u'IX',
u'IX',
'IX',
'IX',
roles=['dbOwner'],
mechanisms=['SCRAM-SHA-256'])
self.assertTrue(
client.testscram.authenticate(u'\u2168', u'\u2163'))
client.testscram.authenticate('\u2168', '\u2163'))
client.testscram.command('dbstats')
client.testscram.logout()
self.assertTrue(
client.testscram.authenticate(
u'\u2168', u'\u2163', mechanism='SCRAM-SHA-256'))
'\u2168', '\u2163', mechanism='SCRAM-SHA-256'))
client.testscram.command('dbstats')
client.testscram.logout()
self.assertTrue(
client.testscram.authenticate(u'\u2168', u'IV'))
client.testscram.authenticate('\u2168', 'IV'))
client.testscram.command('dbstats')
client.testscram.logout()
self.assertTrue(
client.testscram.authenticate(u'IX', u'I\u00ADX'))
client.testscram.authenticate('IX', 'I\u00ADX'))
client.testscram.command('dbstats')
client.testscram.logout()
self.assertTrue(
client.testscram.authenticate(
u'IX', u'I\u00ADX', mechanism='SCRAM-SHA-256'))
'IX', 'I\u00ADX', mechanism='SCRAM-SHA-256'))
client.testscram.command('dbstats')
client.testscram.logout()
self.assertTrue(
client.testscram.authenticate(u'IX', u'IX'))
client.testscram.authenticate('IX', 'IX'))
client.testscram.command('dbstats')
client.testscram.logout()
client = rs_or_single_client_noauth(
u'mongodb://\u2168:\u2163@%s:%d/testscram' % (host, port))
'mongodb://\u2168:\u2163@%s:%d/testscram' % (host, port))
client.testscram.command('dbstats')
client = rs_or_single_client_noauth(
u'mongodb://\u2168:IV@%s:%d/testscram' % (host, port))
'mongodb://\u2168:IV@%s:%d/testscram' % (host, port))
client.testscram.command('dbstats')
client = rs_or_single_client_noauth(
u'mongodb://IX:I\u00ADX@%s:%d/testscram' % (host, port))
'mongodb://IX:I\u00ADX@%s:%d/testscram' % (host, port))
client.testscram.command('dbstats')
client = rs_or_single_client_noauth(
u'mongodb://IX:IX@%s:%d/testscram' % (host, port))
'mongodb://IX:IX@%s:%d/testscram' % (host, port))
client.testscram.command('dbstats')
self.listener.results.clear()

View File

@ -97,10 +97,7 @@ class TestBinary(unittest.TestCase):
self.assertRaises(ValueError, Binary, b"hello", 256)
self.assertTrue(Binary(b"hello", 0))
self.assertTrue(Binary(b"hello", 255))
if platform.python_implementation() != "Jython":
# Jython's memoryview accepts unicode strings...
# https://bugs.jython.org/issue2784
self.assertRaises(TypeError, Binary, "hello")
self.assertRaises(TypeError, Binary, "hello")
def test_subtype(self):
one = Binary(b"hello")

View File

@ -147,8 +147,8 @@ class TestBSON(unittest.TestCase):
helper({"a binary": Binary(b"test", 128)})
helper({"a binary": Binary(b"test", 254)})
helper({"another binary": Binary(b"test", 2)})
helper(SON([(u'test dst', datetime.datetime(1993, 4, 4, 2))]))
helper(SON([(u'test negative dst',
helper(SON([('test dst', datetime.datetime(1993, 4, 4, 2))]))
helper(SON([('test negative dst',
datetime.datetime(1, 1, 1, 1, 1, 1))]))
helper({"big float": float(10000000000)})
helper({"ref": DBRef("coll", 5)})
@ -653,7 +653,7 @@ class TestBSON(unittest.TestCase):
# The C extension was segfaulting on unicode RegExs, so we have this test
# that doesn't really test anything but the lack of a segfault.
def test_unicode_regex(self):
regex = re.compile(u'revisi\xf3n')
regex = re.compile('revisi\xf3n')
decode(encode({"regex": regex}))
def test_non_string_keys(self):
@ -675,9 +675,6 @@ class TestBSON(unittest.TestCase):
doc = {"a": "\x00"}
self.assertEqual(doc, decode(encode(doc)))
# This test doesn't make much sense in Python2
# since {'a': '\x00'} == {'a': u'\x00'}.
# Decoding here actually returns {'a': '\x00'}
doc = {"a": "\x00"}
self.assertEqual(doc, decode(encode(doc)))
@ -770,8 +767,8 @@ class TestBSON(unittest.TestCase):
self.assertEqual(doc1, decode(doc1_bson))
# Valid Python regex, with flags.
re2 = re.compile(u'.*', re.I | re.M | re.S | re.U | re.X)
bson_re2 = Regex(u'.*', re.I | re.M | re.S | re.U | re.X)
re2 = re.compile('.*', re.I | re.M | re.S | re.U | re.X)
bson_re2 = Regex('.*', re.I | re.M | re.S | re.U | re.X)
doc2_with_re = {'r': re2}
doc2_with_bson_re = {'r': bson_re2}

View File

@ -54,7 +54,7 @@ class TestCode(unittest.TestCase):
self.assertEqual(str(with_scope), str(another_scope))
self.assertEqual({'new_var': 42, 'my_var': 5}, another_scope.scope)
# No error.
Code(u'héllø world¡')
Code('héllø world¡')
def test_repr(self):
c = Code("hello world", {})

View File

@ -1299,11 +1299,11 @@ class TestCollection(IntegrationTest):
self.addCleanup(coll.drop)
coll.create_index('a', unique=True)
coll.insert_one({'a': u'unicode \U0001f40d'})
coll.insert_one({'a': 'unicode \U0001f40d'})
with self.assertRaisesRegex(
DuplicateKeyError,
'E11000 duplicate key error') as ctx:
coll.insert_one({'a': u'unicode \U0001f40d'})
coll.insert_one({'a': 'unicode \U0001f40d'})
# Once more for good measure.
self.assertIn('E11000 duplicate key error',

View File

@ -857,7 +857,6 @@ class TestCursor(IntegrationTest):
self.assertEqual(3, db.test.find().where('this.x < 3').count())
self.assertEqual(10, db.test.find().count())
self.assertEqual(3, db.test.find().where(u'this.x < 3').count())
self.assertEqual([0, 1, 2],
[a["x"] for a in
db.test.find().where('this.x < 3')])

View File

@ -61,7 +61,7 @@ class TestDBRef(unittest.TestCase):
self.assertEqual(repr(DBRef("coll",
ObjectId("1234567890abcdef12345678"))),
"DBRef(%s, ObjectId('1234567890abcdef12345678'))"
% (repr(u'coll'),)
% (repr('coll'),)
)
self.assertEqual(repr(DBRef("coll", 5, foo="bar")),
"DBRef('coll', 5, foo='bar')")

View File

@ -38,7 +38,6 @@ from pymongo.topology_description import TOPOLOGY_TYPE
from pymongo.uri_parser import parse_uri
from test import unittest, IntegrationTest
from test.utils import (assertion_context,
cdecimal_patched,
CMAPListener,
client_context,
get_pool,
@ -358,15 +357,6 @@ class TestIntegration(SpecRunner):
event_type = getattr(monitoring, event)
return self.pool_listener.event_count(event_type)
def maybe_skip_scenario(self, test):
"""Override to skip threaded tests when cdecimal is installed on 2.7
"""
super(TestIntegration, self).maybe_skip_scenario(test)
# PYTHON-2332
ops = [op['name'] for op in test['operations']]
if cdecimal_patched() and 'startThread' in ops:
raise unittest.SkipTest('PYTHON-2332 test fails with cdecimal')
def assert_event_count(self, event, count):
"""Run the assertEventCount test operation.

View File

@ -58,13 +58,13 @@ class TestErrors(PyMongoTestCase):
self.assertIn("full error", traceback.format_exc())
def test_unicode_strs_operation_failure(self):
exc = OperationFailure(u'unicode \U0001f40d', 10,
{"errmsg": u'unicode \U0001f40d'})
exc = OperationFailure('unicode \U0001f40d', 10,
{"errmsg": 'unicode \U0001f40d'})
self._test_unicode_strs(exc)
def test_unicode_strs_not_master_error(self):
exc = NotMasterError(u'unicode \U0001f40d',
{"errmsg": u'unicode \U0001f40d'})
exc = NotMasterError('unicode \U0001f40d',
{"errmsg": 'unicode \U0001f40d'})
self._test_unicode_strs(exc)
def assertPyMongoErrorEqual(self, exc1, exc2):

View File

@ -30,9 +30,9 @@ from test.test_client import IntegrationTest
class TestRawBSONDocument(IntegrationTest):
# {u'_id': ObjectId('556df68b6e32ab21a95e0785'),
# u'name': u'Sherlock',
# u'addresses': [{u'street': u'Baker Street'}]}
# {'_id': ObjectId('556df68b6e32ab21a95e0785'),
# 'name': 'Sherlock',
# 'addresses': [{'street': 'Baker Street'}]}
bson_string = (
b'Z\x00\x00\x00\x07_id\x00Um\xf6\x8bn2\xab!\xa9^\x07\x85\x02name\x00\t'
b'\x00\x00\x00Sherlock\x00\x04addresses\x00&\x00\x00\x00\x030\x00\x1e'
@ -99,8 +99,8 @@ class TestRawBSONDocument(IntegrationTest):
self.assertEqual(raw_coll.find_one(), raw)
def test_with_codec_options(self):
# {u'date': datetime.datetime(2015, 6, 3, 18, 40, 50, 826000),
# u'_id': UUID('026fab8f-975f-4965-9fbf-85ad874c60ff')}
# {'date': datetime.datetime(2015, 6, 3, 18, 40, 50, 826000),
# '_id': UUID('026fab8f-975f-4965-9fbf-85ad874c60ff')}
# encoded with JAVA_LEGACY uuid representation.
bson_string = (
b'-\x00\x00\x00\x05_id\x00\x10\x00\x00\x00\x03eI_\x97\x8f\xabo\x02'
@ -143,8 +143,8 @@ class TestRawBSONDocument(IntegrationTest):
# Make sure that CodecOptions are preserved.
# {'embedded': [
# {u'date': datetime.datetime(2015, 6, 3, 18, 40, 50, 826000),
# u'_id': UUID('026fab8f-975f-4965-9fbf-85ad874c60ff')}
# {'date': datetime.datetime(2015, 6, 3, 18, 40, 50, 826000),
# '_id': UUID('026fab8f-975f-4965-9fbf-85ad874c60ff')}
# ]}
# encoded with JAVA_LEGACY uuid representation.
bson_string = (

View File

@ -20,11 +20,7 @@ import sys
sys.path[0:0] = [""]
try:
from urllib.parse import quote_plus
except ImportError:
# Python 2
from urllib import quote_plus
from urllib.parse import quote_plus
from pymongo import MongoClient, ssl_support
from pymongo.errors import (ConfigurationError,

View File

@ -890,16 +890,6 @@ def is_greenthread_patched():
return gevent_monkey_patched() or eventlet_monkey_patched()
def cdecimal_patched():
"""Check if Python 2.7 cdecimal patching is active."""
try:
import decimal
import cdecimal
return decimal is cdecimal
except ImportError:
return False
def disable_replication(client):
"""Disable replication on all secondaries, requires MongoDB 3.2."""
for host, port in client.secondaries: