Skip to content

PYTHON-5344 and PYTHON-5403 Allow Instantiated MongoClients to Send Client Metadata On-Demand #2358

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .evergreen/resync-specs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ do
gridfs)
cpjson gridfs/tests gridfs
;;
handshake)
cpjson mongodb-handshake/tests handshake
;;
index|index-management)
cpjson index-management/tests index_management
;;
Expand Down
14 changes: 14 additions & 0 deletions test/asynchronous/test_client_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
from __future__ import annotations

import asyncio
import os
import pathlib
import time
import unittest
from test.asynchronous import AsyncIntegrationTest
from test.asynchronous.unified_format import generate_test_classes
from test.utils_shared import CMAPListener
from typing import Any, Optional

Expand All @@ -37,6 +40,17 @@

_IS_SYNC = False

# Location of JSON test specifications.
if _IS_SYNC:
_TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "handshake", "unified")
else:
_TEST_PATH = os.path.join(
pathlib.Path(__file__).resolve().parent.parent, "handshake", "unified"
)

# Generate unified tests.
globals().update(generate_test_classes(_TEST_PATH, module=__name__, RUN_ON_SERVERLESS=True))


def _get_handshake_driver_info(request):
assert "client" in request
Expand Down
14 changes: 13 additions & 1 deletion test/asynchronous/unified_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
from pymongo.asynchronous.database import AsyncDatabase
from pymongo.asynchronous.encryption import AsyncClientEncryption
from pymongo.asynchronous.helpers import anext
from pymongo.driver_info import DriverInfo
from pymongo.encryption_options import _HAVE_PYMONGOCRYPT
from pymongo.errors import (
AutoReconnect,
Expand Down Expand Up @@ -703,6 +704,7 @@ async def _collectionOperation_createChangeStream(self, target, *args, **kwargs)
async def _databaseOperation_runCommand(self, target, **kwargs):
self.__raise_if_unsupported("runCommand", target, AsyncDatabase)
# Ensure the first key is the command name.
print(kwargs)
ordered_command = SON([(kwargs.pop("command_name"), 1)])
ordered_command.update(kwargs["command"])
kwargs["command"] = ordered_command
Expand Down Expand Up @@ -840,6 +842,13 @@ async def _cursor_close(self, target, *args, **kwargs):
self.__raise_if_unsupported("close", target, NonLazyCursor, AsyncCommandCursor)
return await target.close()

async def _clientOperation_appendMetadata(self, target, *args, **kwargs):
print("IN MY FUNC")
print(kwargs)
info_opts = kwargs["driver_info_options"]
driver_info = DriverInfo(info_opts["name"], info_opts["version"], info_opts["platform"])
target.append_metadata(driver_info)

async def _clientEncryptionOperation_createDataKey(self, target, *args, **kwargs):
if "opts" in kwargs:
kwargs.update(camel_to_snake_args(kwargs.pop("opts")))
Expand Down Expand Up @@ -925,11 +934,11 @@ async def run_entity_operation(self, spec):
)
else:
arguments = {}

if isinstance(target, AsyncMongoClient):
method_name = f"_clientOperation_{opname}"
elif isinstance(target, AsyncDatabase):
method_name = f"_databaseOperation_{opname}"
print(f"{method_name=}")
elif isinstance(target, AsyncCollection):
method_name = f"_collectionOperation_{opname}"
# contentType is always stored in metadata in pymongo.
Expand Down Expand Up @@ -976,6 +985,7 @@ async def run_entity_operation(self, spec):
with pymongo.timeout(timeout):
result = await cmd(**dict(arguments))
else:
print(f"{cmd=} {dict=} {arguments=}")
result = await cmd(**dict(arguments))
except Exception as exc:
# Ignore all operation errors but to avoid masking bugs don't
Expand Down Expand Up @@ -1238,6 +1248,7 @@ async def run_special_operation(self, spec):

async def run_operations(self, spec):
for op in spec:
print(f"{op=}")
if op["object"] == "testRunner":
await self.run_special_operation(op)
else:
Expand Down Expand Up @@ -1440,6 +1451,7 @@ async def _run_scenario(self, spec, uri=None):
await self.check_log_messages(spec["operations"], expect_log_messages)
else:
# process operations
print(f"{spec['operations']=}")
await self.run_operations(spec["operations"])

