Merge branch 'master' of github.com:mongodb/mongo-python-driver

This commit is contained in:
Steven Silvester 2024-09-06 07:44:04 -05:00
commit 017e1ef9e0
No known key found for this signature in database
GPG Key ID: B1BF5EC3A8B32F91
26 changed files with 1813 additions and 161 deletions

View File

@ -34,51 +34,14 @@ functions:
# Applies the subitted patch, if any
# Deprecated. Should be removed. But still needed for certain agents (ZAP)
- command: git.apply_patch
# Make an evergreen exapanstion file with dynamic values
- command: shell.exec
# Make an evergreen expansion file with dynamic values
- command: subprocess.exec
params:
include_expansions_in_env: ["is_patch", "project", "version_id"]
binary: bash
working_dir: "src"
script: |
set +x
# Get the current unique version of this checkout
if [ "${is_patch}" = "true" ]; then
CURRENT_VERSION=$(git describe)-patch-${version_id}
else
CURRENT_VERSION=latest
fi
export DRIVERS_TOOLS="$(dirname $(pwd))/drivers-tools"
export PROJECT_DIRECTORY="$(pwd)"
# Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory
if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin
export DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS)
export PROJECT_DIRECTORY=$(cygpath -m $PROJECT_DIRECTORY)
fi
export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration"
export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin"
cat <<EOT > expansion.yml
CURRENT_VERSION: "$CURRENT_VERSION"
DRIVERS_TOOLS: "$DRIVERS_TOOLS"
MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME"
MONGODB_BINARIES: "$MONGODB_BINARIES"
PROJECT_DIRECTORY: "$PROJECT_DIRECTORY"
PREPARE_SHELL: |
set -o errexit
export SKIP_LEGACY_SHELL=1
export DRIVERS_TOOLS="$DRIVERS_TOOLS"
export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME"
export MONGODB_BINARIES="$MONGODB_BINARIES"
export PROJECT_DIRECTORY="$PROJECT_DIRECTORY"
export TMPDIR="$MONGO_ORCHESTRATION_HOME/db"
export PATH="$MONGODB_BINARIES:$PATH"
export PROJECT="${project}"
export PIP_QUIET=1
EOT
args:
- .evergreen/scripts/configure-env.sh
# Load the expansion file to make an evergreen variable with the current unique version
- command: expansions.update
params:
@ -88,14 +51,14 @@ functions:
- command: shell.exec
params:
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
set -o xtrace
rm -rf $DRIVERS_TOOLS
if [ "${project}" = "drivers-tools" ]; then
if [ "$PROJECT" = "drivers-tools" ]; then
# If this was a patch build, doing a fresh clone would not actually test the patch
cp -R ${PROJECT_DIRECTORY}/ $DRIVERS_TOOLS
cp -R ${PROJECT_DIRECTORY}/ ${DRIVERS_TOOLS}
else
git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git $DRIVERS_TOOLS
git clone https://github.com/mongodb-labs/drivers-evergreen-tools.git ${DRIVERS_TOOLS}
fi
echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config
@ -129,12 +92,12 @@ functions:
script: |
# Download all the task coverage files.
aws s3 cp --recursive s3://${bucket_name}/coverage/${revision}/${version_id}/coverage/ coverage/
- command: shell.exec
- command: subprocess.exec
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
bash .evergreen/combine-coverage.sh
binary: bash
args:
- .evergreen/combine-coverage.sh
# Upload the resulting html coverage report.
- command: shell.exec
params:
@ -164,7 +127,7 @@ functions:
- command: shell.exec
params:
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
set -o xtrace
mkdir out_dir
find $MONGO_ORCHESTRATION_HOME -name \*.log -exec sh -c 'x="{}"; mv $x $PWD/out_dir/$(basename $(dirname $x))_$(basename $x)' \;
@ -266,7 +229,7 @@ functions:
- command: shell.exec
params:
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
set -o xtrace
# Enable core dumps if enabled on the machine
@ -325,13 +288,13 @@ functions:
type: setup
params:
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/pull-mongohouse-image.sh
- command: shell.exec
type: setup
params:
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
bash ${DRIVERS_TOOLS}/.evergreen/atlas_data_lake/run-mongohouse-image.sh
sleep 1
docker ps
@ -340,7 +303,7 @@ functions:
- command: shell.exec
params:
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
set -o xtrace
bash ${DRIVERS_TOOLS}/.evergreen/stop-orchestration.sh
@ -350,7 +313,7 @@ functions:
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
set -o xtrace
PYTHON_BINARY=${PYTHON_BINARY} MOD_WSGI_VERSION=${MOD_WSGI_VERSION} \
MOD_WSGI_EMBEDDED=${MOD_WSGI_EMBEDDED} PROJECT_DIRECTORY=${PROJECT_DIRECTORY} \
@ -362,7 +325,7 @@ functions:
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
set -o xtrace
export PYTHON_BINARY=${PYTHON_BINARY}
bash ${PROJECT_DIRECTORY}/.evergreen/hatch.sh test:test-mockupdb
@ -373,7 +336,7 @@ functions:
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
set -o xtrace
PYTHON_BINARY=${PYTHON_BINARY} bash ${PROJECT_DIRECTORY}/.evergreen/hatch.sh doctest:test
@ -385,7 +348,7 @@ functions:
background: true
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
if [ -n "${test_encryption}" ]; then
./.evergreen/hatch.sh encryption:setup
fi
@ -397,7 +360,7 @@ functions:
script: |
# Disable xtrace
set +x
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
if [ -n "${MONGODB_STARTED}" ]; then
export PYMONGO_MUST_CONNECT=true
fi
@ -447,6 +410,7 @@ functions:
SSL=${SSL} \
TEST_DATA_LAKE=${TEST_DATA_LAKE} \
MONGODB_API_VERSION=${MONGODB_API_VERSION} \
SKIP_HATCH=${SKIP_HATCH} \
bash ${PROJECT_DIRECTORY}/.evergreen/hatch.sh test:test-eg
"run enterprise auth tests":
@ -497,7 +461,7 @@ functions:
shell: "bash"
working_dir: "src"
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
.evergreen/run-mongodb-aws-test.sh regular
"run aws auth test with assume role credentials":
@ -507,7 +471,7 @@ functions:
shell: "bash"
working_dir: "src"
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
.evergreen/run-mongodb-aws-test.sh assume-role
"run aws auth test with aws EC2 credentials":
@ -521,7 +485,7 @@ functions:
echo "This platform does not support the EC2 auth test, skipping..."
exit 0
fi
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
.evergreen/run-mongodb-aws-test.sh ec2
"run aws auth test with aws web identity credentials":
@ -535,7 +499,7 @@ functions:
echo "This platform does not support the web identity auth test, skipping..."
exit 0
fi
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
# Test with and without AWS_ROLE_SESSION_NAME set.
.evergreen/run-mongodb-aws-test.sh web-identity
AWS_ROLE_SESSION_NAME="test" \
@ -558,7 +522,7 @@ functions:
working_dir: "src"
shell: bash
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
.evergreen/run-mongodb-aws-test.sh env-creds
"run aws auth test with aws credentials and session token as environment variables":
@ -568,7 +532,7 @@ functions:
working_dir: "src"
shell: bash
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
.evergreen/run-mongodb-aws-test.sh session-creds
"run aws ECS auth test":
@ -582,12 +546,12 @@ functions:
echo "This platform does not support the ECS auth test, skipping..."
exit 0
fi
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
set -ex
cd ${DRIVERS_TOOLS}/.evergreen/auth_aws
. ./activate-authawsvenv.sh
. aws_setup.sh ecs
export MONGODB_BINARIES="${MONGODB_BINARIES}";
export MONGODB_BINARIES="$MONGODB_BINARIES";
export PROJECT_DIRECTORY="${PROJECT_DIRECTORY}";
python aws_tester.py ecs
cd -
@ -597,9 +561,11 @@ functions:
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
. .evergreen/hatch.sh encryption:teardown
rm -rf $DRIVERS_TOOLS || true
. .evergreen/scripts/env.sh
if [ -f $DRIVERS_TOOLS/.evergreen/csfle/secrets-export.sh ]; then
. .evergreen/hatch.sh encryption:teardown
fi
rm -rf ${DRIVERS_TOOLS} || true
rm -f ./secrets-export.sh || true
"fix absolute paths":
@ -607,7 +573,7 @@ functions:
params:
script: |
set +x
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
for filename in $(find ${DRIVERS_TOOLS} -name \*.json); do
perl -p -i -e "s|ABSOLUTE_PATH_REPLACEMENT_TOKEN|${DRIVERS_TOOLS}|g" $filename
done
@ -617,20 +583,20 @@ functions:
params:
script: |
set +x
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do
cat $i | tr -d '\r' > $i.new
mv $i.new $i
done
# Copy client certificate because symlinks do not work on Windows.
cp ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem ${MONGO_ORCHESTRATION_HOME}/lib/client.pem
cp ${DRIVERS_TOOLS}/.evergreen/x509gen/client.pem $MONGO_ORCHESTRATION_HOME/lib/client.pem
"make files executable":
- command: shell.exec
params:
script: |
set +x
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
for i in $(find ${DRIVERS_TOOLS}/.evergreen ${PROJECT_DIRECTORY}/.evergreen -name \*.sh); do
chmod +x $i
done
@ -640,7 +606,7 @@ functions:
params:
script: |
set +x
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
echo '{"results": [{ "status": "FAIL", "test_file": "Build", "log_raw": "No test-results.json found was created" } ]}' > ${PROJECT_DIRECTORY}/test-results.json
"install dependencies":
@ -648,7 +614,7 @@ functions:
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
set -o xtrace
file="${PROJECT_DIRECTORY}/.evergreen/install-dependencies.sh"
# Don't use ${file} syntax here because evergreen treats it as an empty expansion.
@ -679,10 +645,10 @@ functions:
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
TEST_OCSP=1 \
PYTHON_BINARY=${PYTHON_BINARY} \
CA_FILE="$DRIVERS_TOOLS/.evergreen/ocsp/${OCSP_ALGORITHM}/ca.pem" \
CA_FILE="${DRIVERS_TOOLS}/.evergreen/ocsp/${OCSP_ALGORITHM}/ca.pem" \
OCSP_TLS_SHOULD_SUCCEED="${OCSP_TLS_SHOULD_SUCCEED}" \
bash ${PROJECT_DIRECTORY}/.evergreen/hatch.sh test:test-eg
@ -691,7 +657,7 @@ functions:
params:
background: true
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
cd ${DRIVERS_TOOLS}/.evergreen/ocsp
. ./activate-ocspvenv.sh
python ocsp_mock.py \
@ -704,7 +670,7 @@ functions:
params:
background: true
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
cd ${DRIVERS_TOOLS}/.evergreen/ocsp
. ./activate-ocspvenv.sh
python ocsp_mock.py \
@ -719,7 +685,7 @@ functions:
params:
background: true
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
cd ${DRIVERS_TOOLS}/.evergreen/ocsp
. ./activate-ocspvenv.sh
python ocsp_mock.py \
@ -732,7 +698,7 @@ functions:
params:
background: true
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
cd ${DRIVERS_TOOLS}/.evergreen/ocsp
. ./activate-ocspvenv.sh
python ocsp_mock.py \
@ -774,7 +740,7 @@ functions:
params:
shell: "bash"
script: |
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
cd "${DRIVERS_TOOLS}/.evergreen/auth_aws"
if [ -f "./aws_e2e_setup.json" ]; then
. ./activate-authawsvenv.sh
@ -794,7 +760,7 @@ functions:
params:
working_dir: "src"
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
PROJECT_DIRECTORY=${PROJECT_DIRECTORY} bash ${PROJECT_DIRECTORY}/.evergreen/run-perf-tests.sh
"attach benchmark test results":
@ -1889,7 +1855,7 @@ tasks:
shell: bash
script: |-
set -o errexit
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
cd src
git add .
git commit -m "add files"
@ -1906,7 +1872,7 @@ tasks:
shell: bash
script: |-
set -o errexit
${PREPARE_SHELL}
. src/.evergreen/scripts/env.sh
cd src
git add .
git commit -m "add files"
@ -1974,7 +1940,7 @@ tasks:
working_dir: "src"
shell: "bash"
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
export PYTHON_BINARY=/opt/mongodbtoolchain/v4/bin/python3
export LIBMONGOCRYPT_URL=https://s3.amazonaws.com/mciuploads/libmongocrypt/debian11/master/latest/libmongocrypt.tar.gz
SKIP_SERVERS=1 bash ./.evergreen/setup-encryption.sh
@ -2051,7 +2017,7 @@ tasks:
shell: "bash"
working_dir: src
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
set -x
export CONFIG=$PROJECT_DIRECTORY/.github/reviewers.txt
export SCRIPT="$DRIVERS_TOOLS/.evergreen/github_app/assign-reviewer.sh"
@ -2067,7 +2033,7 @@ tasks:
shell: "bash"
working_dir: src
script: |
${PREPARE_SHELL}
. .evergreen/scripts/env.sh
set -x
export BASE_SHA=${revision}
export HEAD_SHA=${github_commit}
@ -2120,10 +2086,14 @@ axes:
display_name: "RHEL 8.3 (zSeries)"
run_on: rhel83-zseries-small
batchtime: 10080 # 7 days
variables:
SKIP_HATCH: true
- id: rhel81-power8
display_name: "RHEL 8.1 (POWER8)"
run_on: rhel81-power8-small
batchtime: 10080 # 7 days
variables:
SKIP_HATCH: true
- id: rhel82-arm64
display_name: "RHEL 8.2 (ARM64)"
run_on: rhel82-arm64-small

View File

@ -8,7 +8,17 @@ if [ -z "$PYTHON_BINARY" ]; then
PYTHON_BINARY=$(find_python3)
fi
if $PYTHON_BINARY -m hatch --version; then
# Check if we should skip hatch and run the tests directly.
if [ -n "$SKIP_HATCH" ]; then
ENV_NAME=testenv-$RANDOM
createvirtualenv "$PYTHON_BINARY" $ENV_NAME
# shellcheck disable=SC2064
trap "deactivate; rm -rf $ENV_NAME" EXIT HUP
python -m pip install -e ".[test]"
run_hatch() {
bash ./.evergreen/run-tests.sh
}
elif $PYTHON_BINARY -m hatch --version; then
run_hatch() {
$PYTHON_BINARY -m hatch run "$@"
}

View File

@ -0,0 +1,53 @@
#!/bin/bash -ex
# Get the current unique version of this checkout
# shellcheck disable=SC2154
if [ "$is_patch" = "true" ]; then
# shellcheck disable=SC2154
CURRENT_VERSION="$(git describe)-patch-$version_id"
else
CURRENT_VERSION=latest
fi
PROJECT_DIRECTORY="$(pwd)"
DRIVERS_TOOLS="$(dirname $PROJECT_DIRECTORY)/drivers-tools"
# Python has cygwin path problems on Windows. Detect prospective mongo-orchestration home directory
if [ "Windows_NT" = "$OS" ]; then # Magic variable in cygwin
DRIVERS_TOOLS=$(cygpath -m $DRIVERS_TOOLS)
PROJECT_DIRECTORY=$(cygpath -m $PROJECT_DIRECTORY)
fi
SCRIPT_DIR="$PROJECT_DIRECTORY/.evergreen/scripts"
if [ -f "$SCRIPT_DIR/env.sh" ]; then
echo "Reading $SCRIPT_DIR/env.sh file"
. "$SCRIPT_DIR/env.sh"
exit 0
fi
export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration"
export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin"
cat <<EOT > $SCRIPT_DIR/env.sh
set -o errexit
export PROJECT_DIRECTORY="$PROJECT_DIRECTORY"
export CURRENT_VERSION="$CURRENT_VERSION"
export SKIP_LEGACY_SHELL=1
export DRIVERS_TOOLS="$DRIVERS_TOOLS"
export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME"
export MONGODB_BINARIES="$MONGODB_BINARIES"
export PROJECT_DIRECTORY="$PROJECT_DIRECTORY"
export TMPDIR="$MONGO_ORCHESTRATION_HOME/db"
export PATH="$MONGODB_BINARIES:$PATH"
# shellcheck disable=SC2154
export PROJECT="$project"
export PIP_QUIET=1
EOT
# Add these expansions to make it easier to call out tests scripts from the EVG yaml
cat <<EOT > expansion.yml
DRIVERS_TOOLS: "$DRIVERS_TOOLS"
PROJECT_DIRECTORY: "$PROJECT_DIRECTORY"
EOT

View File

@ -383,10 +383,11 @@ static int millis_from_datetime_ms(PyObject* dt, long long* out){
static PyObject* decode_datetime(PyObject* self, long long millis, const codec_options_t* options){
PyObject* naive = NULL;
PyObject* replace = NULL;
PyObject* args = NULL;
PyObject* kwargs = NULL;
PyObject* value = NULL;
struct module_state *state = GETSTATE(self);
if (!state) {
goto invalid;
}
if (options->datetime_conversion == DATETIME_MS){
return datetime_ms_from_millis(self, millis);
}
@ -414,8 +415,8 @@ static PyObject* decode_datetime(PyObject* self, long long millis, const codec_o
Py_DECREF(utcoffset);
return 0;
}
min_millis_offset = (PyDateTime_DELTA_GET_DAYS(utcoffset) * 86400 +
PyDateTime_DELTA_GET_SECONDS(utcoffset)) * 1000 +
min_millis_offset = (PyDateTime_DELTA_GET_DAYS(utcoffset) * (int64_t)86400 +
PyDateTime_DELTA_GET_SECONDS(utcoffset)) * (int64_t)1000 +
(PyDateTime_DELTA_GET_MICROSECONDS(utcoffset) / 1000);
}
Py_DECREF(utcoffset);
@ -433,8 +434,8 @@ static PyObject* decode_datetime(PyObject* self, long long millis, const codec_o
Py_DECREF(utcoffset);
return 0;
}
max_millis_offset = (PyDateTime_DELTA_GET_DAYS(utcoffset) * 86400 +
PyDateTime_DELTA_GET_SECONDS(utcoffset)) * 1000 +
max_millis_offset = (PyDateTime_DELTA_GET_DAYS(utcoffset) * (int64_t)86400 +
PyDateTime_DELTA_GET_SECONDS(utcoffset)) * (int64_t)1000 +
(PyDateTime_DELTA_GET_MICROSECONDS(utcoffset) / 1000);
}
Py_DECREF(utcoffset);
@ -487,8 +488,6 @@ static PyObject* decode_datetime(PyObject* self, long long millis, const codec_o
invalid:
Py_XDECREF(naive);
Py_XDECREF(replace);
Py_XDECREF(args);
Py_XDECREF(kwargs);
return value;
}

View File

@ -4,6 +4,9 @@ Changelog
Changes in Version 4.9.0
-------------------------
.. warning:: Driver support for MongoDB 3.6 reached end of life in April 2024.
PyMongo 4.9 will be the last release to support MongoDB 3.6.
PyMongo 4.9 brings a number of improvements including:
- Added support for MongoDB 8.0.
@ -11,6 +14,8 @@ PyMongo 4.9 brings a number of improvements including:
- Added support for In-Use Encryption range queries with MongoDB 8.0.
Added :attr:`~pymongo.encryption.Algorithm.RANGE`.
``sparsity`` and ``trim_factor`` are now optional in :class:`~pymongo.encryption_options.RangeOpts`.
- Added support for the "delegated" option for the KMIP ``master_key`` in
:meth:`~pymongo.encryption.ClientEncryption.create_data_key`.
- pymongocrypt>=1.10 is now required for :ref:`In-Use Encryption` support.
- Added :meth:`~pymongo.cursor.Cursor.to_list` to :class:`~pymongo.cursor.Cursor`,
:class:`~pymongo.command_cursor.CommandCursor`,

View File

@ -281,6 +281,7 @@ class _AsyncBulk:
)
if bwc.publish:
bwc._succeed(request_id, reply, duration) # type: ignore[arg-type]
await client._process_response(reply, bwc.session) # type: ignore[arg-type]
except Exception as exc:
duration = datetime.datetime.now() - bwc.start_time
if isinstance(exc, (NotPrimaryError, OperationFailure)):
@ -308,6 +309,9 @@ class _AsyncBulk:
if bwc.publish:
bwc._fail(request_id, failure, duration)
# Process the response from the server.
if isinstance(exc, (NotPrimaryError, OperationFailure)):
await client._process_response(exc.details, bwc.session) # type: ignore[arg-type]
raise
finally:
bwc.start_time = datetime.datetime.now()
@ -449,7 +453,6 @@ class _AsyncBulk:
else:
request_id, msg, to_send = bwc.batch_command(cmd, ops)
result = await self.write_command(bwc, cmd, request_id, msg, to_send, client) # type: ignore[arg-type]
await client._process_response(result, bwc.session) # type: ignore[arg-type]
return result, to_send # type: ignore[return-value]

View File

@ -283,6 +283,8 @@ class _AsyncClientBulk:
)
if bwc.publish:
bwc._succeed(request_id, reply, duration) # type: ignore[arg-type]
# Process the response from the server.
await self.client._process_response(reply, bwc.session) # type: ignore[arg-type]
except Exception as exc:
duration = datetime.datetime.now() - bwc.start_time
if isinstance(exc, (NotPrimaryError, OperationFailure)):
@ -312,6 +314,11 @@ class _AsyncClientBulk:
bwc._fail(request_id, failure, duration)
# Top-level error will be embedded in ClientBulkWriteException.
reply = {"error": exc}
# Process the response from the server.
if isinstance(exc, OperationFailure):
await self.client._process_response(exc.details, bwc.session) # type: ignore[arg-type]
else:
await self.client._process_response({}, bwc.session) # type: ignore[arg-type]
finally:
bwc.start_time = datetime.datetime.now()
return reply # type: ignore[return-value]
@ -431,7 +438,6 @@ class _AsyncClientBulk:
result = await self.write_command(
bwc, cmd, request_id, msg, to_send_ops, to_send_ns, self.client
) # type: ignore[arg-type]
await self.client._process_response(result, bwc.session) # type: ignore[arg-type]
return result, to_send_ops, to_send_ns # type: ignore[return-value]
async def _process_results_cursor(

View File

@ -764,6 +764,9 @@ class AsyncClientEncryption(Generic[_DocumentType]):
Secret Data managed object.
- `endpoint` (string): Optional. Host with optional
port, e.g. "example.vault.azure.net:".
- `delegated` (bool): Optional. If True (recommended), the
KMIP server will perform encryption and decryption. If
delegated is not provided, defaults to false.
:param key_alt_names: An optional list of string alternate
names used to reference a key. If a key is created with alternate

View File

@ -281,6 +281,7 @@ class _Bulk:
)
if bwc.publish:
bwc._succeed(request_id, reply, duration) # type: ignore[arg-type]
client._process_response(reply, bwc.session) # type: ignore[arg-type]
except Exception as exc:
duration = datetime.datetime.now() - bwc.start_time
if isinstance(exc, (NotPrimaryError, OperationFailure)):
@ -308,6 +309,9 @@ class _Bulk:
if bwc.publish:
bwc._fail(request_id, failure, duration)
# Process the response from the server.
if isinstance(exc, (NotPrimaryError, OperationFailure)):
client._process_response(exc.details, bwc.session) # type: ignore[arg-type]
raise
finally:
bwc.start_time = datetime.datetime.now()
@ -449,7 +453,6 @@ class _Bulk:
else:
request_id, msg, to_send = bwc.batch_command(cmd, ops)
result = self.write_command(bwc, cmd, request_id, msg, to_send, client) # type: ignore[arg-type]
client._process_response(result, bwc.session) # type: ignore[arg-type]
return result, to_send # type: ignore[return-value]

View File

@ -283,6 +283,8 @@ class _ClientBulk:
)
if bwc.publish:
bwc._succeed(request_id, reply, duration) # type: ignore[arg-type]
# Process the response from the server.
self.client._process_response(reply, bwc.session) # type: ignore[arg-type]
except Exception as exc:
duration = datetime.datetime.now() - bwc.start_time
if isinstance(exc, (NotPrimaryError, OperationFailure)):
@ -312,6 +314,11 @@ class _ClientBulk:
bwc._fail(request_id, failure, duration)
# Top-level error will be embedded in ClientBulkWriteException.
reply = {"error": exc}
# Process the response from the server.
if isinstance(exc, OperationFailure):
self.client._process_response(exc.details, bwc.session) # type: ignore[arg-type]
else:
self.client._process_response({}, bwc.session) # type: ignore[arg-type]
finally:
bwc.start_time = datetime.datetime.now()
return reply # type: ignore[return-value]
@ -429,7 +436,6 @@ class _ClientBulk:
"""Executes a batch of bulkWrite server commands (ack)."""
request_id, msg, to_send_ops, to_send_ns = bwc.batch_command(cmd, ops, namespaces)
result = self.write_command(bwc, cmd, request_id, msg, to_send_ops, to_send_ns, self.client) # type: ignore[arg-type]
self.client._process_response(result, bwc.session) # type: ignore[arg-type]
return result, to_send_ops, to_send_ns # type: ignore[return-value]
def _process_results_cursor(

View File

@ -762,6 +762,9 @@ class ClientEncryption(Generic[_DocumentType]):
Secret Data managed object.
- `endpoint` (string): Optional. Host with optional
port, e.g. "example.vault.azure.net:".
- `delegated` (bool): Optional. If True (recommended), the
KMIP server will perform encryption and decryption. If
delegated is not provided, defaults to false.
:param key_alt_names: An optional list of string alternate
names used to reference a key. If a key is created with alternate

255
test/asynchronous/qcheck.py Normal file
View File

@ -0,0 +1,255 @@
# Copyright 2009-present MongoDB, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
import datetime
import random
import re
import sys
import traceback
sys.path[0:0] = [""]
from bson.dbref import DBRef
from bson.objectid import ObjectId
from bson.son import SON
_IS_SYNC = False
gen_target = 100
reduction_attempts = 10
examples = 5
def lift(value):
return lambda: value
def choose_lifted(generator_list):
return lambda: random.choice(generator_list)
def my_map(generator, function):
return lambda: function(generator())
def choose(list):
return lambda: random.choice(list)()
def gen_range(start, stop):
return lambda: random.randint(start, stop)
def gen_int():
max_int = 2147483647
return lambda: random.randint(-max_int - 1, max_int)
def gen_float():
return lambda: (random.random() - 0.5) * sys.maxsize
def gen_boolean():
return lambda: random.choice([True, False])
def gen_printable_char():
return lambda: chr(random.randint(32, 126))
def gen_printable_string(gen_length):
return lambda: "".join(gen_list(gen_printable_char(), gen_length)())
def gen_char(set=None):
return lambda: bytes([random.randint(0, 255)])
def gen_string(gen_length):
return lambda: b"".join(gen_list(gen_char(), gen_length)())
def gen_unichar():
return lambda: chr(random.randint(1, 0xFFF))
def gen_unicode(gen_length):
return lambda: "".join([x for x in gen_list(gen_unichar(), gen_length)() if x not in ".$"])
def gen_list(generator, gen_length):
return lambda: [generator() for _ in range(gen_length())]
def gen_datetime():
return lambda: datetime.datetime(
random.randint(1970, 2037),
random.randint(1, 12),
random.randint(1, 28),
random.randint(0, 23),
random.randint(0, 59),
random.randint(0, 59),
random.randint(0, 999) * 1000,
)
def gen_dict(gen_key, gen_value, gen_length):
def a_dict(gen_key, gen_value, length):
result = {}
for _ in range(length):
result[gen_key()] = gen_value()
return result
return lambda: a_dict(gen_key, gen_value, gen_length())
def gen_regexp(gen_length):
# TODO our patterns only consist of one letter.
# this is because of a bug in CPython's regex equality testing,
# which I haven't quite tracked down, so I'm just ignoring it...
def pattern():
return "".join(gen_list(choose_lifted("a"), gen_length)())
def gen_flags():
flags = 0
if random.random() > 0.5:
flags = flags | re.IGNORECASE
if random.random() > 0.5:
flags = flags | re.MULTILINE
if random.random() > 0.5:
flags = flags | re.VERBOSE
return flags
return lambda: re.compile(pattern(), gen_flags())
def gen_objectid():
return lambda: ObjectId()
def gen_dbref():
collection = gen_unicode(gen_range(0, 20))
return lambda: DBRef(collection(), gen_mongo_value(1, True)())
def gen_mongo_value(depth, ref):
choices = [
gen_unicode(gen_range(0, 50)),
gen_printable_string(gen_range(0, 50)),
my_map(gen_string(gen_range(0, 1000)), bytes),
gen_int(),
gen_float(),
gen_boolean(),
gen_datetime(),
gen_objectid(),
lift(None),
]
if ref:
choices.append(gen_dbref())
if depth > 0:
choices.append(gen_mongo_list(depth, ref))
choices.append(gen_mongo_dict(depth, ref))
return choose(choices)
def gen_mongo_list(depth, ref):
return gen_list(gen_mongo_value(depth - 1, ref), gen_range(0, 10))
def gen_mongo_dict(depth, ref=True):
return my_map(
gen_dict(gen_unicode(gen_range(0, 20)), gen_mongo_value(depth - 1, ref), gen_range(0, 10)),
SON,
)
def simplify(case): # TODO this is a hack
if isinstance(case, SON) and "$ref" not in case:
simplified = SON(case) # make a copy!
if random.choice([True, False]):
# delete
simplified_keys = list(simplified)
if not len(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):
return (False, case)
(key, value) = random.choice(simplified_items)
(success, value) = simplify(value)
simplified[key] = value
return (success, success and simplified or case)
if isinstance(case, list):
simplified = list(case)
if random.choice([True, False]):
# delete
if not len(simplified):
return (False, case)
simplified.pop(random.randrange(len(simplified)))
return (True, simplified)
else:
# simplify an item
if not len(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 (False, case)
async def reduce(case, predicate, reductions=0):
for _ in range(reduction_attempts):
(reduced, simplified) = simplify(case)
if reduced and not await predicate(simplified):
return await reduce(simplified, predicate, reductions + 1)
return (reductions, case)
async def isnt(predicate):
async def is_not(x):
return not await predicate(x)
return is_not
async def check(predicate, generator):
counter_examples = []
for _ in range(gen_target):
case = generator()
try:
if not await predicate(case):
reduction = await reduce(case, predicate)
counter_examples.append("after {} reductions: {!r}".format(*reduction))
except:
counter_examples.append(f"{case!r} : {traceback.format_exc()}")
return counter_examples
async def check_unittest(test, predicate, generator):
counter_examples = await check(predicate, generator)
if counter_examples:
failures = len(counter_examples)
message = "\n".join([" -> %s" % f for f in counter_examples[:examples]])
message = "found %d counter examples, displaying first %d:\n%s" % (
failures,
min(failures, examples),
message,
)
test.fail(message)

View File

@ -26,8 +26,8 @@ sys.path[0:0] = [""]
from test.asynchronous import AsyncIntegrationTest, async_client_context, remove_all_users, unittest
from test.utils import (
async_rs_or_single_client_noauth,
async_single_client,
async_wait_until,
single_client,
)
from bson.binary import Binary, UuidRepresentation
@ -817,7 +817,7 @@ class AsyncBulkAuthorizationTestBase(AsyncBulkTestBase):
roles=[],
)
async_client_context.create_user(self.db.name, "noremove", "pw", ["noremove"])
await async_client_context.create_user(self.db.name, "noremove", "pw", ["noremove"])
async def asyncTearDown(self):
await self.db.command("dropRole", "noremove")
@ -919,7 +919,7 @@ class AsyncTestBulkAuthorization(AsyncBulkAuthorizationTestBase):
username="readonly", password="pw", authSource="pymongo_test"
)
coll = cli.pymongo_test.test
coll.find_one()
await coll.find_one()
with self.assertRaises(OperationFailure):
await coll.bulk_write([InsertOne({"x": 1})])
@ -930,7 +930,7 @@ class AsyncTestBulkAuthorization(AsyncBulkAuthorizationTestBase):
username="noremove", password="pw", authSource="pymongo_test"
)
coll = cli.pymongo_test.test
coll.find_one()
await coll.find_one()
requests = [
InsertOne({"x": 1}),
ReplaceOne({"x": 2}, {"x": 2}, upsert=True),
@ -954,7 +954,7 @@ class AsyncTestBulkWriteConcern(AsyncBulkTestBase):
if cls.w is not None and cls.w > 1:
for member in (await async_client_context.hello)["hosts"]:
if member != (await async_client_context.hello)["primary"]:
cls.secondary = single_client(*partition_node(member))
cls.secondary = await async_single_client(*partition_node(member))
break
@classmethod

View File

@ -617,7 +617,7 @@ class AsyncClientUnitTest(AsyncUnitTest):
mock_get_hosts.return_value = [(host, 1)]
AsyncMongoClient(host)
AsyncMongoClient(multi_host)
logs = [record.message for record in cm.records if record.name == "pymongo.client"]
logs = [record.getMessage() for record in cm.records if record.name == "pymongo.client"]
self.assertEqual(len(logs), 7)
@patch("pymongo.srv_resolver._SrvResolver.get_hosts")

View File

@ -21,17 +21,21 @@ import io
import sys
import zipfile
from io import BytesIO
from test.asynchronous import AsyncIntegrationTest, AsyncUnitTest, async_client_context
from test.asynchronous import (
AsyncIntegrationTest,
AsyncUnitTest,
async_client_context,
qcheck,
unittest,
)
from pymongo.asynchronous.database import AsyncDatabase
sys.path[0:0] = [""]
from test import IntegrationTest, qcheck, unittest
from test.utils import EventListener, async_rs_or_single_client, rs_or_single_client
from test.utils import EventListener, async_rs_or_single_client
from bson.objectid import ObjectId
from gridfs import GridFS
from gridfs.asynchronous.grid_file import (
_SEEK_CUR,
_SEEK_END,
@ -44,7 +48,7 @@ from gridfs.asynchronous.grid_file import (
from gridfs.errors import NoFile
from pymongo import AsyncMongoClient
from pymongo.asynchronous.helpers import aiter, anext
from pymongo.errors import ConfigurationError, InvalidOperation, ServerSelectionTimeoutError
from pymongo.errors import ConfigurationError, ServerSelectionTimeoutError
from pymongo.message import _CursorAddress
_IS_SYNC = False
@ -407,8 +411,6 @@ class AsyncTestGridFile(AsyncIntegrationTest):
g = AsyncGridOut(self.db.fs, f._id)
self.assertEqual(random_string, await g.read())
# TODO: https://jira.mongodb.org/browse/PYTHON-4708
@async_client_context.require_sync
async def test_small_chunks(self):
self.files = 0
self.chunks = 0
@ -431,7 +433,7 @@ class AsyncTestGridFile(AsyncIntegrationTest):
self.assertEqual(data, await g.read(10) + await g.read(10))
return True
qcheck.check_unittest(self, helper, qcheck.gen_string(qcheck.gen_range(0, 20)))
await qcheck.check_unittest(self, helper, qcheck.gen_string(qcheck.gen_range(0, 20)))
async def test_seek(self):
f = AsyncGridIn(self.db.fs, chunkSize=3)

View File

@ -37,15 +37,15 @@ class TestLogger(AsyncIntegrationTest):
with self.assertLogs("pymongo.command", level="DEBUG") as cm:
await db.test.insert_many(docs)
cmd_started_log = json_util.loads(cm.records[0].message)
cmd_started_log = json_util.loads(cm.records[0].getMessage())
self.assertEqual(len(cmd_started_log["command"]), _DEFAULT_DOCUMENT_LENGTH + 3)
cmd_succeeded_log = json_util.loads(cm.records[1].message)
cmd_succeeded_log = json_util.loads(cm.records[1].getMessage())
self.assertLessEqual(len(cmd_succeeded_log["reply"]), _DEFAULT_DOCUMENT_LENGTH + 3)
with self.assertLogs("pymongo.command", level="DEBUG") as cm:
await db.test.find({}).to_list()
cmd_succeeded_log = json_util.loads(cm.records[1].message)
cmd_succeeded_log = json_util.loads(cm.records[1].getMessage())
self.assertEqual(len(cmd_succeeded_log["reply"]), _DEFAULT_DOCUMENT_LENGTH + 3)
async def test_configured_truncation_limit(self):
@ -55,14 +55,14 @@ class TestLogger(AsyncIntegrationTest):
with self.assertLogs("pymongo.command", level="DEBUG") as cm:
await db.command(cmd)
cmd_started_log = json_util.loads(cm.records[0].message)
cmd_started_log = json_util.loads(cm.records[0].getMessage())
self.assertEqual(len(cmd_started_log["command"]), 5 + 3)
cmd_succeeded_log = json_util.loads(cm.records[1].message)
cmd_succeeded_log = json_util.loads(cm.records[1].getMessage())
self.assertLessEqual(len(cmd_succeeded_log["reply"]), 5 + 3)
with self.assertRaises(OperationFailure):
await db.command({"notARealCommand": True})
cmd_failed_log = json_util.loads(cm.records[-1].message)
cmd_failed_log = json_util.loads(cm.records[-1].getMessage())
self.assertEqual(len(cmd_failed_log["failure"]), 5 + 3)
async def test_truncation_multi_byte_codepoints(self):
@ -78,7 +78,7 @@ class TestLogger(AsyncIntegrationTest):
with patch.dict("os.environ", {"MONGOB_LOG_MAX_DOCUMENT_LENGTH": length}):
with self.assertLogs("pymongo.command", level="DEBUG") as cm:
await self.db.test.insert_one({"x": multi_byte_char_str})
cmd_started_log = json_util.loads(cm.records[0].message)["command"]
cmd_started_log = json_util.loads(cm.records[0].getMessage())["command"]
cmd_started_log = cmd_started_log[:-3]
last_3_bytes = cmd_started_log.encode()[-3:].decode()

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,8 @@
"replicaset",
"sharded",
"load-balanced"
]
],
"serverless": "forbid"
}
],
"database_name": "default",

View File

@ -29,21 +29,22 @@ except ImportError:
from bson import Timestamp
from pymongo import DeleteMany, InsertOne, MongoClient, UpdateOne
from pymongo.errors import OperationFailure
pytestmark = pytest.mark.mockupdb
class TestClusterTime(unittest.TestCase):
def cluster_time_conversation(self, callback, replies):
def cluster_time_conversation(self, callback, replies, max_wire_version=6):
cluster_time = Timestamp(0, 0)
server = MockupDB()
# First test all commands include $clusterTime with wire version 6.
# First test all commands include $clusterTime with max_wire_version.
_ = server.autoresponds(
"ismaster",
{
"minWireVersion": 0,
"maxWireVersion": 6,
"maxWireVersion": max_wire_version,
"$clusterTime": {"clusterTime": cluster_time},
},
)
@ -166,6 +167,30 @@ class TestClusterTime(unittest.TestCase):
request.reply(reply)
client.close()
def test_collection_bulk_error(self):
def callback(client: MongoClient[dict]) -> None:
with self.assertRaises(OperationFailure):
client.db.collection.bulk_write([InsertOne({}), InsertOne({})])
self.cluster_time_conversation(
callback,
[{"ok": 0, "errmsg": "mock error"}],
)
def test_client_bulk_error(self):
def callback(client: MongoClient[dict]) -> None:
with self.assertRaises(OperationFailure):
client.bulk_write(
[
InsertOne({}, namespace="db.collection"),
InsertOne({}, namespace="db.collection"),
]
)
self.cluster_time_conversation(
callback, [{"ok": 0, "errmsg": "mock error"}], max_wire_version=25
)
if __name__ == "__main__":
unittest.main()

