diff --git a/.flake8 b/.flake8
deleted file mode 100644
index 1ad38100..00000000
--- a/.flake8
+++ /dev/null
@@ -1,31 +0,0 @@
-[flake8]
-max-line-length = 100
-enable-extensions = G
-extend-ignore =
- G200, G202, G001
- # black adds spaces around ':'
- E203,
- # E501 line too long (let black handle line length)
- E501
- # B305 `.next()` is not a thing on Python 3. Use the `next()` builtin. For Python 2 compatibility, use `six.next()`.
- B305
-per-file-ignores =
- # F841 local variable 'foo' is assigned to but never used
- # E731 do not assign a lambda expression, use a def
- # F811 redefinition of unused 'foo' from line XXX
- test/*/test_examples.py: F841,E731,F811
-
- # F811 redefinition of unused 'foo' from line XXX
- # B011 Do not call assert False since python -O removes these calls. Instead callers should raise AssertionError().
-
- test/*: F811,B011
-
- # E402 module level import not at top of file
- doc/examples/monitoring_example.py: E402
-
- # F403 'from foo import *' used; unable to detect undefined names
- # F401 'foo' imported but unused
- synchro/__init__.py: F403,F401
-
- # F401 'foo' imported but unused
- motor/__init__.py: F401
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index a6871f6a..14f6dfaf 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -23,22 +23,12 @@ repos:
files: \.(py|pyi)$
args: [--line-length=100]
-- repo: https://github.com/PyCQA/isort
- rev: 5.12.0
+- repo: https://github.com/astral-sh/ruff-pre-commit
+ # Ruff version.
+ rev: v0.1.1
hooks:
- - id: isort
- files: \.(py|pyi)$
- args: [--profile=black]
-
-- repo: https://github.com/pycqa/flake8
- rev: 3.9.2
- hooks:
- - id: flake8
- additional_dependencies: [
- 'flake8-bugbear==20.1.4',
- 'flake8-logging-format==0.6.0',
- 'flake8-implicit-str-concat==0.2.0',
- ]
+ - id: ruff
+ args: ["--fix", "--show-fixes"]
# We use the Python version instead of the original version which seems to require Docker
# https://github.com/koalaman/shellcheck-precommit
diff --git a/doc/conf.py b/doc/conf.py
index 89be7bea..ee7d8e7e 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -1,9 +1,7 @@
-# -*- coding: utf-8 -*-
#
# Motor documentation build configuration file
#
# This file is execfile()d with the current directory set to its containing dir.
-
import os
import sys
diff --git a/doc/coroutine_annotation.py b/doc/coroutine_annotation.py
index 8d796b89..fd371b55 100644
--- a/doc/coroutine_annotation.py
+++ b/doc/coroutine_annotation.py
@@ -1,10 +1,9 @@
"""Gratefully adapted from aiohttp, provides coroutine support to autodoc."""
-
from sphinx import addnodes
from sphinx.domains.python import PyFunction, PyMethod
-class PyCoroutineMixin(object):
+class PyCoroutineMixin:
def handle_signature(self, sig, signode):
ret = super().handle_signature(sig, signode)
signode.insert(0, addnodes.desc_annotation("coroutine ", "coroutine "))
diff --git a/doc/examples/aiohttp_example.py b/doc/examples/aiohttp_example.py
index 7d0c37d4..f768a7ab 100644
--- a/doc/examples/aiohttp_example.py
+++ b/doc/examples/aiohttp_example.py
@@ -31,7 +31,7 @@ async def page_handler(request):
document = await db.pages.find_one(page_name)
if not document:
- return web.HTTPNotFound(text="No page named {!r}".format(page_name))
+ return web.HTTPNotFound(text=f"No page named {page_name!r}")
return web.Response(body=document["body"].encode(), content_type="text/html")
diff --git a/doc/examples/auto_csfle_example.py b/doc/examples/auto_csfle_example.py
index 9927985c..7a37c52e 100644
--- a/doc/examples/auto_csfle_example.py
+++ b/doc/examples/auto_csfle_example.py
@@ -74,7 +74,7 @@ async def main():
await create_json_schema_file(kms_providers, key_vault_namespace, key_vault_client)
# Load the JSON Schema and construct the local schema_map option.
- with open("jsonSchema.json", "r") as file:
+ with open("jsonSchema.json") as file:
json_schema_string = file.read()
json_schema = json_util.loads(json_schema_string)
schema_map = {encrypted_namespace: json_schema}
@@ -91,10 +91,10 @@ async def main():
await coll.insert_one({"encryptedField": "123456789"})
decrypted_doc = await coll.find_one()
- print("Decrypted document: %s" % (decrypted_doc,))
+ print(f"Decrypted document: {decrypted_doc}")
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name]
encrypted_doc = await unencrypted_coll.find_one()
- print("Encrypted document: %s" % (encrypted_doc,))
+ print(f"Encrypted document: {encrypted_doc}")
if __name__ == "__main__":
diff --git a/doc/examples/explicit_encryption_automatic_decryption_example.py b/doc/examples/explicit_encryption_automatic_decryption_example.py
index 11095a3a..f3e380f6 100644
--- a/doc/examples/explicit_encryption_automatic_decryption_example.py
+++ b/doc/examples/explicit_encryption_automatic_decryption_example.py
@@ -64,9 +64,9 @@ async def main():
await coll.insert_one({"encryptedField": encrypted_field})
# Automatically decrypts any encrypted fields.
doc = await coll.find_one()
- print("Decrypted document: %s" % (doc,))
+ print(f"Decrypted document: {doc}")
unencrypted_coll = AsyncIOMotorClient().test.coll
- print("Encrypted document: %s" % (await unencrypted_coll.find_one(),))
+ print(f"Encrypted document: {await unencrypted_coll.find_one()}")
# Cleanup resources.
await client_encryption.close()
diff --git a/doc/examples/explicit_encryption_example.py b/doc/examples/explicit_encryption_example.py
index e745a0c1..70b15da5 100644
--- a/doc/examples/explicit_encryption_example.py
+++ b/doc/examples/explicit_encryption_example.py
@@ -54,11 +54,11 @@ async def main():
)
await coll.insert_one({"encryptedField": encrypted_field})
doc = await coll.find_one()
- print("Encrypted document: %s" % (doc,))
+ print(f"Encrypted document: {doc}")
# Explicitly decrypt the field:
doc["encryptedField"] = await client_encryption.decrypt(doc["encryptedField"])
- print("Decrypted document: %s" % (doc,))
+ print(f"Decrypted document: {doc}")
# Cleanup resources.
await client_encryption.close()
diff --git a/doc/examples/monitoring_example.py b/doc/examples/monitoring_example.py
index 29b196da..a574d675 100644
--- a/doc/examples/monitoring_example.py
+++ b/doc/examples/monitoring_example.py
@@ -27,25 +27,25 @@ logging.basicConfig(stream=sys.stdout, level=logging.INFO)
class CommandLogger(monitoring.CommandListener):
def started(self, event):
logging.info(
- "Command {0.command_name} with request id "
- "{0.request_id} started on server "
- "{0.connection_id}".format(event)
+ f"Command {event.command_name} with request id "
+ f"{event.request_id} started on server "
+ f"{event.connection_id}"
)
def succeeded(self, event):
logging.info(
- "Command {0.command_name} with request id "
- "{0.request_id} on server {0.connection_id} "
- "succeeded in {0.duration_micros} "
- "microseconds".format(event)
+ f"Command {event.command_name} with request id "
+ f"{event.request_id} on server {event.connection_id} "
+ f"succeeded in {event.duration_micros} "
+ "microseconds"
)
def failed(self, event):
logging.info(
- "Command {0.command_name} with request id "
- "{0.request_id} on server {0.connection_id} "
- "failed in {0.duration_micros} "
- "microseconds".format(event)
+ f"Command {event.command_name} with request id "
+ f"{event.request_id} on server {event.connection_id} "
+ f"failed in {event.duration_micros} "
+ "microseconds"
)
@@ -77,22 +77,20 @@ ioloop.IOLoop.current().run_sync(do_insert)
# server logger start
class ServerLogger(monitoring.ServerListener):
def opened(self, event):
- logging.info("Server {0.server_address} added to topology {0.topology_id}".format(event))
+ logging.info(f"Server {event.server_address} added to topology {event.topology_id}")
def description_changed(self, event):
previous_server_type = event.previous_description.server_type
new_server_type = event.new_description.server_type
if new_server_type != previous_server_type:
logging.info(
- "Server {0.server_address} changed type from "
- "{0.previous_description.server_type_name} to "
- "{0.new_description.server_type_name}".format(event)
+ f"Server {event.server_address} changed type from "
+ f"{event.previous_description.server_type_name} to "
+ f"{event.new_description.server_type_name}"
)
def closed(self, event):
- logging.warning(
- "Server {0.server_address} removed from topology {0.topology_id}".format(event)
- )
+ logging.warning(f"Server {event.server_address} removed from topology {event.topology_id}")
monitoring.register(ServerLogger())
@@ -102,21 +100,21 @@ monitoring.register(ServerLogger())
# topology logger start
class TopologyLogger(monitoring.TopologyListener):
def opened(self, event):
- logging.info("Topology with id {0.topology_id} opened".format(event))
+ logging.info(f"Topology with id {event.topology_id} opened")
def description_changed(self, event):
- logging.info("Topology description updated for topology id {0.topology_id}".format(event))
+ logging.info(f"Topology description updated for topology id {event.topology_id}")
previous_topology_type = event.previous_description.topology_type
new_topology_type = event.new_description.topology_type
if new_topology_type != previous_topology_type:
logging.info(
- "Topology {0.topology_id} changed type from "
- "{0.previous_description.topology_type_name} to "
- "{0.new_description.topology_type_name}".format(event)
+ f"Topology {event.topology_id} changed type from "
+ f"{event.previous_description.topology_type_name} to "
+ f"{event.new_description.topology_type_name}"
)
def closed(self, event):
- logging.info("Topology with id {0.topology_id} closed".format(event))
+ logging.info(f"Topology with id {event.topology_id} closed")
monitoring.register(TopologyLogger())
@@ -126,18 +124,18 @@ monitoring.register(TopologyLogger())
# heartbeat logger start
class HeartbeatLogger(monitoring.ServerHeartbeatListener):
def started(self, event):
- logging.info("Heartbeat sent to server {0.connection_id}".format(event))
+ logging.info(f"Heartbeat sent to server {event.connection_id}")
def succeeded(self, event):
logging.info(
- "Heartbeat to server {0.connection_id} "
+ f"Heartbeat to server {event.connection_id} "
"succeeded with reply "
- "{0.reply.document}".format(event)
+ f"{event.reply.document}"
)
def failed(self, event):
logging.warning(
- "Heartbeat to server {0.connection_id} failed with error {0.reply}".format(event)
+ f"Heartbeat to server {event.connection_id} failed with error {event.reply}"
)
diff --git a/doc/examples/server_fle_enforcement_example.py b/doc/examples/server_fle_enforcement_example.py
index b0e26756..91629265 100644
--- a/doc/examples/server_fle_enforcement_example.py
+++ b/doc/examples/server_fle_enforcement_example.py
@@ -86,14 +86,14 @@ async def main():
await coll.insert_one({"encryptedField": "123456789"})
decrypted_doc = await coll.find_one()
- print("Decrypted document: %s" % (decrypted_doc,))
+ print(f"Decrypted document: {decrypted_doc}")
unencrypted_coll = AsyncIOMotorClient()[db_name][coll_name]
encrypted_doc = await unencrypted_coll.find_one()
- print("Encrypted document: %s" % (encrypted_doc,))
+ print(f"Encrypted document: {encrypted_doc}")
try:
await unencrypted_coll.insert_one({"encryptedField": "123456789"})
except OperationFailure as exc:
- print("Unencrypted insert failed: %s" % (exc.details,))
+ print(f"Unencrypted insert failed: {exc.details}")
if __name__ == "__main__":
diff --git a/doc/examples/tornado_change_stream_example.py b/doc/examples/tornado_change_stream_example.py
index 2b312485..fa89caa8 100644
--- a/doc/examples/tornado_change_stream_example.py
+++ b/doc/examples/tornado_change_stream_example.py
@@ -73,11 +73,8 @@ class ChangesHandler(tornado.websocket.WebSocketHandler):
data = data.encode("utf-8")
html_id = urlsafe_b64encode(data).decode().rstrip("=")
change.pop("_id")
- change["html"] = '
' % (
- html_id,
- tornado.escape.xhtml_escape(pformat(change)),
- )
-
+ change_pre = tornado.escape.xhtml_escape(pformat(change))
+ change["html"] = f''
change["html_id"] = html_id
ChangesHandler.send_change(change)
ChangesHandler.update_cache(change)
@@ -97,7 +94,7 @@ async def watch(collection):
def main():
tornado.options.parse_command_line()
if "." not in options.ns:
- sys.stderr.write('Invalid ns "%s", must contain a "."' % (options.ns,))
+ sys.stderr.write(f'Invalid ns "{options.ns}", must contain a "."')
sys.exit(1)
db_name, collection_name = options.ns.split(".", 1)
diff --git a/doc/motor_extensions.py b/doc/motor_extensions.py
index 6da492e3..e47e5531 100644
--- a/doc/motor_extensions.py
+++ b/doc/motor_extensions.py
@@ -32,7 +32,7 @@ def has_node_of_type(root, klass):
if isinstance(root, klass):
return True
- for child in root.children:
+ for child in root.children: # noqa: SIM110
if has_node_of_type(child, klass):
return True
@@ -90,7 +90,7 @@ def process_motor_nodes(app, doctree):
if desc_content_node.line is None and obj_motor_info["is_pymongo_docstring"]:
maybe_warn_about_code_block(name, desc_content_node)
- if obj_motor_info["is_async_method"]:
+ if obj_motor_info["is_async_method"]: # noqa: SIM102
# Might be a handwritten RST with "coroutine" already.
if not has_coro_annotation(signature_node):
coro_annotation = addnodes.desc_annotation(
@@ -125,9 +125,9 @@ def get_motor_attr(motor_class, name, *defargs):
attr = safe_getattr(motor_class, name, *defargs)
# Store some info for process_motor_nodes()
- full_name = "%s.%s.%s" % (motor_class.__module__, motor_class.__name__, name)
+ full_name = f"{motor_class.__module__}.{motor_class.__name__}.{name}"
- full_name_legacy = "motor.%s.%s.%s" % (motor_class.__module__, motor_class.__name__, name)
+ full_name_legacy = f"motor.{motor_class.__module__}.{motor_class.__name__}.{name}"
# These sub-attributes are set in motor.asynchronize()
has_coroutine_annotation = getattr(attr, "coroutine_annotation", False)
diff --git a/motor/__init__.py b/motor/__init__.py
index 9d400173..df05b9d1 100644
--- a/motor/__init__.py
+++ b/motor/__init__.py
@@ -13,18 +13,17 @@
# limitations under the License.
"""Motor, an asynchronous driver for MongoDB."""
-
-from ._version import get_version_string, version, version_tuple # noqa
+from ._version import get_version_string, version, version_tuple # noqa: F401
"""Current version of Motor."""
try:
- import tornado # type: ignore
+ import tornado # type:ignore[import]
except ImportError:
tornado = None
else:
# For backwards compatibility with Motor 0.4, export Motor's Tornado classes
# at module root. This may change in the future.
from .motor_tornado import * # noqa: F403
- from .motor_tornado import __all__
+ from .motor_tornado import __all__ # noqa: F401
diff --git a/motor/_version.py b/motor/_version.py
index f6f2e95a..ffd00a42 100644
--- a/motor/_version.py
+++ b/motor/_version.py
@@ -13,7 +13,6 @@
# limitations under the License.
"""Version-related data for motor."""
-
version_tuple = (3, 4, 0, ".dev0")
diff --git a/motor/aiohttp/__init__.py b/motor/aiohttp/__init__.py
index 6d49fab2..9566da7f 100644
--- a/motor/aiohttp/__init__.py
+++ b/motor/aiohttp/__init__.py
@@ -106,7 +106,6 @@ def set_extra_headers(response, gridout):
- `gridout`: The :class:`~motor.motor_asyncio.AsyncIOMotorGridOut` we
will serve to the client
"""
- pass
def _config_error(request):
@@ -188,8 +187,8 @@ class AIOHTTPGridFS:
try:
gridout = await self._get_gridfs_file(self._bucket, filename, request)
- except gridfs.NoFile:
- raise aiohttp.web.HTTPNotFound(text=request.path)
+ except gridfs.NoFile as e:
+ raise aiohttp.web.HTTPNotFound(text=request.path) from e
resp = aiohttp.web.StreamResponse()
diff --git a/motor/core.py b/motor/core.py
index fec22b33..adbcae65 100644
--- a/motor/core.py
+++ b/motor/core.py
@@ -13,7 +13,6 @@
# limitations under the License.
"""Framework-agnostic core of Motor, an asynchronous driver for MongoDB."""
-
import functools
import time
import warnings
@@ -73,7 +72,7 @@ def _max_time_expired_error(exc):
return isinstance(exc, pymongo.errors.OperationFailure) and exc.code == 50
-class AgnosticBase(object):
+class AgnosticBase:
def __eq__(self, other):
if (
isinstance(other, self.__class__)
@@ -87,7 +86,7 @@ class AgnosticBase(object):
self.delegate = delegate
def __repr__(self):
- return "%s(%r)" % (self.__class__.__name__, self.delegate)
+ return f"{self.__class__.__name__}({self.delegate!r})"
class AgnosticBaseProperties(AgnosticBase):
@@ -213,9 +212,10 @@ class AgnosticClient(AgnosticBaseProperties):
This option and `resume_after` are mutually exclusive.
- `comment` (optional): A user-provided comment to attach to this
command.
- - `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change events
- may now result in a `fullDocumentBeforeChange` response field.
- - `show_expanded_events` (optional): Include expanded events such as DDL events like `dropIndexes`.
+ - `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change
+ events may now result in a `fullDocumentBeforeChange` response field.
+ - `show_expanded_events` (optional): Include expanded events such as DDL events like
+ `dropIndexes`.
:Returns:
A :class:`~MotorChangeStream`.
@@ -260,8 +260,8 @@ class AgnosticClient(AgnosticBaseProperties):
def __getattr__(self, name):
if name.startswith("_"):
raise AttributeError(
- "%s has no attribute %r. To access the %s"
- " database, use client['%s']." % (self.__class__.__name__, name, name, name)
+ f"{self.__class__.__name__} has no attribute {name!r}. To access the {name}"
+ f" database, use client['{name}']."
)
return self[name]
@@ -292,7 +292,7 @@ class AgnosticClient(AgnosticBaseProperties):
return session_class(obj, self)
-class _MotorTransactionContext(object):
+class _MotorTransactionContext:
"""Internal transaction context manager for start_transaction."""
def __init__(self, session):
@@ -678,9 +678,10 @@ class AgnosticDatabase(AgnosticBaseProperties):
This option and `resume_after` are mutually exclusive.
- `comment` (optional): A user-provided comment to attach to this
command.
- - `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change events
- may now result in a `fullDocumentBeforeChange` response field.
- - `show_expanded_events` (optional): Include expanded events such as DDL events like `dropIndexes`.
+ - `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change
+ events may now result in a `fullDocumentBeforeChange` response field.
+ - `show_expanded_events` (optional): Include expanded events such as DDL events like
+ `dropIndexes`.
:Returns:
A :class:`~MotorChangeStream`.
@@ -763,7 +764,8 @@ class AgnosticDatabase(AgnosticBaseProperties):
:class:`MotorClientSession`.
- `comment` (optional): A user-provided comment to attach to future getMores for this
command.
- - `max_await_time_ms` (optional): The number of ms to wait for more data on future getMores for this command.
+ - `max_await_time_ms` (optional): The number of ms to wait for more data on future
+ getMores for this command.
- `**kwargs` (optional): additional keyword arguments will
be added to the command document before it is sent
@@ -812,8 +814,8 @@ class AgnosticDatabase(AgnosticBaseProperties):
def __getattr__(self, name):
if name.startswith("_"):
raise AttributeError(
- "%s has no attribute %r. To access the %s"
- " collection, use database['%s']." % (self.__class__.__name__, name, name, name)
+ f"{self.__class__.__name__} has no attribute {name!r}. To access the {name}"
+ " collection, use database['{name}']."
)
return self[name]
@@ -830,14 +832,14 @@ class AgnosticDatabase(AgnosticBaseProperties):
client_class_name = self._client.__class__.__name__
if database_name == "open_sync":
raise TypeError(
- "%s.open_sync() is unnecessary Motor 0.2, "
- "see changelog for details." % client_class_name
+ f"{client_class_name}.open_sync() is unnecessary Motor 0.2, "
+ "see changelog for details."
)
raise TypeError(
"MotorDatabase object is not callable. If you meant to "
- "call the '%s' method on a %s object it is "
- "failing because no such method exists." % (database_name, client_class_name)
+ f"call the '{database_name}' method on a {client_class_name} object it is "
+ "failing because no such method exists."
)
def wrap(self, obj):
@@ -940,11 +942,10 @@ class AgnosticCollection(AgnosticBaseProperties):
def __getattr__(self, name):
# Dotted collection name, like "foo.bar".
if name.startswith("_"):
- full_name = "%s.%s" % (self.name, name)
+ full_name = f"{self.name}.{name}"
raise AttributeError(
- "%s has no attribute %r. To access the %s"
- " collection, use database['%s']."
- % (self.__class__.__name__, name, full_name, full_name)
+ f"{self.__class__.__name__} has no attribute {name!r}. To access the {full_name}"
+ f" collection, use database['{full_name}']."
)
return self[name]
@@ -1250,9 +1251,10 @@ class AgnosticCollection(AgnosticBaseProperties):
This option and `resume_after` are mutually exclusive.
- `comment` (optional): A user-provided comment to attach to this
command.
- - `full_document_before_change`: Allowed values: `whenAvailable` and `required`. Change events
- may now result in a `fullDocumentBeforeChange` response field.
- - `show_expanded_events` (optional): Include expanded events such as DDL events like `dropIndexes`.
+ - `full_document_before_change`: Allowed values: `whenAvailable` and `required`.
+ Change events may now result in a `fullDocumentBeforeChange` response field.
+ - `show_expanded_events` (optional): Include expanded events such as DDL events
+ like `dropIndexes`.
:Returns:
A :class:`~MotorChangeStream`.
@@ -1809,7 +1811,7 @@ class AgnosticRawBatchCommandCursor(AgnosticCommandCursor):
__delegate_class__ = RawBatchCommandCursor
-class _LatentCursor(object):
+class _LatentCursor:
"""Take the place of a PyMongo CommandCursor until aggregate() begins."""
alive = True
@@ -2151,7 +2153,7 @@ class AgnosticClientEncryption(AgnosticBase):
await self.close()
def __enter__(self):
- raise RuntimeError('Use {} in "async with", not "with"'.format(self.__class__.__name__))
+ raise RuntimeError(f'Use {self.__class__.__name__} in "async with", not "with"')
def __exit__(self, exc_type, exc_val, exc_tb):
pass
@@ -2173,8 +2175,8 @@ class AgnosticClientEncryption(AgnosticBase):
.. warning::
This function does not update the encryptedFieldsMap in the client's
- AutoEncryptionOpts, thus the user must create a new client after calling this function with
- the encryptedFields returned.
+ AutoEncryptionOpts, thus the user must create a new client after calling
+ this function with the encryptedFields returned.
Normally collection creation is automatic. This method should
only be used to specify options on
@@ -2214,10 +2216,12 @@ class AgnosticClientEncryption(AgnosticBase):
All optional `create collection command`_ parameters should be passed
as keyword arguments to this method.
- See the documentation for :meth:`~pymongo.database.Database.create_collection` for all valid options.
+ See the documentation for :meth:`~pymongo.database.Database.create_collection`
+ for all valid options.
:Raises:
- - :class:`~pymongo.errors.EncryptedCollectionError`: When either data-key creation or creating the collection fails.
+ - :class:`~pymongo.errors.EncryptedCollectionError`: When either data-key creation or
+ creating the collection fails.
.. versionadded:: 3.2
diff --git a/motor/core.pyi b/motor/core.pyi
index b31b6e04..5abe4643 100644
--- a/motor/core.pyi
+++ b/motor/core.pyi
@@ -19,25 +19,17 @@ from __future__ import annotations
from asyncio import Future
from typing import (
Any,
- Awaitable,
Callable,
Collection,
Coroutine,
- Dict,
- FrozenSet,
Iterable,
- List,
Mapping,
MutableMapping,
NoReturn,
Optional,
Sequence,
- Set,
- Tuple,
- Type,
TypeVar,
Union,
- overload,
)
import pymongo.common
@@ -86,7 +78,7 @@ _CodecDocumentType = TypeVar("_CodecDocumentType", bound=Mapping[str, Any])
def _within_time_limit(start_time: float) -> bool: ...
def _max_time_expired_error(exc: Exception) -> bool: ...
-class AgnosticBase(object):
+class AgnosticBase:
delegate: Any
def __eq__(self, other: Any) -> bool: ...
@@ -101,10 +93,10 @@ class AgnosticBaseProperties(AgnosticBase):
class AgnosticClient(AgnosticBaseProperties):
__motor_class_name__: str
- __delegate_class__: Type[pymongo.MongoClient]
+ __delegate_class__: type[pymongo.MongoClient]
- def address(self) -> Optional[Tuple[str, int]]: ...
- def arbiters(self) -> Set[Tuple[str, int]]: ...
+ def address(self) -> Optional[tuple[str, int]]: ...
+ def arbiters(self) -> set[tuple[str, int]]: ...
def close(self) -> None: ...
def __hash__(self) -> int: ...
async def drop_database(
@@ -145,15 +137,15 @@ class AgnosticClient(AgnosticBaseProperties):
self,
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
- ) -> List[str]: ...
- def nodes(self) -> FrozenSet[_Address]: ...
+ ) -> list[str]: ...
+ def nodes(self) -> frozenset[_Address]: ...
PORT: int
- def primary(self) -> Optional[Tuple[str, int]]: ...
+ def primary(self) -> Optional[tuple[str, int]]: ...
read_concern: ReadConcern
- def secondaries(self) -> Set[Tuple[str, int]]: ...
+ def secondaries(self) -> set[tuple[str, int]]: ...
async def server_info(
self, session: Optional[AgnosticClientSession] = None
- ) -> Dict[str, Any]: ...
+ ) -> dict[str, Any]: ...
def topology_description(self) -> TopologyDescription: ...
async def start_session(
self,
@@ -197,7 +189,7 @@ class _MotorTransactionContext:
class AgnosticClientSession(AgnosticBase):
__motor_class_name__: str
- __delegate_class__: Type[ClientSession]
+ __delegate_class__: type[ClientSession]
async def commit_transaction(self) -> None: ...
async def abort_transaction(self) -> None: ...
@@ -236,7 +228,7 @@ class AgnosticClientSession(AgnosticBase):
class AgnosticDatabase(AgnosticBaseProperties):
__motor_class_name__: str
- __delegate_class__: Type[Database]
+ __delegate_class__: type[Database]
def __hash__(self) -> int: ...
def __bool__(self) -> int: ...
@@ -262,7 +254,7 @@ class AgnosticDatabase(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
- ) -> Union[Dict[str, Any], _CodecDocumentType]: ...
+ ) -> Union[dict[str, Any], _CodecDocumentType]: ...
async def create_collection(
self,
name: str,
@@ -287,7 +279,7 @@ class AgnosticDatabase(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
encrypted_fields: Optional[Mapping[str, Any]] = None,
- ) -> Dict[str, Any]: ...
+ ) -> dict[str, Any]: ...
async def get_collection(
self,
name: str,
@@ -302,7 +294,7 @@ class AgnosticDatabase(AgnosticBaseProperties):
filter: Optional[Mapping[str, Any]] = None,
comment: Optional[Any] = None,
**kwargs: Any,
- ) -> List[str]: ...
+ ) -> list[str]: ...
async def list_collections(
self,
session: Optional[AgnosticClientSession] = None,
@@ -319,7 +311,7 @@ class AgnosticDatabase(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
background: Optional[bool] = None,
comment: Optional[Any] = None,
- ) -> Dict[str, Any]: ...
+ ) -> dict[str, Any]: ...
def with_options(
self,
codec_options: Optional[CodecOptions[_DocumentTypeArg]] = None,
@@ -359,7 +351,7 @@ class AgnosticDatabase(AgnosticBaseProperties):
class AgnosticCollection(AgnosticBaseProperties):
__motor_class_name__: str
- __delegate_class__: Type[Collection]
+ __delegate_class__: type[Collection]
def __hash__(self) -> int: ...
def __bool__(self) -> bool: ...
@@ -392,7 +384,7 @@ class AgnosticCollection(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
- ) -> List[str]: ...
+ ) -> list[str]: ...
async def delete_many(
self,
filter: Mapping[str, Any],
@@ -418,7 +410,7 @@ class AgnosticCollection(AgnosticBaseProperties):
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
- ) -> List[Any]: ...
+ ) -> list[Any]: ...
async def drop(
self,
session: Optional[AgnosticClientSession] = None,
@@ -575,11 +567,11 @@ class AgnosticCollection(AgnosticBaseProperties):
) -> str: ...
async def create_search_indexes(
self,
- models: List[SearchIndexModel],
+ models: list[SearchIndexModel],
session: Optional[AgnosticClientSession] = None,
comment: Optional[Any] = None,
**kwargs: Any,
- ) -> List[str]: ...
+ ) -> list[str]: ...
async def drop_search_index(
self,
name: str,
@@ -659,8 +651,8 @@ class AgnosticBaseCursor(AgnosticBase):
def next_object(self) -> Any: ...
def each(self, callback: Callable) -> None: ...
def _each_got_more(self, callback: Callable, future: Any) -> None: ...
- def to_list(self, length: int) -> Future[List]: ...
- def _to_list(self, length: int, the_list: List, future: Any, get_more_result: Any) -> None: ...
+ def to_list(self, length: int) -> Future[list]: ...
+ def _to_list(self, length: int, the_list: list, future: Any, get_more_result: Any) -> None: ...
def get_io_loop(self) -> Any: ...
def batch_size(self, batch_size: int) -> AgnosticBaseCursor: ...
def _buffer_size(self) -> int: ...
@@ -671,9 +663,9 @@ class AgnosticBaseCursor(AgnosticBase):
class AgnosticCursor(AgnosticBaseCursor):
__motor_class_name__: str
- __delegate_class__: Type[Cursor]
+ __delegate_class__: type[Cursor]
def collation(self, collation: Optional[_CollationIn]) -> AgnosticCursor: ...
- async def distinct(self, key: str) -> List: ...
+ async def distinct(self, key: str) -> list: ...
async def explain(self) -> _DocumentType: ...
def add_option(self, mask: int) -> AgnosticCursor: ...
def remove_option(self, mask: int) -> AgnosticCursor: ...
@@ -701,11 +693,11 @@ class AgnosticCursor(AgnosticBaseCursor):
class AgnosticRawBatchCursor(AgnosticCursor):
__motor_class_name__: str
- __delegate_class__: Type[RawBatchCursor]
+ __delegate_class__: type[RawBatchCursor]
class AgnosticCommandCursor(AgnosticBaseCursor):
__motor_class_name__: str
- __delegate_class__: Type[CommandCursor]
+ __delegate_class__: type[CommandCursor]
def _query_flags(self) -> int: ...
def _data(self) -> Any: ...
@@ -713,7 +705,7 @@ class AgnosticCommandCursor(AgnosticBaseCursor):
class AgnosticRawBatchCommandCursor(AgnosticCommandCursor):
__motor_class_name__: str
- __delegate_class__: Type[RawBatchCommandCursor]
+ __delegate_class__: type[RawBatchCommandCursor]
class _LatentCursor:
def __init__(self, collection: AgnosticCollection): ...
@@ -729,7 +721,7 @@ class AgnosticLatentCommandCursor(AgnosticCommandCursor):
class AgnosticChangeStream(AgnosticBase):
__motor_class_name__: str
- __delegate_class__: Type[ChangeStream]
+ __delegate_class__: type[ChangeStream]
async def _close(self) -> None: ...
def resume_token(self) -> Optional[Mapping[str, Any]]: ...
@@ -767,7 +759,7 @@ class AgnosticChangeStream(AgnosticBase):
class AgnosticClientEncryption(AgnosticBase):
__motor_class_name__: str
- __delegate_class__: Type[ClientEncryption]
+ __delegate_class__: type[ClientEncryption]
def __init__(
self,
kms_providers: Mapping[str, Any],
@@ -835,4 +827,4 @@ class AgnosticClientEncryption(AgnosticBase):
kms_provider: Optional[str] = None,
master_key: Optional[Mapping[str, Any]] = None,
**kwargs: Any,
- ) -> Tuple[AgnosticCollection, Mapping[str, Any]]: ...
+ ) -> tuple[AgnosticCollection, Mapping[str, Any]]: ...
diff --git a/motor/docstrings.py b/motor/docstrings.py
index dbc71830..4b2a4f98 100644
--- a/motor/docstrings.py
+++ b/motor/docstrings.py
@@ -11,8 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
-
get_database_doc = """
Get a :class:`MotorDatabase` with the given name and options.
diff --git a/motor/frameworks/asyncio/__init__.py b/motor/frameworks/asyncio/__init__.py
index 2fa13b01..1c7431d2 100644
--- a/motor/frameworks/asyncio/__init__.py
+++ b/motor/frameworks/asyncio/__init__.py
@@ -16,8 +16,6 @@
See "Frameworks" in the Developer Guide.
"""
-
-
import asyncio
import asyncio.tasks
import functools
@@ -68,7 +66,7 @@ _EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
def _reset_global_executor():
"""Re-initialize the global ThreadPoolExecutor"""
- global _EXECUTOR
+ global _EXECUTOR # noqa: PLW0603
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
diff --git a/motor/frameworks/tornado/__init__.py b/motor/frameworks/tornado/__init__.py
index ea1a4ad2..befb8908 100644
--- a/motor/frameworks/tornado/__init__.py
+++ b/motor/frameworks/tornado/__init__.py
@@ -63,7 +63,7 @@ _EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
def _reset_global_executor():
"""Re-initialize the global ThreadPoolExecutor"""
- global _EXECUTOR
+ global _EXECUTOR # noqa: PLW0603
_EXECUTOR = ThreadPoolExecutor(max_workers=max_workers)
@@ -148,4 +148,4 @@ def yieldable(future):
def platform_info():
- return "Tornado %s" % (tornado_version,)
+ return f"Tornado {tornado_version}"
diff --git a/motor/metaprogramming.py b/motor/metaprogramming.py
index 90bee645..d80bede7 100644
--- a/motor/metaprogramming.py
+++ b/motor/metaprogramming.py
@@ -13,7 +13,6 @@
# limitations under the License.
"""Dynamic class-creation for Motor."""
-
import functools
import inspect
from typing import Any, Callable, Dict
@@ -121,7 +120,7 @@ def coroutine_annotation(f):
return f
-class MotorAttributeFactory(object):
+class MotorAttributeFactory:
"""Used by Motor classes to mark attributes that delegate in some way to
PyMongo. At module import time, create_class_with_framework calls
create_attribute() for each attr to create the final class attribute.
diff --git a/motor/motor_common.py b/motor/motor_common.py
index 09edb58a..da787e38 100644
--- a/motor/motor_common.py
+++ b/motor/motor_common.py
@@ -13,5 +13,4 @@
# limitations under the License.
"""Common code to support all async frameworks."""
-
callback_type_error = TypeError("callback must be a callable")
diff --git a/motor/motor_gridfs.py b/motor/motor_gridfs.py
index 4bc48d21..8131bff5 100644
--- a/motor/motor_gridfs.py
+++ b/motor/motor_gridfs.py
@@ -13,7 +13,6 @@
# limitations under the License.
"""GridFS implementation for Motor, an asynchronous driver for MongoDB."""
-
import hashlib
import warnings
@@ -73,7 +72,7 @@ class MotorGridOutProperty(ReadOnlyProperty):
return property(fget=fget, doc=doc)
-class AgnosticGridOut(object):
+class AgnosticGridOut:
"""Class to read data out of GridFS.
MotorGridOut supports the same attributes as PyMongo's
@@ -206,7 +205,7 @@ class AgnosticGridOut(object):
written += len(chunk)
-class AgnosticGridIn(object):
+class AgnosticGridIn:
__motor_class_name__ = "MotorGridIn"
__delegate_class__ = gridfs.GridIn
@@ -315,7 +314,7 @@ Metadata set on the file appears as attributes on a
return self.io_loop
-class AgnosticGridFSBucket(object):
+class AgnosticGridFSBucket:
__motor_class_name__ = "MotorGridFSBucket"
__delegate_class__ = gridfs.GridFSBucket
@@ -394,7 +393,7 @@ class AgnosticGridFSBucket(object):
if not isinstance(database, db_class):
raise TypeError(
- "First argument to %s must be MotorDatabase, not %r" % (self.__class__, database)
+ f"First argument to {self.__class__} must be MotorDatabase, not {database!r}"
)
self.io_loop = database.get_io_loop()
diff --git a/motor/motor_gridfs.pyi b/motor/motor_gridfs.pyi
index 9e3eb8af..ad7306ba 100644
--- a/motor/motor_gridfs.pyi
+++ b/motor/motor_gridfs.pyi
@@ -16,7 +16,7 @@
import datetime
import os
-from typing import Any, Iterable, List, Mapping, NoReturn, Optional, Type
+from typing import Any, Iterable, Mapping, NoReturn, Optional
from bson import ObjectId
from gridfs import DEFAULT_CHUNK_SIZE, GridFSBucket, GridIn, GridOut, GridOutCursor
@@ -40,11 +40,11 @@ class AgnosticGridOutCursor(AgnosticCursor):
async def _Cursor__die(self, synchronous: bool = False) -> None: ...
def next_object(self) -> AgnosticGridOutCursor: ...
-class AgnosticGridOut(object):
+class AgnosticGridOut:
__motor_class_name__: str
- __delegate_class__: Type[GridOut]
+ __delegate_class__: type[GridOut]
_id: Any
- aliases: Optional[List[str]]
+ aliases: Optional[list[str]]
chunk_size: int
filename: Optional[str]
name: Optional[str]
@@ -77,9 +77,9 @@ class AgnosticGridOut(object):
def get_io_loop(self) -> Any: ...
async def stream_to_handler(self, request_handler: Any) -> None: ...
-class AgnosticGridIn(object):
+class AgnosticGridIn:
__motor_class_name__: str
- __delegate_class__: Type[GridIn]
+ __delegate_class__: type[GridIn]
__getattr__: Any
_id: Any
filename: str
@@ -111,9 +111,9 @@ class AgnosticGridIn(object):
async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: ...
def get_io_loop(self) -> Any: ...
-class AgnosticGridFSBucket(object):
+class AgnosticGridFSBucket:
__motor_class_name__: str
- __delegate_class__: Type[GridFSBucket]
+ __delegate_class__: type[GridFSBucket]
async def delete(
self, file_id: Any, session: Optional[AgnosticClientSession] = None
) -> None: ...
diff --git a/motor/web.py b/motor/web.py
index ef4b6ec6..d376825c 100644
--- a/motor/web.py
+++ b/motor/web.py
@@ -13,7 +13,6 @@
# limitations under the License.
"""Utilities for using Motor with Tornado web applications."""
-
import datetime
import email.utils
import mimetypes
@@ -97,7 +96,7 @@ class GridFSHandler(tornado.web.RequestHandler):
try:
gridout = await self.get_gridfs_file(fs, path, self.request)
except gridfs.NoFile:
- raise tornado.web.HTTPError(404)
+ raise tornado.web.HTTPError(404) from None
# If-Modified-Since header is only good to the second.
modified = gridout.upload_date.replace(microsecond=0)
@@ -175,4 +174,3 @@ class GridFSHandler(tornado.web.RequestHandler):
def set_extra_headers(self, path, gridout):
"""For subclass to add extra headers to the response"""
- pass
diff --git a/pyproject.toml b/pyproject.toml
index 37cf69bf..543fceae 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -80,3 +80,63 @@ version = {attr = "motor._version.version"}
[tool.setuptools.packages.find]
include = ["motor"]
+
+
+[tool.ruff]
+target-version = "py37"
+line-length = 100
+select = [
+ "E", "F", "W", # flake8
+ "B", # flake8-bugbear
+ "I", # isort
+ "ARG", # flake8-unused-arguments
+ "C4", # flake8-comprehensions
+ "EM", # flake8-errmsg
+ "ICN", # flake8-import-conventions
+ "ISC", # flake8-implicit-str-concat
+ "G", # flake8-logging-format
+ "PGH", # pygrep-hooks
+ "PIE", # flake8-pie
+ "PL", # pylint
+ "PT", # flake8-pytest-style
+ "PTH", # flake8-use-pathlib
+ "RUF", # Ruff-specific
+ "S", # flake8-bandit
+ "SIM", # flake8-simplify
+ "T20", # flake8-print
+ "UP", # pyupgrade
+ "YTT", # flake8-2020
+ "EXE", # flake8-executable
+]
+extend-ignore = [
+ "ARG001", # Unused function argument
+ "UP007", # Use `X | Y` for type annotation
+ "EM101", # Exception must not use a string literal, assign to variable first
+ "EM102", # Exception must not use an f-string literal, assign to variable first
+ "G004", # Logging statement uses f-string"
+ "PLR", # Too many arguments to function call, etc.
+ "SIM108", # Use ternary operator"
+ "SIM105", # Use `contextlib.suppress(OSError)` instead of `try`-`except`-`pass`
+ "ARG002", # Unused method argument:
+ "S101", # Use of `assert` detected
+ "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
+]
+unfixable = [
+ "RUF100", # Unused noqa
+ "T20", # Removes print statements
+ "F841", # Removes unused variables
+ "F401"
+]
+exclude = []
+flake8-unused-arguments.ignore-variadic-names = true
+dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?)|dummy.*)$"
+
+[tool.ruff.per-file-ignores]
+"test/*.py" = ["PT009", "ARG", "E402", "PT027", "UP031",
+ "B904", "C405", "SIM", "PLR", "PTH", "B018", "C4",
+ "S", "E501", "T201", "E731", "F841", "F811",
+ "B011", "PT015", "E721"]
+"synchro/__init__.py" = ["F403", "B904", "F401"]
+"doc/*.py" = [ "T201", "PTH"]
+"motor/docstrings.py" = [ "E501", ]
+"doc/examples/*.py" = [ "E402", ]
diff --git a/pytest.ini b/pytest.ini
index efa935a4..7e14bad8 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -12,5 +12,5 @@ filterwarnings =
ignore: The fetch_next property is deprecated:DeprecationWarning
ignore:The next_object method is deprecated:DeprecationWarning
ignore:unclosed