# process expectEvents
Expand Down
100 changes: 100 additions & 0 deletions test/handshake/unified/metadata-not-propagated.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"description": "client metadata is not propagated to the server",
"schemaVersion": "1.9",
"runOnRequirements": [
{
"minServerVersion": "6.0"
}
],
"createEntities": [
{
"client": {
"id": "client",
"observeEvents": [
"commandSucceededEvent",
"commandFailedEvent",
"connectionClosedEvent",
"connectionCreatedEvent"
]
}
},
{
"database": {
"id": "database",
"client": "client",
"databaseName": "test"
}
}
],
"tests": [
{
"description": "metadata append does not create new connections or close existing ones and no hello command is sent",
"operations": [
{
"name": "runCommand",
"object": "database",
"arguments": {
"commandName": "ping",
"command": {
"ping": 1
}
},
"expectResult": {
"ok": 1
}
},
{
"name": "appendMetadata",
"object": "client",
"arguments": {
"driverInfoOptions": {
"name": "framework",
"version": "2.0",
"platform": "Framework Platform"
}
}
},
{
"name": "runCommand",
"object": "database",
"arguments": {
"commandName": "ping",
"command": {
"ping": 1
}
},
"expectResult": {
"ok": 1
}
}
],
"expectEvents": [
{
"client": "client",
"eventType": "cmap",
"events": [
{
"connectionCreatedEvent": {}
}
]
},
{
"client": "client",
"eventType": "command",
"events": [
{
"commandSucceededEvent": {
"commandName": "ping"
}
},
{
"commandSucceededEvent": {
"commandName": "ping"
}
}
]
}
]
}
]
}
14 changes: 14 additions & 0 deletions test/test_client_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@
from __future__ import annotations

import asyncio
import os
import pathlib
import time
import unittest
from test import IntegrationTest
from test.unified_format import generate_test_classes
from test.utils_shared import CMAPListener
from typing import Any, Optional

Expand All @@ -37,6 +40,17 @@

_IS_SYNC = True

# Location of JSON test specifications.
if _IS_SYNC:
_TEST_PATH = os.path.join(pathlib.Path(__file__).resolve().parent, "handshake", "unified")
else:
_TEST_PATH = os.path.join(
pathlib.Path(__file__).resolve().parent.parent, "handshake", "unified"
)

# Generate unified tests.
globals().update(generate_test_classes(_TEST_PATH, module=__name__, RUN_ON_SERVERLESS=True))


def _get_handshake_driver_info(request):
assert "client" in request
Expand Down
14 changes: 13 additions & 1 deletion test/unified_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
from bson.objectid import ObjectId
from gridfs import GridFSBucket, GridOut, NoFile
from pymongo import ASCENDING, CursorType, MongoClient, _csot
from pymongo.driver_info import DriverInfo
from pymongo.encryption_options import _HAVE_PYMONGOCRYPT
from pymongo.errors import (
AutoReconnect,
Expand Down Expand Up @@ -700,6 +701,7 @@ def _collectionOperation_createChangeStream(self, target, *args, **kwargs):
def _databaseOperation_runCommand(self, target, **kwargs):
self.__raise_if_unsupported("runCommand", target, Database)
# Ensure the first key is the command name.
print(kwargs)
ordered_command = SON([(kwargs.pop("command_name"), 1)])
ordered_command.update(kwargs["command"])
kwargs["command"] = ordered_command
Expand Down Expand Up @@ -837,6 +839,13 @@ def _cursor_close(self, target, *args, **kwargs):
self.__raise_if_unsupported("close", target, NonLazyCursor, CommandCursor)
return target.close()

def _clientOperation_appendMetadata(self, target, *args, **kwargs):
print("IN MY FUNC")
print(kwargs)
info_opts = kwargs["driver_info_options"]
driver_info = DriverInfo(info_opts["name"], info_opts["version"], info_opts["platform"])
target.append_metadata(driver_info)

def _clientEncryptionOperation_createDataKey(self, target, *args, **kwargs):
if "opts" in kwargs:
kwargs.update(camel_to_snake_args(kwargs.pop("opts")))
Expand Down Expand Up @@ -916,11 +925,11 @@ def run_entity_operation(self, spec):
)
else:
arguments = {}

if isinstance(target, MongoClient):
method_name = f"_clientOperation_{opname}"
elif isinstance(target, Database):
method_name = f"_databaseOperation_{opname}"
print(f"{method_name=}")
elif isinstance(target, Collection):
method_name = f"_collectionOperation_{opname}"
# contentType is always stored in metadata in pymongo.
Expand Down Expand Up @@ -967,6 +976,7 @@ def run_entity_operation(self, spec):
with pymongo.timeout(timeout):
result = cmd(**dict(arguments))
else:
print(f"{cmd=} {dict=} {arguments=}")
result = cmd(**dict(arguments))
except Exception as exc:
# Ignore all operation errors but to avoid masking bugs don't
Expand Down Expand Up @@ -1225,6 +1235,7 @@ def run_special_operation(self, spec):

def run_operations(self, spec):
for op in spec:
print(f"{op=}")
if op["object"] == "testRunner":
self.run_special_operation(op)
else:
Expand Down Expand Up @@ -1425,6 +1436,7 @@ def _run_scenario(self, spec, uri=None):
self.check_log_messages(spec["operations"], expect_log_messages)
else:
# process operations
print(f"{spec['operations']=}")
self.run_operations(spec["operations"])

# process expectEvents
Expand Down
Loading