diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f0ee74c78..d8455981f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,6 +30,13 @@ repos: files: \.py$ args: [--profile=black] +- repo: https://github.com/adamchainz/blacken-docs + rev: "1.13.0" + hooks: + - id: blacken-docs + additional_dependencies: + - black==22.3.0 + - repo: https://github.com/PyCQA/flake8 rev: 3.9.2 hooks: diff --git a/README.rst b/README.rst index 530829f95..bb409a94f 100644 --- a/README.rst +++ b/README.rst @@ -148,7 +148,7 @@ Examples ======== Here's a basic example (for more see the *examples* section of the docs): -.. code-block:: python +.. code-block:: pycon >>> import pymongo >>> client = pymongo.MongoClient("localhost", 27017) diff --git a/bson/json_util.py b/bson/json_util.py index ae464e4ed..8842d5c74 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -29,7 +29,9 @@ Example usage (deserialization): .. doctest:: >>> 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=="}}]') + >>> loads( + ... '[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$scope": {}, "$code": "function x() { return 1; }"}}, {"bin": {"$type": "80", "$binary": "AQIDBA=="}}]' + ... ) [{'foo': [1, 2]}, {'bar': {'hello': 'world'}}, {'code': Code('function x() { return 1; }', {})}, {'bin': Binary(b'...', 128)}] Example usage with :const:`RELAXED_JSON_OPTIONS` (the default): @@ -38,10 +40,14 @@ Example usage with :const:`RELAXED_JSON_OPTIONS` (the default): >>> from bson import Binary, Code >>> from bson.json_util import dumps - >>> dumps([{'foo': [1, 2]}, - ... {'bar': {'hello': 'world'}}, - ... {'code': Code("function x() { return 1; }")}, - ... {'bin': Binary(b"\x01\x02\x03\x04")}]) + >>> dumps( + ... [ + ... {"foo": [1, 2]}, + ... {"bar": {"hello": "world"}}, + ... {"code": Code("function x() { return 1; }")}, + ... {"bin": Binary(b"\x01\x02\x03\x04")}, + ... ] + ... ) '[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }"}}, {"bin": {"$binary": {"base64": "AQIDBA==", "subType": "00"}}}]' Example usage (with :const:`CANONICAL_JSON_OPTIONS`): @@ -50,11 +56,15 @@ Example usage (with :const:`CANONICAL_JSON_OPTIONS`): >>> from bson import Binary, Code >>> from bson.json_util import dumps, CANONICAL_JSON_OPTIONS - >>> dumps([{'foo': [1, 2]}, - ... {'bar': {'hello': 'world'}}, - ... {'code': Code("function x() { return 1; }")}, - ... {'bin': Binary(b"\x01\x02\x03\x04")}], - ... json_options=CANONICAL_JSON_OPTIONS) + >>> dumps( + ... [ + ... {"foo": [1, 2]}, + ... {"bar": {"hello": "world"}}, + ... {"code": Code("function x() { return 1; }")}, + ... {"bin": Binary(b"\x01\x02\x03\x04")}, + ... ], + ... json_options=CANONICAL_JSON_OPTIONS, + ... ) '[{"foo": [{"$numberInt": "1"}, {"$numberInt": "2"}]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }"}}, {"bin": {"$binary": {"base64": "AQIDBA==", "subType": "00"}}}]' Example usage (with :const:`LEGACY_JSON_OPTIONS`): @@ -63,11 +73,15 @@ Example usage (with :const:`LEGACY_JSON_OPTIONS`): >>> from bson import Binary, Code >>> from bson.json_util import dumps, LEGACY_JSON_OPTIONS - >>> dumps([{'foo': [1, 2]}, - ... {'bar': {'hello': 'world'}}, - ... {'code': Code("function x() { return 1; }", {})}, - ... {'bin': Binary(b"\x01\x02\x03\x04")}], - ... json_options=LEGACY_JSON_OPTIONS) + >>> dumps( + ... [ + ... {"foo": [1, 2]}, + ... {"bar": {"hello": "world"}}, + ... {"code": Code("function x() { return 1; }", {})}, + ... {"bin": Binary(b"\x01\x02\x03\x04")}, + ... ], + ... json_options=LEGACY_JSON_OPTIONS, + ... ) '[{"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": {"$code": "function x() { return 1; }", "$scope": {}}}, {"bin": {"$binary": "AQIDBA==", "$type": "00"}}]' Alternatively, you can manually pass the `default` to :func:`json.dumps`. diff --git a/bson/raw_bson.py b/bson/raw_bson.py index 6a80ea70c..2c2b3c97c 100644 --- a/bson/raw_bson.py +++ b/bson/raw_bson.py @@ -25,18 +25,18 @@ Example: Moving a document between different databases/collections >>> from pymongo import MongoClient >>> from bson.raw_bson import RawBSONDocument >>> client = MongoClient(document_class=RawBSONDocument) - >>> client.drop_database('db') - >>> client.drop_database('replica_db') + >>> client.drop_database("db") + >>> client.drop_database("replica_db") >>> db = client.db - >>> result = db.test.insert_many([{'_id': 1, 'a': 1}, - ... {'_id': 2, 'b': 1}, - ... {'_id': 3, 'c': 1}, - ... {'_id': 4, 'd': 1}]) + >>> result = db.test.insert_many( + ... [{"_id": 1, "a": 1}, {"_id": 2, "b": 1}, {"_id": 3, "c": 1}, {"_id": 4, "d": 1}] + ... ) >>> replica_db = client.replica_db >>> for doc in db.test.find(): - ... print(f"raw document: {doc.raw}") - ... print(f"decoded document: {bson.decode(doc.raw)}") - ... result = replica_db.test.insert_one(doc) + ... print(f"raw document: {doc.raw}") + ... print(f"decoded document: {bson.decode(doc.raw)}") + ... result = replica_db.test.insert_one(doc) + ... raw document: b'...' decoded document: {'_id': 1, 'a': 1} raw document: b'...' diff --git a/doc/examples/aggregation.rst b/doc/examples/aggregation.rst index cdd82ff6f..bd20db230 100644 --- a/doc/examples/aggregation.rst +++ b/doc/examples/aggregation.rst @@ -8,8 +8,9 @@ group method. .. testsetup:: from pymongo import MongoClient + client = MongoClient() - client.drop_database('aggregation_example') + client.drop_database("aggregation_example") Setup ----- @@ -20,10 +21,14 @@ aggregations on: >>> from pymongo import MongoClient >>> db = MongoClient().aggregation_example - >>> result = db.things.insert_many([{"x": 1, "tags": ["dog", "cat"]}, - ... {"x": 2, "tags": ["cat"]}, - ... {"x": 2, "tags": ["mouse", "cat", "dog"]}, - ... {"x": 3, "tags": []}]) + >>> result = db.things.insert_many( + ... [ + ... {"x": 1, "tags": ["dog", "cat"]}, + ... {"x": 2, "tags": ["cat"]}, + ... {"x": 2, "tags": ["mouse", "cat", "dog"]}, + ... {"x": 3, "tags": []}, + ... ] + ... ) >>> result.inserted_ids [ObjectId('...'), ObjectId('...'), ObjectId('...'), ObjectId('...')] @@ -54,7 +59,7 @@ eg "$sort": >>> pipeline = [ ... {"$unwind": "$tags"}, ... {"$group": {"_id": "$tags", "count": {"$sum": 1}}}, - ... {"$sort": SON([("count", -1), ("_id", -1)])} + ... {"$sort": SON([("count", -1), ("_id", -1)])}, ... ] >>> import pprint >>> pprint.pprint(list(db.things.aggregate(pipeline))) diff --git a/doc/examples/bulk.rst b/doc/examples/bulk.rst index 23367dd2c..c2c5acc68 100644 --- a/doc/examples/bulk.rst +++ b/doc/examples/bulk.rst @@ -4,8 +4,9 @@ Bulk Write Operations .. testsetup:: from pymongo import MongoClient + client = MongoClient() - client.drop_database('bulk_example') + client.drop_database("bulk_example") This tutorial explains how to take advantage of PyMongo's bulk write operation features. Executing write operations in batches @@ -27,7 +28,7 @@ bulk insert operations. >>> import pymongo >>> db = pymongo.MongoClient().bulk_example - >>> db.test.insert_many([{'i': i} for i in range(10000)]).inserted_ids + >>> db.test.insert_many([{"i": i} for i in range(10000)]).inserted_ids [...] >>> db.test.count_documents({}) 10000 @@ -56,14 +57,17 @@ of operations performed. >>> from pprint import pprint >>> from pymongo import InsertOne, DeleteMany, ReplaceOne, UpdateOne - >>> result = db.test.bulk_write([ - ... DeleteMany({}), # Remove all documents from the previous example. - ... InsertOne({'_id': 1}), - ... InsertOne({'_id': 2}), - ... InsertOne({'_id': 3}), - ... UpdateOne({'_id': 1}, {'$set': {'foo': 'bar'}}), - ... UpdateOne({'_id': 4}, {'$inc': {'j': 1}}, upsert=True), - ... ReplaceOne({'j': 1}, {'j': 2})]) + >>> result = db.test.bulk_write( + ... [ + ... DeleteMany({}), # Remove all documents from the previous example. + ... InsertOne({"_id": 1}), + ... InsertOne({"_id": 2}), + ... InsertOne({"_id": 3}), + ... UpdateOne({"_id": 1}, {"$set": {"foo": "bar"}}), + ... UpdateOne({"_id": 4}, {"$inc": {"j": 1}}, upsert=True), + ... ReplaceOne({"j": 1}, {"j": 2}), + ... ] + ... ) >>> pprint(result.bulk_api_result) {'nInserted': 3, 'nMatched': 2, @@ -87,9 +91,10 @@ the failure. >>> from pymongo import InsertOne, DeleteOne, ReplaceOne >>> from pymongo.errors import BulkWriteError >>> requests = [ - ... ReplaceOne({'j': 2}, {'i': 5}), - ... InsertOne({'_id': 4}), # Violates the unique key constraint on _id. - ... DeleteOne({'i': 5})] + ... ReplaceOne({"j": 2}, {"i": 5}), + ... InsertOne({"_id": 4}), # Violates the unique key constraint on _id. + ... DeleteOne({"i": 5}), + ... ] >>> try: ... db.test.bulk_write(requests) ... except BulkWriteError as bwe: @@ -124,10 +129,11 @@ and fourth operations succeed. :options: +NORMALIZE_WHITESPACE >>> requests = [ - ... InsertOne({'_id': 1}), - ... DeleteOne({'_id': 2}), - ... InsertOne({'_id': 3}), - ... ReplaceOne({'_id': 4}, {'i': 1})] + ... InsertOne({"_id": 1}), + ... DeleteOne({"_id": 2}), + ... InsertOne({"_id": 3}), + ... ReplaceOne({"_id": 4}, {"i": 1}), + ... ] >>> try: ... db.test.bulk_write(requests, ordered=False) ... except BulkWriteError as bwe: diff --git a/doc/examples/custom_type.rst b/doc/examples/custom_type.rst index 404a6c8b5..cbb2f8515 100644 --- a/doc/examples/custom_type.rst +++ b/doc/examples/custom_type.rst @@ -19,7 +19,7 @@ We'll start by getting a clean database to use for the example: >>> from pymongo import MongoClient >>> client = MongoClient() - >>> client.drop_database('custom_type_example') + >>> client.drop_database("custom_type_example") >>> db = client.custom_type_example @@ -36,7 +36,7 @@ to save an instance of ``Decimal`` with PyMongo, results in an >>> from decimal import Decimal >>> num = Decimal("45.321") - >>> db.test.insert_one({'num': num}) + >>> db.test.insert_one({"num": num}) Traceback (most recent call last): ... bson.errors.InvalidDocument: cannot encode object: Decimal('45.321'), of type: @@ -78,8 +78,8 @@ interested in both encoding and decoding our custom type, we use the >>> from bson.decimal128 import Decimal128 >>> from bson.codec_options import TypeCodec >>> class DecimalCodec(TypeCodec): - ... python_type = Decimal # the Python type acted upon by this type codec - ... bson_type = Decimal128 # the BSON type acted upon by this type codec + ... python_type = Decimal # the Python type acted upon by this type codec + ... bson_type = Decimal128 # the BSON type acted upon by this type codec ... def transform_python(self, value): ... """Function that transforms a custom type value into a type ... that BSON can encode.""" @@ -88,6 +88,7 @@ interested in both encoding and decoding our custom type, we use the ... """Function that transforms a vanilla BSON type value into our ... custom type.""" ... return value.to_decimal() + ... >>> decimal_codec = DecimalCodec() @@ -125,7 +126,7 @@ with our ``type_registry`` and use it to get a >>> from bson.codec_options import CodecOptions >>> codec_options = CodecOptions(type_registry=type_registry) - >>> collection = db.get_collection('test', codec_options=codec_options) + >>> collection = db.get_collection("test", codec_options=codec_options) Now, we can seamlessly encode and decode instances of @@ -133,7 +134,7 @@ Now, we can seamlessly encode and decode instances of .. doctest:: - >>> collection.insert_one({'num': Decimal("45.321")}) + >>> collection.insert_one({"num": Decimal("45.321")}) >>> mydoc = collection.find_one() >>> import pprint @@ -147,7 +148,7 @@ MongoDB: .. doctest:: - >>> vanilla_collection = db.get_collection('test') + >>> vanilla_collection = db.get_collection("test") >>> pprint.pprint(vanilla_collection.find_one()) {'_id': ObjectId('...'), 'num': Decimal128('45.321')} @@ -170,13 +171,14 @@ an integer: ... def my_method(self): ... """Method implementing some custom logic.""" ... return int(self) + ... If we try to save an instance of this type without first registering a type codec for it, we get an error: .. doctest:: - >>> collection.insert_one({'num': DecimalInt("45.321")}) + >>> collection.insert_one({"num": DecimalInt("45.321")}) Traceback (most recent call last): ... bson.errors.InvalidDocument: cannot encode object: Decimal('45.321'), of type: @@ -192,6 +194,7 @@ This is trivial to do since the same transformation as the one used for ... def python_type(self): ... """The Python type acted upon by this type codec.""" ... return DecimalInt + ... >>> decimalint_codec = DecimalIntCodec() @@ -211,9 +214,9 @@ object, we can seamlessly encode instances of ``DecimalInt``: >>> type_registry = TypeRegistry([decimal_codec, decimalint_codec]) >>> codec_options = CodecOptions(type_registry=type_registry) - >>> collection = db.get_collection('test', codec_options=codec_options) + >>> collection = db.get_collection("test", codec_options=codec_options) >>> collection.drop() - >>> collection.insert_one({'num': DecimalInt("45.321")}) + >>> collection.insert_one({"num": DecimalInt("45.321")}) >>> mydoc = collection.find_one() >>> pprint.pprint(mydoc) @@ -236,26 +239,26 @@ writing a ``TypeDecoder`` that modifies how this datatype is decoded. On Python 3.x, :class:`~bson.binary.Binary` data (``subtype = 0``) is decoded as a ``bytes`` instance: -.. code-block:: python +.. code-block:: pycon >>> # On Python 3.x. >>> from bson.binary import Binary - >>> newcoll = db.get_collection('new') - >>> newcoll.insert_one({'_id': 1, 'data': Binary(b"123", subtype=0)}) + >>> newcoll = db.get_collection("new") + >>> newcoll.insert_one({"_id": 1, "data": Binary(b"123", subtype=0)}) >>> doc = newcoll.find_one() - >>> type(doc['data']) + >>> type(doc["data"]) bytes On Python 2.7.x, the same data is decoded as a :class:`~bson.binary.Binary` instance: -.. code-block:: python +.. code-block:: pycon >>> # On Python 2.7.x - >>> newcoll = db.get_collection('new') + >>> newcoll = db.get_collection("new") >>> doc = newcoll.find_one() - >>> type(doc['data']) + >>> type(doc["data"]) bson.binary.Binary @@ -291,6 +294,7 @@ BSON-encodable value. The following fallback encoder encodes python's ... if isinstance(value, Decimal): ... return Decimal128(value) ... return value + ... After declaring the callback, we must create a type registry and codec options with this fallback encoder before it can be used for initializing a collection: @@ -299,14 +303,14 @@ with this fallback encoder before it can be used for initializing a collection: >>> type_registry = TypeRegistry(fallback_encoder=fallback_encoder) >>> codec_options = CodecOptions(type_registry=type_registry) - >>> collection = db.get_collection('test', codec_options=codec_options) + >>> collection = db.get_collection("test", codec_options=codec_options) >>> collection.drop() We can now seamlessly encode instances of :py:class:`~decimal.Decimal`: .. doctest:: - >>> collection.insert_one({'num': Decimal("45.321")}) + >>> collection.insert_one({"num": Decimal("45.321")}) >>> mydoc = collection.find_one() >>> pprint.pprint(mydoc) @@ -343,12 +347,15 @@ We start by defining some arbitrary custom types: class MyStringType(object): def __init__(self, value): self.__value = value + def __repr__(self): return "MyStringType('%s')" % (self.__value,) + class MyNumberType(object): def __init__(self, value): self.__value = value + def __repr__(self): return "MyNumberType(%s)" % (self.__value,) @@ -362,11 +369,15 @@ back into Python objects: import pickle from bson.binary import Binary, USER_DEFINED_SUBTYPE + + def fallback_pickle_encoder(value): return Binary(pickle.dumps(value), USER_DEFINED_SUBTYPE) + class PickledBinaryDecoder(TypeDecoder): bson_type = Binary + def transform_bson(self, value): if value.subtype == USER_DEFINED_SUBTYPE: return pickle.loads(value) @@ -384,19 +395,23 @@ Finally, we create a ``CodecOptions`` instance: .. code-block:: python - codec_options = CodecOptions(type_registry=TypeRegistry( - [PickledBinaryDecoder()], fallback_encoder=fallback_pickle_encoder)) + codec_options = CodecOptions( + type_registry=TypeRegistry( + [PickledBinaryDecoder()], fallback_encoder=fallback_pickle_encoder + ) + ) We can now round trip our custom objects to MongoDB: .. code-block:: python - collection = db.get_collection('test_fe', codec_options=codec_options) - collection.insert_one({'_id': 1, 'str': MyStringType("hello world"), - 'num': MyNumberType(2)}) + collection = db.get_collection("test_fe", codec_options=codec_options) + collection.insert_one( + {"_id": 1, "str": MyStringType("hello world"), "num": MyNumberType(2)} + ) mydoc = collection.find_one() - assert isinstance(mydoc['str'], MyStringType) - assert isinstance(mydoc['num'], MyNumberType) + assert isinstance(mydoc["str"], MyStringType) + assert isinstance(mydoc["num"], MyNumberType) Limitations diff --git a/doc/examples/datetimes.rst b/doc/examples/datetimes.rst index 3b30000ff..562c9480a 100644 --- a/doc/examples/datetimes.rst +++ b/doc/examples/datetimes.rst @@ -6,8 +6,9 @@ Datetimes and Timezones import datetime from pymongo import MongoClient from bson.codec_options import CodecOptions + client = MongoClient() - client.drop_database('dt_example') + client.drop_database("dt_example") db = client.dt_example These examples show how to handle Python :class:`datetime.datetime` objects @@ -24,8 +25,7 @@ time into MongoDB: .. doctest:: - >>> result = db.objects.insert_one( - ... {"last_modified": datetime.datetime.utcnow()}) + >>> result = db.objects.insert_one({"last_modified": datetime.datetime.utcnow()}) Always use :meth:`datetime.datetime.utcnow`, which returns the current time in UTC, instead of :meth:`datetime.datetime.now`, which returns the current local @@ -33,8 +33,7 @@ time. Avoid doing this: .. doctest:: - >>> result = db.objects.insert_one( - ... {"last_modified": datetime.datetime.now()}) + >>> result = db.objects.insert_one({"last_modified": datetime.datetime.now()}) The value for `last_modified` is very different between these two examples, even though both documents were stored at around the same local time. This will be @@ -42,7 +41,7 @@ confusing to the application that reads them: .. doctest:: - >>> [doc['last_modified'] for doc in db.objects.find()] # doctest: +SKIP + >>> [doc["last_modified"] for doc in db.objects.find()] # doctest: +SKIP [datetime.datetime(2015, 7, 8, 18, 17, 28, 324000), datetime.datetime(2015, 7, 8, 11, 17, 42, 911000)] @@ -52,12 +51,11 @@ timezone they're in. By default, PyMongo retrieves naive datetimes: .. doctest:: - >>> result = db.tzdemo.insert_one( - ... {'date': datetime.datetime(2002, 10, 27, 6, 0, 0)}) - >>> db.tzdemo.find_one()['date'] + >>> result = db.tzdemo.insert_one({"date": datetime.datetime(2002, 10, 27, 6, 0, 0)}) + >>> db.tzdemo.find_one()["date"] datetime.datetime(2002, 10, 27, 6, 0) >>> options = CodecOptions(tz_aware=True) - >>> db.get_collection('tzdemo', codec_options=options).find_one()['date'] # doctest: +SKIP + >>> db.get_collection("tzdemo", codec_options=options).find_one()["date"] # doctest: +SKIP datetime.datetime(2002, 10, 27, 6, 0, tzinfo=) @@ -71,11 +69,10 @@ those datetimes to UTC automatically: .. doctest:: >>> import pytz - >>> pacific = pytz.timezone('US/Pacific') - >>> aware_datetime = pacific.localize( - ... datetime.datetime(2002, 10, 27, 6, 0, 0)) + >>> pacific = pytz.timezone("US/Pacific") + >>> aware_datetime = pacific.localize(datetime.datetime(2002, 10, 27, 6, 0, 0)) >>> result = db.times.insert_one({"date": aware_datetime}) - >>> db.times.find_one()['date'] + >>> db.times.find_one()["date"] datetime.datetime(2002, 10, 27, 14, 0) Reading Time @@ -150,7 +147,7 @@ cannot be represented using the builtin Python :class:`~datetime.datetime`: .. doctest:: >>> x = encode({"x": datetime(1970, 1, 1)}) - >>> y = encode({"x": DatetimeMS(-2**62)}) + >>> y = encode({"x": DatetimeMS(-(2**62))}) >>> codec_auto = CodecOptions(datetime_conversion=DatetimeConversion.DATETIME_AUTO) >>> decode(x, codec_options=codec_auto) {'x': datetime.datetime(1970, 1, 1, 0, 0)} @@ -165,7 +162,7 @@ resulting :class:`~datetime.datetime` objects to be within .. doctest:: >>> x = encode({"x": DatetimeMS(2**62)}) - >>> y = encode({"x": DatetimeMS(-2**62)}) + >>> y = encode({"x": DatetimeMS(-(2**62))}) >>> codec_clamp = CodecOptions(datetime_conversion=DatetimeConversion.DATETIME_CLAMP) >>> decode(x, codec_options=codec_clamp) {'x': datetime.datetime(9999, 12, 31, 23, 59, 59, 999000)} diff --git a/doc/examples/geo.rst b/doc/examples/geo.rst index 2234a2075..e7da15672 100644 --- a/doc/examples/geo.rst +++ b/doc/examples/geo.rst @@ -4,8 +4,9 @@ Geospatial Indexing Example .. testsetup:: from pymongo import MongoClient + client = MongoClient() - client.drop_database('geo_example') + client.drop_database("geo_example") This example shows how to create and use a :data:`~pymongo.GEO2D` index in PyMongo. To create a spherical (earth-like) geospatial index use :data:`~pymongo.GEOSPHERE` instead. @@ -33,10 +34,9 @@ insert a couple of example locations: .. doctest:: - >>> result = db.places.insert_many([{"loc": [2, 5]}, - ... {"loc": [30, 5]}, - ... {"loc": [1, 2]}, - ... {"loc": [4, 4]}]) + >>> result = db.places.insert_many( + ... [{"loc": [2, 5]}, {"loc": [30, 5]}, {"loc": [1, 2]}, {"loc": [4, 4]}] + ... ) >>> result.inserted_ids [ObjectId('...'), ObjectId('...'), ObjectId('...'), ObjectId('...')] @@ -51,7 +51,7 @@ Using the geospatial index we can find documents near another point: >>> import pprint >>> for doc in db.places.find({"loc": {"$near": [3, 6]}}).limit(3): - ... pprint.pprint(doc) + ... pprint.pprint(doc) ... {'_id': ObjectId('...'), 'loc': [2, 5]} {'_id': ObjectId('...'), 'loc': [4, 4]} @@ -66,7 +66,7 @@ The $maxDistance operator requires the use of :class:`~bson.son.SON`: >>> from bson.son import SON >>> query = {"loc": SON([("$near", [3, 6]), ("$maxDistance", 100)])} >>> for doc in db.places.find(query).limit(3): - ... pprint.pprint(doc) + ... pprint.pprint(doc) ... {'_id': ObjectId('...'), 'loc': [2, 5]} {'_id': ObjectId('...'), 'loc': [4, 4]} @@ -78,8 +78,9 @@ It's also possible to query for all items within a given rectangle .. doctest:: >>> query = {"loc": {"$within": {"$box": [[2, 2], [5, 6]]}}} - >>> for doc in db.places.find(query).sort('_id'): + >>> for doc in db.places.find(query).sort("_id"): ... pprint.pprint(doc) + ... {'_id': ObjectId('...'), 'loc': [2, 5]} {'_id': ObjectId('...'), 'loc': [4, 4]} @@ -88,8 +89,8 @@ Or circle (specified by center point and radius): .. doctest:: >>> query = {"loc": {"$within": {"$center": [[0, 0], 6]}}} - >>> for doc in db.places.find(query).sort('_id'): - ... pprint.pprint(doc) + >>> for doc in db.places.find(query).sort("_id"): + ... pprint.pprint(doc) ... {'_id': ObjectId('...'), 'loc': [2, 5]} {'_id': ObjectId('...'), 'loc': [1, 2]} diff --git a/doc/examples/gevent.rst b/doc/examples/gevent.rst index 6eb283dca..de3115815 100644 --- a/doc/examples/gevent.rst +++ b/doc/examples/gevent.rst @@ -38,10 +38,12 @@ handler to end background greenlets when your application receives SIGHUP: import signal + def graceful_reload(signum, traceback): """Explicitly close some global MongoClient object.""" client.close() + signal.signal(signal.SIGHUP, graceful_reload) Applications using uWSGI prior to 1.9.16 are affected by this issue, diff --git a/doc/examples/gridfs.rst b/doc/examples/gridfs.rst index a015f6a9f..5f40805d7 100644 --- a/doc/examples/gridfs.rst +++ b/doc/examples/gridfs.rst @@ -4,8 +4,9 @@ GridFS Example .. testsetup:: from pymongo import MongoClient + client = MongoClient() - client.drop_database('gridfs_example') + client.drop_database("gridfs_example") This example shows how to use :mod:`gridfs` to store large binary objects (e.g. files) in MongoDB. diff --git a/doc/examples/server_selection.rst b/doc/examples/server_selection.rst index be2172489..18de677a5 100644 --- a/doc/examples/server_selection.rst +++ b/doc/examples/server_selection.rst @@ -55,12 +55,12 @@ selector function: >>> def server_selector(server_descriptions): ... servers = [ - ... server for server in server_descriptions - ... if server.address[0] == 'localhost' + ... server for server in server_descriptions if server.address[0] == "localhost" ... ] ... if not servers: ... return server_descriptions ... return servers + ... diff --git a/doc/examples/type_hints.rst b/doc/examples/type_hints.rst index e5ad3338e..f202ab32e 100644 --- a/doc/examples/type_hints.rst +++ b/doc/examples/type_hints.rst @@ -81,7 +81,7 @@ Subclasses of :py:class:`collections.abc.Mapping` can also be used, such as :cla >>> from pymongo import MongoClient >>> client = MongoClient(document_class=SON[str, int]) >>> collection = client.test.test - >>> inserted = collection.insert_one({"x": 1, "y": 2 }) + >>> inserted = collection.insert_one({"x": 1, "y": 2}) >>> result = collection.find_one({"x": 1}) >>> assert result is not None >>> assert result["x"] == 1 @@ -103,8 +103,8 @@ These methods automatically add an "_id" field. >>> from pymongo import MongoClient >>> from pymongo.collection import Collection >>> class Movie(TypedDict): - ... name: str - ... year: int + ... name: str + ... year: int ... >>> client: MongoClient = MongoClient() >>> collection: Collection[Movie] = client.test.test @@ -113,7 +113,7 @@ These methods automatically add an "_id" field. >>> assert result is not None >>> assert result["year"] == 1993 >>> # This will raise a type-checking error, despite being present, because it is added by PyMongo. - >>> assert result["_id"] # type:ignore[typeddict-item] + >>> assert result["_id"] # type:ignore[typeddict-item] This same typing scheme works for all of the insert methods (:meth:`~pymongo.collection.Collection.insert_one`, :meth:`~pymongo.collection.Collection.insert_many`, and :meth:`~pymongo.collection.Collection.bulk_write`). @@ -158,18 +158,18 @@ Note: to use :py:class:`~typing.TypedDict` and :py:class:`~typing.NotRequired` i >>> from pymongo.collection import Collection >>> from bson import ObjectId >>> class Movie(TypedDict): - ... name: str - ... year: int + ... name: str + ... year: int ... >>> class ExplicitMovie(TypedDict): - ... _id: ObjectId - ... name: str - ... year: int + ... _id: ObjectId + ... name: str + ... year: int ... >>> class NotRequiredMovie(TypedDict): - ... _id: NotRequired[ObjectId] - ... name: str - ... year: int + ... _id: NotRequired[ObjectId] + ... name: str + ... year: int ... >>> client: MongoClient = MongoClient() >>> collection: Collection[Movie] = client.test.test @@ -180,7 +180,9 @@ Note: to use :py:class:`~typing.TypedDict` and :py:class:`~typing.NotRequired` i >>> assert result["_id"] # type:ignore[typeddict-item] >>> collection: Collection[ExplicitMovie] = client.test.test >>> # Note that the _id keyword argument must be supplied - >>> inserted = collection.insert_one(ExplicitMovie(_id=ObjectId(), name="Jurassic Park", year=1993)) + >>> inserted = collection.insert_one( + ... ExplicitMovie(_id=ObjectId(), name="Jurassic Park", year=1993) + ... ) >>> result = collection.find_one({"name": "Jurassic Park"}) >>> assert result is not None >>> # This will not raise a type-checking error. @@ -207,13 +209,13 @@ match a well-defined schema using :py:class:`~typing.TypedDict` (Python 3.8+). >>> from pymongo import MongoClient >>> from pymongo.database import Database >>> class Movie(TypedDict): - ... name: str - ... year: int + ... name: str + ... year: int ... >>> client: MongoClient = MongoClient() >>> db: Database[Movie] = client.test >>> collection = db.test - >>> inserted = collection.insert_one({"name": "Jurassic Park", "year": 1993 }) + >>> inserted = collection.insert_one({"name": "Jurassic Park", "year": 1993}) >>> result = collection.find_one({"name": "Jurassic Park"}) >>> assert result is not None >>> assert result["year"] == 1993 @@ -244,11 +246,11 @@ You can specify the document type returned by :mod:`bson` decoding functions by >>> from typing import Any, Dict >>> from bson import CodecOptions, encode, decode >>> class MyDict(Dict[str, Any]): - ... def foo(self): - ... return "bar" + ... def foo(self): + ... return "bar" ... >>> options = CodecOptions(document_class=MyDict) - >>> doc = {"x": 1, "y": 2 } + >>> doc = {"x": 1, "y": 2} >>> bsonbytes = encode(doc, codec_options=options) >>> rt_document = decode(bsonbytes, codec_options=options) >>> assert rt_document.foo() == "bar" diff --git a/doc/faq.rst b/doc/faq.rst index 876dc68ed..e64e3c79e 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -244,8 +244,7 @@ Key order in subdocuments -- why does my query work in the shell but not PyMongo collection = MongoClient().test.collection collection.drop() - collection.insert_one({'_id': 1.0, - 'subdocument': SON([('b', 1.0), ('a', 1.0)])}) + collection.insert_one({"_id": 1.0, "subdocument": SON([("b", 1.0), ("a", 1.0)])}) The key-value pairs in a BSON document can have any order (except that ``_id`` is always first). The mongo shell preserves key order when reading and writing @@ -537,6 +536,7 @@ objects as before: >>> for x in client.db.collection.find(): ... print(x) + ... {'_id': ObjectId('...'), 'x': datetime.datetime(1970, 1, 1, 0, 0)} {'_id': ObjectId('...'), 'x': DatetimeMS(4611686018427387904)} diff --git a/doc/migrate-to-pymongo4.rst b/doc/migrate-to-pymongo4.rst index 561261c7a..687fec11b 100644 --- a/doc/migrate-to-pymongo4.rst +++ b/doc/migrate-to-pymongo4.rst @@ -6,6 +6,7 @@ PyMongo 4 Migration Guide .. testsetup:: from pymongo import MongoClient, ReadPreference + client = MongoClient() database = client.my_database collection = database.my_collection diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 55961241e..d7854c885 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -4,8 +4,9 @@ Tutorial .. testsetup:: from pymongo import MongoClient + client = MongoClient() - client.drop_database('test-database') + client.drop_database("test-database") This tutorial is intended as an introduction to working with **MongoDB** and **PyMongo**. @@ -45,13 +46,13 @@ specify the host and port explicitly, as follows: .. doctest:: - >>> client = MongoClient('localhost', 27017) + >>> client = MongoClient("localhost", 27017) Or use the MongoDB URI format: .. doctest:: - >>> client = MongoClient('mongodb://localhost:27017/') + >>> client = MongoClient("mongodb://localhost:27017/") Getting a Database ------------------ @@ -70,7 +71,7 @@ instead: .. doctest:: - >>> db = client['test-database'] + >>> db = client["test-database"] Getting a Collection -------------------- @@ -87,7 +88,7 @@ or (using dictionary style access): .. doctest:: - >>> collection = db['test-collection'] + >>> collection = db["test-collection"] An important note about collections (and databases) in MongoDB is that they are created lazily - none of the above commands have actually @@ -104,10 +105,12 @@ post: .. doctest:: >>> import datetime - >>> post = {"author": "Mike", - ... "text": "My first blog post!", - ... "tags": ["mongodb", "python", "pymongo"], - ... "date": datetime.datetime.utcnow()} + >>> post = { + ... "author": "Mike", + ... "text": "My first blog post!", + ... "tags": ["mongodb", "python", "pymongo"], + ... "date": datetime.datetime.utcnow(), + ... } Note that documents can contain native Python types (like :class:`datetime.datetime` instances) which will be automatically @@ -212,7 +215,7 @@ Note that an ObjectId is not the same as its string representation: .. doctest:: >>> post_id_as_str = str(post_id) - >>> posts.find_one({"_id": post_id_as_str}) # No result + >>> posts.find_one({"_id": post_id_as_str}) # No result >>> A common task in web applications is to get an ObjectId from the @@ -240,14 +243,20 @@ command to the server: .. doctest:: - >>> new_posts = [{"author": "Mike", - ... "text": "Another post!", - ... "tags": ["bulk", "insert"], - ... "date": datetime.datetime(2009, 11, 12, 11, 14)}, - ... {"author": "Eliot", - ... "title": "MongoDB is fun", - ... "text": "and pretty easy too!", - ... "date": datetime.datetime(2009, 11, 10, 10, 45)}] + >>> new_posts = [ + ... { + ... "author": "Mike", + ... "text": "Another post!", + ... "tags": ["bulk", "insert"], + ... "date": datetime.datetime(2009, 11, 12, 11, 14), + ... }, + ... { + ... "author": "Eliot", + ... "title": "MongoDB is fun", + ... "text": "and pretty easy too!", + ... "date": datetime.datetime(2009, 11, 10, 10, 45), + ... }, + ... ] >>> result = posts.insert_many(new_posts) >>> result.inserted_ids [ObjectId('...'), ObjectId('...')] @@ -274,7 +283,7 @@ document in the ``posts`` collection: .. doctest:: >>> for post in posts.find(): - ... pprint.pprint(post) + ... pprint.pprint(post) ... {'_id': ObjectId('...'), 'author': 'Mike', @@ -300,7 +309,7 @@ author is "Mike": .. doctest:: >>> for post in posts.find({"author": "Mike"}): - ... pprint.pprint(post) + ... pprint.pprint(post) ... {'_id': ObjectId('...'), 'author': 'Mike', @@ -343,7 +352,7 @@ than a certain date, but also sort the results by author: >>> d = datetime.datetime(2009, 11, 12, 12) >>> for post in posts.find({"date": {"$lt": d}}).sort("author"): - ... pprint.pprint(post) + ... pprint.pprint(post) ... {'_id': ObjectId('...'), 'author': 'Eliot', @@ -373,8 +382,7 @@ First, we'll need to create the index: .. doctest:: - >>> result = db.profiles.create_index([('user_id', pymongo.ASCENDING)], - ... unique=True) + >>> result = db.profiles.create_index([("user_id", pymongo.ASCENDING)], unique=True) >>> sorted(list(db.profiles.index_information())) ['_id_', 'user_id_1'] @@ -386,9 +394,7 @@ Now let's set up some user profiles: .. doctest:: - >>> user_profiles = [ - ... {'user_id': 211, 'name': 'Luke'}, - ... {'user_id': 212, 'name': 'Ziltoid'}] + >>> user_profiles = [{"user_id": 211, "name": "Luke"}, {"user_id": 212, "name": "Ziltoid"}] >>> result = db.profiles.insert_many(user_profiles) The index prevents us from inserting a document whose ``user_id`` is already in @@ -397,8 +403,8 @@ the collection: .. doctest:: :options: +IGNORE_EXCEPTION_DETAIL - >>> new_profile = {'user_id': 213, 'name': 'Drew'} - >>> duplicate_profile = {'user_id': 212, 'name': 'Tommy'} + >>> new_profile = {"user_id": 213, "name": "Drew"} + >>> duplicate_profile = {"user_id": 212, "name": "Tommy"} >>> result = db.profiles.insert_one(new_profile) # This is fine. >>> result = db.profiles.insert_one(duplicate_profile) Traceback (most recent call last): diff --git a/pymongo/client_session.py b/pymongo/client_session.py index d2479942e..d73672c5b 100644 --- a/pymongo/client_session.py +++ b/pymongo/client_session.py @@ -23,12 +23,11 @@ Causally Consistent Reads with client.start_session(causal_consistency=True) as session: collection = client.db.collection - collection.update_one({'_id': 1}, {'$set': {'x': 10}}, session=session) - secondary_c = collection.with_options( - read_preference=ReadPreference.SECONDARY) + collection.update_one({"_id": 1}, {"$set": {"x": 10}}, session=session) + secondary_c = collection.with_options(read_preference=ReadPreference.SECONDARY) # A secondary read waits for replication of the write. - secondary_c.find_one({'_id': 1}, session=session) + secondary_c.find_one({"_id": 1}, session=session) If `causal_consistency` is True (the default), read operations that use the session are causally after previous read and write operations. Using a @@ -57,8 +56,11 @@ operation: with client.start_session() as session: with session.start_transaction(): orders.insert_one({"sku": "abc123", "qty": 100}, session=session) - inventory.update_one({"sku": "abc123", "qty": {"$gte": 100}}, - {"$inc": {"qty": -100}}, session=session) + inventory.update_one( + {"sku": "abc123", "qty": {"$gte": 100}}, + {"$inc": {"qty": -100}}, + session=session, + ) Upon normal completion of ``with session.start_transaction()`` block, the transaction automatically calls :meth:`ClientSession.commit_transaction`. diff --git a/pymongo/collection.py b/pymongo/collection.py index 4cb3fa79c..7ce881613 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -2533,14 +2533,13 @@ class Collection(common.BaseObject, Generic[_DocumentType]): .. code-block:: python try: - with db.collection.watch( - [{'$match': {'operationType': 'insert'}}]) as stream: + with db.collection.watch([{"$match": {"operationType": "insert"}}]) as stream: for insert_change in stream: print(insert_change) except pymongo.errors.PyMongoError: # The ChangeStream encountered an unrecoverable error or the # resume attempt failed to recreate the cursor. - logging.error('...') + logging.error("...") For a precise description of the resume process see the `change streams specification`_. diff --git a/pymongo/database.py b/pymongo/database.py index b3c6c6085..6a73f884c 100644 --- a/pymongo/database.py +++ b/pymongo/database.py @@ -591,14 +591,13 @@ class Database(common.BaseObject, Generic[_DocumentType]): .. code-block:: python try: - with db.watch( - [{'$match': {'operationType': 'insert'}}]) as stream: + with db.watch([{"$match": {"operationType": "insert"}}]) as stream: for insert_change in stream: print(insert_change) except pymongo.errors.PyMongoError: # The ChangeStream encountered an unrecoverable error or the # resume attempt failed to recreate the cursor. - logging.error('...') + logging.error("...") For a precise description of the resume process see the `change streams specification`_. diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index 05f00b48e..ca60affdf 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -27,7 +27,7 @@ access: >>> c = MongoClient() >>> c.test_database Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'test_database') - >>> c['test-database'] + >>> c["test-database"] Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'test-database') """ @@ -935,14 +935,13 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]): .. code-block:: python try: - with client.watch( - [{'$match': {'operationType': 'insert'}}]) as stream: + with client.watch([{"$match": {"operationType": "insert"}}]) as stream: for insert_change in stream: print(insert_change) except pymongo.errors.PyMongoError: # The ChangeStream encountered an unrecoverable error or the # resume attempt failed to recreate the cursor. - logging.error('...') + logging.error("...") For a precise description of the resume process see the `change streams specification`_.