Compare commits
25 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d504d14eb2 | ||
|
|
ab9e7488e7 | ||
|
|
2fdf707ece | ||
|
|
5139adbf2c | ||
|
|
b3c55ffc0b | ||
|
|
113b9da2d4 | ||
|
|
585411ae3a | ||
|
|
f1f493888b | ||
|
|
a0d232bf86 | ||
|
|
14ed482eb7 | ||
|
|
94de52aedb | ||
|
|
2edadda4d2 | ||
|
|
a7b6938ce8 | ||
|
|
ed7a58640f | ||
|
|
255d1906d2 | ||
|
|
5d8b4336f2 | ||
|
|
8cbefe5d79 | ||
|
|
2e39101f10 | ||
|
|
e059fdef6b | ||
|
|
2fa651c739 | ||
|
|
23a3f3c128 | ||
|
|
11b3f9aca0 | ||
|
|
a3ee1f825c | ||
|
|
7713a727d8 | ||
|
|
6e76e3bc39 |
@ -58,14 +58,12 @@ functions:
|
|||||||
|
|
||||||
export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration"
|
export MONGO_ORCHESTRATION_HOME="$DRIVERS_TOOLS/.evergreen/orchestration"
|
||||||
export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin"
|
export MONGODB_BINARIES="$DRIVERS_TOOLS/mongodb/bin"
|
||||||
export UPLOAD_BUCKET="${project}"
|
|
||||||
|
|
||||||
cat <<EOT > expansion.yml
|
cat <<EOT > expansion.yml
|
||||||
CURRENT_VERSION: "$CURRENT_VERSION"
|
CURRENT_VERSION: "$CURRENT_VERSION"
|
||||||
DRIVERS_TOOLS: "$DRIVERS_TOOLS"
|
DRIVERS_TOOLS: "$DRIVERS_TOOLS"
|
||||||
MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME"
|
MONGO_ORCHESTRATION_HOME: "$MONGO_ORCHESTRATION_HOME"
|
||||||
MONGODB_BINARIES: "$MONGODB_BINARIES"
|
MONGODB_BINARIES: "$MONGODB_BINARIES"
|
||||||
UPLOAD_BUCKET: "$UPLOAD_BUCKET"
|
|
||||||
PROJECT_DIRECTORY: "$PROJECT_DIRECTORY"
|
PROJECT_DIRECTORY: "$PROJECT_DIRECTORY"
|
||||||
PREPARE_SHELL: |
|
PREPARE_SHELL: |
|
||||||
set -o errexit
|
set -o errexit
|
||||||
@ -73,7 +71,6 @@ functions:
|
|||||||
export DRIVERS_TOOLS="$DRIVERS_TOOLS"
|
export DRIVERS_TOOLS="$DRIVERS_TOOLS"
|
||||||
export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME"
|
export MONGO_ORCHESTRATION_HOME="$MONGO_ORCHESTRATION_HOME"
|
||||||
export MONGODB_BINARIES="$MONGODB_BINARIES"
|
export MONGODB_BINARIES="$MONGODB_BINARIES"
|
||||||
export UPLOAD_BUCKET="$UPLOAD_BUCKET"
|
|
||||||
export PROJECT_DIRECTORY="$PROJECT_DIRECTORY"
|
export PROJECT_DIRECTORY="$PROJECT_DIRECTORY"
|
||||||
|
|
||||||
export TMPDIR="$MONGO_ORCHESTRATION_HOME/db"
|
export TMPDIR="$MONGO_ORCHESTRATION_HOME/db"
|
||||||
@ -103,30 +100,35 @@ functions:
|
|||||||
echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config
|
echo "{ \"releases\": { \"default\": \"$MONGODB_BINARIES\" }}" > $MONGO_ORCHESTRATION_HOME/orchestration.config
|
||||||
|
|
||||||
"upload coverage" :
|
"upload coverage" :
|
||||||
|
- command: ec2.assume_role
|
||||||
|
params:
|
||||||
|
role_arn: ${assume_role_arn}
|
||||||
- command: s3.put
|
- command: s3.put
|
||||||
params:
|
params:
|
||||||
aws_key: ${aws_key}
|
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||||
aws_secret: ${aws_secret}
|
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||||
|
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||||
local_file: src/.coverage
|
local_file: src/.coverage
|
||||||
optional: true
|
optional: true
|
||||||
# Upload the coverage report for all tasks in a single build to the same directory.
|
# Upload the coverage report for all tasks in a single build to the same directory.
|
||||||
remote_file: ${UPLOAD_BUCKET}/coverage/${revision}/${version_id}/coverage/coverage.${build_variant}.${task_name}
|
remote_file: coverage/${revision}/${version_id}/coverage/coverage.${build_variant}.${task_name}
|
||||||
bucket: mciuploads
|
bucket: ${bucket_name}
|
||||||
permissions: public-read
|
permissions: public-read
|
||||||
content_type: text/html
|
content_type: text/html
|
||||||
display_name: "Raw Coverage Report"
|
display_name: "Raw Coverage Report"
|
||||||
|
|
||||||
"download and merge coverage" :
|
"download and merge coverage" :
|
||||||
|
- command: ec2.assume_role
|
||||||
|
params:
|
||||||
|
role_arn: ${assume_role_arn}
|
||||||
- command: shell.exec
|
- command: shell.exec
|
||||||
params:
|
params:
|
||||||
silent: true
|
|
||||||
working_dir: "src"
|
working_dir: "src"
|
||||||
|
silent: true
|
||||||
|
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
|
||||||
script: |
|
script: |
|
||||||
export AWS_ACCESS_KEY_ID=${aws_key}
|
|
||||||
export AWS_SECRET_ACCESS_KEY=${aws_secret}
|
|
||||||
|
|
||||||
# Download all the task coverage files.
|
# Download all the task coverage files.
|
||||||
aws s3 cp --recursive s3://mciuploads/${UPLOAD_BUCKET}/coverage/${revision}/${version_id}/coverage/ coverage/
|
aws s3 cp --recursive s3://${bucket_name}/coverage/${revision}/${version_id}/coverage/ coverage/
|
||||||
- command: shell.exec
|
- command: shell.exec
|
||||||
params:
|
params:
|
||||||
working_dir: "src"
|
working_dir: "src"
|
||||||
@ -136,20 +138,20 @@ functions:
|
|||||||
# Upload the resulting html coverage report.
|
# Upload the resulting html coverage report.
|
||||||
- command: shell.exec
|
- command: shell.exec
|
||||||
params:
|
params:
|
||||||
silent: true
|
|
||||||
working_dir: "src"
|
working_dir: "src"
|
||||||
|
silent: true
|
||||||
|
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
|
||||||
script: |
|
script: |
|
||||||
export AWS_ACCESS_KEY_ID=${aws_key}
|
aws s3 cp htmlcov/ s3://${bucket_name}/coverage/${revision}/${version_id}/htmlcov/ --recursive --acl public-read --region us-east-1
|
||||||
export AWS_SECRET_ACCESS_KEY=${aws_secret}
|
|
||||||
aws s3 cp htmlcov/ s3://mciuploads/${UPLOAD_BUCKET}/coverage/${revision}/${version_id}/htmlcov/ --recursive --acl public-read --region us-east-1
|
|
||||||
# Attach the index.html with s3.put so it shows up in the Evergreen UI.
|
# Attach the index.html with s3.put so it shows up in the Evergreen UI.
|
||||||
- command: s3.put
|
- command: s3.put
|
||||||
params:
|
params:
|
||||||
aws_key: ${aws_key}
|
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||||
aws_secret: ${aws_secret}
|
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||||
|
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||||
local_file: src/htmlcov/index.html
|
local_file: src/htmlcov/index.html
|
||||||
remote_file: ${UPLOAD_BUCKET}/coverage/${revision}/${version_id}/htmlcov/index.html
|
remote_file: coverage/${revision}/${version_id}/htmlcov/index.html
|
||||||
bucket: mciuploads
|
bucket: ${bucket_name}
|
||||||
permissions: public-read
|
permissions: public-read
|
||||||
content_type: text/html
|
content_type: text/html
|
||||||
display_name: "Coverage Report HTML"
|
display_name: "Coverage Report HTML"
|
||||||
@ -172,34 +174,40 @@ functions:
|
|||||||
include:
|
include:
|
||||||
- "./**.core"
|
- "./**.core"
|
||||||
- "./**.mdmp" # Windows: minidumps
|
- "./**.mdmp" # Windows: minidumps
|
||||||
|
- command: ec2.assume_role
|
||||||
|
params:
|
||||||
|
role_arn: ${assume_role_arn}
|
||||||
- command: s3.put
|
- command: s3.put
|
||||||
params:
|
params:
|
||||||
aws_key: ${aws_key}
|
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||||
aws_secret: ${aws_secret}
|
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||||
|
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||||
local_file: mongo-coredumps.tgz
|
local_file: mongo-coredumps.tgz
|
||||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/coredumps/${task_id}-${execution}-mongodb-coredumps.tar.gz
|
remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/coredumps/${task_id}-${execution}-mongodb-coredumps.tar.gz
|
||||||
bucket: mciuploads
|
bucket: ${bucket_name}
|
||||||
permissions: public-read
|
permissions: public-read
|
||||||
content_type: ${content_type|application/gzip}
|
content_type: ${content_type|application/gzip}
|
||||||
display_name: Core Dumps - Execution
|
display_name: Core Dumps - Execution
|
||||||
optional: true
|
optional: true
|
||||||
- command: s3.put
|
- command: s3.put
|
||||||
params:
|
params:
|
||||||
aws_key: ${aws_key}
|
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||||
aws_secret: ${aws_secret}
|
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||||
|
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||||
local_file: mongodb-logs.tar.gz
|
local_file: mongodb-logs.tar.gz
|
||||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz
|
remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-mongodb-logs.tar.gz
|
||||||
bucket: mciuploads
|
bucket: ${bucket_name}
|
||||||
permissions: public-read
|
permissions: public-read
|
||||||
content_type: ${content_type|application/x-gzip}
|
content_type: ${content_type|application/x-gzip}
|
||||||
display_name: "mongodb-logs.tar.gz"
|
display_name: "mongodb-logs.tar.gz"
|
||||||
- command: s3.put
|
- command: s3.put
|
||||||
params:
|
params:
|
||||||
aws_key: ${aws_key}
|
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||||
aws_secret: ${aws_secret}
|
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||||
|
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||||
local_file: drivers-tools/.evergreen/orchestration/server.log
|
local_file: drivers-tools/.evergreen/orchestration/server.log
|
||||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-orchestration.log
|
remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/logs/${task_id}-${execution}-orchestration.log
|
||||||
bucket: mciuploads
|
bucket: ${bucket_name}
|
||||||
permissions: public-read
|
permissions: public-read
|
||||||
content_type: ${content_type|text/plain}
|
content_type: ${content_type|text/plain}
|
||||||
display_name: "orchestration.log"
|
display_name: "orchestration.log"
|
||||||
@ -211,13 +219,17 @@ functions:
|
|||||||
source_dir: ${PROJECT_DIRECTORY}/
|
source_dir: ${PROJECT_DIRECTORY}/
|
||||||
include:
|
include:
|
||||||
- "./**"
|
- "./**"
|
||||||
|
- command: ec2.assume_role
|
||||||
|
params:
|
||||||
|
role_arn: ${assume_role_arn}
|
||||||
- command: s3.put
|
- command: s3.put
|
||||||
params:
|
params:
|
||||||
aws_key: ${aws_key}
|
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||||
aws_secret: ${aws_secret}
|
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||||
|
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||||
local_file: working-dir.tar.gz
|
local_file: working-dir.tar.gz
|
||||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-working-dir.tar.gz
|
remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-working-dir.tar.gz
|
||||||
bucket: mciuploads
|
bucket: ${bucket_name}
|
||||||
permissions: public-read
|
permissions: public-read
|
||||||
content_type: ${content_type|application/x-gzip}
|
content_type: ${content_type|application/x-gzip}
|
||||||
display_name: "working-dir.tar.gz"
|
display_name: "working-dir.tar.gz"
|
||||||
@ -232,11 +244,12 @@ functions:
|
|||||||
- "*.lock"
|
- "*.lock"
|
||||||
- command: s3.put
|
- command: s3.put
|
||||||
params:
|
params:
|
||||||
aws_key: ${aws_key}
|
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||||
aws_secret: ${aws_secret}
|
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||||
|
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||||
local_file: drivers-dir.tar.gz
|
local_file: drivers-dir.tar.gz
|
||||||
remote_file: ${UPLOAD_BUCKET}/${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-drivers-dir.tar.gz
|
remote_file: ${build_variant}/${revision}/${version_id}/${build_id}/artifacts/${task_id}-${execution}-drivers-dir.tar.gz
|
||||||
bucket: mciuploads
|
bucket: ${bucket_name}
|
||||||
permissions: public-read
|
permissions: public-read
|
||||||
content_type: ${content_type|application/x-gzip}
|
content_type: ${content_type|application/x-gzip}
|
||||||
display_name: "drivers-dir.tar.gz"
|
display_name: "drivers-dir.tar.gz"
|
||||||
@ -791,27 +804,32 @@ functions:
|
|||||||
source_dir: "src/dist"
|
source_dir: "src/dist"
|
||||||
include:
|
include:
|
||||||
- "*"
|
- "*"
|
||||||
|
- command: ec2.assume_role
|
||||||
|
params:
|
||||||
|
role_arn: ${assume_role_arn}
|
||||||
- command: s3.put
|
- command: s3.put
|
||||||
params:
|
params:
|
||||||
aws_key: ${aws_key}
|
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||||
aws_secret: ${aws_secret}
|
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||||
|
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||||
local_file: release-files.tgz
|
local_file: release-files.tgz
|
||||||
remote_file: ${UPLOAD_BUCKET}/release/${revision}/${task_id}-${execution}-release-files.tar.gz
|
remote_file: release/${revision}/${task_id}-${execution}-release-files.tar.gz
|
||||||
bucket: mciuploads
|
bucket: ${bucket_name}
|
||||||
permissions: public-read
|
permissions: public-read
|
||||||
content_type: ${content_type|application/gzip}
|
content_type: ${content_type|application/gzip}
|
||||||
display_name: Release files
|
display_name: Release files
|
||||||
|
|
||||||
"download and merge releases":
|
"download and merge releases":
|
||||||
|
- command: ec2.assume_role
|
||||||
|
params:
|
||||||
|
role_arn: ${assume_role_arn}
|
||||||
- command: shell.exec
|
- command: shell.exec
|
||||||
params:
|
params:
|
||||||
silent: true
|
silent: true
|
||||||
|
include_expansions_in_env: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN"]
|
||||||
script: |
|
script: |
|
||||||
export AWS_ACCESS_KEY_ID=${aws_key}
|
|
||||||
export AWS_SECRET_ACCESS_KEY=${aws_secret}
|
|
||||||
|
|
||||||
# Download all the task coverage files.
|
# Download all the task coverage files.
|
||||||
aws s3 cp --recursive s3://mciuploads/${UPLOAD_BUCKET}/release/${revision}/ release/
|
aws s3 cp --recursive s3://${bucket_name}/release/${revision}/ release/
|
||||||
- command: shell.exec
|
- command: shell.exec
|
||||||
params:
|
params:
|
||||||
shell: "bash"
|
shell: "bash"
|
||||||
@ -845,11 +863,12 @@ functions:
|
|||||||
- "*"
|
- "*"
|
||||||
- command: s3.put
|
- command: s3.put
|
||||||
params:
|
params:
|
||||||
aws_key: ${aws_key}
|
aws_key: ${AWS_ACCESS_KEY_ID}
|
||||||
aws_secret: ${aws_secret}
|
aws_secret: ${AWS_SECRET_ACCESS_KEY}
|
||||||
|
aws_session_token: ${AWS_SESSION_TOKEN}
|
||||||
local_file: release-files-all.tgz
|
local_file: release-files-all.tgz
|
||||||
remote_file: ${UPLOAD_BUCKET}/release-all/${revision}/${task_id}-${execution}-release-files-all.tar.gz
|
remote_file: release-all/${revision}/${task_id}-${execution}-release-files-all.tar.gz
|
||||||
bucket: mciuploads
|
bucket: ${bucket_name}
|
||||||
permissions: public-read
|
permissions: public-read
|
||||||
content_type: ${content_type|application/gzip}
|
content_type: ${content_type|application/gzip}
|
||||||
display_name: Release files all
|
display_name: Release files all
|
||||||
@ -962,7 +981,7 @@ task_groups:
|
|||||||
- ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/delete-vm.sh
|
- ${DRIVERS_TOOLS}/.evergreen/csfle/azurekms/delete-vm.sh
|
||||||
- func: "upload test results"
|
- func: "upload test results"
|
||||||
setup_group_can_fail_task: true
|
setup_group_can_fail_task: true
|
||||||
teardown_group_can_fail_task: true
|
teardown_task_can_fail_task: true
|
||||||
setup_group_timeout_secs: 1800
|
setup_group_timeout_secs: 1800
|
||||||
tasks:
|
tasks:
|
||||||
- testazurekms-task
|
- testazurekms-task
|
||||||
@ -2220,9 +2239,9 @@ axes:
|
|||||||
display_name: "RHEL 8.x"
|
display_name: "RHEL 8.x"
|
||||||
run_on: rhel87-small
|
run_on: rhel87-small
|
||||||
batchtime: 10080 # 7 days
|
batchtime: 10080 # 7 days
|
||||||
- id: rhel80-fips
|
- id: rhel92-fips
|
||||||
display_name: "RHEL 8.0 FIPS"
|
display_name: "RHEL 9.2 FIPS"
|
||||||
run_on: rhel80-fips
|
run_on: rhel92-fips
|
||||||
batchtime: 10080 # 7 days
|
batchtime: 10080 # 7 days
|
||||||
- id: ubuntu-22.04
|
- id: ubuntu-22.04
|
||||||
display_name: "Ubuntu 22.04"
|
display_name: "Ubuntu 22.04"
|
||||||
@ -2596,7 +2615,7 @@ buildvariants:
|
|||||||
- matrix_name: "tests-fips"
|
- matrix_name: "tests-fips"
|
||||||
matrix_spec:
|
matrix_spec:
|
||||||
platform:
|
platform:
|
||||||
- rhel80-fips
|
- rhel92-fips
|
||||||
auth: "auth"
|
auth: "auth"
|
||||||
ssl: "ssl"
|
ssl: "ssl"
|
||||||
display_name: "${platform} ${auth} ${ssl}"
|
display_name: "${platform} ${auth} ${ssl}"
|
||||||
|
|||||||
@ -25,7 +25,9 @@ function get_import_time() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get_import_time $HEAD_SHA
|
get_import_time $HEAD_SHA
|
||||||
|
git stash
|
||||||
git checkout $BASE_SHA
|
git checkout $BASE_SHA
|
||||||
get_import_time $BASE_SHA
|
get_import_time $BASE_SHA
|
||||||
git checkout $HEAD_SHA
|
git checkout $HEAD_SHA
|
||||||
|
git stash apply
|
||||||
python tools/compare_import_time.py $HEAD_SHA $BASE_SHA
|
python tools/compare_import_time.py $HEAD_SHA $BASE_SHA
|
||||||
|
|||||||
@ -19,7 +19,10 @@ fi
|
|||||||
PYTHON_VERSION=$(${PYTHON_BINARY} -c "import sys; sys.stdout.write('.'.join(str(val) for val in sys.version_info[:2]))")
|
PYTHON_VERSION=$(${PYTHON_BINARY} -c "import sys; sys.stdout.write('.'.join(str(val) for val in sys.version_info[:2]))")
|
||||||
|
|
||||||
# Ensure the C extensions are installed.
|
# Ensure the C extensions are installed.
|
||||||
${PYTHON_BINARY} setup.py build_ext -i
|
${PYTHON_BINARY} -m venv --system-site-packages .venv
|
||||||
|
source .venv/bin/activate
|
||||||
|
pip install -U pip
|
||||||
|
python -m pip install -e .
|
||||||
|
|
||||||
export MOD_WSGI_SO=/opt/python/mod_wsgi/python_version/$PYTHON_VERSION/mod_wsgi_version/$MOD_WSGI_VERSION/mod_wsgi.so
|
export MOD_WSGI_SO=/opt/python/mod_wsgi/python_version/$PYTHON_VERSION/mod_wsgi_version/$MOD_WSGI_VERSION/mod_wsgi.so
|
||||||
export PYTHONHOME=/opt/python/$PYTHON_VERSION
|
export PYTHONHOME=/opt/python/$PYTHON_VERSION
|
||||||
@ -38,10 +41,12 @@ trap '$APACHE -k stop -f ${PROJECT_DIRECTORY}/test/mod_wsgi_test/${APACHE_CONFIG
|
|||||||
wget -t 1 -T 10 -O - "http://localhost:8080/interpreter1${PROJECT_DIRECTORY}" || (cat error_log && exit 1)
|
wget -t 1 -T 10 -O - "http://localhost:8080/interpreter1${PROJECT_DIRECTORY}" || (cat error_log && exit 1)
|
||||||
wget -t 1 -T 10 -O - "http://localhost:8080/interpreter2${PROJECT_DIRECTORY}" || (cat error_log && exit 1)
|
wget -t 1 -T 10 -O - "http://localhost:8080/interpreter2${PROJECT_DIRECTORY}" || (cat error_log && exit 1)
|
||||||
|
|
||||||
${PYTHON_BINARY} ${PROJECT_DIRECTORY}/test/mod_wsgi_test/test_client.py -n 25000 -t 100 parallel \
|
python ${PROJECT_DIRECTORY}/test/mod_wsgi_test/test_client.py -n 25000 -t 100 parallel \
|
||||||
http://localhost:8080/interpreter1${PROJECT_DIRECTORY} http://localhost:8080/interpreter2${PROJECT_DIRECTORY} || \
|
http://localhost:8080/interpreter1${PROJECT_DIRECTORY} http://localhost:8080/interpreter2${PROJECT_DIRECTORY} || \
|
||||||
(tail -n 100 error_log && exit 1)
|
(tail -n 100 error_log && exit 1)
|
||||||
|
|
||||||
${PYTHON_BINARY} ${PROJECT_DIRECTORY}/test/mod_wsgi_test/test_client.py -n 25000 serial \
|
python ${PROJECT_DIRECTORY}/test/mod_wsgi_test/test_client.py -n 25000 serial \
|
||||||
http://localhost:8080/interpreter1${PROJECT_DIRECTORY} http://localhost:8080/interpreter2${PROJECT_DIRECTORY} || \
|
http://localhost:8080/interpreter1${PROJECT_DIRECTORY} http://localhost:8080/interpreter2${PROJECT_DIRECTORY} || \
|
||||||
(tail -n 100 error_log && exit 1)
|
(tail -n 100 error_log && exit 1)
|
||||||
|
|
||||||
|
rm -rf .venv
|
||||||
|
|||||||
@ -31,9 +31,6 @@ set -o xtrace
|
|||||||
AUTH=${AUTH:-noauth}
|
AUTH=${AUTH:-noauth}
|
||||||
SSL=${SSL:-nossl}
|
SSL=${SSL:-nossl}
|
||||||
TEST_ARGS="${*:1}"
|
TEST_ARGS="${*:1}"
|
||||||
PYTHON=$(which python)
|
|
||||||
# TODO: Remove when we drop PyPy 3.8 support.
|
|
||||||
OLD_PYPY=$(python -c "import sys; print(sys.implementation.name.lower() == 'pypy' and sys.implementation.version < (7, 3, 12))")
|
|
||||||
|
|
||||||
export PIP_QUIET=1 # Quiet by default
|
export PIP_QUIET=1 # Quiet by default
|
||||||
export PIP_PREFER_BINARY=1 # Prefer binary dists by default
|
export PIP_PREFER_BINARY=1 # Prefer binary dists by default
|
||||||
@ -113,10 +110,6 @@ fi
|
|||||||
|
|
||||||
if [ "$COMPRESSORS" = "snappy" ]; then
|
if [ "$COMPRESSORS" = "snappy" ]; then
|
||||||
python -m pip install '.[snappy]'
|
python -m pip install '.[snappy]'
|
||||||
if [ "$OLD_PYPY" == "True" ]; then
|
|
||||||
pip install "python-snappy<0.7.0"
|
|
||||||
fi
|
|
||||||
PYTHON=python
|
|
||||||
elif [ "$COMPRESSORS" = "zstd" ]; then
|
elif [ "$COMPRESSORS" = "zstd" ]; then
|
||||||
python -m pip install zstandard
|
python -m pip install zstandard
|
||||||
fi
|
fi
|
||||||
@ -158,6 +151,7 @@ if [ -n "$TEST_ENCRYPTION" ] || [ -n "$TEST_FLE_AZURE_AUTO" ] || [ -n "$TEST_FLE
|
|||||||
if [ ! -d "libmongocrypt_git" ]; then
|
if [ ! -d "libmongocrypt_git" ]; then
|
||||||
git clone https://github.com/mongodb/libmongocrypt.git libmongocrypt_git
|
git clone https://github.com/mongodb/libmongocrypt.git libmongocrypt_git
|
||||||
fi
|
fi
|
||||||
|
python -m pip install -U setuptools
|
||||||
python -m pip install ./libmongocrypt_git/bindings/python
|
python -m pip install ./libmongocrypt_git/bindings/python
|
||||||
python -c "import pymongocrypt; print('pymongocrypt version: '+pymongocrypt.__version__)"
|
python -c "import pymongocrypt; print('pymongocrypt version: '+pymongocrypt.__version__)"
|
||||||
python -c "import pymongocrypt; print('libmongocrypt version: '+pymongocrypt.libmongocrypt_version())"
|
python -c "import pymongocrypt; print('libmongocrypt version: '+pymongocrypt.libmongocrypt_version())"
|
||||||
@ -236,7 +230,7 @@ if [ -n "$PERF_TEST" ]; then
|
|||||||
TEST_ARGS="test/performance/perf_test.py"
|
TEST_ARGS="test/performance/perf_test.py"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Running $AUTH tests over $SSL with python $PYTHON"
|
echo "Running $AUTH tests over $SSL with python $(which python)"
|
||||||
python -c 'import sys; print(sys.version)'
|
python -c 'import sys; print(sys.version)'
|
||||||
|
|
||||||
|
|
||||||
@ -245,7 +239,7 @@ python -c 'import sys; print(sys.version)'
|
|||||||
|
|
||||||
# Run the tests with coverage if requested and coverage is installed.
|
# Run the tests with coverage if requested and coverage is installed.
|
||||||
# Only cover CPython. PyPy reports suspiciously low coverage.
|
# Only cover CPython. PyPy reports suspiciously low coverage.
|
||||||
PYTHON_IMPL=$($PYTHON -c "import platform; print(platform.python_implementation())")
|
PYTHON_IMPL=$(python -c "import platform; print(platform.python_implementation())")
|
||||||
if [ -n "$COVERAGE" ] && [ "$PYTHON_IMPL" = "CPython" ]; then
|
if [ -n "$COVERAGE" ] && [ "$PYTHON_IMPL" = "CPython" ]; then
|
||||||
# Keep in sync with combine-coverage.sh.
|
# Keep in sync with combine-coverage.sh.
|
||||||
# coverage >=5 is needed for relative_files=true.
|
# coverage >=5 is needed for relative_files=true.
|
||||||
|
|||||||
@ -66,7 +66,7 @@ createvirtualenv () {
|
|||||||
|
|
||||||
export PIP_QUIET=1
|
export PIP_QUIET=1
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
python -m pip install --upgrade setuptools tox
|
python -m pip install --upgrade tox
|
||||||
}
|
}
|
||||||
|
|
||||||
# Usage:
|
# Usage:
|
||||||
|
|||||||
10
.github/workflows/codeql.yml
vendored
10
.github/workflows/codeql.yml
vendored
@ -5,6 +5,11 @@ on:
|
|||||||
branches: [ "master", "v*"]
|
branches: [ "master", "v*"]
|
||||||
tags: ['*']
|
tags: ['*']
|
||||||
pull_request:
|
pull_request:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
ref:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '17 10 * * 2'
|
- cron: '17 10 * * 2'
|
||||||
|
|
||||||
@ -21,9 +26,6 @@ jobs:
|
|||||||
# required for all workflows
|
# required for all workflows
|
||||||
security-events: write
|
security-events: write
|
||||||
|
|
||||||
# required to fetch internal or private CodeQL packs
|
|
||||||
packages: read
|
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -35,6 +37,8 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
- uses: actions/setup-python@v3
|
- uses: actions/setup-python@v3
|
||||||
|
|
||||||
# Initializes the CodeQL tools for scanning.
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
|||||||
146
.github/workflows/dist.yml
vendored
Normal file
146
.github/workflows/dist.yml
vendored
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
name: Python Dist
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "[0-9]+.[0-9]+.[0-9]+"
|
||||||
|
- "[0-9]+.[0-9]+.[0-9]+.post[0-9]+"
|
||||||
|
- "[0-9]+.[0-9]+.[0-9]+[a-b][0-9]+"
|
||||||
|
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
||||||
|
workflow_dispatch:
|
||||||
|
pull_request:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
ref:
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: dist-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
shell: bash -eux {0}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_wheels:
|
||||||
|
name: Build wheels for ${{ matrix.buildplat[1] }}
|
||||||
|
runs-on: ${{ matrix.buildplat[0] }}
|
||||||
|
strategy:
|
||||||
|
# Ensure that a wheel builder finishes even if another fails
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
# Github Actions doesn't support pairing matrix values together, let's improvise
|
||||||
|
# https://github.com/github/feedback/discussions/7835#discussioncomment-1769026
|
||||||
|
buildplat:
|
||||||
|
- [ubuntu-20.04, "manylinux_x86_64", "cp3*-manylinux_x86_64"]
|
||||||
|
- [ubuntu-20.04, "manylinux_aarch64", "cp3*-manylinux_aarch64"]
|
||||||
|
- [ubuntu-20.04, "manylinux_ppc64le", "cp3*-manylinux_ppc64le"]
|
||||||
|
- [ubuntu-20.04, "manylinux_s390x", "cp3*-manylinux_s390x"]
|
||||||
|
- [ubuntu-20.04, "manylinux_i686", "cp3*-manylinux_i686"]
|
||||||
|
- [windows-2019, "win_amd6", "cp3*-win_amd64"]
|
||||||
|
- [windows-2019, "win32", "cp3*-win32"]
|
||||||
|
- [macos-14, "macos", "cp*-macosx_*"]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout pymongo
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
cache: 'pip'
|
||||||
|
python-version: 3.8
|
||||||
|
cache-dependency-path: 'pyproject.toml'
|
||||||
|
allow-prereleases: true
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
if: runner.os == 'Linux'
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Install cibuildwheel
|
||||||
|
# Note: the default manylinux is manylinux2014
|
||||||
|
run: |
|
||||||
|
python -m pip install -U pip
|
||||||
|
python -m pip install "cibuildwheel>=2.17,<3"
|
||||||
|
|
||||||
|
- name: Build wheels
|
||||||
|
env:
|
||||||
|
CIBW_BUILD: ${{ matrix.buildplat[2] }}
|
||||||
|
run: python -m cibuildwheel --output-dir wheelhouse
|
||||||
|
|
||||||
|
- name: Build manylinux1 wheels
|
||||||
|
if: ${{ matrix.buildplat[1] == 'manylinux_x86_64' || matrix.buildplat[1] == 'manylinux_i686' }}
|
||||||
|
env:
|
||||||
|
CIBW_MANYLINUX_X86_64_IMAGE: manylinux1
|
||||||
|
CIBW_MANYLINUX_I686_IMAGE: manylinux1
|
||||||
|
CIBW_BUILD: "cp38-${{ matrix.buildplat[1] }} cp39-${{ matrix.buildplat[1] }}"
|
||||||
|
run: python -m cibuildwheel --output-dir wheelhouse
|
||||||
|
|
||||||
|
- name: Assert all versions in wheelhouse
|
||||||
|
if: ${{ ! startsWith(matrix.buildplat[1], 'macos') }}
|
||||||
|
run: |
|
||||||
|
ls wheelhouse/*cp38*.whl
|
||||||
|
ls wheelhouse/*cp39*.whl
|
||||||
|
ls wheelhouse/*cp310*.whl
|
||||||
|
ls wheelhouse/*cp311*.whl
|
||||||
|
ls wheelhouse/*cp312*.whl
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: wheel-${{ matrix.buildplat[1] }}
|
||||||
|
path: ./wheelhouse/*.whl
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
make_sdist:
|
||||||
|
name: Make SDist
|
||||||
|
runs-on: macos-13
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
ref: ${{ inputs.ref }}
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
# Build sdist on lowest supported Python
|
||||||
|
python-version: '3.8'
|
||||||
|
|
||||||
|
- name: Build SDist
|
||||||
|
run: |
|
||||||
|
set -ex
|
||||||
|
python -m pip install -U pip build
|
||||||
|
python -m build --sdist .
|
||||||
|
|
||||||
|
- name: Test SDist
|
||||||
|
run: |
|
||||||
|
python -m pip install dist/*.gz
|
||||||
|
cd ..
|
||||||
|
python -c "from pymongo import has_c; assert has_c()"
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: "sdist"
|
||||||
|
path: ./dist/*.tar.gz
|
||||||
|
|
||||||
|
collect_dist:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build_wheels, make_sdist]
|
||||||
|
name: Download Wheels
|
||||||
|
steps:
|
||||||
|
- name: Download all workflow run artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
- name: Flatten directory
|
||||||
|
working-directory: .
|
||||||
|
run: |
|
||||||
|
find . -mindepth 2 -type f -exec mv {} . \;
|
||||||
|
find . -type d -empty -delete
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: all-dist-${{ github.run_id }}
|
||||||
|
path: "./*"
|
||||||
211
.github/workflows/release-python.yml
vendored
211
.github/workflows/release-python.yml
vendored
@ -1,156 +1,95 @@
|
|||||||
name: Python Wheels
|
name: Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- "[0-9]+.[0-9]+.[0-9]+"
|
|
||||||
- "[0-9]+.[0-9]+.[0-9]+.post[0-9]+"
|
|
||||||
- "[0-9]+.[0-9]+.[0-9]+[a-b][0-9]+"
|
|
||||||
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
pull_request:
|
inputs:
|
||||||
|
version:
|
||||||
|
description: "The new version to set"
|
||||||
|
required: true
|
||||||
|
following_version:
|
||||||
|
description: "The post (dev) version to set"
|
||||||
|
required: true
|
||||||
|
dry_run:
|
||||||
|
description: "Dry Run?"
|
||||||
|
default: false
|
||||||
|
type: boolean
|
||||||
|
|
||||||
concurrency:
|
env:
|
||||||
group: wheels-${{ github.ref }}
|
# Changes per repo
|
||||||
cancel-in-progress: true
|
PRODUCT_NAME: PyMongo
|
||||||
|
# Changes per branch
|
||||||
|
SILK_ASSET_GROUP: mongodb-python-driver
|
||||||
|
EVERGREEN_PROJECT: mongo-python-driver-v4.8
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash -eux {0}
|
shell: bash -eux {0}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build_wheels:
|
pre-publish:
|
||||||
name: Build wheels for ${{ matrix.buildplat[1] }}
|
environment: release
|
||||||
runs-on: ${{ matrix.buildplat[0] }}
|
|
||||||
strategy:
|
|
||||||
# Ensure that a wheel builder finishes even if another fails
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
# Github Actions doesn't support pairing matrix values together, let's improvise
|
|
||||||
# https://github.com/github/feedback/discussions/7835#discussioncomment-1769026
|
|
||||||
buildplat:
|
|
||||||
- [ubuntu-20.04, "manylinux_x86_64", "cp3*-manylinux_x86_64"]
|
|
||||||
- [ubuntu-20.04, "manylinux_aarch64", "cp3*-manylinux_aarch64"]
|
|
||||||
- [ubuntu-20.04, "manylinux_ppc64le", "cp3*-manylinux_ppc64le"]
|
|
||||||
- [ubuntu-20.04, "manylinux_s390x", "cp3*-manylinux_s390x"]
|
|
||||||
- [ubuntu-20.04, "manylinux_i686", "cp3*-manylinux_i686"]
|
|
||||||
- [windows-2019, "win_amd6", "cp3*-win_amd64"]
|
|
||||||
- [windows-2019, "win32", "cp3*-win32"]
|
|
||||||
- [macos-14, "macos", "cp*-macosx_*"]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout pymongo
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
cache: 'pip'
|
|
||||||
python-version: 3.8
|
|
||||||
cache-dependency-path: 'pyproject.toml'
|
|
||||||
allow-prereleases: true
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
if: runner.os == 'Linux'
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
with:
|
|
||||||
platforms: all
|
|
||||||
|
|
||||||
- name: Install cibuildwheel
|
|
||||||
# Note: the default manylinux is manylinux2014
|
|
||||||
run: |
|
|
||||||
python -m pip install -U pip
|
|
||||||
python -m pip install "cibuildwheel>=2.17,<3"
|
|
||||||
|
|
||||||
- name: Build wheels
|
|
||||||
env:
|
|
||||||
CIBW_BUILD: ${{ matrix.buildplat[2] }}
|
|
||||||
run: python -m cibuildwheel --output-dir wheelhouse
|
|
||||||
|
|
||||||
- name: Build manylinux1 wheels
|
|
||||||
if: ${{ matrix.buildplat[1] == 'manylinux_x86_64' || matrix.buildplat[1] == 'manylinux_i686' }}
|
|
||||||
env:
|
|
||||||
CIBW_MANYLINUX_X86_64_IMAGE: manylinux1
|
|
||||||
CIBW_MANYLINUX_I686_IMAGE: manylinux1
|
|
||||||
CIBW_BUILD: "cp38-${{ matrix.buildplat[1] }} cp39-${{ matrix.buildplat[1] }}"
|
|
||||||
run: python -m cibuildwheel --output-dir wheelhouse
|
|
||||||
|
|
||||||
- name: Assert all versions in wheelhouse
|
|
||||||
if: ${{ ! startsWith(matrix.buildplat[1], 'macos') }}
|
|
||||||
run: |
|
|
||||||
ls wheelhouse/*cp38*.whl
|
|
||||||
ls wheelhouse/*cp39*.whl
|
|
||||||
ls wheelhouse/*cp310*.whl
|
|
||||||
ls wheelhouse/*cp311*.whl
|
|
||||||
ls wheelhouse/*cp312*.whl
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: wheel-${{ matrix.buildplat[1] }}
|
|
||||||
path: ./wheelhouse/*.whl
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
make_sdist:
|
|
||||||
name: Make SDist
|
|
||||||
runs-on: macos-13
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
|
||||||
with:
|
|
||||||
# Build sdist on lowest supported Python
|
|
||||||
python-version: '3.8'
|
|
||||||
|
|
||||||
- name: Build SDist
|
|
||||||
run: |
|
|
||||||
set -ex
|
|
||||||
python -m pip install -U pip build
|
|
||||||
python -m build --sdist .
|
|
||||||
|
|
||||||
- name: Test SDist
|
|
||||||
run: |
|
|
||||||
python -m pip install dist/*.gz
|
|
||||||
cd ..
|
|
||||||
python -c "from pymongo import has_c; assert has_c()"
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: "sdist"
|
|
||||||
path: ./dist/*.tar.gz
|
|
||||||
|
|
||||||
collect_dist:
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [build_wheels, make_sdist]
|
permissions:
|
||||||
name: Download Wheels
|
id-token: write
|
||||||
|
contents: write
|
||||||
|
outputs:
|
||||||
|
version: ${{ steps.pre-publish.outputs.version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Download all workflow run artifacts
|
- uses: mongodb-labs/drivers-github-tools/secure-checkout@v2
|
||||||
uses: actions/download-artifact@v4
|
|
||||||
- name: Flatten directory
|
|
||||||
working-directory: .
|
|
||||||
run: |
|
|
||||||
find . -mindepth 2 -type f -exec mv {} . \;
|
|
||||||
find . -type d -empty -delete
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
with:
|
||||||
name: all-dist-${{ github.run_id }}
|
app_id: ${{ vars.APP_ID }}
|
||||||
path: "./*"
|
private_key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
- uses: mongodb-labs/drivers-github-tools/setup@v2
|
||||||
|
with:
|
||||||
|
aws_role_arn: ${{ secrets.AWS_ROLE_ARN }}
|
||||||
|
aws_region_name: ${{ vars.AWS_REGION_NAME }}
|
||||||
|
aws_secret_id: ${{ secrets.AWS_SECRET_ID }}
|
||||||
|
artifactory_username: ${{ vars.ARTIFACTORY_USERNAME }}
|
||||||
|
- uses: mongodb-labs/drivers-github-tools/python/pre-publish@v2
|
||||||
|
id: pre-publish
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.version }}
|
||||||
|
dry_run: ${{ inputs.dry_run }}
|
||||||
|
|
||||||
|
build-dist:
|
||||||
|
needs: [pre-publish]
|
||||||
|
uses: ./.github/workflows/dist.yml
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.pre-publish.outputs.version }}
|
||||||
|
|
||||||
|
static-scan:
|
||||||
|
needs: [pre-publish]
|
||||||
|
permissions:
|
||||||
|
security-events: write
|
||||||
|
uses: ./.github/workflows/codeql.yml
|
||||||
|
with:
|
||||||
|
ref: ${{ needs.pre-publish.outputs.version }}
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
# https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/#publishing-the-distribution-to-pypi
|
needs: [build-dist, static-scan]
|
||||||
needs: [collect_dist]
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
environment: release
|
environment: release
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write
|
id-token: write
|
||||||
|
contents: write
|
||||||
|
security-events: write
|
||||||
steps:
|
steps:
|
||||||
- name: Download all the dists
|
- uses: mongodb-labs/drivers-github-tools/secure-checkout@v2
|
||||||
uses: actions/download-artifact@v4
|
with:
|
||||||
with:
|
app_id: ${{ vars.APP_ID }}
|
||||||
name: all-dist-${{ github.run_id }}
|
private_key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
path: dist/
|
- uses: mongodb-labs/drivers-github-tools/setup@v2
|
||||||
- name: Publish distribution 📦 to PyPI
|
with:
|
||||||
uses: pypa/gh-action-pypi-publish@release/v1
|
aws_role_arn: ${{ secrets.AWS_ROLE_ARN }}
|
||||||
|
aws_region_name: ${{ vars.AWS_REGION_NAME }}
|
||||||
|
aws_secret_id: ${{ secrets.AWS_SECRET_ID }}
|
||||||
|
artifactory_username: ${{ vars.ARTIFACTORY_USERNAME }}
|
||||||
|
- uses: mongodb-labs/drivers-github-tools/python/publish@v2
|
||||||
|
with:
|
||||||
|
version: ${{ inputs.version }}
|
||||||
|
following_version: ${{ inputs.following_version }}
|
||||||
|
product_name: ${{ env.PRODUCT_NAME }}
|
||||||
|
silk_asset_group: ${{ env.SILK_ASSET_GROUP }}
|
||||||
|
evergreen_project: ${{ env.EVERGREEN_PROJECT }}
|
||||||
|
token: ${{ github.token }}
|
||||||
|
dry_run: ${{ inputs.dry_run }}
|
||||||
|
|||||||
6
.github/workflows/test-python.yml
vendored
6
.github/workflows/test-python.yml
vendored
@ -31,12 +31,10 @@ jobs:
|
|||||||
- name: Run linters
|
- name: Run linters
|
||||||
run: |
|
run: |
|
||||||
tox -m lint-manual
|
tox -m lint-manual
|
||||||
- name: Check Manifest
|
|
||||||
run: |
|
|
||||||
tox -m manifest
|
|
||||||
- name: Run compilation
|
- name: Run compilation
|
||||||
run: |
|
run: |
|
||||||
pip install -e .
|
export PYMONGO_C_EXT_MUST_BUILD=1
|
||||||
|
pip install -v -e .
|
||||||
python tools/fail_if_no_c.py
|
python tools/fail_if_no_c.py
|
||||||
- name: Run typecheck
|
- name: Run typecheck
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
33
MANIFEST.in
33
MANIFEST.in
@ -1,33 +0,0 @@
|
|||||||
include README.md
|
|
||||||
include LICENSE
|
|
||||||
include THIRD-PARTY-NOTICES
|
|
||||||
include *.ini
|
|
||||||
include sbom.json
|
|
||||||
include requirements.txt
|
|
||||||
exclude .coveragerc
|
|
||||||
exclude .git-blame-ignore-revs
|
|
||||||
exclude .pre-commit-config.yaml
|
|
||||||
exclude .readthedocs.yaml
|
|
||||||
exclude CONTRIBUTING.md
|
|
||||||
exclude RELEASE.md
|
|
||||||
recursive-include doc *.rst
|
|
||||||
recursive-include doc *.py
|
|
||||||
recursive-include doc *.conf
|
|
||||||
recursive-include doc *.css
|
|
||||||
recursive-include doc *.js
|
|
||||||
recursive-include doc *.png
|
|
||||||
include doc/Makefile
|
|
||||||
include doc/_templates/layout.html
|
|
||||||
include doc/make.bat
|
|
||||||
include doc/static/periodic-executor-refs.dot
|
|
||||||
recursive-include requirements *.txt
|
|
||||||
recursive-include tools *.py
|
|
||||||
include tools/README.rst
|
|
||||||
include green_framework_test.py
|
|
||||||
recursive-include test *.pem
|
|
||||||
recursive-include test *.py
|
|
||||||
recursive-include test *.json
|
|
||||||
recursive-include bson *.h
|
|
||||||
prune test/mod_wsgi_test
|
|
||||||
prune test/lambda
|
|
||||||
prune .evergreen
|
|
||||||
@ -78,12 +78,6 @@ PyMongo can be installed with [pip](http://pypi.python.org/pypi/pip):
|
|||||||
python -m pip install pymongo
|
python -m pip install pymongo
|
||||||
```
|
```
|
||||||
|
|
||||||
Or `easy_install` from [setuptools](http://pypi.python.org/pypi/setuptools):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python -m easy_install pymongo
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also download the project source and do:
|
You can also download the project source and do:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
143
_setup.py
Normal file
143
_setup.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
# Hack to silence atexit traceback in some Python versions
|
||||||
|
try:
|
||||||
|
import multiprocessing # noqa: F401
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
from setuptools.command.build_ext import build_ext
|
||||||
|
from setuptools.extension import Extension
|
||||||
|
|
||||||
|
|
||||||
|
class custom_build_ext(build_ext):
|
||||||
|
"""Allow C extension building to fail.
|
||||||
|
|
||||||
|
The C extension speeds up BSON encoding, but is not essential.
|
||||||
|
"""
|
||||||
|
|
||||||
|
warning_message = """
|
||||||
|
********************************************************************
|
||||||
|
WARNING: %s could not
|
||||||
|
be compiled. No C extensions are essential for PyMongo to run,
|
||||||
|
although they do result in significant speed improvements.
|
||||||
|
%s
|
||||||
|
|
||||||
|
Please see the installation docs for solutions to build issues:
|
||||||
|
|
||||||
|
https://pymongo.readthedocs.io/en/stable/installation.html
|
||||||
|
|
||||||
|
Here are some hints for popular operating systems:
|
||||||
|
|
||||||
|
If you are seeing this message on Linux you probably need to
|
||||||
|
install GCC and/or the Python development package for your
|
||||||
|
version of Python.
|
||||||
|
|
||||||
|
Debian and Ubuntu users should issue the following command:
|
||||||
|
|
||||||
|
$ sudo apt-get install build-essential python-dev
|
||||||
|
|
||||||
|
Users of Red Hat based distributions (RHEL, CentOS, Amazon Linux,
|
||||||
|
Oracle Linux, Fedora, etc.) should issue the following command:
|
||||||
|
|
||||||
|
$ sudo yum install gcc python-devel
|
||||||
|
|
||||||
|
If you are seeing this message on Microsoft Windows please install
|
||||||
|
PyMongo using pip. Modern versions of pip will install PyMongo
|
||||||
|
from binary wheels available on pypi. If you must install from
|
||||||
|
source read the documentation here:
|
||||||
|
|
||||||
|
https://pymongo.readthedocs.io/en/stable/installation.html#installing-from-source-on-windows
|
||||||
|
|
||||||
|
If you are seeing this message on macOS / OSX please install PyMongo
|
||||||
|
using pip. Modern versions of pip will install PyMongo from binary
|
||||||
|
wheels available on pypi. If wheels are not available for your version
|
||||||
|
of macOS / OSX, or you must install from source read the documentation
|
||||||
|
here:
|
||||||
|
|
||||||
|
https://pymongo.readthedocs.io/en/stable/installation.html#osx
|
||||||
|
********************************************************************
|
||||||
|
"""
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
build_ext.run(self)
|
||||||
|
except Exception:
|
||||||
|
if os.environ.get("PYMONGO_C_EXT_MUST_BUILD"):
|
||||||
|
raise
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
sys.stdout.write("%s\n" % str(e))
|
||||||
|
warnings.warn(
|
||||||
|
self.warning_message
|
||||||
|
% (
|
||||||
|
"Extension modules",
|
||||||
|
"There was an issue with your platform configuration - see above.",
|
||||||
|
),
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
def build_extension(self, ext):
|
||||||
|
name = ext.name
|
||||||
|
try:
|
||||||
|
build_ext.build_extension(self, ext)
|
||||||
|
except Exception:
|
||||||
|
if os.environ.get("PYMONGO_C_EXT_MUST_BUILD"):
|
||||||
|
raise
|
||||||
|
e = sys.exc_info()[1]
|
||||||
|
sys.stdout.write("%s\n" % str(e))
|
||||||
|
warnings.warn(
|
||||||
|
self.warning_message
|
||||||
|
% (
|
||||||
|
"The %s extension module" % (name,), # noqa: UP031
|
||||||
|
"The output above this warning shows how the compilation failed.",
|
||||||
|
),
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ext_modules = [
|
||||||
|
Extension(
|
||||||
|
"bson._cbson",
|
||||||
|
include_dirs=["bson"],
|
||||||
|
sources=["bson/_cbsonmodule.c", "bson/time64.c", "bson/buffer.c"],
|
||||||
|
),
|
||||||
|
Extension(
|
||||||
|
"pymongo._cmessage",
|
||||||
|
include_dirs=["bson"],
|
||||||
|
sources=[
|
||||||
|
"pymongo/_cmessagemodule.c",
|
||||||
|
"bson/_cbsonmodule.c",
|
||||||
|
"bson/time64.c",
|
||||||
|
"bson/buffer.c",
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
if "--no_ext" in sys.argv or os.environ.get("NO_EXT"):
|
||||||
|
try:
|
||||||
|
sys.argv.remove("--no_ext")
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
ext_modules = []
|
||||||
|
elif sys.platform.startswith("java") or sys.platform == "cli" or "PyPy" in sys.version:
|
||||||
|
sys.stdout.write(
|
||||||
|
"""
|
||||||
|
*****************************************************\n
|
||||||
|
The optional C extensions are currently not supported\n
|
||||||
|
by this python implementation.\n
|
||||||
|
*****************************************************\n
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
ext_modules = []
|
||||||
|
|
||||||
|
setup(
|
||||||
|
cmdclass={"build_ext": custom_build_ext},
|
||||||
|
ext_modules=ext_modules,
|
||||||
|
packages=["bson", "pymongo", "gridfs"],
|
||||||
|
) # type:ignore
|
||||||
@ -4,12 +4,35 @@ Changelog
|
|||||||
Changes in Version 4.8.0
|
Changes in Version 4.8.0
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
The handshake metadata for "os.name" on Windows has been simplified to "Windows" to improve import time.
|
|
||||||
|
|
||||||
The repr of ``bson.binary.Binary`` is now redacted when the subtype is SENSITIVE_SUBTYPE(8).
|
|
||||||
|
|
||||||
.. warning:: PyMongo 4.8 drops support for Python 3.7 and PyPy 3.8: Python 3.8+ or PyPy 3.9+ is now required.
|
.. warning:: PyMongo 4.8 drops support for Python 3.7 and PyPy 3.8: Python 3.8+ or PyPy 3.9+ is now required.
|
||||||
|
|
||||||
|
PyMongo 4.8 brings a number of improvements including:
|
||||||
|
|
||||||
|
- The handshake metadata for "os.name" on Windows has been simplified to "Windows" to improve import time.
|
||||||
|
- The repr of ``bson.binary.Binary`` is now redacted when the subtype is SENSITIVE_SUBTYPE(8).
|
||||||
|
- Secure Software Development Life Cycle automation for release process.
|
||||||
|
GitHub Releases now include a Software Bill of Materials, and signature
|
||||||
|
files corresponding to the distribution files released on PyPI.
|
||||||
|
- Fixed a bug in change streams where both ``startAtOperationTime`` and ``resumeToken``
|
||||||
|
could be added to a retry attempt, which caused the retry to fail.
|
||||||
|
- Fallback to stdlib ``ssl`` module when ``pyopenssl`` import fails with AttributeError.
|
||||||
|
- Improved performance of MongoClient operations, especially when many operations are being run concurrently.
|
||||||
|
|
||||||
|
Unavoidable breaking changes
|
||||||
|
............................
|
||||||
|
|
||||||
|
- Since we are now using ``hatch`` as our build backend, we no longer have a usable ``setup.py`` file
|
||||||
|
and require installation using ``pip``. Attempts to invoke the ``setup.py`` file will raise an exception.
|
||||||
|
Additionally, ``pip`` >= 21.3 is now required for editable installs.
|
||||||
|
|
||||||
|
Issues Resolved
|
||||||
|
...............
|
||||||
|
|
||||||
|
See the `PyMongo 4.8 release notes in JIRA`_ for the list of resolved issues
|
||||||
|
in this release.
|
||||||
|
|
||||||
|
.. _PyMongo 4.8 release notes in JIRA: https://jira.mongodb.org/secure/ReleaseNote.jspa?projectId=10004&version=37057
|
||||||
|
|
||||||
Changes in Version 4.7.3
|
Changes in Version 4.7.3
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
|
|||||||
36
hatch_build.py
Normal file
36
hatch_build.py
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
"""A custom hatch build hook for pymongo."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from hatchling.builders.hooks.plugin.interface import BuildHookInterface
|
||||||
|
|
||||||
|
|
||||||
|
class CustomHook(BuildHookInterface):
|
||||||
|
"""The pymongo build hook."""
|
||||||
|
|
||||||
|
def initialize(self, version, build_data):
|
||||||
|
"""Initialize the hook."""
|
||||||
|
if self.target_name == "sdist":
|
||||||
|
return
|
||||||
|
here = Path(__file__).parent.resolve()
|
||||||
|
sys.path.insert(0, str(here))
|
||||||
|
|
||||||
|
subprocess.check_call([sys.executable, "_setup.py", "build_ext", "-i"])
|
||||||
|
|
||||||
|
# Ensure wheel is marked as binary and contains the binary files.
|
||||||
|
build_data["infer_tag"] = True
|
||||||
|
build_data["pure_python"] = False
|
||||||
|
if os.name == "nt":
|
||||||
|
patt = ".pyd"
|
||||||
|
else:
|
||||||
|
patt = ".so"
|
||||||
|
for pkg in ["bson", "pymongo"]:
|
||||||
|
dpath = here / pkg
|
||||||
|
for fpath in dpath.glob(f"*{patt}"):
|
||||||
|
relpath = os.path.relpath(fpath, here)
|
||||||
|
build_data["artifacts"].append(relpath)
|
||||||
|
build_data["force_include"][relpath] = relpath
|
||||||
@ -15,16 +15,29 @@
|
|||||||
"""Current version of PyMongo."""
|
"""Current version of PyMongo."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Tuple, Union
|
import re
|
||||||
|
from typing import List, Tuple, Union
|
||||||
|
|
||||||
version_tuple: Tuple[Union[int, str], ...] = (4, 8, 0, ".dev0")
|
__version__ = "4.8.0"
|
||||||
|
|
||||||
|
|
||||||
|
def get_version_tuple(version: str) -> Tuple[Union[int, str], ...]:
|
||||||
|
pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)"
|
||||||
|
match = re.match(pattern, version)
|
||||||
|
if match:
|
||||||
|
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):
|
||||||
|
parts = [int(part) for part in version.split(".")]
|
||||||
|
else:
|
||||||
|
raise ValueError("Could not parse version")
|
||||||
|
return tuple(parts)
|
||||||
|
|
||||||
|
|
||||||
|
version_tuple = get_version_tuple(__version__)
|
||||||
|
version = __version__
|
||||||
|
|
||||||
|
|
||||||
def get_version_string() -> str:
|
def get_version_string() -> str:
|
||||||
if isinstance(version_tuple[-1], str):
|
return __version__
|
||||||
return ".".join(map(str, version_tuple[:-1])) + version_tuple[-1]
|
|
||||||
return ".".join(map(str, version_tuple))
|
|
||||||
|
|
||||||
|
|
||||||
__version__: str = get_version_string()
|
|
||||||
version = __version__
|
|
||||||
|
|||||||
@ -515,9 +515,6 @@ class ClientSession:
|
|||||||
|
|
||||||
It is an error to use the session after the session has ended.
|
It is an error to use the session after the session has ended.
|
||||||
"""
|
"""
|
||||||
self._end_session(lock=True)
|
|
||||||
|
|
||||||
def _end_session(self, lock: bool) -> None:
|
|
||||||
if self._server_session is not None:
|
if self._server_session is not None:
|
||||||
try:
|
try:
|
||||||
if self.in_transaction:
|
if self.in_transaction:
|
||||||
@ -526,7 +523,7 @@ class ClientSession:
|
|||||||
# is in the committed state when the session is discarded.
|
# is in the committed state when the session is discarded.
|
||||||
self._unpin()
|
self._unpin()
|
||||||
finally:
|
finally:
|
||||||
self._client._return_server_session(self._server_session, lock)
|
self._client._return_server_session(self._server_session)
|
||||||
self._server_session = None
|
self._server_session = None
|
||||||
|
|
||||||
def _check_ended(self) -> None:
|
def _check_ended(self) -> None:
|
||||||
@ -537,7 +534,7 @@ class ClientSession:
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
||||||
self._end_session(lock=True)
|
self.end_session()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def client(self) -> MongoClient:
|
def client(self) -> MongoClient:
|
||||||
@ -1097,7 +1094,7 @@ class _ServerSession:
|
|||||||
class _ServerSessionPool(collections.deque):
|
class _ServerSessionPool(collections.deque):
|
||||||
"""Pool of _ServerSession objects.
|
"""Pool of _ServerSession objects.
|
||||||
|
|
||||||
This class is not thread-safe, access it while holding the Topology lock.
|
This class is thread-safe.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args: Any, **kwargs: Any):
|
def __init__(self, *args: Any, **kwargs: Any):
|
||||||
@ -1110,8 +1107,11 @@ class _ServerSessionPool(collections.deque):
|
|||||||
|
|
||||||
def pop_all(self) -> list[_ServerSession]:
|
def pop_all(self) -> list[_ServerSession]:
|
||||||
ids = []
|
ids = []
|
||||||
while self:
|
while True:
|
||||||
ids.append(self.pop().session_id)
|
try:
|
||||||
|
ids.append(self.pop().session_id)
|
||||||
|
except IndexError:
|
||||||
|
break
|
||||||
return ids
|
return ids
|
||||||
|
|
||||||
def get_server_session(self, session_timeout_minutes: Optional[int]) -> _ServerSession:
|
def get_server_session(self, session_timeout_minutes: Optional[int]) -> _ServerSession:
|
||||||
@ -1123,23 +1123,17 @@ class _ServerSessionPool(collections.deque):
|
|||||||
self._clear_stale(session_timeout_minutes)
|
self._clear_stale(session_timeout_minutes)
|
||||||
|
|
||||||
# The most recently used sessions are on the left.
|
# The most recently used sessions are on the left.
|
||||||
while self:
|
while True:
|
||||||
s = self.popleft()
|
try:
|
||||||
|
s = self.popleft()
|
||||||
|
except IndexError:
|
||||||
|
break
|
||||||
if not s.timed_out(session_timeout_minutes):
|
if not s.timed_out(session_timeout_minutes):
|
||||||
return s
|
return s
|
||||||
|
|
||||||
return _ServerSession(self.generation)
|
return _ServerSession(self.generation)
|
||||||
|
|
||||||
def return_server_session(
|
def return_server_session(self, server_session: _ServerSession) -> None:
|
||||||
self, server_session: _ServerSession, session_timeout_minutes: Optional[int]
|
|
||||||
) -> None:
|
|
||||||
if session_timeout_minutes is not None:
|
|
||||||
self._clear_stale(session_timeout_minutes)
|
|
||||||
if server_session.timed_out(session_timeout_minutes):
|
|
||||||
return
|
|
||||||
self.return_server_session_no_lock(server_session)
|
|
||||||
|
|
||||||
def return_server_session_no_lock(self, server_session: _ServerSession) -> None:
|
|
||||||
# Discard sessions from an old pool to avoid duplicate sessions in the
|
# Discard sessions from an old pool to avoid duplicate sessions in the
|
||||||
# child process after a fork.
|
# child process after a fork.
|
||||||
if server_session.generation == self.generation and not server_session.dirty:
|
if server_session.generation == self.generation and not server_session.dirty:
|
||||||
@ -1147,9 +1141,12 @@ class _ServerSessionPool(collections.deque):
|
|||||||
|
|
||||||
def _clear_stale(self, session_timeout_minutes: Optional[int]) -> None:
|
def _clear_stale(self, session_timeout_minutes: Optional[int]) -> None:
|
||||||
# Clear stale sessions. The least recently used are on the right.
|
# Clear stale sessions. The least recently used are on the right.
|
||||||
while self:
|
while True:
|
||||||
if self[-1].timed_out(session_timeout_minutes):
|
try:
|
||||||
self.pop()
|
s = self.pop()
|
||||||
else:
|
except IndexError:
|
||||||
|
break
|
||||||
|
if not s.timed_out(session_timeout_minutes):
|
||||||
|
self.append(s)
|
||||||
# The remaining sessions also haven't timed out.
|
# The remaining sessions also haven't timed out.
|
||||||
break
|
break
|
||||||
|
|||||||
@ -73,7 +73,7 @@ class CommandCursor(Generic[_DocumentType]):
|
|||||||
self.__killed = self.__id == 0
|
self.__killed = self.__id == 0
|
||||||
self.__comment = comment
|
self.__comment = comment
|
||||||
if self.__killed:
|
if self.__killed:
|
||||||
self.__end_session(True)
|
self.__end_session()
|
||||||
|
|
||||||
if "ns" in cursor_info: # noqa: SIM401
|
if "ns" in cursor_info: # noqa: SIM401
|
||||||
self.__ns = cursor_info["ns"]
|
self.__ns = cursor_info["ns"]
|
||||||
@ -112,9 +112,9 @@ class CommandCursor(Generic[_DocumentType]):
|
|||||||
self.__session = None
|
self.__session = None
|
||||||
self.__sock_mgr = None
|
self.__sock_mgr = None
|
||||||
|
|
||||||
def __end_session(self, synchronous: bool) -> None:
|
def __end_session(self) -> None:
|
||||||
if self.__session and not self.__explicit_session:
|
if self.__session and not self.__explicit_session:
|
||||||
self.__session._end_session(lock=synchronous)
|
self.__session.end_session()
|
||||||
self.__session = None
|
self.__session = None
|
||||||
|
|
||||||
def close(self) -> None:
|
def close(self) -> None:
|
||||||
|
|||||||
@ -862,6 +862,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
|
|||||||
server_monitoring_mode=options.server_monitoring_mode,
|
server_monitoring_mode=options.server_monitoring_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._opened = False
|
||||||
self._init_background()
|
self._init_background()
|
||||||
|
|
||||||
if connect:
|
if connect:
|
||||||
@ -903,10 +904,13 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
|
|||||||
# this closure. When the client is freed, stop the executor soon.
|
# this closure. When the client is freed, stop the executor soon.
|
||||||
self_ref: Any = weakref.ref(self, executor.close)
|
self_ref: Any = weakref.ref(self, executor.close)
|
||||||
self._kill_cursors_executor = executor
|
self._kill_cursors_executor = executor
|
||||||
|
self._opened = False
|
||||||
|
|
||||||
def _after_fork(self) -> None:
|
def _after_fork(self) -> None:
|
||||||
"""Resets topology in a child after successfully forking."""
|
"""Resets topology in a child after successfully forking."""
|
||||||
self._init_background(self._topology._pid)
|
self._init_background(self._topology._pid)
|
||||||
|
# Reset the session pool to avoid duplicate sessions in the child process.
|
||||||
|
self._topology._session_pool.reset()
|
||||||
|
|
||||||
def _duplicate(self, **kwargs: Any) -> MongoClient:
|
def _duplicate(self, **kwargs: Any) -> MongoClient:
|
||||||
args = self.__init_kwargs.copy()
|
args = self.__init_kwargs.copy()
|
||||||
@ -1243,9 +1247,11 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
|
|||||||
If this client was created with "connect=False", calling _get_topology
|
If this client was created with "connect=False", calling _get_topology
|
||||||
launches the connection process in the background.
|
launches the connection process in the background.
|
||||||
"""
|
"""
|
||||||
self._topology.open()
|
if not self._opened:
|
||||||
with self.__lock:
|
self._topology.open()
|
||||||
self._kill_cursors_executor.open()
|
with self.__lock:
|
||||||
|
self._kill_cursors_executor.open()
|
||||||
|
self._opened = True
|
||||||
return self._topology
|
return self._topology
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
@ -1679,7 +1685,7 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
|
|||||||
if cursor_id or conn_mgr:
|
if cursor_id or conn_mgr:
|
||||||
self._close_cursor_soon(cursor_id, address, conn_mgr)
|
self._close_cursor_soon(cursor_id, address, conn_mgr)
|
||||||
if session and not explicit_session:
|
if session and not explicit_session:
|
||||||
session._end_session(lock=locks_allowed)
|
session.end_session()
|
||||||
|
|
||||||
def _close_cursor_soon(
|
def _close_cursor_soon(
|
||||||
self,
|
self,
|
||||||
@ -1838,12 +1844,12 @@ class MongoClient(common.BaseObject, Generic[_DocumentType]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _return_server_session(
|
def _return_server_session(
|
||||||
self, server_session: Union[_ServerSession, _EmptyServerSession], lock: bool
|
self, server_session: Union[_ServerSession, _EmptyServerSession]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Internal: return a _ServerSession to the pool."""
|
"""Internal: return a _ServerSession to the pool."""
|
||||||
if isinstance(server_session, _EmptyServerSession):
|
if isinstance(server_session, _EmptyServerSession):
|
||||||
return None
|
return None
|
||||||
return self._topology.return_server_session(server_session, lock)
|
return self._topology.return_server_session(server_session)
|
||||||
|
|
||||||
def _ensure_session(self, session: Optional[ClientSession] = None) -> Optional[ClientSession]:
|
def _ensure_session(self, session: Optional[ClientSession] = None) -> Optional[ClientSession]:
|
||||||
"""If provided session is None, lend a temporary session."""
|
"""If provided session is None, lend a temporary session."""
|
||||||
|
|||||||
292
pymongo/pool.py
292
pymongo/pool.py
@ -734,6 +734,7 @@ class Connection:
|
|||||||
self.op_msg_enabled = False
|
self.op_msg_enabled = False
|
||||||
self.listeners = pool.opts._event_listeners
|
self.listeners = pool.opts._event_listeners
|
||||||
self.enabled_for_cmap = pool.enabled_for_cmap
|
self.enabled_for_cmap = pool.enabled_for_cmap
|
||||||
|
self.enabled_for_logging = pool.enabled_for_logging
|
||||||
self.compression_settings = pool.opts._compression_settings
|
self.compression_settings = pool.opts._compression_settings
|
||||||
self.compression_context: Union[SnappyContext, ZlibContext, ZstdContext, None] = None
|
self.compression_context: Union[SnappyContext, ZlibContext, ZstdContext, None] = None
|
||||||
self.socket_checker: SocketChecker = SocketChecker()
|
self.socket_checker: SocketChecker = SocketChecker()
|
||||||
@ -1097,20 +1098,20 @@ class Connection:
|
|||||||
|
|
||||||
auth.authenticate(creds, self, reauthenticate=reauthenticate)
|
auth.authenticate(creds, self, reauthenticate=reauthenticate)
|
||||||
self.ready = True
|
self.ready = True
|
||||||
|
duration = time.monotonic() - self.creation_time
|
||||||
if self.enabled_for_cmap:
|
if self.enabled_for_cmap:
|
||||||
assert self.listeners is not None
|
assert self.listeners is not None
|
||||||
duration = time.monotonic() - self.creation_time
|
|
||||||
self.listeners.publish_connection_ready(self.address, self.id, duration)
|
self.listeners.publish_connection_ready(self.address, self.id, duration)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.CONN_READY,
|
message=_ConnectionStatusMessage.CONN_READY,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
driverConnectionId=self.id,
|
driverConnectionId=self.id,
|
||||||
durationMS=duration,
|
durationMS=duration,
|
||||||
)
|
)
|
||||||
|
|
||||||
def validate_session(
|
def validate_session(
|
||||||
self, client: Optional[MongoClient], session: Optional[ClientSession]
|
self, client: Optional[MongoClient], session: Optional[ClientSession]
|
||||||
@ -1128,10 +1129,11 @@ class Connection:
|
|||||||
if self.closed:
|
if self.closed:
|
||||||
return
|
return
|
||||||
self._close_conn()
|
self._close_conn()
|
||||||
if reason and self.enabled_for_cmap:
|
if reason:
|
||||||
assert self.listeners is not None
|
if self.enabled_for_cmap:
|
||||||
self.listeners.publish_connection_closed(self.address, self.id, reason)
|
assert self.listeners is not None
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
self.listeners.publish_connection_closed(self.address, self.id, reason)
|
||||||
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
@ -1441,6 +1443,7 @@ class Pool:
|
|||||||
and self.opts._event_listeners is not None
|
and self.opts._event_listeners is not None
|
||||||
and self.opts._event_listeners.enabled_for_cmap
|
and self.opts._event_listeners.enabled_for_cmap
|
||||||
)
|
)
|
||||||
|
self.enabled_for_logging = self.handshake
|
||||||
|
|
||||||
# The first portion of the wait queue.
|
# The first portion of the wait queue.
|
||||||
# Enforces: maxPoolSize
|
# Enforces: maxPoolSize
|
||||||
@ -1462,15 +1465,15 @@ class Pool:
|
|||||||
self.opts._event_listeners.publish_pool_created(
|
self.opts._event_listeners.publish_pool_created(
|
||||||
self.address, self.opts.non_default_options
|
self.address, self.opts.non_default_options
|
||||||
)
|
)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.POOL_CREATED,
|
message=_ConnectionStatusMessage.POOL_CREATED,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
**self.opts.non_default_options,
|
**self.opts.non_default_options,
|
||||||
)
|
)
|
||||||
# Similar to active_sockets but includes threads in the wait queue.
|
# Similar to active_sockets but includes threads in the wait queue.
|
||||||
self.operation_count: int = 0
|
self.operation_count: int = 0
|
||||||
# Retain references to pinned connections to prevent the CPython GC
|
# Retain references to pinned connections to prevent the CPython GC
|
||||||
@ -1488,14 +1491,14 @@ class Pool:
|
|||||||
if self.enabled_for_cmap:
|
if self.enabled_for_cmap:
|
||||||
assert self.opts._event_listeners is not None
|
assert self.opts._event_listeners is not None
|
||||||
self.opts._event_listeners.publish_pool_ready(self.address)
|
self.opts._event_listeners.publish_pool_ready(self.address)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.POOL_READY,
|
message=_ConnectionStatusMessage.POOL_READY,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def closed(self) -> bool:
|
def closed(self) -> bool:
|
||||||
@ -1553,23 +1556,24 @@ class Pool:
|
|||||||
if self.enabled_for_cmap:
|
if self.enabled_for_cmap:
|
||||||
assert listeners is not None
|
assert listeners is not None
|
||||||
listeners.publish_pool_closed(self.address)
|
listeners.publish_pool_closed(self.address)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.POOL_CLOSED,
|
message=_ConnectionStatusMessage.POOL_CLOSED,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
)
|
|
||||||
else:
|
|
||||||
if old_state != PoolState.PAUSED and self.enabled_for_cmap:
|
|
||||||
assert listeners is not None
|
|
||||||
listeners.publish_pool_cleared(
|
|
||||||
self.address,
|
|
||||||
service_id=service_id,
|
|
||||||
interrupt_connections=interrupt_connections,
|
|
||||||
)
|
)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
else:
|
||||||
|
if old_state != PoolState.PAUSED:
|
||||||
|
if self.enabled_for_cmap:
|
||||||
|
assert listeners is not None
|
||||||
|
listeners.publish_pool_cleared(
|
||||||
|
self.address,
|
||||||
|
service_id=service_id,
|
||||||
|
interrupt_connections=interrupt_connections,
|
||||||
|
)
|
||||||
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
@ -1677,15 +1681,15 @@ class Pool:
|
|||||||
if self.enabled_for_cmap:
|
if self.enabled_for_cmap:
|
||||||
assert listeners is not None
|
assert listeners is not None
|
||||||
listeners.publish_connection_created(self.address, conn_id)
|
listeners.publish_connection_created(self.address, conn_id)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.CONN_CREATED,
|
message=_ConnectionStatusMessage.CONN_CREATED,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
driverConnectionId=conn_id,
|
driverConnectionId=conn_id,
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
sock = _configured_socket(self.address, self.opts)
|
sock = _configured_socket(self.address, self.opts)
|
||||||
@ -1695,17 +1699,17 @@ class Pool:
|
|||||||
listeners.publish_connection_closed(
|
listeners.publish_connection_closed(
|
||||||
self.address, conn_id, ConnectionClosedReason.ERROR
|
self.address, conn_id, ConnectionClosedReason.ERROR
|
||||||
)
|
)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.CONN_CLOSED,
|
message=_ConnectionStatusMessage.CONN_CLOSED,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
driverConnectionId=conn_id,
|
driverConnectionId=conn_id,
|
||||||
reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR),
|
reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR),
|
||||||
error=ConnectionClosedReason.ERROR,
|
error=ConnectionClosedReason.ERROR,
|
||||||
)
|
)
|
||||||
if isinstance(error, (IOError, OSError, SSLError)):
|
if isinstance(error, (IOError, OSError, SSLError)):
|
||||||
details = _get_timeout_details(self.opts)
|
details = _get_timeout_details(self.opts)
|
||||||
_raise_connection_failure(self.address, error, timeout_details=details)
|
_raise_connection_failure(self.address, error, timeout_details=details)
|
||||||
@ -1751,31 +1755,31 @@ class Pool:
|
|||||||
if self.enabled_for_cmap:
|
if self.enabled_for_cmap:
|
||||||
assert listeners is not None
|
assert listeners is not None
|
||||||
listeners.publish_connection_check_out_started(self.address)
|
listeners.publish_connection_check_out_started(self.address)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.CHECKOUT_STARTED,
|
message=_ConnectionStatusMessage.CHECKOUT_STARTED,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
)
|
)
|
||||||
|
|
||||||
conn = self._get_conn(checkout_started_time, handler=handler)
|
conn = self._get_conn(checkout_started_time, handler=handler)
|
||||||
|
|
||||||
|
duration = time.monotonic() - checkout_started_time
|
||||||
if self.enabled_for_cmap:
|
if self.enabled_for_cmap:
|
||||||
assert listeners is not None
|
assert listeners is not None
|
||||||
duration = time.monotonic() - checkout_started_time
|
|
||||||
listeners.publish_connection_checked_out(self.address, conn.id, duration)
|
listeners.publish_connection_checked_out(self.address, conn.id, duration)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.CHECKOUT_SUCCEEDED,
|
message=_ConnectionStatusMessage.CHECKOUT_SUCCEEDED,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
driverConnectionId=conn.id,
|
driverConnectionId=conn.id,
|
||||||
durationMS=duration,
|
durationMS=duration,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.active_contexts.add(conn.cancel_context)
|
self.active_contexts.add(conn.cancel_context)
|
||||||
@ -1807,13 +1811,14 @@ class Pool:
|
|||||||
|
|
||||||
def _raise_if_not_ready(self, checkout_started_time: float, emit_event: bool) -> None:
|
def _raise_if_not_ready(self, checkout_started_time: float, emit_event: bool) -> None:
|
||||||
if self.state != PoolState.READY:
|
if self.state != PoolState.READY:
|
||||||
if self.enabled_for_cmap and emit_event:
|
if emit_event:
|
||||||
assert self.opts._event_listeners is not None
|
|
||||||
duration = time.monotonic() - checkout_started_time
|
duration = time.monotonic() - checkout_started_time
|
||||||
self.opts._event_listeners.publish_connection_check_out_failed(
|
if self.enabled_for_cmap:
|
||||||
self.address, ConnectionCheckOutFailedReason.CONN_ERROR, duration
|
assert self.opts._event_listeners is not None
|
||||||
)
|
self.opts._event_listeners.publish_connection_check_out_failed(
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
self.address, ConnectionCheckOutFailedReason.CONN_ERROR, duration
|
||||||
|
)
|
||||||
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
@ -1841,23 +1846,23 @@ class Pool:
|
|||||||
self.reset_without_pause()
|
self.reset_without_pause()
|
||||||
|
|
||||||
if self.closed:
|
if self.closed:
|
||||||
|
duration = time.monotonic() - checkout_started_time
|
||||||
if self.enabled_for_cmap:
|
if self.enabled_for_cmap:
|
||||||
assert self.opts._event_listeners is not None
|
assert self.opts._event_listeners is not None
|
||||||
duration = time.monotonic() - checkout_started_time
|
|
||||||
self.opts._event_listeners.publish_connection_check_out_failed(
|
self.opts._event_listeners.publish_connection_check_out_failed(
|
||||||
self.address, ConnectionCheckOutFailedReason.POOL_CLOSED, duration
|
self.address, ConnectionCheckOutFailedReason.POOL_CLOSED, duration
|
||||||
)
|
)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.CHECKOUT_FAILED,
|
message=_ConnectionStatusMessage.CHECKOUT_FAILED,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
reason="Connection pool was closed",
|
reason="Connection pool was closed",
|
||||||
error=ConnectionCheckOutFailedReason.POOL_CLOSED,
|
error=ConnectionCheckOutFailedReason.POOL_CLOSED,
|
||||||
durationMS=duration,
|
durationMS=duration,
|
||||||
)
|
)
|
||||||
raise _PoolClosedError(
|
raise _PoolClosedError(
|
||||||
"Attempted to check out a connection from closed connection pool"
|
"Attempted to check out a connection from closed connection pool"
|
||||||
)
|
)
|
||||||
@ -1933,13 +1938,14 @@ class Pool:
|
|||||||
self.active_sockets -= 1
|
self.active_sockets -= 1
|
||||||
self.size_cond.notify()
|
self.size_cond.notify()
|
||||||
|
|
||||||
if self.enabled_for_cmap and not emitted_event:
|
if not emitted_event:
|
||||||
assert self.opts._event_listeners is not None
|
|
||||||
duration = time.monotonic() - checkout_started_time
|
duration = time.monotonic() - checkout_started_time
|
||||||
self.opts._event_listeners.publish_connection_check_out_failed(
|
if self.enabled_for_cmap:
|
||||||
self.address, ConnectionCheckOutFailedReason.CONN_ERROR, duration
|
assert self.opts._event_listeners is not None
|
||||||
)
|
self.opts._event_listeners.publish_connection_check_out_failed(
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
self.address, ConnectionCheckOutFailedReason.CONN_ERROR, duration
|
||||||
|
)
|
||||||
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
@ -1972,15 +1978,15 @@ class Pool:
|
|||||||
if self.enabled_for_cmap:
|
if self.enabled_for_cmap:
|
||||||
assert listeners is not None
|
assert listeners is not None
|
||||||
listeners.publish_connection_checked_in(self.address, conn.id)
|
listeners.publish_connection_checked_in(self.address, conn.id)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.CHECKEDIN,
|
message=_ConnectionStatusMessage.CHECKEDIN,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
driverConnectionId=conn.id,
|
driverConnectionId=conn.id,
|
||||||
)
|
)
|
||||||
if self.pid != os.getpid():
|
if self.pid != os.getpid():
|
||||||
self.reset_without_pause()
|
self.reset_without_pause()
|
||||||
else:
|
else:
|
||||||
@ -1993,17 +1999,17 @@ class Pool:
|
|||||||
listeners.publish_connection_closed(
|
listeners.publish_connection_closed(
|
||||||
self.address, conn.id, ConnectionClosedReason.ERROR
|
self.address, conn.id, ConnectionClosedReason.ERROR
|
||||||
)
|
)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.CONN_CLOSED,
|
message=_ConnectionStatusMessage.CONN_CLOSED,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
driverConnectionId=conn.id,
|
driverConnectionId=conn.id,
|
||||||
reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR),
|
reason=_verbose_connection_error_reason(ConnectionClosedReason.ERROR),
|
||||||
error=ConnectionClosedReason.ERROR,
|
error=ConnectionClosedReason.ERROR,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
with self.lock:
|
with self.lock:
|
||||||
# Hold the lock to ensure this section does not race with
|
# Hold the lock to ensure this section does not race with
|
||||||
@ -2065,23 +2071,23 @@ class Pool:
|
|||||||
|
|
||||||
def _raise_wait_queue_timeout(self, checkout_started_time: float) -> NoReturn:
|
def _raise_wait_queue_timeout(self, checkout_started_time: float) -> NoReturn:
|
||||||
listeners = self.opts._event_listeners
|
listeners = self.opts._event_listeners
|
||||||
|
duration = time.monotonic() - checkout_started_time
|
||||||
if self.enabled_for_cmap:
|
if self.enabled_for_cmap:
|
||||||
assert listeners is not None
|
assert listeners is not None
|
||||||
duration = time.monotonic() - checkout_started_time
|
|
||||||
listeners.publish_connection_check_out_failed(
|
listeners.publish_connection_check_out_failed(
|
||||||
self.address, ConnectionCheckOutFailedReason.TIMEOUT, duration
|
self.address, ConnectionCheckOutFailedReason.TIMEOUT, duration
|
||||||
)
|
)
|
||||||
if _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
if self.enabled_for_logging and _CONNECTION_LOGGER.isEnabledFor(logging.DEBUG):
|
||||||
_debug_log(
|
_debug_log(
|
||||||
_CONNECTION_LOGGER,
|
_CONNECTION_LOGGER,
|
||||||
clientId=self._client_id,
|
clientId=self._client_id,
|
||||||
message=_ConnectionStatusMessage.CHECKOUT_FAILED,
|
message=_ConnectionStatusMessage.CHECKOUT_FAILED,
|
||||||
serverHost=self.address[0],
|
serverHost=self.address[0],
|
||||||
serverPort=self.address[1],
|
serverPort=self.address[1],
|
||||||
reason="Wait queue timeout elapsed without a connection becoming available",
|
reason="Wait queue timeout elapsed without a connection becoming available",
|
||||||
error=ConnectionCheckOutFailedReason.TIMEOUT,
|
error=ConnectionCheckOutFailedReason.TIMEOUT,
|
||||||
durationMS=duration,
|
durationMS=duration,
|
||||||
)
|
)
|
||||||
timeout = _csot.get_timeout() or self.opts.wait_queue_timeout
|
timeout = _csot.get_timeout() or self.opts.wait_queue_timeout
|
||||||
if self.opts.load_balanced:
|
if self.opts.load_balanced:
|
||||||
other_ops = self.active_sockets - self.ncursors - self.ntxns
|
other_ops = self.active_sockets - self.ncursors - self.ntxns
|
||||||
|
|||||||
@ -15,6 +15,7 @@
|
|||||||
"""Support for SSL in PyMongo."""
|
"""Support for SSL in PyMongo."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import warnings
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from pymongo.errors import ConfigurationError
|
from pymongo.errors import ConfigurationError
|
||||||
@ -23,7 +24,17 @@ HAVE_SSL = True
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
import pymongo.pyopenssl_context as _ssl
|
import pymongo.pyopenssl_context as _ssl
|
||||||
except ImportError:
|
except (ImportError, AttributeError) as exc:
|
||||||
|
if isinstance(exc, AttributeError):
|
||||||
|
warnings.warn(
|
||||||
|
"Failed to use the installed version of PyOpenSSL. "
|
||||||
|
"Falling back to stdlib ssl, disabling OCSP support. "
|
||||||
|
"This is likely caused by incompatible versions "
|
||||||
|
"of PyOpenSSL < 23.2.0 and cryptography >= 42.0.0. "
|
||||||
|
"Try updating PyOpenSSL >= 23.2.0 to enable OCSP.",
|
||||||
|
UserWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
import pymongo.ssl_context as _ssl # type: ignore[no-redef]
|
import pymongo.ssl_context as _ssl # type: ignore[no-redef]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|||||||
@ -669,23 +669,14 @@ class Topology:
|
|||||||
|
|
||||||
def pop_all_sessions(self) -> list[_ServerSession]:
|
def pop_all_sessions(self) -> list[_ServerSession]:
|
||||||
"""Pop all session ids from the pool."""
|
"""Pop all session ids from the pool."""
|
||||||
with self._lock:
|
return self._session_pool.pop_all()
|
||||||
return self._session_pool.pop_all()
|
|
||||||
|
|
||||||
def get_server_session(self, session_timeout_minutes: Optional[int]) -> _ServerSession:
|
def get_server_session(self, session_timeout_minutes: Optional[int]) -> _ServerSession:
|
||||||
"""Start or resume a server session, or raise ConfigurationError."""
|
"""Start or resume a server session, or raise ConfigurationError."""
|
||||||
with self._lock:
|
return self._session_pool.get_server_session(session_timeout_minutes)
|
||||||
return self._session_pool.get_server_session(session_timeout_minutes)
|
|
||||||
|
|
||||||
def return_server_session(self, server_session: _ServerSession, lock: bool) -> None:
|
def return_server_session(self, server_session: _ServerSession) -> None:
|
||||||
if lock:
|
self._session_pool.return_server_session(server_session)
|
||||||
with self._lock:
|
|
||||||
self._session_pool.return_server_session(
|
|
||||||
server_session, self._description.logical_session_timeout_minutes
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Called from a __del__ method, can't use a lock.
|
|
||||||
self._session_pool.return_server_session_no_lock(server_session)
|
|
||||||
|
|
||||||
def _new_selection(self) -> Selection:
|
def _new_selection(self) -> Selection:
|
||||||
"""A Selection object, initially including all known servers.
|
"""A Selection object, initially including all known servers.
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=63.0"]
|
requires = ["hatchling>1.24","setuptools>=65.0","hatch-requirements-txt>=0.4.1"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "pymongo"
|
name = "pymongo"
|
||||||
@ -45,16 +45,28 @@ Documentation = "https://pymongo.readthedocs.io"
|
|||||||
Source = "https://github.com/mongodb/mongo-python-driver"
|
Source = "https://github.com/mongodb/mongo-python-driver"
|
||||||
Tracker = "https://jira.mongodb.org/projects/PYTHON/issues"
|
Tracker = "https://jira.mongodb.org/projects/PYTHON/issues"
|
||||||
|
|
||||||
[tool.setuptools.dynamic]
|
# Used to call hatch_build.py
|
||||||
version = {attr = "pymongo._version.__version__"}
|
[tool.hatch.build.hooks.custom]
|
||||||
|
|
||||||
[tool.setuptools.packages.find]
|
[tool.hatch.version]
|
||||||
include = ["bson","gridfs", "pymongo"]
|
path = "pymongo/_version.py"
|
||||||
|
validate-bump = false
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.hatch.build.targets.wheel]
|
||||||
bson=["py.typed", "*.pyi"]
|
packages = ["bson","gridfs", "pymongo"]
|
||||||
pymongo=["py.typed", "*.pyi"]
|
|
||||||
gridfs=["py.typed", "*.pyi"]
|
[tool.hatch.metadata.hooks.requirements_txt]
|
||||||
|
files = ["requirements.txt"]
|
||||||
|
|
||||||
|
[tool.hatch.metadata.hooks.requirements_txt.optional-dependencies]
|
||||||
|
aws = ["requirements/aws.txt"]
|
||||||
|
docs = ["requirements/docs.txt"]
|
||||||
|
encryption = ["requirements/encryption.txt"]
|
||||||
|
gssapi = ["requirements/gssapi.txt"]
|
||||||
|
ocsp = ["requirements/ocsp.txt"]
|
||||||
|
snappy = ["requirements/snappy.txt"]
|
||||||
|
test = ["requirements/test.txt"]
|
||||||
|
zstd = ["requirements/zstd.txt"]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
minversion = "7"
|
minversion = "7"
|
||||||
@ -168,6 +180,7 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?)|dummy.*)$"
|
|||||||
"UP031", "F401", "B023", "F811"]
|
"UP031", "F401", "B023", "F811"]
|
||||||
"tools/*.py" = ["T201"]
|
"tools/*.py" = ["T201"]
|
||||||
"green_framework_test.py" = ["T201"]
|
"green_framework_test.py" = ["T201"]
|
||||||
|
"hatch_build.py" = ["S"]
|
||||||
|
|
||||||
[tool.coverage.run]
|
[tool.coverage.run]
|
||||||
branch = true
|
branch = true
|
||||||
|
|||||||
19
sbom.json
19
sbom.json
@ -1,10 +1,11 @@
|
|||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"timestamp": "2024-05-02T17:36:12.698229+00:00"
|
"timestamp": "2024-06-10T18:55:17.710940+00:00",
|
||||||
},
|
},
|
||||||
"serialNumber": "urn:uuid:9876a8a6-060e-486f-b128-910aecf0fe7b",
|
"components": [],
|
||||||
"version": 1,
|
"serialNumber": "urn:uuid:a6c08d96-55e1-4cdb-945c-0e21ced83e34",
|
||||||
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
"version": 1,
|
||||||
"bomFormat": "CycloneDX",
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
||||||
"specVersion": "1.5"
|
"bomFormat": "CycloneDX",
|
||||||
}
|
"specVersion": "1.5"
|
||||||
|
}
|
||||||
|
|||||||
167
setup.py
167
setup.py
@ -1,167 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
msg = (
|
||||||
import sys
|
"PyMongo>=4.8 no longer supports building via setup.py, use python -m pip install <path/to/pymongo> instead. If "
|
||||||
import warnings
|
"this is an editable install (-e) please upgrade to pip>=21.3 first: python -m pip install --upgrade pip"
|
||||||
|
|
||||||
# Hack to silence atexit traceback in some Python versions
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa: F401
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
from setuptools import setup
|
|
||||||
from setuptools.command.build_ext import build_ext
|
|
||||||
from setuptools.extension import Extension
|
|
||||||
|
|
||||||
|
|
||||||
class custom_build_ext(build_ext):
|
|
||||||
"""Allow C extension building to fail.
|
|
||||||
|
|
||||||
The C extension speeds up BSON encoding, but is not essential.
|
|
||||||
"""
|
|
||||||
|
|
||||||
warning_message = """
|
|
||||||
********************************************************************
|
|
||||||
WARNING: %s could not
|
|
||||||
be compiled. No C extensions are essential for PyMongo to run,
|
|
||||||
although they do result in significant speed improvements.
|
|
||||||
%s
|
|
||||||
|
|
||||||
Please see the installation docs for solutions to build issues:
|
|
||||||
|
|
||||||
https://pymongo.readthedocs.io/en/stable/installation.html
|
|
||||||
|
|
||||||
Here are some hints for popular operating systems:
|
|
||||||
|
|
||||||
If you are seeing this message on Linux you probably need to
|
|
||||||
install GCC and/or the Python development package for your
|
|
||||||
version of Python.
|
|
||||||
|
|
||||||
Debian and Ubuntu users should issue the following command:
|
|
||||||
|
|
||||||
$ sudo apt-get install build-essential python-dev
|
|
||||||
|
|
||||||
Users of Red Hat based distributions (RHEL, CentOS, Amazon Linux,
|
|
||||||
Oracle Linux, Fedora, etc.) should issue the following command:
|
|
||||||
|
|
||||||
$ sudo yum install gcc python-devel
|
|
||||||
|
|
||||||
If you are seeing this message on Microsoft Windows please install
|
|
||||||
PyMongo using pip. Modern versions of pip will install PyMongo
|
|
||||||
from binary wheels available on pypi. If you must install from
|
|
||||||
source read the documentation here:
|
|
||||||
|
|
||||||
https://pymongo.readthedocs.io/en/stable/installation.html#installing-from-source-on-windows
|
|
||||||
|
|
||||||
If you are seeing this message on macOS / OSX please install PyMongo
|
|
||||||
using pip. Modern versions of pip will install PyMongo from binary
|
|
||||||
wheels available on pypi. If wheels are not available for your version
|
|
||||||
of macOS / OSX, or you must install from source read the documentation
|
|
||||||
here:
|
|
||||||
|
|
||||||
https://pymongo.readthedocs.io/en/stable/installation.html#osx
|
|
||||||
********************************************************************
|
|
||||||
"""
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
try:
|
|
||||||
build_ext.run(self)
|
|
||||||
except Exception:
|
|
||||||
if os.environ.get("PYMONGO_C_EXT_MUST_BUILD"):
|
|
||||||
raise
|
|
||||||
e = sys.exc_info()[1]
|
|
||||||
sys.stdout.write("%s\n" % str(e))
|
|
||||||
warnings.warn(
|
|
||||||
self.warning_message
|
|
||||||
% (
|
|
||||||
"Extension modules",
|
|
||||||
"There was an issue with your platform configuration - see above.",
|
|
||||||
),
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
def build_extension(self, ext):
|
|
||||||
name = ext.name
|
|
||||||
try:
|
|
||||||
build_ext.build_extension(self, ext)
|
|
||||||
except Exception:
|
|
||||||
if os.environ.get("PYMONGO_C_EXT_MUST_BUILD"):
|
|
||||||
raise
|
|
||||||
e = sys.exc_info()[1]
|
|
||||||
sys.stdout.write("%s\n" % str(e))
|
|
||||||
warnings.warn(
|
|
||||||
self.warning_message
|
|
||||||
% (
|
|
||||||
"The %s extension module" % (name,), # noqa: UP031
|
|
||||||
"The output above this warning shows how the compilation failed.",
|
|
||||||
),
|
|
||||||
stacklevel=2,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ext_modules = [
|
|
||||||
Extension(
|
|
||||||
"bson._cbson",
|
|
||||||
include_dirs=["bson"],
|
|
||||||
sources=["bson/_cbsonmodule.c", "bson/time64.c", "bson/buffer.c"],
|
|
||||||
),
|
|
||||||
Extension(
|
|
||||||
"pymongo._cmessage",
|
|
||||||
include_dirs=["bson"],
|
|
||||||
sources=[
|
|
||||||
"pymongo/_cmessagemodule.c",
|
|
||||||
"bson/_cbsonmodule.c",
|
|
||||||
"bson/time64.c",
|
|
||||||
"bson/buffer.c",
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
if "--no_ext" in sys.argv or os.environ.get("NO_EXT"):
|
|
||||||
try:
|
|
||||||
sys.argv.remove("--no_ext")
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
ext_modules = []
|
|
||||||
elif sys.platform.startswith("java") or sys.platform == "cli" or "PyPy" in sys.version:
|
|
||||||
sys.stdout.write(
|
|
||||||
"""
|
|
||||||
*****************************************************\n
|
|
||||||
The optional C extensions are currently not supported\n
|
|
||||||
by this python implementation.\n
|
|
||||||
*****************************************************\n
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
ext_modules = []
|
|
||||||
|
|
||||||
|
|
||||||
def parse_reqs_file(fname):
|
|
||||||
with open(fname) as fid:
|
|
||||||
lines = [li.strip() for li in fid.readlines()]
|
|
||||||
return [li for li in lines if li and not li.startswith("#")]
|
|
||||||
|
|
||||||
|
|
||||||
dependencies = parse_reqs_file("requirements.txt")
|
|
||||||
|
|
||||||
extras_require = dict(
|
|
||||||
aws=parse_reqs_file("requirements/aws.txt"),
|
|
||||||
encryption=parse_reqs_file("requirements/encryption.txt"),
|
|
||||||
gssapi=parse_reqs_file("requirements/gssapi.txt"),
|
|
||||||
ocsp=parse_reqs_file("requirements/ocsp.txt"),
|
|
||||||
snappy=parse_reqs_file("requirements/snappy.txt"),
|
|
||||||
# PYTHON-3423 Removed in 4.3 but kept here to avoid pip warnings.
|
|
||||||
srv=[],
|
|
||||||
tls=[],
|
|
||||||
# PYTHON-2133 Removed in 4.0 but kept here to avoid pip warnings.
|
|
||||||
zstd=parse_reqs_file("requirements/zstd.txt"),
|
|
||||||
test=parse_reqs_file("requirements/test.txt"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
setup(
|
raise RuntimeError(msg)
|
||||||
cmdclass={"build_ext": custom_build_ext},
|
|
||||||
install_requires=dependencies,
|
|
||||||
extras_require=extras_require,
|
|
||||||
ext_modules=ext_modules,
|
|
||||||
) # type:ignore
|
|
||||||
|
|||||||
@ -277,6 +277,7 @@ class ClientContext:
|
|||||||
self.is_data_lake = False
|
self.is_data_lake = False
|
||||||
self.load_balancer = TEST_LOADBALANCER
|
self.load_balancer = TEST_LOADBALANCER
|
||||||
self.serverless = TEST_SERVERLESS
|
self.serverless = TEST_SERVERLESS
|
||||||
|
self._fips_enabled = None
|
||||||
if self.load_balancer or self.serverless:
|
if self.load_balancer or self.serverless:
|
||||||
self.default_client_options["loadBalanced"] = True
|
self.default_client_options["loadBalanced"] = True
|
||||||
if COMPRESSORS:
|
if COMPRESSORS:
|
||||||
@ -523,6 +524,17 @@ class ClientContext:
|
|||||||
# Raised if self.server_status is None.
|
# Raised if self.server_status is None.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def fips_enabled(self):
|
||||||
|
if self._fips_enabled is not None:
|
||||||
|
return self._fips_enabled
|
||||||
|
try:
|
||||||
|
subprocess.check_call(["fips-mode-setup", "--is-enabled"])
|
||||||
|
self._fips_enabled = True
|
||||||
|
except (subprocess.SubprocessError, FileNotFoundError):
|
||||||
|
self._fips_enabled = False
|
||||||
|
return self._fips_enabled
|
||||||
|
|
||||||
def check_auth_type(self, auth_type):
|
def check_auth_type(self, auth_type):
|
||||||
auth_mechs = self.server_parameters.get("authenticationMechanisms", [])
|
auth_mechs = self.server_parameters.get("authenticationMechanisms", [])
|
||||||
return auth_type in auth_mechs
|
return auth_type in auth_mechs
|
||||||
@ -670,6 +682,12 @@ class ClientContext:
|
|||||||
lambda: self.auth_enabled, "Authentication is not enabled on the server", func=func
|
lambda: self.auth_enabled, "Authentication is not enabled on the server", func=func
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def require_no_fips(self, func):
|
||||||
|
"""Run a test only if the host does not have FIPS enabled."""
|
||||||
|
return self._require(
|
||||||
|
lambda: not self.fips_enabled, "Test cannot run on a FIPS-enabled host", func=func
|
||||||
|
)
|
||||||
|
|
||||||
def require_no_auth(self, func):
|
def require_no_auth(self, func):
|
||||||
"""Run a test only if the server is running without auth enabled."""
|
"""Run a test only if the server is running without auth enabled."""
|
||||||
return self._require(
|
return self._require(
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Colon in a key value pair",
|
"description": "Colon in a key value pair",
|
||||||
"uri": "mongodb://example.com?authMechanismProperties=TOKEN_RESOURCE:mongodb://test-cluster",
|
"uri": "mongodb://example.com/?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://test-cluster",
|
||||||
"valid": true,
|
"valid": true,
|
||||||
"warning": false,
|
"warning": false,
|
||||||
"hosts": [
|
"hosts": [
|
||||||
|
|||||||
@ -96,13 +96,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "Comma in a key value pair causes a warning",
|
"description": "Comma in a key value pair causes a warning",
|
||||||
"uri": "mongodb://example.com?authMechanismProperties=TOKEN_RESOURCE:mongodb://host1%2Chost2",
|
"uri": "mongodb://localhost?authMechanism=MONGODB-OIDC&authMechanismProperties=TOKEN_RESOURCE:mongodb://host1%2Chost2",
|
||||||
"valid": true,
|
"valid": true,
|
||||||
"warning": true,
|
"warning": true,
|
||||||
"hosts": [
|
"hosts": [
|
||||||
{
|
{
|
||||||
"type": "hostname",
|
"type": "hostname",
|
||||||
"host": "example.com",
|
"host": "localhost",
|
||||||
"port": null
|
"port": null
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@ -343,6 +343,7 @@ class TestSCRAMSHA1(IntegrationTest):
|
|||||||
client_context.drop_user("pymongo_test", "user")
|
client_context.drop_user("pymongo_test", "user")
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
|
||||||
|
@client_context.require_no_fips
|
||||||
def test_scram_sha1(self):
|
def test_scram_sha1(self):
|
||||||
host, port = client_context.host, client_context.port
|
host, port = client_context.host, client_context.port
|
||||||
|
|
||||||
@ -404,6 +405,7 @@ class TestSCRAM(IntegrationTest):
|
|||||||
else:
|
else:
|
||||||
self.assertEqual(started, ["saslStart", "saslContinue", "saslContinue"])
|
self.assertEqual(started, ["saslStart", "saslContinue", "saslContinue"])
|
||||||
|
|
||||||
|
@client_context.require_no_fips
|
||||||
def test_scram(self):
|
def test_scram(self):
|
||||||
# Step 1: create users
|
# Step 1: create users
|
||||||
client_context.create_user(
|
client_context.create_user(
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import mmap
|
|||||||
import os
|
import os
|
||||||
import pickle
|
import pickle
|
||||||
import re
|
import re
|
||||||
|
import struct
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import uuid
|
import uuid
|
||||||
@ -489,6 +490,33 @@ class TestBSON(unittest.TestCase):
|
|||||||
b"\x00",
|
b"\x00",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_bad_code(self):
|
||||||
|
# Assert that decoding invalid Code with scope does not include a field name.
|
||||||
|
def generate_payload(length: int) -> bytes:
|
||||||
|
string_size = length - 0x1E
|
||||||
|
|
||||||
|
return bytes.fromhex(
|
||||||
|
struct.pack("<I", length).hex() # payload size
|
||||||
|
+ "0f" # type "code with scope"
|
||||||
|
+ "3100" # key (cstring)
|
||||||
|
+ "0a000000" # c_w_s_size
|
||||||
|
+ "04000000" # code_size
|
||||||
|
+ "41004200" # code (cstring)
|
||||||
|
+ "feffffff" # scope_size
|
||||||
|
+ "02" # type "string"
|
||||||
|
+ "3200" # key (cstring)
|
||||||
|
+ struct.pack("<I", string_size).hex() # string size
|
||||||
|
+ "00" * string_size # value (cstring)
|
||||||
|
# next bytes is a field name for type \x00
|
||||||
|
# type \x00 is invalid so bson throws an exception
|
||||||
|
)
|
||||||
|
|
||||||
|
for i in range(100):
|
||||||
|
payload = generate_payload(0x54F + i)
|
||||||
|
with self.assertRaisesRegex(InvalidBSON, "invalid") as ctx:
|
||||||
|
bson.decode(payload)
|
||||||
|
self.assertNotIn("fieldname", str(ctx.exception))
|
||||||
|
|
||||||
def test_unknown_type(self):
|
def test_unknown_type(self):
|
||||||
# Repr value differs with major python version
|
# Repr value differs with major python version
|
||||||
part = "type {!r} for fieldname 'foo'".format(b"\x14")
|
part = "type {!r} for fieldname 'foo'".format(b"\x14")
|
||||||
|
|||||||
@ -1016,6 +1016,7 @@ class TestClient(IntegrationTest):
|
|||||||
MongoClient("http://localhost")
|
MongoClient("http://localhost")
|
||||||
|
|
||||||
@client_context.require_auth
|
@client_context.require_auth
|
||||||
|
@client_context.require_no_fips
|
||||||
def test_auth_from_uri(self):
|
def test_auth_from_uri(self):
|
||||||
host, port = client_context.host, client_context.port
|
host, port = client_context.host, client_context.port
|
||||||
client_context.create_user("admin", "admin", "pass")
|
client_context.create_user("admin", "admin", "pass")
|
||||||
@ -1072,6 +1073,7 @@ class TestClient(IntegrationTest):
|
|||||||
rs_or_single_client_noauth(username="ad min", password="foo").server_info()
|
rs_or_single_client_noauth(username="ad min", password="foo").server_info()
|
||||||
|
|
||||||
@client_context.require_auth
|
@client_context.require_auth
|
||||||
|
@client_context.require_no_fips
|
||||||
def test_lazy_auth_raises_operation_failure(self):
|
def test_lazy_auth_raises_operation_failure(self):
|
||||||
lazy_client = rs_or_single_client_noauth(
|
lazy_client = rs_or_single_client_noauth(
|
||||||
f"mongodb://user:wrong@{client_context.host}/pymongo_test", connect=False
|
f"mongodb://user:wrong@{client_context.host}/pymongo_test", connect=False
|
||||||
|
|||||||
@ -400,6 +400,7 @@ class TestCMAP(IntegrationTest):
|
|||||||
failed_event = listener.events[3]
|
failed_event = listener.events[3]
|
||||||
self.assertEqual(failed_event.reason, ConnectionCheckOutFailedReason.CONN_ERROR)
|
self.assertEqual(failed_event.reason, ConnectionCheckOutFailedReason.CONN_ERROR)
|
||||||
|
|
||||||
|
@client_context.require_no_fips
|
||||||
def test_5_check_out_fails_auth_error(self):
|
def test_5_check_out_fails_auth_error(self):
|
||||||
listener = CMAPListener()
|
listener = CMAPListener()
|
||||||
client = single_client_noauth(
|
client = single_client_noauth(
|
||||||
|
|||||||
@ -431,6 +431,7 @@ class TestDatabase(IntegrationTest):
|
|||||||
def test_cursor_command_invalid(self):
|
def test_cursor_command_invalid(self):
|
||||||
self.assertRaises(InvalidOperation, self.db.cursor_command, "usersInfo", "test")
|
self.assertRaises(InvalidOperation, self.db.cursor_command, "usersInfo", "test")
|
||||||
|
|
||||||
|
@client_context.require_no_fips
|
||||||
def test_password_digest(self):
|
def test_password_digest(self):
|
||||||
self.assertRaises(TypeError, auth._password_digest, 5)
|
self.assertRaises(TypeError, auth._password_digest, 5)
|
||||||
self.assertRaises(TypeError, auth._password_digest, True)
|
self.assertRaises(TypeError, auth._password_digest, True)
|
||||||
|
|||||||
@ -16,6 +16,7 @@ from __future__ import annotations
|
|||||||
import os
|
import os
|
||||||
from test import unittest
|
from test import unittest
|
||||||
from test.test_client import IntegrationTest
|
from test.test_client import IntegrationTest
|
||||||
|
from test.utils import single_client
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from bson import json_util
|
from bson import json_util
|
||||||
@ -82,6 +83,19 @@ class TestLogger(IntegrationTest):
|
|||||||
|
|
||||||
self.assertEqual(last_3_bytes, str_to_repeat)
|
self.assertEqual(last_3_bytes, str_to_repeat)
|
||||||
|
|
||||||
|
def test_logging_without_listeners(self):
|
||||||
|
c = single_client()
|
||||||
|
self.assertEqual(len(c._event_listeners.event_listeners()), 0)
|
||||||
|
with self.assertLogs("pymongo.connection", level="DEBUG") as cm:
|
||||||
|
c.db.test.insert_one({"x": "1"})
|
||||||
|
self.assertGreater(len(cm.records), 0)
|
||||||
|
with self.assertLogs("pymongo.command", level="DEBUG") as cm:
|
||||||
|
c.db.test.insert_one({"x": "1"})
|
||||||
|
self.assertGreater(len(cm.records), 0)
|
||||||
|
with self.assertLogs("pymongo.serverSelection", level="DEBUG") as cm:
|
||||||
|
c.db.test.insert_one({"x": "1"})
|
||||||
|
self.assertGreater(len(cm.records), 0)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@ -22,6 +22,7 @@ sys.path[0:0] = [""]
|
|||||||
from test import unittest
|
from test import unittest
|
||||||
|
|
||||||
import pymongo
|
import pymongo
|
||||||
|
from pymongo._version import get_version_tuple
|
||||||
|
|
||||||
|
|
||||||
class TestPyMongo(unittest.TestCase):
|
class TestPyMongo(unittest.TestCase):
|
||||||
@ -29,6 +30,14 @@ class TestPyMongo(unittest.TestCase):
|
|||||||
# Testing that pymongo module imports mongo_client.MongoClient
|
# Testing that pymongo module imports mongo_client.MongoClient
|
||||||
self.assertEqual(pymongo.MongoClient, pymongo.mongo_client.MongoClient)
|
self.assertEqual(pymongo.MongoClient, pymongo.mongo_client.MongoClient)
|
||||||
|
|
||||||
|
def test_get_version_tuple(self):
|
||||||
|
self.assertEqual(get_version_tuple("4.8.0.dev1"), (4, 8, 0, ".dev1"))
|
||||||
|
self.assertEqual(get_version_tuple("4.8.1"), (4, 8, 1))
|
||||||
|
self.assertEqual(get_version_tuple("5.0.0rc1"), (5, 0, 0, "rc1"))
|
||||||
|
self.assertEqual(get_version_tuple("5.0"), (5, 0))
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
get_version_tuple("5")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@ -29,6 +29,14 @@ import bson # noqa: E402
|
|||||||
import pymongo # noqa: E402
|
import pymongo # noqa: E402
|
||||||
|
|
||||||
if not pymongo.has_c() or not bson.has_c():
|
if not pymongo.has_c() or not bson.has_c():
|
||||||
|
try:
|
||||||
|
from pymongo import _cmessage # type:ignore[attr-defined] # noqa: F401
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
try:
|
||||||
|
from bson import _cbson # type:ignore[attr-defined] # noqa: F401
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
sys.exit("could not load C extensions")
|
sys.exit("could not load C extensions")
|
||||||
|
|
||||||
if os.environ.get("ENSURE_UNIVERSAL2") == "1":
|
if os.environ.get("ENSURE_UNIVERSAL2") == "1":
|
||||||
|
|||||||
13
tox.ini
13
tox.ini
@ -31,8 +31,6 @@ envlist =
|
|||||||
doc-test,
|
doc-test,
|
||||||
# Linkcheck sphinx docs
|
# Linkcheck sphinx docs
|
||||||
linkcheck
|
linkcheck
|
||||||
# Check the sdist integrity.
|
|
||||||
manifest
|
|
||||||
|
|
||||||
labels = # Use labels and -m instead of -e so that tox -m <label> fails instantly if the label does not exist
|
labels = # Use labels and -m instead of -e so that tox -m <label> fails instantly if the label does not exist
|
||||||
test = test
|
test = test
|
||||||
@ -51,7 +49,6 @@ labels = # Use labels and -m instead of -e so that tox -m <label> fails instantl
|
|||||||
linkcheck = linkcheck
|
linkcheck = linkcheck
|
||||||
test-mockupdb = test-mockupdb
|
test-mockupdb = test-mockupdb
|
||||||
aws-secrets = aws-secrets
|
aws-secrets = aws-secrets
|
||||||
manifest = manifest
|
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
package = editable
|
package = editable
|
||||||
@ -71,8 +68,6 @@ commands =
|
|||||||
description = run tests using run-tests.sh Evergreen script
|
description = run tests using run-tests.sh Evergreen script
|
||||||
passenv = *
|
passenv = *
|
||||||
extras = test
|
extras = test
|
||||||
deps =
|
|
||||||
setuptools
|
|
||||||
allowlist_externals =
|
allowlist_externals =
|
||||||
bash
|
bash
|
||||||
commands =
|
commands =
|
||||||
@ -184,14 +179,6 @@ allowlist_externals =
|
|||||||
commands =
|
commands =
|
||||||
{[testenv:test]commands} ./test/mockupdb
|
{[testenv:test]commands} ./test/mockupdb
|
||||||
|
|
||||||
[testenv:manifest]
|
|
||||||
description = ensure the sdist manifest is correct
|
|
||||||
skip_install = true
|
|
||||||
deps =
|
|
||||||
check-manifest
|
|
||||||
commands =
|
|
||||||
python -m check_manifest -v
|
|
||||||
|
|
||||||
[testenv:setup-encryption]
|
[testenv:setup-encryption]
|
||||||
description = set up encryption assets and servers
|
description = set up encryption assets and servers
|
||||||
skip_install = true
|
skip_install = true
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user