From 4de1ee8e453e8c64240d98ba420bf4fcafdcb4c1 Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Fri, 5 Jun 2026 16:19:56 -0400 Subject: [PATCH 1/2] PYTHON-5858 - Upgrade ruff from v0.1.3 to v0.15.15 --- .evergreen/scripts/generate_config_utils.py | 13 +- .evergreen/scripts/kms_tester.py | 12 +- .evergreen/scripts/oidc_tester.py | 6 +- .evergreen/scripts/resync-all-specs.py | 12 +- .evergreen/scripts/run_tests.py | 2 +- .pre-commit-config.yaml | 4 +- _setup.py | 4 +- bson/__init__.py | 205 +++++++++--------- bson/_helpers.py | 4 +- bson/binary.py | 34 +-- bson/code.py | 6 +- bson/codec_options.py | 64 ++---- bson/datetime_ms.py | 1 + bson/dbref.py | 6 +- bson/decimal128.py | 20 +- bson/errors.py | 1 + bson/int64.py | 1 + bson/json_util.py | 56 ++--- bson/max_key.py | 1 + bson/min_key.py | 1 + bson/objectid.py | 10 +- bson/raw_bson.py | 8 +- bson/regex.py | 14 +- bson/son.py | 24 +- bson/timestamp.py | 3 +- bson/typings.py | 4 +- bson/tz_util.py | 5 +- gridfs/__init__.py | 13 +- gridfs/asynchronous/__init__.py | 5 +- gridfs/asynchronous/grid_file.py | 36 +-- gridfs/errors.py | 1 + gridfs/grid_file.py | 1 + gridfs/grid_file_shared.py | 14 +- gridfs/synchronous/__init__.py | 5 +- gridfs/synchronous/grid_file.py | 36 +-- hatch_build.py | 1 + pymongo/__init__.py | 24 +- pymongo/_asyncio_lock.py | 3 +- pymongo/_asyncio_task.py | 4 +- pymongo/_azure_helpers.py | 3 +- pymongo/_client_bulk_shared.py | 4 +- pymongo/_csot.py | 5 +- pymongo/_gcp_helpers.py | 3 +- pymongo/_version.py | 7 +- pymongo/asynchronous/aggregation.py | 1 + pymongo/asynchronous/auth.py | 7 +- pymongo/asynchronous/auth_aws.py | 6 +- pymongo/asynchronous/auth_oidc.py | 4 +- pymongo/asynchronous/bulk.py | 8 +- pymongo/asynchronous/change_stream.py | 13 +- pymongo/asynchronous/client_bulk.py | 7 +- pymongo/asynchronous/client_session.py | 20 +- pymongo/asynchronous/collection.py | 23 +- pymongo/asynchronous/command_cursor.py | 9 +- pymongo/asynchronous/cursor.py | 20 +- pymongo/asynchronous/cursor_base.py | 1 + pymongo/asynchronous/database.py | 27 +-- pymongo/asynchronous/encryption.py | 10 +- pymongo/asynchronous/helpers.py | 1 + pymongo/asynchronous/mongo_client.py | 43 ++-- pymongo/asynchronous/network.py | 5 +- pymongo/asynchronous/pool.py | 25 +-- pymongo/asynchronous/server.py | 5 +- pymongo/asynchronous/settings.py | 25 ++- pymongo/asynchronous/srv_resolver.py | 1 + pymongo/asynchronous/topology.py | 21 +- pymongo/asynchronous/uri_parser.py | 3 +- pymongo/auth.py | 1 + pymongo/auth_oidc.py | 1 + pymongo/auth_oidc_shared.py | 1 + pymongo/auth_shared.py | 6 +- pymongo/bulk_shared.py | 4 +- pymongo/change_stream.py | 1 + pymongo/client_options.py | 8 +- pymongo/client_session.py | 1 + pymongo/collation.py | 8 +- pymongo/collection.py | 1 + pymongo/command_cursor.py | 1 + pymongo/common.py | 21 +- pymongo/compression_support.py | 7 +- pymongo/cursor.py | 1 + pymongo/cursor_shared.py | 6 +- pymongo/daemon.py | 12 +- pymongo/database.py | 1 + pymongo/database_shared.py | 6 +- pymongo/driver_info.py | 1 + pymongo/encryption.py | 1 + pymongo/encryption_options.py | 4 +- pymongo/errors.py | 8 +- pymongo/event_loggers.py | 5 +- pymongo/hello.py | 6 +- pymongo/helpers_shared.py | 6 +- pymongo/logger.py | 2 +- pymongo/max_staleness_selectors.py | 12 +- pymongo/message.py | 100 ++++----- pymongo/mongo_client.py | 1 + pymongo/monitoring.py | 99 ++------- pymongo/network_layer.py | 9 +- pymongo/ocsp_support.py | 12 +- pymongo/operations.py | 89 +++----- pymongo/pool.py | 1 + pymongo/pool_options.py | 32 +-- pymongo/pool_shared.py | 11 +- pymongo/pyopenssl_context.py | 3 +- pymongo/read_concern.py | 5 +- pymongo/read_preferences.py | 20 +- pymongo/response.py | 6 +- pymongo/results.py | 13 +- pymongo/saslprep.py | 1 + pymongo/server_api.py | 4 +- pymongo/server_description.py | 44 ++-- pymongo/server_selectors.py | 4 +- pymongo/server_type.py | 1 + pymongo/socket_checker.py | 1 + pymongo/ssl_context.py | 1 + pymongo/ssl_support.py | 1 + pymongo/synchronous/aggregation.py | 1 + pymongo/synchronous/auth.py | 6 +- pymongo/synchronous/auth_aws.py | 6 +- pymongo/synchronous/auth_oidc.py | 4 +- pymongo/synchronous/bulk.py | 8 +- pymongo/synchronous/change_stream.py | 13 +- pymongo/synchronous/client_bulk.py | 7 +- pymongo/synchronous/client_session.py | 19 +- pymongo/synchronous/collection.py | 22 +- pymongo/synchronous/command_cursor.py | 9 +- pymongo/synchronous/cursor.py | 20 +- pymongo/synchronous/cursor_base.py | 1 + pymongo/synchronous/database.py | 27 +-- pymongo/synchronous/encryption.py | 10 +- pymongo/synchronous/helpers.py | 1 + pymongo/synchronous/mongo_client.py | 42 ++-- pymongo/synchronous/network.py | 5 +- pymongo/synchronous/pool.py | 25 +-- pymongo/synchronous/server.py | 5 +- pymongo/synchronous/settings.py | 25 ++- pymongo/synchronous/srv_resolver.py | 1 + pymongo/synchronous/topology.py | 21 +- pymongo/synchronous/uri_parser.py | 3 +- pymongo/topology_description.py | 50 ++--- pymongo/typings.py | 13 +- pymongo/uri_parser.py | 11 +- pymongo/uri_parser_shared.py | 11 +- pymongo/write_concern.py | 5 +- pyproject.toml | 5 +- test/__init__.py | 10 +- test/asynchronous/__init__.py | 10 +- test/asynchronous/conftest.py | 5 +- test/asynchronous/helpers.py | 13 +- test/asynchronous/pymongo_mocks.py | 5 +- test/asynchronous/qcheck.py | 12 +- test/asynchronous/test_async_cancellation.py | 2 + .../test_async_contextvars_reset.py | 2 + test/asynchronous/test_async_loop_safety.py | 1 + .../asynchronous/test_async_loop_unblocked.py | 3 +- test/asynchronous/test_auth.py | 24 +- test/asynchronous/test_auth_oidc.py | 28 +-- test/asynchronous/test_auth_spec.py | 12 +- test/asynchronous/test_bulk.py | 6 +- test/asynchronous/test_causal_consistency.py | 1 + test/asynchronous/test_change_stream.py | 30 +-- test/asynchronous/test_client.py | 69 +++--- test/asynchronous/test_client_backpressure.py | 4 +- test/asynchronous/test_client_bulk_write.py | 19 +- test/asynchronous/test_client_context.py | 4 +- test/asynchronous/test_client_metadata.py | 8 +- test/asynchronous/test_collation.py | 5 +- test/asynchronous/test_collection.py | 33 +-- .../test_collection_management.py | 1 + test/asynchronous/test_command_logging.py | 1 + test/asynchronous/test_command_monitoring.py | 1 + test/asynchronous/test_comment.py | 4 +- test/asynchronous/test_common.py | 4 +- test/asynchronous/test_concurrency.py | 2 + test/asynchronous/test_connection_logging.py | 1 + .../test_connection_monitoring.py | 19 +- ...nnections_survive_primary_stepdown_spec.py | 13 +- test/asynchronous/test_crud_unified.py | 1 + test/asynchronous/test_csot.py | 8 +- test/asynchronous/test_cursor.py | 22 +- test/asynchronous/test_custom_types.py | 16 +- test/asynchronous/test_database.py | 23 +- .../test_discovery_and_monitoring.py | 51 ++--- test/asynchronous/test_dns.py | 17 +- test/asynchronous/test_encryption.py | 65 +++--- test/asynchronous/test_examples.py | 7 +- test/asynchronous/test_grid_file.py | 8 +- test/asynchronous/test_gridfs.py | 11 +- test/asynchronous/test_gridfs_bucket.py | 11 +- test/asynchronous/test_gridfs_spec.py | 1 + .../asynchronous/test_heartbeat_monitoring.py | 7 +- test/asynchronous/test_index_management.py | 11 +- .../test_json_util_integration.py | 7 +- test/asynchronous/test_load_balancer.py | 9 +- test/asynchronous/test_locks.py | 1 + test/asynchronous/test_logger.py | 4 +- test/asynchronous/test_max_staleness.py | 6 +- .../test_mongos_load_balancing.py | 10 +- test/asynchronous/test_monitor.py | 4 +- test/asynchronous/test_monitoring.py | 19 +- test/asynchronous/test_on_demand_csfle.py | 4 +- test/asynchronous/test_periodic_executor.py | 3 +- test/asynchronous/test_pooling.py | 14 +- test/asynchronous/test_raw_bson.py | 3 +- test/asynchronous/test_read_concern.py | 6 +- test/asynchronous/test_read_preferences.py | 30 +-- .../test_read_write_concern_spec.py | 8 +- test/asynchronous/test_retryable_reads.py | 26 +-- .../test_retryable_reads_unified.py | 1 + test/asynchronous/test_retryable_writes.py | 38 ++-- .../test_retryable_writes_unified.py | 1 + test/asynchronous/test_run_command.py | 2 + .../asynchronous/test_sdam_monitoring_spec.py | 20 +- test/asynchronous/test_server_selection.py | 1 + .../test_server_selection_in_window.py | 15 +- .../test_server_selection_logging.py | 1 + .../asynchronous/test_server_selection_rtt.py | 4 +- test/asynchronous/test_session.py | 29 +-- test/asynchronous/test_sessions_unified.py | 1 + test/asynchronous/test_srv_polling.py | 9 +- test/asynchronous/test_ssl.py | 15 +- test/asynchronous/test_streaming_protocol.py | 6 +- test/asynchronous/test_transactions.py | 17 +- .../asynchronous/test_transactions_unified.py | 1 + test/asynchronous/test_unified_format.py | 3 +- .../test_versioned_api_integration.py | 4 +- test/asynchronous/unified_format.py | 87 ++++---- test/asynchronous/utils.py | 3 +- test/asynchronous/utils_selection_tests.py | 17 +- test/asynchronous/utils_spec_runner.py | 9 +- test/atlas/test_connection.py | 4 +- test/auth_aws/test_auth_aws.py | 4 +- test/conftest.py | 3 +- test/helpers.py | 13 +- test/lambda/mongodb/app.py | 7 +- test/mockupdb/test_auth_recovering_member.py | 3 +- test/mockupdb/test_cluster_time.py | 4 +- test/mockupdb/test_cursor.py | 4 +- test/mockupdb/test_cursor_namespace.py | 4 +- test/mockupdb/test_getmore_sharded.py | 4 +- test/mockupdb/test_initial_ismaster.py | 3 +- test/mockupdb/test_list_indexes.py | 4 +- test/mockupdb/test_max_staleness.py | 8 +- test/mockupdb/test_mixed_version_sharded.py | 9 +- test/mockupdb/test_op_msg.py | 3 +- test/mockupdb/test_op_msg_read_preference.py | 3 +- test/mockupdb/test_query_read_pref_sharded.py | 4 +- test/mockupdb/test_reset_and_request_check.py | 3 +- test/mockupdb/test_rsghost.py | 1 + test/mockupdb/test_slave_okay_rs.py | 1 + test/mockupdb/test_slave_okay_sharded.py | 1 + test/mockupdb/test_slave_okay_single.py | 1 + test/mockupdb/test_standalone_shard.py | 1 + test/mod_wsgi_test/mod_wsgi_test.py | 16 +- test/mod_wsgi_test/test_client.py | 9 +- test/mypy_fails/raw_bson_document.py | 6 +- test/performance/async_perf_test.py | 12 +- test/performance/perf_test.py | 8 +- test/pymongo_mocks.py | 3 +- test/qcheck.py | 12 +- test/sigstop_sigcont.py | 1 + test/test_auth.py | 24 +- test/test_auth_oidc.py | 28 +-- test/test_auth_spec.py | 12 +- test/test_binary.py | 12 +- test/test_bson.py | 106 ++++----- test/test_bson_binary_vector.py | 2 +- test/test_bson_corpus.py | 4 +- test/test_bulk.py | 6 +- test/test_causal_consistency.py | 1 + test/test_change_stream.py | 30 +-- test/test_client.py | 69 +++--- test/test_client_backpressure.py | 4 +- test/test_client_bulk_write.py | 19 +- test/test_client_context.py | 4 +- test/test_client_metadata.py | 8 +- test/test_code.py | 8 +- test/test_collation.py | 5 +- test/test_collection.py | 33 +-- test/test_collection_management.py | 1 + test/test_command_logging.py | 1 + test/test_command_monitoring.py | 1 + test/test_comment.py | 4 +- test/test_common.py | 4 +- test/test_compression_support.py | 13 +- test/test_connection_logging.py | 1 + test/test_connection_monitoring.py | 19 +- ...nnections_survive_primary_stepdown_spec.py | 13 +- test/test_crud_unified.py | 1 + test/test_crud_v1.py | 4 +- test/test_csot.py | 8 +- test/test_cursor.py | 22 +- test/test_custom_types.py | 16 +- test/test_daemon.py | 4 +- test/test_database.py | 21 +- test/test_dbref.py | 3 +- test/test_decimal128.py | 4 +- test/test_default_exports.py | 1 + test/test_discovery_and_monitoring.py | 51 ++--- test/test_dns.py | 17 +- test/test_encryption.py | 65 +++--- test/test_errors.py | 3 +- test/test_event_loggers.py | 4 +- test/test_examples.py | 7 +- test/test_fork.py | 4 +- test/test_grid_file.py | 8 +- test/test_gridfs.py | 11 +- test/test_gridfs_bucket.py | 11 +- test/test_gridfs_spec.py | 1 + test/test_heartbeat_monitoring.py | 7 +- test/test_index_management.py | 11 +- test/test_json_util.py | 8 +- test/test_json_util_integration.py | 7 +- test/test_load_balancer.py | 9 +- test/test_logger.py | 2 +- test/test_max_staleness.py | 6 +- test/test_mongos_load_balancing.py | 10 +- test/test_monitor.py | 4 +- test/test_monitoring.py | 19 +- test/test_objectid.py | 6 +- test/test_ocsp_cache.py | 4 +- test/test_ocsp_support.py | 4 +- test/test_on_demand_csfle.py | 4 +- test/test_operations.py | 4 +- test/test_periodic_executor.py | 3 +- test/test_pooling.py | 8 +- test/test_pymongo.py | 4 +- test/test_raw_bson.py | 3 +- test/test_read_concern.py | 6 +- test/test_read_preferences.py | 30 +-- test/test_read_write_concern_spec.py | 8 +- test/test_replica_set_reconfig.py | 6 +- test/test_results.py | 4 +- test/test_retryable_reads.py | 28 +-- test/test_retryable_reads_unified.py | 1 + test/test_retryable_writes.py | 38 ++-- test/test_retryable_writes_unified.py | 1 + test/test_run_command.py | 2 + test/test_saslprep.py | 7 +- test/test_sdam_monitoring_spec.py | 20 +- test/test_server.py | 4 +- test/test_server_description.py | 4 +- test/test_server_selection.py | 1 + test/test_server_selection_in_window.py | 15 +- test/test_server_selection_logging.py | 1 + test/test_server_selection_rtt.py | 4 +- test/test_session.py | 29 +-- test/test_sessions_unified.py | 1 + test/test_son.py | 6 +- test/test_srv_polling.py | 9 +- test/test_ssl.py | 15 +- test/test_streaming_protocol.py | 6 +- test/test_threads.py | 2 + test/test_timestamp.py | 4 +- test/test_topology.py | 10 +- test/test_transactions.py | 17 +- test/test_transactions_unified.py | 1 + test/test_typing.py | 54 +++-- test/test_typing_strict.py | 5 +- test/test_unified_format.py | 3 +- test/test_uri_parser.py | 14 +- test/test_uri_spec.py | 6 +- test/test_versioned_api.py | 4 +- test/test_versioned_api_integration.py | 4 +- test/test_write_concern.py | 1 + test/unicode/test_utf8.py | 3 +- test/unified_format.py | 85 ++++---- test/unified_format_shared.py | 28 +-- test/utils.py | 3 +- test/utils_selection_tests.py | 17 +- test/utils_selection_tests_shared.py | 1 + test/utils_shared.py | 16 +- test/utils_spec_runner.py | 9 +- test/version.py | 1 + tools/clean.py | 1 + tools/convert_test_to_async.py | 4 +- tools/ensure_future_annotations_import.py | 4 +- tools/fail_if_no_c.py | 1 + tools/synchro.py | 39 ++-- 379 files changed, 2242 insertions(+), 2307 deletions(-) diff --git a/.evergreen/scripts/generate_config_utils.py b/.evergreen/scripts/generate_config_utils.py index ee5da4bcd6..50fd8526aa 100644 --- a/.evergreen/scripts/generate_config_utils.py +++ b/.evergreen/scripts/generate_config_utils.py @@ -106,7 +106,7 @@ def create_variant_generic( task_refs.append(t) else: task_refs.append(EvgTaskRef(name=t)) - expansions = expansions and expansions.copy() or dict() + expansions = (expansions and expansions.copy()) or dict() if "run_on" in kwargs: run_on = kwargs.pop("run_on") elif host: @@ -137,15 +137,12 @@ def create_variant( expansions: dict | None = None, **kwargs: Any, ) -> BuildVariant: - expansions = expansions and expansions.copy() or dict() + expansions = (expansions and expansions.copy()) or dict() if version: expansions["VERSION"] = version # 8.0+ Windows builds must run on win-latest - if ( - "win64" in display_name.lower() - or "win32" in display_name.lower() - and version - and version >= "8.0" + if "win64" in display_name.lower() or ( + "win32" in display_name.lower() and version and version >= "8.0" ): kwargs["run_on"] = HOSTS["win-latest"].run_on return create_variant_generic( @@ -166,7 +163,7 @@ def get_versions_until(max_version: str) -> list[str]: max_version_float = float(max_version) versions = [v for v in ALL_VERSIONS if v not in ["rapid", "latest"]] versions = [v for v in versions if float(v) <= max_version_float] - if not len(versions): + if not versions: raise ValueError(f"No server versions found less <= {max_version}") return versions diff --git a/.evergreen/scripts/kms_tester.py b/.evergreen/scripts/kms_tester.py index 9bafdc1f64..65c0f3fc92 100644 --- a/.evergreen/scripts/kms_tester.py +++ b/.evergreen/scripts/kms_tester.py @@ -99,9 +99,9 @@ def setup_kms(sub_test_name: str) -> None: os.environ["AZUREKMS_VMNAME_PREFIX"] = "PYTHON_DRIVER" # Found using "az vm image list --output table" - os.environ[ - "AZUREKMS_IMAGE" - ] = "Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest" + os.environ["AZUREKMS_IMAGE"] = ( + "Canonical:0001-com-ubuntu-server-jammy:22_04-lts-gen2:latest" + ) else: os.environ["GCPKMS_IMAGEFAMILY"] = "debian-12" @@ -127,9 +127,9 @@ def test_kms_send_to_remote(sub_test_name: str) -> None: if sub_test_name == "azure": key_name = os.environ["KEY_NAME"] key_vault_endpoint = os.environ["KEY_VAULT_ENDPOINT"] - env[ - "AZUREKMS_CMD" - ] = f'KEY_NAME="{key_name}" KEY_VAULT_ENDPOINT="{key_vault_endpoint}" bash ./.evergreen/just.sh run-tests' + env["AZUREKMS_CMD"] = ( + f'KEY_NAME="{key_name}" KEY_VAULT_ENDPOINT="{key_vault_endpoint}" bash ./.evergreen/just.sh run-tests' + ) else: env["GCPKMS_CMD"] = "./.evergreen/just.sh run-tests" cmd = f"{DIRS[sub_test_name]}/run-command.sh" diff --git a/.evergreen/scripts/oidc_tester.py b/.evergreen/scripts/oidc_tester.py index a8949a9381..e4df91f350 100644 --- a/.evergreen/scripts/oidc_tester.py +++ b/.evergreen/scripts/oidc_tester.py @@ -87,9 +87,9 @@ def test_oidc_send_to_remote(sub_test_name: str) -> None: if sub_test_name in ["azure", "gcp"]: upper_name = sub_test_name.upper() env[f"{upper_name}OIDC_DRIVERS_TAR_FILE"] = TMP_DRIVER_FILE - env[ - f"{upper_name}OIDC_TEST_CMD" - ] = f"OIDC_ENV={sub_test_name} ./.evergreen/run-mongodb-oidc-test.sh" + env[f"{upper_name}OIDC_TEST_CMD"] = ( + f"OIDC_ENV={sub_test_name} ./.evergreen/run-mongodb-oidc-test.sh" + ) elif sub_test_name in K8S_NAMES: env["K8S_DRIVERS_TAR_FILE"] = TMP_DRIVER_FILE env["K8S_TEST_CMD"] = "OIDC_ENV=k8s ./.evergreen/run-mongodb-oidc-test.sh" diff --git a/.evergreen/scripts/resync-all-specs.py b/.evergreen/scripts/resync-all-specs.py index 9886650a9d..238cc51bcb 100644 --- a/.evergreen/scripts/resync-all-specs.py +++ b/.evergreen/scripts/resync-all-specs.py @@ -20,8 +20,8 @@ def resync_specs(directory: pathlib.Path, errored: dict[str, str]) -> None: if spec.name in ["asynchronous"]: continue try: - subprocess.run( - ["bash", "./.evergreen/resync-specs.sh", spec.name], # noqa: S603, S607 + subprocess.run( # noqa: S603 + ["bash", "./.evergreen/resync-specs.sh", spec.name], # noqa: S607 capture_output=True, text=True, check=True, @@ -34,7 +34,7 @@ def resync_specs(directory: pathlib.Path, errored: dict[str, str]) -> None: def apply_patches(errored): print("Beginning to apply patches") subprocess.run( - ["bash", "./.evergreen/remove-unimplemented-tests.sh"], # noqa: S603, S607 + ["bash", "./.evergreen/remove-unimplemented-tests.sh"], # noqa: S607 check=True, ) try: @@ -44,8 +44,8 @@ def apply_patches(errored): patches = [str(p) for p in spec_patch_dir.glob("*.patch")] patches += [str(p) for p in spec_patch_dir.glob("permanent/*.patch")] if patches: - subprocess.run( - [ # noqa: S603, S607 + subprocess.run( # noqa: S603 + [ # noqa: S607 "git", "apply", "--allow-empty", @@ -93,7 +93,7 @@ def write_summary(errored: dict[str, str], new: list[str], filename: str | None) pr_body = "" # Avoid shell=True and complex pipes by using Python to process git output process = subprocess.run( - ["git", "diff", "--name-only"], # noqa: S603, S607 + ["git", "diff", "--name-only"], # noqa: S607 capture_output=True, text=True, check=True, diff --git a/.evergreen/scripts/run_tests.py b/.evergreen/scripts/run_tests.py index fcf5fe76cd..49986c67ab 100644 --- a/.evergreen/scripts/run_tests.py +++ b/.evergreen/scripts/run_tests.py @@ -177,7 +177,7 @@ def run() -> None: if TEST_NAME == "doctest": from sphinx.cmd.build import main - result = main("-E -b doctest doc ./doc/_build/doctest".split()) + result = main(["-E", "-b", "doctest", "doc", "./doc/_build/doctest"]) sys.exit(result) # Send ecs tests to run remotely. diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 00026f8661..b4db99e165 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,7 +20,7 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.1.3 + rev: v0.15.16 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -35,7 +35,7 @@ repos: require_serial: true fail_fast: true additional_dependencies: - - ruff==0.1.3 + - ruff==0.15.16 - unasync - repo: https://github.com/adamchainz/blacken-docs diff --git a/_setup.py b/_setup.py index f99e9e7dc8..f8536b3371 100644 --- a/_setup.py +++ b/_setup.py @@ -71,7 +71,7 @@ def run(self): if os.environ.get("PYMONGO_C_EXT_MUST_BUILD"): raise e = sys.exc_info()[1] - sys.stdout.write("%s\n" % str(e)) + sys.stdout.write(f"{e!s}\n") warnings.warn( self.warning_message % ( @@ -94,7 +94,7 @@ def build_extension(self, ext): if os.environ.get("PYMONGO_C_EXT_MUST_BUILD"): raise e = sys.exc_info()[1] - sys.stdout.write("%s\n" % str(e)) + sys.stdout.write(f"{e!s}\n") warnings.warn( self.warning_message % ( diff --git a/bson/__init__.py b/bson/__init__.py index ebb1bd0ccc..793c2bbd8f 100644 --- a/bson/__init__.py +++ b/bson/__init__.py @@ -69,6 +69,7 @@ .. [#bytes] The bytes type is encoded as BSON binary with subtype 0. It will be decoded back to bytes. """ + from __future__ import annotations import datetime @@ -81,21 +82,15 @@ from codecs import utf_8_decode as _utf_8_decode from codecs import utf_8_encode as _utf_8_encode from collections import abc as _abc +from collections.abc import Generator, Iterator, Mapping, MutableMapping, Sequence from typing import ( IO, TYPE_CHECKING, Any, BinaryIO, Callable, - Generator, - Iterator, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, - Tuple, - Type, TypeVar, Union, cast, @@ -152,65 +147,65 @@ __all__ = [ "ALL_UUID_SUBTYPES", + "BSON", + "BSONARR", + "BSONBIN", + "BSONBOO", + "BSONCOD", + "BSONCWS", + "BSONDAT", + "BSONDEC", + "BSONINT", + "BSONLON", + "BSONMAX", + "BSONMIN", + "BSONNUL", + "BSONNUM", + "BSONOBJ", + "BSONOID", + "BSONREF", + "BSONRGX", + "BSONSTR", + "BSONSYM", + "BSONTIM", + "BSONUND", "CSHARP_LEGACY", + "DEFAULT_CODEC_OPTIONS", + "EPOCH_AWARE", + "EPOCH_NAIVE", "JAVA_LEGACY", "OLD_UUID_SUBTYPE", + "RE_TYPE", + "SON", "STANDARD", "UUID_SUBTYPE", "Binary", - "UuidRepresentation", "Code", - "DEFAULT_CODEC_OPTIONS", "CodecOptions", "DBRef", + "DatetimeConversion", + "DatetimeMS", "Decimal128", + "Int64", "InvalidBSON", "InvalidDocument", "InvalidStringData", - "Int64", "MaxKey", "MinKey", "ObjectId", "Regex", - "RE_TYPE", - "SON", "Timestamp", - "utc", - "EPOCH_AWARE", - "EPOCH_NAIVE", - "BSONNUM", - "BSONSTR", - "BSONOBJ", - "BSONARR", - "BSONBIN", - "BSONUND", - "BSONOID", - "BSONBOO", - "BSONDAT", - "BSONNUL", - "BSONRGX", - "BSONREF", - "BSONCOD", - "BSONSYM", - "BSONCWS", - "BSONINT", - "BSONTIM", - "BSONLON", - "BSONDEC", - "BSONMIN", - "BSONMAX", - "get_data_and_view", - "gen_list_name", - "encode", + "UuidRepresentation", "decode", "decode_all", - "decode_iter", "decode_file_iter", - "is_valid", - "BSON", + "decode_iter", + "encode", + "gen_list_name", + "get_data_and_view", "has_c", - "DatetimeConversion", - "DatetimeMS", + "is_valid", + "utc", ] BSONNUM = b"\x01" # Floating point @@ -222,18 +217,18 @@ BSONOID = b"\x07" # ObjectId BSONBOO = b"\x08" # Boolean BSONDAT = b"\x09" # UTC Datetime -BSONNUL = b"\x0A" # Null -BSONRGX = b"\x0B" # Regex -BSONREF = b"\x0C" # DBRef -BSONCOD = b"\x0D" # Javascript code -BSONSYM = b"\x0E" # Symbol -BSONCWS = b"\x0F" # Javascript code with scope +BSONNUL = b"\x0a" # Null +BSONRGX = b"\x0b" # Regex +BSONREF = b"\x0c" # DBRef +BSONCOD = b"\x0d" # Javascript code +BSONSYM = b"\x0e" # Symbol +BSONCWS = b"\x0f" # Javascript code with scope BSONINT = b"\x10" # 32bit int BSONTIM = b"\x11" # Timestamp BSONLON = b"\x12" # 64bit int BSONDEC = b"\x13" # Decimal128 -BSONMIN = b"\xFF" # Min key -BSONMAX = b"\x7F" # Max key +BSONMIN = b"\xff" # Min key +BSONMAX = b"\x7f" # Max key _UNPACK_FLOAT_FROM = struct.Struct(" Tuple[Any, memoryview]: +def get_data_and_view(data: Any) -> tuple[Any, memoryview]: if isinstance(data, (bytes, bytearray)): return data, memoryview(data) view = memoryview(data) @@ -254,19 +249,19 @@ def get_data_and_view(data: Any) -> Tuple[Any, memoryview]: def _raise_unknown_type(element_type: int, element_name: str) -> NoReturn: """Unknown type helper.""" raise InvalidBSON( - "Detected unknown BSON type {!r} for fieldname '{}'. Are " - "you using the latest driver version?".format(chr(element_type).encode(), element_name) + f"Detected unknown BSON type {chr(element_type).encode()!r} for fieldname '{element_name}'. Are " + "you using the latest driver version?" ) def _get_int( data: Any, _view: Any, position: int, dummy0: Any, dummy1: Any, dummy2: Any -) -> Tuple[int, int]: +) -> tuple[int, int]: """Decode a BSON int32 to python int.""" return _UNPACK_INT_FROM(data, position)[0], position + 4 -def _get_c_string(data: Any, view: Any, position: int, opts: CodecOptions[Any]) -> Tuple[str, int]: +def _get_c_string(data: Any, view: Any, position: int, opts: CodecOptions[Any]) -> tuple[str, int]: """Decode a BSON 'C' string to python str.""" end = data.index(b"\x00", position) return _utf_8_decode(view[position:end], opts.unicode_decode_error_handler, True)[0], end + 1 @@ -274,14 +269,14 @@ def _get_c_string(data: Any, view: Any, position: int, opts: CodecOptions[Any]) def _get_float( data: Any, _view: Any, position: int, dummy0: Any, dummy1: Any, dummy2: Any -) -> Tuple[float, int]: +) -> tuple[float, int]: """Decode a BSON double to python float.""" return _UNPACK_FLOAT_FROM(data, position)[0], position + 8 def _get_string( data: Any, view: Any, position: int, obj_end: int, opts: CodecOptions[Any], dummy: Any -) -> Tuple[str, int]: +) -> tuple[str, int]: """Decode a BSON string to python str.""" length = _UNPACK_INT_FROM(data, position)[0] position += 4 @@ -293,7 +288,7 @@ def _get_string( return _utf_8_decode(view[position:end], opts.unicode_decode_error_handler, True)[0], end + 1 -def _get_object_size(data: Any, position: int, obj_end: int) -> Tuple[int, int]: +def _get_object_size(data: Any, position: int, obj_end: int) -> tuple[int, int]: """Validate and return a BSON document's size.""" try: obj_size = _UNPACK_INT_FROM(data, position)[0] @@ -312,7 +307,7 @@ def _get_object_size(data: Any, position: int, obj_end: int) -> Tuple[int, int]: def _get_object( data: Any, view: Any, position: int, obj_end: int, opts: CodecOptions[Any], dummy: Any -) -> Tuple[Any, int]: +) -> tuple[Any, int]: """Decode a BSON subdocument to opts.document_class or bson.dbref.DBRef.""" obj_size, end = _get_object_size(data, position, obj_end) if _raw_document_class(opts.document_class): @@ -333,7 +328,7 @@ def _get_object( def _get_array( data: Any, view: Any, position: int, obj_end: int, opts: CodecOptions[Any], element_name: str -) -> Tuple[Any, int]: +) -> tuple[Any, int]: """Decode a BSON array to python list.""" size = _UNPACK_INT_FROM(data, position)[0] end = position + size - 1 @@ -375,7 +370,7 @@ def _get_array( def _get_binary( data: Any, _view: Any, position: int, obj_end: int, opts: CodecOptions[Any], dummy1: Any -) -> Tuple[Union[Binary, uuid.UUID], int]: +) -> tuple[Union[Binary, uuid.UUID], int]: """Decode a BSON binary to bson.binary.Binary or python UUID.""" length, subtype = _UNPACK_LENGTH_SUBTYPE_FROM(data, position) position += 5 @@ -412,7 +407,7 @@ def _get_binary( def _get_oid( data: Any, _view: Any, position: int, dummy0: Any, dummy1: Any, dummy2: Any -) -> Tuple[ObjectId, int]: +) -> tuple[ObjectId, int]: """Decode a BSON ObjectId to bson.objectid.ObjectId.""" end = position + 12 return ObjectId(data[position:end]), end @@ -420,7 +415,7 @@ def _get_oid( def _get_boolean( data: Any, _view: Any, position: int, dummy0: Any, dummy1: Any, dummy2: Any -) -> Tuple[bool, int]: +) -> tuple[bool, int]: """Decode a BSON true/false to python True/False.""" end = position + 1 boolean_byte = data[position:end] @@ -428,19 +423,19 @@ def _get_boolean( return False, end elif boolean_byte == b"\x01": return True, end - raise InvalidBSON("invalid boolean value: %r" % boolean_byte) + raise InvalidBSON(f"invalid boolean value: {boolean_byte!r}") def _get_date( data: Any, _view: Any, position: int, dummy0: int, opts: CodecOptions[Any], dummy1: Any -) -> Tuple[Union[datetime.datetime, DatetimeMS], int]: +) -> tuple[Union[datetime.datetime, DatetimeMS], int]: """Decode a BSON datetime to python datetime.datetime.""" return _millis_to_datetime(_UNPACK_LONG_FROM(data, position)[0], opts), position + 8 def _get_code( data: Any, view: Any, position: int, obj_end: int, opts: CodecOptions[Any], element_name: str -) -> Tuple[Code, int]: +) -> tuple[Code, int]: """Decode a BSON code to bson.code.Code.""" code, position = _get_string(data, view, position, obj_end, opts, element_name) return Code(code), position @@ -448,7 +443,7 @@ def _get_code( def _get_code_w_scope( data: Any, view: Any, position: int, _obj_end: int, opts: CodecOptions[Any], element_name: str -) -> Tuple[Code, int]: +) -> tuple[Code, int]: """Decode a BSON code_w_scope to bson.code.Code.""" code_end = position + _UNPACK_INT_FROM(data, position)[0] code, position = _get_string(data, view, position + 4, code_end, opts, element_name) @@ -460,7 +455,7 @@ def _get_code_w_scope( def _get_regex( data: Any, view: Any, position: int, dummy0: Any, opts: CodecOptions[Any], dummy1: Any -) -> Tuple[Regex[Any], int]: +) -> tuple[Regex[Any], int]: """Decode a BSON regex to bson.regex.Regex or a python pattern object.""" pattern, position = _get_c_string(data, view, position, opts) bson_flags, position = _get_c_string(data, view, position, opts) @@ -470,7 +465,7 @@ def _get_regex( def _get_ref( data: Any, view: Any, position: int, obj_end: int, opts: CodecOptions[Any], element_name: str -) -> Tuple[DBRef, int]: +) -> tuple[DBRef, int]: """Decode (deprecated) BSON DBPointer to bson.dbref.DBRef.""" collection, position = _get_string(data, view, position, obj_end, opts, element_name) oid, position = _get_oid(data, view, position, obj_end, opts, element_name) @@ -479,7 +474,7 @@ def _get_ref( def _get_timestamp( data: Any, _view: Any, position: int, dummy0: Any, dummy1: Any, dummy2: Any -) -> Tuple[Timestamp, int]: +) -> tuple[Timestamp, int]: """Decode a BSON timestamp to bson.timestamp.Timestamp.""" inc, timestamp = _UNPACK_TIMESTAMP_FROM(data, position) return Timestamp(timestamp, inc), position + 8 @@ -487,14 +482,14 @@ def _get_timestamp( def _get_int64( data: Any, _view: Any, position: int, dummy0: Any, dummy1: Any, dummy2: Any -) -> Tuple[Int64, int]: +) -> tuple[Int64, int]: """Decode a BSON int64 to bson.int64.Int64.""" return Int64(_UNPACK_LONG_FROM(data, position)[0]), position + 8 def _get_decimal128( data: Any, _view: Any, position: int, dummy0: Any, dummy1: Any, dummy2: Any -) -> Tuple[Decimal128, int]: +) -> tuple[Decimal128, int]: """Decode a BSON decimal128 to bson.decimal128.Decimal128.""" end = position + 16 return Decimal128.from_bid(data[position:end]), end @@ -506,7 +501,7 @@ def _get_decimal128( # - position: int, beginning of object in 'data' to decode # - obj_end: int, end of object to decode in 'data' if variable-length type # - opts: a CodecOptions -_ELEMENT_GETTER: dict[int, Callable[..., Tuple[Any, int]]] = { +_ELEMENT_GETTER: dict[int, Callable[..., tuple[Any, int]]] = { ord(BSONNUM): _get_float, ord(BSONSTR): _get_string, ord(BSONOBJ): _get_object, @@ -540,9 +535,9 @@ def _element_to_dict( obj_end: int, opts: CodecOptions[Any], raw_array: bool = False, - ) -> Tuple[str, Any, int]: + ) -> tuple[str, Any, int]: return cast( - "Tuple[str, Any, int]", + "tuple[str, Any, int]", _cbson._element_to_dict(data, position, obj_end, opts, raw_array), ) @@ -555,7 +550,7 @@ def _element_to_dict( obj_end: int, opts: CodecOptions[Any], raw_array: bool = False, - ) -> Tuple[str, Any, int]: + ) -> tuple[str, Any, int]: """Decode a single key, value pair.""" element_type = data[position] position += 1 @@ -670,7 +665,7 @@ def _make_c_string_check(string: Union[str, bytes]) -> bytes: return string + b"\x00" except UnicodeError: raise InvalidStringData( - "strings in documents must be valid UTF-8: %r" % string + f"strings in documents must be valid UTF-8: {string!r}" ) from None else: if "\x00" in string: @@ -686,7 +681,7 @@ def _make_c_string(string: Union[str, bytes]) -> bytes: return string + b"\x00" except UnicodeError: raise InvalidStringData( - "strings in documents must be valid UTF-8: %r" % string + f"strings in documents must be valid UTF-8: {string!r}" ) from None else: return _utf_8_encode(string)[0] + b"\x00" @@ -772,7 +767,7 @@ def _encode_objectid(name: bytes, value: ObjectId, dummy: Any, dummy1: Any) -> b def _encode_bool(name: bytes, value: bool, dummy0: Any, dummy1: Any) -> bytes: """Encode a python boolean (True/False).""" - return b"\x08" + name + (value and b"\x01" or b"\x00") + return b"\x08" + name + ((value and b"\x01") or b"\x00") def _encode_datetime(name: bytes, value: datetime.datetime, dummy0: Any, dummy1: Any) -> bytes: @@ -789,7 +784,7 @@ def _encode_datetime_ms(name: bytes, value: DatetimeMS, dummy0: Any, dummy1: Any def _encode_none(name: bytes, dummy0: Any, dummy1: Any, dummy2: Any) -> bytes: """Encode python None.""" - return b"\x0A" + name + return b"\x0a" + name def _encode_regex(name: bytes, value: Regex[Any], dummy0: Any, dummy1: Any) -> bytes: @@ -797,9 +792,9 @@ def _encode_regex(name: bytes, value: Regex[Any], dummy0: Any, dummy1: Any) -> b flags = value.flags # Python 3 common case if flags == re.UNICODE: - return b"\x0B" + name + _make_c_string_check(value.pattern) + b"u\x00" + return b"\x0b" + name + _make_c_string_check(value.pattern) + b"u\x00" elif flags == 0: - return b"\x0B" + name + _make_c_string_check(value.pattern) + b"\x00" + return b"\x0b" + name + _make_c_string_check(value.pattern) + b"\x00" else: sflags = b"" if flags & re.IGNORECASE: @@ -815,7 +810,7 @@ def _encode_regex(name: bytes, value: Regex[Any], dummy0: Any, dummy1: Any) -> b if flags & re.VERBOSE: sflags += b"x" sflags += b"\x00" - return b"\x0B" + name + _make_c_string_check(value.pattern) + sflags + return b"\x0b" + name + _make_c_string_check(value.pattern) + sflags def _encode_code(name: bytes, value: Code, dummy: Any, opts: CodecOptions[Any]) -> bytes: @@ -823,10 +818,10 @@ def _encode_code(name: bytes, value: Code, dummy: Any, opts: CodecOptions[Any]) cstring = _make_c_string(value) cstrlen = len(cstring) if value.scope is None: - return b"\x0D" + name + _PACK_INT(cstrlen) + cstring + return b"\x0d" + name + _PACK_INT(cstrlen) + cstring scope = _dict_to_bson(value.scope, False, opts, False) full_length = _PACK_INT(8 + cstrlen + len(scope)) - return b"\x0F" + name + full_length + _PACK_INT(cstrlen) + cstring + scope + return b"\x0f" + name + full_length + _PACK_INT(cstrlen) + cstring + scope def _encode_int(name: bytes, value: int, dummy0: Any, dummy1: Any) -> bytes: @@ -860,12 +855,12 @@ def _encode_decimal128(name: bytes, value: Decimal128, dummy0: Any, dummy1: Any) def _encode_minkey(name: bytes, dummy0: Any, dummy1: Any, dummy2: Any) -> bytes: """Encode bson.min_key.MinKey.""" - return b"\xFF" + name + return b"\xff" + name def _encode_maxkey(name: bytes, dummy0: Any, dummy1: Any, dummy2: Any) -> bytes: """Encode bson.max_key.MaxKey.""" - return b"\x7F" + name + return b"\x7f" + name # Each encoder function's signature is: @@ -904,9 +899,9 @@ def _encode_maxkey(name: bytes, dummy0: Any, dummy1: Any, dummy2: Any) -> bytes: # Map each _type_marker to its encoder for faster lookup. _MARKERS = {} -for _typ in _ENCODERS: +for _typ, _encoder in _ENCODERS.items(): if hasattr(_typ, "_type_marker"): - _MARKERS[_typ._type_marker] = _ENCODERS[_typ] + _MARKERS[_typ._type_marker] = _encoder _BUILT_IN_TYPES = tuple(t for t in _ENCODERS) @@ -1054,13 +1049,11 @@ def encode( @overload -def decode(data: _ReadableBuffer, codec_options: None = None) -> dict[str, Any]: - ... +def decode(data: _ReadableBuffer, codec_options: None = None) -> dict[str, Any]: ... @overload -def decode(data: _ReadableBuffer, codec_options: CodecOptions[_DocumentType]) -> _DocumentType: - ... +def decode(data: _ReadableBuffer, codec_options: CodecOptions[_DocumentType]) -> _DocumentType: ... def decode( @@ -1134,15 +1127,13 @@ def _decode_all(data: _ReadableBuffer, opts: CodecOptions[_DocumentType]) -> lis @overload -def decode_all(data: _ReadableBuffer, codec_options: None = None) -> list[dict[str, Any]]: - ... +def decode_all(data: _ReadableBuffer, codec_options: None = None) -> list[dict[str, Any]]: ... @overload def decode_all( data: _ReadableBuffer, codec_options: CodecOptions[_DocumentType] -) -> list[_DocumentType]: - ... +) -> list[_DocumentType]: ... def decode_all( @@ -1288,13 +1279,13 @@ def _decode_all_selective( @overload -def decode_iter(data: bytes, codec_options: None = None) -> Iterator[dict[str, Any]]: - ... +def decode_iter(data: bytes, codec_options: None = None) -> Iterator[dict[str, Any]]: ... @overload -def decode_iter(data: bytes, codec_options: CodecOptions[_DocumentType]) -> Iterator[_DocumentType]: - ... +def decode_iter( + data: bytes, codec_options: CodecOptions[_DocumentType] +) -> Iterator[_DocumentType]: ... def decode_iter( @@ -1335,15 +1326,13 @@ def decode_iter( @overload def decode_file_iter( file_obj: Union[BinaryIO, IO[bytes]], codec_options: None = None -) -> Iterator[dict[str, Any]]: - ... +) -> Iterator[dict[str, Any]]: ... @overload def decode_file_iter( file_obj: Union[BinaryIO, IO[bytes]], codec_options: CodecOptions[_DocumentType] -) -> Iterator[_DocumentType]: - ... +) -> Iterator[_DocumentType]: ... def decode_file_iter( @@ -1407,7 +1396,7 @@ class BSON(bytes): @classmethod def encode( - cls: Type[BSON], + cls: type[BSON], document: Mapping[str, Any], check_keys: bool = False, codec_options: CodecOptions[Any] = DEFAULT_CODEC_OPTIONS, diff --git a/bson/_helpers.py b/bson/_helpers.py index 5a479867c2..6f05878b5d 100644 --- a/bson/_helpers.py +++ b/bson/_helpers.py @@ -15,9 +15,11 @@ """Setstate and getstate functions for objects with __slots__, allowing compatibility with default pickling protocol """ + from __future__ import annotations -from typing import Any, Mapping +from collections.abc import Mapping +from typing import Any def _setstate_slots(self: Any, state: Any) -> None: diff --git a/bson/binary.py b/bson/binary.py index aa91de4ede..325f71f605 100644 --- a/bson/binary.py +++ b/bson/binary.py @@ -15,8 +15,9 @@ import struct import warnings +from collections.abc import Sequence from enum import Enum -from typing import TYPE_CHECKING, Any, Optional, Sequence, Tuple, Type, Union, overload +from typing import TYPE_CHECKING, Any, Optional, Union, overload from uuid import UUID """Tools for representing BSON binary data. @@ -266,6 +267,8 @@ def __eq__(self, other: Any) -> bool: self.dtype == other.dtype and self.padding == other.padding and self.data == other.data ) + __hash__ = None # type: ignore[assignment] + def __len__(self) -> int: return len(self.data) @@ -307,7 +310,7 @@ class Binary(bytes): __subtype: int def __new__( - cls: Type[Binary], + cls: type[Binary], data: Union[memoryview, bytes, bytearray, _mmap, _array[Any]], subtype: int = BINARY_SUBTYPE, ) -> Binary: @@ -322,7 +325,7 @@ def __new__( @classmethod def from_uuid( - cls: Type[Binary], uuid: UUID, uuid_representation: int = UuidRepresentation.STANDARD + cls: type[Binary], uuid: UUID, uuid_representation: int = UuidRepresentation.STANDARD ) -> Binary: """Create a BSON Binary object from a Python UUID. @@ -421,32 +424,29 @@ def as_uuid(self, uuid_representation: int = UuidRepresentation.STANDARD) -> UUI @classmethod @overload - def from_vector(cls: Type[Binary], vector: BinaryVector) -> Binary: - ... + def from_vector(cls: type[Binary], vector: BinaryVector) -> Binary: ... @classmethod @overload def from_vector( - cls: Type[Binary], + cls: type[Binary], vector: Union[list[int], list[float]], dtype: BinaryVectorDtype, padding: int = 0, - ) -> Binary: - ... + ) -> Binary: ... @classmethod @overload def from_vector( - cls: Type[Binary], + cls: type[Binary], vector: npt.NDArray[np.number], dtype: BinaryVectorDtype, padding: int = 0, - ) -> Binary: - ... + ) -> Binary: ... @classmethod def from_vector( - cls: Type[Binary], + cls: type[Binary], vector: Union[BinaryVector, list[int], list[float], npt.NDArray[np.number]], dtype: Optional[BinaryVectorDtype] = None, padding: Optional[int] = None, @@ -501,7 +501,7 @@ def from_vector( if padding: raise ValueError(f"padding does not apply to {dtype=}") else: - raise NotImplementedError("%s not yet supported" % dtype) + raise NotImplementedError(f"{dtype} not yet supported") data = struct.pack(f"<{len(vector)}{format_str}", *vector) else: # vector is numpy array or incorrect type. try: @@ -532,7 +532,7 @@ def from_vector( else: raise ValueError("Values found outside UINT8 range.") else: - raise NotImplementedError("%s not yet supported" % dtype) + raise NotImplementedError(f"{dtype} not yet supported") data = vector.tobytes() if padding and len(vector) and not (data[-1] & ((1 << padding) - 1)) == 0: @@ -599,7 +599,7 @@ def as_vector(self, return_numpy: bool = False) -> BinaryVector: return BinaryVector(unpacked_uint8s, dtype, padding) else: - raise NotImplementedError("Binary Vector dtype %s not yet supported" % dtype.name) + raise NotImplementedError(f"Binary Vector dtype {dtype.name} not yet supported") else: # create a numpy array try: import numpy as np @@ -629,7 +629,7 @@ def as_vector(self, return_numpy: bool = False) -> BinaryVector: stacklevel=2, ) else: - raise NotImplementedError("Binary Vector dtype %s not yet supported" % dtype.name) + raise NotImplementedError(f"Binary Vector dtype {dtype.name} not yet supported") return BinaryVector(data, dtype, padding) @property @@ -637,7 +637,7 @@ def subtype(self) -> int: """Subtype of this binary data.""" return self.__subtype - def __getnewargs__(self) -> Tuple[bytes, int]: # type: ignore[override] + def __getnewargs__(self) -> tuple[bytes, int]: # type: ignore[override] # Work around http://bugs.python.org/issue7382 data = super().__getnewargs__()[0] if not isinstance(data, bytes): diff --git a/bson/code.py b/bson/code.py index f0523b2a95..983c566a3d 100644 --- a/bson/code.py +++ b/bson/code.py @@ -13,10 +13,12 @@ # limitations under the License. """Tools for representing JavaScript code in BSON.""" + from __future__ import annotations +from collections.abc import Mapping from collections.abc import Mapping as _Mapping -from typing import Any, Mapping, Optional, Type, Union +from typing import Any, Optional, Union class Code(str): @@ -50,7 +52,7 @@ class Code(str): __scope: Union[Mapping[str, Any], None] def __new__( - cls: Type[Code], + cls: type[Code], code: Union[str, Code], scope: Optional[Mapping[str, Any]] = None, **kwargs: Any, diff --git a/bson/codec_options.py b/bson/codec_options.py index 569ac55026..f641efefdf 100644 --- a/bson/codec_options.py +++ b/bson/codec_options.py @@ -13,23 +13,21 @@ # limitations under the License. """Tools for specifying BSON codec options.""" + from __future__ import annotations import abc import datetime import enum +from collections.abc import Iterable, Mapping from collections.abc import MutableMapping as _MutableMapping from typing import ( TYPE_CHECKING, Any, Callable, Generic, - Iterable, - Mapping, NamedTuple, Optional, - Tuple, - Type, Union, cast, ) @@ -144,7 +142,7 @@ def __init__( if self._fallback_encoder is not None: if not callable(fallback_encoder): - raise TypeError("fallback_encoder %r is not a callable" % (fallback_encoder)) + raise TypeError(f"fallback_encoder {fallback_encoder!r} is not a callable") for codec in self.__type_codecs: is_valid_codec = False @@ -182,11 +180,7 @@ def _validate_type_encoder(self, codec: _Codec) -> None: raise TypeError(err_msg) def __repr__(self) -> str: - return "{}(type_codecs={!r}, fallback_encoder={!r})".format( - self.__class__.__name__, - self.__type_codecs, - self._fallback_encoder, - ) + return f"{self.__class__.__name__}(type_codecs={self.__type_codecs!r}, fallback_encoder={self._fallback_encoder!r})" def __eq__(self, other: Any) -> Any: if not isinstance(other, type(self)): @@ -197,6 +191,8 @@ def __eq__(self, other: Any) -> Any: and (self._fallback_encoder == other._fallback_encoder) ) + __hash__ = None # type: ignore[assignment] + class DatetimeConversion(int, enum.Enum): """Options for decoding BSON datetimes.""" @@ -234,7 +230,7 @@ class DatetimeConversion(int, enum.Enum): class _BaseCodecOptions(NamedTuple): - document_class: Type[Mapping[str, Any]] + document_class: type[Mapping[str, Any]] tz_aware: bool uuid_representation: int unicode_decode_error_handler: str @@ -245,8 +241,8 @@ class _BaseCodecOptions(NamedTuple): if TYPE_CHECKING: - class CodecOptions(Tuple[_DocumentType], Generic[_DocumentType]): - document_class: Type[_DocumentType] + class CodecOptions(tuple[_DocumentType], Generic[_DocumentType]): + document_class: type[_DocumentType] tz_aware: bool uuid_representation: int unicode_decode_error_handler: Optional[str] @@ -255,37 +251,31 @@ class CodecOptions(Tuple[_DocumentType], Generic[_DocumentType]): datetime_conversion: Optional[int] def __new__( - cls: Type[CodecOptions[_DocumentType]], - document_class: Optional[Type[_DocumentType]] = ..., + cls: type[CodecOptions[_DocumentType]], + document_class: Optional[type[_DocumentType]] = ..., tz_aware: bool = ..., uuid_representation: Optional[int] = ..., unicode_decode_error_handler: Optional[str] = ..., tzinfo: Optional[datetime.tzinfo] = ..., type_registry: Optional[TypeRegistry] = ..., datetime_conversion: Optional[int] = ..., - ) -> CodecOptions[_DocumentType]: - ... + ) -> CodecOptions[_DocumentType]: ... # CodecOptions API - def with_options(self, **kwargs: Any) -> CodecOptions[Any]: - ... + def with_options(self, **kwargs: Any) -> CodecOptions[Any]: ... - def _arguments_repr(self) -> str: - ... + def _arguments_repr(self) -> str: ... # NamedTuple API @classmethod - def _make(cls, obj: Iterable[Any]) -> CodecOptions[_DocumentType]: - ... + def _make(cls, obj: Iterable[Any]) -> CodecOptions[_DocumentType]: ... - def _asdict(self) -> dict[str, Any]: - ... + def _asdict(self) -> dict[str, Any]: ... - def _replace(self, **kwargs: Any) -> CodecOptions[_DocumentType]: - ... + def _replace(self, **kwargs: Any) -> CodecOptions[_DocumentType]: ... _source: str - _fields: Tuple[str] + _fields: tuple[str] else: @@ -377,8 +367,8 @@ def __init__(self, *args, **kwargs): super().__init__() def __new__( - cls: Type[CodecOptions], - document_class: Optional[Type[Mapping[str, Any]]] = None, + cls: type[CodecOptions], + document_class: Optional[type[Mapping[str, Any]]] = None, tz_aware: bool = False, uuid_representation: Optional[int] = UuidRepresentation.UNSPECIFIED, unicode_decode_error_handler: str = "strict", @@ -450,17 +440,9 @@ def _arguments_repr(self) -> str: ) return ( - "document_class={}, tz_aware={!r}, uuid_representation={}, " - "unicode_decode_error_handler={!r}, tzinfo={!r}, " - "type_registry={!r}, datetime_conversion={!s}".format( - document_class_repr, - self.tz_aware, - uuid_rep_repr, - self.unicode_decode_error_handler, - self.tzinfo, - self.type_registry, - self.datetime_conversion, - ) + f"document_class={document_class_repr}, tz_aware={self.tz_aware!r}, uuid_representation={uuid_rep_repr}, " + f"unicode_decode_error_handler={self.unicode_decode_error_handler!r}, tzinfo={self.tzinfo!r}, " + f"type_registry={self.type_registry!r}, datetime_conversion={self.datetime_conversion!s}" ) def __repr__(self) -> str: diff --git a/bson/datetime_ms.py b/bson/datetime_ms.py index 2047bd30b2..f5a38e63f6 100644 --- a/bson/datetime_ms.py +++ b/bson/datetime_ms.py @@ -16,6 +16,7 @@ .. versionadded:: 4.3 """ + from __future__ import annotations import calendar diff --git a/bson/dbref.py b/bson/dbref.py index 40bdb73cff..a6bcb467ab 100644 --- a/bson/dbref.py +++ b/bson/dbref.py @@ -13,10 +13,12 @@ # limitations under the License. """Tools for manipulating DBRefs (references to MongoDB documents).""" + from __future__ import annotations +from collections.abc import Mapping from copy import deepcopy -from typing import Any, Mapping, Optional +from typing import Any, Optional from bson._helpers import _getstate_slots, _setstate_slots from bson.son import SON @@ -25,7 +27,7 @@ class DBRef: """A reference to a document stored in MongoDB.""" - __slots__ = "__collection", "__id", "__database", "__kwargs" + __slots__ = "__collection", "__database", "__id", "__kwargs" __getstate__ = _getstate_slots __setstate__ = _setstate_slots # DBRef isn't actually a BSON "type" so this number was arbitrarily chosen. diff --git a/bson/decimal128.py b/bson/decimal128.py index 7480f94d0a..efdbecb3fc 100644 --- a/bson/decimal128.py +++ b/bson/decimal128.py @@ -16,12 +16,14 @@ .. versionadded:: 3.4 """ + from __future__ import annotations import decimal import struct +from collections.abc import Sequence from decimal import Decimal -from typing import Any, Sequence, Tuple, Type, Union +from typing import Any, Union from bson.codec_options import TypeDecoder, TypeEncoder @@ -58,7 +60,7 @@ } _DEC128_CTX = decimal.Context(**_CTX_OPTIONS.copy()) # type: ignore -_VALUE_OPTIONS = Union[decimal.Decimal, float, str, Tuple[int, Sequence[int], int]] +_VALUE_OPTIONS = Union[decimal.Decimal, float, str, tuple[int, Sequence[int], int]] class DecimalEncoder(TypeEncoder): @@ -72,7 +74,7 @@ class DecimalEncoder(TypeEncoder): """ @property - def python_type(self) -> Type[Decimal]: + def python_type(self) -> type[Decimal]: return Decimal def transform_python(self, value: Any) -> Decimal128: @@ -90,7 +92,7 @@ class DecimalDecoder(TypeDecoder): """ @property - def bson_type(self) -> Type[Decimal128]: + def bson_type(self) -> type[Decimal128]: return Decimal128 def transform_bson(self, value: Any) -> decimal.Decimal: @@ -106,7 +108,7 @@ def create_decimal128_context() -> decimal.Context: return decimal.Context(**opts) # type: ignore -def _decimal_to_128(value: _VALUE_OPTIONS) -> Tuple[int, int]: +def _decimal_to_128(value: _VALUE_OPTIONS) -> tuple[int, int]: """Converts a decimal.Decimal to BID (high bits, low bits). :param value: An instance of decimal.Decimal @@ -308,7 +310,7 @@ def to_decimal(self) -> decimal.Decimal: return ctx.create_decimal((sign, digits, exponent)) @classmethod - def from_bid(cls: Type[Decimal128], value: bytes) -> Decimal128: + def from_bid(cls: type[Decimal128], value: bytes) -> Decimal128: """Create an instance of :class:`Decimal128` from Binary Integer Decimal string. @@ -336,10 +338,10 @@ def __str__(self) -> str: def __repr__(self) -> str: return f"Decimal128('{self!s}')" - def __setstate__(self, value: Tuple[int, int]) -> None: + def __setstate__(self, value: tuple[int, int]) -> None: self.__high, self.__low = value - def __getstate__(self) -> Tuple[int, int]: + def __getstate__(self) -> tuple[int, int]: return self.__high, self.__low def __eq__(self, other: Any) -> bool: @@ -347,5 +349,7 @@ def __eq__(self, other: Any) -> bool: return self.bid == other.bid return NotImplemented + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: return not self == other diff --git a/bson/errors.py b/bson/errors.py index ffc117f7ac..a9e082ec11 100644 --- a/bson/errors.py +++ b/bson/errors.py @@ -13,6 +13,7 @@ # limitations under the License. """Exceptions raised by the BSON package.""" + from __future__ import annotations from typing import Any, Optional diff --git a/bson/int64.py b/bson/int64.py index 5846504a2d..a1a77b5b73 100644 --- a/bson/int64.py +++ b/bson/int64.py @@ -13,6 +13,7 @@ # limitations under the License. """A BSON wrapper for long (int in python3)""" + from __future__ import annotations from typing import Any diff --git a/bson/json_util.py b/bson/json_util.py index 0b59c406dd..b387b7a139 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -99,6 +99,7 @@ `libbson `_. `python-bsonjs` works best with PyMongo when using :class:`~bson.raw_bson.RawBSONDocument`. """ + from __future__ import annotations import base64 @@ -107,16 +108,12 @@ import math import re import uuid +from collections.abc import Mapping, MutableMapping, Sequence from typing import ( TYPE_CHECKING, Any, Callable, - Mapping, - MutableMapping, Optional, - Sequence, - Tuple, - Type, Union, cast, ) @@ -244,7 +241,7 @@ class JSONOptions(_BASE_CLASS): strict_number_long: bool datetime_representation: int strict_uuid: bool - document_class: Type[MutableMapping[str, Any]] + document_class: type[MutableMapping[str, Any]] def __init__(self, *args: Any, **kwargs: Any): """Encapsulates JSON options for :func:`dumps` and :func:`loads`. @@ -303,7 +300,7 @@ def __init__(self, *args: Any, **kwargs: Any): super().__init__() def __new__( - cls: Type[JSONOptions], + cls: type[JSONOptions], strict_number_long: Optional[bool] = None, datetime_representation: Optional[int] = None, strict_uuid: Optional[bool] = None, @@ -327,8 +324,7 @@ def __new__( self = cast(JSONOptions, super().__new__(cls, *args, **kwargs)) if json_mode not in (JSONMode.LEGACY, JSONMode.RELAXED, JSONMode.CANONICAL): raise ValueError( - "JSONOptions.json_mode must be one of LEGACY, RELAXED, " - "or CANONICAL from JSONMode." + "JSONOptions.json_mode must be one of LEGACY, RELAXED, or CANONICAL from JSONMode." ) self.json_mode = json_mode if self.json_mode == JSONMode.RELAXED: @@ -371,15 +367,9 @@ def __new__( def _arguments_repr(self) -> str: return ( - "strict_number_long={!r}, " - "datetime_representation={!r}, " - "strict_uuid={!r}, json_mode={!r}, {}".format( - self.strict_number_long, - self.datetime_representation, - self.strict_uuid, - self.json_mode, - super()._arguments_repr(), - ) + f"strict_number_long={self.strict_number_long!r}, " + f"datetime_representation={self.datetime_representation!r}, " + f"strict_uuid={self.strict_uuid!r}, json_mode={self.json_mode!r}, {super()._arguments_repr()}" ) def with_options(self, **kwargs: Any) -> JSONOptions: @@ -509,7 +499,7 @@ def _json_convert(obj: Any, json_options: JSONOptions = DEFAULT_JSON_OPTIONS) -> def object_pairs_hook( - pairs: Sequence[Tuple[str, Any]], json_options: JSONOptions = DEFAULT_JSON_OPTIONS + pairs: Sequence[tuple[str, Any]], json_options: JSONOptions = DEFAULT_JSON_OPTIONS ) -> Any: return object_hook(json_options.document_class(pairs), json_options) # type:ignore[call-arg] @@ -572,7 +562,7 @@ def _binary_or_uuid(data: Any, subtype: int, json_options: JSONOptions) -> Union def _parse_legacy_binary(doc: Any, json_options: JSONOptions) -> Union[Binary, uuid.UUID]: if isinstance(doc["$type"], int): - doc["$type"] = "%02x" % doc["$type"] + doc["$type"] = "{:02x}".format(doc["$type"]) subtype = int(doc["$type"], 16) if subtype >= 0xFFFFFF80: # Handle mongoexport values subtype = int(doc["$type"][6:], 16) @@ -699,7 +689,7 @@ def _parse_canonical_regex(doc: Any, dummy0: Any) -> Regex[str]: opts = regex["options"] if not isinstance(opts, str): raise TypeError( - "Bad $regularExpression options, options must be string, was type %s" % (type(opts)) + f"Bad $regularExpression options, options must be string, was type {type(opts)}" ) return Regex(regex["pattern"], opts) @@ -774,7 +764,7 @@ def _parse_canonical_decimal128(doc: Any, dummy0: Any) -> Decimal128: def _parse_canonical_minkey(doc: Any, dummy0: Any) -> MinKey: """Decode a JSON MinKey to bson.min_key.MinKey.""" - if type(doc["$minKey"]) is not int or doc["$minKey"] != 1: # noqa: E721 + if type(doc["$minKey"]) is not int or doc["$minKey"] != 1: raise TypeError(f"$minKey value must be 1: {doc}") if len(doc) != 1: raise TypeError(f"Bad $minKey, extra field(s): {doc}") @@ -783,7 +773,7 @@ def _parse_canonical_minkey(doc: Any, dummy0: Any) -> MinKey: def _parse_canonical_maxkey(doc: Any, dummy0: Any) -> MaxKey: """Decode a JSON MaxKey to bson.max_key.MaxKey.""" - if type(doc["$maxKey"]) is not int or doc["$maxKey"] != 1: # noqa: E721 + if type(doc["$maxKey"]) is not int or doc["$maxKey"] != 1: raise TypeError("$maxKey value must be 1: %s", (doc,)) if len(doc) != 1: raise TypeError(f"Bad $minKey, extra field(s): {doc}") @@ -827,8 +817,8 @@ def _parse_timestamp(doc: Any, dummy0: Any) -> Timestamp: def _encode_binary(data: bytes, subtype: int, json_options: JSONOptions) -> Any: if json_options.json_mode == JSONMode.LEGACY: - return {"$binary": base64.b64encode(data).decode(), "$type": "%02x" % subtype} - return {"$binary": {"base64": base64.b64encode(data).decode(), "subType": "%02x" % subtype}} + return {"$binary": base64.b64encode(data).decode(), "$type": f"{subtype:02x}"} + return {"$binary": {"base64": base64.b64encode(data).decode(), "subType": f"{subtype:02x}"}} def _encode_datetimems(obj: Any, json_options: JSONOptions) -> dict: # type: ignore[type-arg] @@ -917,7 +907,7 @@ def _encode_datetime(obj: datetime.datetime, json_options: JSONOptions) -> dict: else: tz_string = obj.strftime("%z") millis = int(obj.microsecond / 1000) - fracsecs = ".%03d" % (millis,) if millis else "" + fracsecs = f".{millis:03d}" if millis else "" return { "$date": "{}{}{}".format(obj.strftime("%Y-%m-%dT%H:%M:%S"), fracsecs, tz_string) } @@ -972,7 +962,7 @@ def _encode_maxkey(dummy0: Any, dummy1: Any) -> dict: # type: ignore[type-arg] # Each encoder function's signature is: # - obj: a Python data type, e.g. a Python int for _encode_int # - json_options: a JSONOptions -_ENCODERS: dict[Type, Callable[[Any, JSONOptions], Any]] = { # type: ignore[type-arg] +_ENCODERS: dict[type, Callable[[Any, JSONOptions], Any]] = { # type: ignore[type-arg] bool: _encode_noop, bytes: _encode_bytes, datetime.datetime: _encode_datetime, @@ -997,9 +987,9 @@ def _encode_maxkey(dummy0: Any, dummy1: Any) -> dict: # type: ignore[type-arg] # Map each _type_marker to its encoder for faster lookup. _MARKERS: dict[int, Callable[[Any, JSONOptions], Any]] = {} -for _typ in _ENCODERS: +for _typ, _encoder in _ENCODERS.items(): if hasattr(_typ, "_type_marker"): - _MARKERS[_typ._type_marker] = _ENCODERS[_typ] + _MARKERS[_typ._type_marker] = _encoder _BUILT_IN_TYPES = tuple(t for t in _ENCODERS) @@ -1032,7 +1022,7 @@ def default(obj: Any, json_options: JSONOptions = DEFAULT_JSON_OPTIONS) -> Any: _ENCODERS[type(obj)] = func return func(obj, json_options) - raise TypeError("%r is not JSON serializable" % obj) + raise TypeError(f"{obj!r} is not JSON serializable") def _get_str_size(obj: Any) -> int: @@ -1097,7 +1087,7 @@ def get_size(obj: Any, max_size: int, current_size: int = 0) -> int: ) else: current_size += 5 + len(obj) - elif obj_type == dict: + elif obj_type is dict: for k, v in obj.items(): current_size += get_size(k, max_size, current_size) current_size += get_size(v, max_size, current_size) @@ -1111,7 +1101,7 @@ def get_size(obj: Any, max_size: int, current_size: int = 0) -> int: return current_size -def _truncate_documents(obj: Any, max_length: int) -> Tuple[Any, int]: +def _truncate_documents(obj: Any, max_length: int) -> tuple[Any, int]: """Recursively truncate documents as needed to fit inside max_length characters.""" if max_length <= 0: return None, 0 @@ -1138,7 +1128,7 @@ def _truncate_documents(obj: Any, max_length: int) -> Tuple[Any, int]: return _truncate(obj, remaining) -def _truncate(obj: Any, remaining: int) -> Tuple[Any, int]: +def _truncate(obj: Any, remaining: int) -> tuple[Any, int]: size = get_size(obj, remaining) if size <= remaining: diff --git a/bson/max_key.py b/bson/max_key.py index 445e12f519..0c4e7cffc2 100644 --- a/bson/max_key.py +++ b/bson/max_key.py @@ -13,6 +13,7 @@ # limitations under the License. """Representation for the MongoDB internal MaxKey type.""" + from __future__ import annotations from typing import Any diff --git a/bson/min_key.py b/bson/min_key.py index 37828dcf74..5324e74fab 100644 --- a/bson/min_key.py +++ b/bson/min_key.py @@ -13,6 +13,7 @@ # limitations under the License. """Representation for the MongoDB internal MinKey type.""" + from __future__ import annotations from typing import Any diff --git a/bson/objectid.py b/bson/objectid.py index e66a77b95e..4422c98955 100644 --- a/bson/objectid.py +++ b/bson/objectid.py @@ -13,6 +13,7 @@ # limitations under the License. """Tools for working with MongoDB ObjectIds.""" + from __future__ import annotations import datetime @@ -21,7 +22,7 @@ import threading import time from random import SystemRandom -from typing import Any, NoReturn, Optional, Type, Union +from typing import Any, NoReturn, Optional, Union from bson.datetime_ms import _datetime_to_millis from bson.errors import InvalidId @@ -35,8 +36,7 @@ def _raise_invalid_id(oid: str) -> NoReturn: raise InvalidId( - "%r is not a valid ObjectId, it must be a 12-byte input" - " or a 24-character hex string" % oid + f"{oid!r} is not a valid ObjectId, it must be a 12-byte input or a 24-character hex string" ) @@ -120,7 +120,7 @@ def __init__(self, oid: Optional[Union[str, ObjectId, bytes]] = None) -> None: raise TypeError(f"id must be an instance of (bytes, str, ObjectId), not {type(oid)}") @classmethod - def from_datetime(cls: Type[ObjectId], generation_time: datetime.datetime) -> ObjectId: + def from_datetime(cls: type[ObjectId], generation_time: datetime.datetime) -> ObjectId: """Create a dummy ObjectId instance with a specific generation time. This method is useful for doing range queries on a field @@ -153,7 +153,7 @@ def from_datetime(cls: Type[ObjectId], generation_time: datetime.datetime) -> Ob return cls(oid) @classmethod - def is_valid(cls: Type[ObjectId], oid: Any) -> bool: + def is_valid(cls: type[ObjectId], oid: Any) -> bool: """Checks if a `oid` string is valid or not. :param oid: the object id to validate diff --git a/bson/raw_bson.py b/bson/raw_bson.py index 9ead0765dc..42bd19cab4 100644 --- a/bson/raw_bson.py +++ b/bson/raw_bson.py @@ -50,9 +50,11 @@ blobs to disk, using raw BSON documents provides better speed and avoids the overhead of decoding or encoding BSON. """ + from __future__ import annotations -from typing import Any, ItemsView, Iterator, Mapping, Optional +from collections.abc import ItemsView, Iterator, Mapping +from typing import Any, Optional from bson import _get_object_size, _raw_to_dict from bson.codec_options import _RAW_BSON_DOCUMENT_MARKER, CodecOptions @@ -82,7 +84,7 @@ class RawBSONDocument(Mapping[str, Any]): RawBSONDocument decode its bytes. """ - __slots__ = ("__raw", "__inflated_doc", "__codec_options") + __slots__ = ("__codec_options", "__inflated_doc", "__raw") _type_marker = _RAW_BSON_DOCUMENT_MARKER __codec_options: CodecOptions[RawBSONDocument] @@ -175,6 +177,8 @@ def __eq__(self, other: Any) -> bool: return self.__raw == other.raw return NotImplemented + __hash__ = None # type: ignore[assignment] + def __repr__(self) -> str: return f"{self.__class__.__name__}({self.raw!r}, codec_options={self.__codec_options!r})" diff --git a/bson/regex.py b/bson/regex.py index 60cff4fd08..39d1814fa0 100644 --- a/bson/regex.py +++ b/bson/regex.py @@ -13,10 +13,12 @@ # limitations under the License. """Tools for representing MongoDB regular expressions.""" + from __future__ import annotations import re -from typing import Any, Generic, Pattern, Type, TypeVar, Union +from re import Pattern +from typing import Any, Generic, TypeVar, Union from bson._helpers import _getstate_slots, _setstate_slots from bson.son import RE_TYPE @@ -46,7 +48,7 @@ def str_flags_to_int(str_flags: str) -> int: class Regex(Generic[_T]): """BSON regular expression data.""" - __slots__ = ("pattern", "flags") + __slots__ = ("flags", "pattern") __getstate__ = _getstate_slots __setstate__ = _setstate_slots @@ -54,7 +56,7 @@ class Regex(Generic[_T]): _type_marker = 11 @classmethod - def from_native(cls: Type[Regex[Any]], regex: Pattern[_T]) -> Regex[_T]: + def from_native(cls: type[Regex[Any]], regex: Pattern[_T]) -> Regex[_T]: """Convert a Python regular expression into a ``Regex`` instance. Note that in Python 3, a regular expression compiled from a @@ -78,7 +80,7 @@ def from_native(cls: Type[Regex[Any]], regex: Pattern[_T]) -> Regex[_T]: .. _PCRE: http://www.pcre.org/ """ if not isinstance(regex, RE_TYPE): - raise TypeError("regex must be a compiled regular expression, not %s" % type(regex)) + raise TypeError(f"regex must be a compiled regular expression, not {type(regex)}") return Regex(regex.pattern, regex.flags) @@ -93,7 +95,7 @@ def __init__(self, pattern: _T, flags: Union[str, int] = 0) -> None: characters like "im" for IGNORECASE and MULTILINE """ if not isinstance(pattern, (str, bytes)): - raise TypeError("pattern must be a string, not %s" % type(pattern)) + raise TypeError(f"pattern must be a string, not {type(pattern)}") self.pattern: _T = pattern if isinstance(flags, str): @@ -101,7 +103,7 @@ def __init__(self, pattern: _T, flags: Union[str, int] = 0) -> None: elif isinstance(flags, int): self.flags = flags else: - raise TypeError("flags must be a string or int, not %s" % type(flags)) + raise TypeError(f"flags must be a string or int, not {type(flags)}") def __eq__(self, other: Any) -> bool: if isinstance(other, Regex): diff --git a/bson/son.py b/bson/son.py index ccb6bdb273..1404e4f921 100644 --- a/bson/son.py +++ b/bson/son.py @@ -18,22 +18,18 @@ of keys is important. A SON object can be used just like a normal Python dictionary. """ + from __future__ import annotations import copy import re import warnings +from collections.abc import Iterable, Iterator, Mapping from collections.abc import Mapping as _Mapping +from re import Pattern from typing import ( Any, - Dict, - Iterable, - Iterator, - Mapping, Optional, - Pattern, - Tuple, - Type, TypeVar, Union, cast, @@ -41,14 +37,14 @@ # This sort of sucks, but seems to be as good as it gets... # This is essentially the same as re._pattern_type -RE_TYPE: Type[Pattern[Any]] = type(re.compile("")) +RE_TYPE: type[Pattern[Any]] = type(re.compile("")) _Key = TypeVar("_Key") _Value = TypeVar("_Value") _T = TypeVar("_T") -class SON(Dict[_Key, _Value]): +class SON(dict[_Key, _Value]): """SON data. A subclass of dict that maintains ordering of keys and provides a @@ -60,7 +56,7 @@ class SON(Dict[_Key, _Value]): def __init__( self, - data: Optional[Union[Mapping[_Key, _Value], Iterable[Tuple[_Key, _Value]]]] = None, + data: Optional[Union[Mapping[_Key, _Value], Iterable[tuple[_Key, _Value]]]] = None, **kwargs: Any, ) -> None: self.__keys = [] @@ -68,7 +64,7 @@ def __init__( self.update(data) self.update(kwargs) - def __new__(cls: Type[SON[_Key, _Value]], *args: Any, **kwargs: Any) -> SON[_Key, _Value]: + def __new__(cls: type[SON[_Key, _Value]], *args: Any, **kwargs: Any) -> SON[_Key, _Value]: instance = super().__new__(cls, *args, **kwargs) instance.__keys = [] return instance @@ -77,7 +73,7 @@ def __repr__(self) -> str: result = [] for key in self.__keys: result.append(f"({key!r}, {self[key]!r})") - return "SON([%s])" % ", ".join(result) + return "SON([{}])".format(", ".join(result)) def __setitem__(self, key: _Key, value: _Value) -> None: if key not in self.__keys: @@ -151,7 +147,7 @@ def pop(self, key: _Key, *args: Union[_Value, _T]) -> Union[_Value, _T]: del self[key] return value - def popitem(self) -> Tuple[_Key, _Value]: + def popitem(self) -> tuple[_Key, _Value]: try: k, v = next(iter(self.items())) except StopIteration: @@ -191,6 +187,8 @@ def __eq__(self, other: Any) -> bool: return len(self) == len(other) and list(self.items()) == list(other.items()) return cast(bool, self.to_dict() == other) + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: return not self == other diff --git a/bson/timestamp.py b/bson/timestamp.py index 949bd7b36c..02a3fff627 100644 --- a/bson/timestamp.py +++ b/bson/timestamp.py @@ -13,6 +13,7 @@ # limitations under the License. """Tools for representing MongoDB internal Timestamps.""" + from __future__ import annotations import calendar @@ -28,7 +29,7 @@ class Timestamp: """MongoDB internal timestamps used in the opLog.""" - __slots__ = ("__time", "__inc") + __slots__ = ("__inc", "__time") __getstate__ = _getstate_slots __setstate__ = _setstate_slots diff --git a/bson/typings.py b/bson/typings.py index 5913860556..3823182531 100644 --- a/bson/typings.py +++ b/bson/typings.py @@ -13,9 +13,11 @@ # limitations under the License. """Type aliases used by bson""" + from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, MutableMapping, TypeVar, Union +from collections.abc import Mapping, MutableMapping +from typing import TYPE_CHECKING, Any, TypeVar, Union if TYPE_CHECKING: from array import array diff --git a/bson/tz_util.py b/bson/tz_util.py index 4d31c04f9c..18a543633e 100644 --- a/bson/tz_util.py +++ b/bson/tz_util.py @@ -13,10 +13,11 @@ # limitations under the License. """Timezone related utilities for BSON.""" + from __future__ import annotations from datetime import datetime, timedelta, tzinfo -from typing import Optional, Tuple, Union +from typing import Optional, Union ZERO: timedelta = timedelta(0) @@ -36,7 +37,7 @@ def __init__(self, offset: Union[float, timedelta], name: str) -> None: self.__offset = timedelta(minutes=offset) self.__name = name - def __getinitargs__(self) -> Tuple[timedelta, str]: + def __getinitargs__(self) -> tuple[timedelta, str]: return self.__offset, self.__name def __repr__(self) -> str: diff --git a/gridfs/__init__.py b/gridfs/__init__.py index 8173561beb..c6793c8680 100644 --- a/gridfs/__init__.py +++ b/gridfs/__init__.py @@ -19,6 +19,7 @@ .. seealso:: The MongoDB documentation on `gridfs `_. """ + from __future__ import annotations from gridfs.asynchronous.grid_file import ( @@ -39,16 +40,16 @@ ) __all__ = [ + "DEFAULT_CHUNK_SIZE", "AsyncGridFS", - "GridFS", "AsyncGridFSBucket", - "GridFSBucket", - "NoFile", - "DEFAULT_CHUNK_SIZE", "AsyncGridIn", - "GridIn", "AsyncGridOut", - "GridOut", "AsyncGridOutCursor", + "GridFS", + "GridFSBucket", + "GridIn", + "GridOut", "GridOutCursor", + "NoFile", ] diff --git a/gridfs/asynchronous/__init__.py b/gridfs/asynchronous/__init__.py index 0826145b11..1e4d1c7e38 100644 --- a/gridfs/asynchronous/__init__.py +++ b/gridfs/asynchronous/__init__.py @@ -19,6 +19,7 @@ .. seealso:: The MongoDB documentation on `gridfs `_. """ + from __future__ import annotations from gridfs.asynchronous.grid_file import ( @@ -32,11 +33,11 @@ from gridfs.grid_file_shared import DEFAULT_CHUNK_SIZE __all__ = [ + "DEFAULT_CHUNK_SIZE", "AsyncGridFS", "AsyncGridFSBucket", - "NoFile", - "DEFAULT_CHUNK_SIZE", "AsyncGridIn", "AsyncGridOut", "AsyncGridOutCursor", + "NoFile", ] diff --git a/gridfs/asynchronous/grid_file.py b/gridfs/asynchronous/grid_file.py index e512f796a8..3bbe3754fe 100644 --- a/gridfs/asynchronous/grid_file.py +++ b/gridfs/asynchronous/grid_file.py @@ -13,6 +13,7 @@ # limitations under the License. """Tools for representing files stored in GridFS.""" + from __future__ import annotations import datetime @@ -20,7 +21,8 @@ import io import math from collections import abc -from typing import Any, Iterable, Mapping, NoReturn, Optional, cast +from collections.abc import Iterable, Mapping +from typing import Any, NoReturn, Optional, cast from bson.int64 import Int64 from bson.objectid import ObjectId @@ -232,7 +234,7 @@ async def get_version( doc = await anext(cursor) return AsyncGridOut(self._collection, file_document=doc, session=session) except StopAsyncIteration: - raise NoFile("no version %d for filename %r" % (version, filename)) from None + raise NoFile(f"no version {version} for filename {filename!r}") from None async def get_last_version( self, @@ -832,7 +834,7 @@ async def delete(self, file_id: Any, session: Optional[AsyncClientSession] = Non res = await self._files.delete_one({"_id": file_id}, session=session) await self._chunks.delete_many({"files_id": file_id}, session=session) if not res.deleted_count: - raise NoFile("no file could be deleted because none matched %s" % file_id) + raise NoFile(f"no file could be deleted because none matched {file_id}") @_csot.apply async def delete_by_name( @@ -962,7 +964,7 @@ async def open_download_stream_by_name( grid_file = await anext(cursor) return AsyncGridOut(self._collection, file_document=grid_file, session=session) except StopAsyncIteration: - raise NoFile("no version %d for filename %r" % (revision, filename)) from None + raise NoFile(f"no version {revision} for filename {filename!r}") from None @_csot.apply async def download_to_stream_by_name( @@ -1046,8 +1048,7 @@ async def rename( ) if not result.matched_count: raise NoFile( - "no files could be renamed %r because none " - "matched file_id %i" % (new_filename, file_id) + f"no files could be renamed {new_filename!r} because none matched file_id {file_id}" ) async def rename_by_name( @@ -1233,7 +1234,7 @@ def __getattr__(self, name: str) -> Any: return object.__getattribute__(self, name) elif name in self._file: return self._file[name] - raise AttributeError("GridIn object has no attribute '%s'" % name) + raise AttributeError(f"GridIn object has no attribute '{name}'") def __setattr__(self, name: str, value: Any) -> None: # For properties of this instance like _buffer, or descriptors set on @@ -1314,7 +1315,7 @@ async def _flush(self) -> Any: def _raise_file_exists(self, file_id: Any) -> NoReturn: """Raise a FileExists exception for the given file_id.""" - raise FileExists("file with _id %r already exists" % file_id) + raise FileExists(f"file with _id {file_id!r} already exists") async def close(self) -> None: """Flush the file and close it. @@ -1451,7 +1452,6 @@ async def __aexit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Any: class AsyncGridOut(GRIDOUT_BASE_CLASS): # type: ignore - """Class to read data out of GridFS.""" def __init__( @@ -1593,11 +1593,11 @@ def __getattr__(self, name: str) -> Any: self.open() # type: ignore[unused-coroutine] elif not self._file: raise InvalidOperation( - "You must call AsyncGridOut.open() before accessing the %s property" % name + f"You must call AsyncGridOut.open() before accessing the {name} property" ) if name in self._file: return self._file[name] - raise AttributeError("GridOut object has no attribute '%s'" % name) + raise AttributeError(f"GridOut object has no attribute '{name}'") def readable(self) -> bool: return True @@ -1882,13 +1882,13 @@ async def next(self) -> Mapping[str, Any]: except StopAsyncIteration: if self._next_chunk >= self._num_chunks: raise - raise CorruptGridFile("no chunk #%d" % self._next_chunk) from None + raise CorruptGridFile(f"no chunk #{self._next_chunk}") from None if chunk["n"] != self._next_chunk: await self.close() raise CorruptGridFile( - "Missing chunk: expected chunk #%d but found " - "chunk with n=%d" % (self._next_chunk, chunk["n"]) + f"Missing chunk: expected chunk #{self._next_chunk} but found " + f"chunk with n={chunk['n']}" ) if chunk["n"] >= self._num_chunks: @@ -1896,16 +1896,16 @@ async def next(self) -> Mapping[str, Any]: if len(chunk["data"]): await self.close() raise CorruptGridFile( - "Extra chunk found: expected %d chunks but found " - "chunk with n=%d" % (self._num_chunks, chunk["n"]) + f"Extra chunk found: expected {self._num_chunks} chunks but found " + f"chunk with n={chunk['n']}" ) expected_length = self.expected_chunk_length(chunk["n"]) if len(chunk["data"]) != expected_length: await self.close() raise CorruptGridFile( - "truncated chunk #%d: expected chunk length to be %d but " - "found chunk with length %d" % (chunk["n"], expected_length, len(chunk["data"])) + f"truncated chunk #{chunk['n']}: expected chunk length to be {expected_length} but " + f"found chunk with length {len(chunk['data'])}" ) self._next_chunk += 1 diff --git a/gridfs/errors.py b/gridfs/errors.py index e8c02cef4f..3b9a83e783 100644 --- a/gridfs/errors.py +++ b/gridfs/errors.py @@ -13,6 +13,7 @@ # limitations under the License. """Exceptions raised by the :mod:`gridfs` package""" + from __future__ import annotations from pymongo.errors import PyMongoError diff --git a/gridfs/grid_file.py b/gridfs/grid_file.py index b2cab71515..b55199a41c 100644 --- a/gridfs/grid_file.py +++ b/gridfs/grid_file.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous gridfs API for compatibility.""" + from __future__ import annotations from gridfs.synchronous.grid_file import * # noqa: F403 diff --git a/gridfs/grid_file_shared.py b/gridfs/grid_file_shared.py index 79a0ad7f8c..81585b003c 100644 --- a/gridfs/grid_file_shared.py +++ b/gridfs/grid_file_shared.py @@ -48,7 +48,7 @@ def getter(self: Any) -> Any: if warn_str: warnings.warn(warn_str, stacklevel=2, category=DeprecationWarning) if closed_only and not self._closed: - raise AttributeError("can only get %r on a closed file" % field_name) + raise AttributeError(f"can only get {field_name!r} on a closed file") # Protect against PHP-237 if field_name == "length": return self._file.get(field_name, 0) @@ -68,9 +68,7 @@ def setter(self: Any, value: Any) -> Any: elif closed_only: docstring = "{}\n\n{}".format( docstring, - "This attribute is read-only and " - "can only be read after :meth:`close` " - "has been called.", + "This attribute is read-only and can only be read after :meth:`close` has been called.", ) if not read_only and not closed_only: @@ -84,7 +82,7 @@ def _a_grid_out_property(field_name: str, docstring: str) -> Any: def a_getter(self: Any) -> Any: if not self._file: raise InvalidOperation( - "You must call GridOut.open() before accessing " "the %s property" % field_name + f"You must call GridOut.open() before accessing the {field_name} property" ) # Protect against PHP-237 if field_name == "length": @@ -112,7 +110,7 @@ def getter(self: Any) -> Any: if warn_str: warnings.warn(warn_str, stacklevel=2, category=DeprecationWarning) if closed_only and not self._closed: - raise AttributeError("can only get %r on a closed file" % field_name) + raise AttributeError(f"can only get {field_name!r} on a closed file") # Protect against PHP-237 if field_name == "length": return self._file.get(field_name, 0) @@ -130,9 +128,7 @@ def setter(self: Any, value: Any) -> Any: elif closed_only: docstring = "{}\n\n{}".format( docstring, - "This attribute is read-only and " - "can only be read after :meth:`close` " - "has been called.", + "This attribute is read-only and can only be read after :meth:`close` has been called.", ) if not read_only and not closed_only: diff --git a/gridfs/synchronous/__init__.py b/gridfs/synchronous/__init__.py index bc2704364b..0a1017a693 100644 --- a/gridfs/synchronous/__init__.py +++ b/gridfs/synchronous/__init__.py @@ -19,6 +19,7 @@ .. seealso:: The MongoDB documentation on `gridfs `_. """ + from __future__ import annotations from gridfs.errors import NoFile @@ -32,11 +33,11 @@ ) __all__ = [ + "DEFAULT_CHUNK_SIZE", "GridFS", "GridFSBucket", - "NoFile", - "DEFAULT_CHUNK_SIZE", "GridIn", "GridOut", "GridOutCursor", + "NoFile", ] diff --git a/gridfs/synchronous/grid_file.py b/gridfs/synchronous/grid_file.py index 70a4f80774..7bd2aad75f 100644 --- a/gridfs/synchronous/grid_file.py +++ b/gridfs/synchronous/grid_file.py @@ -13,6 +13,7 @@ # limitations under the License. """Tools for representing files stored in GridFS.""" + from __future__ import annotations import datetime @@ -20,7 +21,8 @@ import io import math from collections import abc -from typing import Any, Iterable, Mapping, NoReturn, Optional, cast +from collections.abc import Iterable, Mapping +from typing import Any, NoReturn, Optional, cast from bson.int64 import Int64 from bson.objectid import ObjectId @@ -232,7 +234,7 @@ def get_version( doc = next(cursor) return GridOut(self._collection, file_document=doc, session=session) except StopIteration: - raise NoFile("no version %d for filename %r" % (version, filename)) from None + raise NoFile(f"no version {version} for filename {filename!r}") from None def get_last_version( self, @@ -828,7 +830,7 @@ def delete(self, file_id: Any, session: Optional[ClientSession] = None) -> None: res = self._files.delete_one({"_id": file_id}, session=session) self._chunks.delete_many({"files_id": file_id}, session=session) if not res.deleted_count: - raise NoFile("no file could be deleted because none matched %s" % file_id) + raise NoFile(f"no file could be deleted because none matched {file_id}") @_csot.apply def delete_by_name(self, filename: str, session: Optional[ClientSession] = None) -> None: @@ -956,7 +958,7 @@ def open_download_stream_by_name( grid_file = next(cursor) return GridOut(self._collection, file_document=grid_file, session=session) except StopIteration: - raise NoFile("no version %d for filename %r" % (revision, filename)) from None + raise NoFile(f"no version {revision} for filename {filename!r}") from None @_csot.apply def download_to_stream_by_name( @@ -1038,8 +1040,7 @@ def rename( ) if not result.matched_count: raise NoFile( - "no files could be renamed %r because none " - "matched file_id %i" % (new_filename, file_id) + f"no files could be renamed {new_filename!r} because none matched file_id {file_id}" ) def rename_by_name( @@ -1221,7 +1222,7 @@ def __getattr__(self, name: str) -> Any: return object.__getattribute__(self, name) elif name in self._file: return self._file[name] - raise AttributeError("GridIn object has no attribute '%s'" % name) + raise AttributeError(f"GridIn object has no attribute '{name}'") def __setattr__(self, name: str, value: Any) -> None: # For properties of this instance like _buffer, or descriptors set on @@ -1302,7 +1303,7 @@ def _flush(self) -> Any: def _raise_file_exists(self, file_id: Any) -> NoReturn: """Raise a FileExists exception for the given file_id.""" - raise FileExists("file with _id %r already exists" % file_id) + raise FileExists(f"file with _id {file_id!r} already exists") def close(self) -> None: """Flush the file and close it. @@ -1439,7 +1440,6 @@ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> Any: class GridOut(GRIDOUT_BASE_CLASS): # type: ignore - """Class to read data out of GridFS.""" def __init__( @@ -1581,11 +1581,11 @@ def __getattr__(self, name: str) -> Any: self.open() # type: ignore[unused-coroutine] elif not self._file: raise InvalidOperation( - "You must call GridOut.open() before accessing the %s property" % name + f"You must call GridOut.open() before accessing the {name} property" ) if name in self._file: return self._file[name] - raise AttributeError("GridOut object has no attribute '%s'" % name) + raise AttributeError(f"GridOut object has no attribute '{name}'") def readable(self) -> bool: return True @@ -1870,13 +1870,13 @@ def next(self) -> Mapping[str, Any]: except StopIteration: if self._next_chunk >= self._num_chunks: raise - raise CorruptGridFile("no chunk #%d" % self._next_chunk) from None + raise CorruptGridFile(f"no chunk #{self._next_chunk}") from None if chunk["n"] != self._next_chunk: self.close() raise CorruptGridFile( - "Missing chunk: expected chunk #%d but found " - "chunk with n=%d" % (self._next_chunk, chunk["n"]) + f"Missing chunk: expected chunk #{self._next_chunk} but found " + f"chunk with n={chunk['n']}" ) if chunk["n"] >= self._num_chunks: @@ -1884,16 +1884,16 @@ def next(self) -> Mapping[str, Any]: if len(chunk["data"]): self.close() raise CorruptGridFile( - "Extra chunk found: expected %d chunks but found " - "chunk with n=%d" % (self._num_chunks, chunk["n"]) + f"Extra chunk found: expected {self._num_chunks} chunks but found " + f"chunk with n={chunk['n']}" ) expected_length = self.expected_chunk_length(chunk["n"]) if len(chunk["data"]) != expected_length: self.close() raise CorruptGridFile( - "truncated chunk #%d: expected chunk length to be %d but " - "found chunk with length %d" % (chunk["n"], expected_length, len(chunk["data"])) + f"truncated chunk #{chunk['n']}: expected chunk length to be {expected_length} but " + f"found chunk with length {len(chunk['data'])}" ) self._next_chunk += 1 diff --git a/hatch_build.py b/hatch_build.py index 40271972dd..47a72c1303 100644 --- a/hatch_build.py +++ b/hatch_build.py @@ -1,4 +1,5 @@ """A custom hatch build hook for pymongo.""" + from __future__ import annotations import os diff --git a/pymongo/__init__.py b/pymongo/__init__.py index ac540d94db..dac07ce184 100644 --- a/pymongo/__init__.py +++ b/pymongo/__init__.py @@ -13,9 +13,11 @@ # limitations under the License. """Python driver for MongoDB.""" + from __future__ import annotations -from typing import ContextManager, Optional +from contextlib import AbstractContextManager +from typing import Optional __all__ = [ "ASCENDING", @@ -23,28 +25,28 @@ "GEO2D", "GEOSPHERE", "HASHED", - "TEXT", - "version_tuple", - "get_version_string", - "__version__", - "version", - "ReturnDocument", "MAX_SUPPORTED_WIRE_VERSION", "MIN_SUPPORTED_WIRE_VERSION", - "CursorType", - "MongoClient", + "TEXT", "AsyncMongoClient", + "CursorType", "DeleteMany", "DeleteOne", "IndexModel", "InsertOne", + "MongoClient", + "ReadPreference", "ReplaceOne", + "ReturnDocument", "UpdateMany", "UpdateOne", - "ReadPreference", "WriteConcern", + "__version__", + "get_version_string", "has_c", "timeout", + "version", + "version_tuple", ] ASCENDING = 1 @@ -119,7 +121,7 @@ """Current version of PyMongo.""" -def timeout(seconds: Optional[float]) -> ContextManager[None]: +def timeout(seconds: Optional[float]) -> AbstractContextManager[None]: """**(Provisional)** Apply the given timeout for a block of operations. .. note:: :func:`~pymongo.timeout` is currently provisional. Backwards diff --git a/pymongo/_asyncio_lock.py b/pymongo/_asyncio_lock.py index 5ca09982fa..cde1b0ca51 100644 --- a/pymongo/_asyncio_lock.py +++ b/pymongo/_asyncio_lock.py @@ -9,7 +9,8 @@ import collections import threading from asyncio import events, exceptions -from typing import Any, Coroutine, Optional +from collections.abc import Coroutine +from typing import Any, Optional _global_lock = threading.Lock() diff --git a/pymongo/_asyncio_task.py b/pymongo/_asyncio_task.py index 118471963a..1b2678dbec 100644 --- a/pymongo/_asyncio_task.py +++ b/pymongo/_asyncio_task.py @@ -15,12 +15,12 @@ """A custom asyncio.Task that allows checking if a task has been sent a cancellation request. Can be removed once we drop Python 3.10 support in favor of asyncio.Task.cancelling.""" - from __future__ import annotations import asyncio import sys -from typing import Any, Coroutine, Optional +from collections.abc import Coroutine +from typing import Any, Optional # TODO (https://jira.mongodb.org/browse/PYTHON-4981): Revisit once the underlying cause of the swallowed cancellations is uncovered diff --git a/pymongo/_azure_helpers.py b/pymongo/_azure_helpers.py index b7a5181857..231625d8d7 100644 --- a/pymongo/_azure_helpers.py +++ b/pymongo/_azure_helpers.py @@ -13,6 +13,7 @@ # limitations under the License. """Azure helpers.""" + from __future__ import annotations import json @@ -38,7 +39,7 @@ def _get_azure_response( status = response.status body = response.read().decode("utf8") except Exception as e: - msg = "Failed to acquire IMDS access token: %s" % e + msg = f"Failed to acquire IMDS access token: {e}" raise ValueError(msg) from None if status != 200: diff --git a/pymongo/_client_bulk_shared.py b/pymongo/_client_bulk_shared.py index 5814025566..f4f7f8fe7f 100644 --- a/pymongo/_client_bulk_shared.py +++ b/pymongo/_client_bulk_shared.py @@ -14,9 +14,11 @@ """Constants, types, and classes shared across Client Bulk Write API implementations.""" + from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, MutableMapping, NoReturn +from collections.abc import Mapping, MutableMapping +from typing import TYPE_CHECKING, Any, NoReturn from pymongo.errors import ClientBulkWriteException, OperationFailure from pymongo.helpers_shared import _get_wce_doc diff --git a/pymongo/_csot.py b/pymongo/_csot.py index ce72a66486..8a599af403 100644 --- a/pymongo/_csot.py +++ b/pymongo/_csot.py @@ -20,9 +20,10 @@ import inspect import time from collections import deque +from collections.abc import MutableMapping from contextlib import AbstractContextManager from contextvars import ContextVar, Token -from typing import TYPE_CHECKING, Any, Callable, Deque, MutableMapping, Optional, TypeVar, cast +from typing import TYPE_CHECKING, Any, Callable, Optional, TypeVar, cast if TYPE_CHECKING: from pymongo.write_concern import WriteConcern @@ -147,7 +148,7 @@ def apply_write_concern( class MovingMinimum: """Tracks a minimum RTT within the last 10 RTT samples.""" - samples: Deque[float] + samples: deque[float] def __init__(self) -> None: self.samples = deque(maxlen=_MAX_RTT_SAMPLES) diff --git a/pymongo/_gcp_helpers.py b/pymongo/_gcp_helpers.py index 7979d1e807..71fddc36f6 100644 --- a/pymongo/_gcp_helpers.py +++ b/pymongo/_gcp_helpers.py @@ -13,6 +13,7 @@ # limitations under the License. """GCP helpers.""" + from __future__ import annotations from typing import Any @@ -30,7 +31,7 @@ def _get_gcp_response(resource: str, timeout: float = 5) -> dict[str, Any]: status = response.status body = response.read().decode("utf8") except Exception as e: - msg = "Failed to acquire IMDS access token: %s" % e + msg = f"Failed to acquire IMDS access token: {e}" raise ValueError(msg) from None if status != 200: diff --git a/pymongo/_version.py b/pymongo/_version.py index 9c67ac76ef..66ac48d157 100644 --- a/pymongo/_version.py +++ b/pymongo/_version.py @@ -13,19 +13,20 @@ # limitations under the License. """Current version of PyMongo.""" + from __future__ import annotations import re -from typing import List, Tuple, Union +from typing import Union __version__ = "4.18.0.dev0" -def get_version_tuple(version: str) -> Tuple[Union[int, str], ...]: +def get_version_tuple(version: str) -> tuple[Union[int, str], ...]: pattern = r"(?P\d+).(?P\d+).(?P\d+)(?P.*)" match = re.match(pattern, version) if match: - parts: List[Union[int, str]] = [int(match[part]) for part in ["major", "minor", "patch"]] + parts: list[Union[int, str]] = [int(match[part]) for part in ["major", "minor", "patch"]] if match["rest"]: parts.append(match["rest"]) elif re.match(r"\d+.\d+", version): diff --git a/pymongo/asynchronous/aggregation.py b/pymongo/asynchronous/aggregation.py index 6ca60ad9c3..f1f77acc73 100644 --- a/pymongo/asynchronous/aggregation.py +++ b/pymongo/asynchronous/aggregation.py @@ -13,6 +13,7 @@ # permissions and limitations under the License. """Perform aggregation operations on a collection or database.""" + from __future__ import annotations from collections.abc import Callable, Mapping, MutableMapping diff --git a/pymongo/asynchronous/auth.py b/pymongo/asynchronous/auth.py index c1321f1d90..cd3cee6295 100644 --- a/pymongo/asynchronous/auth.py +++ b/pymongo/asynchronous/auth.py @@ -13,6 +13,7 @@ # limitations under the License. """Authentication helpers.""" + from __future__ import annotations import functools @@ -20,13 +21,11 @@ import hmac import socket from base64 import standard_b64decode, standard_b64encode +from collections.abc import Coroutine, Mapping, MutableMapping from typing import ( TYPE_CHECKING, Any, Callable, - Coroutine, - Mapping, - MutableMapping, Optional, cast, ) @@ -184,7 +183,7 @@ async def _canonicalize_hostname(hostname: str, option: str | bool) -> str: if option in [False, "none"]: return hostname - af, socktype, proto, canonname, sockaddr = ( + _af, _socktype, _proto, canonname, sockaddr = ( await _getaddrinfo( hostname, None, diff --git a/pymongo/asynchronous/auth_aws.py b/pymongo/asynchronous/auth_aws.py index 210d306046..5fb8ec7e07 100644 --- a/pymongo/asynchronous/auth_aws.py +++ b/pymongo/asynchronous/auth_aws.py @@ -13,9 +13,11 @@ # limitations under the License. """MONGODB-AWS Authentication helpers.""" + from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, Type +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any import bson from bson.binary import Binary @@ -51,7 +53,7 @@ async def _authenticate_aws(credentials: MongoCredential, conn: AsyncConnection) class AwsSaslContext(pymongo_auth_aws.AwsSaslContext): # type: ignore # Dependency injection: - def binary_type(self) -> Type[Binary]: + def binary_type(self) -> type[Binary]: """Return the bson.binary.Binary type.""" return Binary diff --git a/pymongo/asynchronous/auth_oidc.py b/pymongo/asynchronous/auth_oidc.py index f8f046bd94..85df19a514 100644 --- a/pymongo/asynchronous/auth_oidc.py +++ b/pymongo/asynchronous/auth_oidc.py @@ -13,13 +13,15 @@ # limitations under the License. """MONGODB-OIDC Authentication helpers.""" + from __future__ import annotations import asyncio import threading import time +from collections.abc import Mapping, MutableMapping from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Mapping, MutableMapping, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union import bson from bson.binary import Binary diff --git a/pymongo/asynchronous/bulk.py b/pymongo/asynchronous/bulk.py index 4a54f9eb3f..c548403648 100644 --- a/pymongo/asynchronous/bulk.py +++ b/pymongo/asynchronous/bulk.py @@ -16,20 +16,18 @@ .. versionadded:: 2.7 """ + from __future__ import annotations import copy import datetime import logging -from collections.abc import MutableMapping +from collections.abc import Iterator, Mapping, MutableMapping from itertools import islice from typing import ( TYPE_CHECKING, Any, - Iterator, - Mapping, Optional, - Type, Union, ) @@ -119,7 +117,7 @@ def __init__( self.is_encrypted = False @property - def bulk_ctx_class(self) -> Type[_BulkWriteContext]: + def bulk_ctx_class(self) -> type[_BulkWriteContext]: encrypter = self.collection.database.client._encrypter if encrypter and not encrypter._bypass_auto_encryption: self.is_encrypted = True diff --git a/pymongo/asynchronous/change_stream.py b/pymongo/asynchronous/change_stream.py index b2b78b0660..e9d588ac95 100644 --- a/pymongo/asynchronous/change_stream.py +++ b/pymongo/asynchronous/change_stream.py @@ -13,10 +13,12 @@ # permissions and limitations under the License. """Watch changes on a collection, a database, or the entire cluster.""" + from __future__ import annotations import copy -from typing import TYPE_CHECKING, Any, Generic, Mapping, Optional, Type, Union +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Generic, Optional, Union from bson import CodecOptions, _bson_to_dict from bson.raw_bson import RawBSONDocument @@ -159,7 +161,7 @@ async def _initialize_cursor(self) -> None: self._cursor = await self._create_cursor() @property - def _aggregation_command_class(self) -> Type[_AggregationCommand]: + def _aggregation_command_class(self) -> type[_AggregationCommand]: """The aggregation command class to be used.""" raise NotImplementedError @@ -231,8 +233,7 @@ def _process_result(self, result: Mapping[str, Any], conn: AsyncConnection) -> N # PYTHON-2181: informative error on missing operationTime. if self._start_at_operation_time is None: raise OperationFailure( - "Expected field 'operationTime' missing from command " - f"response : {result!r}" + f"Expected field 'operationTime' missing from command response : {result!r}" ) async def _run_aggregation_cmd( @@ -453,7 +454,7 @@ class AsyncCollectionChangeStream(AsyncChangeStream[_DocumentType]): _target: AsyncCollection[_DocumentType] @property - def _aggregation_command_class(self) -> Type[_CollectionAggregationCommand]: + def _aggregation_command_class(self) -> type[_CollectionAggregationCommand]: return _CollectionAggregationCommand @property @@ -473,7 +474,7 @@ class AsyncDatabaseChangeStream(AsyncChangeStream[_DocumentType]): _target: AsyncDatabase[_DocumentType] @property - def _aggregation_command_class(self) -> Type[_DatabaseAggregationCommand]: + def _aggregation_command_class(self) -> type[_DatabaseAggregationCommand]: return _DatabaseAggregationCommand @property diff --git a/pymongo/asynchronous/client_bulk.py b/pymongo/asynchronous/client_bulk.py index 015947d7ef..fecf41b675 100644 --- a/pymongo/asynchronous/client_bulk.py +++ b/pymongo/asynchronous/client_bulk.py @@ -16,19 +16,18 @@ .. versionadded:: 4.9 """ + from __future__ import annotations import copy import datetime import logging -from collections.abc import MutableMapping +from collections.abc import Mapping, MutableMapping from itertools import islice from typing import ( TYPE_CHECKING, Any, - Mapping, Optional, - Type, Union, ) @@ -119,7 +118,7 @@ def __init__( self.started_retryable_write = False @property - def bulk_ctx_class(self) -> Type[_ClientBulkWriteContext]: + def bulk_ctx_class(self) -> type[_ClientBulkWriteContext]: return _ClientBulkWriteContext def add_insert(self, namespace: str, document: _DocumentOut) -> None: diff --git a/pymongo/asynchronous/client_session.py b/pymongo/asynchronous/client_session.py index f9d778f648..66bafbc44d 100644 --- a/pymongo/asynchronous/client_session.py +++ b/pymongo/asynchronous/client_session.py @@ -140,19 +140,16 @@ import random import time import uuid +from collections.abc import Awaitable, Mapping, MutableMapping from collections.abc import Mapping as _Mapping +from contextlib import AbstractAsyncContextManager from contextvars import ContextVar, Token from typing import ( TYPE_CHECKING, Any, - AsyncContextManager, - Awaitable, Callable, - Mapping, - MutableMapping, NoReturn, Optional, - Type, TypeVar, ) @@ -243,9 +240,7 @@ def __init__( if not isinstance(default_transaction_options, TransactionOptions): raise TypeError( "default_transaction_options must be an instance of " - "pymongo.client_session.TransactionOptions, not: {!r}".format( - default_transaction_options - ) + f"pymongo.client_session.TransactionOptions, not: {default_transaction_options!r}" ) self._default_transaction_options = default_transaction_options self._snapshot = snapshot @@ -325,8 +320,7 @@ def __init__( ) if not write_concern.acknowledged: raise ConfigurationError( - "transactions do not support unacknowledged write concern" - f": {write_concern!r}" + f"transactions do not support unacknowledged write concern: {write_concern!r}" ) if read_preference is not None: if not isinstance(read_preference, _ServerMode): @@ -400,7 +394,7 @@ async def __aenter__(self) -> _TransactionContext: async def __aexit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: @@ -481,7 +475,7 @@ def __del__(self) -> None: def _reraise_with_unknown_commit(exc: Any) -> NoReturn: """Re-raise an exception with the UnknownTransactionCommitResult label.""" exc._add_error_label("UnknownTransactionCommitResult") - raise + raise exc def _max_time_expired_error(exc: PyMongoError) -> bool: @@ -835,7 +829,7 @@ async def start_transaction( write_concern: Optional[WriteConcern] = None, read_preference: Optional[_ServerMode] = None, max_commit_time_ms: Optional[int] = None, - ) -> AsyncContextManager[Any]: + ) -> AbstractAsyncContextManager[Any]: """Start a multi-statement transaction. Takes the same arguments as :class:`TransactionOptions`. diff --git a/pymongo/asynchronous/collection.py b/pymongo/asynchronous/collection.py index 4fff6650f1..ddffc46632 100644 --- a/pymongo/asynchronous/collection.py +++ b/pymongo/asynchronous/collection.py @@ -13,24 +13,19 @@ # limitations under the License. """Collection level utilities for Mongo.""" + from __future__ import annotations import warnings from collections import abc +from collections.abc import Coroutine, Iterable, Iterator, Mapping, MutableMapping, Sequence from typing import ( TYPE_CHECKING, Any, Callable, - Coroutine, Generic, - Iterable, - Iterator, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, - Type, TypeVar, Union, cast, @@ -238,9 +233,9 @@ def __init__( if not name or ".." in name: raise InvalidName("collection names cannot be empty") if "$" in name and not (name.startswith(("oplog.$main", "$cmd"))): - raise InvalidName("collection names must not contain '$': %r" % name) + raise InvalidName(f"collection names must not contain '$': {name!r}") if name[0] == "." or name[-1] == ".": - raise InvalidName("collection names must not start or end with '.': %r" % name) + raise InvalidName(f"collection names must not start or end with '.': {name!r}") if "\x00" in name: raise InvalidName("collection names must not contain the null character") @@ -339,8 +334,7 @@ def with_options( read_preference: Optional[_ServerMode] = ..., write_concern: Optional[WriteConcern] = ..., read_concern: Optional[ReadConcern] = ..., - ) -> AsyncCollection[_DocumentType]: - ... + ) -> AsyncCollection[_DocumentType]: ... @overload def with_options( @@ -349,8 +343,7 @@ def with_options( read_preference: Optional[_ServerMode] = ..., write_concern: Optional[WriteConcern] = ..., read_concern: Optional[ReadConcern] = ..., - ) -> AsyncCollection[_DocumentTypeArg]: - ... + ) -> AsyncCollection[_DocumentTypeArg]: ... def with_options( self, @@ -2920,9 +2913,9 @@ async def options( @_csot.apply async def _aggregate( self, - aggregation_command: Type[_AggregationCommand], + aggregation_command: type[_AggregationCommand], pipeline: _Pipeline, - cursor_class: Type[AsyncCommandCursor], # type: ignore[type-arg] + cursor_class: type[AsyncCommandCursor], # type: ignore[type-arg] session: Optional[AsyncClientSession], let: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, diff --git a/pymongo/asynchronous/command_cursor.py b/pymongo/asynchronous/command_cursor.py index 5a59c67a15..1109c3f3ab 100644 --- a/pymongo/asynchronous/command_cursor.py +++ b/pymongo/asynchronous/command_cursor.py @@ -13,17 +13,16 @@ # limitations under the License. """CommandCursor class to iterate over command results.""" + from __future__ import annotations from collections import deque +from collections.abc import AsyncIterator, Mapping, Sequence from typing import ( TYPE_CHECKING, Any, - AsyncIterator, - Mapping, NoReturn, Optional, - Sequence, Union, ) @@ -78,7 +77,7 @@ def __init__( if self._killed: self._end_session() - if "ns" in cursor_info: # noqa: SIM401 + if "ns" in cursor_info: self._ns = cursor_info["ns"] else: self._ns = collection.full_name @@ -113,7 +112,7 @@ def batch_size(self, batch_size: int) -> AsyncCommandCursor[_DocumentType]: if batch_size < 0: raise ValueError("batch_size must be >= 0") - self._batch_size = batch_size == 1 and 2 or batch_size + self._batch_size = (batch_size == 1 and 2) or batch_size return self def _has_next(self) -> bool: diff --git a/pymongo/asynchronous/cursor.py b/pymongo/asynchronous/cursor.py index a60c082ade..8bb274ac77 100644 --- a/pymongo/asynchronous/cursor.py +++ b/pymongo/asynchronous/cursor.py @@ -13,20 +13,18 @@ # limitations under the License. """Cursor class to iterate over Mongo query results.""" + from __future__ import annotations import copy import warnings from collections import deque +from collections.abc import Iterable, Mapping, Sequence from typing import ( TYPE_CHECKING, Any, - Iterable, - List, - Mapping, NoReturn, Optional, - Sequence, Union, cast, overload, @@ -169,7 +167,7 @@ def __init__( self._skip = skip self._limit = limit self._batch_size = batch_size - self._ordering = sort and helpers_shared._index_document(sort) or None + self._ordering = (sort and helpers_shared._index_document(sort)) or None self._max_scan = max_scan self._explain = False self._comment = comment @@ -533,12 +531,10 @@ def max_await_time_ms(self, max_await_time_ms: Optional[int]) -> AsyncCursor[_Do return self @overload - def __getitem__(self, index: int) -> _DocumentType: - ... + def __getitem__(self, index: int) -> _DocumentType: ... @overload - def __getitem__(self, index: slice) -> AsyncCursor[_DocumentType]: - ... + def __getitem__(self, index: slice) -> AsyncCursor[_DocumentType]: ... def __getitem__( self, index: Union[int, slice] @@ -599,7 +595,7 @@ def __getitem__( limit = index.stop - skip if limit < 0: raise IndexError( - "stop index must be greater than start index for slice %r" % index + f"stop index must be greater than start index for slice {index!r}" ) if limit == 0: self._empty = True @@ -620,7 +616,7 @@ def __getitem__( for doc in clone: # type: ignore[attr-defined] return doc raise IndexError("no such item for AsyncCursor instance") - raise TypeError("index %r cannot be applied to AsyncCursor instances" % index) + raise TypeError(f"index {index!r} cannot be applied to AsyncCursor instances") else: raise IndexError("AsyncCursor does not support indexing") @@ -1206,7 +1202,7 @@ def _unpack_response( # OP_MSG returns firstBatch/nextBatch documents as a BSON array # Re-assemble the array of documents into a document stream _convert_raw_document_lists_to_streams(raw_response[0]) - return cast(List["_DocumentOut"], raw_response) + return cast(list["_DocumentOut"], raw_response) async def explain(self) -> _DocumentType: """Returns an explain plan record for this cursor. diff --git a/pymongo/asynchronous/cursor_base.py b/pymongo/asynchronous/cursor_base.py index 1e293a1812..ce3114684a 100644 --- a/pymongo/asynchronous/cursor_base.py +++ b/pymongo/asynchronous/cursor_base.py @@ -13,6 +13,7 @@ # permissions and limitations under the License. """Asynchronous cursor base extending the shared agnostic cursor base.""" + from __future__ import annotations from abc import abstractmethod diff --git a/pymongo/asynchronous/database.py b/pymongo/asynchronous/database.py index 28ed36073c..88ca9cab5b 100644 --- a/pymongo/asynchronous/database.py +++ b/pymongo/asynchronous/database.py @@ -13,18 +13,17 @@ # limitations under the License. """Database level operations.""" + from __future__ import annotations +from collections.abc import Mapping, MutableMapping, Sequence from copy import deepcopy from typing import ( TYPE_CHECKING, Any, Generic, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, Union, cast, overload, @@ -153,8 +152,7 @@ def with_options( read_preference: Optional[_ServerMode] = ..., write_concern: Optional[WriteConcern] = ..., read_concern: Optional[ReadConcern] = ..., - ) -> AsyncDatabase[_DocumentType]: - ... + ) -> AsyncDatabase[_DocumentType]: ... @overload def with_options( @@ -163,8 +161,7 @@ def with_options( read_preference: Optional[_ServerMode] = ..., write_concern: Optional[WriteConcern] = ..., read_concern: Optional[ReadConcern] = ..., - ) -> AsyncDatabase[_DocumentTypeArg]: - ... + ) -> AsyncDatabase[_DocumentTypeArg]: ... def with_options( self, @@ -620,7 +617,7 @@ async def create_collection( and (not s or not s.in_transaction) and name in await self._list_collection_names(filter={"name": name}, session=s) ): - raise CollectionInvalid("collection %s already exists" % name) + raise CollectionInvalid(f"collection {name} already exists") if s: s._leave_alive = False coll = AsyncCollection( @@ -733,8 +730,7 @@ async def _command( parse_write_concern_error: bool = False, session: Optional[AsyncClientSession] = None, **kwargs: Any, - ) -> dict[str, Any]: - ... + ) -> dict[str, Any]: ... @overload async def _command( @@ -750,8 +746,7 @@ async def _command( parse_write_concern_error: bool = False, session: Optional[AsyncClientSession] = None, **kwargs: Any, - ) -> _CodecDocumentType: - ... + ) -> _CodecDocumentType: ... async def _command( self, @@ -800,8 +795,7 @@ async def command( session: Optional[AsyncClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> dict[str, Any]: - ... + ) -> dict[str, Any]: ... @overload async def command( @@ -815,8 +809,7 @@ async def command( session: Optional[AsyncClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> _CodecDocumentType: - ... + ) -> _CodecDocumentType: ... @_csot.apply async def command( @@ -1471,7 +1464,7 @@ async def dereference( Added ``session`` parameter. """ if not isinstance(dbref, DBRef): - raise TypeError("cannot dereference a %s" % type(dbref)) + raise TypeError(f"cannot dereference a {type(dbref)}") if dbref.database is not None and dbref.database != self._name: raise ValueError( "trying to dereference a DBRef that points to " diff --git a/pymongo/asynchronous/encryption.py b/pymongo/asynchronous/encryption.py index c7779a3e31..c7540265f9 100644 --- a/pymongo/asynchronous/encryption.py +++ b/pymongo/asynchronous/encryption.py @@ -13,6 +13,7 @@ # limitations under the License. """Support for explicit client-side field level encryption.""" + from __future__ import annotations import asyncio @@ -22,18 +23,13 @@ import time as time # noqa: PLC0414 # needed in sync version import uuid import weakref +from collections.abc import AsyncGenerator, Iterator, Mapping, MutableMapping, Sequence from copy import deepcopy from typing import ( TYPE_CHECKING, Any, - AsyncGenerator, - Dict, Generic, - Iterator, - Mapping, - MutableMapping, Optional, - Sequence, Union, cast, ) @@ -109,7 +105,7 @@ _MONGOCRYPTD_TIMEOUT_MS = 10000 _DATA_KEY_OPTS: CodecOptions[dict[str, Any]] = CodecOptions( - document_class=Dict[str, Any], uuid_representation=STANDARD + document_class=dict[str, Any], uuid_representation=STANDARD ) # Use RawBSONDocument codec options to avoid needlessly decoding # documents from the key vault. diff --git a/pymongo/asynchronous/helpers.py b/pymongo/asynchronous/helpers.py index 16b007c38f..0ee99a441a 100644 --- a/pymongo/asynchronous/helpers.py +++ b/pymongo/asynchronous/helpers.py @@ -13,6 +13,7 @@ # limitations under the License. """Miscellaneous pieces that need to be synchronized.""" + from __future__ import annotations import asyncio diff --git a/pymongo/asynchronous/mongo_client.py b/pymongo/asynchronous/mongo_client.py index 412a13ec70..beca65fbc4 100644 --- a/pymongo/asynchronous/mongo_client.py +++ b/pymongo/asynchronous/mongo_client.py @@ -30,6 +30,7 @@ >>> c["test-database"] AsyncDatabase(AsyncMongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'test-database') """ + from __future__ import annotations import asyncio @@ -39,22 +40,15 @@ import warnings import weakref from collections import defaultdict +from collections.abc import AsyncGenerator, Collection, Coroutine, Mapping, MutableMapping, Sequence +from contextlib import AbstractAsyncContextManager from typing import ( TYPE_CHECKING, Any, - AsyncContextManager, - AsyncGenerator, Callable, - Collection, - Coroutine, - FrozenSet, Generic, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, - Type, TypeVar, Union, cast, @@ -186,7 +180,7 @@ def __init__( self, host: Optional[Union[str, Sequence[str]]] = None, port: Optional[int] = None, - document_class: Optional[Type[_DocumentType]] = None, + document_class: Optional[type[_DocumentType]] = None, tz_aware: Optional[bool] = None, connect: Optional[bool] = None, type_registry: Optional[TypeRegistry] = None, @@ -1255,7 +1249,7 @@ def topology_description(self) -> TopologyDescription: return self._topology.description @property - def nodes(self) -> FrozenSet[_Address]: + def nodes(self) -> frozenset[_Address]: """Set of all currently connected servers. .. warning:: When connected to a replica set the value of :attr:`nodes` @@ -1317,11 +1311,12 @@ def option_repr(option: str, value: Any) -> str: options = [f"host='mongodb+srv://{self._resolve_srv_info['fqdn']}'"] else: options = [ - "host=%r" - % [ - "%s:%d" % (host, port) if port is not None else host - for host, port in self._topology_settings.seeds - ] + "host={!r}".format( + [ + f"{host}:{port}" if port is not None else host + for host, port in self._topology_settings.seeds + ] + ) ] # ... then everything in self._constructor_args... options.extend( @@ -1868,7 +1863,7 @@ async def _select_server( async def _conn_for_writes( self, session: Optional[AsyncClientSession], operation: str - ) -> AsyncContextManager[AsyncConnection]: + ) -> AbstractAsyncContextManager[AsyncConnection]: server = await self._select_server(writable_server_selector, session, operation) return self._checkout(server, session) @@ -1902,7 +1897,7 @@ async def _conn_for_reads( read_preference: _ServerMode, session: Optional[AsyncClientSession], operation: str, - ) -> AsyncContextManager[tuple[AsyncConnection, _ServerMode]]: + ) -> AbstractAsyncContextManager[tuple[AsyncConnection, _ServerMode]]: assert read_preference is not None, "read_preference must not be None" server = await self._select_server(read_preference, session, operation) return self._conn_from_server(read_preference, server, session) @@ -2677,13 +2672,13 @@ class _MongoClientErrorHandler: __slots__ = ( "client", + "completed_handshake", + "handled", + "max_wire_version", "server_address", + "service_id", "session", - "max_wire_version", "sock_generation", - "completed_handshake", - "service_id", - "handled", ) def __init__( @@ -2718,7 +2713,7 @@ def contribute_socket(self, conn: AsyncConnection, completed_handshake: bool = T self.completed_handshake = completed_handshake async def handle( - self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException] + self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException] ) -> None: if self.handled or exc_val is None: return @@ -2751,7 +2746,7 @@ async def __aenter__(self) -> _MongoClientErrorHandler: async def __aexit__( self, - exc_type: Optional[Type[Exception]], + exc_type: Optional[type[Exception]], exc_val: Optional[Exception], exc_tb: Optional[TracebackType], ) -> None: diff --git a/pymongo/asynchronous/network.py b/pymongo/asynchronous/network.py index 5a5dc7fa2c..a5f978572a 100644 --- a/pymongo/asynchronous/network.py +++ b/pymongo/asynchronous/network.py @@ -13,17 +13,16 @@ # limitations under the License. """Internal network layer helper methods.""" + from __future__ import annotations import datetime import logging +from collections.abc import Mapping, MutableMapping, Sequence from typing import ( TYPE_CHECKING, Any, - Mapping, - MutableMapping, Optional, - Sequence, Union, cast, ) diff --git a/pymongo/asynchronous/pool.py b/pymongo/asynchronous/pool.py index a5d5b28990..ec82a27552 100644 --- a/pymongo/asynchronous/pool.py +++ b/pymongo/asynchronous/pool.py @@ -24,15 +24,12 @@ import sys import time import weakref +from collections.abc import AsyncGenerator, Mapping, MutableMapping, Sequence from typing import ( TYPE_CHECKING, Any, - AsyncGenerator, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, Union, ) @@ -437,8 +434,8 @@ async def send_message(self, message: bytes, max_doc_size: int) -> None: """ if self.max_bson_size is not None and max_doc_size > self.max_bson_size: raise DocumentTooLarge( - "BSON document too large (%d bytes) - the connected server " - "supports BSON document sizes up to %d bytes." % (max_doc_size, self.max_bson_size) + f"BSON document too large ({max_doc_size} bytes) - the connected server " + f"supports BSON document sizes up to {self.max_bson_size} bytes." ) try: @@ -634,7 +631,7 @@ async def _raise_connection_failure(self, error: BaseException) -> NoReturn: details = _get_timeout_details(self.opts) _raise_connection_failure(self.address, error, timeout_details=details) else: - raise + raise error def __eq__(self, other: Any) -> bool: return self.conn == other.conn @@ -648,7 +645,7 @@ def __hash__(self) -> int: def __repr__(self) -> str: return "AsyncConnection({}){} at {}".format( repr(self.conn), - self.closed and " CLOSED" or "", + (self.closed and " CLOSED") or "", id(self), ) @@ -1464,15 +1461,9 @@ def _raise_wait_queue_timeout(self, checkout_started_time: float) -> NoReturn: other_ops = self.active_sockets - self.ncursors - self.ntxns raise WaitQueueTimeoutError( "Timeout waiting for connection from the connection pool. " - "maxPoolSize: {}, connections in use by cursors: {}, " - "connections in use by transactions: {}, connections in use " - "by other operations: {}, timeout: {}".format( - self.opts.max_pool_size, - self.ncursors, - self.ntxns, - other_ops, - timeout, - ) + f"maxPoolSize: {self.opts.max_pool_size}, connections in use by cursors: {self.ncursors}, " + f"connections in use by transactions: {self.ntxns}, connections in use " + f"by other operations: {other_ops}, timeout: {timeout}" ) raise WaitQueueTimeoutError( "Timed out while checking out a connection from connection pool. " diff --git a/pymongo/asynchronous/server.py b/pymongo/asynchronous/server.py index f212306174..ec24a7c66a 100644 --- a/pymongo/asynchronous/server.py +++ b/pymongo/asynchronous/server.py @@ -13,14 +13,15 @@ # permissions and limitations under the License. """Communicate with one MongoDB server in a topology.""" + from __future__ import annotations import logging +from contextlib import AbstractAsyncContextManager from datetime import datetime from typing import ( TYPE_CHECKING, Any, - AsyncContextManager, Callable, Optional, Union, @@ -349,7 +350,7 @@ async def run_operation( async def checkout( self, handler: Optional[_MongoClientErrorHandler] = None - ) -> AsyncContextManager[AsyncConnection]: + ) -> AbstractAsyncContextManager[AsyncConnection]: return self.pool.checkout(handler) @property diff --git a/pymongo/asynchronous/settings.py b/pymongo/asynchronous/settings.py index 9c2331971a..e3c2ee7fb3 100644 --- a/pymongo/asynchronous/settings.py +++ b/pymongo/asynchronous/settings.py @@ -13,11 +13,13 @@ # permissions and limitations under the License. """Represent MongoClient's configuration.""" + from __future__ import annotations import threading import traceback -from typing import Any, Collection, Optional, Type, Union +from collections.abc import Collection +from typing import Any, Optional, Union from bson.objectid import ObjectId from pymongo import common @@ -37,10 +39,10 @@ def __init__( self, seeds: Optional[Collection[tuple[str, int]]] = None, replica_set_name: Optional[str] = None, - pool_class: Optional[Type[Pool]] = None, + pool_class: Optional[type[Pool]] = None, pool_options: Optional[PoolOptions] = None, - monitor_class: Optional[Type[monitor.Monitor]] = None, - condition_class: Optional[Type[threading.Condition]] = None, + monitor_class: Optional[type[monitor.Monitor]] = None, + condition_class: Optional[type[threading.Condition]] = None, local_threshold_ms: int = LOCAL_THRESHOLD_MS, server_selection_timeout: int = SERVER_SELECTION_TIMEOUT, heartbeat_frequency: int = common.HEARTBEAT_FREQUENCY, @@ -59,16 +61,15 @@ def __init__( """ if heartbeat_frequency < common.MIN_HEARTBEAT_INTERVAL: raise ConfigurationError( - "heartbeatFrequencyMS cannot be less than %d" - % (common.MIN_HEARTBEAT_INTERVAL * 1000,) + f"heartbeatFrequencyMS cannot be less than {common.MIN_HEARTBEAT_INTERVAL * 1000}" ) self._seeds: Collection[tuple[str, int]] = seeds or [("localhost", 27017)] self._replica_set_name = replica_set_name - self._pool_class: Type[Pool] = pool_class or pool.Pool + self._pool_class: type[Pool] = pool_class or pool.Pool self._pool_options: PoolOptions = pool_options or PoolOptions() - self._monitor_class: Type[monitor.Monitor] = monitor_class or monitor.Monitor - self._condition_class: Type[threading.Condition] = condition_class or threading.Condition + self._monitor_class: type[monitor.Monitor] = monitor_class or monitor.Monitor + self._condition_class: type[threading.Condition] = condition_class or threading.Condition self._local_threshold_ms = local_threshold_ms self._server_selection_timeout = server_selection_timeout self._server_selector = server_selector @@ -97,7 +98,7 @@ def replica_set_name(self) -> Optional[str]: return self._replica_set_name @property - def pool_class(self) -> Type[Pool]: + def pool_class(self) -> type[Pool]: return self._pool_class @property @@ -105,11 +106,11 @@ def pool_options(self) -> PoolOptions: return self._pool_options @property - def monitor_class(self) -> Type[monitor.Monitor]: + def monitor_class(self) -> type[monitor.Monitor]: return self._monitor_class @property - def condition_class(self) -> Type[threading.Condition]: + def condition_class(self) -> type[threading.Condition]: return self._condition_class @property diff --git a/pymongo/asynchronous/srv_resolver.py b/pymongo/asynchronous/srv_resolver.py index 9c4d9a9d57..2d99ef16c8 100644 --- a/pymongo/asynchronous/srv_resolver.py +++ b/pymongo/asynchronous/srv_resolver.py @@ -13,6 +13,7 @@ # permissions and limitations under the License. """Support for resolving hosts and options from mongodb+srv:// URIs.""" + from __future__ import annotations import ipaddress diff --git a/pymongo/asynchronous/topology.py b/pymongo/asynchronous/topology.py index 01e346bfa8..6af075cdc2 100644 --- a/pymongo/asynchronous/topology.py +++ b/pymongo/asynchronous/topology.py @@ -25,8 +25,9 @@ import time import warnings import weakref +from collections.abc import Mapping from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, cast +from typing import TYPE_CHECKING, Any, Callable, Optional, cast from pymongo import _csot, common, helpers_shared, periodic_executor from pymongo.asynchronous.client_session import _ServerSession, _ServerSessionPool @@ -236,8 +237,7 @@ async def open(self) -> None: kwargs = {"skip_file_prefixes": (_pymongo_dir,)} else: kwargs = {"stacklevel": 6} - # Ignore B028 warning for missing stacklevel. - warnings.warn( # type: ignore[call-overload] # noqa: B028 + warnings.warn( # type: ignore[call-overload] "AsyncMongoClient opened before fork. May not be entirely fork-safe, " "proceed with caution. See PyMongo's documentation for details: " "https://dochub.mongodb.org/core/pymongo-fork-deadlock", @@ -1036,7 +1036,7 @@ def _error_message(self, selector: Callable[[Selection], Selection]) -> str: if is_replica_set: return "No primary available for writes" else: - return "No %s available for writes" % server_plural + return f"No {server_plural} available for writes" else: return f'No {server_plural} match selector "{selector}"' else: @@ -1045,12 +1045,9 @@ def _error_message(self, selector: Callable[[Selection], Selection]) -> str: if not servers: if is_replica_set: # We removed all servers because of the wrong setName? - return 'No {} available for replica set name "{}"'.format( - server_plural, - self._settings.replica_set_name, - ) + return f'No {server_plural} available for replica set name "{self._settings.replica_set_name}"' else: - return "No %s available" % server_plural + return f"No {server_plural} available" # 1 or more servers, all Unknown. Are they unknown for one reason? error = servers[0].error @@ -1058,13 +1055,13 @@ def _error_message(self, selector: Callable[[Selection], Selection]) -> str: if same: if error is None: # We're still discovering. - return "No %s found yet" % server_plural + return f"No {server_plural} found yet" if is_replica_set and not set(addresses).intersection(self._seed_addresses): # We replaced our seeds with new hosts but can't reach any. return ( - "Could not reach any servers in %s. Replica set is" - " configured with internal hostnames or IPs?" % addresses + f"Could not reach any servers in {addresses}. Replica set is" + " configured with internal hostnames or IPs?" ) return str(error) diff --git a/pymongo/asynchronous/uri_parser.py b/pymongo/asynchronous/uri_parser.py index 055b04d75a..c1d4b1d2f4 100644 --- a/pymongo/asynchronous/uri_parser.py +++ b/pymongo/asynchronous/uri_parser.py @@ -14,6 +14,7 @@ """Tools to parse and validate a MongoDB URI.""" + from __future__ import annotations from typing import Any, Optional @@ -159,7 +160,7 @@ async def _parse_srv( srv_max_hosts = srv_max_hosts or options.get("srvMaxHosts") if is_srv: nodes = split_hosts(hosts, default_port=None) - fqdn, port = nodes[0] + fqdn, _port = nodes[0] # Use the connection timeout. connectTimeoutMS passed as a keyword # argument overrides the same option passed in the connection string. diff --git a/pymongo/auth.py b/pymongo/auth.py index a36f3f4233..017de17270 100644 --- a/pymongo/auth.py +++ b/pymongo/auth.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous Auth API for compatibility.""" + from __future__ import annotations from pymongo.auth_shared import * # noqa: F403 diff --git a/pymongo/auth_oidc.py b/pymongo/auth_oidc.py index 61764b8111..e3256ca369 100644 --- a/pymongo/auth_oidc.py +++ b/pymongo/auth_oidc.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous AuthOIDC API for compatibility.""" + from __future__ import annotations from pymongo.auth_oidc_shared import * # noqa: F403 diff --git a/pymongo/auth_oidc_shared.py b/pymongo/auth_oidc_shared.py index d33397f52d..22d7e1fa8e 100644 --- a/pymongo/auth_oidc_shared.py +++ b/pymongo/auth_oidc_shared.py @@ -14,6 +14,7 @@ """Constants, types, and classes shared across OIDC auth implementations.""" + from __future__ import annotations import abc diff --git a/pymongo/auth_shared.py b/pymongo/auth_shared.py index 715749243f..34a7e27451 100644 --- a/pymongo/auth_shared.py +++ b/pymongo/auth_shared.py @@ -14,13 +14,15 @@ """Constants and types shared across multiple auth types.""" + from __future__ import annotations import os import typing from base64 import standard_b64encode from collections import namedtuple -from typing import Any, Dict, Mapping, Optional +from collections.abc import Mapping +from typing import Any, Optional from bson import Binary from pymongo.auth_oidc_shared import ( @@ -229,7 +231,7 @@ def _xor(fir: bytes, sec: bytes) -> bytes: return b"".join([bytes([x ^ y]) for x, y in zip(fir, sec)]) -def _parse_scram_response(response: bytes) -> Dict[bytes, bytes]: +def _parse_scram_response(response: bytes) -> dict[bytes, bytes]: """Split a scram response into key, value pairs.""" return dict( typing.cast(typing.Tuple[bytes, bytes], item.split(b"=", 1)) diff --git a/pymongo/bulk_shared.py b/pymongo/bulk_shared.py index 9276419d8a..a086611be5 100644 --- a/pymongo/bulk_shared.py +++ b/pymongo/bulk_shared.py @@ -14,9 +14,11 @@ """Constants, types, and classes shared across Bulk Write API implementations.""" + from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, MutableMapping, NoReturn +from collections.abc import Mapping, MutableMapping +from typing import TYPE_CHECKING, Any, NoReturn from pymongo.errors import BulkWriteError, OperationFailure from pymongo.helpers_shared import _get_wce_doc diff --git a/pymongo/change_stream.py b/pymongo/change_stream.py index f9abddec44..ac1ae64005 100644 --- a/pymongo/change_stream.py +++ b/pymongo/change_stream.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous ChangeStream API for compatibility.""" + from __future__ import annotations from pymongo.synchronous.change_stream import * # noqa: F403 diff --git a/pymongo/client_options.py b/pymongo/client_options.py index e5dc609946..99a2bcb4e4 100644 --- a/pymongo/client_options.py +++ b/pymongo/client_options.py @@ -16,9 +16,11 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence, cast +from collections.abc import Mapping, Sequence +from typing import TYPE_CHECKING, Any, Optional, cast from bson.codec_options import _parse_codec_options from pymongo import common @@ -108,7 +110,7 @@ def _parse_ssl_options( "tlscrlfile", ): # Any non-null value of these options implies tls=True. - if opt in options and options[opt]: + if options.get(opt): enabled_tls_opts.append(opt) for opt in ( "tlsallowinvalidcertificates", @@ -128,7 +130,7 @@ def _parse_ssl_options( raise ConfigurationError( "TLS has not been enabled but the " "following tls parameters have been set: " - "%s. Please set `tls=True` or remove." % ", ".join(enabled_tls_opts) + "{}. Please set `tls=True` or remove.".format(", ".join(enabled_tls_opts)) ) if use_tls: diff --git a/pymongo/client_session.py b/pymongo/client_session.py index db72b0b2e1..e30f564dac 100644 --- a/pymongo/client_session.py +++ b/pymongo/client_session.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous ClientSession API for compatibility.""" + from __future__ import annotations from pymongo.synchronous.client_session import * # noqa: F403 diff --git a/pymongo/collation.py b/pymongo/collation.py index 8a1eca7aff..9f108c447c 100644 --- a/pymongo/collation.py +++ b/pymongo/collation.py @@ -18,9 +18,11 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations -from typing import Any, Mapping, Optional, Union +from collections.abc import Mapping +from typing import Any, Optional, Union from pymongo import common from pymongo.write_concern import validate_boolean @@ -210,12 +212,14 @@ def __eq__(self, other: Any) -> bool: return self.document == other.document return NotImplemented + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: return not self == other def validate_collation_or_none( - value: Optional[Union[Mapping[str, Any], Collation]] + value: Optional[Union[Mapping[str, Any], Collation]], ) -> Optional[dict[str, Any]]: if value is None: return None diff --git a/pymongo/collection.py b/pymongo/collection.py index 16063425a7..0664e2a35b 100644 --- a/pymongo/collection.py +++ b/pymongo/collection.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous Collection API for compatibility.""" + from __future__ import annotations from pymongo.synchronous.collection import * # noqa: F403 diff --git a/pymongo/command_cursor.py b/pymongo/command_cursor.py index 941e3a0eda..ec9843649c 100644 --- a/pymongo/command_cursor.py +++ b/pymongo/command_cursor.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous CommandCursor API for compatibility.""" + from __future__ import annotations from pymongo.synchronous.command_cursor import * # noqa: F403 diff --git a/pymongo/common.py b/pymongo/common.py index 7ab4b64d2c..08da34c7bf 100644 --- a/pymongo/common.py +++ b/pymongo/common.py @@ -14,24 +14,21 @@ """Functions and classes common to multiple pymongo modules.""" + from __future__ import annotations import datetime import warnings from collections import OrderedDict, abc +from collections.abc import Iterator, Mapping, MutableMapping, Sequence from difflib import get_close_matches from importlib.metadata import requires, version from typing import ( TYPE_CHECKING, Any, Callable, - Iterator, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, - Type, Union, overload, ) @@ -57,7 +54,7 @@ from pymongo.typings import _AgnosticClientSession -ORDERED_TYPES: Sequence[Type[Any]] = (SON, OrderedDict) +ORDERED_TYPES: Sequence[type[Any]] = (SON, OrderedDict) # Defaults until we connect to a server and get updated limits. MAX_BSON_SIZE = 16 * (1024**2) @@ -483,7 +480,7 @@ def validate_auth_mechanism_properties(option: str, value: Any) -> dict[str, Uni def validate_document_class( option: str, value: Any -) -> Union[Type[MutableMapping[str, Any]], Type[RawBSONDocument]]: +) -> Union[type[MutableMapping[str, Any]], type[RawBSONDocument]]: """Validate the document_class option.""" # issubclass can raise TypeError for generic aliases like SON[str, Any]. # In that case we can use the base class for the comparison. @@ -632,7 +629,7 @@ def validate_unicode_decode_error_handler(dummy: Any, value: str) -> str: def validate_tzinfo(dummy: Any, value: Any) -> Optional[datetime.tzinfo]: """Validate the tzinfo option""" if value is not None and not isinstance(value, datetime.tzinfo): - raise TypeError("%s must be an instance of datetime.tzinfo" % value) + raise TypeError(f"{value} must be an instance of datetime.tzinfo") return value @@ -1018,6 +1015,8 @@ def __eq__(self, other: Any) -> bool: return True + __hash__ = None # type: ignore[assignment] + def get(self, key: str, default: Optional[Any] = None) -> Any: return self.__data.get(key.lower(), default) @@ -1036,12 +1035,10 @@ def clear(self) -> None: self.__data.clear() @overload - def setdefault(self, key: str, default: None = None) -> Optional[Any]: - ... + def setdefault(self, key: str, default: None = None) -> Optional[Any]: ... @overload - def setdefault(self, key: str, default: Any) -> Any: - ... + def setdefault(self, key: str, default: Any) -> Any: ... def setdefault(self, key: str, default: Optional[Any] = None) -> Optional[Any]: lc_key = key.lower() diff --git a/pymongo/compression_support.py b/pymongo/compression_support.py index f7ed1aadcc..d669e02b75 100644 --- a/pymongo/compression_support.py +++ b/pymongo/compression_support.py @@ -15,7 +15,8 @@ import sys import warnings -from typing import Any, Iterable, Optional, Union +from collections.abc import Iterable +from typing import Any, Optional, Union from pymongo.hello import HelloCompat from pymongo.helpers_shared import _SENSITIVE_COMMANDS @@ -104,7 +105,7 @@ def validate_zlib_compression_level(option: str, value: Any) -> int: except Exception: raise TypeError(f"{option} must be an integer, not {value!r}") from None if level < -1 or level > 9: - raise ValueError("%s must be between -1 and 9, not %d." % (option, level)) + raise ValueError(f"{option} must be between -1 and 9, not {level}.") return level @@ -184,4 +185,4 @@ def decompress(data: bytes | memoryview, compressor_id: int) -> bytes: return zstd.decompress(data) else: - raise ValueError("Unknown compressorId %d" % (compressor_id,)) + raise ValueError(f"Unknown compressorId {compressor_id}") diff --git a/pymongo/cursor.py b/pymongo/cursor.py index 869adddc37..7a33ed5973 100644 --- a/pymongo/cursor.py +++ b/pymongo/cursor.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous Cursor API for compatibility.""" + from __future__ import annotations from pymongo.cursor_shared import * # noqa: F403 diff --git a/pymongo/cursor_shared.py b/pymongo/cursor_shared.py index e5b76a404f..5d58f1845b 100644 --- a/pymongo/cursor_shared.py +++ b/pymongo/cursor_shared.py @@ -14,10 +14,12 @@ """Constants and types shared across all cursor classes.""" + from __future__ import annotations from abc import ABC, abstractmethod -from typing import Any, Generic, Mapping, Optional, Sequence, Tuple, Union +from collections.abc import Mapping, Sequence +from typing import Any, Generic, Optional, Union from pymongo.message import _CursorAddress from pymongo.typings import _Address, _DocumentType @@ -186,6 +188,6 @@ class CursorType: _Sort = Union[ - Sequence[Union[str, Tuple[str, Union[int, str, Mapping[str, Any]]]]], Mapping[str, Any] + Sequence[Union[str, tuple[str, Union[int, str, Mapping[str, Any]]]]], Mapping[str, Any] ] _Hint = Union[str, _Sort] diff --git a/pymongo/daemon.py b/pymongo/daemon.py index c0a01db16d..90e8fa42fc 100644 --- a/pymongo/daemon.py +++ b/pymongo/daemon.py @@ -18,13 +18,15 @@ client-side field level encryption is enabled. See `Client-side Field Level Encryption `_ for more info. """ + from __future__ import annotations import os import subprocess import sys import warnings -from typing import Any, Optional, Sequence +from collections.abc import Sequence +from typing import Any, Optional # The maximum amount of time to wait for the intermediate subprocess. _WAIT_TIMEOUT = 10 @@ -62,8 +64,8 @@ def _spawn_daemon(args: Sequence[str]) -> None: """Spawn a daemon process (Windows).""" try: with open(os.devnull, "r+b") as devnull: - popen = subprocess.Popen( - args, # noqa: S603 + popen = subprocess.Popen( # noqa: S603 + args, creationflags=_DETACHED_PROCESS, stdin=devnull, stderr=devnull, @@ -93,8 +95,8 @@ def _spawn(args: Sequence[str]) -> Optional[subprocess.Popen[Any]]: """Spawn the process and silence stdout/stderr.""" try: with open(os.devnull, "r+b") as devnull: - return subprocess.Popen( - args, # noqa: S603 + return subprocess.Popen( # noqa: S603 + args, close_fds=True, stdin=devnull, stderr=devnull, diff --git a/pymongo/database.py b/pymongo/database.py index f85b312f91..ff7a976791 100644 --- a/pymongo/database.py +++ b/pymongo/database.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous Database API for compatibility.""" + from __future__ import annotations from pymongo.synchronous.database import * # noqa: F403 diff --git a/pymongo/database_shared.py b/pymongo/database_shared.py index d6563a4b3d..8bbf04f29c 100644 --- a/pymongo/database_shared.py +++ b/pymongo/database_shared.py @@ -14,9 +14,11 @@ """Constants, helpers, and types shared across all database classes.""" + from __future__ import annotations -from typing import Any, Mapping, TypeVar +from collections.abc import Mapping +from typing import Any, TypeVar from pymongo.errors import InvalidName @@ -28,7 +30,7 @@ def _check_name(name: str) -> None: for invalid_char in [" ", ".", "$", "/", "\\", "\x00", '"']: if invalid_char in name: - raise InvalidName("database names cannot contain the character %r" % invalid_char) + raise InvalidName(f"database names cannot contain the character {invalid_char!r}") _CodecDocumentType = TypeVar("_CodecDocumentType", bound=Mapping[str, Any]) diff --git a/pymongo/driver_info.py b/pymongo/driver_info.py index f24321d973..18a51ae638 100644 --- a/pymongo/driver_info.py +++ b/pymongo/driver_info.py @@ -16,6 +16,7 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations from collections import namedtuple diff --git a/pymongo/encryption.py b/pymongo/encryption.py index 71c1d4b723..8fddc4c891 100644 --- a/pymongo/encryption.py +++ b/pymongo/encryption.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous Encryption API for compatibility.""" + from __future__ import annotations from pymongo.synchronous.encryption import * # noqa: F403 diff --git a/pymongo/encryption_options.py b/pymongo/encryption_options.py index b2037617b0..f2fcd47c65 100644 --- a/pymongo/encryption_options.py +++ b/pymongo/encryption_options.py @@ -16,9 +16,11 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, Optional, TypedDict +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Optional, TypedDict from pymongo.uri_parser_shared import _parse_kms_tls_options diff --git a/pymongo/errors.py b/pymongo/errors.py index 794b5a9398..59d9c203d9 100644 --- a/pymongo/errors.py +++ b/pymongo/errors.py @@ -16,10 +16,12 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations +from collections.abc import Iterable, Mapping, Sequence from ssl import SSLCertVerificationError as _CertificateError # noqa: F401 -from typing import TYPE_CHECKING, Any, Iterable, Mapping, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Optional, Union from bson.errors import InvalidDocument @@ -304,9 +306,7 @@ def timeout(self) -> bool: return True werrs = self.details.get("writeErrors", []) - if werrs and werrs[-1].get("code") == 50: - return True - return False + return bool(werrs and werrs[-1].get("code") == 50) class ClientBulkWriteException(OperationFailure): diff --git a/pymongo/event_loggers.py b/pymongo/event_loggers.py index 80acaa10c0..c158636098 100644 --- a/pymongo/event_loggers.py +++ b/pymongo/event_loggers.py @@ -29,6 +29,7 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations import logging @@ -116,9 +117,7 @@ def started(self, event: monitoring.ServerHeartbeatStartedEvent) -> None: def succeeded(self, event: monitoring.ServerHeartbeatSucceededEvent) -> None: # The reply.document attribute was added in PyMongo 3.4. logging.info( - f"Heartbeat to server {event.connection_id} " - "succeeded with reply " - f"{event.reply.document}" + f"Heartbeat to server {event.connection_id} succeeded with reply {event.reply.document}" ) def failed(self, event: monitoring.ServerHeartbeatFailedEvent) -> None: diff --git a/pymongo/hello.py b/pymongo/hello.py index 1eb40ed929..a6519eed8d 100644 --- a/pymongo/hello.py +++ b/pymongo/hello.py @@ -13,12 +13,14 @@ # limitations under the License. """Helpers for the 'hello' and legacy hello commands.""" + from __future__ import annotations import copy import datetime import itertools -from typing import Any, Generic, Mapping, Optional +from collections.abc import Mapping +from typing import Any, Generic, Optional from bson.objectid import ObjectId from pymongo import common @@ -68,7 +70,7 @@ class Hello(Generic[_DocumentType]): .. versionadded:: 3.12 """ - __slots__ = ("_doc", "_server_type", "_is_writable", "_is_readable", "_awaitable") + __slots__ = ("_awaitable", "_doc", "_is_readable", "_is_writable", "_server_type") def __init__(self, doc: _DocumentType, awaitable: bool = False) -> None: self._server_type = _get_server_type(doc) diff --git a/pymongo/helpers_shared.py b/pymongo/helpers_shared.py index 1ba69876e9..ccd1b942e3 100644 --- a/pymongo/helpers_shared.py +++ b/pymongo/helpers_shared.py @@ -13,20 +13,18 @@ # limitations under the License. """Bits and pieces used by the driver that don't really fit elsewhere.""" + from __future__ import annotations import sys import traceback from collections import abc +from collections.abc import Container, Iterable, Mapping, Sequence from typing import ( TYPE_CHECKING, Any, - Container, - Iterable, - Mapping, NoReturn, Optional, - Sequence, Union, ) diff --git a/pymongo/logger.py b/pymongo/logger.py index 1b3fe43b86..0441f05fc3 100644 --- a/pymongo/logger.py +++ b/pymongo/logger.py @@ -133,7 +133,7 @@ def __init__(self, **kwargs: Any): def __str__(self) -> str: self._redact() - return "%s" % ( + return "{}".format( json_util.dumps( self._kwargs, json_options=_JSON_OPTIONS, default=lambda o: o.__repr__() ) diff --git a/pymongo/max_staleness_selectors.py b/pymongo/max_staleness_selectors.py index 5f1e404720..22eb50a3fd 100644 --- a/pymongo/max_staleness_selectors.py +++ b/pymongo/max_staleness_selectors.py @@ -26,6 +26,7 @@ where "SMax" is the secondary with the greatest lastWriteDate. """ + from __future__ import annotations from typing import TYPE_CHECKING @@ -47,16 +48,15 @@ def _validate_max_staleness(max_staleness: int, heartbeat_frequency: int) -> Non # We checked for max staleness -1 before this, it must be positive here. if max_staleness < heartbeat_frequency + IDLE_WRITE_PERIOD: raise ConfigurationError( - "maxStalenessSeconds must be at least heartbeatFrequencyMS +" - " %d seconds. maxStalenessSeconds is set to %d," - " heartbeatFrequencyMS is set to %d." - % (IDLE_WRITE_PERIOD, max_staleness, heartbeat_frequency * 1000) + f"maxStalenessSeconds must be at least heartbeatFrequencyMS +" + f" {IDLE_WRITE_PERIOD} seconds. maxStalenessSeconds is set to {max_staleness}," + f" heartbeatFrequencyMS is set to {heartbeat_frequency * 1000}." ) if max_staleness < SMALLEST_MAX_STALENESS: raise ConfigurationError( - "maxStalenessSeconds must be at least %d. " - "maxStalenessSeconds is set to %d." % (SMALLEST_MAX_STALENESS, max_staleness) + f"maxStalenessSeconds must be at least {SMALLEST_MAX_STALENESS}. " + f"maxStalenessSeconds is set to {max_staleness}." ) diff --git a/pymongo/message.py b/pymongo/message.py index b0d1ceb105..081069375d 100644 --- a/pymongo/message.py +++ b/pymongo/message.py @@ -19,19 +19,18 @@ .. note:: This module is for internal use and is generally not needed by application developers. """ + from __future__ import annotations import datetime import random import struct +from collections.abc import Iterable, Mapping, MutableMapping from io import BytesIO as _BytesIO from typing import ( TYPE_CHECKING, Any, Callable, - Iterable, - Mapping, - MutableMapping, NoReturn, Optional, Union, @@ -575,18 +574,18 @@ class _BulkWriteContextBase: """Private base class for wrapping around AsyncConnection to use with write splitting functions.""" __slots__ = ( - "db_name", + "codec", + "compress", "conn", - "op_id", - "name", + "db_name", "field", - "publish", - "start_time", "listeners", - "session", - "compress", + "name", + "op_id", "op_type", - "codec", + "publish", + "session", + "start_time", ) def __init__( @@ -748,10 +747,10 @@ def _raise_document_too_large(operation: str, doc_size: int, max_size: int) -> N """Internal helper for raising DocumentTooLarge.""" if operation == "insert": raise DocumentTooLarge( - "BSON document too large (%d bytes)" - " - the connected server supports" - " BSON document sizes up to %d" - " bytes." % (doc_size, max_size) + f"BSON document too large ({doc_size} bytes)" + f" - the connected server supports" + f" BSON document sizes up to {max_size}" + f" bytes." ) else: # There's nothing intelligent we can say @@ -799,8 +798,7 @@ def _batched_op_msg_impl( raise InvalidOperation("Unknown command") from None to_send = [] - idx = 0 - for doc in docs: + for idx, doc in enumerate(docs): # Encode the current operation value = _dict_to_bson(doc, False, opts) doc_length = len(value) @@ -821,9 +819,8 @@ def _batched_op_msg_impl( break buf.write(value) to_send.append(doc) - idx += 1 # We have enough documents, return this batch. - if idx == max_write_batch_size: + if idx + 1 == max_write_batch_size: break # Write type 1 section size @@ -1088,9 +1085,8 @@ def _check_doc_size_limits( to_send_ns_encoded: list[bytes] = [] total_ops_length = 0 total_ns_length = 0 - idx = 0 - for (real_op_type, op_doc), namespace in zip(operations, namespaces): + for idx, ((real_op_type, op_doc), namespace) in enumerate(zip(operations, namespaces)): op_type = real_op_type # Check insert/replace document size if unacknowledged. if real_op_type == "insert": @@ -1142,10 +1138,8 @@ def _check_doc_size_limits( to_send_ns_encoded.append(ns_doc_encoded) total_ns_length += ns_length - idx += 1 - # We have enough documents, return this batch. - if idx == max_write_batch_size: + if idx + 1 == max_write_batch_size: break # Construct the entire OP_MSG. @@ -1306,8 +1300,7 @@ def _batched_write_command_impl( # Where to write list document length list_start = buf.tell() - 4 to_send = [] - idx = 0 - for doc in docs: + for idx, doc in enumerate(docs): # Encode the current operation key = str(idx).encode("utf8") value = _dict_to_bson(doc, False, opts) @@ -1326,7 +1319,6 @@ def _batched_write_command_impl( buf.write(_ZERO_8) buf.write(value) to_send.append(doc) - idx += 1 # Finalize the current OP_QUERY message. # Close list and command documents @@ -1345,7 +1337,7 @@ def _batched_write_command_impl( class _OpReply: """A MongoDB OP_REPLY response message.""" - __slots__ = ("flags", "cursor_id", "number_returned", "documents") + __slots__ = ("cursor_id", "documents", "flags", "number_returned") UNPACK_FROM = struct.Struct(" _OpReply: class _OpMsg: """A MongoDB OP_MSG response message.""" - __slots__ = ("flags", "cursor_id", "number_returned", "payload_document") + __slots__ = ("cursor_id", "flags", "number_returned", "payload_document") UNPACK_FROM = struct.Struct(" bool: use_find_cmd = True elif not self.read_concern.ok_for_legacy: raise ConfigurationError( - "read concern level of %s is not valid " - "with a max wire version of %d." % (self.read_concern.level, conn.max_wire_version) + f"read concern level of {self.read_concern.level} is not valid " + f"with a max wire version of {conn.max_wire_version}." ) conn.validate_session(self.client, self.session) # type: ignore[arg-type] @@ -1707,7 +1699,7 @@ def get_message( # OP_QUERY treats ntoreturn of -1 and 1 the same, return # one document and close the cursor. We have to use 2 for # batch size if 1 is specified. - ntoreturn = self.batch_size == 1 and 2 or self.batch_size + ntoreturn = (self.batch_size == 1 and 2) or self.batch_size if self.limit: if ntoreturn: ntoreturn = min(self.limit, ntoreturn) @@ -1734,19 +1726,19 @@ class _GetMore: """A getmore operation.""" __slots__ = ( - "db", + "_as_command", + "client", + "codec_options", "coll", - "ntoreturn", + "comment", + "conn_mgr", "cursor_id", + "db", + "exhaust", "max_await_time_ms", - "codec_options", + "ntoreturn", "read_preference", "session", - "client", - "conn_mgr", - "_as_command", - "exhaust", - "comment", ) name = "getMore" diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index 778abe27ef..21cc65ac26 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous MongoClient API for compatibility.""" + from __future__ import annotations from pymongo.synchronous.mongo_client import * # noqa: F403 diff --git a/pymongo/monitoring.py b/pymongo/monitoring.py index 46a78aea0b..a6fcbaffa7 100644 --- a/pymongo/monitoring.py +++ b/pymongo/monitoring.py @@ -190,7 +190,8 @@ def connection_checked_in(self, event): import datetime from collections import abc, namedtuple -from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence +from collections.abc import Mapping, Sequence +from typing import TYPE_CHECKING, Any, Optional from bson.objectid import ObjectId from pymongo.hello import Hello, HelloCompat @@ -515,12 +516,10 @@ def register(listener: _EventListener) -> None: # The "hello" command is also deemed sensitive when attempting speculative # authentication. def _is_speculative_authenticate(command_name: str, doc: Mapping[str, Any]) -> bool: - if ( + return bool( command_name.lower() in ("hello", HelloCompat.LEGACY_CMD) and "speculativeAuthenticate" in doc - ): - return True - return False + ) class _CommandEvent: @@ -528,12 +527,12 @@ class _CommandEvent: __slots__ = ( "__cmd_name", - "__rqst_id", "__conn_id", - "__op_id", - "__service_id", "__db", + "__op_id", + "__rqst_id", "__server_conn_id", + "__service_id", ) def __init__( @@ -653,17 +652,7 @@ def database_name(self) -> str: return super().database_name def __repr__(self) -> str: - return ( - "<{} {} db: {!r}, command: {!r}, operation_id: {}, service_id: {}, server_connection_id: {}>" - ).format( - self.__class__.__name__, - self.connection_id, - self.database_name, - self.command_name, - self.operation_id, - self.service_id, - self.server_connection_id, - ) + return f"<{self.__class__.__name__} {self.connection_id} db: {self.database_name!r}, command: {self.command_name!r}, operation_id: {self.operation_id}, service_id: {self.service_id}, server_connection_id: {self.server_connection_id}>" class CommandSucceededEvent(_CommandEvent): @@ -721,18 +710,7 @@ def reply(self) -> _DocumentOut: return self.__reply def __repr__(self) -> str: - return ( - "<{} {} db: {!r}, command: {!r}, operation_id: {}, duration_micros: {}, service_id: {}, server_connection_id: {}>" - ).format( - self.__class__.__name__, - self.connection_id, - self.database_name, - self.command_name, - self.operation_id, - self.duration_micros, - self.service_id, - self.server_connection_id, - ) + return f"<{self.__class__.__name__} {self.connection_id} db: {self.database_name!r}, command: {self.command_name!r}, operation_id: {self.operation_id}, duration_micros: {self.duration_micros}, service_id: {self.service_id}, server_connection_id: {self.server_connection_id}>" class CommandFailedEvent(_CommandEvent): @@ -787,18 +765,8 @@ def failure(self) -> _DocumentOut: def __repr__(self) -> str: return ( - "<{} {} db: {!r}, command: {!r}, operation_id: {}, duration_micros: {}, " - "failure: {!r}, service_id: {}, server_connection_id: {}>" - ).format( - self.__class__.__name__, - self.connection_id, - self.database_name, - self.command_name, - self.operation_id, - self.duration_micros, - self.failure, - self.service_id, - self.server_connection_id, + f"<{self.__class__.__name__} {self.connection_id} db: {self.database_name!r}, command: {self.command_name!r}, operation_id: {self.operation_id}, duration_micros: {self.duration_micros}, " + f"failure: {self.failure!r}, service_id: {self.service_id}, server_connection_id: {self.server_connection_id}>" ) @@ -868,7 +836,7 @@ class PoolClearedEvent(_PoolEvent): .. versionadded:: 3.9 """ - __slots__ = ("__service_id", "__interrupt_connections") + __slots__ = ("__interrupt_connections", "__service_id") def __init__( self, @@ -1068,12 +1036,7 @@ def reason(self) -> str: return self.__reason def __repr__(self) -> str: - return "{}({!r}, {!r}, {!r})".format( - self.__class__.__name__, - self.address, - self.connection_id, - self.__reason, - ) + return f"{self.__class__.__name__}({self.address!r}, {self.connection_id!r}, {self.__reason!r})" class ConnectionCheckOutStartedEvent(_ConnectionEvent): @@ -1172,7 +1135,7 @@ class ServerDescriptionChangedEvent(_ServerEvent): .. versionadded:: 3.3 """ - __slots__ = ("__previous_description", "__new_description") + __slots__ = ("__new_description", "__previous_description") def __init__( self, @@ -1199,12 +1162,7 @@ def new_description(self) -> ServerDescription: return self.__new_description def __repr__(self) -> str: - return "<{} {} changed from: {}, to: {}>".format( - self.__class__.__name__, - self.server_address, - self.previous_description, - self.new_description, - ) + return f"<{self.__class__.__name__} {self.server_address} changed from: {self.previous_description}, to: {self.new_description}>" class ServerOpeningEvent(_ServerEvent): @@ -1248,7 +1206,7 @@ class TopologyDescriptionChangedEvent(TopologyEvent): .. versionadded:: 3.3 """ - __slots__ = ("__previous_description", "__new_description") + __slots__ = ("__new_description", "__previous_description") def __init__( self, @@ -1275,12 +1233,7 @@ def new_description(self) -> TopologyDescription: return self.__new_description def __repr__(self) -> str: - return "<{} topology_id: {} changed from: {}, to: {}>".format( - self.__class__.__name__, - self.topology_id, - self.previous_description, - self.new_description, - ) + return f"<{self.__class__.__name__} topology_id: {self.topology_id} changed from: {self.previous_description}, to: {self.new_description}>" class TopologyOpenedEvent(TopologyEvent): @@ -1304,7 +1257,7 @@ class TopologyClosedEvent(TopologyEvent): class _ServerHeartbeatEvent: """Base class for server heartbeat events.""" - __slots__ = ("__connection_id", "__awaited") + __slots__ = ("__awaited", "__connection_id") def __init__(self, connection_id: _Address, awaited: bool = False) -> None: self.__connection_id = connection_id @@ -1380,13 +1333,7 @@ def awaited(self) -> bool: return super().awaited def __repr__(self) -> str: - return "<{} {} duration: {}, awaited: {}, reply: {}>".format( - self.__class__.__name__, - self.connection_id, - self.duration, - self.awaited, - self.reply, - ) + return f"<{self.__class__.__name__} {self.connection_id} duration: {self.duration}, awaited: {self.awaited}, reply: {self.reply}>" class ServerHeartbeatFailedEvent(_ServerHeartbeatEvent): @@ -1428,13 +1375,7 @@ def awaited(self) -> bool: return super().awaited def __repr__(self) -> str: - return "<{} {} duration: {}, awaited: {}, reply: {!r}>".format( - self.__class__.__name__, - self.connection_id, - self.duration, - self.awaited, - self.reply, - ) + return f"<{self.__class__.__name__} {self.connection_id} duration: {self.duration}, awaited: {self.awaited}, reply: {self.reply!r}>" class _EventListeners: diff --git a/pymongo/network_layer.py b/pymongo/network_layer.py index 7c62a251f8..12f1e79fb2 100644 --- a/pymongo/network_layer.py +++ b/pymongo/network_layer.py @@ -13,6 +13,7 @@ # limitations under the License. """Internal network layer helper methods.""" + from __future__ import annotations import asyncio @@ -362,9 +363,11 @@ def receive_data(conn: Connection, length: int, deadline: Optional[float]) -> me if ( _PYPY or _WINDOWS - or not conn.is_sdam - and deadline is not None - and deadline - time.monotonic() < 0 + or ( + not conn.is_sdam + and deadline is not None + and deadline - time.monotonic() < 0 + ) ): # We reached the true deadline. raise diff --git a/pymongo/ocsp_support.py b/pymongo/ocsp_support.py index 7f30fd0ebc..ecdd8d96ae 100644 --- a/pymongo/ocsp_support.py +++ b/pymongo/ocsp_support.py @@ -13,13 +13,15 @@ # permissions and limitations under the License. """Support for requesting and verifying OCSP responses.""" + from __future__ import annotations import logging as _logging import re as _re +from collections.abc import Iterable from datetime import datetime as _datetime from datetime import timezone -from typing import TYPE_CHECKING, Iterable, Optional, Type, Union +from typing import TYPE_CHECKING, Optional, Union from cryptography.exceptions import InvalidSignature as _InvalidSignature from cryptography.hazmat.backends import default_backend as _default_backend @@ -147,7 +149,7 @@ def _verify_signature( def _get_extension( - cert: Certificate, klass: Type[ExtensionTypeVar] + cert: Certificate, klass: type[ExtensionTypeVar] ) -> Optional[Extension[ExtensionTypeVar]]: try: return cert.extensions.get_extension_for_class(klass) @@ -198,7 +200,7 @@ def _verify_response_signature(issuer: Certificate, response: OCSPResponse) -> i name = response.responder_name rkey_hash = response.responder_key_hash ikey_hash = response.issuer_key_hash - if name is not None and name == issuer.subject or rkey_hash == ikey_hash: + if (name is not None and name == issuer.subject) or rkey_hash == ikey_hash: _LOGGER.debug("Responder is issuer") # Responder is the issuer responder_cert = issuer @@ -412,6 +414,4 @@ def _ocsp_callback(conn: Connection, ocsp_bytes: bytes, user_data: Optional[_Cal # Cache the verified, stapled response. ocsp_response_cache[_build_ocsp_request(cert, issuer)] = response _LOGGER.debug("OCSP cert status: %r", response.certificate_status) - if response.certificate_status == _OCSPCertStatus.REVOKED: - return False - return True + return response.certificate_status != _OCSPCertStatus.REVOKED diff --git a/pymongo/operations.py b/pymongo/operations.py index f546335b6e..b664a289ba 100644 --- a/pymongo/operations.py +++ b/pymongo/operations.py @@ -16,17 +16,16 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations import enum +from collections.abc import Mapping, Sequence from typing import ( TYPE_CHECKING, Any, Generic, - Mapping, Optional, - Sequence, - Tuple, Union, ) @@ -45,7 +44,7 @@ # Hint supports index name, "myIndex", a list of either strings or index pairs: [('x', 1), ('y', -1), 'z''], or a dictionary _IndexList = Union[ - Sequence[Union[str, Tuple[str, Union[int, str, Mapping[str, Any]]]]], Mapping[str, Any] + Sequence[Union[str, tuple[str, Union[int, str, Mapping[str, Any]]]]], Mapping[str, Any] ] _IndexKeyHint = Union[str, _IndexList] @@ -143,10 +142,12 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}({self._doc!r})" def __eq__(self, other: Any) -> bool: - if type(other) == type(self): + if type(other) is type(self): return other._doc == self._doc and other._namespace == self._namespace return NotImplemented + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: return not self == other @@ -155,8 +156,8 @@ class _DeleteOp: """Private base class for delete operations.""" __slots__ = ( - "_filter", "_collation", + "_filter", "_hint", "_namespace", ) @@ -180,7 +181,7 @@ def __init__( self._namespace = namespace def __eq__(self, other: Any) -> bool: - if type(other) == type(self): + if type(other) is type(self): return ( other._filter, other._collation, @@ -194,18 +195,14 @@ def __eq__(self, other: Any) -> bool: ) return NotImplemented + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: return not self == other def __repr__(self) -> str: if self._namespace: - return "{}({!r}, {!r}, {!r}, {!r})".format( - self.__class__.__name__, - self._filter, - self._collation, - self._hint, - self._namespace, - ) + return f"{self.__class__.__name__}({self._filter!r}, {self._collation!r}, {self._hint!r}, {self._namespace!r})" return f"{self.__class__.__name__}({self._filter!r}, {self._collation!r}, {self._hint!r})" @@ -335,13 +332,13 @@ class ReplaceOne(Generic[_DocumentType]): """Represents a replace_one operation.""" __slots__ = ( - "_filter", - "_doc", - "_upsert", "_collation", + "_doc", + "_filter", "_hint", "_namespace", "_sort", + "_upsert", ) def __init__( @@ -428,7 +425,7 @@ def _add_to_client_bulk(self, bulkobj: _AgnosticClientBulk) -> None: ) def __eq__(self, other: Any) -> bool: - if type(other) == type(self): + if type(other) is type(self): return ( other._filter, other._doc, @@ -448,44 +445,29 @@ def __eq__(self, other: Any) -> bool: ) return NotImplemented + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: return not self == other def __repr__(self) -> str: if self._namespace: - return "{}({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})".format( - self.__class__.__name__, - self._filter, - self._doc, - self._upsert, - self._collation, - self._hint, - self._namespace, - self._sort, - ) - return "{}({!r}, {!r}, {!r}, {!r}, {!r}, {!r})".format( - self.__class__.__name__, - self._filter, - self._doc, - self._upsert, - self._collation, - self._hint, - self._sort, - ) + return f"{self.__class__.__name__}({self._filter!r}, {self._doc!r}, {self._upsert!r}, {self._collation!r}, {self._hint!r}, {self._namespace!r}, {self._sort!r})" + return f"{self.__class__.__name__}({self._filter!r}, {self._doc!r}, {self._upsert!r}, {self._collation!r}, {self._hint!r}, {self._sort!r})" class _UpdateOp: """Private base class for update operations.""" __slots__ = ( - "_filter", - "_doc", - "_upsert", - "_collation", "_array_filters", + "_collation", + "_doc", + "_filter", "_hint", "_namespace", "_sort", + "_upsert", ) def __init__( @@ -540,32 +522,15 @@ def __eq__(self, other: object) -> bool: ) return NotImplemented + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: return not self == other def __repr__(self) -> str: if self._namespace: - return "{}({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})".format( - self.__class__.__name__, - self._filter, - self._doc, - self._upsert, - self._collation, - self._array_filters, - self._hint, - self._namespace, - self._sort, - ) - return "{}({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})".format( - self.__class__.__name__, - self._filter, - self._doc, - self._upsert, - self._collation, - self._array_filters, - self._hint, - self._sort, - ) + return f"{self.__class__.__name__}({self._filter!r}, {self._doc!r}, {self._upsert!r}, {self._collation!r}, {self._array_filters!r}, {self._hint!r}, {self._namespace!r}, {self._sort!r})" + return f"{self.__class__.__name__}({self._filter!r}, {self._doc!r}, {self._upsert!r}, {self._collation!r}, {self._array_filters!r}, {self._hint!r}, {self._sort!r})" class UpdateOne(_UpdateOp): diff --git a/pymongo/pool.py b/pymongo/pool.py index 456ff3df0a..3d3e581fdd 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -13,6 +13,7 @@ # limitations under the License. """Re-import of synchronous Pool API for compatibility.""" + from __future__ import annotations from pymongo.synchronous.pool import * # noqa: F403 diff --git a/pymongo/pool_options.py b/pymongo/pool_options.py index d4c00ac46c..8b26b4baf2 100644 --- a/pymongo/pool_options.py +++ b/pymongo/pool_options.py @@ -16,14 +16,16 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations import copy import os import platform import sys +from collections.abc import MutableMapping from pathlib import Path -from typing import TYPE_CHECKING, Any, MutableMapping, Optional +from typing import TYPE_CHECKING, Any, Optional import bson from pymongo import __version__ @@ -94,7 +96,7 @@ ( platform.python_implementation(), ".".join(map(str, sys.pypy_version_info)), # type: ignore - "(Python %s)" % ".".join(map(str, sys.version_info)), + "(Python {})".format(".".join(map(str, sys.version_info))), ) ) else: @@ -275,24 +277,24 @@ class PoolOptions: """ __slots__ = ( - "__max_pool_size", - "__min_pool_size", - "__max_idle_time_seconds", - "__connect_timeout", - "__socket_timeout", - "__wait_queue_timeout", - "__ssl_context", - "__tls_allow_invalid_hostnames", - "__event_listeners", "__appname", - "__driver", - "__metadata", "__compression_settings", + "__connect_timeout", + "__credentials", + "__driver", + "__event_listeners", + "__load_balanced", "__max_connecting", + "__max_idle_time_seconds", + "__max_pool_size", + "__metadata", + "__min_pool_size", "__pause_enabled", "__server_api", - "__load_balanced", - "__credentials", + "__socket_timeout", + "__ssl_context", + "__tls_allow_invalid_hostnames", + "__wait_queue_timeout", ) def __init__( diff --git a/pymongo/pool_shared.py b/pymongo/pool_shared.py index a6f434885b..e5be991eb8 100644 --- a/pymongo/pool_shared.py +++ b/pymongo/pool_shared.py @@ -13,6 +13,7 @@ # limitations under the License. """Pool utilities and shared helper methods.""" + from __future__ import annotations import asyncio @@ -58,7 +59,7 @@ def _set_non_inheritable_non_atomic(fd: int) -> None: # Windows, various platforms we don't claim to support # (Jython, IronPython, ..), systems that don't provide # everything we need from fcntl, etc. - def _set_non_inheritable_non_atomic(fd: int) -> None: # noqa: ARG001 + def _set_non_inheritable_non_atomic(fd: int) -> None: """Dummy function for platforms that don't provide fcntl.""" @@ -131,17 +132,15 @@ def _raise_connection_failure( host, port = address # If connecting to a Unix socket, port will be None. if port is not None: - msg = "%s:%d: %s" % (host, port, error) + msg = f"{host}:{port}: {error}" else: msg = f"{host}: {error}" if msg_prefix: msg = msg_prefix + msg if "configured timeouts" not in msg: msg += format_timeout_details(timeout_details) - if ( - isinstance(error, socket.timeout) - or isinstance(error, SSLErrors) - and "timed out" in str(error) + if isinstance(error, socket.timeout) or ( + isinstance(error, SSLErrors) and "timed out" in str(error) ): raise NetworkTimeout(msg) from error else: diff --git a/pymongo/pyopenssl_context.py b/pymongo/pyopenssl_context.py index 0c027b1e61..93990c250b 100644 --- a/pymongo/pyopenssl_context.py +++ b/pymongo/pyopenssl_context.py @@ -17,6 +17,7 @@ Due to limitations of the CPython asyncio.Protocol implementation for SSL, the async API does not support PyOpenSSL. """ + from __future__ import annotations import socket as _socket @@ -201,7 +202,7 @@ class SSLContext: context. """ - __slots__ = ("_protocol", "_ctx", "_callback_data", "_check_hostname", "_options") + __slots__ = ("_callback_data", "_check_hostname", "_ctx", "_options", "_protocol") def __init__(self, protocol: int): self._protocol = protocol diff --git a/pymongo/read_concern.py b/pymongo/read_concern.py index 2adc403366..9a11c8f562 100644 --- a/pymongo/read_concern.py +++ b/pymongo/read_concern.py @@ -16,6 +16,7 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations from typing import Any, Optional @@ -70,9 +71,11 @@ def __eq__(self, other: Any) -> bool: return self.document == other.document return NotImplemented + __hash__ = None # type: ignore[assignment] + def __repr__(self) -> str: if self.level: - return "ReadConcern(%s)" % self.level + return f"ReadConcern({self.level})" return "ReadConcern()" diff --git a/pymongo/read_preferences.py b/pymongo/read_preferences.py index 35b92c4d01..6cdea1d3f6 100644 --- a/pymongo/read_preferences.py +++ b/pymongo/read_preferences.py @@ -21,7 +21,8 @@ import warnings from collections import abc -from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence +from collections.abc import Mapping, Sequence +from typing import TYPE_CHECKING, Any, Optional from pymongo import max_staleness_selectors from pymongo.errors import ConfigurationError @@ -78,7 +79,7 @@ def _validate_tag_sets(tag_sets: Optional[_TagSets]) -> Optional[_TagSets]: def _invalid_max_staleness_msg(max_staleness: Any) -> str: - return "maxStalenessSeconds must be a positive integer, not %s" % max_staleness + return f"maxStalenessSeconds must be a positive integer, not {max_staleness}" # Some duplication with common.py to avoid import cycle. @@ -115,7 +116,7 @@ def _validate_hedge(hedge: Optional[_Hedge]) -> Optional[_Hedge]: class _ServerMode: """Base class for all read preferences.""" - __slots__ = ("__mongos_mode", "__mode", "__tag_sets", "__max_staleness", "__hedge") + __slots__ = ("__hedge", "__max_staleness", "__mode", "__mongos_mode", "__tag_sets") def __init__( self, @@ -233,12 +234,7 @@ def min_wire_version(self) -> int: return 0 if self.__max_staleness == -1 else 5 def __repr__(self) -> str: - return "{}(tag_sets={!r}, max_staleness={!r}, hedge={!r})".format( - self.name, - self.__tag_sets, - self.__max_staleness, - self.__hedge, - ) + return f"{self.name}(tag_sets={self.__tag_sets!r}, max_staleness={self.__max_staleness!r}, hedge={self.__hedge!r})" def __eq__(self, other: Any) -> bool: if isinstance(other, _ServerMode): @@ -250,6 +246,8 @@ def __eq__(self, other: Any) -> bool: ) return NotImplemented + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: return not self == other @@ -304,6 +302,8 @@ def __eq__(self, other: Any) -> bool: return other.mode == _PRIMARY return NotImplemented + __hash__ = None # type: ignore[assignment] + class PrimaryPreferred(_ServerMode): """PrimaryPreferred read preference. @@ -488,7 +488,7 @@ class _AggWritePref: :param pref: The read preference to use on MongoDB 5.0+. """ - __slots__ = ("pref", "effective_pref") + __slots__ = ("effective_pref", "pref") def __init__(self, pref: _ServerMode): self.pref = pref diff --git a/pymongo/response.py b/pymongo/response.py index 211ddf2354..69f6a145eb 100644 --- a/pymongo/response.py +++ b/pymongo/response.py @@ -13,9 +13,11 @@ # limitations under the License. """Represent a response from the server.""" + from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence, Union +from collections.abc import Mapping, Sequence +from typing import TYPE_CHECKING, Any, Optional, Union if TYPE_CHECKING: from datetime import timedelta @@ -25,7 +27,7 @@ class Response: - __slots__ = ("_data", "_address", "_request_id", "_duration", "_from_command", "_docs") + __slots__ = ("_address", "_data", "_docs", "_duration", "_from_command", "_request_id") def __init__( self, diff --git a/pymongo/results.py b/pymongo/results.py index bcce121fe7..594a28bc88 100644 --- a/pymongo/results.py +++ b/pymongo/results.py @@ -16,9 +16,11 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations -from typing import Any, Mapping, MutableMapping, Optional, cast +from collections.abc import Mapping, MutableMapping +from typing import Any, Optional, cast from pymongo.errors import InvalidOperation @@ -120,8 +122,8 @@ class UpdateResult(_WriteResult): """ __slots__ = ( - "__raw_result", "__in_client_bulk", + "__raw_result", ) def __init__( @@ -315,12 +317,7 @@ def __init__( ) def __repr__(self) -> str: - return "{}({!r}, acknowledged={}, verbose={})".format( - self.__class__.__name__, - self.bulk_api_result, - self.acknowledged, - self.has_verbose_results, - ) + return f"{self.__class__.__name__}({self.bulk_api_result!r}, acknowledged={self.acknowledged}, verbose={self.has_verbose_results})" def _raise_if_not_verbose(self, property_name: str) -> None: """Raise an exception on property access if verbose results are off.""" diff --git a/pymongo/saslprep.py b/pymongo/saslprep.py index 9cef22419e..25d97721c1 100644 --- a/pymongo/saslprep.py +++ b/pymongo/saslprep.py @@ -13,6 +13,7 @@ # limitations under the License. """An implementation of RFC4013 SASLprep.""" + from __future__ import annotations from typing import Any, Optional diff --git a/pymongo/server_api.py b/pymongo/server_api.py index 40bb1aac3e..b492c4e278 100644 --- a/pymongo/server_api.py +++ b/pymongo/server_api.py @@ -83,9 +83,11 @@ Classes ======= """ + from __future__ import annotations -from typing import Any, MutableMapping, Optional +from collections.abc import MutableMapping +from typing import Any, Optional class ServerApiVersion: diff --git a/pymongo/server_description.py b/pymongo/server_description.py index d038c04b1c..c5bb7f9769 100644 --- a/pymongo/server_description.py +++ b/pymongo/server_description.py @@ -16,11 +16,13 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations import time import warnings -from typing import Any, Mapping, Optional +from collections.abc import Mapping +from typing import Any, Optional from bson import EPOCH_NAIVE from bson.objectid import ObjectId @@ -41,28 +43,28 @@ class ServerDescription: __slots__ = ( "_address", - "_server_type", "_all_hosts", - "_tags", - "_replica_set_name", - "_primary", + "_cluster_time", + "_election_id", + "_error", + "_is_readable", + "_is_writable", + "_last_update_time", + "_last_write_date", + "_ls_timeout_minutes", "_max_bson_size", "_max_message_size", + "_max_wire_version", "_max_write_batch_size", + "_me", + "_min_round_trip_time", "_min_wire_version", - "_max_wire_version", + "_primary", + "_replica_set_name", "_round_trip_time", - "_min_round_trip_time", - "_me", - "_is_writable", - "_is_readable", - "_ls_timeout_minutes", - "_error", + "_server_type", "_set_version", - "_election_id", - "_cluster_time", - "_last_write_date", - "_last_update_time", + "_tags", "_topology_version", ) @@ -283,6 +285,8 @@ def __eq__(self, other: Any) -> bool: return NotImplemented + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: return not self == other @@ -290,13 +294,7 @@ def __repr__(self) -> str: errmsg = "" if self.error: errmsg = f", error={self.error!r}" - return "<{} {} server_type: {}, rtt: {}{}>".format( - self.__class__.__name__, - self.address, - self.server_type_name, - self.round_trip_time, - errmsg, - ) + return f"<{self.__class__.__name__} {self.address} server_type: {self.server_type_name}, rtt: {self.round_trip_time}{errmsg}>" # For unittesting only. Use under no circumstances! _host_to_round_trip_time: dict = {} # type: ignore[type-arg] diff --git a/pymongo/server_selectors.py b/pymongo/server_selectors.py index ee116b84ed..b43272f7f3 100644 --- a/pymongo/server_selectors.py +++ b/pymongo/server_selectors.py @@ -13,9 +13,11 @@ # permissions and limitations under the License. """Criteria to select some ServerDescriptions from a TopologyDescription.""" + from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, Optional, Sequence, TypeVar, cast +from collections.abc import Mapping, Sequence +from typing import TYPE_CHECKING, Any, Optional, TypeVar, cast from pymongo.server_type import SERVER_TYPE diff --git a/pymongo/server_type.py b/pymongo/server_type.py index 7a6d2aaf14..0f5f047dfb 100644 --- a/pymongo/server_type.py +++ b/pymongo/server_type.py @@ -13,6 +13,7 @@ # limitations under the License. """Type codes for MongoDB servers.""" + from __future__ import annotations from typing import NamedTuple diff --git a/pymongo/socket_checker.py b/pymongo/socket_checker.py index a2f65ebc38..8424bf5062 100644 --- a/pymongo/socket_checker.py +++ b/pymongo/socket_checker.py @@ -13,6 +13,7 @@ # limitations under the License. """Select / poll helper""" + from __future__ import annotations import errno diff --git a/pymongo/ssl_context.py b/pymongo/ssl_context.py index 2ff7428cab..b99023935d 100644 --- a/pymongo/ssl_context.py +++ b/pymongo/ssl_context.py @@ -13,6 +13,7 @@ # permissions and limitations under the License. """A fake SSLContext implementation.""" + from __future__ import annotations import ssl as _ssl diff --git a/pymongo/ssl_support.py b/pymongo/ssl_support.py index 7dbd0f2148..2e87ebe907 100644 --- a/pymongo/ssl_support.py +++ b/pymongo/ssl_support.py @@ -13,6 +13,7 @@ # permissions and limitations under the License. """Support for SSL in PyMongo.""" + from __future__ import annotations import types diff --git a/pymongo/synchronous/aggregation.py b/pymongo/synchronous/aggregation.py index 486768ab7d..d540484fa8 100644 --- a/pymongo/synchronous/aggregation.py +++ b/pymongo/synchronous/aggregation.py @@ -13,6 +13,7 @@ # permissions and limitations under the License. """Perform aggregation operations on a collection or database.""" + from __future__ import annotations from collections.abc import Callable, Mapping, MutableMapping diff --git a/pymongo/synchronous/auth.py b/pymongo/synchronous/auth.py index 650e25234d..5bb5cb454f 100644 --- a/pymongo/synchronous/auth.py +++ b/pymongo/synchronous/auth.py @@ -13,6 +13,7 @@ # limitations under the License. """Authentication helpers.""" + from __future__ import annotations import functools @@ -20,12 +21,11 @@ import hmac import socket from base64 import standard_b64decode, standard_b64encode +from collections.abc import Mapping, MutableMapping from typing import ( TYPE_CHECKING, Any, Callable, - Mapping, - MutableMapping, Optional, cast, ) @@ -181,7 +181,7 @@ def _canonicalize_hostname(hostname: str, option: str | bool) -> str: if option in [False, "none"]: return hostname - af, socktype, proto, canonname, sockaddr = ( + _af, _socktype, _proto, canonname, sockaddr = ( _getaddrinfo( hostname, None, diff --git a/pymongo/synchronous/auth_aws.py b/pymongo/synchronous/auth_aws.py index c7ea47886f..b20f3c2110 100644 --- a/pymongo/synchronous/auth_aws.py +++ b/pymongo/synchronous/auth_aws.py @@ -13,9 +13,11 @@ # limitations under the License. """MONGODB-AWS Authentication helpers.""" + from __future__ import annotations -from typing import TYPE_CHECKING, Any, Mapping, Type +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any import bson from bson.binary import Binary @@ -51,7 +53,7 @@ def _authenticate_aws(credentials: MongoCredential, conn: Connection) -> None: class AwsSaslContext(pymongo_auth_aws.AwsSaslContext): # type: ignore # Dependency injection: - def binary_type(self) -> Type[Binary]: + def binary_type(self) -> type[Binary]: """Return the bson.binary.Binary type.""" return Binary diff --git a/pymongo/synchronous/auth_oidc.py b/pymongo/synchronous/auth_oidc.py index 583ee39f67..6b723b94d8 100644 --- a/pymongo/synchronous/auth_oidc.py +++ b/pymongo/synchronous/auth_oidc.py @@ -13,13 +13,15 @@ # limitations under the License. """MONGODB-OIDC Authentication helpers.""" + from __future__ import annotations import asyncio import threading import time +from collections.abc import Mapping, MutableMapping from dataclasses import dataclass, field -from typing import TYPE_CHECKING, Any, Mapping, MutableMapping, Optional, Union +from typing import TYPE_CHECKING, Any, Optional, Union import bson from bson.binary import Binary diff --git a/pymongo/synchronous/bulk.py b/pymongo/synchronous/bulk.py index 22d6a7a76a..9aaae4449c 100644 --- a/pymongo/synchronous/bulk.py +++ b/pymongo/synchronous/bulk.py @@ -16,20 +16,18 @@ .. versionadded:: 2.7 """ + from __future__ import annotations import copy import datetime import logging -from collections.abc import MutableMapping +from collections.abc import Iterator, Mapping, MutableMapping from itertools import islice from typing import ( TYPE_CHECKING, Any, - Iterator, - Mapping, Optional, - Type, Union, ) @@ -119,7 +117,7 @@ def __init__( self.is_encrypted = False @property - def bulk_ctx_class(self) -> Type[_BulkWriteContext]: + def bulk_ctx_class(self) -> type[_BulkWriteContext]: encrypter = self.collection.database.client._encrypter if encrypter and not encrypter._bypass_auto_encryption: self.is_encrypted = True diff --git a/pymongo/synchronous/change_stream.py b/pymongo/synchronous/change_stream.py index 7e34d7b848..f442cc6c67 100644 --- a/pymongo/synchronous/change_stream.py +++ b/pymongo/synchronous/change_stream.py @@ -13,10 +13,12 @@ # permissions and limitations under the License. """Watch changes on a collection, a database, or the entire cluster.""" + from __future__ import annotations import copy -from typing import TYPE_CHECKING, Any, Generic, Mapping, Optional, Type, Union +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Generic, Optional, Union from bson import CodecOptions, _bson_to_dict from bson.raw_bson import RawBSONDocument @@ -159,7 +161,7 @@ def _initialize_cursor(self) -> None: self._cursor = self._create_cursor() @property - def _aggregation_command_class(self) -> Type[_AggregationCommand]: + def _aggregation_command_class(self) -> type[_AggregationCommand]: """The aggregation command class to be used.""" raise NotImplementedError @@ -231,8 +233,7 @@ def _process_result(self, result: Mapping[str, Any], conn: Connection) -> None: # PYTHON-2181: informative error on missing operationTime. if self._start_at_operation_time is None: raise OperationFailure( - "Expected field 'operationTime' missing from command " - f"response : {result!r}" + f"Expected field 'operationTime' missing from command response : {result!r}" ) def _run_aggregation_cmd(self, session: Optional[ClientSession]) -> CommandCursor: # type: ignore[type-arg] @@ -451,7 +452,7 @@ class CollectionChangeStream(ChangeStream[_DocumentType]): _target: Collection[_DocumentType] @property - def _aggregation_command_class(self) -> Type[_CollectionAggregationCommand]: + def _aggregation_command_class(self) -> type[_CollectionAggregationCommand]: return _CollectionAggregationCommand @property @@ -471,7 +472,7 @@ class DatabaseChangeStream(ChangeStream[_DocumentType]): _target: Database[_DocumentType] @property - def _aggregation_command_class(self) -> Type[_DatabaseAggregationCommand]: + def _aggregation_command_class(self) -> type[_DatabaseAggregationCommand]: return _DatabaseAggregationCommand @property diff --git a/pymongo/synchronous/client_bulk.py b/pymongo/synchronous/client_bulk.py index 1134594ae9..ba530f2e2a 100644 --- a/pymongo/synchronous/client_bulk.py +++ b/pymongo/synchronous/client_bulk.py @@ -16,19 +16,18 @@ .. versionadded:: 4.9 """ + from __future__ import annotations import copy import datetime import logging -from collections.abc import MutableMapping +from collections.abc import Mapping, MutableMapping from itertools import islice from typing import ( TYPE_CHECKING, Any, - Mapping, Optional, - Type, Union, ) @@ -119,7 +118,7 @@ def __init__( self.started_retryable_write = False @property - def bulk_ctx_class(self) -> Type[_ClientBulkWriteContext]: + def bulk_ctx_class(self) -> type[_ClientBulkWriteContext]: return _ClientBulkWriteContext def add_insert(self, namespace: str, document: _DocumentOut) -> None: diff --git a/pymongo/synchronous/client_session.py b/pymongo/synchronous/client_session.py index 240372f0cf..6833b9c6e7 100644 --- a/pymongo/synchronous/client_session.py +++ b/pymongo/synchronous/client_session.py @@ -139,18 +139,16 @@ import random import time import uuid +from collections.abc import Mapping, MutableMapping from collections.abc import Mapping as _Mapping +from contextlib import AbstractContextManager from contextvars import ContextVar, Token from typing import ( TYPE_CHECKING, Any, Callable, - ContextManager, - Mapping, - MutableMapping, NoReturn, Optional, - Type, TypeVar, ) @@ -241,9 +239,7 @@ def __init__( if not isinstance(default_transaction_options, TransactionOptions): raise TypeError( "default_transaction_options must be an instance of " - "pymongo.client_session.TransactionOptions, not: {!r}".format( - default_transaction_options - ) + f"pymongo.client_session.TransactionOptions, not: {default_transaction_options!r}" ) self._default_transaction_options = default_transaction_options self._snapshot = snapshot @@ -323,8 +319,7 @@ def __init__( ) if not write_concern.acknowledged: raise ConfigurationError( - "transactions do not support unacknowledged write concern" - f": {write_concern!r}" + f"transactions do not support unacknowledged write concern: {write_concern!r}" ) if read_preference is not None: if not isinstance(read_preference, _ServerMode): @@ -398,7 +393,7 @@ def __enter__(self) -> _TransactionContext: def __exit__( self, - exc_type: Optional[Type[BaseException]], + exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType], ) -> None: @@ -479,7 +474,7 @@ def __del__(self) -> None: def _reraise_with_unknown_commit(exc: Any) -> NoReturn: """Re-raise an exception with the UnknownTransactionCommitResult label.""" exc._add_error_label("UnknownTransactionCommitResult") - raise + raise exc def _max_time_expired_error(exc: PyMongoError) -> bool: @@ -831,7 +826,7 @@ def start_transaction( write_concern: Optional[WriteConcern] = None, read_preference: Optional[_ServerMode] = None, max_commit_time_ms: Optional[int] = None, - ) -> ContextManager[Any]: + ) -> AbstractContextManager[Any]: """Start a multi-statement transaction. Takes the same arguments as :class:`TransactionOptions`. diff --git a/pymongo/synchronous/collection.py b/pymongo/synchronous/collection.py index 1057151e59..15a38b2f3e 100644 --- a/pymongo/synchronous/collection.py +++ b/pymongo/synchronous/collection.py @@ -13,23 +13,19 @@ # limitations under the License. """Collection level utilities for Mongo.""" + from __future__ import annotations import warnings from collections import abc +from collections.abc import Iterable, Iterator, Mapping, MutableMapping, Sequence from typing import ( TYPE_CHECKING, Any, Callable, Generic, - Iterable, - Iterator, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, - Type, TypeVar, Union, cast, @@ -241,9 +237,9 @@ def __init__( if not name or ".." in name: raise InvalidName("collection names cannot be empty") if "$" in name and not (name.startswith(("oplog.$main", "$cmd"))): - raise InvalidName("collection names must not contain '$': %r" % name) + raise InvalidName(f"collection names must not contain '$': {name!r}") if name[0] == "." or name[-1] == ".": - raise InvalidName("collection names must not start or end with '.': %r" % name) + raise InvalidName(f"collection names must not start or end with '.': {name!r}") if "\x00" in name: raise InvalidName("collection names must not contain the null character") @@ -340,8 +336,7 @@ def with_options( read_preference: Optional[_ServerMode] = ..., write_concern: Optional[WriteConcern] = ..., read_concern: Optional[ReadConcern] = ..., - ) -> Collection[_DocumentType]: - ... + ) -> Collection[_DocumentType]: ... @overload def with_options( @@ -350,8 +345,7 @@ def with_options( read_preference: Optional[_ServerMode] = ..., write_concern: Optional[WriteConcern] = ..., read_concern: Optional[ReadConcern] = ..., - ) -> Collection[_DocumentTypeArg]: - ... + ) -> Collection[_DocumentTypeArg]: ... def with_options( self, @@ -2913,9 +2907,9 @@ def options( @_csot.apply def _aggregate( self, - aggregation_command: Type[_AggregationCommand], + aggregation_command: type[_AggregationCommand], pipeline: _Pipeline, - cursor_class: Type[CommandCursor], # type: ignore[type-arg] + cursor_class: type[CommandCursor], # type: ignore[type-arg] session: Optional[ClientSession], let: Optional[Mapping[str, Any]] = None, comment: Optional[Any] = None, diff --git a/pymongo/synchronous/command_cursor.py b/pymongo/synchronous/command_cursor.py index 34f60c6540..1ecf1ed2e1 100644 --- a/pymongo/synchronous/command_cursor.py +++ b/pymongo/synchronous/command_cursor.py @@ -13,17 +13,16 @@ # limitations under the License. """CommandCursor class to iterate over command results.""" + from __future__ import annotations from collections import deque +from collections.abc import Iterator, Mapping, Sequence from typing import ( TYPE_CHECKING, Any, - Iterator, - Mapping, NoReturn, Optional, - Sequence, Union, ) @@ -78,7 +77,7 @@ def __init__( if self._killed: self._end_session() - if "ns" in cursor_info: # noqa: SIM401 + if "ns" in cursor_info: self._ns = cursor_info["ns"] else: self._ns = collection.full_name @@ -113,7 +112,7 @@ def batch_size(self, batch_size: int) -> CommandCursor[_DocumentType]: if batch_size < 0: raise ValueError("batch_size must be >= 0") - self._batch_size = batch_size == 1 and 2 or batch_size + self._batch_size = (batch_size == 1 and 2) or batch_size return self def _has_next(self) -> bool: diff --git a/pymongo/synchronous/cursor.py b/pymongo/synchronous/cursor.py index 5a721d8e06..85aa0d7768 100644 --- a/pymongo/synchronous/cursor.py +++ b/pymongo/synchronous/cursor.py @@ -13,20 +13,18 @@ # limitations under the License. """Cursor class to iterate over Mongo query results.""" + from __future__ import annotations import copy import warnings from collections import deque +from collections.abc import Iterable, Mapping, Sequence from typing import ( TYPE_CHECKING, Any, - Iterable, - List, - Mapping, NoReturn, Optional, - Sequence, Union, cast, overload, @@ -169,7 +167,7 @@ def __init__( self._skip = skip self._limit = limit self._batch_size = batch_size - self._ordering = sort and helpers_shared._index_document(sort) or None + self._ordering = (sort and helpers_shared._index_document(sort)) or None self._max_scan = max_scan self._explain = False self._comment = comment @@ -533,12 +531,10 @@ def max_await_time_ms(self, max_await_time_ms: Optional[int]) -> Cursor[_Documen return self @overload - def __getitem__(self, index: int) -> _DocumentType: - ... + def __getitem__(self, index: int) -> _DocumentType: ... @overload - def __getitem__(self, index: slice) -> Cursor[_DocumentType]: - ... + def __getitem__(self, index: slice) -> Cursor[_DocumentType]: ... def __getitem__(self, index: Union[int, slice]) -> Union[_DocumentType, Cursor[_DocumentType]]: """Get a single document or a slice of documents from this cursor. @@ -597,7 +593,7 @@ def __getitem__(self, index: Union[int, slice]) -> Union[_DocumentType, Cursor[_ limit = index.stop - skip if limit < 0: raise IndexError( - "stop index must be greater than start index for slice %r" % index + f"stop index must be greater than start index for slice {index!r}" ) if limit == 0: self._empty = True @@ -618,7 +614,7 @@ def __getitem__(self, index: Union[int, slice]) -> Union[_DocumentType, Cursor[_ for doc in clone: # type: ignore[attr-defined] return doc raise IndexError("no such item for Cursor instance") - raise TypeError("index %r cannot be applied to Cursor instances" % index) + raise TypeError(f"index {index!r} cannot be applied to Cursor instances") else: raise IndexError("Cursor does not support indexing") @@ -1202,7 +1198,7 @@ def _unpack_response( # OP_MSG returns firstBatch/nextBatch documents as a BSON array # Re-assemble the array of documents into a document stream _convert_raw_document_lists_to_streams(raw_response[0]) - return cast(List["_DocumentOut"], raw_response) + return cast(list["_DocumentOut"], raw_response) def explain(self) -> _DocumentType: """Returns an explain plan record for this cursor. diff --git a/pymongo/synchronous/cursor_base.py b/pymongo/synchronous/cursor_base.py index 52ada613c7..96e69cb6ee 100644 --- a/pymongo/synchronous/cursor_base.py +++ b/pymongo/synchronous/cursor_base.py @@ -13,6 +13,7 @@ # permissions and limitations under the License. """Synchronous cursor base extending the shared agnostic cursor base.""" + from __future__ import annotations from abc import abstractmethod diff --git a/pymongo/synchronous/database.py b/pymongo/synchronous/database.py index 1956795bcb..65c5d2a7f0 100644 --- a/pymongo/synchronous/database.py +++ b/pymongo/synchronous/database.py @@ -13,18 +13,17 @@ # limitations under the License. """Database level operations.""" + from __future__ import annotations +from collections.abc import Mapping, MutableMapping, Sequence from copy import deepcopy from typing import ( TYPE_CHECKING, Any, Generic, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, Union, cast, overload, @@ -153,8 +152,7 @@ def with_options( read_preference: Optional[_ServerMode] = ..., write_concern: Optional[WriteConcern] = ..., read_concern: Optional[ReadConcern] = ..., - ) -> Database[_DocumentType]: - ... + ) -> Database[_DocumentType]: ... @overload def with_options( @@ -163,8 +161,7 @@ def with_options( read_preference: Optional[_ServerMode] = ..., write_concern: Optional[WriteConcern] = ..., read_concern: Optional[ReadConcern] = ..., - ) -> Database[_DocumentTypeArg]: - ... + ) -> Database[_DocumentTypeArg]: ... def with_options( self, @@ -620,7 +617,7 @@ def create_collection( and (not s or not s.in_transaction) and name in self._list_collection_names(filter={"name": name}, session=s) ): - raise CollectionInvalid("collection %s already exists" % name) + raise CollectionInvalid(f"collection {name} already exists") if s: s._leave_alive = False coll = Collection( @@ -733,8 +730,7 @@ def _command( parse_write_concern_error: bool = False, session: Optional[ClientSession] = None, **kwargs: Any, - ) -> dict[str, Any]: - ... + ) -> dict[str, Any]: ... @overload def _command( @@ -750,8 +746,7 @@ def _command( parse_write_concern_error: bool = False, session: Optional[ClientSession] = None, **kwargs: Any, - ) -> _CodecDocumentType: - ... + ) -> _CodecDocumentType: ... def _command( self, @@ -800,8 +795,7 @@ def command( session: Optional[ClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> dict[str, Any]: - ... + ) -> dict[str, Any]: ... @overload def command( @@ -815,8 +809,7 @@ def command( session: Optional[ClientSession] = None, comment: Optional[Any] = None, **kwargs: Any, - ) -> _CodecDocumentType: - ... + ) -> _CodecDocumentType: ... @_csot.apply def command( @@ -1468,7 +1461,7 @@ def dereference( Added ``session`` parameter. """ if not isinstance(dbref, DBRef): - raise TypeError("cannot dereference a %s" % type(dbref)) + raise TypeError(f"cannot dereference a {type(dbref)}") if dbref.database is not None and dbref.database != self._name: raise ValueError( "trying to dereference a DBRef that points to " diff --git a/pymongo/synchronous/encryption.py b/pymongo/synchronous/encryption.py index 051e6e7a5d..8b9a7e3c27 100644 --- a/pymongo/synchronous/encryption.py +++ b/pymongo/synchronous/encryption.py @@ -13,6 +13,7 @@ # limitations under the License. """Support for explicit client-side field level encryption.""" + from __future__ import annotations import contextlib @@ -21,18 +22,13 @@ import time as time # noqa: PLC0414 # needed in sync version import uuid import weakref +from collections.abc import Generator, Iterator, Mapping, MutableMapping, Sequence from copy import deepcopy from typing import ( TYPE_CHECKING, Any, - Dict, - Generator, Generic, - Iterator, - Mapping, - MutableMapping, Optional, - Sequence, Union, cast, ) @@ -108,7 +104,7 @@ _MONGOCRYPTD_TIMEOUT_MS = 10000 _DATA_KEY_OPTS: CodecOptions[dict[str, Any]] = CodecOptions( - document_class=Dict[str, Any], uuid_representation=STANDARD + document_class=dict[str, Any], uuid_representation=STANDARD ) # Use RawBSONDocument codec options to avoid needlessly decoding # documents from the key vault. diff --git a/pymongo/synchronous/helpers.py b/pymongo/synchronous/helpers.py index bbe8963fe7..f24d72aea9 100644 --- a/pymongo/synchronous/helpers.py +++ b/pymongo/synchronous/helpers.py @@ -13,6 +13,7 @@ # limitations under the License. """Miscellaneous pieces that need to be synchronized.""" + from __future__ import annotations import asyncio diff --git a/pymongo/synchronous/mongo_client.py b/pymongo/synchronous/mongo_client.py index 2bd6f31b72..61275eff57 100644 --- a/pymongo/synchronous/mongo_client.py +++ b/pymongo/synchronous/mongo_client.py @@ -30,6 +30,7 @@ >>> c["test-database"] Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'test-database') """ + from __future__ import annotations import asyncio @@ -39,21 +40,15 @@ import warnings import weakref from collections import defaultdict +from collections.abc import Collection, Generator, Mapping, MutableMapping, Sequence +from contextlib import AbstractContextManager from typing import ( TYPE_CHECKING, Any, Callable, - Collection, - ContextManager, - FrozenSet, - Generator, Generic, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, - Type, TypeVar, Union, cast, @@ -183,7 +178,7 @@ def __init__( self, host: Optional[Union[str, Sequence[str]]] = None, port: Optional[int] = None, - document_class: Optional[Type[_DocumentType]] = None, + document_class: Optional[type[_DocumentType]] = None, tz_aware: Optional[bool] = None, connect: Optional[bool] = None, type_registry: Optional[TypeRegistry] = None, @@ -1255,7 +1250,7 @@ def topology_description(self) -> TopologyDescription: return self._topology.description @property - def nodes(self) -> FrozenSet[_Address]: + def nodes(self) -> frozenset[_Address]: """Set of all currently connected servers. .. warning:: When connected to a replica set the value of :attr:`nodes` @@ -1317,11 +1312,12 @@ def option_repr(option: str, value: Any) -> str: options = [f"host='mongodb+srv://{self._resolve_srv_info['fqdn']}'"] else: options = [ - "host=%r" - % [ - "%s:%d" % (host, port) if port is not None else host - for host, port in self._topology_settings.seeds - ] + "host={!r}".format( + [ + f"{host}:{port}" if port is not None else host + for host, port in self._topology_settings.seeds + ] + ) ] # ... then everything in self._constructor_args... options.extend( @@ -1864,7 +1860,7 @@ def _select_server( def _conn_for_writes( self, session: Optional[ClientSession], operation: str - ) -> ContextManager[Connection]: + ) -> AbstractContextManager[Connection]: server = self._select_server(writable_server_selector, session, operation) return self._checkout(server, session) @@ -1898,7 +1894,7 @@ def _conn_for_reads( read_preference: _ServerMode, session: Optional[ClientSession], operation: str, - ) -> ContextManager[tuple[Connection, _ServerMode]]: + ) -> AbstractContextManager[tuple[Connection, _ServerMode]]: assert read_preference is not None, "read_preference must not be None" server = self._select_server(read_preference, session, operation) return self._conn_from_server(read_preference, server, session) @@ -2667,13 +2663,13 @@ class _MongoClientErrorHandler: __slots__ = ( "client", + "completed_handshake", + "handled", + "max_wire_version", "server_address", + "service_id", "session", - "max_wire_version", "sock_generation", - "completed_handshake", - "service_id", - "handled", ) def __init__( @@ -2708,7 +2704,7 @@ def contribute_socket(self, conn: Connection, completed_handshake: bool = True) self.completed_handshake = completed_handshake def handle( - self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException] + self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException] ) -> None: if self.handled or exc_val is None: return @@ -2741,7 +2737,7 @@ def __enter__(self) -> _MongoClientErrorHandler: def __exit__( self, - exc_type: Optional[Type[Exception]], + exc_type: Optional[type[Exception]], exc_val: Optional[Exception], exc_tb: Optional[TracebackType], ) -> None: diff --git a/pymongo/synchronous/network.py b/pymongo/synchronous/network.py index 7d9bca4d58..8008863395 100644 --- a/pymongo/synchronous/network.py +++ b/pymongo/synchronous/network.py @@ -13,17 +13,16 @@ # limitations under the License. """Internal network layer helper methods.""" + from __future__ import annotations import datetime import logging +from collections.abc import Mapping, MutableMapping, Sequence from typing import ( TYPE_CHECKING, Any, - Mapping, - MutableMapping, Optional, - Sequence, Union, cast, ) diff --git a/pymongo/synchronous/pool.py b/pymongo/synchronous/pool.py index 25f2d08fe7..f45540cd3d 100644 --- a/pymongo/synchronous/pool.py +++ b/pymongo/synchronous/pool.py @@ -24,15 +24,12 @@ import sys import time import weakref +from collections.abc import Generator, Mapping, MutableMapping, Sequence from typing import ( TYPE_CHECKING, Any, - Generator, - Mapping, - MutableMapping, NoReturn, Optional, - Sequence, Union, ) @@ -437,8 +434,8 @@ def send_message(self, message: bytes, max_doc_size: int) -> None: """ if self.max_bson_size is not None and max_doc_size > self.max_bson_size: raise DocumentTooLarge( - "BSON document too large (%d bytes) - the connected server " - "supports BSON document sizes up to %d bytes." % (max_doc_size, self.max_bson_size) + f"BSON document too large ({max_doc_size} bytes) - the connected server " + f"supports BSON document sizes up to {self.max_bson_size} bytes." ) try: @@ -632,7 +629,7 @@ def _raise_connection_failure(self, error: BaseException) -> NoReturn: details = _get_timeout_details(self.opts) _raise_connection_failure(self.address, error, timeout_details=details) else: - raise + raise error def __eq__(self, other: Any) -> bool: return self.conn == other.conn @@ -646,7 +643,7 @@ def __hash__(self) -> int: def __repr__(self) -> str: return "Connection({}){} at {}".format( repr(self.conn), - self.closed and " CLOSED" or "", + (self.closed and " CLOSED") or "", id(self), ) @@ -1460,15 +1457,9 @@ def _raise_wait_queue_timeout(self, checkout_started_time: float) -> NoReturn: other_ops = self.active_sockets - self.ncursors - self.ntxns raise WaitQueueTimeoutError( "Timeout waiting for connection from the connection pool. " - "maxPoolSize: {}, connections in use by cursors: {}, " - "connections in use by transactions: {}, connections in use " - "by other operations: {}, timeout: {}".format( - self.opts.max_pool_size, - self.ncursors, - self.ntxns, - other_ops, - timeout, - ) + f"maxPoolSize: {self.opts.max_pool_size}, connections in use by cursors: {self.ncursors}, " + f"connections in use by transactions: {self.ntxns}, connections in use " + f"by other operations: {other_ops}, timeout: {timeout}" ) raise WaitQueueTimeoutError( "Timed out while checking out a connection from connection pool. " diff --git a/pymongo/synchronous/server.py b/pymongo/synchronous/server.py index f57420918b..e2620df97f 100644 --- a/pymongo/synchronous/server.py +++ b/pymongo/synchronous/server.py @@ -13,15 +13,16 @@ # permissions and limitations under the License. """Communicate with one MongoDB server in a topology.""" + from __future__ import annotations import logging +from contextlib import AbstractContextManager from datetime import datetime from typing import ( TYPE_CHECKING, Any, Callable, - ContextManager, Optional, Union, ) @@ -349,7 +350,7 @@ def run_operation( def checkout( self, handler: Optional[_MongoClientErrorHandler] = None - ) -> ContextManager[Connection]: + ) -> AbstractContextManager[Connection]: return self.pool.checkout(handler) @property diff --git a/pymongo/synchronous/settings.py b/pymongo/synchronous/settings.py index 61b86fa18d..bc664e8421 100644 --- a/pymongo/synchronous/settings.py +++ b/pymongo/synchronous/settings.py @@ -13,11 +13,13 @@ # permissions and limitations under the License. """Represent MongoClient's configuration.""" + from __future__ import annotations import threading import traceback -from typing import Any, Collection, Optional, Type, Union +from collections.abc import Collection +from typing import Any, Optional, Union from bson.objectid import ObjectId from pymongo import common @@ -37,10 +39,10 @@ def __init__( self, seeds: Optional[Collection[tuple[str, int]]] = None, replica_set_name: Optional[str] = None, - pool_class: Optional[Type[Pool]] = None, + pool_class: Optional[type[Pool]] = None, pool_options: Optional[PoolOptions] = None, - monitor_class: Optional[Type[monitor.Monitor]] = None, - condition_class: Optional[Type[threading.Condition]] = None, + monitor_class: Optional[type[monitor.Monitor]] = None, + condition_class: Optional[type[threading.Condition]] = None, local_threshold_ms: int = LOCAL_THRESHOLD_MS, server_selection_timeout: int = SERVER_SELECTION_TIMEOUT, heartbeat_frequency: int = common.HEARTBEAT_FREQUENCY, @@ -59,16 +61,15 @@ def __init__( """ if heartbeat_frequency < common.MIN_HEARTBEAT_INTERVAL: raise ConfigurationError( - "heartbeatFrequencyMS cannot be less than %d" - % (common.MIN_HEARTBEAT_INTERVAL * 1000,) + f"heartbeatFrequencyMS cannot be less than {common.MIN_HEARTBEAT_INTERVAL * 1000}" ) self._seeds: Collection[tuple[str, int]] = seeds or [("localhost", 27017)] self._replica_set_name = replica_set_name - self._pool_class: Type[Pool] = pool_class or pool.Pool + self._pool_class: type[Pool] = pool_class or pool.Pool self._pool_options: PoolOptions = pool_options or PoolOptions() - self._monitor_class: Type[monitor.Monitor] = monitor_class or monitor.Monitor - self._condition_class: Type[threading.Condition] = condition_class or threading.Condition + self._monitor_class: type[monitor.Monitor] = monitor_class or monitor.Monitor + self._condition_class: type[threading.Condition] = condition_class or threading.Condition self._local_threshold_ms = local_threshold_ms self._server_selection_timeout = server_selection_timeout self._server_selector = server_selector @@ -97,7 +98,7 @@ def replica_set_name(self) -> Optional[str]: return self._replica_set_name @property - def pool_class(self) -> Type[Pool]: + def pool_class(self) -> type[Pool]: return self._pool_class @property @@ -105,11 +106,11 @@ def pool_options(self) -> PoolOptions: return self._pool_options @property - def monitor_class(self) -> Type[monitor.Monitor]: + def monitor_class(self) -> type[monitor.Monitor]: return self._monitor_class @property - def condition_class(self) -> Type[threading.Condition]: + def condition_class(self) -> type[threading.Condition]: return self._condition_class @property diff --git a/pymongo/synchronous/srv_resolver.py b/pymongo/synchronous/srv_resolver.py index 4802310698..de1a0fc9e6 100644 --- a/pymongo/synchronous/srv_resolver.py +++ b/pymongo/synchronous/srv_resolver.py @@ -13,6 +13,7 @@ # permissions and limitations under the License. """Support for resolving hosts and options from mongodb+srv:// URIs.""" + from __future__ import annotations import ipaddress diff --git a/pymongo/synchronous/topology.py b/pymongo/synchronous/topology.py index ec1615f0c6..b419833256 100644 --- a/pymongo/synchronous/topology.py +++ b/pymongo/synchronous/topology.py @@ -25,8 +25,9 @@ import time import warnings import weakref +from collections.abc import Mapping from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Mapping, Optional, cast +from typing import TYPE_CHECKING, Any, Callable, Optional, cast from pymongo import _csot, common, helpers_shared, periodic_executor from pymongo.errors import ( @@ -236,8 +237,7 @@ def open(self) -> None: kwargs = {"skip_file_prefixes": (_pymongo_dir,)} else: kwargs = {"stacklevel": 6} - # Ignore B028 warning for missing stacklevel. - warnings.warn( # type: ignore[call-overload] # noqa: B028 + warnings.warn( # type: ignore[call-overload] "MongoClient opened before fork. May not be entirely fork-safe, " "proceed with caution. See PyMongo's documentation for details: " "https://dochub.mongodb.org/core/pymongo-fork-deadlock", @@ -1034,7 +1034,7 @@ def _error_message(self, selector: Callable[[Selection], Selection]) -> str: if is_replica_set: return "No primary available for writes" else: - return "No %s available for writes" % server_plural + return f"No {server_plural} available for writes" else: return f'No {server_plural} match selector "{selector}"' else: @@ -1043,12 +1043,9 @@ def _error_message(self, selector: Callable[[Selection], Selection]) -> str: if not servers: if is_replica_set: # We removed all servers because of the wrong setName? - return 'No {} available for replica set name "{}"'.format( - server_plural, - self._settings.replica_set_name, - ) + return f'No {server_plural} available for replica set name "{self._settings.replica_set_name}"' else: - return "No %s available" % server_plural + return f"No {server_plural} available" # 1 or more servers, all Unknown. Are they unknown for one reason? error = servers[0].error @@ -1056,13 +1053,13 @@ def _error_message(self, selector: Callable[[Selection], Selection]) -> str: if same: if error is None: # We're still discovering. - return "No %s found yet" % server_plural + return f"No {server_plural} found yet" if is_replica_set and not set(addresses).intersection(self._seed_addresses): # We replaced our seeds with new hosts but can't reach any. return ( - "Could not reach any servers in %s. Replica set is" - " configured with internal hostnames or IPs?" % addresses + f"Could not reach any servers in {addresses}. Replica set is" + " configured with internal hostnames or IPs?" ) return str(error) diff --git a/pymongo/synchronous/uri_parser.py b/pymongo/synchronous/uri_parser.py index 45c1752953..8d86c91e90 100644 --- a/pymongo/synchronous/uri_parser.py +++ b/pymongo/synchronous/uri_parser.py @@ -14,6 +14,7 @@ """Tools to parse and validate a MongoDB URI.""" + from __future__ import annotations from typing import Any, Optional @@ -159,7 +160,7 @@ def _parse_srv( srv_max_hosts = srv_max_hosts or options.get("srvMaxHosts") if is_srv: nodes = split_hosts(hosts, default_port=None) - fqdn, port = nodes[0] + fqdn, _port = nodes[0] # Use the connection timeout. connectTimeoutMS passed as a keyword # argument overrides the same option passed in the connection string. diff --git a/pymongo/topology_description.py b/pymongo/topology_description.py index 074bbc7fca..c31244842a 100644 --- a/pymongo/topology_description.py +++ b/pymongo/topology_description.py @@ -16,15 +16,14 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations +from collections.abc import Mapping, MutableMapping from random import sample from typing import ( Any, Callable, - List, - Mapping, - MutableMapping, NamedTuple, Optional, cast, @@ -57,7 +56,7 @@ class _TopologyType(NamedTuple): SRV_POLLING_TOPOLOGIES: tuple[int, int] = (TOPOLOGY_TYPE.Unknown, TOPOLOGY_TYPE.Sharded) -_ServerSelector = Callable[[List[ServerDescription]], List[ServerDescription]] +_ServerSelector = Callable[[list[ServerDescription]], list[ServerDescription]] class TopologyDescription: @@ -133,28 +132,18 @@ def _init_incompatible_err(self) -> None: ) if server_too_new: - self._incompatible_err = ( - "Server at %s:%d requires wire version %d, but this " # type: ignore - "version of PyMongo only supports up to %d." - % ( - s.address[0], - s.address[1] or 0, - s.min_wire_version, - common.MAX_SUPPORTED_WIRE_VERSION, - ) + self._incompatible_err = ( # type: ignore + f"Server at {s.address[0]}:{s.address[1] or 0} requires wire version " + f"{s.min_wire_version}, but this version of PyMongo only supports up to " + f"{common.MAX_SUPPORTED_WIRE_VERSION}." ) elif server_too_old: - self._incompatible_err = ( - "Server at %s:%d reports wire version %d, but this " # type: ignore - "version of PyMongo requires at least %d (MongoDB %s)." - % ( - s.address[0], - s.address[1] or 0, - s.max_wire_version, - common.MIN_SUPPORTED_WIRE_VERSION, - common.MIN_SUPPORTED_SERVER_VERSION, - ) + self._incompatible_err = ( # type: ignore + f"Server at {s.address[0]}:{s.address[1] or 0} reports wire version " + f"{s.max_wire_version}, but this version of PyMongo requires at least " + f"{common.MIN_SUPPORTED_WIRE_VERSION} (MongoDB " + f"{common.MIN_SUPPORTED_SERVER_VERSION})." ) break @@ -329,8 +318,8 @@ def apply_selector( common_wv = self.common_wire_version if common_wv and common_wv < selector.min_wire_version: raise ConfigurationError( - "%s requires min wire version %d, but topology's min" - " wire version is %d" % (selector, selector.min_wire_version, common_wv) + f"{selector} requires min wire version {selector.min_wire_version}, " + f"but topology's min wire version is {common_wv}" ) if isinstance(selector, _AggWritePref): @@ -412,12 +401,7 @@ def has_writable_server(self) -> bool: def __repr__(self) -> str: # Sort the servers by address. servers = sorted(self._server_descriptions.values(), key=lambda sd: sd.address) - return "<{} id: {}, topology_type: {}, servers: {!r}>".format( - self.__class__.__name__, - self._topology_settings._topology_id, - self.topology_type_name, - servers, - ) + return f"<{self.__class__.__name__} id: {self._topology_settings._topology_id}, topology_type: {self.topology_type_name}, servers: {servers!r}>" # If topology type is Unknown and we receive a hello response, what should @@ -465,9 +449,7 @@ def updated_topology_description( if set_name is not None and set_name != server_description.replica_set_name: error = ConfigurationError( "client is configured to connect to a replica set named " - "'{}' but this node belongs to a set named '{}'".format( - set_name, server_description.replica_set_name - ) + f"'{set_name}' but this node belongs to a set named '{server_description.replica_set_name}'" ) sds[address] = server_description.to_unknown(error=error) # Single type never changes. diff --git a/pymongo/typings.py b/pymongo/typings.py index e678720db9..6ed660e57a 100644 --- a/pymongo/typings.py +++ b/pymongo/typings.py @@ -13,15 +13,14 @@ # limitations under the License. """Type aliases used by PyMongo""" + from __future__ import annotations +from collections.abc import Mapping, Sequence from typing import ( TYPE_CHECKING, Any, - Mapping, Optional, - Sequence, - Tuple, TypeVar, Union, ) @@ -43,7 +42,7 @@ # Common Shared Types. -_Address = Tuple[str, Optional[int]] +_Address = tuple[str, Optional[int]] _CollationIn = Union[Mapping[str, Any], "Collation"] _Pipeline = Sequence[Mapping[str, Any]] ClusterTime = Mapping[str, Any] @@ -67,12 +66,12 @@ def strip_optional(elem: Optional[_T]) -> _T: __all__ = [ + "_Address", + "_AgnosticMongoClient", + "_CollationIn", "_DocumentOut", "_DocumentType", "_DocumentTypeArg", - "_Address", - "_CollationIn", "_Pipeline", "strip_optional", - "_AgnosticMongoClient", ] diff --git a/pymongo/uri_parser.py b/pymongo/uri_parser.py index fe253b9bbf..4241e651e9 100644 --- a/pymongo/uri_parser.py +++ b/pymongo/uri_parser.py @@ -14,6 +14,7 @@ """Re-import of synchronous URI Parser API for compatibility.""" + from __future__ import annotations import sys @@ -25,13 +26,13 @@ __doc__ = original_doc __all__ = [ # noqa: F405 - "parse_userinfo", - "parse_ipv6_literal_host", "parse_host", - "validate_options", - "split_options", - "split_hosts", + "parse_ipv6_literal_host", "parse_uri", + "parse_userinfo", + "split_hosts", + "split_options", + "validate_options", ] if __name__ == "__main__": diff --git a/pymongo/uri_parser_shared.py b/pymongo/uri_parser_shared.py index 59168d1e9f..62d862bcfa 100644 --- a/pymongo/uri_parser_shared.py +++ b/pymongo/uri_parser_shared.py @@ -17,18 +17,17 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations import re import sys import warnings +from collections.abc import Mapping, MutableMapping, Sized from typing import ( TYPE_CHECKING, Any, - Mapping, - MutableMapping, Optional, - Sized, Union, cast, ) @@ -525,7 +524,7 @@ def _validate_uri( 'The "dnspython" module must be ' "installed to use mongodb+srv:// URIs. " "To fix this error install pymongo again:\n " - "%s -m pip install pymongo>=4.3" % (python_path) + f"{python_path} -m pip install pymongo>=4.3" ) is_srv = True scheme_free = uri[SRV_SCHEME_LEN:] @@ -552,7 +551,7 @@ def _validate_uri( if "." in dbase: dbase, collection = dbase.split(".", 1) if _BAD_DB_CHARS.search(dbase): - raise InvalidURI('Bad database name "%s"' % dbase) + raise InvalidURI(f'Bad database name "{dbase}"') else: dbase = None @@ -565,7 +564,7 @@ def _validate_uri( hosts = host_part if "/" in hosts: - raise InvalidURI("Any '/' in a unix domain socket must be percent-encoded: %s" % host_part) + raise InvalidURI(f"Any '/' in a unix domain socket must be percent-encoded: {host_part}") hosts = unquote_plus(hosts) fqdn = None diff --git a/pymongo/write_concern.py b/pymongo/write_concern.py index 1f9da7af2e..6f3731849f 100644 --- a/pymongo/write_concern.py +++ b/pymongo/write_concern.py @@ -16,6 +16,7 @@ .. seealso:: This module is compatible with both the synchronous and asynchronous PyMongo APIs. """ + from __future__ import annotations from typing import Any, Optional, Union @@ -60,7 +61,7 @@ class WriteConcern: Deprecated parameter ``wtimeout``, use :meth:`~pymongo.timeout`. """ - __slots__ = ("__document", "__acknowledged", "__server_default") + __slots__ = ("__acknowledged", "__document", "__server_default") def __init__( self, @@ -135,6 +136,8 @@ def __eq__(self, other: Any) -> bool: return self.__document == other.document return NotImplemented + __hash__ = None # type: ignore[assignment] + def __ne__(self, other: Any) -> bool: if isinstance(other, WriteConcern): return self.__document != other.document diff --git a/pyproject.toml b/pyproject.toml index 4faa88d814..3bb6696d0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -164,7 +164,7 @@ warn_unused_ignores = false disable_error_code = ["unused-coroutine", "no-redef"] [tool.ruff] -target-version = "py37" +target-version = "py39" line-length = 100 [tool.ruff.lint] @@ -195,7 +195,6 @@ select = [ ignore = [ "PLR", # Design related pylint codes "E501", # Line too long - "PT004", # Use underscore for non-returning fixture (use usefixture instead) "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 @@ -215,6 +214,8 @@ ignore = [ "EM103", # Exception must not use a `.format()` string directly, assign to variable first "C408", # Unnecessary `dict` call (rewrite as a literal) "SIM117", # Use a single `with` statement with multiple contexts instead of nested `with` statements + "UP045", # Use `X | Y` for union annotation (Enable when we drop 3.9 support) + "PLC0415", # `import` should be at the top-level of a file ] unfixable = [ "RUF100", # Unused noqa diff --git a/test/__init__.py b/test/__init__.py index 8540c442e0..f4ae7fe948 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -13,6 +13,7 @@ # limitations under the License. """Synchronous test suite for pymongo, bson, and gridfs.""" + from __future__ import annotations import asyncio @@ -42,9 +43,10 @@ HAVE_IPADDRESS = True except ImportError: HAVE_IPADDRESS = False +from collections.abc import Generator from contextlib import contextmanager from functools import partial, wraps -from typing import Any, Callable, Dict, Generator, overload +from typing import Any, Callable, overload from unittest import SkipTest from urllib.parse import quote_plus @@ -117,7 +119,7 @@ def __init__(self): self.tls = False self.tlsCertificateKeyFile = False self.server_is_resolvable = is_server_resolvable() - self.default_client_options: Dict = {} + self.default_client_options: dict = {} self.sessions_enabled = False self.client = None # type: ignore self.conn_lock = threading.Lock() @@ -701,7 +703,7 @@ def require_failCommand_appName(self, func): """Run a test only if the server supports the failCommand appName.""" # SERVER-47195 and SERVER-49336. return self._require( - lambda: (self.test_commands_enabled and self.version >= (4, 4, 7)), + lambda: self.test_commands_enabled and self.version >= (4, 4, 7), "failCommand appName must be supported", func=func, ) @@ -1156,7 +1158,7 @@ class IntegrationTest(PyMongoTestCase): client: MongoClient[dict] db: Database - credentials: Dict[str, str] + credentials: dict[str, str] @client_context.require_connection def setUp(self) -> None: diff --git a/test/asynchronous/__init__.py b/test/asynchronous/__init__.py index 4dde0acf1f..0699451d7e 100644 --- a/test/asynchronous/__init__.py +++ b/test/asynchronous/__init__.py @@ -13,6 +13,7 @@ # limitations under the License. """Asynchronous test suite for pymongo, bson, and gridfs.""" + from __future__ import annotations import asyncio @@ -42,9 +43,10 @@ HAVE_IPADDRESS = True except ImportError: HAVE_IPADDRESS = False +from collections.abc import Generator from contextlib import asynccontextmanager, contextmanager from functools import partial, wraps -from typing import Any, Callable, Dict, Generator, overload +from typing import Any, Callable, overload from unittest import SkipTest from urllib.parse import quote_plus @@ -117,7 +119,7 @@ def __init__(self): self.tls = False self.tlsCertificateKeyFile = False self.server_is_resolvable = is_server_resolvable() - self.default_client_options: Dict = {} + self.default_client_options: dict = {} self.sessions_enabled = False self.client = None # type: ignore self.conn_lock = threading.Lock() @@ -701,7 +703,7 @@ def require_failCommand_appName(self, func): """Run a test only if the server supports the failCommand appName.""" # SERVER-47195 and SERVER-49336. return self._require( - lambda: (self.test_commands_enabled and self.version >= (4, 4, 7)), + lambda: self.test_commands_enabled and self.version >= (4, 4, 7), "failCommand appName must be supported", func=func, ) @@ -1172,7 +1174,7 @@ class AsyncIntegrationTest(AsyncPyMongoTestCase): client: AsyncMongoClient[dict] db: AsyncDatabase - credentials: Dict[str, str] + credentials: dict[str, str] @async_client_context.require_connection async def asyncSetUp(self) -> None: diff --git a/test/asynchronous/conftest.py b/test/asynchronous/conftest.py index afbd532a67..b3903b02ce 100644 --- a/test/asynchronous/conftest.py +++ b/test/asynchronous/conftest.py @@ -2,11 +2,12 @@ import asyncio import sys -from test import pytest_conf -from test.asynchronous import async_setup, async_teardown import pytest_asyncio +from test import pytest_conf +from test.asynchronous import async_setup, async_teardown + _IS_SYNC = False # The default asyncio loop implementation on Windows has issues with sharing diff --git a/test/asynchronous/helpers.py b/test/asynchronous/helpers.py index 892c629631..3153eeda52 100644 --- a/test/asynchronous/helpers.py +++ b/test/asynchronous/helpers.py @@ -13,6 +13,7 @@ # limitations under the License. """Shared helper methods for pymongo, bson, and gridfs test suites.""" + from __future__ import annotations import asyncio @@ -108,15 +109,9 @@ async def wrap(*args, **kwargs): def __del__(self): if self._enabled: msg = ( - "ERROR: client_knobs still enabled! HEARTBEAT_FREQUENCY={}, " - "MIN_HEARTBEAT_INTERVAL={}, KILL_CURSOR_FREQUENCY={}, " - "EVENTS_QUEUE_FREQUENCY={}, stack:\n{}".format( - common.HEARTBEAT_FREQUENCY, - common.MIN_HEARTBEAT_INTERVAL, - common.KILL_CURSOR_FREQUENCY, - common.EVENTS_QUEUE_FREQUENCY, - self._stack, - ) + f"ERROR: client_knobs still enabled! HEARTBEAT_FREQUENCY={common.HEARTBEAT_FREQUENCY}, " + f"MIN_HEARTBEAT_INTERVAL={common.MIN_HEARTBEAT_INTERVAL}, KILL_CURSOR_FREQUENCY={common.KILL_CURSOR_FREQUENCY}, " + f"EVENTS_QUEUE_FREQUENCY={common.EVENTS_QUEUE_FREQUENCY}, stack:\n{self._stack}" ) self.disable() raise Exception(msg) diff --git a/test/asynchronous/pymongo_mocks.py b/test/asynchronous/pymongo_mocks.py index 40beb3c0dc..4413cbe43b 100644 --- a/test/asynchronous/pymongo_mocks.py +++ b/test/asynchronous/pymongo_mocks.py @@ -13,13 +13,12 @@ # limitations under the License. """Tools for mocking parts of PyMongo to test other parts.""" + from __future__ import annotations import contextlib import weakref from functools import partial -from test import client_context -from test.asynchronous import async_client_context from pymongo import AsyncMongoClient, common from pymongo.asynchronous.monitor import Monitor @@ -27,6 +26,8 @@ from pymongo.errors import AutoReconnect, NetworkTimeout from pymongo.hello import Hello, HelloCompat from pymongo.server_description import ServerDescription +from test import client_context +from test.asynchronous import async_client_context _IS_SYNC = False diff --git a/test/asynchronous/qcheck.py b/test/asynchronous/qcheck.py index 190a7f1a91..ddf184f4c4 100644 --- a/test/asynchronous/qcheck.py +++ b/test/asynchronous/qcheck.py @@ -182,35 +182,35 @@ def simplify(case): # TODO this is a hack if random.choice([True, False]): # delete simplified_keys = list(simplified) - if not len(simplified_keys): + if not simplified_keys: return (False, case) simplified.pop(random.choice(simplified_keys)) return (True, simplified) else: # simplify a value simplified_items = list(simplified.items()) - if not len(simplified_items): + if not simplified_items: return (False, case) (key, value) = random.choice(simplified_items) (success, value) = simplify(value) simplified[key] = value - return (success, success and simplified or case) + return (success, (success and simplified) or case) if isinstance(case, list): simplified = list(case) if random.choice([True, False]): # delete - if not len(simplified): + if not simplified: return (False, case) simplified.pop(random.randrange(len(simplified))) return (True, simplified) else: # simplify an item - if not len(simplified): + if not simplified: return (False, case) index = random.randrange(len(simplified)) (success, value) = simplify(simplified[index]) simplified[index] = value - return (success, success and simplified or case) + return (success, (success and simplified) or case) return (False, case) diff --git a/test/asynchronous/test_async_cancellation.py b/test/asynchronous/test_async_cancellation.py index f450ea23cc..a96e28d832 100644 --- a/test/asynchronous/test_async_cancellation.py +++ b/test/asynchronous/test_async_cancellation.py @@ -13,10 +13,12 @@ # limitations under the License. """Test that async cancellation performed by users clean up resources correctly.""" + from __future__ import annotations import asyncio import sys + from test.asynchronous.utils import async_get_pool from test.utils_shared import delay, one diff --git a/test/asynchronous/test_async_contextvars_reset.py b/test/asynchronous/test_async_contextvars_reset.py index c6e825bbdf..c2a3cd1959 100644 --- a/test/asynchronous/test_async_contextvars_reset.py +++ b/test/asynchronous/test_async_contextvars_reset.py @@ -13,10 +13,12 @@ # limitations under the License. """Test that AsyncPeriodicExecutors do not copy ContextVars from their parents.""" + from __future__ import annotations import asyncio import sys + from test.asynchronous.utils import async_get_pool from test.utils_shared import delay, one diff --git a/test/asynchronous/test_async_loop_safety.py b/test/asynchronous/test_async_loop_safety.py index 7516cb8eeb..3c455b4b03 100644 --- a/test/asynchronous/test_async_loop_safety.py +++ b/test/asynchronous/test_async_loop_safety.py @@ -13,6 +13,7 @@ # limitations under the License. """Test that the asynchronous API detects event loop changes and fails correctly.""" + from __future__ import annotations import asyncio diff --git a/test/asynchronous/test_async_loop_unblocked.py b/test/asynchronous/test_async_loop_unblocked.py index 86f934b798..7f3f97ae44 100644 --- a/test/asynchronous/test_async_loop_unblocked.py +++ b/test/asynchronous/test_async_loop_unblocked.py @@ -13,13 +13,14 @@ # limitations under the License. """Test that the asynchronous API does not block the event loop.""" + from __future__ import annotations import asyncio import time -from test.asynchronous import AsyncIntegrationTest from pymongo.errors import ServerSelectionTimeoutError +from test.asynchronous import AsyncIntegrationTest class TestClientLoopUnblocked(AsyncIntegrationTest): diff --git a/test/asynchronous/test_auth.py b/test/asynchronous/test_auth.py index 904674db16..ab96808d26 100644 --- a/test/asynchronous/test_auth.py +++ b/test/asynchronous/test_auth.py @@ -13,6 +13,7 @@ # limitations under the License. """Authentication Tests.""" + from __future__ import annotations import asyncio @@ -23,15 +24,6 @@ sys.path[0:0] = [""] -from test.asynchronous import ( - AsyncIntegrationTest, - AsyncPyMongoTestCase, - SkipTest, - async_client_context, - unittest, -) -from test.utils_shared import AllowListEventListener, delay, ignore_deprecations - import pytest from pymongo import AsyncMongoClient, monitoring @@ -41,6 +33,14 @@ from pymongo.hello import HelloCompat from pymongo.read_preferences import ReadPreference from pymongo.saslprep import HAVE_STRINGPREP +from test.asynchronous import ( + AsyncIntegrationTest, + AsyncPyMongoTestCase, + SkipTest, + async_client_context, + unittest, +) +from test.utils_shared import AllowListEventListener, delay, ignore_deprecations _IS_SYNC = False @@ -598,13 +598,13 @@ async def test_scram_saslprep(self): await client.testscram.command("dbstats") client = await self.async_rs_or_single_client_noauth( - username="IX", password="I\u00ADX", authSource="testscram" + username="IX", password="I\u00adX", authSource="testscram" ) await client.testscram.command("dbstats") client = await self.async_rs_or_single_client_noauth( username="IX", - password="I\u00ADX", + password="I\u00adX", authSource="testscram", authMechanism="SCRAM-SHA-256", ) @@ -625,7 +625,7 @@ async def test_scram_saslprep(self): await client.testscram.command("dbstats") client = await self.async_rs_or_single_client_noauth( - "mongodb://IX:I\u00ADX@%s:%d/testscram" % (host, port) + "mongodb://IX:I\u00adX@%s:%d/testscram" % (host, port) ) await client.testscram.command("dbstats") client = await self.async_rs_or_single_client_noauth( diff --git a/test/asynchronous/test_auth_oidc.py b/test/asynchronous/test_auth_oidc.py index 3567d7706b..1eb3393f2b 100644 --- a/test/asynchronous/test_auth_oidc.py +++ b/test/asynchronous/test_auth_oidc.py @@ -13,6 +13,7 @@ # limitations under the License. """Test MONGODB-OIDC Authentication.""" + from __future__ import annotations import os @@ -22,16 +23,13 @@ import warnings from contextlib import asynccontextmanager from pathlib import Path -from test.asynchronous import AsyncPyMongoTestCase -from test.asynchronous.helpers import ConcurrentRunner -from typing import Dict import pytest -sys.path[0:0] = [""] +from test.asynchronous import AsyncPyMongoTestCase +from test.asynchronous.helpers import ConcurrentRunner -from test.asynchronous.unified_format import generate_test_classes, get_test_path -from test.utils_shared import EventListener, OvertCommandListener +sys.path[0:0] = [""] from bson import SON from pymongo import AsyncMongoClient @@ -50,6 +48,8 @@ from pymongo.hello import HelloCompat from pymongo.operations import InsertOne from pymongo.synchronous.uri_parser import parse_uri +from test.asynchronous.unified_format import generate_test_classes, get_test_path +from test.utils_shared import EventListener, OvertCommandListener _IS_SYNC = False @@ -87,7 +87,7 @@ def get_token(self, username=None): token_file = TOKEN_FILE else: token_file = os.path.join(TOKEN_DIR, username) - with open(token_file) as fid: # noqa: ASYNC101,RUF100 + with open(token_file) as fid: # noqa:RUF100 return fid.read() elif ENVIRON == "azure": opts = parse_uri(self.uri_single)["options"] @@ -166,7 +166,7 @@ async def create_client(self, *args, **kwargs): request_cb = kwargs.pop("request_cb", self.create_request_cb(username=username)) props = kwargs.pop("authmechanismproperties", {"OIDC_HUMAN_CALLBACK": request_cb}) kwargs["retryReads"] = False - if not len(args): + if not args: args = [self.uri_single] client = self.simple_client(*args, authmechanismproperties=props, **kwargs) @@ -224,7 +224,7 @@ async def test_1_5_multiple_principal_no_user(self): async def test_1_6_allowed_hosts_blocked(self): # Create a default OIDC client, with an ALLOWED_HOSTS that is an empty list. request_token = self.create_request_cb() - props: Dict = {"OIDC_HUMAN_CALLBACK": request_token, "ALLOWED_HOSTS": []} + props: dict = {"OIDC_HUMAN_CALLBACK": request_token, "ALLOWED_HOSTS": []} client = await self.create_client(authmechanismproperties=props) # Assert that a find operation fails with a client-side error. with self.assertRaises(ConfigurationError): @@ -234,7 +234,7 @@ async def test_1_6_allowed_hosts_blocked(self): # Create a client that uses the URL mongodb://localhost/?authMechanism=MONGODB-OIDC&ignored=example.com, # a human callback, and an ALLOWED_HOSTS that contains ["example.com"]. - props: Dict = { + props: dict = { "OIDC_HUMAN_CALLBACK": request_token, "ALLOWED_HOSTS": ["example.com"], } @@ -776,7 +776,7 @@ async def create_client(self, *args, **kwargs): request_cb = kwargs.pop("request_cb", self.create_request_cb()) props = kwargs.pop("authmechanismproperties", {"OIDC_CALLBACK": request_cb}) kwargs["retryReads"] = False - if not len(args): + if not args: args = [self.uri_single] client = AsyncMongoClient(*args, authmechanismproperties=props, **kwargs) self.addAsyncCleanup(client.close) @@ -848,20 +848,20 @@ def fetch(self, a): async def test_2_4_invalid_client_configuration_with_callback(self): # Create a AsyncMongoClient configured with an OIDC callback and auth mechanism property ENVIRONMENT:test. request_cb = self.create_request_cb() - props: Dict = {"OIDC_CALLBACK": request_cb, "ENVIRONMENT": "test"} + props: dict = {"OIDC_CALLBACK": request_cb, "ENVIRONMENT": "test"} # Assert it returns a client configuration error. with self.assertRaises(ConfigurationError): await self.create_client(authmechanismproperties=props) async def test_2_5_invalid_use_of_ALLOWED_HOSTS(self): # Create an OIDC configured client with auth mechanism properties `{"ENVIRONMENT": "test", "ALLOWED_HOSTS": []}`. - props: Dict = {"ENVIRONMENT": "test", "ALLOWED_HOSTS": []} + props: dict = {"ENVIRONMENT": "test", "ALLOWED_HOSTS": []} # Assert it returns a client configuration error. with self.assertRaises(ConfigurationError): await self.create_client(authmechanismproperties=props) # Create an OIDC configured client with auth mechanism properties `{"OIDC_CALLBACK": "", "ALLOWED_HOSTS": []}`. - props: Dict = {"OIDC_CALLBACK": self.create_request_cb(), "ALLOWED_HOSTS": []} + props: dict = {"OIDC_CALLBACK": self.create_request_cb(), "ALLOWED_HOSTS": []} # Assert it returns a client configuration error. with self.assertRaises(ConfigurationError): await self.create_client(authmechanismproperties=props) diff --git a/test/asynchronous/test_auth_spec.py b/test/asynchronous/test_auth_spec.py index a40687348c..1cfd706fbb 100644 --- a/test/asynchronous/test_auth_spec.py +++ b/test/asynchronous/test_auth_spec.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the auth spec tests.""" + from __future__ import annotations import glob @@ -20,17 +21,18 @@ import os import sys import warnings -from test.asynchronous import AsyncPyMongoTestCase import pytest -sys.path[0:0] = [""] +from pymongo.errors import ConfigurationError +from test.asynchronous import AsyncPyMongoTestCase -from test import unittest -from test.asynchronous.unified_format import generate_test_classes, get_test_path +sys.path[0:0] = [""] from pymongo import AsyncMongoClient from pymongo.auth_oidc_shared import OIDCCallback +from test import unittest +from test.asynchronous.unified_format import generate_test_classes, get_test_path pytestmark = pytest.mark.auth @@ -56,7 +58,7 @@ def run_test(self): if not valid: with warnings.catch_warnings(): warnings.simplefilter("default") - self.assertRaises(Exception, AsyncMongoClient, uri, connect=False) + self.assertRaises(ConfigurationError, AsyncMongoClient, uri, connect=False) else: client = self.simple_client(uri, connect=False) credentials = client.options.pool_options._credentials diff --git a/test/asynchronous/test_bulk.py b/test/asynchronous/test_bulk.py index ce505bcb37..b98983df9a 100644 --- a/test/asynchronous/test_bulk.py +++ b/test/asynchronous/test_bulk.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the bulk API.""" + from __future__ import annotations import sys @@ -23,9 +24,6 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, remove_all_users, unittest -from test.utils_shared import async_wait_until - from bson.binary import Binary, UuidRepresentation from bson.codec_options import CodecOptions from bson.objectid import ObjectId @@ -34,6 +32,8 @@ from pymongo.errors import BulkWriteError, ConfigurationError, InvalidOperation, OperationFailure from pymongo.operations import * from pymongo.write_concern import WriteConcern +from test.asynchronous import AsyncIntegrationTest, async_client_context, remove_all_users, unittest +from test.utils_shared import async_wait_until _IS_SYNC = False diff --git a/test/asynchronous/test_causal_consistency.py b/test/asynchronous/test_causal_consistency.py index e802b5244e..284d7463fc 100644 --- a/test/asynchronous/test_causal_consistency.py +++ b/test/asynchronous/test_causal_consistency.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the causal consistency unified spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_change_stream.py b/test/asynchronous/test_change_stream.py index 2e1f83884b..6ada828d7e 100644 --- a/test/asynchronous/test_change_stream.py +++ b/test/asynchronous/test_change_stream.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the change_stream module.""" + from __future__ import annotations import asyncio @@ -28,21 +29,6 @@ sys.path[0:0] = [""] -from test.asynchronous import ( - AsyncIntegrationTest, - AsyncPyMongoTestCase, - Version, - async_client_context, - unittest, -) -from test.asynchronous.unified_format import generate_test_classes, get_test_path -from test.utils_shared import ( - AllowListEventListener, - EventListener, - OvertCommandListener, - async_wait_until, -) - from bson import SON, ObjectId, Timestamp, encode from bson.binary import ALL_UUID_REPRESENTATIONS, PYTHON_LEGACY, STANDARD, Binary from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument @@ -57,6 +43,20 @@ from pymongo.message import _CursorAddress from pymongo.read_concern import ReadConcern from pymongo.write_concern import WriteConcern +from test.asynchronous import ( + AsyncIntegrationTest, + AsyncPyMongoTestCase, + Version, + async_client_context, + unittest, +) +from test.asynchronous.unified_format import generate_test_classes, get_test_path +from test.utils_shared import ( + AllowListEventListener, + EventListener, + OvertCommandListener, + async_wait_until, +) _IS_SYNC = False diff --git a/test/asynchronous/test_client.py b/test/asynchronous/test_client.py index dea1161afa..e0848f6369 100644 --- a/test/asynchronous/test_client.py +++ b/test/asynchronous/test_client.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the mongo_client module.""" + from __future__ import annotations import _thread as thread @@ -33,7 +34,8 @@ import threading import time import uuid -from typing import Any, Iterable, Type, no_type_check +from collections.abc import Iterable +from typing import Any, no_type_check from unittest import mock, skipIf from unittest.mock import patch @@ -45,38 +47,6 @@ sys.path[0:0] = [""] -from test.asynchronous import ( - HAVE_IPADDRESS, - AsyncIntegrationTest, - AsyncMockClientTest, - AsyncUnitTest, - SkipTest, - async_client_context, - client_knobs, - connected, - db_pwd, - db_user, - remove_all_users, - unittest, -) -from test.asynchronous.pymongo_mocks import AsyncMockClient -from test.asynchronous.utils import ( - async_get_pool, - async_wait_until, - asyncAssertRaisesExactly, -) -from test.test_binary import BinaryData -from test.utils_shared import ( - NTHREADS, - CMAPListener, - FunctionCallRecorder, - delay, - gevent_monkey_patched, - is_greenthread_patched, - lazy_client_trial, - one, -) - import bson import pymongo from bson import encode @@ -124,6 +94,37 @@ from pymongo.server_type import SERVER_TYPE from pymongo.topology_description import TopologyDescription from pymongo.write_concern import WriteConcern +from test.asynchronous import ( + HAVE_IPADDRESS, + AsyncIntegrationTest, + AsyncMockClientTest, + AsyncUnitTest, + SkipTest, + async_client_context, + client_knobs, + connected, + db_pwd, + db_user, + remove_all_users, + unittest, +) +from test.asynchronous.pymongo_mocks import AsyncMockClient +from test.asynchronous.utils import ( + async_get_pool, + async_wait_until, + asyncAssertRaisesExactly, +) +from test.test_binary import BinaryData +from test.utils_shared import ( + NTHREADS, + CMAPListener, + FunctionCallRecorder, + delay, + gevent_monkey_patched, + is_greenthread_patched, + lazy_client_trial, + one, +) _IS_SYNC = False @@ -437,7 +438,7 @@ def transform_python(self, value): return int(value) # Ensure codec options are passed in correctly - document_class: Type[SON] = SON + document_class: type[SON] = SON type_registry = TypeRegistry([MyFloatAsIntEncoder()]) tz_aware = True uuid_representation_label = "javaLegacy" diff --git a/test/asynchronous/test_client_backpressure.py b/test/asynchronous/test_client_backpressure.py index 3e75ed9b0d..fc3c9b85d8 100644 --- a/test/asynchronous/test_client_backpressure.py +++ b/test/asynchronous/test_client_backpressure.py @@ -13,6 +13,7 @@ # limitations under the License. """Test Client Backpressure spec.""" + from __future__ import annotations import os @@ -25,6 +26,7 @@ sys.path[0:0] = [""] +from pymongo.errors import OperationFailure, PyMongoError from test.asynchronous import ( AsyncIntegrationTest, async_client_context, @@ -33,8 +35,6 @@ from test.asynchronous.unified_format import generate_test_classes from test.utils_shared import EventListener, OvertCommandListener -from pymongo.errors import OperationFailure, PyMongoError - _IS_SYNC = False # Mock a system overload error. diff --git a/test/asynchronous/test_client_bulk_write.py b/test/asynchronous/test_client_bulk_write.py index 49f969fa34..0a17d25e93 100644 --- a/test/asynchronous/test_client_bulk_write.py +++ b/test/asynchronous/test_client_bulk_write.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the client bulk write API.""" + from __future__ import annotations import os @@ -20,15 +21,6 @@ sys.path[0:0] = [""] -from test.asynchronous import ( - AsyncIntegrationTest, - async_client_context, - unittest, -) -from test.asynchronous.utils import flaky -from test.utils_shared import ( - OvertCommandListener, -) from unittest.mock import patch import pymongo @@ -42,6 +34,15 @@ ) from pymongo.operations import * from pymongo.write_concern import WriteConcern +from test.asynchronous import ( + AsyncIntegrationTest, + async_client_context, + unittest, +) +from test.asynchronous.utils import flaky +from test.utils_shared import ( + OvertCommandListener, +) _IS_SYNC = False diff --git a/test/asynchronous/test_client_context.py b/test/asynchronous/test_client_context.py index 652b32e798..e2f967cd50 100644 --- a/test/asynchronous/test_client_context.py +++ b/test/asynchronous/test_client_context.py @@ -31,9 +31,7 @@ def test_must_connect(self): self.assertTrue( async_client_context.connected, "client context must be connected when " - "PYMONGO_MUST_CONNECT is set. Failed attempts:\n{}".format( - async_client_context.connection_attempt_info() - ), + f"PYMONGO_MUST_CONNECT is set. Failed attempts:\n{async_client_context.connection_attempt_info()}", ) def test_enableTestCommands_is_disabled(self): diff --git a/test/asynchronous/test_client_metadata.py b/test/asynchronous/test_client_metadata.py index d4f887b1fc..147e01c39b 100644 --- a/test/asynchronous/test_client_metadata.py +++ b/test/asynchronous/test_client_metadata.py @@ -18,9 +18,6 @@ import pathlib import time import unittest -from test.asynchronous import AsyncIntegrationTest -from test.asynchronous.unified_format import generate_test_classes, get_test_path -from test.utils_shared import CMAPListener from typing import Any, Optional import pytest @@ -28,6 +25,9 @@ from pymongo import AsyncMongoClient from pymongo.driver_info import DriverInfo from pymongo.monitoring import ConnectionClosedEvent +from test.asynchronous import AsyncIntegrationTest +from test.asynchronous.unified_format import generate_test_classes, get_test_path +from test.utils_shared import CMAPListener try: from mockupdb import MockupDB, OpMsgReply @@ -193,7 +193,7 @@ async def test_doesnt_update_established_connections(self): ) # send initial metadata - name, version, platform, metadata = await self.send_ping_and_get_metadata(client, True) + name, version, platform, _metadata = await self.send_ping_and_get_metadata(client, True) self.assertIsNotNone(name) self.assertIsNotNone(version) self.assertIsNotNone(platform) diff --git a/test/asynchronous/test_collation.py b/test/asynchronous/test_collation.py index 78b28ab3df..72ce70cc78 100644 --- a/test/asynchronous/test_collation.py +++ b/test/asynchronous/test_collation.py @@ -13,12 +13,11 @@ # limitations under the License. """Test the collation module.""" + from __future__ import annotations import functools import warnings -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.utils_shared import EventListener, OvertCommandListener from typing import Any from pymongo.asynchronous.helpers import anext @@ -39,6 +38,8 @@ UpdateOne, ) from pymongo.write_concern import WriteConcern +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.utils_shared import EventListener, OvertCommandListener _IS_SYNC = False diff --git a/test/asynchronous/test_collection.py b/test/asynchronous/test_collection.py index 3232650487..48c69e536d 100644 --- a/test/asynchronous/test_collection.py +++ b/test/asynchronous/test_collection.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the collection module.""" + from __future__ import annotations import asyncio @@ -21,28 +22,15 @@ import sys from codecs import utf_8_decode from collections import defaultdict -from test.asynchronous.utils import async_get_pool, async_is_mongos -from typing import Any, Iterable, no_type_check +from collections.abc import Iterable +from typing import Any, no_type_check from pymongo.asynchronous.database import AsyncDatabase from pymongo.asynchronous.helpers import anext +from test.asynchronous.utils import async_get_pool, async_is_mongos sys.path[0:0] = [""] -from test import unittest -from test.asynchronous import ( # TODO: fix sync imports in PYTHON-4528 - AsyncIntegrationTest, - AsyncUnitTest, - async_client_context, -) -from test.utils_shared import ( - IMPOSSIBLE_WRITE_CONCERN, - EventListener, - OvertCommandListener, - async_wait_until, -) -from test.version import Version - from bson import encode from bson.codec_options import CodecOptions from bson.objectid import ObjectId @@ -77,6 +65,19 @@ UpdateResult, ) from pymongo.write_concern import WriteConcern +from test import unittest +from test.asynchronous import ( # TODO: fix sync imports in PYTHON-4528 + AsyncIntegrationTest, + AsyncUnitTest, + async_client_context, +) +from test.utils_shared import ( + IMPOSSIBLE_WRITE_CONCERN, + EventListener, + OvertCommandListener, + async_wait_until, +) +from test.version import Version _IS_SYNC = False diff --git a/test/asynchronous/test_collection_management.py b/test/asynchronous/test_collection_management.py index 7a142dc65b..36b442cf80 100644 --- a/test/asynchronous/test_collection_management.py +++ b/test/asynchronous/test_collection_management.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the collection management unified spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_command_logging.py b/test/asynchronous/test_command_logging.py index 831dd0c109..844889f771 100644 --- a/test/asynchronous/test_command_logging.py +++ b/test/asynchronous/test_command_logging.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the command monitoring unified format spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_command_monitoring.py b/test/asynchronous/test_command_monitoring.py index a04ba449ae..69217066b4 100644 --- a/test/asynchronous/test_command_monitoring.py +++ b/test/asynchronous/test_command_monitoring.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the command monitoring unified format spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_comment.py b/test/asynchronous/test_comment.py index 2d6d0f5f1e..09c57891a7 100644 --- a/test/asynchronous/test_comment.py +++ b/test/asynchronous/test_comment.py @@ -21,12 +21,12 @@ sys.path[0:0] = [""] from inspect import iscoroutinefunction -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.utils_shared import OvertCommandListener from bson.dbref import DBRef from pymongo.asynchronous.command_cursor import AsyncCommandCursor from pymongo.operations import IndexModel +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.utils_shared import OvertCommandListener _IS_SYNC = False diff --git a/test/asynchronous/test_common.py b/test/asynchronous/test_common.py index 00495e7c30..8440ca98dc 100644 --- a/test/asynchronous/test_common.py +++ b/test/asynchronous/test_common.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the pymongo common module.""" + from __future__ import annotations import sys @@ -20,13 +21,12 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, connected, unittest - from bson.binary import PYTHON_LEGACY, STANDARD, Binary, UuidRepresentation from bson.codec_options import CodecOptions from bson.objectid import ObjectId from pymongo.errors import OperationFailure from pymongo.write_concern import WriteConcern +from test.asynchronous import AsyncIntegrationTest, async_client_context, connected, unittest _IS_SYNC = False diff --git a/test/asynchronous/test_concurrency.py b/test/asynchronous/test_concurrency.py index 65ea90c03f..48091ebb0e 100644 --- a/test/asynchronous/test_concurrency.py +++ b/test/asynchronous/test_concurrency.py @@ -13,10 +13,12 @@ # limitations under the License. """Tests to ensure that the async API is properly concurrent with asyncio.""" + from __future__ import annotations import asyncio import time + from test.asynchronous import AsyncIntegrationTest, async_client_context from test.utils_shared import delay diff --git a/test/asynchronous/test_connection_logging.py b/test/asynchronous/test_connection_logging.py index 4d03391dd2..fdd9aa6eb4 100644 --- a/test/asynchronous/test_connection_logging.py +++ b/test/asynchronous/test_connection_logging.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the connection logging unified format spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_connection_monitoring.py b/test/asynchronous/test_connection_monitoring.py index c6dc6f0a69..d3bb6ad29e 100644 --- a/test/asynchronous/test_connection_monitoring.py +++ b/test/asynchronous/test_connection_monitoring.py @@ -13,6 +13,7 @@ # limitations under the License. """Execute Transactions Spec tests.""" + from __future__ import annotations import asyncio @@ -20,19 +21,11 @@ import sys import time from pathlib import Path + from test.asynchronous.utils import async_get_pool, async_get_pools sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, client_knobs, unittest -from test.asynchronous.pymongo_mocks import DummyMonitor -from test.asynchronous.utils_spec_runner import AsyncSpecTestCreator, SpecRunnerTask -from test.utils_shared import ( - CMAPListener, - async_wait_until, - camel_to_snake, -) - from bson.objectid import ObjectId from bson.son import SON from pymongo.asynchronous.pool import PoolState, _PoolClosedError @@ -59,6 +52,14 @@ ) from pymongo.read_preferences import ReadPreference from pymongo.topology_description import updated_topology_description +from test.asynchronous import AsyncIntegrationTest, async_client_context, client_knobs, unittest +from test.asynchronous.pymongo_mocks import DummyMonitor +from test.asynchronous.utils_spec_runner import AsyncSpecTestCreator, SpecRunnerTask +from test.utils_shared import ( + CMAPListener, + async_wait_until, + camel_to_snake, +) _IS_SYNC = False diff --git a/test/asynchronous/test_connections_survive_primary_stepdown_spec.py b/test/asynchronous/test_connections_survive_primary_stepdown_spec.py index aed3c1ce7b..7ce93ab360 100644 --- a/test/asynchronous/test_connections_survive_primary_stepdown_spec.py +++ b/test/asynchronous/test_connections_survive_primary_stepdown_spec.py @@ -13,13 +13,20 @@ # limitations under the License. """Test compliance with the connections survive primary step down spec.""" + from __future__ import annotations import sys + from test.asynchronous.utils import async_ensure_all_connected sys.path[0:0] = [""] +from bson import SON +from pymongo import monitoring +from pymongo.asynchronous.collection import AsyncCollection +from pymongo.errors import NotPrimaryError +from pymongo.write_concern import WriteConcern from test.asynchronous import ( AsyncIntegrationTest, async_client_context, @@ -30,12 +37,6 @@ CMAPListener, ) -from bson import SON -from pymongo import monitoring -from pymongo.asynchronous.collection import AsyncCollection -from pymongo.errors import NotPrimaryError -from pymongo.write_concern import WriteConcern - _IS_SYNC = False diff --git a/test/asynchronous/test_crud_unified.py b/test/asynchronous/test_crud_unified.py index 94e47a26e7..0e72372dc6 100644 --- a/test/asynchronous/test_crud_unified.py +++ b/test/asynchronous/test_crud_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the CRUD unified spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_csot.py b/test/asynchronous/test_csot.py index 547ee20a54..a3989c9c18 100644 --- a/test/asynchronous/test_csot.py +++ b/test/asynchronous/test_csot.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the CSOT unified spec tests.""" + from __future__ import annotations import os @@ -21,13 +22,12 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.asynchronous.unified_format import generate_test_classes, get_test_path -from test.asynchronous.utils import flaky - import pymongo from pymongo import _csot from pymongo.errors import PyMongoError +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.asynchronous.unified_format import generate_test_classes, get_test_path +from test.asynchronous.utils import flaky _IS_SYNC = False diff --git a/test/asynchronous/test_cursor.py b/test/asynchronous/test_cursor.py index 95604f65df..6e41d54eec 100644 --- a/test/asynchronous/test_cursor.py +++ b/test/asynchronous/test_cursor.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the cursor module.""" + from __future__ import annotations import copy @@ -30,17 +31,6 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.asynchronous.utils import flaky -from test.utils_shared import ( - AllowListEventListener, - EventListener, - OvertCommandListener, - async_wait_until, - delay, - ignore_deprecations, -) - from bson import decode_all from bson.code import Code from bson.raw_bson import RawBSONDocument @@ -53,6 +43,16 @@ from pymongo.read_concern import ReadConcern from pymongo.read_preferences import ReadPreference from pymongo.write_concern import WriteConcern +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.asynchronous.utils import flaky +from test.utils_shared import ( + AllowListEventListener, + EventListener, + OvertCommandListener, + async_wait_until, + delay, + ignore_deprecations, +) _IS_SYNC = False diff --git a/test/asynchronous/test_custom_types.py b/test/asynchronous/test_custom_types.py index 82c54512cc..c0564dd876 100644 --- a/test/asynchronous/test_custom_types.py +++ b/test/asynchronous/test_custom_types.py @@ -13,6 +13,7 @@ # limitations under the License. """Test support for callbacks to encode/decode custom types.""" + from __future__ import annotations import datetime @@ -21,15 +22,13 @@ from collections import OrderedDict from decimal import Decimal from random import random -from typing import Any, Tuple, Type, no_type_check +from typing import Any, no_type_check from bson.decimal128 import DecimalDecoder, DecimalEncoder from gridfs.asynchronous.grid_file import AsyncGridIn, AsyncGridOut sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest - from bson import ( _BUILT_IN_TYPES, RE_TYPE, @@ -56,6 +55,7 @@ from pymongo.asynchronous.helpers import anext from pymongo.errors import DuplicateKeyError from pymongo.message import _CursorAddress +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest _IS_SYNC = False @@ -341,9 +341,9 @@ class TestBSONCustomTypeEncoderAndFallbackEncoderTandem(unittest.TestCase): TypeB: Any fallback_encoder_A2B: Any fallback_encoder_A2BSON: Any - B2BSON: Type[TypeEncoder] - B2A: Type[TypeEncoder] - A2B: Type[TypeEncoder] + B2BSON: type[TypeEncoder] + B2A: type[TypeEncoder] + A2B: type[TypeEncoder] @classmethod def setUpClass(cls): @@ -433,8 +433,8 @@ def test_infinite_loop_exceeds_max_recursion_depth(self): class TestTypeRegistry(unittest.TestCase): - types: Tuple[object, object] - codecs: Tuple[Type[TypeCodec], Type[TypeCodec]] + types: tuple[object, object] + codecs: tuple[type[TypeCodec], type[TypeCodec]] fallback_encoder: Any @classmethod diff --git a/test/asynchronous/test_database.py b/test/asynchronous/test_database.py index 22213d6e8a..e8b37f6405 100644 --- a/test/asynchronous/test_database.py +++ b/test/asynchronous/test_database.py @@ -13,25 +13,18 @@ # limitations under the License. """Test the database module.""" + from __future__ import annotations import re import sys -from typing import Any, Iterable, List, Mapping, Union +from collections.abc import Iterable, Mapping +from typing import Any, Union from pymongo.asynchronous.command_cursor import AsyncCommandCursor sys.path[0:0] = [""] -from test import unittest -from test.asynchronous import AsyncIntegrationTest, async_client_context -from test.test_custom_types import DECIMAL_CODECOPTS -from test.utils_shared import ( - IMPOSSIBLE_WRITE_CONCERN, - OvertCommandListener, - async_wait_until, -) - from bson.codec_options import CodecOptions from bson.dbref import DBRef from bson.int64 import Int64 @@ -55,6 +48,14 @@ from pymongo.read_concern import ReadConcern from pymongo.read_preferences import ReadPreference from pymongo.write_concern import WriteConcern +from test import unittest +from test.asynchronous import AsyncIntegrationTest, async_client_context +from test.test_custom_types import DECIMAL_CODECOPTS +from test.utils_shared import ( + IMPOSSIBLE_WRITE_CONCERN, + OvertCommandListener, + async_wait_until, +) _IS_SYNC = False @@ -692,7 +693,7 @@ def test_with_options(self): class TestDatabaseAggregation(AsyncIntegrationTest): async def asyncSetUp(self): await super().asyncSetUp() - self.pipeline: List[Mapping[str, Any]] = [ + self.pipeline: list[Mapping[str, Any]] = [ {"$listLocalSessions": {}}, {"$limit": 1}, {"$addFields": {"dummy": "dummy field"}}, diff --git a/test/asynchronous/test_discovery_and_monitoring.py b/test/asynchronous/test_discovery_and_monitoring.py index 17a90db60f..6f0857a92c 100644 --- a/test/asynchronous/test_discovery_and_monitoring.py +++ b/test/asynchronous/test_discovery_and_monitoring.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the topology module.""" + from __future__ import annotations import asyncio @@ -23,39 +24,17 @@ import time from asyncio import StreamReader, StreamWriter from pathlib import Path -from test.asynchronous.helpers import ConcurrentRunner -from test.asynchronous.utils import flaky -from test.utils_shared import delay from pymongo.asynchronous.pool import AsyncConnection from pymongo.errors import ConnectionFailure from pymongo.operations import _Op from pymongo.server_selectors import writable_server_selector +from test.asynchronous.helpers import ConcurrentRunner +from test.asynchronous.utils import flaky +from test.utils_shared import delay sys.path[0:0] = [""] -from test.asynchronous import ( - AsyncIntegrationTest, - AsyncPyMongoTestCase, - AsyncUnitTest, - async_client_context, - unittest, -) -from test.asynchronous.pymongo_mocks import DummyMonitor -from test.asynchronous.unified_format import generate_test_classes, get_test_path -from test.asynchronous.utils import ( - async_get_pool, -) -from test.utils_shared import ( - CMAPListener, - HeartbeatEventListener, - HeartbeatEventsListListener, - assertion_context, - async_barrier_wait, - async_create_barrier, - async_wait_until, - server_name_to_type, -) from unittest.mock import patch from bson import Timestamp, json_util @@ -80,6 +59,28 @@ ) from pymongo.server_description import SERVER_TYPE, ServerDescription from pymongo.topology_description import TOPOLOGY_TYPE +from test.asynchronous import ( + AsyncIntegrationTest, + AsyncPyMongoTestCase, + AsyncUnitTest, + async_client_context, + unittest, +) +from test.asynchronous.pymongo_mocks import DummyMonitor +from test.asynchronous.unified_format import generate_test_classes, get_test_path +from test.asynchronous.utils import ( + async_get_pool, +) +from test.utils_shared import ( + CMAPListener, + HeartbeatEventListener, + HeartbeatEventsListListener, + assertion_context, + async_barrier_wait, + async_create_barrier, + async_wait_until, + server_name_to_type, +) _IS_SYNC = False diff --git a/test/asynchronous/test_dns.py b/test/asynchronous/test_dns.py index 5666612218..b915653571 100644 --- a/test/asynchronous/test_dns.py +++ b/test/asynchronous/test_dns.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the SRV support tests.""" + from __future__ import annotations import glob @@ -23,6 +24,12 @@ sys.path[0:0] = [""] +from unittest.mock import MagicMock, patch + +from pymongo.asynchronous.uri_parser import parse_uri +from pymongo.common import validate_read_preference_tags +from pymongo.errors import ConfigurationError +from pymongo.uri_parser_shared import split_hosts from test.asynchronous import ( AsyncIntegrationTest, AsyncPyMongoTestCase, @@ -30,12 +37,6 @@ unittest, ) from test.utils_shared import async_wait_until -from unittest.mock import MagicMock, patch - -from pymongo.asynchronous.uri_parser import parse_uri -from pymongo.common import validate_read_preference_tags -from pymongo.errors import ConfigurationError -from pymongo.uri_parser_shared import split_hosts _IS_SYNC = False @@ -271,7 +272,7 @@ async def test_3_throw_when_return_address_is_identical_to_srv_hostname(self): await self.run_initial_dns_seedlist_discovery_prose_tests(test_cases) async def test_4_throw_when_return_address_does_not_contain_dot_separating_shared_part_of_domain( - self + self, ): test_cases = [ { @@ -293,7 +294,7 @@ async def test_4_throw_when_return_address_does_not_contain_dot_separating_share await self.run_initial_dns_seedlist_discovery_prose_tests(test_cases) async def test_5_when_srv_hostname_has_two_dot_separated_parts_it_is_valid_for_the_returned_hostname_to_be_identical( - self + self, ): test_cases = [ { diff --git a/test/asynchronous/test_encryption.py b/test/asynchronous/test_encryption.py index 455b1940c4..5b6a86d3dc 100644 --- a/test/asynchronous/test_encryption.py +++ b/test/asynchronous/test_encryption.py @@ -13,6 +13,7 @@ # limitations under the License. """Test client side encryption spec.""" + from __future__ import annotations import base64 @@ -30,17 +31,18 @@ import traceback import uuid import warnings -from test.asynchronous import AsyncIntegrationTest, AsyncPyMongoTestCase, async_client_context -from test.asynchronous.test_bulk import AsyncBulkTestBase -from test.asynchronous.utils import flaky +from collections.abc import Mapping from threading import Thread -from typing import Any, Dict, Mapping, Optional +from typing import Any, Optional import pytest from pymongo.asynchronous.collection import AsyncCollection from pymongo.daemon import _spawn_daemon from pymongo.uri_parser_shared import _parse_kms_tls_options +from test.asynchronous import AsyncIntegrationTest, AsyncPyMongoTestCase, async_client_context +from test.asynchronous.test_bulk import AsyncBulkTestBase +from test.asynchronous.utils import flaky try: from pymongo.pyopenssl_context import IS_PYOPENSSL @@ -49,32 +51,6 @@ sys.path[0:0] = [""] -from test import ( - unittest, -) -from test.asynchronous.test_bulk import AsyncBulkTestBase -from test.asynchronous.unified_format import generate_test_classes, get_test_path -from test.helpers_shared import ( - ALL_KMS_PROVIDERS, - AWS_CREDS, - AWS_TEMP_CREDS, - AZURE_CREDS, - CA_PEM, - CLIENT_PEM, - DEFAULT_KMS_TLS, - GCP_CREDS, - KMIP_CREDS, - LOCAL_MASTER_KEY, -) -from test.utils_shared import ( - AllowListEventListener, - OvertCommandListener, - TopologyEventListener, - async_wait_until, - camel_to_snake_args, - is_greenthread_patched, -) - from bson import BSON, DatetimeMS, Decimal128, encode, json_util from bson.binary import UUID_SUBTYPE, Binary, UuidRepresentation from bson.codec_options import CodecOptions @@ -103,6 +79,31 @@ ) from pymongo.operations import InsertOne, ReplaceOne, UpdateOne from pymongo.write_concern import WriteConcern +from test import ( + unittest, +) +from test.asynchronous.test_bulk import AsyncBulkTestBase +from test.asynchronous.unified_format import generate_test_classes, get_test_path +from test.helpers_shared import ( + ALL_KMS_PROVIDERS, + AWS_CREDS, + AWS_TEMP_CREDS, + AZURE_CREDS, + CA_PEM, + CLIENT_PEM, + DEFAULT_KMS_TLS, + GCP_CREDS, + KMIP_CREDS, + LOCAL_MASTER_KEY, +) +from test.utils_shared import ( + AllowListEventListener, + OvertCommandListener, + TopologyEventListener, + async_wait_until, + camel_to_snake_args, + is_greenthread_patched, +) _IS_SYNC = False @@ -2927,7 +2928,7 @@ async def encrypt_and_cast(i): EncryptionError, "expected matching 'min' and value type. Got range option" ): await self.client_encryption.encrypt( - 6 if cast_func != int else float(6), + 6 if cast_func is int else float(6), key_id=self.key1_id, algorithm=Algorithm.RANGE, contention_factor=0, @@ -3195,7 +3196,7 @@ async def test_copy_encrypted_fields(self): self.assertIsNone(encrypted_fields["fields"][0]["keyId"]) async def test_options_forward(self): - coll, ef = await self.client_encryption.create_encrypted_collection( + coll, _ = await self.client_encryption.create_encrypted_collection( database=self.db, name="testing1", kms_provider="local", diff --git a/test/asynchronous/test_examples.py b/test/asynchronous/test_examples.py index dd27623654..58ce1621c1 100644 --- a/test/asynchronous/test_examples.py +++ b/test/asynchronous/test_examples.py @@ -13,6 +13,7 @@ # limitations under the License. """MongoDB documentation examples in Python.""" + from __future__ import annotations import asyncio @@ -21,13 +22,11 @@ import sys import threading import time + from test.asynchronous.helpers import ConcurrentRunner sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.utils_shared import async_wait_until - import pymongo from pymongo.asynchronous.helpers import anext from pymongo.errors import ConnectionFailure, OperationFailure @@ -35,6 +34,8 @@ from pymongo.read_preferences import ReadPreference from pymongo.server_api import ServerApi from pymongo.write_concern import WriteConcern +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.utils_shared import async_wait_until _IS_SYNC = False diff --git a/test/asynchronous/test_grid_file.py b/test/asynchronous/test_grid_file.py index f3ca596142..32d87bc66f 100644 --- a/test/asynchronous/test_grid_file.py +++ b/test/asynchronous/test_grid_file.py @@ -14,6 +14,7 @@ # limitations under the License. """Tests for the grid_file module.""" + from __future__ import annotations import datetime @@ -21,6 +22,8 @@ import sys import zipfile from io import BytesIO + +from pymongo.asynchronous.database import AsyncDatabase from test.asynchronous import ( AsyncIntegrationTest, AsyncUnitTest, @@ -29,12 +32,8 @@ unittest, ) -from pymongo.asynchronous.database import AsyncDatabase - sys.path[0:0] = [""] -from test.utils_shared import OvertCommandListener - from bson.objectid import ObjectId from gridfs.asynchronous.grid_file import ( _SEEK_CUR, @@ -50,6 +49,7 @@ from pymongo.asynchronous.helpers import aiter, anext from pymongo.errors import ConfigurationError, ServerSelectionTimeoutError from pymongo.message import _CursorAddress +from test.utils_shared import OvertCommandListener _IS_SYNC = False diff --git a/test/asynchronous/test_gridfs.py b/test/asynchronous/test_gridfs.py index f60352f3cb..81aa1b6e8e 100644 --- a/test/asynchronous/test_gridfs.py +++ b/test/asynchronous/test_gridfs.py @@ -14,6 +14,7 @@ # limitations under the License. """Tests for the gridfs package.""" + from __future__ import annotations import asyncio @@ -22,14 +23,11 @@ import threading import time from io import BytesIO -from test.asynchronous.helpers import ConcurrentRunner from unittest.mock import patch -sys.path[0:0] = [""] +from test.asynchronous.helpers import ConcurrentRunner -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.asynchronous.utils import async_joinall -from test.utils_shared import one +sys.path[0:0] = [""] import gridfs from bson.binary import Binary @@ -43,6 +41,9 @@ ServerSelectionTimeoutError, ) from pymongo.read_preferences import ReadPreference +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.asynchronous.utils import async_joinall +from test.utils_shared import one _IS_SYNC = False diff --git a/test/asynchronous/test_gridfs_bucket.py b/test/asynchronous/test_gridfs_bucket.py index fd9b9883bf..6b3503d122 100644 --- a/test/asynchronous/test_gridfs_bucket.py +++ b/test/asynchronous/test_gridfs_bucket.py @@ -14,6 +14,7 @@ # limitations under the License. """Tests for the gridfs package.""" + from __future__ import annotations import asyncio @@ -23,14 +24,11 @@ import threading import time from io import BytesIO -from test.asynchronous.helpers import ConcurrentRunner from unittest.mock import patch -sys.path[0:0] = [""] +from test.asynchronous.helpers import ConcurrentRunner -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.asynchronous.utils import async_joinall -from test.utils_shared import one +sys.path[0:0] = [""] import gridfs from bson.binary import Binary @@ -46,6 +44,9 @@ WriteConcernError, ) from pymongo.read_preferences import ReadPreference +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.asynchronous.utils import async_joinall +from test.utils_shared import one _IS_SYNC = False diff --git a/test/asynchronous/test_gridfs_spec.py b/test/asynchronous/test_gridfs_spec.py index ab1c8a0ebb..754edf4ee6 100644 --- a/test/asynchronous/test_gridfs_spec.py +++ b/test/asynchronous/test_gridfs_spec.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the AsyncGridFS unified spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_heartbeat_monitoring.py b/test/asynchronous/test_heartbeat_monitoring.py index aa8a205021..ff8c7c9eb9 100644 --- a/test/asynchronous/test_heartbeat_monitoring.py +++ b/test/asynchronous/test_heartbeat_monitoring.py @@ -13,19 +13,20 @@ # limitations under the License. """Test the monitoring of the server heartbeats.""" + from __future__ import annotations import sys + from test.asynchronous.utils import AsyncMockPool sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, client_knobs, unittest -from test.utils_shared import HeartbeatEventListener, async_wait_until - from pymongo.asynchronous.monitor import Monitor from pymongo.errors import ConnectionFailure from pymongo.hello import Hello, HelloCompat +from test.asynchronous import AsyncIntegrationTest, client_knobs, unittest +from test.utils_shared import HeartbeatEventListener, async_wait_until _IS_SYNC = False diff --git a/test/asynchronous/test_index_management.py b/test/asynchronous/test_index_management.py index 591278414a..ffd9012e85 100644 --- a/test/asynchronous/test_index_management.py +++ b/test/asynchronous/test_index_management.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the auth spec tests.""" + from __future__ import annotations import asyncio @@ -21,20 +22,20 @@ import sys import time import uuid -from typing import Any, Mapping +from collections.abc import Mapping +from typing import Any import pytest sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, AsyncPyMongoTestCase, unittest -from test.asynchronous.unified_format import generate_test_classes, get_test_path -from test.utils_shared import AllowListEventListener, OvertCommandListener - from pymongo.errors import OperationFailure from pymongo.operations import SearchIndexModel from pymongo.read_concern import ReadConcern from pymongo.write_concern import WriteConcern +from test.asynchronous import AsyncIntegrationTest, AsyncPyMongoTestCase, unittest +from test.asynchronous.unified_format import generate_test_classes, get_test_path +from test.utils_shared import AllowListEventListener, OvertCommandListener _IS_SYNC = False diff --git a/test/asynchronous/test_json_util_integration.py b/test/asynchronous/test_json_util_integration.py index 32312cb9d3..9d364d06c8 100644 --- a/test/asynchronous/test_json_util_integration.py +++ b/test/asynchronous/test_json_util_integration.py @@ -1,10 +1,11 @@ from __future__ import annotations -from test.asynchronous import AsyncIntegrationTest -from typing import Any, List, MutableMapping +from collections.abc import MutableMapping +from typing import Any from bson import Binary, Code, DBRef, ObjectId, json_util from bson.binary import USER_DEFINED_SUBTYPE +from test.asynchronous import AsyncIntegrationTest _IS_SYNC = False @@ -14,7 +15,7 @@ async def test_cursor(self): db = self.db await db.drop_collection("test") - docs: List[MutableMapping[str, Any]] = [ + docs: list[MutableMapping[str, Any]] = [ {"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": Code("function x() { return 1; }")}, diff --git a/test/asynchronous/test_load_balancer.py b/test/asynchronous/test_load_balancer.py index 8c5d57434c..cd4c76c43a 100644 --- a/test/asynchronous/test_load_balancer.py +++ b/test/asynchronous/test_load_balancer.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Load Balancer unified spec tests.""" + from __future__ import annotations import asyncio @@ -22,13 +23,15 @@ import sys import threading from asyncio import Event -from test.asynchronous.helpers import ConcurrentRunner, ExceptionCatchingTask -from test.asynchronous.utils import async_get_pool import pytest +from test.asynchronous.helpers import ConcurrentRunner, ExceptionCatchingTask +from test.asynchronous.utils import async_get_pool + sys.path[0:0] = [""] +from pymongo.asynchronous.helpers import anext from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest from test.asynchronous.unified_format import generate_test_classes, get_test_path from test.utils_shared import ( @@ -36,8 +39,6 @@ create_async_event, ) -from pymongo.asynchronous.helpers import anext - _IS_SYNC = False pytestmark = pytest.mark.load_balancer diff --git a/test/asynchronous/test_locks.py b/test/asynchronous/test_locks.py index e5a0adfee6..54c31cbe35 100644 --- a/test/asynchronous/test_locks.py +++ b/test/asynchronous/test_locks.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Tests for lock.py""" + from __future__ import annotations import asyncio diff --git a/test/asynchronous/test_logger.py b/test/asynchronous/test_logger.py index d024735fd8..55379a9faa 100644 --- a/test/asynchronous/test_logger.py +++ b/test/asynchronous/test_logger.py @@ -14,13 +14,13 @@ from __future__ import annotations import os -from test import unittest -from test.asynchronous import AsyncIntegrationTest, async_client_context from unittest.mock import patch from bson import json_util from pymongo.errors import OperationFailure from pymongo.logger import _DEFAULT_DOCUMENT_LENGTH +from test import unittest +from test.asynchronous import AsyncIntegrationTest, async_client_context _IS_SYNC = False diff --git a/test/asynchronous/test_max_staleness.py b/test/asynchronous/test_max_staleness.py index b6e15f9158..cd6b462fcc 100644 --- a/test/asynchronous/test_max_staleness.py +++ b/test/asynchronous/test_max_staleness.py @@ -13,6 +13,7 @@ # limitations under the License. """Test maxStalenessSeconds support.""" + from __future__ import annotations import asyncio @@ -27,11 +28,10 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncPyMongoTestCase, async_client_context, unittest -from test.asynchronous.utils_selection_tests import create_selection_tests - from pymongo.errors import ConfigurationError from pymongo.server_selectors import writable_server_selector +from test.asynchronous import AsyncPyMongoTestCase, async_client_context, unittest +from test.asynchronous.utils_selection_tests import create_selection_tests _IS_SYNC = False diff --git a/test/asynchronous/test_mongos_load_balancing.py b/test/asynchronous/test_mongos_load_balancing.py index 97170aa9e0..028c9a5cdb 100644 --- a/test/asynchronous/test_mongos_load_balancing.py +++ b/test/asynchronous/test_mongos_load_balancing.py @@ -13,24 +13,24 @@ # limitations under the License. """Test AsyncMongoClient's mongos load balancing using a mock.""" + from __future__ import annotations import asyncio import sys import threading -from test.asynchronous.helpers import ConcurrentRunner from pymongo.operations import _Op +from test.asynchronous.helpers import ConcurrentRunner sys.path[0:0] = [""] -from test.asynchronous import AsyncMockClientTest, async_client_context, connected, unittest -from test.asynchronous.pymongo_mocks import AsyncMockClient -from test.utils_shared import async_wait_until - from pymongo.errors import AutoReconnect, InvalidOperation from pymongo.server_selectors import writable_server_selector from pymongo.topology_description import TOPOLOGY_TYPE +from test.asynchronous import AsyncMockClientTest, async_client_context, connected, unittest +from test.asynchronous.pymongo_mocks import AsyncMockClient +from test.utils_shared import async_wait_until _IS_SYNC = False diff --git a/test/asynchronous/test_monitor.py b/test/asynchronous/test_monitor.py index dde8976c06..842c470b8f 100644 --- a/test/asynchronous/test_monitor.py +++ b/test/asynchronous/test_monitor.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the monitor module.""" + from __future__ import annotations import asyncio @@ -24,14 +25,13 @@ sys.path[0:0] = [""] +from pymongo.periodic_executor import _EXECUTORS from test.asynchronous import AsyncIntegrationTest, async_client_context, connected, unittest from test.asynchronous.utils import ( async_wait_until, ) from test.utils_shared import ServerAndTopologyEventListener, gevent_monkey_patched -from pymongo.periodic_executor import _EXECUTORS - _IS_SYNC = False diff --git a/test/asynchronous/test_monitoring.py b/test/asynchronous/test_monitoring.py index 9b2a3691eb..929bd35104 100644 --- a/test/asynchronous/test_monitoring.py +++ b/test/asynchronous/test_monitoring.py @@ -22,6 +22,15 @@ sys.path[0:0] = [""] +from bson.int64 import Int64 +from bson.objectid import ObjectId +from bson.son import SON +from pymongo import CursorType, DeleteOne, InsertOne, UpdateOne, monitoring +from pymongo.asynchronous.command_cursor import AsyncCommandCursor +from pymongo.asynchronous.helpers import anext +from pymongo.errors import AutoReconnect, NotPrimaryError, OperationFailure +from pymongo.read_preferences import ReadPreference +from pymongo.write_concern import WriteConcern from test.asynchronous import ( AsyncIntegrationTest, async_client_context, @@ -35,16 +44,6 @@ async_wait_until, ) -from bson.int64 import Int64 -from bson.objectid import ObjectId -from bson.son import SON -from pymongo import CursorType, DeleteOne, InsertOne, UpdateOne, monitoring -from pymongo.asynchronous.command_cursor import AsyncCommandCursor -from pymongo.asynchronous.helpers import anext -from pymongo.errors import AutoReconnect, NotPrimaryError, OperationFailure -from pymongo.read_preferences import ReadPreference -from pymongo.write_concern import WriteConcern - _IS_SYNC = False diff --git a/test/asynchronous/test_on_demand_csfle.py b/test/asynchronous/test_on_demand_csfle.py index 55394ddeb8..cd1592318e 100644 --- a/test/asynchronous/test_on_demand_csfle.py +++ b/test/asynchronous/test_on_demand_csfle.py @@ -13,6 +13,7 @@ # limitations under the License. """Test client side encryption with on demand credentials.""" + from __future__ import annotations import os @@ -23,14 +24,13 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context - from bson.codec_options import CodecOptions from pymongo.asynchronous.encryption import ( _HAVE_PYMONGOCRYPT, AsyncClientEncryption, EncryptionError, ) +from test.asynchronous import AsyncIntegrationTest, async_client_context _IS_SYNC = False diff --git a/test/asynchronous/test_periodic_executor.py b/test/asynchronous/test_periodic_executor.py index 696955d40c..15186c4f88 100644 --- a/test/asynchronous/test_periodic_executor.py +++ b/test/asynchronous/test_periodic_executor.py @@ -23,9 +23,8 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncUnitTest, unittest - from pymongo.periodic_executor import AsyncPeriodicExecutor +from test.asynchronous import AsyncUnitTest, unittest _IS_SYNC = False diff --git a/test/asynchronous/test_pooling.py b/test/asynchronous/test_pooling.py index 9db9b5ab3a..4c31fe7070 100644 --- a/test/asynchronous/test_pooling.py +++ b/test/asynchronous/test_pooling.py @@ -13,6 +13,7 @@ # limitations under the License. """Test built in connection-pooling with threads.""" + from __future__ import annotations import asyncio @@ -21,7 +22,6 @@ import socket import sys import time -from test.asynchronous.utils import async_get_pool, async_joinall, flaky from bson.codec_options import DEFAULT_CODEC_OPTIONS from bson.son import SON @@ -29,16 +29,16 @@ from pymongo.errors import AutoReconnect, ConnectionFailure, DuplicateKeyError from pymongo.hello import HelloCompat from pymongo.lock import _async_create_lock +from test.asynchronous.utils import async_get_pool, async_joinall, flaky sys.path[0:0] = [""] +from pymongo.asynchronous.pool import Pool, PoolOptions +from pymongo.socket_checker import SocketChecker from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest from test.asynchronous.helpers import ConcurrentRunner from test.utils_shared import delay -from pymongo.asynchronous.pool import Pool, PoolOptions -from pymongo.socket_checker import SocketChecker - _IS_SYNC = False @@ -346,13 +346,13 @@ async def test_no_wait_queue_timeout(self): async with pool.checkout() as s1: t = SocketGetter(self.c, pool) await t.start() - while t.state != "get_socket": + while t.state != "get_socket": # noqa: ASYNC110 await asyncio.sleep(0.1) await asyncio.sleep(1) self.assertEqual(t.state, "get_socket") - while t.state != "connection": + while t.state != "connection": # noqa: ASYNC110 await asyncio.sleep(0.1) self.assertEqual(t.state, "connection") @@ -521,7 +521,7 @@ async def test_pool_backpressure_preserves_existing_connections(self): await coll.insert_many([{"x": 1} for _ in range(10)]) t = SocketGetter(self.c, pool) await t.start() - while t.state != "connection": + while t.state != "connection": # noqa: ASYNC110 await asyncio.sleep(0.1) assert not t.sock.conn_closed() diff --git a/test/asynchronous/test_raw_bson.py b/test/asynchronous/test_raw_bson.py index 70832ea668..688da7a670 100644 --- a/test/asynchronous/test_raw_bson.py +++ b/test/asynchronous/test_raw_bson.py @@ -19,14 +19,13 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest - from bson import Code, DBRef, decode, encode from bson.binary import JAVA_LEGACY, Binary, UuidRepresentation from bson.codec_options import CodecOptions from bson.errors import InvalidBSON from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument from bson.son import SON +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest _IS_SYNC = False diff --git a/test/asynchronous/test_read_concern.py b/test/asynchronous/test_read_concern.py index 8659bf80b2..487ae78cf6 100644 --- a/test/asynchronous/test_read_concern.py +++ b/test/asynchronous/test_read_concern.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the read_concern module.""" + from __future__ import annotations import sys @@ -20,12 +21,11 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context -from test.utils_shared import OvertCommandListener - from bson.son import SON from pymongo.errors import OperationFailure from pymongo.read_concern import ReadConcern +from test.asynchronous import AsyncIntegrationTest, async_client_context +from test.utils_shared import OvertCommandListener _IS_SYNC = False diff --git a/test/asynchronous/test_read_preferences.py b/test/asynchronous/test_read_preferences.py index 72dd809db0..9f92a39920 100644 --- a/test/asynchronous/test_read_preferences.py +++ b/test/asynchronous/test_read_preferences.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the replica_set_connection module.""" + from __future__ import annotations import contextlib @@ -26,21 +27,6 @@ sys.path[0:0] = [""] -from test.asynchronous import ( - AsyncIntegrationTest, - SkipTest, - async_client_context, - connected, - unittest, -) -from test.utils_shared import ( - OvertCommandListener, - _ignore_deprecations, - async_wait_until, - one, -) -from test.version import Version - from bson.son import SON from pymongo.asynchronous.helpers import anext from pymongo.asynchronous.mongo_client import AsyncMongoClient @@ -59,6 +45,20 @@ from pymongo.server_selectors import Selection, readable_server_selector from pymongo.server_type import SERVER_TYPE from pymongo.write_concern import WriteConcern +from test.asynchronous import ( + AsyncIntegrationTest, + SkipTest, + async_client_context, + connected, + unittest, +) +from test.utils_shared import ( + OvertCommandListener, + _ignore_deprecations, + async_wait_until, + one, +) +from test.version import Version _IS_SYNC = False diff --git a/test/asynchronous/test_read_write_concern_spec.py b/test/asynchronous/test_read_write_concern_spec.py index 2d08de7804..f7830fb6b0 100644 --- a/test/asynchronous/test_read_write_concern_spec.py +++ b/test/asynchronous/test_read_write_concern_spec.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the read and write concern tests.""" + from __future__ import annotations import json @@ -23,10 +24,6 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.asynchronous.unified_format import generate_test_classes, get_test_path -from test.utils_shared import OvertCommandListener - from pymongo import DESCENDING from pymongo.asynchronous.mongo_client import AsyncMongoClient from pymongo.errors import ( @@ -39,6 +36,9 @@ from pymongo.operations import IndexModel, InsertOne from pymongo.read_concern import ReadConcern from pymongo.write_concern import WriteConcern +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.asynchronous.unified_format import generate_test_classes, get_test_path +from test.utils_shared import OvertCommandListener _IS_SYNC = False diff --git a/test/asynchronous/test_retryable_reads.py b/test/asynchronous/test_retryable_reads.py index 361db4ca92..09d95d50b8 100644 --- a/test/asynchronous/test_retryable_reads.py +++ b/test/asynchronous/test_retryable_reads.py @@ -13,21 +13,29 @@ # limitations under the License. """Test retryable reads spec.""" + from __future__ import annotations import os import pprint import sys import threading -from test.asynchronous.utils import async_ensure_all_connected, async_set_fail_point from unittest import mock from pymongo import MongoClient from pymongo.common import MAX_ADAPTIVE_RETRIES from pymongo.errors import OperationFailure, PyMongoError +from test.asynchronous.utils import async_ensure_all_connected, async_set_fail_point sys.path[0:0] = [""] +from pymongo.monitoring import ( + CommandFailedEvent, + ConnectionCheckedOutEvent, + ConnectionCheckOutFailedEvent, + ConnectionCheckOutFailedReason, + PoolClearedEvent, +) from test.asynchronous import ( AsyncIntegrationTest, AsyncPyMongoTestCase, @@ -40,14 +48,6 @@ OvertCommandListener, ) -from pymongo.monitoring import ( - CommandFailedEvent, - ConnectionCheckedOutEvent, - ConnectionCheckOutFailedEvent, - ConnectionCheckOutFailedReason, - PoolClearedEvent, -) - _IS_SYNC = False @@ -202,7 +202,7 @@ async def test_retryable_reads_are_retried_on_a_different_mongos_when_one_is_ava @async_client_context.require_multiple_mongoses @async_client_context.require_failCommand_fail_point async def test_retryable_reads_are_retried_on_the_same_mongos_when_no_others_are_available( - self + self, ): fail_command = { "configureFailPoint": "failCommand", @@ -283,7 +283,7 @@ async def test_retryable_reads_are_retried_on_the_same_implicit_session(self): @async_client_context.require_failCommand_fail_point @async_client_context.require_version_min(4, 4, 0) async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available_and_overload_retargeting_is_enabled( - self + self, ): listener = OvertCommandListener() @@ -328,7 +328,7 @@ async def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_ @async_client_context.require_failCommand_fail_point @async_client_context.require_version_min(4, 4, 0) async def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_same_replicaset_server( - self + self, ): listener = OvertCommandListener() @@ -370,7 +370,7 @@ async def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_o @async_client_context.require_failCommand_fail_point @async_client_context.require_version_min(4, 4, 0) async def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_the_same_replicaset_server_when_one_is_available_and_overload_retargeting_is_disabled( - self + self, ): listener = OvertCommandListener() diff --git a/test/asynchronous/test_retryable_reads_unified.py b/test/asynchronous/test_retryable_reads_unified.py index 3de8aa96a3..9a15da37cd 100644 --- a/test/asynchronous/test_retryable_reads_unified.py +++ b/test/asynchronous/test_retryable_reads_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Retryable Reads unified spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_retryable_writes.py b/test/asynchronous/test_retryable_writes.py index a63aa6783e..9055e6dcd3 100644 --- a/test/asynchronous/test_retryable_writes.py +++ b/test/asynchronous/test_retryable_writes.py @@ -13,6 +13,7 @@ # limitations under the License. """Test retryable writes.""" + from __future__ import annotations import asyncio @@ -20,28 +21,13 @@ import pprint import sys import threading -from test.asynchronous.utils import async_set_fail_point, flaky from unittest import mock from pymongo.common import MAX_ADAPTIVE_RETRIES +from test.asynchronous.utils import async_set_fail_point, flaky sys.path[0:0] = [""] -from test.asynchronous import ( - AsyncIntegrationTest, - SkipTest, - async_client_context, - unittest, -) -from test.asynchronous.helpers import client_knobs -from test.utils_shared import ( - CMAPListener, - DeprecationFilter, - EventListener, - OvertCommandListener, -) -from test.version import Version - from bson.codec_options import DEFAULT_CODEC_OPTIONS from bson.int64 import Int64 from bson.raw_bson import RawBSONDocument @@ -72,6 +58,20 @@ UpdateOne, ) from pymongo.write_concern import WriteConcern +from test.asynchronous import ( + AsyncIntegrationTest, + SkipTest, + async_client_context, + unittest, +) +from test.asynchronous.helpers import client_knobs +from test.utils_shared import ( + CMAPListener, + DeprecationFilter, + EventListener, + OvertCommandListener, +) +from test.version import Version _IS_SYNC = False @@ -627,7 +627,7 @@ def configure_fail_point_sync(self, command_args, off=False) -> None: self.setup_client.admin.command(cmd) async def test_01_drivers_return_the_correct_error_when_receiving_only_errors_without_NoWritesPerformed( - self + self, ) -> None: # Create a client with retryWrites=true. listener = OvertCommandListener() @@ -679,7 +679,7 @@ def failed(event: CommandFailedEvent) -> None: assert exc.exception.errors["code"] == 10107 # type:ignore[call-overload] async def test_02_drivers_return_the_correct_error_when_receiving_only_errors_with_NoWritesPerformed( - self + self, ) -> None: # Create a client with retryWrites=true. listener = OvertCommandListener() @@ -732,7 +732,7 @@ def failed(event: CommandFailedEvent) -> None: assert exc.exception.errors["code"] == 91 # type:ignore[call-overload] async def test_03_drivers_return_the_correct_error_when_receiving_some_errors_with_NoWritesPerformed_and_some_without_NoWritesPerformed( - self + self, ) -> None: # Create a client with retryWrites=true. listener = OvertCommandListener() diff --git a/test/asynchronous/test_retryable_writes_unified.py b/test/asynchronous/test_retryable_writes_unified.py index 7d33c5252a..93aef65151 100644 --- a/test/asynchronous/test_retryable_writes_unified.py +++ b/test/asynchronous/test_retryable_writes_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Retryable Writes unified spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_run_command.py b/test/asynchronous/test_run_command.py index cfd1adfab7..b84591ea82 100644 --- a/test/asynchronous/test_run_command.py +++ b/test/asynchronous/test_run_command.py @@ -13,11 +13,13 @@ # limitations under the License. """Run Command unified tests.""" + from __future__ import annotations import os import unittest from pathlib import Path + from test.asynchronous.unified_format import generate_test_classes, get_test_path _IS_SYNC = False diff --git a/test/asynchronous/test_sdam_monitoring_spec.py b/test/asynchronous/test_sdam_monitoring_spec.py index 71ec6c6b46..1ed529cf7f 100644 --- a/test/asynchronous/test_sdam_monitoring_spec.py +++ b/test/asynchronous/test_sdam_monitoring_spec.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the sdam monitoring spec tests.""" + from __future__ import annotations import asyncio @@ -24,13 +25,6 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, client_knobs, unittest -from test.utils_shared import ( - ServerAndTopologyEventListener, - async_wait_until, - server_name_to_type, -) - from bson.json_util import object_hook from pymongo import AsyncMongoClient, monitoring from pymongo.asynchronous.collection import AsyncCollection @@ -40,6 +34,12 @@ from pymongo.hello import Hello from pymongo.server_description import ServerDescription from pymongo.topology_description import TOPOLOGY_TYPE +from test.asynchronous import AsyncIntegrationTest, async_client_context, client_knobs, unittest +from test.utils_shared import ( + ServerAndTopologyEventListener, + async_wait_until, + server_name_to_type, +) _IS_SYNC = False @@ -89,7 +89,7 @@ def compare_events(expected_dict, actual): if expected["address"] != "{}:{}".format(*actual.server_address): return ( False, - "ServerOpeningEvent published with wrong address (expected" " {}, got {}".format( + "ServerOpeningEvent published with wrong address (expected {}, got {}".format( expected["address"], actual.server_address ), ) @@ -100,7 +100,7 @@ def compare_events(expected_dict, actual): if expected["address"] != "{}:{}".format(*actual.server_address): return ( False, - "ServerDescriptionChangedEvent has wrong address" " (expected {}, got {}".format( + "ServerDescriptionChangedEvent has wrong address (expected {}, got {}".format( expected["address"], actual.server_address ), ) @@ -121,7 +121,7 @@ def compare_events(expected_dict, actual): if expected["address"] != "{}:{}".format(*actual.server_address): return ( False, - "ServerClosedEvent published with wrong address" " (expected {}, got {}".format( + "ServerClosedEvent published with wrong address (expected {}, got {}".format( expected["address"], actual.server_address ), ) diff --git a/test/asynchronous/test_server_selection.py b/test/asynchronous/test_server_selection.py index b704fcea83..b9c84778b2 100644 --- a/test/asynchronous/test_server_selection.py +++ b/test/asynchronous/test_server_selection.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the topology module's Server Selection Spec implementation.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_server_selection_in_window.py b/test/asynchronous/test_server_selection_in_window.py index 44b17413dc..70c0d1297d 100644 --- a/test/asynchronous/test_server_selection_in_window.py +++ b/test/asynchronous/test_server_selection_in_window.py @@ -13,12 +13,18 @@ # limitations under the License. """Test the topology module's Server Selection Spec implementation.""" + from __future__ import annotations import asyncio import os import threading from pathlib import Path + +from pymongo.common import clean_node +from pymongo.monitoring import ConnectionReadyEvent +from pymongo.operations import _Op +from pymongo.read_preferences import ReadPreference from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest from test.asynchronous.helpers import ConcurrentRunner from test.asynchronous.utils_selection_tests import create_topology @@ -29,11 +35,6 @@ async_wait_until, ) -from pymongo.common import clean_node -from pymongo.monitoring import ConnectionReadyEvent -from pymongo.operations import _Op -from pymongo.read_preferences import ReadPreference - _IS_SYNC = False # Location of JSON test specifications. if _IS_SYNC: @@ -55,7 +56,7 @@ async def run_scenario(self, scenario_def): server.pool.operation_count = mock["operation_count"] pref = ReadPreference.NEAREST - counts = {address: 0 for address in topology._description.server_descriptions()} + counts = dict.fromkeys(topology._description.server_descriptions(), 0) # Number of times to repeat server selection iterations = scenario_def["iterations"] @@ -128,7 +129,7 @@ async def frequencies(self, client, listener, n_finds=10): self.assertEqual(len(events), n_finds * N_TASKS) nodes = client.nodes self.assertEqual(len(nodes), 2) - freqs = {address: 0.0 for address in nodes} + freqs = dict.fromkeys(nodes, 0.0) for event in events: freqs[event.connection_id] += 1 for address in freqs: diff --git a/test/asynchronous/test_server_selection_logging.py b/test/asynchronous/test_server_selection_logging.py index 6f3ea207f4..516a15b9e0 100644 --- a/test/asynchronous/test_server_selection_logging.py +++ b/test/asynchronous/test_server_selection_logging.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the server selection logging unified format spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_server_selection_rtt.py b/test/asynchronous/test_server_selection_rtt.py index 1f8f6bc7df..7c871febf9 100644 --- a/test/asynchronous/test_server_selection_rtt.py +++ b/test/asynchronous/test_server_selection_rtt.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the topology module.""" + from __future__ import annotations import json @@ -22,11 +23,10 @@ sys.path[0:0] = [""] +from pymongo.read_preferences import MovingAverage from test import unittest from test.asynchronous import AsyncPyMongoTestCase -from pymongo.read_preferences import MovingAverage - _IS_SYNC = False # Location of JSON test specifications. diff --git a/test/asynchronous/test_session.py b/test/asynchronous/test_session.py index c5e51fa2f2..828e951f23 100644 --- a/test/asynchronous/test_session.py +++ b/test/asynchronous/test_session.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the client_session module.""" + from __future__ import annotations import copy @@ -20,11 +21,22 @@ import time from inspect import iscoroutinefunction from io import BytesIO +from typing import Any, Callable + from test.asynchronous.helpers import ExceptionCatchingTask -from typing import Any, Callable, List, Set, Tuple sys.path[0:0] = [""] +from bson import DBRef +from gridfs.asynchronous.grid_file import AsyncGridFS, AsyncGridFSBucket +from pymongo import ASCENDING, AsyncMongoClient, monitoring +from pymongo.asynchronous.command_cursor import AsyncCommandCursor +from pymongo.asynchronous.cursor import AsyncCursor +from pymongo.asynchronous.helpers import anext +from pymongo.common import _MAX_END_SESSIONS +from pymongo.errors import ConfigurationError, InvalidOperation, OperationFailure +from pymongo.operations import IndexModel, InsertOne, UpdateOne +from pymongo.read_concern import ReadConcern from test.asynchronous import ( AsyncIntegrationTest, AsyncUnitTest, @@ -40,17 +52,6 @@ async_wait_until, ) -from bson import DBRef -from gridfs.asynchronous.grid_file import AsyncGridFS, AsyncGridFSBucket -from pymongo import ASCENDING, AsyncMongoClient, monitoring -from pymongo.asynchronous.command_cursor import AsyncCommandCursor -from pymongo.asynchronous.cursor import AsyncCursor -from pymongo.asynchronous.helpers import anext -from pymongo.common import _MAX_END_SESSIONS -from pymongo.errors import ConfigurationError, InvalidOperation, OperationFailure -from pymongo.operations import IndexModel, InsertOne, UpdateOne -from pymongo.read_concern import ReadConcern - _IS_SYNC = False @@ -80,7 +81,7 @@ def session_ids(client): class TestSession(AsyncIntegrationTest): client2: AsyncMongoClient - sensitive_commands: Set[str] + sensitive_commands: set[str] @async_client_context.require_sessions async def asyncSetUp(self): @@ -243,7 +244,7 @@ async def test_implicit_sessions_checkout(self): # sessions to be used: connection check in happens before session check in for _ in range(10): cursor = client.db.test.find({}) - ops: List[Tuple[Callable, List[Any]]] = [ + ops: list[tuple[Callable, list[Any]]] = [ (client.db.test.find_one, [{"_id": 1}]), (client.db.test.delete_one, [{}]), (client.db.test.update_one, [{}, {"$set": {"x": 2}}]), diff --git a/test/asynchronous/test_sessions_unified.py b/test/asynchronous/test_sessions_unified.py index ee2b4d418a..ffbb4f6b64 100644 --- a/test/asynchronous/test_sessions_unified.py +++ b/test/asynchronous/test_sessions_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Sessions unified spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_srv_polling.py b/test/asynchronous/test_srv_polling.py index 3d4aed1bc1..520d520c7b 100644 --- a/test/asynchronous/test_srv_polling.py +++ b/test/asynchronous/test_srv_polling.py @@ -13,24 +13,25 @@ # limitations under the License. """Run the SRV support tests.""" + from __future__ import annotations import asyncio import sys import time +from typing import Any + from test.asynchronous.utils import flaky from test.utils_shared import FunctionCallRecorder -from typing import Any sys.path[0:0] = [""] -from test.asynchronous import AsyncPyMongoTestCase, client_knobs, unittest -from test.asynchronous.utils import async_wait_until - import pymongo from pymongo import common from pymongo.asynchronous.srv_resolver import _have_dnspython from pymongo.errors import ConfigurationError +from test.asynchronous import AsyncPyMongoTestCase, client_knobs, unittest +from test.asynchronous.utils import async_wait_until _IS_SYNC = False diff --git a/test/asynchronous/test_ssl.py b/test/asynchronous/test_ssl.py index 7fe57e8503..de4c0f43c5 100644 --- a/test/asynchronous/test_ssl.py +++ b/test/asynchronous/test_ssl.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for SSL support.""" + from __future__ import annotations import os @@ -22,6 +23,13 @@ sys.path[0:0] = [""] +from urllib.parse import quote_plus + +from pymongo import AsyncMongoClient, ssl_support +from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure +from pymongo.hello import HelloCompat +from pymongo.ssl_support import HAVE_PYSSL, HAVE_SSL, _ssl, get_ssl_context +from pymongo.write_concern import WriteConcern from test.asynchronous import ( HAVE_IPADDRESS, AsyncIntegrationTest, @@ -38,13 +46,6 @@ cat_files, ignore_deprecations, ) -from urllib.parse import quote_plus - -from pymongo import AsyncMongoClient, ssl_support -from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure -from pymongo.hello import HelloCompat -from pymongo.ssl_support import HAVE_PYSSL, HAVE_SSL, _ssl, get_ssl_context -from pymongo.write_concern import WriteConcern _HAVE_PYOPENSSL = False try: diff --git a/test/asynchronous/test_streaming_protocol.py b/test/asynchronous/test_streaming_protocol.py index 70ec49de80..f019cf4143 100644 --- a/test/asynchronous/test_streaming_protocol.py +++ b/test/asynchronous/test_streaming_protocol.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the database module.""" + from __future__ import annotations import sys @@ -20,6 +21,8 @@ sys.path[0:0] = [""] +from pymongo import monitoring +from pymongo.hello import HelloCompat from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest from test.utils_shared import ( HeartbeatEventListener, @@ -27,9 +30,6 @@ async_wait_until, ) -from pymongo import monitoring -from pymongo.hello import HelloCompat - _IS_SYNC = False diff --git a/test/asynchronous/test_transactions.py b/test/asynchronous/test_transactions.py index e17bfb14c0..fca9879e39 100644 --- a/test/asynchronous/test_transactions.py +++ b/test/asynchronous/test_transactions.py @@ -13,6 +13,7 @@ # limitations under the License. """Execute Transactions Spec tests.""" + from __future__ import annotations import asyncio @@ -29,12 +30,6 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest -from test.utils_shared import ( - OvertCommandListener, - async_wait_until, -) -from typing import List from bson import encode from bson.raw_bson import RawBSONDocument @@ -57,6 +52,11 @@ from pymongo.operations import IndexModel, InsertOne from pymongo.read_concern import ReadConcern from pymongo.read_preferences import ReadPreference +from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest +from test.utils_shared import ( + OvertCommandListener, + async_wait_until, +) _IS_SYNC = False @@ -320,8 +320,7 @@ async def gridfs_open_upload_stream(*args, **kwargs): async def test_transaction_starts_with_batched_write(self): if "PyPy" in sys.version and async_client_context.tls: self.skipTest( - "PYTHON-2937 PyPy is so slow sending large " - "messages over TLS that this test fails" + "PYTHON-2937 PyPy is so slow sending large messages over TLS that this test fails" ) # Start a transaction with a batch of operations that needs to be # split. @@ -332,7 +331,7 @@ async def test_transaction_starts_with_batched_write(self): listener.reset() self.addAsyncCleanup(coll.drop) large_str = "\0" * (1 * 1024 * 1024) - ops: List[InsertOne[RawBSONDocument]] = [ + ops: list[InsertOne[RawBSONDocument]] = [ InsertOne(RawBSONDocument(encode({"a": large_str}))) for _ in range(48) ] async with client.start_session() as session: diff --git a/test/asynchronous/test_transactions_unified.py b/test/asynchronous/test_transactions_unified.py index 5f9b5d0225..0bf6c31a66 100644 --- a/test/asynchronous/test_transactions_unified.py +++ b/test/asynchronous/test_transactions_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Transactions unified spec tests.""" + from __future__ import annotations import os diff --git a/test/asynchronous/test_unified_format.py b/test/asynchronous/test_unified_format.py index 8136641236..1e0ab21b57 100644 --- a/test/asynchronous/test_unified_format.py +++ b/test/asynchronous/test_unified_format.py @@ -20,6 +20,7 @@ sys.path[0:0] = [""] +from bson import ObjectId from test import UnitTest, unittest from test.asynchronous.unified_format import ( MatchEvaluatorUtil, @@ -27,8 +28,6 @@ get_test_path, ) -from bson import ObjectId - _IS_SYNC = False # Location of JSON test specifications. diff --git a/test/asynchronous/test_versioned_api_integration.py b/test/asynchronous/test_versioned_api_integration.py index 7228b945ab..3e2bbf5f1a 100644 --- a/test/asynchronous/test_versioned_api_integration.py +++ b/test/asynchronous/test_versioned_api_integration.py @@ -16,15 +16,15 @@ import os import sys from pathlib import Path + from test.asynchronous.unified_format import generate_test_classes, get_test_path sys.path[0:0] = [""] +from pymongo.server_api import ServerApi from test.asynchronous import AsyncIntegrationTest, async_client_context, unittest from test.utils_shared import OvertCommandListener -from pymongo.server_api import ServerApi - _IS_SYNC = False # Generate unified tests. diff --git a/test/asynchronous/unified_format.py b/test/asynchronous/unified_format.py index f63f716726..9e5b7b09a7 100644 --- a/test/asynchronous/unified_format.py +++ b/test/asynchronous/unified_format.py @@ -16,6 +16,7 @@ https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md """ + from __future__ import annotations import asyncio @@ -28,40 +29,10 @@ import time import traceback from collections import defaultdict +from collections.abc import Mapping from inspect import iscoroutinefunction from pathlib import Path -from test.asynchronous import ( - AsyncIntegrationTest, - async_client_context, - client_knobs, - unittest, -) -from test.asynchronous.utils import async_get_pool, flaky -from test.asynchronous.utils_spec_runner import SpecRunnerTask -from test.helpers_shared import ALL_KMS_PROVIDERS, DEFAULT_KMS_TLS -from test.unified_format_shared import ( - KMS_TLS_OPTS, - PLACEHOLDER_MAP, - EventListenerUtil, - MatchEvaluatorUtil, - coerce_result, - parse_bulk_write_error_result, - parse_bulk_write_result, - parse_client_bulk_write_error_result, - parse_collection_or_database_options, - with_metaclass, -) -from test.utils_shared import ( - async_wait_until, - camel_to_snake, - camel_to_snake_args, - parse_spec_options, - prepare_spec_arguments, - snake_to_camel, - wait_until, -) -from test.version import Version -from typing import Any, Dict, List, Mapping, Optional +from typing import Any, Optional import pytest @@ -107,6 +78,37 @@ from pymongo.topology_description import TopologyDescription from pymongo.typings import _Address from pymongo.write_concern import WriteConcern +from test.asynchronous import ( + AsyncIntegrationTest, + async_client_context, + client_knobs, + unittest, +) +from test.asynchronous.utils import async_get_pool, flaky +from test.asynchronous.utils_spec_runner import SpecRunnerTask +from test.helpers_shared import ALL_KMS_PROVIDERS, DEFAULT_KMS_TLS +from test.unified_format_shared import ( + KMS_TLS_OPTS, + PLACEHOLDER_MAP, + EventListenerUtil, + MatchEvaluatorUtil, + coerce_result, + parse_bulk_write_error_result, + parse_bulk_write_result, + parse_client_bulk_write_error_result, + parse_collection_or_database_options, + with_metaclass, +) +from test.utils_shared import ( + async_wait_until, + camel_to_snake, + camel_to_snake_args, + parse_spec_options, + prepare_spec_arguments, + snake_to_camel, + wait_until, +) +from test.version import Version _IS_SYNC = False @@ -224,9 +226,9 @@ class EntityMapUtil: """ def __init__(self, test_class): - self._entities: Dict[str, Any] = {} - self._listeners: Dict[str, EventListenerUtil] = {} - self._session_lsids: Dict[str, Mapping[str, Any]] = {} + self._entities: dict[str, Any] = {} + self._listeners: dict[str, EventListenerUtil] = {} + self._session_lsids: dict[str, Mapping[str, Any]] = {} self.test: UnifiedSpecTestMixinV1 = test_class def __contains__(self, item): @@ -618,10 +620,8 @@ def maybe_skip_test(self, spec): self.skipTest("CSOT not implemented for watch()") if "cursors" in class_name: self.skipTest("CSOT not implemented for cursors") - if ( - "tailable" in class_name - or "tailable" in description - and "non-tailable" not in description + if "tailable" in class_name or ( + "tailable" in description and "non-tailable" not in description ): self.skipTest("CSOT not implemented for tailable cursors") if "sessions" in class_name: @@ -648,7 +648,7 @@ def maybe_skip_test(self, spec): def process_error(self, exception, spec): if isinstance(exception, unittest.SkipTest): - raise + raise exception is_error = spec.get("isError") is_client_error = spec.get("isClientError") is_timeout_error = spec.get("isTimeoutError") @@ -998,7 +998,7 @@ async def _bucketOperation_uploadWithId( async def _bucketOperation_find( self, target: AsyncGridFSBucket, *args: Any, **kwargs: Any - ) -> List[GridOut]: + ) -> list[GridOut]: return await target.find(*args, **kwargs).to_list() async def run_entity_operation(self, spec): @@ -1114,7 +1114,7 @@ async def _testOperation_targetedFailPoint(self, spec): session = self.entity_map[spec["session"]] if not session._pinned_address: self.fail( - "Cannot use targetedFailPoint operation with unpinned " "session {}".format( + "Cannot use targetedFailPoint operation with unpinned session {}".format( spec["session"] ) ) @@ -1147,8 +1147,7 @@ def __get_last_two_command_lsids(self, listener): cmd_started_events.append(event) if len(cmd_started_events) < 2: self.fail( - "Needed 2 CommandStartedEvents to compare lsids, " - "got %s" % (len(cmd_started_events)) + "Needed 2 CommandStartedEvents to compare lsids, got %s" % (len(cmd_started_events)) ) return tuple([e.command["lsid"] for e in cmd_started_events][:2]) diff --git a/test/asynchronous/utils.py b/test/asynchronous/utils.py index 9021b06082..5842224220 100644 --- a/test/asynchronous/utils.py +++ b/test/asynchronous/utils.py @@ -13,6 +13,7 @@ # limitations under the License. """Utilities for testing pymongo that require synchronization.""" + from __future__ import annotations import asyncio @@ -204,7 +205,7 @@ async def wrapper(*args, **kwargs): if i == max_runs - 1: raise e print( - f"Retrying after attempt {i+1} of {func_name or target_func.__name__} failed with ({reason})):\n" + f"Retrying after attempt {i + 1} of {func_name or target_func.__name__} failed with ({reason})):\n" f"{traceback.format_exc()}", file=sys.stderr, ) diff --git a/test/asynchronous/utils_selection_tests.py b/test/asynchronous/utils_selection_tests.py index 9d4e6a3894..eec6dc9719 100644 --- a/test/asynchronous/utils_selection_tests.py +++ b/test/asynchronous/utils_selection_tests.py @@ -13,16 +13,25 @@ # limitations under the License. """Utilities for testing Server Selection and Max Staleness.""" + from __future__ import annotations import datetime import os import sys + from test.asynchronous import AsyncPyMongoTestCase from test.asynchronous.utils import AsyncMockPool sys.path[0:0] = [""] +from bson import json_util +from pymongo.asynchronous.settings import TopologySettings +from pymongo.asynchronous.topology import Topology +from pymongo.common import HEARTBEAT_FREQUENCY, clean_node +from pymongo.errors import AutoReconnect, ConfigurationError +from pymongo.operations import _Op +from pymongo.server_selectors import writable_server_selector from test import unittest from test.pymongo_mocks import DummyMonitor from test.utils_selection_tests_shared import ( @@ -32,14 +41,6 @@ ) from test.utils_shared import parse_read_preference -from bson import json_util -from pymongo.asynchronous.settings import TopologySettings -from pymongo.asynchronous.topology import Topology -from pymongo.common import HEARTBEAT_FREQUENCY, clean_node -from pymongo.errors import AutoReconnect, ConfigurationError -from pymongo.operations import _Op -from pymongo.server_selectors import writable_server_selector - _IS_SYNC = False diff --git a/test/asynchronous/utils_spec_runner.py b/test/asynchronous/utils_spec_runner.py index ff5f61db06..2d573065d6 100644 --- a/test/asynchronous/utils_spec_runner.py +++ b/test/asynchronous/utils_spec_runner.py @@ -13,16 +13,17 @@ # limitations under the License. """Utilities for testing driver specs.""" + from __future__ import annotations import asyncio import os -from test.asynchronous import async_client_context -from test.asynchronous.helpers import ConcurrentRunner -from test.utils_shared import ScenarioDict from bson import json_util from pymongo.lock import _async_cond_wait, _async_create_condition, _async_create_lock +from test.asynchronous import async_client_context +from test.asynchronous.helpers import ConcurrentRunner +from test.utils_shared import ScenarioDict _IS_SYNC = False @@ -159,7 +160,7 @@ async def _create_tests(self): dirname = os.path.split(dirpath)[-1] for filename in filenames: - with open(os.path.join(dirpath, filename)) as scenario_stream: # noqa: ASYNC101, RUF100 + with open(os.path.join(dirpath, filename)) as scenario_stream: # noqa: ASYNC230 # Use tz_aware=False to match how CodecOptions decodes # dates. opts = json_util.JSONOptions(tz_aware=False) diff --git a/test/atlas/test_connection.py b/test/atlas/test_connection.py index ac217ab40d..32610b05c1 100644 --- a/test/atlas/test_connection.py +++ b/test/atlas/test_connection.py @@ -13,16 +13,18 @@ # limitations under the License. """Test connections to various Atlas cluster types.""" + from __future__ import annotations import os import sys import unittest from collections import defaultdict -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + sys.path[0:0] = [""] import pymongo diff --git a/test/auth_aws/test_auth_aws.py b/test/auth_aws/test_auth_aws.py index 9738694d85..4fb6fcdc37 100644 --- a/test/auth_aws/test_auth_aws.py +++ b/test/auth_aws/test_auth_aws.py @@ -13,16 +13,18 @@ # limitations under the License. """Test MONGODB-AWS Authentication.""" + from __future__ import annotations import os import sys import unittest -from test import PyMongoTestCase from unittest.mock import patch import pytest +from test import PyMongoTestCase + sys.path[0:0] = [""] try: diff --git a/test/conftest.py b/test/conftest.py index eb12d9f928..281e7beabd 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -2,10 +2,11 @@ import asyncio import sys -from test import pytest_conf, setup, teardown import pytest +from test import pytest_conf, setup, teardown + _IS_SYNC = True # The default asyncio loop implementation on Windows has issues with sharing diff --git a/test/helpers.py b/test/helpers.py index 163bf01c12..5b9afa192e 100644 --- a/test/helpers.py +++ b/test/helpers.py @@ -13,6 +13,7 @@ # limitations under the License. """Shared helper methods for pymongo, bson, and gridfs test suites.""" + from __future__ import annotations import asyncio @@ -108,15 +109,9 @@ def wrap(*args, **kwargs): def __del__(self): if self._enabled: msg = ( - "ERROR: client_knobs still enabled! HEARTBEAT_FREQUENCY={}, " - "MIN_HEARTBEAT_INTERVAL={}, KILL_CURSOR_FREQUENCY={}, " - "EVENTS_QUEUE_FREQUENCY={}, stack:\n{}".format( - common.HEARTBEAT_FREQUENCY, - common.MIN_HEARTBEAT_INTERVAL, - common.KILL_CURSOR_FREQUENCY, - common.EVENTS_QUEUE_FREQUENCY, - self._stack, - ) + f"ERROR: client_knobs still enabled! HEARTBEAT_FREQUENCY={common.HEARTBEAT_FREQUENCY}, " + f"MIN_HEARTBEAT_INTERVAL={common.MIN_HEARTBEAT_INTERVAL}, KILL_CURSOR_FREQUENCY={common.KILL_CURSOR_FREQUENCY}, " + f"EVENTS_QUEUE_FREQUENCY={common.EVENTS_QUEUE_FREQUENCY}, stack:\n{self._stack}" ) self.disable() raise Exception(msg) diff --git a/test/lambda/mongodb/app.py b/test/lambda/mongodb/app.py index 274990d3bc..ca74bd2c3c 100644 --- a/test/lambda/mongodb/app.py +++ b/test/lambda/mongodb/app.py @@ -4,6 +4,7 @@ Creates the client that is cached for all requests, subscribes to relevant events, and forces the connection pool to get populated. """ + from __future__ import annotations import json @@ -161,8 +162,8 @@ def lambda_handler(event, context): response = json.dumps(create_response()) reset() print("finished!") - assert ( - streaming_heartbeat_count == 0 - ), f"streaming_heartbeat_count was {streaming_heartbeat_count} not 0" + assert streaming_heartbeat_count == 0, ( + f"streaming_heartbeat_count was {streaming_heartbeat_count} not 0" + ) return dict(statusCode=200, body=response) diff --git a/test/mockupdb/test_auth_recovering_member.py b/test/mockupdb/test_auth_recovering_member.py index 046d8d4b0f..fa5779dda0 100644 --- a/test/mockupdb/test_auth_recovering_member.py +++ b/test/mockupdb/test_auth_recovering_member.py @@ -14,10 +14,11 @@ from __future__ import annotations import unittest -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB diff --git a/test/mockupdb/test_cluster_time.py b/test/mockupdb/test_cluster_time.py index 42ca916971..bdd5f8f48e 100644 --- a/test/mockupdb/test_cluster_time.py +++ b/test/mockupdb/test_cluster_time.py @@ -13,13 +13,15 @@ # limitations under the License. """Test $clusterTime handling.""" + from __future__ import annotations import unittest -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, going diff --git a/test/mockupdb/test_cursor.py b/test/mockupdb/test_cursor.py index e61f220d5b..09ded813ac 100644 --- a/test/mockupdb/test_cursor.py +++ b/test/mockupdb/test_cursor.py @@ -13,13 +13,15 @@ # limitations under the License. """Test PyMongo cursor does not set exhaustAllowed automatically (PYTHON-4007).""" + from __future__ import annotations import unittest -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, OpMsg, going diff --git a/test/mockupdb/test_cursor_namespace.py b/test/mockupdb/test_cursor_namespace.py index 7538540bda..f77f4a5c2c 100644 --- a/test/mockupdb/test_cursor_namespace.py +++ b/test/mockupdb/test_cursor_namespace.py @@ -13,13 +13,15 @@ # limitations under the License. """Test list_indexes with more than one batch.""" + from __future__ import annotations import unittest -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, going diff --git a/test/mockupdb/test_getmore_sharded.py b/test/mockupdb/test_getmore_sharded.py index d24c8aa10a..564ff4ba23 100644 --- a/test/mockupdb/test_getmore_sharded.py +++ b/test/mockupdb/test_getmore_sharded.py @@ -13,14 +13,16 @@ # limitations under the License. """Test PyMongo cursor with a sharded cluster.""" + from __future__ import annotations import unittest from queue import Queue -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, going diff --git a/test/mockupdb/test_initial_ismaster.py b/test/mockupdb/test_initial_ismaster.py index a52930c742..355e917fd9 100644 --- a/test/mockupdb/test_initial_ismaster.py +++ b/test/mockupdb/test_initial_ismaster.py @@ -15,10 +15,11 @@ import time import unittest -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, wait_until diff --git a/test/mockupdb/test_list_indexes.py b/test/mockupdb/test_list_indexes.py index 71cad43aa2..24b3d65ae4 100644 --- a/test/mockupdb/test_list_indexes.py +++ b/test/mockupdb/test_list_indexes.py @@ -13,13 +13,15 @@ # limitations under the License. """Test list_indexes with more than one batch.""" + from __future__ import annotations import unittest -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, going diff --git a/test/mockupdb/test_max_staleness.py b/test/mockupdb/test_max_staleness.py index 7168bd2954..4bb6a4817c 100644 --- a/test/mockupdb/test_max_staleness.py +++ b/test/mockupdb/test_max_staleness.py @@ -14,10 +14,11 @@ from __future__ import annotations import unittest -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, going @@ -57,10 +58,7 @@ def test_mongos(self): # Set maxStalenessSeconds to 1. Client has no minimum with mongos, # we let mongos enforce the 90-second minimum and return an error: # SERVER-27146. - uri = ( - "mongodb://localhost:%d/?readPreference=secondary" - "&maxStalenessSeconds=1" % mongos.port - ) + uri = "mongodb://localhost:%d/?readPreference=secondary&maxStalenessSeconds=1" % mongos.port client = self.simple_client(uri) with going(client.db.coll.find_one) as future: diff --git a/test/mockupdb/test_mixed_version_sharded.py b/test/mockupdb/test_mixed_version_sharded.py index adbe61204b..a3e587bab2 100644 --- a/test/mockupdb/test_mixed_version_sharded.py +++ b/test/mockupdb/test_mixed_version_sharded.py @@ -13,15 +13,17 @@ # limitations under the License. """Test PyMongo with a mixed-version cluster.""" + from __future__ import annotations import time import unittest from queue import Queue -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, go @@ -56,9 +58,8 @@ def setup_server(self, upgrade): "ismaster", ismaster=True, msg="isdbgrid", maxWireVersion=upgrade.wire_version ) - self.mongoses_uri = "mongodb://{},{}".format( - self.mongos_old.address_string, - self.mongos_new.address_string, + self.mongoses_uri = ( + f"mongodb://{self.mongos_old.address_string},{self.mongos_new.address_string}" ) self.client = self.simple_client(self.mongoses_uri) diff --git a/test/mockupdb/test_op_msg.py b/test/mockupdb/test_op_msg.py index 4b85c5a48a..a509d13182 100644 --- a/test/mockupdb/test_op_msg.py +++ b/test/mockupdb/test_op_msg.py @@ -15,10 +15,11 @@ import unittest from collections import namedtuple -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import OP_MSG_FLAGS, MockupDB, OpMsg, OpMsgReply, going diff --git a/test/mockupdb/test_op_msg_read_preference.py b/test/mockupdb/test_op_msg_read_preference.py index 4cf82c760e..e2d7942f08 100644 --- a/test/mockupdb/test_op_msg_read_preference.py +++ b/test/mockupdb/test_op_msg_read_preference.py @@ -16,11 +16,12 @@ import copy import itertools import unittest -from test import PyMongoTestCase from typing import Any import pytest +from test import PyMongoTestCase + try: from mockupdb import CommandBase, MockupDB, going diff --git a/test/mockupdb/test_query_read_pref_sharded.py b/test/mockupdb/test_query_read_pref_sharded.py index 2fae46be76..ccfce28e18 100644 --- a/test/mockupdb/test_query_read_pref_sharded.py +++ b/test/mockupdb/test_query_read_pref_sharded.py @@ -13,13 +13,15 @@ # limitations under the License. """Test PyMongo query and read preference with a sharded cluster.""" + from __future__ import annotations import unittest -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, OpMsg, going diff --git a/test/mockupdb/test_reset_and_request_check.py b/test/mockupdb/test_reset_and_request_check.py index b438afe894..4eb78f62e1 100644 --- a/test/mockupdb/test_reset_and_request_check.py +++ b/test/mockupdb/test_reset_and_request_check.py @@ -16,10 +16,11 @@ import itertools import time import unittest -from test import PyMongoTestCase import pytest +from test import PyMongoTestCase + try: from mockupdb import MockupDB, going, wait_until diff --git a/test/mockupdb/test_rsghost.py b/test/mockupdb/test_rsghost.py index 84d52170db..1bcf5c4951 100644 --- a/test/mockupdb/test_rsghost.py +++ b/test/mockupdb/test_rsghost.py @@ -13,6 +13,7 @@ # limitations under the License. """Test connections to RSGhost nodes.""" + from __future__ import annotations import datetime diff --git a/test/mockupdb/test_slave_okay_rs.py b/test/mockupdb/test_slave_okay_rs.py index 728e4e2ce0..bd2d377a23 100644 --- a/test/mockupdb/test_slave_okay_rs.py +++ b/test/mockupdb/test_slave_okay_rs.py @@ -16,6 +16,7 @@ Just make sure SlaveOkay is *not* set on primary reads. """ + from __future__ import annotations import unittest diff --git a/test/mockupdb/test_slave_okay_sharded.py b/test/mockupdb/test_slave_okay_sharded.py index 6efbff6583..e83c715a4a 100644 --- a/test/mockupdb/test_slave_okay_sharded.py +++ b/test/mockupdb/test_slave_okay_sharded.py @@ -18,6 +18,7 @@ - A direct connection to a slave. - A direct connection to a mongos. """ + from __future__ import annotations import itertools diff --git a/test/mockupdb/test_slave_okay_single.py b/test/mockupdb/test_slave_okay_single.py index 88cb1a48a5..2830603b60 100644 --- a/test/mockupdb/test_slave_okay_single.py +++ b/test/mockupdb/test_slave_okay_single.py @@ -18,6 +18,7 @@ - A direct connection to a slave. - A direct connection to a mongos. """ + from __future__ import annotations import itertools diff --git a/test/mockupdb/test_standalone_shard.py b/test/mockupdb/test_standalone_shard.py index 28a582071c..c290d63bce 100644 --- a/test/mockupdb/test_standalone_shard.py +++ b/test/mockupdb/test_standalone_shard.py @@ -13,6 +13,7 @@ # limitations under the License. """Test errors that come from a standalone shard.""" + from __future__ import annotations import unittest diff --git a/test/mod_wsgi_test/mod_wsgi_test.py b/test/mod_wsgi_test/mod_wsgi_test.py index 4ab2df2442..3aaf0da264 100644 --- a/test/mod_wsgi_test/mod_wsgi_test.py +++ b/test/mod_wsgi_test/mod_wsgi_test.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Minimal test of PyMongo in a WSGI application, see bug PYTHON-353 -""" +"""Minimal test of PyMongo in a WSGI application, see bug PYTHON-353""" + from __future__ import annotations import datetime @@ -97,16 +97,16 @@ def application(environ, start_response): if isinstance(value, re.Pattern): value = Regex.from_native(value) assert decoded[key] == value, f"failed on doc[{key!r}]: {decoded[key]!r} != {value!r}" - assert isinstance( - decoded[key], type(value) - ), f"failed on doc[{key}]: {decoded[key]!r} is not an instance of {type(value)}" + assert isinstance(decoded[key], type(value)), ( + f"failed on doc[{key}]: {decoded[key]!r} is not an instance of {type(value)}" + ) output = ( f" python {sys.version}, mod_wsgi {mod_wsgi_version}," f" pymongo {pymongo.version}," - f' mod_wsgi.process_group = {environ["mod_wsgi.process_group"]!r}' - f' mod_wsgi.application_group = {environ["mod_wsgi.application_group"]!r}' - f' wsgi.multithread = {environ["wsgi.multithread"]!r}' + f" mod_wsgi.process_group = {environ['mod_wsgi.process_group']!r}" + f" mod_wsgi.application_group = {environ['mod_wsgi.application_group']!r}" + f" wsgi.multithread = {environ['wsgi.multithread']!r}" "\n" ) response_headers = [("Content-Length", str(len(output)))] diff --git a/test/mod_wsgi_test/test_client.py b/test/mod_wsgi_test/test_client.py index c122863bfa..96c9c99a38 100644 --- a/test/mod_wsgi_test/test_client.py +++ b/test/mod_wsgi_test/test_client.py @@ -13,6 +13,7 @@ # limitations under the License. """Test client for mod_wsgi application, see bug PYTHON-353.""" + from __future__ import annotations import _thread as thread @@ -131,12 +132,8 @@ def main(options, mode, urls): if options.verbose: print( - "Getting {} {} times total in {} threads, " "{} times per thread".format( - urls, - nrequests_per_thread * options.nthreads, - options.nthreads, - nrequests_per_thread, - ) + f"Getting {urls} {nrequests_per_thread * options.nthreads} times total in {options.nthreads} threads, " + f"{nrequests_per_thread} times per thread" ) threads = [ URLGetterThread(options, urls, nrequests_per_thread) for _ in range(options.nthreads) diff --git a/test/mypy_fails/raw_bson_document.py b/test/mypy_fails/raw_bson_document.py index 49f3659e90..23424ea89f 100644 --- a/test/mypy_fails/raw_bson_document.py +++ b/test/mypy_fails/raw_bson_document.py @@ -10,6 +10,6 @@ retrieved = coll.find_one({"_id": doc["_id"]}) assert retrieved is not None assert len(retrieved.raw) > 0 -retrieved[ - "foo" -] = "bar" # error: Unsupported target for indexed assignment ("RawBSONDocument") [index] +retrieved["foo"] = ( + "bar" # error: Unsupported target for indexed assignment ("RawBSONDocument") [index] +) diff --git a/test/performance/async_perf_test.py b/test/performance/async_perf_test.py index 6eb31ea4fe..e4a68e228e 100644 --- a/test/performance/async_perf_test.py +++ b/test/performance/async_perf_test.py @@ -37,6 +37,7 @@ FASTBENCH=1 python test/performance/async_perf_test.py -v TestRunCommand TestFindManyAndEmptyCursor """ + from __future__ import annotations import asyncio @@ -45,7 +46,7 @@ import tempfile import time import warnings -from typing import Any, List, Optional, Union +from typing import Any, Optional, Union import pytest @@ -56,8 +57,6 @@ sys.path[0:0] = [""] -from test.asynchronous import AsyncPyMongoTestCase, async_client_context, unittest - from bson import encode from gridfs import AsyncGridFSBucket from pymongo import ( @@ -65,6 +64,7 @@ InsertOne, ReplaceOne, ) +from test.asynchronous import AsyncPyMongoTestCase, async_client_context, unittest pytestmark = pytest.mark.perf @@ -87,7 +87,7 @@ OUTPUT_FILE = os.environ.get("OUTPUT_FILE") -result_data: List = [] +result_data: list = [] def tearDownModule(): @@ -239,7 +239,7 @@ class TestDocument(PerformanceTest): async def asyncSetUp(self): await super().asyncSetUp() # Location of test data. - with open( # noqa: ASYNC101 + with open( # noqa: ASYNC230 os.path.join(TEST_PATH, os.path.join("single_and_multi_document", self.dataset)) ) as data: self.document = json.loads(data.read()) @@ -464,7 +464,7 @@ async def asyncSetUp(self): gridfs_path = os.path.join( TEST_PATH, os.path.join("single_and_multi_document", "gridfs_large.bin") ) - with open(gridfs_path, "rb") as data: # noqa: ASYNC101 + with open(gridfs_path, "rb") as data: # noqa: ASYNC230 self.document = data.read() self.data_size = len(self.document) self.bucket = AsyncGridFSBucket(self.client.perftest) diff --git a/test/performance/perf_test.py b/test/performance/perf_test.py index 5688d28d2d..5ec13a2b56 100644 --- a/test/performance/perf_test.py +++ b/test/performance/perf_test.py @@ -37,6 +37,7 @@ FASTBENCH=1 python test/performance/perf_test.py -v TestRunCommand TestFindManyAndEmptyCursor """ + from __future__ import annotations import multiprocessing as mp @@ -46,7 +47,7 @@ import threading import time import warnings -from typing import Any, List, Optional, Union +from typing import Any, Optional, Union import pytest @@ -57,8 +58,6 @@ sys.path[0:0] = [""] -from test import client_context, unittest - from bson import decode, encode, json_util from gridfs import GridFSBucket from pymongo import ( @@ -67,6 +66,7 @@ MongoClient, ReplaceOne, ) +from test import client_context, unittest pytestmark = pytest.mark.perf @@ -89,7 +89,7 @@ OUTPUT_FILE = os.environ.get("OUTPUT_FILE") -result_data: List = [] +result_data: list = [] def tearDownModule(): diff --git a/test/pymongo_mocks.py b/test/pymongo_mocks.py index 7662dc9682..206c1a61b7 100644 --- a/test/pymongo_mocks.py +++ b/test/pymongo_mocks.py @@ -13,12 +13,12 @@ # limitations under the License. """Tools for mocking parts of PyMongo to test other parts.""" + from __future__ import annotations import contextlib import weakref from functools import partial -from test import client_context from pymongo import MongoClient, common from pymongo.errors import AutoReconnect, NetworkTimeout @@ -26,6 +26,7 @@ from pymongo.server_description import ServerDescription from pymongo.synchronous.monitor import Monitor from pymongo.synchronous.pool import Pool +from test import client_context _IS_SYNC = True diff --git a/test/qcheck.py b/test/qcheck.py index 842580cbff..cf18cbda4c 100644 --- a/test/qcheck.py +++ b/test/qcheck.py @@ -182,35 +182,35 @@ def simplify(case): # TODO this is a hack if random.choice([True, False]): # delete simplified_keys = list(simplified) - if not len(simplified_keys): + if not simplified_keys: return (False, case) simplified.pop(random.choice(simplified_keys)) return (True, simplified) else: # simplify a value simplified_items = list(simplified.items()) - if not len(simplified_items): + if not simplified_items: return (False, case) (key, value) = random.choice(simplified_items) (success, value) = simplify(value) simplified[key] = value - return (success, success and simplified or case) + return (success, (success and simplified) or case) if isinstance(case, list): simplified = list(case) if random.choice([True, False]): # delete - if not len(simplified): + if not simplified: return (False, case) simplified.pop(random.randrange(len(simplified))) return (True, simplified) else: # simplify an item - if not len(simplified): + if not simplified: return (False, case) index = random.randrange(len(simplified)) (success, value) = simplify(simplified[index]) simplified[index] = value - return (success, success and simplified or case) + return (success, (success and simplified) or case) return (False, case) diff --git a/test/sigstop_sigcont.py b/test/sigstop_sigcont.py index bc1bacce33..1d0073a482 100644 --- a/test/sigstop_sigcont.py +++ b/test/sigstop_sigcont.py @@ -13,6 +13,7 @@ # limitations under the License. """Used by test_client.TestClient.test_sigstop_sigcont.""" + from __future__ import annotations import logging diff --git a/test/test_auth.py b/test/test_auth.py index 27f6743fae..884f888fbf 100644 --- a/test/test_auth.py +++ b/test/test_auth.py @@ -13,6 +13,7 @@ # limitations under the License. """Authentication Tests.""" + from __future__ import annotations import asyncio @@ -23,15 +24,6 @@ sys.path[0:0] = [""] -from test import ( - IntegrationTest, - PyMongoTestCase, - SkipTest, - client_context, - unittest, -) -from test.utils_shared import AllowListEventListener, delay, ignore_deprecations - import pytest from pymongo import MongoClient, monitoring @@ -41,6 +33,14 @@ from pymongo.read_preferences import ReadPreference from pymongo.saslprep import HAVE_STRINGPREP from pymongo.synchronous.auth import HAVE_KERBEROS, _canonicalize_hostname +from test import ( + IntegrationTest, + PyMongoTestCase, + SkipTest, + client_context, + unittest, +) +from test.utils_shared import AllowListEventListener, delay, ignore_deprecations _IS_SYNC = True @@ -596,13 +596,13 @@ def test_scram_saslprep(self): client.testscram.command("dbstats") client = self.rs_or_single_client_noauth( - username="IX", password="I\u00ADX", authSource="testscram" + username="IX", password="I\u00adX", authSource="testscram" ) client.testscram.command("dbstats") client = self.rs_or_single_client_noauth( username="IX", - password="I\u00ADX", + password="I\u00adX", authSource="testscram", authMechanism="SCRAM-SHA-256", ) @@ -623,7 +623,7 @@ def test_scram_saslprep(self): client.testscram.command("dbstats") client = self.rs_or_single_client_noauth( - "mongodb://IX:I\u00ADX@%s:%d/testscram" % (host, port) + "mongodb://IX:I\u00adX@%s:%d/testscram" % (host, port) ) client.testscram.command("dbstats") client = self.rs_or_single_client_noauth("mongodb://IX:IX@%s:%d/testscram" % (host, port)) diff --git a/test/test_auth_oidc.py b/test/test_auth_oidc.py index e88e067b2c..30ac60ec6c 100644 --- a/test/test_auth_oidc.py +++ b/test/test_auth_oidc.py @@ -13,6 +13,7 @@ # limitations under the License. """Test MONGODB-OIDC Authentication.""" + from __future__ import annotations import os @@ -22,16 +23,13 @@ import warnings from contextlib import contextmanager from pathlib import Path -from test import PyMongoTestCase -from test.helpers import ConcurrentRunner -from typing import Dict import pytest -sys.path[0:0] = [""] +from test import PyMongoTestCase +from test.helpers import ConcurrentRunner -from test.unified_format import generate_test_classes, get_test_path -from test.utils_shared import EventListener, OvertCommandListener +sys.path[0:0] = [""] from bson import SON from pymongo import MongoClient @@ -50,6 +48,8 @@ _get_authenticator, ) from pymongo.synchronous.uri_parser import parse_uri +from test.unified_format import generate_test_classes, get_test_path +from test.utils_shared import EventListener, OvertCommandListener _IS_SYNC = True @@ -87,7 +87,7 @@ def get_token(self, username=None): token_file = TOKEN_FILE else: token_file = os.path.join(TOKEN_DIR, username) - with open(token_file) as fid: # noqa: ASYNC101,RUF100 + with open(token_file) as fid: # noqa:RUF100 return fid.read() elif ENVIRON == "azure": opts = parse_uri(self.uri_single)["options"] @@ -166,7 +166,7 @@ def create_client(self, *args, **kwargs): request_cb = kwargs.pop("request_cb", self.create_request_cb(username=username)) props = kwargs.pop("authmechanismproperties", {"OIDC_HUMAN_CALLBACK": request_cb}) kwargs["retryReads"] = False - if not len(args): + if not args: args = [self.uri_single] client = self.simple_client(*args, authmechanismproperties=props, **kwargs) @@ -224,7 +224,7 @@ def test_1_5_multiple_principal_no_user(self): def test_1_6_allowed_hosts_blocked(self): # Create a default OIDC client, with an ALLOWED_HOSTS that is an empty list. request_token = self.create_request_cb() - props: Dict = {"OIDC_HUMAN_CALLBACK": request_token, "ALLOWED_HOSTS": []} + props: dict = {"OIDC_HUMAN_CALLBACK": request_token, "ALLOWED_HOSTS": []} client = self.create_client(authmechanismproperties=props) # Assert that a find operation fails with a client-side error. with self.assertRaises(ConfigurationError): @@ -234,7 +234,7 @@ def test_1_6_allowed_hosts_blocked(self): # Create a client that uses the URL mongodb://localhost/?authMechanism=MONGODB-OIDC&ignored=example.com, # a human callback, and an ALLOWED_HOSTS that contains ["example.com"]. - props: Dict = { + props: dict = { "OIDC_HUMAN_CALLBACK": request_token, "ALLOWED_HOSTS": ["example.com"], } @@ -776,7 +776,7 @@ def create_client(self, *args, **kwargs): request_cb = kwargs.pop("request_cb", self.create_request_cb()) props = kwargs.pop("authmechanismproperties", {"OIDC_CALLBACK": request_cb}) kwargs["retryReads"] = False - if not len(args): + if not args: args = [self.uri_single] client = MongoClient(*args, authmechanismproperties=props, **kwargs) self.addCleanup(client.close) @@ -848,20 +848,20 @@ def fetch(self, a): def test_2_4_invalid_client_configuration_with_callback(self): # Create a MongoClient configured with an OIDC callback and auth mechanism property ENVIRONMENT:test. request_cb = self.create_request_cb() - props: Dict = {"OIDC_CALLBACK": request_cb, "ENVIRONMENT": "test"} + props: dict = {"OIDC_CALLBACK": request_cb, "ENVIRONMENT": "test"} # Assert it returns a client configuration error. with self.assertRaises(ConfigurationError): self.create_client(authmechanismproperties=props) def test_2_5_invalid_use_of_ALLOWED_HOSTS(self): # Create an OIDC configured client with auth mechanism properties `{"ENVIRONMENT": "test", "ALLOWED_HOSTS": []}`. - props: Dict = {"ENVIRONMENT": "test", "ALLOWED_HOSTS": []} + props: dict = {"ENVIRONMENT": "test", "ALLOWED_HOSTS": []} # Assert it returns a client configuration error. with self.assertRaises(ConfigurationError): self.create_client(authmechanismproperties=props) # Create an OIDC configured client with auth mechanism properties `{"OIDC_CALLBACK": "", "ALLOWED_HOSTS": []}`. - props: Dict = {"OIDC_CALLBACK": self.create_request_cb(), "ALLOWED_HOSTS": []} + props: dict = {"OIDC_CALLBACK": self.create_request_cb(), "ALLOWED_HOSTS": []} # Assert it returns a client configuration error. with self.assertRaises(ConfigurationError): self.create_client(authmechanismproperties=props) diff --git a/test/test_auth_spec.py b/test/test_auth_spec.py index 93c5e7666d..974a9df1de 100644 --- a/test/test_auth_spec.py +++ b/test/test_auth_spec.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the auth spec tests.""" + from __future__ import annotations import glob @@ -20,17 +21,18 @@ import os import sys import warnings -from test import PyMongoTestCase import pytest -sys.path[0:0] = [""] +from pymongo.errors import ConfigurationError +from test import PyMongoTestCase -from test import unittest -from test.unified_format import generate_test_classes, get_test_path +sys.path[0:0] = [""] from pymongo import MongoClient from pymongo.auth_oidc_shared import OIDCCallback +from test import unittest +from test.unified_format import generate_test_classes, get_test_path pytestmark = pytest.mark.auth @@ -56,7 +58,7 @@ def run_test(self): if not valid: with warnings.catch_warnings(): warnings.simplefilter("default") - self.assertRaises(Exception, MongoClient, uri, connect=False) + self.assertRaises(ConfigurationError, MongoClient, uri, connect=False) else: client = self.simple_client(uri, connect=False) credentials = client.options.pool_options._credentials diff --git a/test/test_binary.py b/test/test_binary.py index a64aa42280..b46f4f7395 100644 --- a/test/test_binary.py +++ b/test/test_binary.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for the Binary wrapper.""" + from __future__ import annotations import array @@ -26,8 +27,6 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest - import bson from bson import decode, encode from bson.binary import * @@ -35,6 +34,7 @@ from bson.son import SON from pymongo.common import validate_uuid_representation from pymongo.write_concern import WriteConcern +from test import IntegrationTest, client_context, unittest class BinaryData: @@ -123,10 +123,10 @@ def test_repr(self): self.assertEqual(repr(one), "Binary({}, 0)".format(repr(b"hello world"))) two = Binary(b"hello world", 2) self.assertEqual(repr(two), "Binary({}, 2)".format(repr(b"hello world"))) - three = Binary(b"\x08\xFF") - self.assertEqual(repr(three), "Binary({}, 0)".format(repr(b"\x08\xFF"))) - four = Binary(b"\x08\xFF", 2) - self.assertEqual(repr(four), "Binary({}, 2)".format(repr(b"\x08\xFF"))) + three = Binary(b"\x08\xff") + self.assertEqual(repr(three), "Binary({}, 0)".format(repr(b"\x08\xff"))) + four = Binary(b"\x08\xff", 2) + self.assertEqual(repr(four), "Binary({}, 2)".format(repr(b"\x08\xff"))) five = Binary(b"test", 100) self.assertEqual(repr(five), "Binary({}, 100)".format(repr(b"test"))) diff --git a/test/test_bson.py b/test/test_bson.py index ae1807e5fc..da1cdf42c3 100644 --- a/test/test_bson.py +++ b/test/test_bson.py @@ -14,6 +14,7 @@ # limitations under the License. """Test the bson module.""" + from __future__ import annotations import array @@ -33,9 +34,6 @@ sys.path[0:0] = [""] -from test import qcheck, unittest -from test.helpers import ExceptionCatchingTask - import bson from bson import ( BSON, @@ -71,6 +69,8 @@ from bson.son import SON from bson.timestamp import Timestamp from bson.tz_util import FixedOffset, utc +from test import qcheck, unittest +from test.helpers import ExceptionCatchingTask _NUMPY_AVAILABLE = importlib.util.find_spec("numpy") is not None @@ -223,9 +223,7 @@ def test_basic_validation(self): ) self.assertInvalid(b"\x15\x00\x00\x00\x03foo\x00\x0c\x00\x00\x00\x08bar\x00\x01\x00\x00") self.assertInvalid( - b"\x1c\x00\x00\x00\x03foo\x00" - b"\x12\x00\x00\x00\x02bar\x00" - b"\x05\x00\x00\x00baz\x00\x00\x00" + b"\x1c\x00\x00\x00\x03foo\x00\x12\x00\x00\x00\x02bar\x00\x05\x00\x00\x00baz\x00\x00\x00" ) self.assertInvalid(b"\x10\x00\x00\x00\x02a\x00\x04\x00\x00\x00abc\xff\x00") @@ -238,9 +236,7 @@ def test_bad_string_lengths(self): b"\x18\x00\x00\x00\x0c\x00\x00\x00\x00\x00\x00RY\xb5j\xfa[\xd8A\xd6X]\x99\x00" ) self.assertInvalid( - b"\x1e\x00\x00\x00\x0c\x00" - b"\xff\xff\xff\xfffoobar\x00" - b"RY\xb5j\xfa[\xd8A\xd6X]\x99\x00" + b"\x1e\x00\x00\x00\x0c\x00\xff\xff\xff\xfffoobar\x00RY\xb5j\xfa[\xd8A\xd6X]\x99\x00" ) self.assertInvalid(b"\x0c\x00\x00\x00\r\x00\x00\x00\x00\x00\x00\x00") self.assertInvalid(b"\x0c\x00\x00\x00\r\x00\xff\xff\xff\xff\x00\x00") @@ -282,17 +278,17 @@ def test_basic_decode(self): self.assertEqual( {"test": "hello world"}, decode( - b"\x1B\x00\x00\x00\x0E\x74\x65\x73\x74\x00\x0C" - b"\x00\x00\x00\x68\x65\x6C\x6C\x6F\x20\x77\x6F" - b"\x72\x6C\x64\x00\x00" + b"\x1b\x00\x00\x00\x0e\x74\x65\x73\x74\x00\x0c" + b"\x00\x00\x00\x68\x65\x6c\x6c\x6f\x20\x77\x6f" + b"\x72\x6c\x64\x00\x00" ), ) self.assertEqual( [{"test": "hello world"}, {}], decode_all( - b"\x1B\x00\x00\x00\x0E\x74\x65\x73\x74" - b"\x00\x0C\x00\x00\x00\x68\x65\x6C\x6C" - b"\x6f\x20\x77\x6F\x72\x6C\x64\x00\x00" + b"\x1b\x00\x00\x00\x0e\x74\x65\x73\x74" + b"\x00\x0c\x00\x00\x00\x68\x65\x6c\x6c" + b"\x6f\x20\x77\x6f\x72\x6c\x64\x00\x00" b"\x05\x00\x00\x00\x00" ), ) @@ -300,9 +296,9 @@ def test_basic_decode(self): [{"test": "hello world"}, {}], list( decode_iter( - b"\x1B\x00\x00\x00\x0E\x74\x65\x73\x74" - b"\x00\x0C\x00\x00\x00\x68\x65\x6C\x6C" - b"\x6f\x20\x77\x6F\x72\x6C\x64\x00\x00" + b"\x1b\x00\x00\x00\x0e\x74\x65\x73\x74" + b"\x00\x0c\x00\x00\x00\x68\x65\x6c\x6c" + b"\x6f\x20\x77\x6f\x72\x6c\x64\x00\x00" b"\x05\x00\x00\x00\x00" ) ), @@ -312,9 +308,9 @@ def test_basic_decode(self): list( decode_file_iter( BytesIO( - b"\x1B\x00\x00\x00\x0E\x74\x65\x73\x74" - b"\x00\x0C\x00\x00\x00\x68\x65\x6C\x6C" - b"\x6f\x20\x77\x6F\x72\x6C\x64\x00\x00" + b"\x1b\x00\x00\x00\x0e\x74\x65\x73\x74" + b"\x00\x0c\x00\x00\x00\x68\x65\x6c\x6c" + b"\x6f\x20\x77\x6f\x72\x6c\x64\x00\x00" b"\x05\x00\x00\x00\x00" ) ) @@ -350,7 +346,7 @@ def test_invalid_decodes(self): # Invalid object size (not enough bytes in document for even # an object size of first object. # NOTE: decode_all and decode_iter don't care, not sure if they should? - self.assertRaises(InvalidBSON, list, decode_file_iter(BytesIO(b"\x1B"))) + self.assertRaises(InvalidBSON, list, decode_file_iter(BytesIO(b"\x1b"))) bad_bsons = [ # An object size that's too small to even include the object size, @@ -359,25 +355,25 @@ def test_invalid_decodes(self): # One object, but with object size listed smaller than it is in the # data. ( - b"\x1A\x00\x00\x00\x0E\x74\x65\x73\x74" - b"\x00\x0C\x00\x00\x00\x68\x65\x6C\x6C" - b"\x6f\x20\x77\x6F\x72\x6C\x64\x00\x00" + b"\x1a\x00\x00\x00\x0e\x74\x65\x73\x74" + b"\x00\x0c\x00\x00\x00\x68\x65\x6c\x6c" + b"\x6f\x20\x77\x6f\x72\x6c\x64\x00\x00" b"\x05\x00\x00\x00\x00" ), # One object, missing the EOO at the end. ( - b"\x1B\x00\x00\x00\x0E\x74\x65\x73\x74" - b"\x00\x0C\x00\x00\x00\x68\x65\x6C\x6C" - b"\x6f\x20\x77\x6F\x72\x6C\x64\x00\x00" + b"\x1b\x00\x00\x00\x0e\x74\x65\x73\x74" + b"\x00\x0c\x00\x00\x00\x68\x65\x6c\x6c" + b"\x6f\x20\x77\x6f\x72\x6c\x64\x00\x00" b"\x05\x00\x00\x00" ), # One object, sized correctly, with a spot for an EOO, but the EOO # isn't 0x00. ( - b"\x1B\x00\x00\x00\x0E\x74\x65\x73\x74" - b"\x00\x0C\x00\x00\x00\x68\x65\x6C\x6C" - b"\x6f\x20\x77\x6F\x72\x6C\x64\x00\x00" - b"\x05\x00\x00\x00\xFF" + b"\x1b\x00\x00\x00\x0e\x74\x65\x73\x74" + b"\x00\x0c\x00\x00\x00\x68\x65\x6c\x6c" + b"\x6f\x20\x77\x6f\x72\x6c\x64\x00\x00" + b"\x05\x00\x00\x00\xff" ), ] for i, data in enumerate(bad_bsons): @@ -417,31 +413,31 @@ def test_basic_encode(self): self.assertEqual(encode({}), b"\x05\x00\x00\x00\x00") self.assertEqual( encode({"test": "hello world"}), - b"\x1B\x00\x00\x00\x02\x74\x65\x73\x74\x00\x0C\x00" - b"\x00\x00\x68\x65\x6C\x6C\x6F\x20\x77\x6F\x72\x6C" + b"\x1b\x00\x00\x00\x02\x74\x65\x73\x74\x00\x0c\x00" + b"\x00\x00\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c" b"\x64\x00\x00", ) self.assertEqual( encode({"mike": 100}), - b"\x0F\x00\x00\x00\x10\x6D\x69\x6B\x65\x00\x64\x00\x00\x00\x00", + b"\x0f\x00\x00\x00\x10\x6d\x69\x6b\x65\x00\x64\x00\x00\x00\x00", ) self.assertEqual( encode({"hello": 1.5}), - b"\x14\x00\x00\x00\x01\x68\x65\x6C\x6C\x6F\x00\x00\x00\x00\x00\x00\x00\xF8\x3F\x00", + b"\x14\x00\x00\x00\x01\x68\x65\x6c\x6c\x6f\x00\x00\x00\x00\x00\x00\x00\xf8\x3f\x00", ) self.assertEqual( - encode({"true": True}), b"\x0C\x00\x00\x00\x08\x74\x72\x75\x65\x00\x01\x00" + encode({"true": True}), b"\x0c\x00\x00\x00\x08\x74\x72\x75\x65\x00\x01\x00" ) self.assertEqual( - encode({"false": False}), b"\x0D\x00\x00\x00\x08\x66\x61\x6C\x73\x65\x00\x00\x00" + encode({"false": False}), b"\x0d\x00\x00\x00\x08\x66\x61\x6c\x73\x65\x00\x00\x00" ) self.assertEqual( encode({"empty": []}), - b"\x11\x00\x00\x00\x04\x65\x6D\x70\x74\x79\x00\x05\x00\x00\x00\x00\x00", + b"\x11\x00\x00\x00\x04\x65\x6d\x70\x74\x79\x00\x05\x00\x00\x00\x00\x00", ) self.assertEqual( encode({"none": {}}), - b"\x10\x00\x00\x00\x03\x6E\x6F\x6E\x65\x00\x05\x00\x00\x00\x00\x00", + b"\x10\x00\x00\x00\x03\x6e\x6f\x6e\x65\x00\x05\x00\x00\x00\x00\x00", ) self.assertEqual( encode({"test": Binary(b"test", 0)}), @@ -470,14 +466,14 @@ def test_basic_encode(self): ), b"$\x00\x00\x00\x05vector_float32\x00\n\x00\x00\x00\t'\x00\xcd\xcc\x8c\xbf\xac\xe9#P\x00", ) - self.assertEqual(encode({"test": None}), b"\x0B\x00\x00\x00\x0A\x74\x65\x73\x74\x00\x00") + self.assertEqual(encode({"test": None}), b"\x0b\x00\x00\x00\x0a\x74\x65\x73\x74\x00\x00") self.assertEqual( encode({"date": datetime.datetime(2007, 1, 8, 0, 30, 11)}), - b"\x13\x00\x00\x00\x09\x64\x61\x74\x65\x00\x38\xBE\x1C\xFF\x0F\x01\x00\x00\x00", + b"\x13\x00\x00\x00\x09\x64\x61\x74\x65\x00\x38\xbe\x1c\xff\x0f\x01\x00\x00\x00", ) self.assertEqual( encode({"regex": re.compile(b"a*b", re.IGNORECASE)}), - b"\x12\x00\x00\x00\x0B\x72\x65\x67\x65\x78\x00\x61\x2A\x62\x00\x69\x00\x00", + b"\x12\x00\x00\x00\x0b\x72\x65\x67\x65\x78\x00\x61\x2a\x62\x00\x69\x00\x00", ) self.assertEqual( encode({"$where": Code("test")}), @@ -500,17 +496,17 @@ def test_basic_encode(self): b"\x00\x00function(){ return 'h\xc3\xa9llo';}\x00\x05" b"\x00\x00\x00\x00\x00", ) - a = ObjectId(b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B") + a = ObjectId(b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b") self.assertEqual( encode({"oid": a}), - b"\x16\x00\x00\x00\x07\x6F\x69\x64\x00\x00\x01\x02" - b"\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x00", + b"\x16\x00\x00\x00\x07\x6f\x69\x64\x00\x00\x01\x02" + b"\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x00", ) self.assertEqual( encode({"ref": DBRef("coll", a)}), - b"\x2F\x00\x00\x00\x03ref\x00\x25\x00\x00\x00\x02" + b"\x2f\x00\x00\x00\x03ref\x00\x25\x00\x00\x00\x02" b"$ref\x00\x05\x00\x00\x00coll\x00\x07$id\x00\x00" - b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0A\x0B\x00" + b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x00" b"\x00", ) @@ -680,14 +676,13 @@ def test_dst(self): d = {"x": datetime.datetime(1993, 4, 4, 2)} self.assertEqual(d, decode(encode(d))) - @unittest.skip("Disabled due to http://bugs.python.org/issue25222") def test_bad_encode(self): evil_list: dict = {"a": []} evil_list["a"].append(evil_list) evil_dict: dict = {} evil_dict["a"] = evil_dict for evil_data in [evil_dict, evil_list]: - self.assertRaises(Exception, encode, evil_data) + self.assertRaises(RecursionError, encode, evil_data) def test_overflow(self): self.assertTrue(encode({"x": 9223372036854775807})) @@ -966,8 +961,7 @@ def test_null_character(self): def test_move_id(self): self.assertEqual( - b"\x19\x00\x00\x00\x02_id\x00\x02\x00\x00\x00a\x00" - b"\x02a\x00\x02\x00\x00\x00a\x00\x00", + b"\x19\x00\x00\x00\x02_id\x00\x02\x00\x00\x00a\x00\x02a\x00\x02\x00\x00\x00a\x00\x00", encode(SON([("a", "a"), ("_id", "a")])), ) @@ -1046,9 +1040,7 @@ def test_bson_regex(self): self.assertEqual(0, bson_re1.flags) doc1 = {"r": bson_re1} - doc1_bson = ( - b"\x11\x00\x00\x00\x0br\x00[\\w-\\.]\x00\x00\x00" - ) # document length # r: regex # document terminator + doc1_bson = b"\x11\x00\x00\x00\x0br\x00[\\w-\\.]\x00\x00\x00" # document length # r: regex # document terminator self.assertEqual(doc1_bson, encode(doc1)) self.assertEqual(doc1, decode(doc1_bson)) @@ -1059,9 +1051,7 @@ def test_bson_regex(self): doc2_with_re = {"r": re2} doc2_with_bson_re = {"r": bson_re2} - doc2_bson = ( - b"\x11\x00\x00\x00\x0br\x00.*\x00imsux\x00\x00" - ) # document length # r: regex # document terminator + doc2_bson = b"\x11\x00\x00\x00\x0br\x00.*\x00imsux\x00\x00" # document length # r: regex # document terminator self.assertEqual(doc2_bson, encode(doc2_with_re)) self.assertEqual(doc2_bson, encode(doc2_with_bson_re)) diff --git a/test/test_bson_binary_vector.py b/test/test_bson_binary_vector.py index 2783338793..4893e91961 100644 --- a/test/test_bson_binary_vector.py +++ b/test/test_bson_binary_vector.py @@ -17,10 +17,10 @@ import binascii import struct from pathlib import Path -from test import unittest from bson import decode, encode, json_util from bson.binary import Binary, BinaryVectorDtype +from test import unittest _TEST_PATH = Path(__file__).parent / "bson_binary_vector" diff --git a/test/test_bson_corpus.py b/test/test_bson_corpus.py index 3370c18bda..b595e36214 100644 --- a/test/test_bson_corpus.py +++ b/test/test_bson_corpus.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the BSON corpus specification tests.""" + from __future__ import annotations import binascii @@ -25,8 +26,6 @@ sys.path[0:0] = [""] -from test import unittest - from bson import decode, encode, json_util from bson.binary import STANDARD from bson.codec_options import CodecOptions @@ -35,6 +34,7 @@ from bson.errors import InvalidBSON, InvalidDocument, InvalidId from bson.json_util import JSONMode from bson.son import SON +from test import unittest _TEST_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "bson_corpus") diff --git a/test/test_bulk.py b/test/test_bulk.py index 712c6c6a26..baefb1ad30 100644 --- a/test/test_bulk.py +++ b/test/test_bulk.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the bulk API.""" + from __future__ import annotations import sys @@ -23,9 +24,6 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, remove_all_users, unittest -from test.utils_shared import wait_until - from bson.binary import Binary, UuidRepresentation from bson.codec_options import CodecOptions from bson.objectid import ObjectId @@ -34,6 +32,8 @@ from pymongo.operations import * from pymongo.synchronous.collection import Collection from pymongo.write_concern import WriteConcern +from test import IntegrationTest, client_context, remove_all_users, unittest +from test.utils_shared import wait_until _IS_SYNC = True diff --git a/test/test_causal_consistency.py b/test/test_causal_consistency.py index 40264e0f17..d4825e0dc7 100644 --- a/test/test_causal_consistency.py +++ b/test/test_causal_consistency.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the causal consistency unified spec tests.""" + from __future__ import annotations import os diff --git a/test/test_change_stream.py b/test/test_change_stream.py index 792b39cc29..cc40ccd0e6 100644 --- a/test/test_change_stream.py +++ b/test/test_change_stream.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the change_stream module.""" + from __future__ import annotations import asyncio @@ -28,21 +29,6 @@ sys.path[0:0] = [""] -from test import ( - IntegrationTest, - PyMongoTestCase, - Version, - client_context, - unittest, -) -from test.unified_format import generate_test_classes, get_test_path -from test.utils_shared import ( - AllowListEventListener, - EventListener, - OvertCommandListener, - wait_until, -) - from bson import SON, ObjectId, Timestamp, encode from bson.binary import ALL_UUID_REPRESENTATIONS, PYTHON_LEGACY, STANDARD, Binary from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument @@ -57,6 +43,20 @@ from pymongo.synchronous.command_cursor import CommandCursor from pymongo.synchronous.helpers import next from pymongo.write_concern import WriteConcern +from test import ( + IntegrationTest, + PyMongoTestCase, + Version, + client_context, + unittest, +) +from test.unified_format import generate_test_classes, get_test_path +from test.utils_shared import ( + AllowListEventListener, + EventListener, + OvertCommandListener, + wait_until, +) _IS_SYNC = True diff --git a/test/test_client.py b/test/test_client.py index d2d93c6ba2..22b39bf341 100644 --- a/test/test_client.py +++ b/test/test_client.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the mongo_client module.""" + from __future__ import annotations import _thread as thread @@ -33,7 +34,8 @@ import threading import time import uuid -from typing import Any, Iterable, Type, no_type_check +from collections.abc import Iterable +from typing import Any, no_type_check from unittest import mock, skipIf from unittest.mock import patch @@ -44,38 +46,6 @@ sys.path[0:0] = [""] -from test import ( - HAVE_IPADDRESS, - IntegrationTest, - MockClientTest, - SkipTest, - UnitTest, - client_context, - client_knobs, - connected, - db_pwd, - db_user, - remove_all_users, - unittest, -) -from test.pymongo_mocks import MockClient -from test.test_binary import BinaryData -from test.utils import ( - assertRaisesExactly, - get_pool, - wait_until, -) -from test.utils_shared import ( - NTHREADS, - CMAPListener, - FunctionCallRecorder, - delay, - gevent_monkey_patched, - is_greenthread_patched, - lazy_client_trial, - one, -) - import bson import pymongo from bson import encode @@ -123,6 +93,37 @@ from pymongo.synchronous.topology import _ErrorContext from pymongo.topology_description import TopologyDescription from pymongo.write_concern import WriteConcern +from test import ( + HAVE_IPADDRESS, + IntegrationTest, + MockClientTest, + SkipTest, + UnitTest, + client_context, + client_knobs, + connected, + db_pwd, + db_user, + remove_all_users, + unittest, +) +from test.pymongo_mocks import MockClient +from test.test_binary import BinaryData +from test.utils import ( + assertRaisesExactly, + get_pool, + wait_until, +) +from test.utils_shared import ( + NTHREADS, + CMAPListener, + FunctionCallRecorder, + delay, + gevent_monkey_patched, + is_greenthread_patched, + lazy_client_trial, + one, +) _IS_SYNC = True @@ -430,7 +431,7 @@ def transform_python(self, value): return int(value) # Ensure codec options are passed in correctly - document_class: Type[SON] = SON + document_class: type[SON] = SON type_registry = TypeRegistry([MyFloatAsIntEncoder()]) tz_aware = True uuid_representation_label = "javaLegacy" diff --git a/test/test_client_backpressure.py b/test/test_client_backpressure.py index 61334a1218..c50767db66 100644 --- a/test/test_client_backpressure.py +++ b/test/test_client_backpressure.py @@ -13,6 +13,7 @@ # limitations under the License. """Test Client Backpressure spec.""" + from __future__ import annotations import os @@ -25,6 +26,7 @@ sys.path[0:0] = [""] +from pymongo.errors import OperationFailure, PyMongoError from test import ( IntegrationTest, client_context, @@ -33,8 +35,6 @@ from test.unified_format import generate_test_classes from test.utils_shared import EventListener, OvertCommandListener -from pymongo.errors import OperationFailure, PyMongoError - _IS_SYNC = True # Mock a system overload error. diff --git a/test/test_client_bulk_write.py b/test/test_client_bulk_write.py index 0cb6845099..4cf799a20b 100644 --- a/test/test_client_bulk_write.py +++ b/test/test_client_bulk_write.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the client bulk write API.""" + from __future__ import annotations import os @@ -20,15 +21,6 @@ sys.path[0:0] = [""] -from test import ( - IntegrationTest, - client_context, - unittest, -) -from test.utils import flaky -from test.utils_shared import ( - OvertCommandListener, -) from unittest.mock import patch import pymongo @@ -42,6 +34,15 @@ from pymongo.operations import * from pymongo.synchronous.client_bulk import _ClientBulk from pymongo.write_concern import WriteConcern +from test import ( + IntegrationTest, + client_context, + unittest, +) +from test.utils import flaky +from test.utils_shared import ( + OvertCommandListener, +) _IS_SYNC = True diff --git a/test/test_client_context.py b/test/test_client_context.py index 9c1b21ee78..1d77fa82a8 100644 --- a/test/test_client_context.py +++ b/test/test_client_context.py @@ -31,9 +31,7 @@ def test_must_connect(self): self.assertTrue( client_context.connected, "client context must be connected when " - "PYMONGO_MUST_CONNECT is set. Failed attempts:\n{}".format( - client_context.connection_attempt_info() - ), + f"PYMONGO_MUST_CONNECT is set. Failed attempts:\n{client_context.connection_attempt_info()}", ) def test_enableTestCommands_is_disabled(self): diff --git a/test/test_client_metadata.py b/test/test_client_metadata.py index 8cdb728ea2..cbb0b1e6b0 100644 --- a/test/test_client_metadata.py +++ b/test/test_client_metadata.py @@ -18,9 +18,6 @@ import pathlib import time import unittest -from test import IntegrationTest -from test.unified_format import generate_test_classes, get_test_path -from test.utils_shared import CMAPListener from typing import Any, Optional import pytest @@ -28,6 +25,9 @@ from pymongo import MongoClient from pymongo.driver_info import DriverInfo from pymongo.monitoring import ConnectionClosedEvent +from test import IntegrationTest +from test.unified_format import generate_test_classes, get_test_path +from test.utils_shared import CMAPListener try: from mockupdb import MockupDB, OpMsgReply @@ -193,7 +193,7 @@ def test_doesnt_update_established_connections(self): ) # send initial metadata - name, version, platform, metadata = self.send_ping_and_get_metadata(client, True) + name, version, platform, _ = self.send_ping_and_get_metadata(client, True) self.assertIsNotNone(name) self.assertIsNotNone(version) self.assertIsNotNone(platform) diff --git a/test/test_code.py b/test/test_code.py index 23f0af5cef..4723def804 100644 --- a/test/test_code.py +++ b/test/test_code.py @@ -14,15 +14,15 @@ # limitations under the License. """Tests for the Code wrapper.""" + from __future__ import annotations import sys sys.path[0:0] = [""] -from test import unittest - from bson.code import Code +from test import unittest class TestCode(unittest.TestCase): @@ -66,8 +66,8 @@ def test_repr(self): self.assertEqual(repr(c), "Code('hello world', {'foo': 'bar'})") c = Code("hello world", {"blah": 3}) self.assertEqual(repr(c), "Code('hello world', {'blah': 3})") - c = Code("\x08\xFF") - self.assertEqual(repr(c), "Code({}, None)".format(repr("\x08\xFF"))) + c = Code("\x08\xff") + self.assertEqual(repr(c), "Code({}, None)".format(repr("\x08\xff"))) def test_equality(self): b = Code("hello") diff --git a/test/test_collation.py b/test/test_collation.py index 0f5efa92dd..6bf8975160 100644 --- a/test/test_collation.py +++ b/test/test_collation.py @@ -13,12 +13,11 @@ # limitations under the License. """Test the collation module.""" + from __future__ import annotations import functools import warnings -from test import IntegrationTest, client_context, unittest -from test.utils_shared import EventListener, OvertCommandListener from typing import Any from pymongo.collation import ( @@ -39,6 +38,8 @@ ) from pymongo.synchronous.helpers import next from pymongo.write_concern import WriteConcern +from test import IntegrationTest, client_context, unittest +from test.utils_shared import EventListener, OvertCommandListener _IS_SYNC = True diff --git a/test/test_collection.py b/test/test_collection.py index ac469782e9..15c5ac710d 100644 --- a/test/test_collection.py +++ b/test/test_collection.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the collection module.""" + from __future__ import annotations import asyncio @@ -21,28 +22,15 @@ import sys from codecs import utf_8_decode from collections import defaultdict -from test.utils import get_pool, is_mongos -from typing import Any, Iterable, no_type_check +from collections.abc import Iterable +from typing import Any, no_type_check from pymongo.synchronous.database import Database from pymongo.synchronous.helpers import next +from test.utils import get_pool, is_mongos sys.path[0:0] = [""] -from test import ( # TODO: fix sync imports in PYTHON-4528 - IntegrationTest, - UnitTest, - client_context, - unittest, -) -from test.utils_shared import ( - IMPOSSIBLE_WRITE_CONCERN, - EventListener, - OvertCommandListener, - wait_until, -) -from test.version import Version - from bson import encode from bson.codec_options import CodecOptions from bson.objectid import ObjectId @@ -77,6 +65,19 @@ from pymongo.synchronous.command_cursor import CommandCursor from pymongo.synchronous.mongo_client import MongoClient from pymongo.write_concern import WriteConcern +from test import ( # TODO: fix sync imports in PYTHON-4528 + IntegrationTest, + UnitTest, + client_context, + unittest, +) +from test.utils_shared import ( + IMPOSSIBLE_WRITE_CONCERN, + EventListener, + OvertCommandListener, + wait_until, +) +from test.version import Version _IS_SYNC = True diff --git a/test/test_collection_management.py b/test/test_collection_management.py index deb43677a6..5b8f759fcd 100644 --- a/test/test_collection_management.py +++ b/test/test_collection_management.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the collection management unified spec tests.""" + from __future__ import annotations import os diff --git a/test/test_command_logging.py b/test/test_command_logging.py index 17bc319d9c..4a6cff28c5 100644 --- a/test/test_command_logging.py +++ b/test/test_command_logging.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the command monitoring unified format spec tests.""" + from __future__ import annotations import os diff --git a/test/test_command_monitoring.py b/test/test_command_monitoring.py index eaa2af5ee8..ce1f9dd977 100644 --- a/test/test_command_monitoring.py +++ b/test/test_command_monitoring.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the command monitoring unified format spec tests.""" + from __future__ import annotations import os diff --git a/test/test_comment.py b/test/test_comment.py index bcab0061fa..9200abd511 100644 --- a/test/test_comment.py +++ b/test/test_comment.py @@ -21,12 +21,12 @@ sys.path[0:0] = [""] from inspect import iscoroutinefunction -from test import IntegrationTest, client_context, unittest -from test.utils_shared import OvertCommandListener from bson.dbref import DBRef from pymongo.operations import IndexModel from pymongo.synchronous.command_cursor import CommandCursor +from test import IntegrationTest, client_context, unittest +from test.utils_shared import OvertCommandListener _IS_SYNC = True diff --git a/test/test_common.py b/test/test_common.py index e69b421c9f..b0c706b9f2 100644 --- a/test/test_common.py +++ b/test/test_common.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the pymongo common module.""" + from __future__ import annotations import sys @@ -20,13 +21,12 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, connected, unittest - from bson.binary import PYTHON_LEGACY, STANDARD, Binary, UuidRepresentation from bson.codec_options import CodecOptions from bson.objectid import ObjectId from pymongo.errors import OperationFailure from pymongo.write_concern import WriteConcern +from test import IntegrationTest, client_context, connected, unittest _IS_SYNC = True diff --git a/test/test_compression_support.py b/test/test_compression_support.py index 7294b3f4dc..0c37627f21 100644 --- a/test/test_compression_support.py +++ b/test/test_compression_support.py @@ -21,8 +21,6 @@ sys.path[0:0] = [""] -from test import unittest - from pymongo.compression_support import ( CompressionSettings, SnappyContext, @@ -35,6 +33,7 @@ validate_compressors, validate_zlib_compression_level, ) +from test import unittest class TestValidateCompressors(unittest.TestCase): @@ -44,8 +43,9 @@ def test_string_input_single(self): self.assertEqual(result, ["zlib"]) def test_string_input_comma_separated(self): - with patch("pymongo.compression_support._have_zlib", return_value=True), patch( - "pymongo.compression_support._have_snappy", return_value=True + with ( + patch("pymongo.compression_support._have_zlib", return_value=True), + patch("pymongo.compression_support._have_snappy", return_value=True), ): result = validate_compressors(None, "zlib,snappy") self.assertEqual(result, ["zlib", "snappy"]) @@ -94,8 +94,9 @@ def test_zstd_unavailable_warns_and_removes_314_plus(self): self.assertIn("compression.zstd", str(ctx.warning)) def test_multiple_valid_compressors_preserves_order(self): - with patch("pymongo.compression_support._have_zlib", return_value=True), patch( - "pymongo.compression_support._have_snappy", return_value=True + with ( + patch("pymongo.compression_support._have_zlib", return_value=True), + patch("pymongo.compression_support._have_snappy", return_value=True), ): result = validate_compressors(None, ["zlib", "snappy"]) self.assertEqual(result, ["zlib", "snappy"]) diff --git a/test/test_connection_logging.py b/test/test_connection_logging.py index 9f5da0c436..cfe9c26f58 100644 --- a/test/test_connection_logging.py +++ b/test/test_connection_logging.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the connection logging unified format spec tests.""" + from __future__ import annotations import os diff --git a/test/test_connection_monitoring.py b/test/test_connection_monitoring.py index 1405824453..e67d8d75fa 100644 --- a/test/test_connection_monitoring.py +++ b/test/test_connection_monitoring.py @@ -13,6 +13,7 @@ # limitations under the License. """Execute Transactions Spec tests.""" + from __future__ import annotations import asyncio @@ -20,19 +21,11 @@ import sys import time from pathlib import Path + from test.utils import get_pool, get_pools sys.path[0:0] = [""] -from test import IntegrationTest, client_context, client_knobs, unittest -from test.pymongo_mocks import DummyMonitor -from test.utils_shared import ( - CMAPListener, - camel_to_snake, - wait_until, -) -from test.utils_spec_runner import SpecRunnerThread, SpecTestCreator - from bson.objectid import ObjectId from bson.son import SON from pymongo.errors import ( @@ -59,6 +52,14 @@ from pymongo.read_preferences import ReadPreference from pymongo.synchronous.pool import PoolState, _PoolClosedError from pymongo.topology_description import updated_topology_description +from test import IntegrationTest, client_context, client_knobs, unittest +from test.pymongo_mocks import DummyMonitor +from test.utils_shared import ( + CMAPListener, + camel_to_snake, + wait_until, +) +from test.utils_spec_runner import SpecRunnerThread, SpecTestCreator _IS_SYNC = True diff --git a/test/test_connections_survive_primary_stepdown_spec.py b/test/test_connections_survive_primary_stepdown_spec.py index 8e9a3b8e62..51d37b7c57 100644 --- a/test/test_connections_survive_primary_stepdown_spec.py +++ b/test/test_connections_survive_primary_stepdown_spec.py @@ -13,13 +13,20 @@ # limitations under the License. """Test compliance with the connections survive primary step down spec.""" + from __future__ import annotations import sys + from test.utils import ensure_all_connected sys.path[0:0] = [""] +from bson import SON +from pymongo import monitoring +from pymongo.errors import NotPrimaryError +from pymongo.synchronous.collection import Collection +from pymongo.write_concern import WriteConcern from test import ( IntegrationTest, client_context, @@ -30,12 +37,6 @@ CMAPListener, ) -from bson import SON -from pymongo import monitoring -from pymongo.errors import NotPrimaryError -from pymongo.synchronous.collection import Collection -from pymongo.write_concern import WriteConcern - _IS_SYNC = True diff --git a/test/test_crud_unified.py b/test/test_crud_unified.py index 45af155bd7..a7473cbb19 100644 --- a/test/test_crud_unified.py +++ b/test/test_crud_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the CRUD unified spec tests.""" + from __future__ import annotations import os diff --git a/test/test_crud_v1.py b/test/test_crud_v1.py index af5228b2c7..e4321b4273 100644 --- a/test/test_crud_v1.py +++ b/test/test_crud_v1.py @@ -13,14 +13,13 @@ # limitations under the License. """Test the collection module.""" + from __future__ import annotations import sys sys.path[0:0] = [""] -from test import unittest - from pymongo.operations import ( DeleteMany, DeleteOne, @@ -29,6 +28,7 @@ UpdateMany, UpdateOne, ) +from test import unittest class TestWriteOpsComparison(unittest.TestCase): diff --git a/test/test_csot.py b/test/test_csot.py index d6dec51d37..da6c1ca384 100644 --- a/test/test_csot.py +++ b/test/test_csot.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the CSOT unified spec tests.""" + from __future__ import annotations import os @@ -21,13 +22,12 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest -from test.unified_format import generate_test_classes, get_test_path -from test.utils import flaky - import pymongo from pymongo import _csot from pymongo.errors import PyMongoError +from test import IntegrationTest, client_context, unittest +from test.unified_format import generate_test_classes, get_test_path +from test.utils import flaky _IS_SYNC = True diff --git a/test/test_cursor.py b/test/test_cursor.py index cd13e9bfff..661d526efb 100644 --- a/test/test_cursor.py +++ b/test/test_cursor.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the cursor module.""" + from __future__ import annotations import copy @@ -30,17 +31,6 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest -from test.utils import flaky -from test.utils_shared import ( - AllowListEventListener, - EventListener, - OvertCommandListener, - delay, - ignore_deprecations, - wait_until, -) - from bson import decode_all from bson.code import Code from bson.raw_bson import RawBSONDocument @@ -53,6 +43,16 @@ from pymongo.synchronous.cursor import Cursor, CursorType from pymongo.synchronous.helpers import next from pymongo.write_concern import WriteConcern +from test import IntegrationTest, client_context, unittest +from test.utils import flaky +from test.utils_shared import ( + AllowListEventListener, + EventListener, + OvertCommandListener, + delay, + ignore_deprecations, + wait_until, +) _IS_SYNC = True diff --git a/test/test_custom_types.py b/test/test_custom_types.py index aba6b55119..b55eb64d9d 100644 --- a/test/test_custom_types.py +++ b/test/test_custom_types.py @@ -13,6 +13,7 @@ # limitations under the License. """Test support for callbacks to encode/decode custom types.""" + from __future__ import annotations import datetime @@ -21,15 +22,13 @@ from collections import OrderedDict from decimal import Decimal from random import random -from typing import Any, Tuple, Type, no_type_check +from typing import Any, no_type_check from bson.decimal128 import DecimalDecoder, DecimalEncoder from gridfs.synchronous.grid_file import GridIn, GridOut sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest - from bson import ( _BUILT_IN_TYPES, RE_TYPE, @@ -56,6 +55,7 @@ from pymongo.message import _CursorAddress from pymongo.synchronous.collection import ReturnDocument from pymongo.synchronous.helpers import next +from test import IntegrationTest, client_context, unittest _IS_SYNC = True @@ -341,9 +341,9 @@ class TestBSONCustomTypeEncoderAndFallbackEncoderTandem(unittest.TestCase): TypeB: Any fallback_encoder_A2B: Any fallback_encoder_A2BSON: Any - B2BSON: Type[TypeEncoder] - B2A: Type[TypeEncoder] - A2B: Type[TypeEncoder] + B2BSON: type[TypeEncoder] + B2A: type[TypeEncoder] + A2B: type[TypeEncoder] @classmethod def setUpClass(cls): @@ -433,8 +433,8 @@ def test_infinite_loop_exceeds_max_recursion_depth(self): class TestTypeRegistry(unittest.TestCase): - types: Tuple[object, object] - codecs: Tuple[Type[TypeCodec], Type[TypeCodec]] + types: tuple[object, object] + codecs: tuple[type[TypeCodec], type[TypeCodec]] fallback_encoder: Any @classmethod diff --git a/test/test_daemon.py b/test/test_daemon.py index 87886d6abd..799367b541 100644 --- a/test/test_daemon.py +++ b/test/test_daemon.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the pymongo daemon module.""" + from __future__ import annotations import subprocess @@ -22,10 +23,9 @@ sys.path[0:0] = [""] -from test import unittest - import pymongo.daemon as daemon_module from pymongo.daemon import _popen_wait, _silence_resource_warning, _spawn_daemon +from test import unittest class TestPopenWait(unittest.TestCase): diff --git a/test/test_database.py b/test/test_database.py index 2f6c312f50..61d2494eb9 100644 --- a/test/test_database.py +++ b/test/test_database.py @@ -13,24 +13,18 @@ # limitations under the License. """Test the database module.""" + from __future__ import annotations import re import sys -from typing import Any, Iterable, List, Mapping, Union +from collections.abc import Iterable, Mapping +from typing import Any, Union from pymongo.synchronous.command_cursor import CommandCursor sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest -from test.test_custom_types import DECIMAL_CODECOPTS -from test.utils_shared import ( - IMPOSSIBLE_WRITE_CONCERN, - OvertCommandListener, - wait_until, -) - from bson.codec_options import CodecOptions from bson.dbref import DBRef from bson.int64 import Int64 @@ -54,6 +48,13 @@ from pymongo.synchronous.helpers import next from pymongo.synchronous.mongo_client import MongoClient from pymongo.write_concern import WriteConcern +from test import IntegrationTest, client_context, unittest +from test.test_custom_types import DECIMAL_CODECOPTS +from test.utils_shared import ( + IMPOSSIBLE_WRITE_CONCERN, + OvertCommandListener, + wait_until, +) _IS_SYNC = True @@ -683,7 +684,7 @@ def test_with_options(self): class TestDatabaseAggregation(IntegrationTest): def setUp(self): super().setUp() - self.pipeline: List[Mapping[str, Any]] = [ + self.pipeline: list[Mapping[str, Any]] = [ {"$listLocalSessions": {}}, {"$limit": 1}, {"$addFields": {"dummy": "dummy field"}}, diff --git a/test/test_dbref.py b/test/test_dbref.py index ac2767a1ce..6793bb0f17 100644 --- a/test/test_dbref.py +++ b/test/test_dbref.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for the dbref module.""" + from __future__ import annotations import pickle @@ -22,11 +23,11 @@ sys.path[0:0] = [""] from copy import deepcopy -from test import unittest from bson import decode, encode from bson.dbref import DBRef from bson.objectid import ObjectId +from test import unittest class TestDBRef(unittest.TestCase): diff --git a/test/test_decimal128.py b/test/test_decimal128.py index 46819dd587..5727dc5233 100644 --- a/test/test_decimal128.py +++ b/test/test_decimal128.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for Decimal128.""" + from __future__ import annotations import pickle @@ -21,9 +22,8 @@ sys.path[0:0] = [""] -from test import client_context, unittest - from bson.decimal128 import Decimal128, create_decimal128_context +from test import client_context, unittest class TestDecimal128(unittest.TestCase): diff --git a/test/test_default_exports.py b/test/test_default_exports.py index adc3882a36..111a3848d4 100644 --- a/test/test_default_exports.py +++ b/test/test_default_exports.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the default exports of the top level packages.""" + from __future__ import annotations import inspect diff --git a/test/test_discovery_and_monitoring.py b/test/test_discovery_and_monitoring.py index 7fb6f312c5..404abcf112 100644 --- a/test/test_discovery_and_monitoring.py +++ b/test/test_discovery_and_monitoring.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the topology module.""" + from __future__ import annotations import asyncio @@ -23,39 +24,17 @@ import time from asyncio import StreamReader, StreamWriter from pathlib import Path -from test.helpers import ConcurrentRunner -from test.utils import flaky -from test.utils_shared import delay from pymongo.errors import ConnectionFailure from pymongo.operations import _Op from pymongo.server_selectors import writable_server_selector from pymongo.synchronous.pool import Connection +from test.helpers import ConcurrentRunner +from test.utils import flaky +from test.utils_shared import delay sys.path[0:0] = [""] -from test import ( - IntegrationTest, - PyMongoTestCase, - UnitTest, - client_context, - unittest, -) -from test.pymongo_mocks import DummyMonitor -from test.unified_format import generate_test_classes, get_test_path -from test.utils import ( - get_pool, -) -from test.utils_shared import ( - CMAPListener, - HeartbeatEventListener, - HeartbeatEventsListListener, - assertion_context, - barrier_wait, - create_barrier, - server_name_to_type, - wait_until, -) from unittest.mock import patch from bson import Timestamp, json_util @@ -80,6 +59,28 @@ from pymongo.synchronous.topology import Topology, _ErrorContext from pymongo.synchronous.uri_parser import parse_uri from pymongo.topology_description import TOPOLOGY_TYPE +from test import ( + IntegrationTest, + PyMongoTestCase, + UnitTest, + client_context, + unittest, +) +from test.pymongo_mocks import DummyMonitor +from test.unified_format import generate_test_classes, get_test_path +from test.utils import ( + get_pool, +) +from test.utils_shared import ( + CMAPListener, + HeartbeatEventListener, + HeartbeatEventsListListener, + assertion_context, + barrier_wait, + create_barrier, + server_name_to_type, + wait_until, +) _IS_SYNC = True diff --git a/test/test_dns.py b/test/test_dns.py index 8f88562e3f..09933b2e27 100644 --- a/test/test_dns.py +++ b/test/test_dns.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the SRV support tests.""" + from __future__ import annotations import glob @@ -23,6 +24,12 @@ sys.path[0:0] = [""] +from unittest.mock import MagicMock, patch + +from pymongo.common import validate_read_preference_tags +from pymongo.errors import ConfigurationError +from pymongo.synchronous.uri_parser import parse_uri +from pymongo.uri_parser_shared import split_hosts from test import ( IntegrationTest, PyMongoTestCase, @@ -30,12 +37,6 @@ unittest, ) from test.utils_shared import wait_until -from unittest.mock import MagicMock, patch - -from pymongo.common import validate_read_preference_tags -from pymongo.errors import ConfigurationError -from pymongo.synchronous.uri_parser import parse_uri -from pymongo.uri_parser_shared import split_hosts _IS_SYNC = True @@ -269,7 +270,7 @@ def test_3_throw_when_return_address_is_identical_to_srv_hostname(self): self.run_initial_dns_seedlist_discovery_prose_tests(test_cases) def test_4_throw_when_return_address_does_not_contain_dot_separating_shared_part_of_domain( - self + self, ): test_cases = [ { @@ -291,7 +292,7 @@ def test_4_throw_when_return_address_does_not_contain_dot_separating_shared_part self.run_initial_dns_seedlist_discovery_prose_tests(test_cases) def test_5_when_srv_hostname_has_two_dot_separated_parts_it_is_valid_for_the_returned_hostname_to_be_identical( - self + self, ): test_cases = [ { diff --git a/test/test_encryption.py b/test/test_encryption.py index 7df9e7ac38..6c06e248c5 100644 --- a/test/test_encryption.py +++ b/test/test_encryption.py @@ -13,6 +13,7 @@ # limitations under the License. """Test client side encryption spec.""" + from __future__ import annotations import base64 @@ -30,17 +31,18 @@ import traceback import uuid import warnings -from test import IntegrationTest, PyMongoTestCase, client_context -from test.test_bulk import BulkTestBase -from test.utils import flaky +from collections.abc import Mapping from threading import Thread -from typing import Any, Dict, Mapping, Optional +from typing import Any, Optional import pytest from pymongo.daemon import _spawn_daemon from pymongo.synchronous.collection import Collection from pymongo.uri_parser_shared import _parse_kms_tls_options +from test import IntegrationTest, PyMongoTestCase, client_context +from test.test_bulk import BulkTestBase +from test.utils import flaky try: from pymongo.pyopenssl_context import IS_PYOPENSSL @@ -49,32 +51,6 @@ sys.path[0:0] = [""] -from test import ( - unittest, -) -from test.helpers_shared import ( - ALL_KMS_PROVIDERS, - AWS_CREDS, - AWS_TEMP_CREDS, - AZURE_CREDS, - CA_PEM, - CLIENT_PEM, - DEFAULT_KMS_TLS, - GCP_CREDS, - KMIP_CREDS, - LOCAL_MASTER_KEY, -) -from test.test_bulk import BulkTestBase -from test.unified_format import generate_test_classes, get_test_path -from test.utils_shared import ( - AllowListEventListener, - OvertCommandListener, - TopologyEventListener, - camel_to_snake_args, - is_greenthread_patched, - wait_until, -) - from bson import BSON, DatetimeMS, Decimal128, encode, json_util from bson.binary import UUID_SUBTYPE, Binary, UuidRepresentation from bson.codec_options import CodecOptions @@ -103,6 +79,31 @@ from pymongo.synchronous.helpers import next from pymongo.synchronous.mongo_client import MongoClient from pymongo.write_concern import WriteConcern +from test import ( + unittest, +) +from test.helpers_shared import ( + ALL_KMS_PROVIDERS, + AWS_CREDS, + AWS_TEMP_CREDS, + AZURE_CREDS, + CA_PEM, + CLIENT_PEM, + DEFAULT_KMS_TLS, + GCP_CREDS, + KMIP_CREDS, + LOCAL_MASTER_KEY, +) +from test.test_bulk import BulkTestBase +from test.unified_format import generate_test_classes, get_test_path +from test.utils_shared import ( + AllowListEventListener, + OvertCommandListener, + TopologyEventListener, + camel_to_snake_args, + is_greenthread_patched, + wait_until, +) _IS_SYNC = True @@ -2909,7 +2910,7 @@ def encrypt_and_cast(i): EncryptionError, "expected matching 'min' and value type. Got range option" ): self.client_encryption.encrypt( - 6 if cast_func != int else float(6), + 6 if cast_func is not int else float(6), key_id=self.key1_id, algorithm=Algorithm.RANGE, contention_factor=0, @@ -3177,7 +3178,7 @@ def test_copy_encrypted_fields(self): self.assertIsNone(encrypted_fields["fields"][0]["keyId"]) def test_options_forward(self): - coll, ef = self.client_encryption.create_encrypted_collection( + coll, _ = self.client_encryption.create_encrypted_collection( database=self.db, name="testing1", kms_provider="local", diff --git a/test/test_errors.py b/test/test_errors.py index d6db6a4ec1..82cc5d952d 100644 --- a/test/test_errors.py +++ b/test/test_errors.py @@ -19,14 +19,13 @@ sys.path[0:0] = [""] -from test import PyMongoTestCase, unittest - from pymongo.errors import ( BulkWriteError, EncryptionError, NotPrimaryError, OperationFailure, ) +from test import PyMongoTestCase, unittest class TestErrors(PyMongoTestCase): diff --git a/test/test_event_loggers.py b/test/test_event_loggers.py index 3bb5a3d25d..4c4a0671bd 100644 --- a/test/test_event_loggers.py +++ b/test/test_event_loggers.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for pymongo.event_loggers.""" + from __future__ import annotations import sys @@ -20,8 +21,6 @@ sys.path[0:0] = [""] -from test import unittest - from pymongo.event_loggers import ( CommandLogger, ConnectionPoolLogger, @@ -29,6 +28,7 @@ ServerLogger, TopologyLogger, ) +from test import unittest class TestCommandLogger(unittest.TestCase): diff --git a/test/test_examples.py b/test/test_examples.py index 13f0c94c56..ccc709e3aa 100644 --- a/test/test_examples.py +++ b/test/test_examples.py @@ -13,6 +13,7 @@ # limitations under the License. """MongoDB documentation examples in Python.""" + from __future__ import annotations import asyncio @@ -21,13 +22,11 @@ import sys import threading import time + from test.helpers import ConcurrentRunner sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest -from test.utils_shared import wait_until - import pymongo from pymongo.errors import ConnectionFailure, OperationFailure from pymongo.read_concern import ReadConcern @@ -35,6 +34,8 @@ from pymongo.server_api import ServerApi from pymongo.synchronous.helpers import next from pymongo.write_concern import WriteConcern +from test import IntegrationTest, client_context, unittest +from test.utils_shared import wait_until _IS_SYNC = True diff --git a/test/test_fork.py b/test/test_fork.py index dad947d8c5..e828b77376 100644 --- a/test/test_fork.py +++ b/test/test_fork.py @@ -13,6 +13,7 @@ # limitations under the License. """Test that pymongo resets its own locks after a fork.""" + from __future__ import annotations import os @@ -23,11 +24,10 @@ sys.path[0:0] = [""] +from bson.objectid import ObjectId from test import IntegrationTest from test.utils_shared import is_greenthread_patched -from bson.objectid import ObjectId - @unittest.skipIf( not hasattr(os, "register_at_fork"), "register_at_fork not available in this version of Python" diff --git a/test/test_grid_file.py b/test/test_grid_file.py index 6fe209f438..dcd186361c 100644 --- a/test/test_grid_file.py +++ b/test/test_grid_file.py @@ -14,6 +14,7 @@ # limitations under the License. """Tests for the grid_file module.""" + from __future__ import annotations import datetime @@ -21,6 +22,8 @@ import sys import zipfile from io import BytesIO + +from pymongo.synchronous.database import Database from test import ( IntegrationTest, UnitTest, @@ -29,12 +32,8 @@ unittest, ) -from pymongo.synchronous.database import Database - sys.path[0:0] = [""] -from test.utils_shared import OvertCommandListener - from bson.objectid import ObjectId from gridfs.errors import NoFile from gridfs.synchronous.grid_file import ( @@ -50,6 +49,7 @@ from pymongo.errors import ConfigurationError, ServerSelectionTimeoutError from pymongo.message import _CursorAddress from pymongo.synchronous.helpers import iter, next +from test.utils_shared import OvertCommandListener _IS_SYNC = True diff --git a/test/test_gridfs.py b/test/test_gridfs.py index 8bda041447..ab6a8f969a 100644 --- a/test/test_gridfs.py +++ b/test/test_gridfs.py @@ -14,6 +14,7 @@ # limitations under the License. """Tests for the gridfs package.""" + from __future__ import annotations import asyncio @@ -22,14 +23,11 @@ import threading import time from io import BytesIO -from test.helpers import ConcurrentRunner from unittest.mock import patch -sys.path[0:0] = [""] +from test.helpers import ConcurrentRunner -from test import IntegrationTest, client_context, unittest -from test.utils import joinall -from test.utils_shared import one +sys.path[0:0] = [""] import gridfs from bson.binary import Binary @@ -43,6 +41,9 @@ from pymongo.read_preferences import ReadPreference from pymongo.synchronous.database import Database from pymongo.synchronous.mongo_client import MongoClient +from test import IntegrationTest, client_context, unittest +from test.utils import joinall +from test.utils_shared import one _IS_SYNC = True diff --git a/test/test_gridfs_bucket.py b/test/test_gridfs_bucket.py index 9dbb082ee9..4d1ca7b811 100644 --- a/test/test_gridfs_bucket.py +++ b/test/test_gridfs_bucket.py @@ -14,6 +14,7 @@ # limitations under the License. """Tests for the gridfs package.""" + from __future__ import annotations import asyncio @@ -23,14 +24,11 @@ import threading import time from io import BytesIO -from test.helpers import ConcurrentRunner from unittest.mock import patch -sys.path[0:0] = [""] +from test.helpers import ConcurrentRunner -from test import IntegrationTest, client_context, unittest -from test.utils import joinall -from test.utils_shared import one +sys.path[0:0] = [""] import gridfs from bson.binary import Binary @@ -46,6 +44,9 @@ ) from pymongo.read_preferences import ReadPreference from pymongo.synchronous.mongo_client import MongoClient +from test import IntegrationTest, client_context, unittest +from test.utils import joinall +from test.utils_shared import one _IS_SYNC = True diff --git a/test/test_gridfs_spec.py b/test/test_gridfs_spec.py index 8e1a37364e..4cd9d689a5 100644 --- a/test/test_gridfs_spec.py +++ b/test/test_gridfs_spec.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the GridFS unified spec tests.""" + from __future__ import annotations import os diff --git a/test/test_heartbeat_monitoring.py b/test/test_heartbeat_monitoring.py index 7864caf6e1..1c0d45f46d 100644 --- a/test/test_heartbeat_monitoring.py +++ b/test/test_heartbeat_monitoring.py @@ -13,19 +13,20 @@ # limitations under the License. """Test the monitoring of the server heartbeats.""" + from __future__ import annotations import sys + from test.utils import MockPool sys.path[0:0] = [""] -from test import IntegrationTest, client_knobs, unittest -from test.utils_shared import HeartbeatEventListener, wait_until - from pymongo.errors import ConnectionFailure from pymongo.hello import Hello, HelloCompat from pymongo.synchronous.monitor import Monitor +from test import IntegrationTest, client_knobs, unittest +from test.utils_shared import HeartbeatEventListener, wait_until _IS_SYNC = True diff --git a/test/test_index_management.py b/test/test_index_management.py index 18552d0640..db093af53a 100644 --- a/test/test_index_management.py +++ b/test/test_index_management.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the auth spec tests.""" + from __future__ import annotations import asyncio @@ -21,20 +22,20 @@ import sys import time import uuid -from typing import Any, Mapping +from collections.abc import Mapping +from typing import Any import pytest sys.path[0:0] = [""] -from test import IntegrationTest, PyMongoTestCase, unittest -from test.unified_format import generate_test_classes, get_test_path -from test.utils_shared import AllowListEventListener, OvertCommandListener - from pymongo.errors import OperationFailure from pymongo.operations import SearchIndexModel from pymongo.read_concern import ReadConcern from pymongo.write_concern import WriteConcern +from test import IntegrationTest, PyMongoTestCase, unittest +from test.unified_format import generate_test_classes, get_test_path +from test.utils_shared import AllowListEventListener, OvertCommandListener _IS_SYNC = True diff --git a/test/test_json_util.py b/test/test_json_util.py index cf2c0efb93..cd86456ab0 100644 --- a/test/test_json_util.py +++ b/test/test_json_util.py @@ -13,6 +13,7 @@ # limitations under the License. """Test some utilities for working with JSON and PyMongo.""" + from __future__ import annotations import datetime @@ -21,14 +22,12 @@ import sys import uuid from collections import OrderedDict -from typing import Any, Tuple, Type +from typing import Any from bson.codec_options import CodecOptions, DatetimeConversion sys.path[0:0] = [""] -from test import unittest - from bson import EPOCH_AWARE, EPOCH_NAIVE, SON, DatetimeMS, json_util from bson.binary import ( ALL_UUID_REPRESENTATIONS, @@ -57,6 +56,7 @@ from bson.regex import Regex from bson.timestamp import Timestamp from bson.tz_util import FixedOffset, utc +from test import unittest STRICT_JSON_OPTIONS = JSONOptions( strict_number_long=True, @@ -586,7 +586,7 @@ def test_loads_document_class(self): self.assertIsInstance(doc["d"], cls) def test_encode_subclass(self): - cases: list[Tuple[Type, Any]] = [ + cases: list[tuple[type, Any]] = [ (int, (1,)), (int, (2 << 60,)), (float, (1.1,)), diff --git a/test/test_json_util_integration.py b/test/test_json_util_integration.py index 4ef5f10fe2..129f28c2ef 100644 --- a/test/test_json_util_integration.py +++ b/test/test_json_util_integration.py @@ -1,10 +1,11 @@ from __future__ import annotations -from test import IntegrationTest -from typing import Any, List, MutableMapping +from collections.abc import MutableMapping +from typing import Any from bson import Binary, Code, DBRef, ObjectId, json_util from bson.binary import USER_DEFINED_SUBTYPE +from test import IntegrationTest _IS_SYNC = True @@ -14,7 +15,7 @@ def test_cursor(self): db = self.db db.drop_collection("test") - docs: List[MutableMapping[str, Any]] = [ + docs: list[MutableMapping[str, Any]] = [ {"foo": [1, 2]}, {"bar": {"hello": "world"}}, {"code": Code("function x() { return 1; }")}, diff --git a/test/test_load_balancer.py b/test/test_load_balancer.py index de4b14e546..37ad9b748c 100644 --- a/test/test_load_balancer.py +++ b/test/test_load_balancer.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Load Balancer unified spec tests.""" + from __future__ import annotations import asyncio @@ -22,13 +23,15 @@ import sys import threading from asyncio import Event -from test.helpers import ConcurrentRunner, ExceptionCatchingTask -from test.utils import get_pool import pytest +from test.helpers import ConcurrentRunner, ExceptionCatchingTask +from test.utils import get_pool + sys.path[0:0] = [""] +from pymongo.synchronous.helpers import next from test import IntegrationTest, client_context, unittest from test.unified_format import generate_test_classes, get_test_path from test.utils_shared import ( @@ -36,8 +39,6 @@ wait_until, ) -from pymongo.synchronous.helpers import next - _IS_SYNC = True pytestmark = pytest.mark.load_balancer diff --git a/test/test_logger.py b/test/test_logger.py index a7d97927fa..aec1167c26 100644 --- a/test/test_logger.py +++ b/test/test_logger.py @@ -14,12 +14,12 @@ from __future__ import annotations import os -from test import IntegrationTest, client_context, unittest from unittest.mock import patch from bson import json_util from pymongo.errors import OperationFailure from pymongo.logger import _DEFAULT_DOCUMENT_LENGTH +from test import IntegrationTest, client_context, unittest _IS_SYNC = True diff --git a/test/test_max_staleness.py b/test/test_max_staleness.py index 56e047fd4b..a5a5561096 100644 --- a/test/test_max_staleness.py +++ b/test/test_max_staleness.py @@ -13,6 +13,7 @@ # limitations under the License. """Test maxStalenessSeconds support.""" + from __future__ import annotations import asyncio @@ -27,11 +28,10 @@ sys.path[0:0] = [""] -from test import PyMongoTestCase, client_context, unittest -from test.utils_selection_tests import create_selection_tests - from pymongo.errors import ConfigurationError from pymongo.server_selectors import writable_server_selector +from test import PyMongoTestCase, client_context, unittest +from test.utils_selection_tests import create_selection_tests _IS_SYNC = True diff --git a/test/test_mongos_load_balancing.py b/test/test_mongos_load_balancing.py index 8c31854343..362f67925c 100644 --- a/test/test_mongos_load_balancing.py +++ b/test/test_mongos_load_balancing.py @@ -13,24 +13,24 @@ # limitations under the License. """Test MongoClient's mongos load balancing using a mock.""" + from __future__ import annotations import asyncio import sys import threading -from test.helpers import ConcurrentRunner from pymongo.operations import _Op +from test.helpers import ConcurrentRunner sys.path[0:0] = [""] -from test import MockClientTest, client_context, connected, unittest -from test.pymongo_mocks import MockClient -from test.utils_shared import wait_until - from pymongo.errors import AutoReconnect, InvalidOperation from pymongo.server_selectors import writable_server_selector from pymongo.topology_description import TOPOLOGY_TYPE +from test import MockClientTest, client_context, connected, unittest +from test.pymongo_mocks import MockClient +from test.utils_shared import wait_until _IS_SYNC = True diff --git a/test/test_monitor.py b/test/test_monitor.py index c10662c893..3f24a5f2a2 100644 --- a/test/test_monitor.py +++ b/test/test_monitor.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the monitor module.""" + from __future__ import annotations import asyncio @@ -24,14 +25,13 @@ sys.path[0:0] = [""] +from pymongo.periodic_executor import _EXECUTORS from test import IntegrationTest, client_context, connected, unittest from test.utils import ( wait_until, ) from test.utils_shared import ServerAndTopologyEventListener, gevent_monkey_patched -from pymongo.periodic_executor import _EXECUTORS - _IS_SYNC = True diff --git a/test/test_monitoring.py b/test/test_monitoring.py index 7cb93adf81..e8047a6848 100644 --- a/test/test_monitoring.py +++ b/test/test_monitoring.py @@ -22,6 +22,15 @@ sys.path[0:0] = [""] +from bson.int64 import Int64 +from bson.objectid import ObjectId +from bson.son import SON +from pymongo import CursorType, DeleteOne, InsertOne, UpdateOne, monitoring +from pymongo.errors import AutoReconnect, NotPrimaryError, OperationFailure +from pymongo.read_preferences import ReadPreference +from pymongo.synchronous.command_cursor import CommandCursor +from pymongo.synchronous.helpers import next +from pymongo.write_concern import WriteConcern from test import ( IntegrationTest, client_context, @@ -35,16 +44,6 @@ wait_until, ) -from bson.int64 import Int64 -from bson.objectid import ObjectId -from bson.son import SON -from pymongo import CursorType, DeleteOne, InsertOne, UpdateOne, monitoring -from pymongo.errors import AutoReconnect, NotPrimaryError, OperationFailure -from pymongo.read_preferences import ReadPreference -from pymongo.synchronous.command_cursor import CommandCursor -from pymongo.synchronous.helpers import next -from pymongo.write_concern import WriteConcern - _IS_SYNC = True diff --git a/test/test_objectid.py b/test/test_objectid.py index dbc61951d1..516ae6b9f9 100644 --- a/test/test_objectid.py +++ b/test/test_objectid.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for the objectid module.""" + from __future__ import annotations import datetime @@ -22,12 +23,11 @@ sys.path[0:0] = [""] -from test import SkipTest, unittest -from test.utils_shared import oid_generated_on_process - from bson.errors import InvalidId from bson.objectid import _MAX_COUNTER_VALUE, ObjectId from bson.tz_util import FixedOffset, utc +from test import SkipTest, unittest +from test.utils_shared import oid_generated_on_process def oid(x): diff --git a/test/test_ocsp_cache.py b/test/test_ocsp_cache.py index 1cc025ccb2..1de1b12ed2 100644 --- a/test/test_ocsp_cache.py +++ b/test/test_ocsp_cache.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the pymongo ocsp_support module.""" + from __future__ import annotations import random @@ -25,9 +26,8 @@ sys.path[0:0] = [""] -from test import unittest - from pymongo.ocsp_cache import _OCSPCache +from test import unittest class TestOcspCache(unittest.TestCase): diff --git a/test/test_ocsp_support.py b/test/test_ocsp_support.py index 726e33bfbc..63b6df37c9 100644 --- a/test/test_ocsp_support.py +++ b/test/test_ocsp_support.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for pymongo.ocsp_support.""" + from __future__ import annotations import logging @@ -27,10 +28,9 @@ sys.path[0:0] = [""] -from test import unittest - import pymongo from pymongo.errors import ServerSelectionTimeoutError +from test import unittest pytestmark = pytest.mark.ocsp diff --git a/test/test_on_demand_csfle.py b/test/test_on_demand_csfle.py index 648e46815a..24a0f917e2 100644 --- a/test/test_on_demand_csfle.py +++ b/test/test_on_demand_csfle.py @@ -13,6 +13,7 @@ # limitations under the License. """Test client side encryption with on demand credentials.""" + from __future__ import annotations import os @@ -23,14 +24,13 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context - from bson.codec_options import CodecOptions from pymongo.synchronous.encryption import ( _HAVE_PYMONGOCRYPT, ClientEncryption, EncryptionError, ) +from test import IntegrationTest, client_context _IS_SYNC = True diff --git a/test/test_operations.py b/test/test_operations.py index 3ee6677735..2b890b2357 100644 --- a/test/test_operations.py +++ b/test/test_operations.py @@ -13,14 +13,14 @@ # limitations under the License. """Test the operations module.""" -from __future__ import annotations -from test import UnitTest, unittest +from __future__ import annotations from pymongo import ASCENDING, DESCENDING from pymongo.collation import Collation from pymongo.errors import OperationFailure from pymongo.operations import IndexModel, SearchIndexModel +from test import UnitTest, unittest class TestOperationsBase(UnitTest): diff --git a/test/test_periodic_executor.py b/test/test_periodic_executor.py index 0797e4c71e..e6bbb31a72 100644 --- a/test/test_periodic_executor.py +++ b/test/test_periodic_executor.py @@ -23,9 +23,8 @@ sys.path[0:0] = [""] -from test import UnitTest, unittest - from pymongo.periodic_executor import PeriodicExecutor +from test import UnitTest, unittest _IS_SYNC = True diff --git a/test/test_pooling.py b/test/test_pooling.py index 95558d00d5..410eccb670 100644 --- a/test/test_pooling.py +++ b/test/test_pooling.py @@ -13,6 +13,7 @@ # limitations under the License. """Test built in connection-pooling with threads.""" + from __future__ import annotations import asyncio @@ -21,7 +22,6 @@ import socket import sys import time -from test.utils import flaky, get_pool, joinall from bson.codec_options import DEFAULT_CODEC_OPTIONS from bson.son import SON @@ -29,16 +29,16 @@ from pymongo.errors import AutoReconnect, ConnectionFailure, DuplicateKeyError from pymongo.hello import HelloCompat from pymongo.lock import _create_lock +from test.utils import flaky, get_pool, joinall sys.path[0:0] = [""] +from pymongo.socket_checker import SocketChecker +from pymongo.synchronous.pool import Pool, PoolOptions from test import IntegrationTest, client_context, unittest from test.helpers import ConcurrentRunner from test.utils_shared import delay -from pymongo.socket_checker import SocketChecker -from pymongo.synchronous.pool import Pool, PoolOptions - _IS_SYNC = True diff --git a/test/test_pymongo.py b/test/test_pymongo.py index fd8ece6c03..c52d2d2a3a 100644 --- a/test/test_pymongo.py +++ b/test/test_pymongo.py @@ -13,16 +13,16 @@ # limitations under the License. """Test the pymongo module itself.""" + from __future__ import annotations import sys sys.path[0:0] = [""] -from test import unittest - import pymongo from pymongo._version import get_version_tuple +from test import unittest class TestPyMongo(unittest.TestCase): diff --git a/test/test_raw_bson.py b/test/test_raw_bson.py index 4d9a3ceb05..0c068d7dbf 100644 --- a/test/test_raw_bson.py +++ b/test/test_raw_bson.py @@ -19,14 +19,13 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest - from bson import Code, DBRef, decode, encode from bson.binary import JAVA_LEGACY, Binary, UuidRepresentation from bson.codec_options import CodecOptions from bson.errors import InvalidBSON from bson.raw_bson import DEFAULT_RAW_BSON_OPTIONS, RawBSONDocument from bson.son import SON +from test import IntegrationTest, client_context, unittest _IS_SYNC = True diff --git a/test/test_read_concern.py b/test/test_read_concern.py index 62b2491475..93398d14c9 100644 --- a/test/test_read_concern.py +++ b/test/test_read_concern.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the read_concern module.""" + from __future__ import annotations import sys @@ -20,12 +21,11 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context -from test.utils_shared import OvertCommandListener - from bson.son import SON from pymongo.errors import OperationFailure from pymongo.read_concern import ReadConcern +from test import IntegrationTest, client_context +from test.utils_shared import OvertCommandListener _IS_SYNC = True diff --git a/test/test_read_preferences.py b/test/test_read_preferences.py index afde01723d..27c8d0704a 100644 --- a/test/test_read_preferences.py +++ b/test/test_read_preferences.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the replica_set_connection module.""" + from __future__ import annotations import contextlib @@ -26,21 +27,6 @@ sys.path[0:0] = [""] -from test import ( - IntegrationTest, - SkipTest, - client_context, - connected, - unittest, -) -from test.utils_shared import ( - OvertCommandListener, - _ignore_deprecations, - one, - wait_until, -) -from test.version import Version - from bson.son import SON from pymongo.errors import ConfigurationError, OperationFailure from pymongo.message import _maybe_add_read_preference @@ -59,6 +45,20 @@ from pymongo.synchronous.helpers import next from pymongo.synchronous.mongo_client import MongoClient from pymongo.write_concern import WriteConcern +from test import ( + IntegrationTest, + SkipTest, + client_context, + connected, + unittest, +) +from test.utils_shared import ( + OvertCommandListener, + _ignore_deprecations, + one, + wait_until, +) +from test.version import Version _IS_SYNC = True diff --git a/test/test_read_write_concern_spec.py b/test/test_read_write_concern_spec.py index 54946c3ea0..d1afe3e2b0 100644 --- a/test/test_read_write_concern_spec.py +++ b/test/test_read_write_concern_spec.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the read and write concern tests.""" + from __future__ import annotations import json @@ -23,10 +24,6 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest -from test.unified_format import generate_test_classes, get_test_path -from test.utils_shared import OvertCommandListener - from pymongo import DESCENDING from pymongo.errors import ( BulkWriteError, @@ -39,6 +36,9 @@ from pymongo.read_concern import ReadConcern from pymongo.synchronous.mongo_client import MongoClient from pymongo.write_concern import WriteConcern +from test import IntegrationTest, client_context, unittest +from test.unified_format import generate_test_classes, get_test_path +from test.utils_shared import OvertCommandListener _IS_SYNC = True diff --git a/test/test_replica_set_reconfig.py b/test/test_replica_set_reconfig.py index 3371543f27..cc948bd247 100644 --- a/test/test_replica_set_reconfig.py +++ b/test/test_replica_set_reconfig.py @@ -13,19 +13,19 @@ # limitations under the License. """Test clients and replica set configuration changes, using mocks.""" + from __future__ import annotations import sys sys.path[0:0] = [""] +from pymongo import ReadPreference +from pymongo.errors import ConnectionFailure, ServerSelectionTimeoutError from test import MockClientTest, client_context, client_knobs, unittest from test.pymongo_mocks import MockClient from test.utils_shared import wait_until -from pymongo import ReadPreference -from pymongo.errors import ConnectionFailure, ServerSelectionTimeoutError - @client_context.require_connection @client_context.require_no_load_balancer diff --git a/test/test_results.py b/test/test_results.py index deb09d7ed4..5ca4b26e04 100644 --- a/test/test_results.py +++ b/test/test_results.py @@ -13,14 +13,13 @@ # limitations under the License. """Test results module.""" + from __future__ import annotations import sys sys.path[0:0] = [""] -from test import unittest - from pymongo.errors import InvalidOperation from pymongo.results import ( BulkWriteResult, @@ -29,6 +28,7 @@ InsertOneResult, UpdateResult, ) +from test import unittest class TestResults(unittest.TestCase): diff --git a/test/test_retryable_reads.py b/test/test_retryable_reads.py index 2da87f8b26..c1cab5a61a 100644 --- a/test/test_retryable_reads.py +++ b/test/test_retryable_reads.py @@ -13,21 +13,29 @@ # limitations under the License. """Test retryable reads spec.""" + from __future__ import annotations import os import pprint import sys import threading -from test.utils import ensure_all_connected, set_fail_point from unittest import mock from pymongo import MongoClient from pymongo.common import MAX_ADAPTIVE_RETRIES from pymongo.errors import OperationFailure, PyMongoError +from test.utils import ensure_all_connected, set_fail_point sys.path[0:0] = [""] +from pymongo.monitoring import ( + CommandFailedEvent, + ConnectionCheckedOutEvent, + ConnectionCheckOutFailedEvent, + ConnectionCheckOutFailedReason, + PoolClearedEvent, +) from test import ( IntegrationTest, PyMongoTestCase, @@ -40,14 +48,6 @@ OvertCommandListener, ) -from pymongo.monitoring import ( - CommandFailedEvent, - ConnectionCheckedOutEvent, - ConnectionCheckOutFailedEvent, - ConnectionCheckOutFailedReason, - PoolClearedEvent, -) - _IS_SYNC = True @@ -201,7 +201,9 @@ def test_retryable_reads_are_retried_on_a_different_mongos_when_one_is_available @client_context.require_multiple_mongoses @client_context.require_failCommand_fail_point - def test_retryable_reads_are_retried_on_the_same_mongos_when_no_others_are_available(self): + def test_retryable_reads_are_retried_on_the_same_mongos_when_no_others_are_available( + self, + ): fail_command = { "configureFailPoint": "failCommand", "mode": {"times": 1}, @@ -281,7 +283,7 @@ def test_retryable_reads_are_retried_on_the_same_implicit_session(self): @client_context.require_failCommand_fail_point @client_context.require_version_min(4, 4, 0) def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_different_replicaset_server_when_one_is_available_and_overload_retargeting_is_enabled( - self + self, ): listener = OvertCommandListener() @@ -326,7 +328,7 @@ def test_03_01_retryable_reads_caused_by_overload_errors_are_retried_on_a_differ @client_context.require_failCommand_fail_point @client_context.require_version_min(4, 4, 0) def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_same_replicaset_server( - self + self, ): listener = OvertCommandListener() @@ -368,7 +370,7 @@ def test_03_02_retryable_reads_caused_by_non_overload_errors_are_retried_on_the_ @client_context.require_failCommand_fail_point @client_context.require_version_min(4, 4, 0) def test_03_03_retryable_reads_caused_by_overload_errors_are_retried_on_the_same_replicaset_server_when_one_is_available_and_overload_retargeting_is_disabled( - self + self, ): listener = OvertCommandListener() diff --git a/test/test_retryable_reads_unified.py b/test/test_retryable_reads_unified.py index c47d89d045..c9250b50c2 100644 --- a/test/test_retryable_reads_unified.py +++ b/test/test_retryable_reads_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Retryable Reads unified spec tests.""" + from __future__ import annotations import os diff --git a/test/test_retryable_writes.py b/test/test_retryable_writes.py index 46ca3a43b8..577f9cbf0f 100644 --- a/test/test_retryable_writes.py +++ b/test/test_retryable_writes.py @@ -13,6 +13,7 @@ # limitations under the License. """Test retryable writes.""" + from __future__ import annotations import asyncio @@ -20,28 +21,13 @@ import pprint import sys import threading -from test.utils import flaky, set_fail_point from unittest import mock from pymongo.common import MAX_ADAPTIVE_RETRIES +from test.utils import flaky, set_fail_point sys.path[0:0] = [""] -from test import ( - IntegrationTest, - SkipTest, - client_context, - unittest, -) -from test.helpers import client_knobs -from test.utils_shared import ( - CMAPListener, - DeprecationFilter, - EventListener, - OvertCommandListener, -) -from test.version import Version - from bson.codec_options import DEFAULT_CODEC_OPTIONS from bson.int64 import Int64 from bson.raw_bson import RawBSONDocument @@ -72,6 +58,20 @@ UpdateOne, ) from pymongo.write_concern import WriteConcern +from test import ( + IntegrationTest, + SkipTest, + client_context, + unittest, +) +from test.helpers import client_knobs +from test.utils_shared import ( + CMAPListener, + DeprecationFilter, + EventListener, + OvertCommandListener, +) +from test.version import Version _IS_SYNC = True @@ -623,7 +623,7 @@ def configure_fail_point_sync(self, command_args, off=False) -> None: self.setup_client.admin.command(cmd) def test_01_drivers_return_the_correct_error_when_receiving_only_errors_without_NoWritesPerformed( - self + self, ) -> None: # Create a client with retryWrites=true. listener = OvertCommandListener() @@ -675,7 +675,7 @@ def failed(event: CommandFailedEvent) -> None: assert exc.exception.errors["code"] == 10107 # type:ignore[call-overload] def test_02_drivers_return_the_correct_error_when_receiving_only_errors_with_NoWritesPerformed( - self + self, ) -> None: # Create a client with retryWrites=true. listener = OvertCommandListener() @@ -728,7 +728,7 @@ def failed(event: CommandFailedEvent) -> None: assert exc.exception.errors["code"] == 91 # type:ignore[call-overload] def test_03_drivers_return_the_correct_error_when_receiving_some_errors_with_NoWritesPerformed_and_some_without_NoWritesPerformed( - self + self, ) -> None: # Create a client with retryWrites=true. listener = OvertCommandListener() diff --git a/test/test_retryable_writes_unified.py b/test/test_retryable_writes_unified.py index d06ee206fc..1f0c4a7138 100644 --- a/test/test_retryable_writes_unified.py +++ b/test/test_retryable_writes_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Retryable Writes unified spec tests.""" + from __future__ import annotations import os diff --git a/test/test_run_command.py b/test/test_run_command.py index df835fb6d7..6a04726f59 100644 --- a/test/test_run_command.py +++ b/test/test_run_command.py @@ -13,11 +13,13 @@ # limitations under the License. """Run Command unified tests.""" + from __future__ import annotations import os import unittest from pathlib import Path + from test.unified_format import generate_test_classes, get_test_path _IS_SYNC = True diff --git a/test/test_saslprep.py b/test/test_saslprep.py index e825cafa35..9c070edb7a 100644 --- a/test/test_saslprep.py +++ b/test/test_saslprep.py @@ -17,9 +17,8 @@ sys.path[0:0] = [""] -from test import unittest - from pymongo.saslprep import saslprep +from test import unittest class TestSASLprep(unittest.TestCase): @@ -32,10 +31,10 @@ def test_saslprep(self): self.assertEqual(saslprep(b"user"), b"user") else: # Examples from RFC4013, Section 3. - self.assertEqual(saslprep("I\u00ADX"), "IX") + self.assertEqual(saslprep("I\u00adX"), "IX") self.assertEqual(saslprep("user"), "user") self.assertEqual(saslprep("USER"), "USER") - self.assertEqual(saslprep("\u00AA"), "a") + self.assertEqual(saslprep("\u00aa"), "a") self.assertEqual(saslprep("\u2168"), "IX") self.assertRaises(ValueError, saslprep, "\u0007") self.assertRaises(ValueError, saslprep, "\u0627\u0031") diff --git a/test/test_sdam_monitoring_spec.py b/test/test_sdam_monitoring_spec.py index 2167e561cf..f289f2729d 100644 --- a/test/test_sdam_monitoring_spec.py +++ b/test/test_sdam_monitoring_spec.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the sdam monitoring spec tests.""" + from __future__ import annotations import asyncio @@ -24,13 +25,6 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, client_knobs, unittest -from test.utils_shared import ( - ServerAndTopologyEventListener, - server_name_to_type, - wait_until, -) - from bson.json_util import object_hook from pymongo import MongoClient, monitoring from pymongo.common import clean_node @@ -40,6 +34,12 @@ from pymongo.synchronous.collection import Collection from pymongo.synchronous.monitor import Monitor from pymongo.topology_description import TOPOLOGY_TYPE +from test import IntegrationTest, client_context, client_knobs, unittest +from test.utils_shared import ( + ServerAndTopologyEventListener, + server_name_to_type, + wait_until, +) _IS_SYNC = True @@ -89,7 +89,7 @@ def compare_events(expected_dict, actual): if expected["address"] != "{}:{}".format(*actual.server_address): return ( False, - "ServerOpeningEvent published with wrong address (expected" " {}, got {}".format( + "ServerOpeningEvent published with wrong address (expected {}, got {}".format( expected["address"], actual.server_address ), ) @@ -100,7 +100,7 @@ def compare_events(expected_dict, actual): if expected["address"] != "{}:{}".format(*actual.server_address): return ( False, - "ServerDescriptionChangedEvent has wrong address" " (expected {}, got {}".format( + "ServerDescriptionChangedEvent has wrong address (expected {}, got {}".format( expected["address"], actual.server_address ), ) @@ -121,7 +121,7 @@ def compare_events(expected_dict, actual): if expected["address"] != "{}:{}".format(*actual.server_address): return ( False, - "ServerClosedEvent published with wrong address" " (expected {}, got {}".format( + "ServerClosedEvent published with wrong address (expected {}, got {}".format( expected["address"], actual.server_address ), ) diff --git a/test/test_server.py b/test/test_server.py index ab5a40a79b..141fb6195a 100644 --- a/test/test_server.py +++ b/test/test_server.py @@ -13,17 +13,17 @@ # limitations under the License. """Test the server module.""" + from __future__ import annotations import sys sys.path[0:0] = [""] -from test import unittest - from pymongo.hello import Hello from pymongo.server_description import ServerDescription from pymongo.synchronous.server import Server +from test import unittest class TestServer(unittest.TestCase): diff --git a/test/test_server_description.py b/test/test_server_description.py index e8c0098cb6..5e48f2fb99 100644 --- a/test/test_server_description.py +++ b/test/test_server_description.py @@ -13,20 +13,20 @@ # limitations under the License. """Test the server_description module.""" + from __future__ import annotations import sys sys.path[0:0] = [""] -from test import unittest - from bson.int64 import Int64 from bson.objectid import ObjectId from pymongo import common from pymongo.hello import Hello, HelloCompat from pymongo.server_description import ServerDescription from pymongo.server_type import SERVER_TYPE +from test import unittest address = ("localhost", 27017) diff --git a/test/test_server_selection.py b/test/test_server_selection.py index d94e9ed0a1..50076a8024 100644 --- a/test/test_server_selection.py +++ b/test/test_server_selection.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the topology module's Server Selection Spec implementation.""" + from __future__ import annotations import os diff --git a/test/test_server_selection_in_window.py b/test/test_server_selection_in_window.py index e6db68ff4d..484086dd66 100644 --- a/test/test_server_selection_in_window.py +++ b/test/test_server_selection_in_window.py @@ -13,12 +13,18 @@ # limitations under the License. """Test the topology module's Server Selection Spec implementation.""" + from __future__ import annotations import asyncio import os import threading from pathlib import Path + +from pymongo.common import clean_node +from pymongo.monitoring import ConnectionReadyEvent +from pymongo.operations import _Op +from pymongo.read_preferences import ReadPreference from test import IntegrationTest, client_context, unittest from test.helpers import ConcurrentRunner from test.utils_selection_tests import create_topology @@ -29,11 +35,6 @@ ) from test.utils_spec_runner import SpecTestCreator -from pymongo.common import clean_node -from pymongo.monitoring import ConnectionReadyEvent -from pymongo.operations import _Op -from pymongo.read_preferences import ReadPreference - _IS_SYNC = True # Location of JSON test specifications. if _IS_SYNC: @@ -55,7 +56,7 @@ def run_scenario(self, scenario_def): server.pool.operation_count = mock["operation_count"] pref = ReadPreference.NEAREST - counts = {address: 0 for address in topology._description.server_descriptions()} + counts = dict.fromkeys(topology._description.server_descriptions(), 0) # Number of times to repeat server selection iterations = scenario_def["iterations"] @@ -128,7 +129,7 @@ def frequencies(self, client, listener, n_finds=10): self.assertEqual(len(events), n_finds * N_TASKS) nodes = client.nodes self.assertEqual(len(nodes), 2) - freqs = {address: 0.0 for address in nodes} + freqs = dict.fromkeys(nodes, 0.0) for event in events: freqs[event.connection_id] += 1 for address in freqs: diff --git a/test/test_server_selection_logging.py b/test/test_server_selection_logging.py index c48e166a19..45e86094fc 100644 --- a/test/test_server_selection_logging.py +++ b/test/test_server_selection_logging.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the server selection logging unified format spec tests.""" + from __future__ import annotations import os diff --git a/test/test_server_selection_rtt.py b/test/test_server_selection_rtt.py index 2aef36a585..3d0dc7ecfe 100644 --- a/test/test_server_selection_rtt.py +++ b/test/test_server_selection_rtt.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the topology module.""" + from __future__ import annotations import json @@ -22,9 +23,8 @@ sys.path[0:0] = [""] -from test import PyMongoTestCase, unittest - from pymongo.read_preferences import MovingAverage +from test import PyMongoTestCase, unittest _IS_SYNC = True diff --git a/test/test_session.py b/test/test_session.py index 684c21260d..4812144ce6 100644 --- a/test/test_session.py +++ b/test/test_session.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the client_session module.""" + from __future__ import annotations import copy @@ -20,11 +21,22 @@ import time from inspect import iscoroutinefunction from io import BytesIO +from typing import Any, Callable + from test.helpers import ExceptionCatchingTask -from typing import Any, Callable, List, Set, Tuple sys.path[0:0] = [""] +from bson import DBRef +from gridfs.synchronous.grid_file import GridFS, GridFSBucket +from pymongo import ASCENDING, MongoClient, monitoring +from pymongo.common import _MAX_END_SESSIONS +from pymongo.errors import ConfigurationError, InvalidOperation, OperationFailure +from pymongo.operations import IndexModel, InsertOne, UpdateOne +from pymongo.read_concern import ReadConcern +from pymongo.synchronous.command_cursor import CommandCursor +from pymongo.synchronous.cursor import Cursor +from pymongo.synchronous.helpers import next from test import ( IntegrationTest, SkipTest, @@ -40,17 +52,6 @@ wait_until, ) -from bson import DBRef -from gridfs.synchronous.grid_file import GridFS, GridFSBucket -from pymongo import ASCENDING, MongoClient, monitoring -from pymongo.common import _MAX_END_SESSIONS -from pymongo.errors import ConfigurationError, InvalidOperation, OperationFailure -from pymongo.operations import IndexModel, InsertOne, UpdateOne -from pymongo.read_concern import ReadConcern -from pymongo.synchronous.command_cursor import CommandCursor -from pymongo.synchronous.cursor import Cursor -from pymongo.synchronous.helpers import next - _IS_SYNC = True @@ -80,7 +81,7 @@ def session_ids(client): class TestSession(IntegrationTest): client2: MongoClient - sensitive_commands: Set[str] + sensitive_commands: set[str] @client_context.require_sessions def setUp(self): @@ -243,7 +244,7 @@ def test_implicit_sessions_checkout(self): # sessions to be used: connection check in happens before session check in for _ in range(10): cursor = client.db.test.find({}) - ops: List[Tuple[Callable, List[Any]]] = [ + ops: list[tuple[Callable, list[Any]]] = [ (client.db.test.find_one, [{"_id": 1}]), (client.db.test.delete_one, [{}]), (client.db.test.update_one, [{}, {"$set": {"x": 2}}]), diff --git a/test/test_sessions_unified.py b/test/test_sessions_unified.py index 3d15fac85f..1a68277ba5 100644 --- a/test/test_sessions_unified.py +++ b/test/test_sessions_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Sessions unified spec tests.""" + from __future__ import annotations import os diff --git a/test/test_son.py b/test/test_son.py index 3d2069a4c2..d5d5573031 100644 --- a/test/test_son.py +++ b/test/test_son.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for the son module.""" + from __future__ import annotations import copy @@ -23,9 +24,9 @@ sys.path[0:0] = [""] from collections import OrderedDict -from test import unittest from bson.son import SON +from test import unittest class TestSON(unittest.TestCase): @@ -177,8 +178,7 @@ def test_keys(self): d - i().keys() except TypeError: self.fail( - "SON().keys() is not returning an object compatible " - "with %s objects" % (str(i)) + "SON().keys() is not returning an object compatible with %s objects" % (str(i)) ) # Test to verify correctness d = SON({"k": "v"}).keys() diff --git a/test/test_srv_polling.py b/test/test_srv_polling.py index f5096bea01..f3d0c3f160 100644 --- a/test/test_srv_polling.py +++ b/test/test_srv_polling.py @@ -13,24 +13,25 @@ # limitations under the License. """Run the SRV support tests.""" + from __future__ import annotations import asyncio import sys import time +from typing import Any + from test.utils import flaky from test.utils_shared import FunctionCallRecorder -from typing import Any sys.path[0:0] = [""] -from test import PyMongoTestCase, client_knobs, unittest -from test.utils import wait_until - import pymongo from pymongo import common from pymongo.errors import ConfigurationError from pymongo.synchronous.srv_resolver import _have_dnspython +from test import PyMongoTestCase, client_knobs, unittest +from test.utils import wait_until _IS_SYNC = True diff --git a/test/test_ssl.py b/test/test_ssl.py index 77bb086ecb..d071a6a0fe 100644 --- a/test/test_ssl.py +++ b/test/test_ssl.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for SSL support.""" + from __future__ import annotations import os @@ -22,6 +23,13 @@ sys.path[0:0] = [""] +from urllib.parse import quote_plus + +from pymongo import MongoClient, ssl_support +from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure +from pymongo.hello import HelloCompat +from pymongo.ssl_support import HAVE_PYSSL, HAVE_SSL, _ssl, get_ssl_context +from pymongo.write_concern import WriteConcern from test import ( HAVE_IPADDRESS, IntegrationTest, @@ -38,13 +46,6 @@ cat_files, ignore_deprecations, ) -from urllib.parse import quote_plus - -from pymongo import MongoClient, ssl_support -from pymongo.errors import ConfigurationError, ConnectionFailure, OperationFailure -from pymongo.hello import HelloCompat -from pymongo.ssl_support import HAVE_PYSSL, HAVE_SSL, _ssl, get_ssl_context -from pymongo.write_concern import WriteConcern _HAVE_PYOPENSSL = False try: diff --git a/test/test_streaming_protocol.py b/test/test_streaming_protocol.py index 927230091f..dddd92d73a 100644 --- a/test/test_streaming_protocol.py +++ b/test/test_streaming_protocol.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the database module.""" + from __future__ import annotations import sys @@ -20,6 +21,8 @@ sys.path[0:0] = [""] +from pymongo import monitoring +from pymongo.hello import HelloCompat from test import IntegrationTest, client_context, unittest from test.utils_shared import ( HeartbeatEventListener, @@ -27,9 +30,6 @@ wait_until, ) -from pymongo import monitoring -from pymongo.hello import HelloCompat - _IS_SYNC = True diff --git a/test/test_threads.py b/test/test_threads.py index 3e469e28fe..fe9f6dcf61 100644 --- a/test/test_threads.py +++ b/test/test_threads.py @@ -13,9 +13,11 @@ # limitations under the License. """Test that pymongo is thread safe.""" + from __future__ import annotations import threading + from test import IntegrationTest, client_context, unittest from test.utils import joinall diff --git a/test/test_timestamp.py b/test/test_timestamp.py index ef7d8bde15..551cc40a2d 100644 --- a/test/test_timestamp.py +++ b/test/test_timestamp.py @@ -13,6 +13,7 @@ # limitations under the License. """Tests for the Timestamp class.""" + from __future__ import annotations import copy @@ -22,10 +23,9 @@ sys.path[0:0] = [""] -from test import unittest - from bson.timestamp import Timestamp from bson.tz_util import utc +from test import unittest class TestTimestamp(unittest.TestCase): diff --git a/test/test_topology.py b/test/test_topology.py index 83e269560f..47e670b20d 100644 --- a/test/test_topology.py +++ b/test/test_topology.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the topology module.""" + from __future__ import annotations import sys @@ -21,11 +22,6 @@ sys.path[0:0] = [""] -from test import client_knobs, unittest -from test.pymongo_mocks import DummyMonitor -from test.utils import MockPool -from test.utils_shared import wait_until - from bson.objectid import ObjectId from pymongo import common from pymongo.errors import AutoReconnect, ConfigurationError, ConnectionFailure @@ -40,6 +36,10 @@ from pymongo.synchronous.settings import TopologySettings from pymongo.synchronous.topology import Topology, _ErrorContext from pymongo.topology_description import TOPOLOGY_TYPE +from test import client_knobs, unittest +from test.pymongo_mocks import DummyMonitor +from test.utils import MockPool +from test.utils_shared import wait_until class SetNameDiscoverySettings(TopologySettings): diff --git a/test/test_transactions.py b/test/test_transactions.py index 609105ec21..1ef2881b12 100644 --- a/test/test_transactions.py +++ b/test/test_transactions.py @@ -13,6 +13,7 @@ # limitations under the License. """Execute Transactions Spec tests.""" + from __future__ import annotations import asyncio @@ -29,12 +30,6 @@ sys.path[0:0] = [""] -from test import IntegrationTest, client_context, unittest -from test.utils_shared import ( - OvertCommandListener, - wait_until, -) -from typing import List from bson import encode from bson.raw_bson import RawBSONDocument @@ -57,6 +52,11 @@ from pymongo.synchronous.command_cursor import CommandCursor from pymongo.synchronous.cursor import Cursor from pymongo.synchronous.helpers import next +from test import IntegrationTest, client_context, unittest +from test.utils_shared import ( + OvertCommandListener, + wait_until, +) _IS_SYNC = True @@ -312,8 +312,7 @@ def gridfs_open_upload_stream(*args, **kwargs): def test_transaction_starts_with_batched_write(self): if "PyPy" in sys.version and client_context.tls: self.skipTest( - "PYTHON-2937 PyPy is so slow sending large " - "messages over TLS that this test fails" + "PYTHON-2937 PyPy is so slow sending large messages over TLS that this test fails" ) # Start a transaction with a batch of operations that needs to be # split. @@ -324,7 +323,7 @@ def test_transaction_starts_with_batched_write(self): listener.reset() self.addCleanup(coll.drop) large_str = "\0" * (1 * 1024 * 1024) - ops: List[InsertOne[RawBSONDocument]] = [ + ops: list[InsertOne[RawBSONDocument]] = [ InsertOne(RawBSONDocument(encode({"a": large_str}))) for _ in range(48) ] with client.start_session() as session: diff --git a/test/test_transactions_unified.py b/test/test_transactions_unified.py index 05e4a1e5c3..730dc4da63 100644 --- a/test/test_transactions_unified.py +++ b/test/test_transactions_unified.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the Transactions unified spec tests.""" + from __future__ import annotations import os diff --git a/test/test_typing.py b/test/test_typing.py index 17dc21b4e0..c4c80189ae 100644 --- a/test/test_typing.py +++ b/test/test_typing.py @@ -22,14 +22,11 @@ import sys import tempfile import unittest +from collections.abc import Iterable, Iterator from typing import ( TYPE_CHECKING, Any, Callable, - Dict, - Iterable, - Iterator, - List, TypeVar, Union, cast, @@ -67,8 +64,6 @@ class ImplicitMovie(TypedDict): sys.path[0:0] = [""] -from test import IntegrationTest, PyMongoTestCase, client_context - from bson import CodecOptions, ObjectId, decode, decode_all, decode_file_iter, decode_iter, encode from bson.raw_bson import RawBSONDocument from bson.son import SON @@ -76,6 +71,7 @@ class ImplicitMovie(TypedDict): from pymongo.operations import DeleteOne, InsertOne, ReplaceOne from pymongo.read_preferences import ReadPreference from pymongo.synchronous.collection import Collection +from test import IntegrationTest, PyMongoTestCase, client_context TEST_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "mypy_fails") @@ -102,7 +98,7 @@ class TestMypyFails(unittest.TestCase): def ensure_mypy_fails(self, filename: str) -> None: if api is None: raise unittest.SkipTest("Mypy is not installed") - stdout, stderr, exit_status = api.run([filename]) + stdout, _, exit_status = api.run([filename]) self.assertTrue(exit_status, msg=stdout) def test_mypy_failures(self) -> None: @@ -133,7 +129,7 @@ def test_insert_find(self) -> None: self.assertEqual(result2.inserted_id, result.inserted_id) def test_cursor_iterable(self) -> None: - def to_list(iterable: Iterable[Dict[str, Any]]) -> List[Dict[str, Any]]: + def to_list(iterable: Iterable[dict[str, Any]]) -> list[dict[str, Any]]: return list(iterable) self.coll.insert_one({}) @@ -171,10 +167,10 @@ def cursor_distinct( def test_bulk_write(self) -> None: self.coll.insert_one({}) coll: Collection[Movie] = self.coll - requests: List[InsertOne[Movie]] = [InsertOne(Movie(name="American Graffiti", year=1973))] + requests: list[InsertOne[Movie]] = [InsertOne(Movie(name="American Graffiti", year=1973))] self.assertTrue(coll.bulk_write(requests).acknowledged) - new_requests: List[Union[InsertOne[Movie], ReplaceOne[Movie]]] = [] - input_list: List[Union[InsertOne[Movie], ReplaceOne[Movie]]] = [ + new_requests: list[Union[InsertOne[Movie], ReplaceOne[Movie]]] = [] + input_list: list[Union[InsertOne[Movie], ReplaceOne[Movie]]] = [ InsertOne(Movie(name="American Graffiti", year=1973)), ReplaceOne({}, Movie(name="American Graffiti", year=1973)), ] @@ -186,13 +182,13 @@ def test_bulk_write(self) -> None: @only_type_check def test_bulk_write_heterogeneous(self): coll: Collection[Movie] = self.coll - requests: List[Union[InsertOne[Movie], ReplaceOne, DeleteOne]] = [ + requests: list[Union[InsertOne[Movie], ReplaceOne, DeleteOne]] = [ InsertOne(Movie(name="American Graffiti", year=1973)), ReplaceOne({}, {"name": "American Graffiti", "year": "WRONG_TYPE"}), DeleteOne({}), ] self.assertTrue(coll.bulk_write(requests).acknowledged) - requests_two: List[Union[InsertOne[Movie], ReplaceOne[Movie], DeleteOne]] = [ + requests_two: list[Union[InsertOne[Movie], ReplaceOne[Movie], DeleteOne]] = [ InsertOne(Movie(name="American Graffiti", year=1973)), ReplaceOne( {}, @@ -203,7 +199,7 @@ def test_bulk_write_heterogeneous(self): self.assertTrue(coll.bulk_write(requests_two).acknowledged) def test_command(self) -> None: - result: Dict = self.client.admin.command("ping") + result: dict = self.client.admin.command("ping") result.items() def test_list_collections(self) -> None: @@ -237,7 +233,7 @@ def test_aggregate_pipeline(self) -> None: ] ) - class mydict(Dict[str, Any]): + class mydict(dict[str, Any]): pass result = coll3.aggregate( @@ -259,7 +255,7 @@ def execute_transaction(session): ) def test_with_options(self) -> None: - coll: Collection[Dict[str, Any]] = self.coll + coll: Collection[dict[str, Any]] = self.coll coll.drop() doc = {"name": "foo", "year": 1982, "other": 1} coll.insert_one(doc) @@ -276,11 +272,11 @@ class TestDecode(unittest.TestCase): def test_bson_decode(self) -> None: doc = {"_id": 1} bsonbytes = encode(doc) - rt_document: Dict[str, Any] = decode(bsonbytes) + rt_document: dict[str, Any] = decode(bsonbytes) assert rt_document["_id"] == 1 rt_document["foo"] = "bar" - class MyDict(Dict[str, Any]): + class MyDict(dict[str, Any]): def foo(self): return "bar" @@ -303,11 +299,11 @@ def test_bson_decode_all(self) -> None: doc = {"_id": 1} bsonbytes = encode(doc) bsonbytes += encode(doc) - rt_documents: List[Dict[str, Any]] = decode_all(bsonbytes) + rt_documents: list[dict[str, Any]] = decode_all(bsonbytes) assert rt_documents[0]["_id"] == 1 rt_documents[0]["foo"] = "bar" - class MyDict(Dict[str, Any]): + class MyDict(dict[str, Any]): def foo(self): return "bar" @@ -336,11 +332,11 @@ def test_bson_decode_iter(self) -> None: doc = {"_id": 1} bsonbytes = encode(doc) bsonbytes += encode(doc) - rt_documents: Iterator[Dict[str, Any]] = decode_iter(bsonbytes) + rt_documents: Iterator[dict[str, Any]] = decode_iter(bsonbytes) assert next(rt_documents)["_id"] == 1 next(rt_documents)["foo"] = "bar" - class MyDict(Dict[str, Any]): + class MyDict(dict[str, Any]): def foo(self): return "bar" @@ -373,11 +369,11 @@ def test_bson_decode_file_iter(self) -> None: bsonbytes = encode(doc) bsonbytes += encode(doc) fileobj = self.make_tempfile(bsonbytes) - rt_documents: Iterator[Dict[str, Any]] = decode_file_iter(fileobj) + rt_documents: Iterator[dict[str, Any]] = decode_file_iter(fileobj) assert next(rt_documents)["_id"] == 1 next(rt_documents)["foo"] = "bar" - class MyDict(Dict[str, Any]): + class MyDict(dict[str, Any]): def foo(self): return "bar" @@ -413,7 +409,7 @@ def test_default(self) -> None: @only_type_check def test_explicit_document_type(self) -> None: - client: MongoClient[Dict[str, Any]] = MongoClient() + client: MongoClient[dict[str, Any]] = MongoClient() coll = client.test.test retrieved = coll.find_one({"_id": "foo"}) assert retrieved is not None @@ -545,7 +541,7 @@ def test_son_document_type_runtime(self) -> None: @only_type_check def test_create_index(self) -> None: - client: MongoClient[Dict[str, str]] = MongoClient("test") + client: MongoClient[dict[str, str]] = MongoClient("test") db = client.test with client.start_session() as session: index = db.test.create_index([("user_id", ASCENDING)], unique=True, session=session) @@ -556,13 +552,13 @@ class TestCommandDocumentType(unittest.TestCase): @only_type_check def test_default(self) -> None: client: MongoClient = MongoClient() - result: Dict = client.admin.command("ping") + result: dict = client.admin.command("ping") result["a"] = 1 @only_type_check def test_explicit_document_type(self) -> None: client: MongoClient = MongoClient() - codec_options: CodecOptions[Dict[str, Any]] = CodecOptions() + codec_options: CodecOptions[dict[str, Any]] = CodecOptions() result = client.admin.command("ping", codec_options=codec_options) result["a"] = 1 @@ -596,7 +592,7 @@ def test_default(self) -> None: obj["a"] = 1 def test_explicit_document_type(self) -> None: - options: CodecOptions[Dict[str, Any]] = CodecOptions() + options: CodecOptions[dict[str, Any]] = CodecOptions() obj = options.document_class() obj["a"] = 1 diff --git a/test/test_typing_strict.py b/test/test_typing_strict.py index 32e9fcfcca..433adef841 100644 --- a/test/test_typing_strict.py +++ b/test/test_typing_strict.py @@ -13,10 +13,11 @@ # limitations under the License. """Test typings in strict mode.""" + from __future__ import annotations import unittest -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any import pymongo from pymongo.synchronous.collection import Collection @@ -27,7 +28,7 @@ def test_generic_arguments() -> None: """Ensure known usages of generic arguments pass strict typing""" if not TYPE_CHECKING: raise unittest.SkipTest("Used for Type Checking Only") - mongo_client: pymongo.MongoClient[Dict[str, Any]] = pymongo.MongoClient() + mongo_client: pymongo.MongoClient[dict[str, Any]] = pymongo.MongoClient() mongo_client.drop_database("foo") mongo_client.get_default_database() db = mongo_client.get_database("test_db") diff --git a/test/test_unified_format.py b/test/test_unified_format.py index a55f810473..4a4323e4ba 100644 --- a/test/test_unified_format.py +++ b/test/test_unified_format.py @@ -20,6 +20,7 @@ sys.path[0:0] = [""] +from bson import ObjectId from test import UnitTest, unittest from test.unified_format import ( MatchEvaluatorUtil, @@ -27,8 +28,6 @@ get_test_path, ) -from bson import ObjectId - _IS_SYNC = True # Location of JSON test specifications. diff --git a/test/test_uri_parser.py b/test/test_uri_parser.py index 8781ee184e..4fe5cb7a0f 100644 --- a/test/test_uri_parser.py +++ b/test/test_uri_parser.py @@ -13,6 +13,7 @@ # limitations under the License. """Test the pymongo uri_parser module.""" + from __future__ import annotations import copy @@ -23,7 +24,6 @@ sys.path[0:0] = [""] -from test import unittest from unittest.mock import patch from bson.binary import JAVA_LEGACY @@ -38,6 +38,7 @@ split_hosts, split_options, ) +from test import unittest class TestURI(unittest.TestCase): @@ -233,9 +234,7 @@ def test_parse_uri(self): self.assertEqual( res, parse_uri( - "mongodb://%2Ftmp%2Fmongodb-27017.sock," - "example2.com:27017" - "/test.yield_historical.in" + "mongodb://%2Ftmp%2Fmongodb-27017.sock,example2.com:27017/test.yield_historical.in" ), ) @@ -400,9 +399,7 @@ def test_parse_uri(self): self.assertEqual( res, parse_uri( - "mongodb://user%40domain.com:password" - "@localhost/foo?uuidrepresentation=" - "javaLegacy" + "mongodb://user%40domain.com:password@localhost/foo?uuidrepresentation=javaLegacy" ), ) @@ -411,8 +408,7 @@ def test_parse_uri(self): self.assertRaises( Warning, parse_uri, - "mongodb://user%40domain.com:password" - "@localhost/foo?uuidrepresentation=notAnOption", + "mongodb://user%40domain.com:password@localhost/foo?uuidrepresentation=notAnOption", warn=True, ) self.assertRaises( diff --git a/test/test_uri_spec.py b/test/test_uri_spec.py index 3d8f7b2b75..c44d57ab8c 100644 --- a/test/test_uri_spec.py +++ b/test/test_uri_spec.py @@ -15,6 +15,7 @@ """Test that the pymongo.uri_parser module is compliant with the connection string and uri options specifications. """ + from __future__ import annotations import json @@ -24,12 +25,11 @@ sys.path[0:0] = [""] -from test import unittest -from test.helpers_shared import clear_warning_registry - from pymongo.common import INTERNAL_URI_OPTION_NAME_MAP, _CaseInsensitiveDictionary, validate from pymongo.compression_support import _have_snappy from pymongo.synchronous.uri_parser import parse_uri +from test import unittest +from test.helpers_shared import clear_warning_registry CONN_STRING_TEST_PATH = os.path.join( os.path.dirname(os.path.realpath(__file__)), os.path.join("connection_string", "test") diff --git a/test/test_versioned_api.py b/test/test_versioned_api.py index 19b125770f..d6e2d0cde8 100644 --- a/test/test_versioned_api.py +++ b/test/test_versioned_api.py @@ -14,14 +14,14 @@ from __future__ import annotations import sys + from test import UnitTest sys.path[0:0] = [""] -from test import unittest - from pymongo.mongo_client import MongoClient from pymongo.server_api import ServerApi, ServerApiVersion +from test import unittest class TestServerApi(UnitTest): diff --git a/test/test_versioned_api_integration.py b/test/test_versioned_api_integration.py index c4ee7856f3..e97ed16601 100644 --- a/test/test_versioned_api_integration.py +++ b/test/test_versioned_api_integration.py @@ -16,15 +16,15 @@ import os import sys from pathlib import Path + from test.unified_format import generate_test_classes, get_test_path sys.path[0:0] = [""] +from pymongo.server_api import ServerApi from test import IntegrationTest, client_context, unittest from test.utils_shared import OvertCommandListener -from pymongo.server_api import ServerApi - _IS_SYNC = True # Generate unified tests. diff --git a/test/test_write_concern.py b/test/test_write_concern.py index 02a7cb6e5c..9eefd62015 100644 --- a/test/test_write_concern.py +++ b/test/test_write_concern.py @@ -13,6 +13,7 @@ # limitations under the License. """Run the unit tests for WriteConcern.""" + from __future__ import annotations import collections diff --git a/test/unicode/test_utf8.py b/test/unicode/test_utf8.py index 578d98bffb..67815d78eb 100644 --- a/test/unicode/test_utf8.py +++ b/test/unicode/test_utf8.py @@ -4,10 +4,9 @@ sys.path[0:0] = [""] -from test import unittest - from bson import encode from bson.errors import InvalidStringData +from test import unittest class TestUTF8(unittest.TestCase): diff --git a/test/unified_format.py b/test/unified_format.py index a7018c01d8..0a71f03504 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -16,6 +16,7 @@ https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md """ + from __future__ import annotations import asyncio @@ -28,39 +29,10 @@ import time import traceback from collections import defaultdict +from collections.abc import Mapping from inspect import iscoroutinefunction from pathlib import Path -from test import ( - IntegrationTest, - client_context, - client_knobs, - unittest, -) -from test.helpers_shared import ALL_KMS_PROVIDERS, DEFAULT_KMS_TLS -from test.unified_format_shared import ( - KMS_TLS_OPTS, - PLACEHOLDER_MAP, - EventListenerUtil, - MatchEvaluatorUtil, - coerce_result, - parse_bulk_write_error_result, - parse_bulk_write_result, - parse_client_bulk_write_error_result, - parse_collection_or_database_options, - with_metaclass, -) -from test.utils import flaky, get_pool -from test.utils_shared import ( - camel_to_snake, - camel_to_snake_args, - parse_spec_options, - prepare_spec_arguments, - snake_to_camel, - wait_until, -) -from test.utils_spec_runner import SpecRunnerThread -from test.version import Version -from typing import Any, Dict, List, Mapping, Optional +from typing import Any, Optional import pytest @@ -106,6 +78,36 @@ from pymongo.topology_description import TopologyDescription from pymongo.typings import _Address from pymongo.write_concern import WriteConcern +from test import ( + IntegrationTest, + client_context, + client_knobs, + unittest, +) +from test.helpers_shared import ALL_KMS_PROVIDERS, DEFAULT_KMS_TLS +from test.unified_format_shared import ( + KMS_TLS_OPTS, + PLACEHOLDER_MAP, + EventListenerUtil, + MatchEvaluatorUtil, + coerce_result, + parse_bulk_write_error_result, + parse_bulk_write_result, + parse_client_bulk_write_error_result, + parse_collection_or_database_options, + with_metaclass, +) +from test.utils import flaky, get_pool +from test.utils_shared import ( + camel_to_snake, + camel_to_snake_args, + parse_spec_options, + prepare_spec_arguments, + snake_to_camel, + wait_until, +) +from test.utils_spec_runner import SpecRunnerThread +from test.version import Version _IS_SYNC = True @@ -223,9 +225,9 @@ class EntityMapUtil: """ def __init__(self, test_class): - self._entities: Dict[str, Any] = {} - self._listeners: Dict[str, EventListenerUtil] = {} - self._session_lsids: Dict[str, Mapping[str, Any]] = {} + self._entities: dict[str, Any] = {} + self._listeners: dict[str, EventListenerUtil] = {} + self._session_lsids: dict[str, Mapping[str, Any]] = {} self.test: UnifiedSpecTestMixinV1 = test_class def __contains__(self, item): @@ -617,10 +619,8 @@ def maybe_skip_test(self, spec): self.skipTest("CSOT not implemented for watch()") if "cursors" in class_name: self.skipTest("CSOT not implemented for cursors") - if ( - "tailable" in class_name - or "tailable" in description - and "non-tailable" not in description + if "tailable" in class_name or ( + "tailable" in description and "non-tailable" not in description ): self.skipTest("CSOT not implemented for tailable cursors") if "sessions" in class_name: @@ -647,7 +647,7 @@ def maybe_skip_test(self, spec): def process_error(self, exception, spec): if isinstance(exception, unittest.SkipTest): - raise + raise exception is_error = spec.get("isError") is_client_error = spec.get("isClientError") is_timeout_error = spec.get("isTimeoutError") @@ -989,7 +989,7 @@ def _bucketOperation_uploadWithId(self, target: GridFSBucket, *args: Any, **kwar def _bucketOperation_find( self, target: GridFSBucket, *args: Any, **kwargs: Any - ) -> List[GridOut]: + ) -> list[GridOut]: return target.find(*args, **kwargs).to_list() def run_entity_operation(self, spec): @@ -1105,7 +1105,7 @@ def _testOperation_targetedFailPoint(self, spec): session = self.entity_map[spec["session"]] if not session._pinned_address: self.fail( - "Cannot use targetedFailPoint operation with unpinned " "session {}".format( + "Cannot use targetedFailPoint operation with unpinned session {}".format( spec["session"] ) ) @@ -1138,8 +1138,7 @@ def __get_last_two_command_lsids(self, listener): cmd_started_events.append(event) if len(cmd_started_events) < 2: self.fail( - "Needed 2 CommandStartedEvents to compare lsids, " - "got %s" % (len(cmd_started_events)) + "Needed 2 CommandStartedEvents to compare lsids, got %s" % (len(cmd_started_events)) ) return tuple([e.command["lsid"] for e in cmd_started_events][:2]) diff --git a/test/unified_format_shared.py b/test/unified_format_shared.py index 5aa989cb24..54f4ed4700 100644 --- a/test/unified_format_shared.py +++ b/test/unified_format_shared.py @@ -16,6 +16,7 @@ https://github.com/mongodb/specifications/blob/master/source/unified-test-format/unified-test-format.md """ + from __future__ import annotations import binascii @@ -25,19 +26,8 @@ import time import types from collections import abc -from test.helpers_shared import ( - AWS_CREDS, - AWS_CREDS_2, - AWS_TEMP_CREDS, - AZURE_CREDS, - CA_PEM, - CLIENT_PEM, - GCP_CREDS, - KMIP_CREDS, - LOCAL_MASTER_KEY, -) -from test.utils_shared import CMAPListener, camel_to_snake, parse_collection_options -from typing import Any, MutableMapping, Union +from collections.abc import MutableMapping +from typing import Any, Union from bson import ( RE_TYPE, @@ -91,6 +81,18 @@ from pymongo.results import BulkWriteResult from pymongo.server_description import ServerDescription from pymongo.topology_description import TopologyDescription +from test.helpers_shared import ( + AWS_CREDS, + AWS_CREDS_2, + AWS_TEMP_CREDS, + AZURE_CREDS, + CA_PEM, + CLIENT_PEM, + GCP_CREDS, + KMIP_CREDS, + LOCAL_MASTER_KEY, +) +from test.utils_shared import CMAPListener, camel_to_snake, parse_collection_options JSON_OPTS = json_util.JSONOptions(tz_aware=False) diff --git a/test/utils.py b/test/utils.py index 7b5a4c8214..54b7e351e3 100644 --- a/test/utils.py +++ b/test/utils.py @@ -13,6 +13,7 @@ # limitations under the License. """Utilities for testing pymongo that require synchronization.""" + from __future__ import annotations import asyncio @@ -202,7 +203,7 @@ def wrapper(*args, **kwargs): if i == max_runs - 1: raise e print( - f"Retrying after attempt {i+1} of {func_name or target_func.__name__} failed with ({reason})):\n" + f"Retrying after attempt {i + 1} of {func_name or target_func.__name__} failed with ({reason})):\n" f"{traceback.format_exc()}", file=sys.stderr, ) diff --git a/test/utils_selection_tests.py b/test/utils_selection_tests.py index 2798e49eb0..3754c74674 100644 --- a/test/utils_selection_tests.py +++ b/test/utils_selection_tests.py @@ -13,16 +13,25 @@ # limitations under the License. """Utilities for testing Server Selection and Max Staleness.""" + from __future__ import annotations import datetime import os import sys + from test import PyMongoTestCase from test.utils import MockPool sys.path[0:0] = [""] +from bson import json_util +from pymongo.common import HEARTBEAT_FREQUENCY, clean_node +from pymongo.errors import AutoReconnect, ConfigurationError +from pymongo.operations import _Op +from pymongo.server_selectors import writable_server_selector +from pymongo.synchronous.settings import TopologySettings +from pymongo.synchronous.topology import Topology from test import unittest from test.pymongo_mocks import DummyMonitor from test.utils_selection_tests_shared import ( @@ -32,14 +41,6 @@ ) from test.utils_shared import parse_read_preference -from bson import json_util -from pymongo.common import HEARTBEAT_FREQUENCY, clean_node -from pymongo.errors import AutoReconnect, ConfigurationError -from pymongo.operations import _Op -from pymongo.server_selectors import writable_server_selector -from pymongo.synchronous.settings import TopologySettings -from pymongo.synchronous.topology import Topology - _IS_SYNC = True diff --git a/test/utils_selection_tests_shared.py b/test/utils_selection_tests_shared.py index dbaed1034f..58e5d0dbec 100644 --- a/test/utils_selection_tests_shared.py +++ b/test/utils_selection_tests_shared.py @@ -13,6 +13,7 @@ # limitations under the License. """Utilities for testing Server Selection and Max Staleness.""" + from __future__ import annotations import datetime diff --git a/test/utils_shared.py b/test/utils_shared.py index 70a195215a..db6ff9aacb 100644 --- a/test/utils_shared.py +++ b/test/utils_shared.py @@ -13,6 +13,7 @@ # limitations under the License. """Shared utilities for testing pymongo""" + from __future__ import annotations import asyncio @@ -29,10 +30,6 @@ from collections import abc, defaultdict from functools import partial from inspect import iscoroutinefunction -from test import client_context -from test.asynchronous.utils import async_wait_until -from test.utils import wait_until -from typing import List from bson.objectid import ObjectId from pymongo import monitoring, operations, read_preferences @@ -58,6 +55,9 @@ from pymongo.synchronous.collection import ReturnDocument from pymongo.synchronous.pool import _CancellationContext, _PoolGeneration from pymongo.write_concern import WriteConcern +from test import client_context +from test.asynchronous.utils import async_wait_until +from test.utils import wait_until IMPOSSIBLE_WRITE_CONCERN = WriteConcern(w=50) @@ -149,15 +149,15 @@ def __init__(self): self.results = defaultdict(list) @property - def started_events(self) -> List[monitoring.CommandStartedEvent]: + def started_events(self) -> list[monitoring.CommandStartedEvent]: return self.results["started"] @property - def succeeded_events(self) -> List[monitoring.CommandSucceededEvent]: + def succeeded_events(self) -> list[monitoring.CommandSucceededEvent]: return self.results["succeeded"] @property - def failed_events(self) -> List[monitoring.CommandFailedEvent]: + def failed_events(self) -> list[monitoring.CommandFailedEvent]: return self.results["failed"] def started(self, event: monitoring.CommandStartedEvent) -> None: @@ -172,7 +172,7 @@ def failed(self, event: monitoring.CommandFailedEvent) -> None: self.failed_events.append(event) self.add_event(event) - def started_command_names(self) -> List[str]: + def started_command_names(self) -> list[str]: """Return list of command names started.""" return [event.command_name for event in self.started_events] diff --git a/test/utils_spec_runner.py b/test/utils_spec_runner.py index f4c1c6bfcc..f92533a9d4 100644 --- a/test/utils_spec_runner.py +++ b/test/utils_spec_runner.py @@ -13,16 +13,17 @@ # limitations under the License. """Utilities for testing driver specs.""" + from __future__ import annotations import asyncio import os -from test import client_context -from test.helpers import ConcurrentRunner -from test.utils_shared import ScenarioDict from bson import json_util from pymongo.lock import _cond_wait, _create_condition, _create_lock +from test import client_context +from test.helpers import ConcurrentRunner +from test.utils_shared import ScenarioDict _IS_SYNC = True @@ -159,7 +160,7 @@ def _create_tests(self): dirname = os.path.split(dirpath)[-1] for filename in filenames: - with open(os.path.join(dirpath, filename)) as scenario_stream: # noqa: ASYNC101, RUF100 + with open(os.path.join(dirpath, filename)) as scenario_stream: # noqa: RUF100 # Use tz_aware=False to match how CodecOptions decodes # dates. opts = json_util.JSONOptions(tz_aware=False) diff --git a/test/version.py b/test/version.py index ae6ecb331f..f88830b2b8 100644 --- a/test/version.py +++ b/test/version.py @@ -13,6 +13,7 @@ # limitations under the License. """Some tools for running tests based on MongoDB server version.""" + from __future__ import annotations from pymongo.common import Version as BaseVersion diff --git a/tools/clean.py b/tools/clean.py index b6e1867a0a..30473d9aed 100644 --- a/tools/clean.py +++ b/tools/clean.py @@ -16,6 +16,7 @@ Only really intended to be used by internal build scripts. """ + from __future__ import annotations import sys diff --git a/tools/convert_test_to_async.py b/tools/convert_test_to_async.py index 6c68c34bf3..9c8743d04c 100644 --- a/tools/convert_test_to_async.py +++ b/tools/convert_test_to_async.py @@ -99,9 +99,9 @@ def apply_replacements(lines: list[str]) -> list[str]: lines[i] = "_IS_SYNC = False" if "def test" in lines[i]: lines[i] = lines[i].replace("def test", "async def test") - for k in replacements: + for k, value in replacements.items(): if k in lines[i]: - lines[i] = lines[i].replace(k, replacements[k]) + lines[i] = lines[i].replace(k, value) for k in async_methods: if k + "(" in lines[i]: tokens = lines[i].split(" ") diff --git a/tools/ensure_future_annotations_import.py b/tools/ensure_future_annotations_import.py index 55080148e4..e98e7ed04e 100644 --- a/tools/ensure_future_annotations_import.py +++ b/tools/ensure_future_annotations_import.py @@ -12,8 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Ensure that 'from __future__ import annotations' is used in all package files -""" +"""Ensure that 'from __future__ import annotations' is used in all package files""" + from __future__ import annotations import sys diff --git a/tools/fail_if_no_c.py b/tools/fail_if_no_c.py index 64280a81d2..bb50081ad5 100644 --- a/tools/fail_if_no_c.py +++ b/tools/fail_if_no_c.py @@ -16,6 +16,7 @@ Only really intended to be used by internal build scripts. """ + from __future__ import annotations import logging diff --git a/tools/synchro.py b/tools/synchro.py index d6ce1171e3..0984d0a018 100644 --- a/tools/synchro.py +++ b/tools/synchro.py @@ -22,7 +22,6 @@ import os import re import sys -from os import listdir from pathlib import Path from unasync import Rule, unasync_files # type: ignore[import-not-found] @@ -173,11 +172,15 @@ Path.mkdir(Path(_gridfs_dest_base)) async_files = [ - _pymongo_base + f for f in listdir(_pymongo_base) if (Path(_pymongo_base) / f).is_file() + _pymongo_base + str(f) + for f in Path.iterdir(Path(_pymongo_base)) + if (Path(_pymongo_base) / f).is_file() ] gridfs_files = [ - _gridfs_base + f for f in listdir(_gridfs_base) if (Path(_gridfs_base) / f).is_file() + _gridfs_base + str(f) + for f in Path.iterdir(Path(_gridfs_base)) + if (Path(_gridfs_base) / f).is_file() ] @@ -194,8 +197,8 @@ def async_only_test(f: str) -> bool: test_files = [ - _test_base + f - for f in listdir(_test_base) + _test_base + str(f) + for f in Path.iterdir(Path(_test_base)) if (Path(_test_base) / f).is_file() and not async_only_test(f) ] @@ -288,7 +291,7 @@ def process_files( files: list[str], docstring_translate_files: list[str], sync_test_files: list[str] ) -> None: for file in files: - if "__init__" not in file or "__init__" and "test" in file: + if "__init__" not in file or ("__init__" and "test" in file): with open(file, "r+") as f: lines = f.readlines() lines = apply_is_sync(lines, file) @@ -365,7 +368,7 @@ def translate_async_sleeps(lines: list[str]) -> list[str]: def translate_docstrings(lines: list[str]) -> list[str]: for i in range(len(lines)): - for k in replacements: + for k, value in replacements.items(): if k in lines[i]: # This sequence of replacements fixes the grammar issues caused by translating async -> sync if "an Async" in lines[i]: @@ -383,19 +386,19 @@ def translate_docstrings(lines: list[str]) -> list[str]: # This ensures docstring links are for `pymongo.X` instead of `pymongo.synchronous.X` if "pymongo.asynchronous" in lines[i] and "import" not in lines[i]: lines[i] = lines[i].replace("pymongo.asynchronous", "pymongo") - lines[i] = lines[i].replace(k, replacements[k]) - if "Sync" in lines[i] and "Synchronous" not in lines[i] and replacements[k] in lines[i]: + lines[i] = lines[i].replace(k, value) + if "Sync" in lines[i] and "Synchronous" not in lines[i] and value in lines[i]: lines[i] = lines[i].replace("Sync", "") if "async for" in lines[i] or "async with" in lines[i] or "async def" in lines[i]: lines[i] = lines[i].replace("async ", "") if "await " in lines[i] and "tailable" not in lines[i]: lines[i] = lines[i].replace("await ", "") for i in range(len(lines)): - for k in docstring_replacements: # type: ignore[assignment] + for k, value in docstring_replacements.items(): # type: ignore[assignment] if f":param {k[1]}: **Not supported by {k[0]}**." in lines[i]: lines[i] = lines[i].replace( f"**Not supported by {k[0]}**.", - docstring_replacements[k], # type: ignore[index] + value, # type: ignore[index] ) for line in docstring_removals: @@ -436,10 +439,8 @@ def main() -> None: continue sync_name = str(fname).replace("asynchronous", "synchronous") test_sync_name = str(fname).replace("/asynchronous", "") - if ( - sync_name in modified_files - or test_sync_name in modified_files - and "OVERRIDE_SYNCHRO_CHECK" not in os.environ + if sync_name in modified_files or ( + test_sync_name in modified_files and "OVERRIDE_SYNCHRO_CHECK" not in os.environ ): print(f"Refusing to overwrite {test_sync_name}") errored = True @@ -451,14 +452,14 @@ def main() -> None: unasync_directory(test_files, _test_base, _test_dest_base, replacements) sync_files = [ - _pymongo_dest_base + f - for f in listdir(_pymongo_dest_base) + _pymongo_dest_base + str(f) + for f in Path.iterdir(Path(_pymongo_dest_base)) if (Path(_pymongo_dest_base) / f).is_file() ] sync_gridfs_files = [ - _gridfs_dest_base + f - for f in listdir(_gridfs_dest_base) + _gridfs_dest_base + str(f) + for f in Path.iterdir(Path(_gridfs_dest_base)) if (Path(_gridfs_dest_base) / f).is_file() ] sync_test_files = [ From 338bc242f8296be531c7c792f3a2a37a81940ead Mon Sep 17 00:00:00 2001 From: Noah Stapp Date: Mon, 15 Jun 2026 14:32:58 -0400 Subject: [PATCH 2/2] Fix failures --- bson/json_util.py | 2 +- bson/son.py | 2 +- pymongo/read_preferences.py | 2 +- pymongo/topology_description.py | 8 ++++---- test/test_default_exports.py | 6 +++++- tools/synchro.py | 6 +++--- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bson/json_util.py b/bson/json_util.py index b387b7a139..098a4b18ce 100644 --- a/bson/json_util.py +++ b/bson/json_util.py @@ -962,7 +962,7 @@ def _encode_maxkey(dummy0: Any, dummy1: Any) -> dict: # type: ignore[type-arg] # Each encoder function's signature is: # - obj: a Python data type, e.g. a Python int for _encode_int # - json_options: a JSONOptions -_ENCODERS: dict[type, Callable[[Any, JSONOptions], Any]] = { # type: ignore[type-arg] +_ENCODERS: dict[type, Callable[[Any, JSONOptions], Any]] = { bool: _encode_noop, bytes: _encode_bytes, datetime.datetime: _encode_datetime, diff --git a/bson/son.py b/bson/son.py index 1404e4f921..84b8028069 100644 --- a/bson/son.py +++ b/bson/son.py @@ -187,7 +187,7 @@ def __eq__(self, other: Any) -> bool: return len(self) == len(other) and list(self.items()) == list(other.items()) return cast(bool, self.to_dict() == other) - __hash__ = None # type: ignore[assignment] + __hash__ = None def __ne__(self, other: Any) -> bool: return not self == other diff --git a/pymongo/read_preferences.py b/pymongo/read_preferences.py index 6cdea1d3f6..204bb49101 100644 --- a/pymongo/read_preferences.py +++ b/pymongo/read_preferences.py @@ -302,7 +302,7 @@ def __eq__(self, other: Any) -> bool: return other.mode == _PRIMARY return NotImplemented - __hash__ = None # type: ignore[assignment] + __hash__ = None class PrimaryPreferred(_ServerMode): diff --git a/pymongo/topology_description.py b/pymongo/topology_description.py index c31244842a..87966aca45 100644 --- a/pymongo/topology_description.py +++ b/pymongo/topology_description.py @@ -132,15 +132,15 @@ def _init_incompatible_err(self) -> None: ) if server_too_new: - self._incompatible_err = ( # type: ignore - f"Server at {s.address[0]}:{s.address[1] or 0} requires wire version " + self._incompatible_err = ( + f"Server at {s.address[0]}:{s.address[1] or 0} requires wire version " # type: ignore[assignment] f"{s.min_wire_version}, but this version of PyMongo only supports up to " f"{common.MAX_SUPPORTED_WIRE_VERSION}." ) elif server_too_old: - self._incompatible_err = ( # type: ignore - f"Server at {s.address[0]}:{s.address[1] or 0} reports wire version " + self._incompatible_err = ( + f"Server at {s.address[0]}:{s.address[1] or 0} reports wire version " # type: ignore[assignment] f"{s.max_wire_version}, but this version of PyMongo requires at least " f"{common.MIN_SUPPORTED_WIRE_VERSION} (MongoDB " f"{common.MIN_SUPPORTED_SERVER_VERSION})." diff --git a/test/test_default_exports.py b/test/test_default_exports.py index 111a3848d4..446892e2eb 100644 --- a/test/test_default_exports.py +++ b/test/test_default_exports.py @@ -54,7 +54,11 @@ def check_module(self, mod, ignores): value = getattr(mod, name) if inspect.ismodule(value): continue - if getattr(value, "__module__", None) == "typing": + if getattr(value, "__module__", None) in ( + "typing", + "collections.abc", + "contextlib", + ): continue if not name.startswith("_"): self.fail(f"{name} was not included in {mod}.__all__") diff --git a/tools/synchro.py b/tools/synchro.py index c5b69adae8..a8af3876eb 100644 --- a/tools/synchro.py +++ b/tools/synchro.py @@ -184,9 +184,9 @@ ] -def async_only_test(f: str) -> bool: +def async_only_test(f: Path) -> bool: """Return True for async tests that should not be converted to sync.""" - return f in [ + return str(f) in [ "test_locks.py", "test_concurrency.py", "test_async_cancellation.py", @@ -399,7 +399,7 @@ def translate_docstrings(lines: list[str]) -> list[str]: if f":param {k[1]}: **Not supported by {k[0]}**." in lines[i]: lines[i] = lines[i].replace( f"**Not supported by {k[0]}**.", - value, # type: ignore[index] + value, ) for line in docstring_removals: