diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 4c057af57..b191debce 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -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 diff --git a/README.rst b/README.rst index 0341ae44b..20c3b56e7 100644 --- a/README.rst +++ b/README.rst @@ -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"]) ... diff --git a/bson/__init__.py b/bson/__init__.py index 0f63929bb..05482d913 100644 --- a/bson/__init__.py +++ b/bson/__init__.py @@ -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: diff --git a/bson/dbref.py b/bson/dbref.py index 9ef842a31..f4395b76c 100644 --- a/bson/dbref.py +++ b/bson/dbref.py @@ -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 diff --git a/bson/json_util.py b/bson/json_util.py index a702ea988..7b42d5668 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -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) diff --git a/bson/objectid.py b/bson/objectid.py index ef601e848..6129df35b 100644 --- a/bson/objectid.py +++ b/bson/objectid.py @@ -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: diff --git a/bson/son.py b/bson/son.py index bef655c8f..c6baaa98c 100644 --- a/bson/son.py +++ b/bson/son.py @@ -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): diff --git a/doc/conf.py b/doc/conf.py index c545ab093..381abb299 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -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 diff --git a/doc/examples/aggregation.rst b/doc/examples/aggregation.rst index f816eed6e..a2a7214b3 100644 --- a/doc/examples/aggregation.rst +++ b/doc/examples/aggregation.rst @@ -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 `_ diff --git a/doc/examples/authentication.rst b/doc/examples/authentication.rst index 4989e6a91..d0a1fba15 100644 --- a/doc/examples/authentication.rst +++ b/doc/examples/authentication.rst @@ -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 diff --git a/doc/examples/bulk.rst b/doc/examples/bulk.rst index 019817fbd..9e8a57a80 100644 --- a/doc/examples/bulk.rst +++ b/doc/examples/bulk.rst @@ -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': []} diff --git a/doc/examples/custom_type.rst b/doc/examples/custom_type.rst index 591a250b6..404a6c8b5 100644 --- a/doc/examples/custom_type.rst +++ b/doc/examples/custom_type.rst @@ -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``: >>> 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`: >>> mydoc = collection.find_one() >>> pprint.pprint(mydoc) - {u'_id': ObjectId('...'), u'num': Decimal128('45.321')} + {'_id': ObjectId('...'), 'num': Decimal128('45.321')} .. note:: diff --git a/doc/examples/geo.rst b/doc/examples/geo.rst index 26de95cb6..5caa36eaf 100644 --- a/doc/examples/geo.rst +++ b/doc/examples/geo.rst @@ -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. diff --git a/doc/examples/gridfs.rst b/doc/examples/gridfs.rst index db55bd2b5..a015f6a9f 100644 --- a/doc/examples/gridfs.rst +++ b/doc/examples/gridfs.rst @@ -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(...) diff --git a/doc/examples/high_availability.rst b/doc/examples/high_availability.rst index b52b22a10..6a72ba75b 100644 --- a/doc/examples/high_availability.rst +++ b/doc/examples/high_availability.rst @@ -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) diff --git a/doc/examples/tls.rst b/doc/examples/tls.rst index 780dec393..07351dc9d 100644 --- a/doc/examples/tls.rst +++ b/doc/examples/tls.rst @@ -16,30 +16,6 @@ command:: $ python -m pip install pymongo[tls] -Starting with PyMongo 3.11 this installs `PyOpenSSL -`_, `requests`_ -and `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, diff --git a/doc/faq.rst b/doc/faq.rst index dc9973e27..89d8e8a7f 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -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 diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 3e77ab1d8..2ec6c44da 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -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 `_. BSON strings are -UTF-8 encoded so PyMongo must ensure that any strings it stores contain only -valid UTF-8 data. Regular strings () are validated and stored -unaltered. Unicode strings () 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 -`_. - 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 diff --git a/gridfs/__init__.py b/gridfs/__init__.py index ab8a5a02b..c12f93f1d 100644 --- a/gridfs/__init__.py +++ b/gridfs/__init__.py @@ -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. diff --git a/gridfs/grid_file.py b/gridfs/grid_file.py index e49ca472a..5a48fee17 100644 --- a/gridfs/grid_file.py +++ b/gridfs/grid_file.py @@ -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. diff --git a/pymongo/collection.py b/pymongo/collection.py index 5314b6e26..97787f3d6 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -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. diff --git a/pymongo/common.py b/pymongo/common.py index 9e61c328c..a36cf6d6b 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -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 diff --git a/pymongo/compression_support.py b/pymongo/compression_support.py index b6662f22f..2dd54e7f7 100644 --- a/pymongo/compression_support.py +++ b/pymongo/compression_support.py @@ -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) diff --git a/pymongo/encryption.py b/pymongo/encryption.py index f3aa2aa28..a1edf45dc 100644 --- a/pymongo/encryption.py +++ b/pymongo/encryption.py @@ -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. diff --git a/pymongo/encryption_options.py b/pymongo/encryption_options.py index e45a3f94d..da3a0f191 100644 --- a/pymongo/encryption_options.py +++ b/pymongo/encryption_options.py @@ -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. diff --git a/pymongo/message.py b/pymongo/message.py index eb2a5fb7e..25de5a72f 100644 --- a/pymongo/message.py +++ b/pymongo/message.py @@ -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( diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index ede6454c1..a9fa28228 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -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) diff --git a/pymongo/pool.py b/pymongo/pool.py index 472a9bf04..e92df9b59 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -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): diff --git a/pymongo/saslprep.py b/pymongo/saslprep.py index 3a4284feb..08a780c05 100644 --- a/pymongo/saslprep.py +++ b/pymongo/saslprep.py @@ -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 diff --git a/pymongo/server_selectors.py b/pymongo/server_selectors.py index 01f13065c..cc18450ad 100644 --- a/pymongo/server_selectors.py +++ b/pymongo/server_selectors.py @@ -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] diff --git a/pymongo/ssl_context.py b/pymongo/ssl_context.py index 6ca28a58b..5bcbc5d9c 100644 --- a/pymongo/ssl_context.py +++ b/pymongo/ssl_context.py @@ -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) diff --git a/setup.py b/setup.py index dc54b0704..18ac6ad54 100755 --- a/setup.py +++ b/setup.py @@ -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|^)(?