View File

@ -25,6 +25,8 @@ from bson.dbref import DBRef
from bson.objectid import ObjectId
from bson.son import SON
_IS_SYNC = True
gen_target = 100
reduction_attempts = 10
examples = 5
@ -221,7 +223,10 @@ def reduce(case, predicate, reductions=0):
def isnt(predicate):
return lambda x: not predicate(x)
def is_not(x):
return not predicate(x)
return is_not
def check(predicate, generator):

View File

@ -611,7 +611,7 @@ class ClientUnitTest(UnitTest):
mock_get_hosts.return_value = [(host, 1)]
MongoClient(host)
MongoClient(multi_host)
logs = [record.message for record in cm.records if record.name == "pymongo.client"]
logs = [record.getMessage() for record in cm.records if record.name == "pymongo.client"]
self.assertEqual(len(logs), 7)
@patch("pymongo.srv_resolver._SrvResolver.get_hosts")

View File

@ -21,17 +21,21 @@ import io
import sys
import zipfile
from io import BytesIO
from test import IntegrationTest, UnitTest, client_context
from test import (
IntegrationTest,
UnitTest,
client_context,
qcheck,
unittest,
)
from pymongo.synchronous.database import Database
sys.path[0:0] = [""]
from test import IntegrationTest, qcheck, unittest
from test.utils import EventListener, rs_or_single_client
from bson.objectid import ObjectId
from gridfs import GridFS
from gridfs.errors import NoFile
from gridfs.synchronous.grid_file import (
_SEEK_CUR,
@ -43,7 +47,7 @@ from gridfs.synchronous.grid_file import (
GridOutCursor,
)
from pymongo import MongoClient
from pymongo.errors import ConfigurationError, InvalidOperation, ServerSelectionTimeoutError
from pymongo.errors import ConfigurationError, ServerSelectionTimeoutError
from pymongo.message import _CursorAddress
from pymongo.synchronous.helpers import iter, next
@ -405,8 +409,6 @@ class TestGridFile(IntegrationTest):
g = GridOut(self.db.fs, f._id)
self.assertEqual(random_string, g.read())
# TODO: https://jira.mongodb.org/browse/PYTHON-4708
@client_context.require_sync
def test_small_chunks(self):
self.files = 0
self.chunks = 0

View File

@ -36,15 +36,15 @@ class TestLogger(IntegrationTest):
with self.assertLogs("pymongo.command", level="DEBUG") as cm:
db.test.insert_many(docs)
cmd_started_log = json_util.loads(cm.records[0].message)
cmd_started_log = json_util.loads(cm.records[0].getMessage())
self.assertEqual(len(cmd_started_log["command"]), _DEFAULT_DOCUMENT_LENGTH + 3)
cmd_succeeded_log = json_util.loads(cm.records[1].message)
cmd_succeeded_log = json_util.loads(cm.records[1].getMessage())
self.assertLessEqual(len(cmd_succeeded_log["reply"]), _DEFAULT_DOCUMENT_LENGTH + 3)
with self.assertLogs("pymongo.command", level="DEBUG") as cm:
db.test.find({}).to_list()
cmd_succeeded_log = json_util.loads(cm.records[1].message)
cmd_succeeded_log = json_util.loads(cm.records[1].getMessage())
self.assertEqual(len(cmd_succeeded_log["reply"]), _DEFAULT_DOCUMENT_LENGTH + 3)
def test_configured_truncation_limit(self):
@ -54,14 +54,14 @@ class TestLogger(IntegrationTest):
with self.assertLogs("pymongo.command", level="DEBUG") as cm:
db.command(cmd)
cmd_started_log = json_util.loads(cm.records[0].message)
cmd_started_log = json_util.loads(cm.records[0].getMessage())
self.assertEqual(len(cmd_started_log["command"]), 5 + 3)
cmd_succeeded_log = json_util.loads(cm.records[1].message)
cmd_succeeded_log = json_util.loads(cm.records[1].getMessage())
self.assertLessEqual(len(cmd_succeeded_log["reply"]), 5 + 3)
with self.assertRaises(OperationFailure):
db.command({"notARealCommand": True})
cmd_failed_log = json_util.loads(cm.records[-1].message)
cmd_failed_log = json_util.loads(cm.records[-1].getMessage())
self.assertEqual(len(cmd_failed_log["failure"]), 5 + 3)
def test_truncation_multi_byte_codepoints(self):
@ -77,7 +77,7 @@ class TestLogger(IntegrationTest):
with patch.dict("os.environ", {"MONGOB_LOG_MAX_DOCUMENT_LENGTH": length}):
with self.assertLogs("pymongo.command", level="DEBUG") as cm:
self.db.test.insert_one({"x": multi_byte_char_str})
cmd_started_log = json_util.loads(cm.records[0].message)["command"]
cmd_started_log = json_util.loads(cm.records[0].getMessage())["command"]
cmd_started_log = cmd_started_log[:-3]
last_3_bytes = cmd_started_log.encode()[-3:].decode()

View File

@ -13,6 +13,7 @@
# limitations under the License.
from __future__ import annotations
import asyncio
import copy
import datetime
import sys
@ -21,8 +22,19 @@ from typing import Any
sys.path[0:0] = [""]
from test import IntegrationTest, client_context, client_knobs, sanitize_cmd, unittest
from test.utils import EventListener, rs_or_single_client, single_client, wait_until
from test import (
IntegrationTest,
client_context,
client_knobs,
sanitize_cmd,
unittest,
)
from test.utils import (
EventListener,
rs_or_single_client,
single_client,
wait_until,
)
from bson.int64 import Int64
from bson.objectid import ObjectId
@ -31,23 +43,26 @@ 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
class TestCommandMonitoring(IntegrationTest):
listener: EventListener
@classmethod
@client_context.require_connection
def setUpClass(cls):
super().setUpClass()
def _setup_class(cls):
super()._setup_class()
cls.listener = EventListener()
cls.client = rs_or_single_client(event_listeners=[cls.listener], retryWrites=False)
@classmethod
def tearDownClass(cls):
def _tearDown_class(cls):
cls.client.close()
super().tearDownClass()
super()._tearDown_class()
def tearDown(self):
self.listener.reset()
@ -171,7 +186,7 @@ class TestCommandMonitoring(IntegrationTest):
self.assertEqual(csr["nextBatch"], [{} for _ in range(4)])
finally:
# Exhaust the cursor to avoid kill cursors.
tuple(cursor)
tuple(cursor.to_list())
def test_find_with_explain(self):
cmd = SON([("explain", SON([("find", "test"), ("filter", {})]))])
@ -230,7 +245,7 @@ class TestCommandMonitoring(IntegrationTest):
self.assertEqual(self.client.address, succeeded.connection_id)
finally:
# Exhaust the cursor to avoid kill cursors.
tuple(cursor)
tuple(cursor.to_list())
def test_find_options(self):
query = {
@ -356,7 +371,7 @@ class TestCommandMonitoring(IntegrationTest):
self.assertEqualReply(expected_result, succeeded.reply)
finally:
# Exhaust the cursor to avoid kill cursors.
tuple(cursor)
tuple(cursor.to_list())
def test_get_more_failure(self):
address = self.client.address
@ -451,7 +466,7 @@ class TestCommandMonitoring(IntegrationTest):
self.assertEqualReply(expected_result, succeeded.reply)
self.listener.reset()
tuple(cursor)
tuple(cursor.to_list())
self.assertEqual(0, len(self.listener.failed_events))
for event in self.listener.started_events:
self.assertTrue(isinstance(event, monitoring.CommandStartedEvent))
@ -898,7 +913,11 @@ class TestCommandMonitoring(IntegrationTest):
self.assertEqual(succeed.operation_id, operation_id)
self.assertEqual(1, succeed.reply.get("ok"))
self.assertEqual(documents, docs)
wait_until(lambda: coll.count_documents({}) == 6, "insert documents with w=0")
def check():
return coll.count_documents({}) == 6
wait_until(check, "insert documents with w=0")
def test_bulk_write(self):
coll = self.client.pymongo_test.test
@ -1058,7 +1077,7 @@ class TestCommandMonitoring(IntegrationTest):
# Regardless of server version and use of helpers._first_batch
# this test should still pass.
self.listener.reset()
tuple(self.client.pymongo_test.test.list_indexes())
tuple((self.client.pymongo_test.test.list_indexes()).to_list())
started = self.listener.started_events[0]
succeeded = self.listener.succeeded_events[0]
self.assertEqual(0, len(self.listener.failed_events))
@ -1119,8 +1138,8 @@ class TestGlobalListener(IntegrationTest):
@classmethod
@client_context.require_connection
def setUpClass(cls):
super().setUpClass()
def _setup_class(cls):
super()._setup_class()
cls.listener = EventListener()
# We plan to call register(), which internally modifies _LISTENERS.
cls.saved_listeners = copy.deepcopy(monitoring._LISTENERS)
@ -1130,10 +1149,10 @@ class TestGlobalListener(IntegrationTest):
cls.client.pymongo_test.command("ping")
@classmethod
def tearDownClass(cls):
def _tearDown_class(cls):
monitoring._LISTENERS = cls.saved_listeners
cls.client.close()
super().tearDownClass()
super()._tearDown_class()
def setUp(self):
super().setUp()

View File

@ -1925,7 +1925,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest):
for log in log_list:
if log.module == "ocsp_support":
continue
data = json_util.loads(log.message)
data = json_util.loads(log.getMessage())
client = data.pop("clientId") if "clientId" in data else data.pop("topologyId")
client_to_log[client].append(
{

View File

@ -159,6 +159,7 @@ converted_tests = [
"conftest.py",
"pymongo_mocks.py",
"utils_spec_runner.py",
"qcheck.py",
"test_bulk.py",
"test_client.py",
"test_client_bulk_write.py",
@ -171,6 +172,7 @@ converted_tests = [
"test_session.py",
"test_transactions.py",
"test_client_context.py",
"test_monitoring.py",
]
sync_test_files = [