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"] = '
%s
' % ( - html_id, - tornado.escape.xhtml_escape(pformat(change)), - ) - + change_pre = tornado.escape.xhtml_escape(pformat(change)) + change["html"] = f'
{change_pre}
' 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