Skip to content

PYTHON-2453 Add MongoDB Versioned API #536

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

Merged
merged 11 commits into from
Jan 12, 2021

Conversation

ShaneHarvey
Copy link
Member

@ShaneHarvey ShaneHarvey commented Dec 18, 2020

This PR adds support for the MongoDB Versioned API version 1: https://github.com/mongodb/specifications/blob/784857f/source/versioned-api/versioned-api.rst

TODO:

@ShaneHarvey ShaneHarvey marked this pull request as ready for review December 23, 2020 04:16
Copy link
Contributor

@prashantmital prashantmital left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Can you edit the docstring for Database.command to specify that users must not explicitly add the serverApi key to the command document? This is mandated by the spec: https://github.com/mongodb/specifications/blob/master/source/versioned-api/versioned-api.rst#generic-command-helper
  2. How does this implementation ensure that serverApi params don't get added to getMore commands?

if (session and session.in_transaction and
not session._starting_transaction):
return
if self.opts.server_api:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make this more explicitly adhere to the spec, I think we should use if self.opts.server_api is not None. Though the enum doesn't have any such value, the above comparison could evaluate to false if the value is set to something which evaluates to a bool false.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.opts.server_api is either None or an instance of ServerApi so I'm not sure I follow.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point here was that since the spec asks us to check if the user passed a value at all, that's exactly what we should do. Since None is the default for this kwarg, if <kwarg name> is not None is a (more) explicit check for whether it contains its default value.
It doesn't really affect behavior so if you prefer it the way it is, leave it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user passes something that's not a ServerApi they will get an error when creating the client:

>>> MongoClient(server_api=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pymongo/mongo_client.py", line 678, in __init__
    keyword_opts.cased_key(k), v) for k, v in keyword_opts.items()))
  File "pymongo/mongo_client.py", line 678, in <genexpr>
    keyword_opts.cased_key(k), v) for k, v in keyword_opts.items()))
  File "pymongo/common.py", line 764, in validate
    value = validator(option, value)
  File "pymongo/common.py", line 537, in validate_server_api_or_none
    raise TypeError("%s must be an instance of ServerApi" % (option,))
TypeError: server_api must be an instance of ServerApi

"""Server API version "1"."""


class ServerApi(object):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about declaring this as an overridden namedtuple instance much like CodecOptions? If you think that's a good idea, I think this would also benefit from having a with_options method like CodecOptions.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see much value in making this a namedtuple. CodecOptions inherits from namedtuple to make it easier to work with in the C extensions. I also see no reason to add a with_options. The intended use case is to create this class once.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One benefit would be immuatability. I believe the spec mandates that this be an immutable object.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is already effectively immutable because there are no public attributes which can be mutated.

test/__init__.py Outdated
@@ -751,6 +764,8 @@ def sanitize_cmd(cmd):
cp.pop('$db', None)
cp.pop('$readPreference', None)
cp.pop('lsid', None)
# Versioned api parameters
cp.pop('apiVersion', None)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why remove the serverApi as opped to skipping the monitoring tests when testing against versioned API?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I recall, lot's of tests failed because of this. What do you think of:

Suggested change
cp.pop('apiVersion', None)
if MONGODB_API_VERSION:
cp.pop('apiVersion', None)

Copy link
Member Author

@ShaneHarvey ShaneHarvey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review. I'll make the requested changes soon. Responding to a few of the comments now.

  1. How does this implementation ensure that serverApi params don't get added to getMore commands?

We don't add serverApi to getMore commands because ... well... we don't add them. The pymongo.message._GetMore class does not call sock_info.add_server_api(command, session). The tests also verify this behavior.

if (session and session.in_transaction and
not session._starting_transaction):
return
if self.opts.server_api:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self.opts.server_api is either None or an instance of ServerApi so I'm not sure I follow.

"""Server API version "1"."""


class ServerApi(object):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see much value in making this a namedtuple. CodecOptions inherits from namedtuple to make it easier to work with in the C extensions. I also see no reason to add a with_options. The intended use case is to create this class once.

test/__init__.py Outdated
@@ -751,6 +764,8 @@ def sanitize_cmd(cmd):
cp.pop('$db', None)
cp.pop('$readPreference', None)
cp.pop('lsid', None)
# Versioned api parameters
cp.pop('apiVersion', None)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I recall, lot's of tests failed because of this. What do you think of:

Suggested change
cp.pop('apiVersion', None)
if MONGODB_API_VERSION:
cp.pop('apiVersion', None)

Copy link
Member Author

@ShaneHarvey ShaneHarvey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you edit the docstring for Database.command to specify that users must not explicitly add the serverApi key to the command document?

Done.

if (session and session.in_transaction and
not session._starting_transaction):
return
if self.opts.server_api:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My point here was that since the spec asks us to check if the user passed a value at all, that's exactly what we should do. Since None is the default for this kwarg, if <kwarg name> is not None is a (more) explicit check for whether it contains its default value.
It doesn't really affect behavior so if you prefer it the way it is, leave it.

"""Server API version "1"."""


class ServerApi(object):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One benefit would be immuatability. I believe the spec mandates that this be an immutable object.

if COMPRESSORS:
self.default_client_options["compressors"] = COMPRESSORS
if MONGODB_API_VERSION:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead, should we check for REQUIRE_API_VERSION and then instantiate the server_api object using MONGODB_API_VERSION?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is contingent on us keeping the REQUIRE_API_VERSION envvar. Seems like we might not need it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

display_name: "requireApiVersion1"
tags: [ "requireApiVersion_tag" ]
variables:
REQUIRE_API_VERSION: "1"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the REQUIRE_API_VERSION variable used for? I don't see it used anywhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need both variables. REQUIRE_API_VERSION tells drivers-evg-tools to start the cluster with the requireApiVersion parameter. MONGODB_API_VERSION tells the test suite which ServerApi.version to use. When we eventually test version 2, we'll need to use:

REQUIRE_API_VERSION: "1"
MONGODB_API_VERSION: "2"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see! That's certainly going to be hard to figure out for a newcomer. Can we add a comment that REQUIRE_API_VERSION is used by cluster orchestration scripts in drivers-evergreen-tools?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

@prashantmital prashantmital left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One suggested addition of a comment. Otherwise, LGTM

@ShaneHarvey
Copy link
Member Author

Now that these changes were approved I'm going to rebase to fix the merge conflicts.

@ShaneHarvey ShaneHarvey merged commit ac2f506 into mongodb:master Jan 12, 2021
ShaneHarvey added a commit to ShaneHarvey/mongo-python-driver that referenced this pull request Jan 12, 2021
Add pymongo.server_api.ServerApi and the MongoClient server_api option.
Support Unified Test Format version 1.1 (serverParameters in
runOnRequirements)
Skip dropRole tests due to SERVER-53499.

(cherry picked from commit ac2f506)
@ShaneHarvey ShaneHarvey deleted the PYTHON-2453 branch January 12, 2021 02:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants