-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
PYTHON-3064 Add typings to test package #844
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few points of feedback.
test/test_son.py
Outdated
self.assertTrue("2" in test_son, "in failed") | ||
self.assertFalse("22" in test_son, "in succeeded when it shouldn't") | ||
self.assertTrue(test_son.has_key("2"), "has_key failed") | ||
self.assertFalse(test_son.has_key("22"), "has_key succeeded when it shouldn't") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of changing these tests we need to change SON itself because it supports non-str keys.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
pymongo/socket_checker.py
Outdated
@@ -43,7 +42,7 @@ def __init__(self) -> None: | |||
else: | |||
self._poller = None | |||
|
|||
def select(self, sock: Any, read: bool = False, write: bool = False, timeout: int = 0) -> bool: | |||
def select(self, sock: Any, read: bool = False, write: bool = False, timeout: Optional[Union[float, int]] = 0) -> bool: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
timeout should be float. The float type supports int automatically. Also is Optional needed? Do we ever pass None?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We call it with None
explicitly in test_pooling.py
, and there is internal handling for None
. I'll change it to Optional[float]
@@ -186,7 +186,7 @@ def test_encode_then_decode_any_mapping_legacy(self): | |||
decoder=lambda *args: BSON(args[0]).decode(*args[1:])) | |||
|
|||
def test_encoding_defaultdict(self): | |||
dct = collections.defaultdict(dict, [('foo', 'bar')]) | |||
dct = collections.defaultdict(dict, [('foo', 'bar')]) # type: ignore[arg-type] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you report this as a mypy bug?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, thinking about it more, I don't think this is a bug. We're declaring that new values in the dictionary are of dictionary type, but the dictionary itself is initialized with a value of string type. If I use foo = defaultdict(dict, {'a': dict(a=1)})
in the mypy playground it works fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a mypy bug. The docs for defaultdict says:
The first argument provides the initial value for the default_factory attribute; it defaults to None. All remaining arguments are treated the same as if they were passed to the dict constructor, including keyword arguments.
eg:
>>> dict([('foo', 'bar')])
{'foo': 'bar'}
>>> dct = collections.defaultdict(dict, [('foo', 'bar')])
>>> dct
defaultdict(<class 'dict'>, {'foo': 'bar'})
>>> dct['g']
{}
>>> dct
defaultdict(<class 'dict'>, {'foo': 'bar', 'g': {}})
That said, we can change this line to:
dct = collections.defaultdict(dict, {'foo': 'bar'})
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess my point is, they'd have to change the signature of defaultdict
in typeshed
to not use generics, or perhaps add more additional overrides to provide non-generic versions. I'll update the line instead. 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see what you meant. Do you still think this is worth bringing up to typeshed
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes we should report this bug to typeshed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh sorry, I just realized I'm wrong here. The problem is that we're passing a 'str' value to a dictionary of type Dict['str', dict]
. This is not a typeshed bug:
>>> dct = collections.defaultdict(dict, [('foo', 'bar')]) # This is a type error
>>> dct = collections.defaultdict(dict, [('foo', {})]) # This works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, that is what I was poorly trying to convey. I still think the raised issue stands, but they are likely to ignore/close it.
.github/workflows/test-python.yml
Outdated
@@ -46,4 +46,4 @@ jobs: | |||
pip install -e ".[zstd, srv]" | |||
- name: Run mypy | |||
run: | | |||
mypy --install-types --non-interactive bson gridfs tools | |||
mypy --install-types --non-interactive bson gridfs tools test |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to separate the test/ type checking so that we can globally ignore certain error codes in the test/ package? Eg:
mypy --install-types --non-interactive bson gridfs tools
mypy --install-types --non-interactive --disable-error-code var-annotated --disable-error-code attr-defined --disable-error-code union-attr --disable-error-code assignment test
Disabling some checks could make the tests easier to maintain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
@@ -45,7 +45,7 @@ def responder(request): | |||
kwargs = {'socketTimeoutMS': 100} | |||
# Disable retryable reads when pymongo supports it. | |||
kwargs['retryReads'] = False | |||
self.client = MongoClient(self.server.uri, **kwargs) | |||
self.client = MongoClient(self.server.uri, **kwargs) # type: ignore |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's up with the type ignore here? It seems perfectly reasonable to pass **kwargs
to MongoClient because it accepts **kwargs
. Is this something we should report to mypy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it is a type limitation of mypy, I'll look for/file an issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Found this comment: I'll try TypedDict
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, it really is a bug, already reported: python/mypy#8862
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created https://jira.mongodb.org/browse/PYTHON-3105 to track this
@@ -121,6 +122,7 @@ def kill_change_stream_cursor(self, change_stream): | |||
|
|||
|
|||
class APITestsMixin(object): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this file is improperly using inheritance. Could you open a tech debt ticket so we can fix this at some point?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I addressed the code review comments, I'll handle the mypy/JIRA tickets tomorrow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import sys | ||
from typing import Any, Optional | ||
from typing import Any, Optional, Union |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Union is now unused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
@@ -302,7 +302,7 @@ def test_basic_decode(self): | |||
|
|||
def test_decode_all_buffer_protocol(self): | |||
docs = [{'foo': 'bar'}, {}] | |||
bs = b"".join(map(encode, docs)) | |||
bs = b"".join(map(encode, docs)) # type: ignore[arg-type] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like another bug we can report to mypy.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about this one. This is another example of a generic in the second argument affecting the value in the first, but I think this time it is warranted. If we change it to docs: list[Mapping[str, Any]]
then it matches and passes type check.
@@ -890,6 +890,7 @@ def update_employee_info(session): | |||
update_employee_info(session) | |||
|
|||
employee = employees.find_one({"employee": 3}) | |||
assert employee is not None | |||
self.assertIsNotNone(employee) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is disappointing. I added a comment to the mypy issue tracker here: python/mypy#4063 (comment)
test/test_examples.py
Outdated
@@ -1091,6 +1095,8 @@ def test_causal_consistency(self): | |||
|
|||
# Start Causal Consistency Example 2 | |||
with client.start_session(causal_consistency=True) as s2: | |||
assert s1.cluster_time is not None | |||
assert s1.operation_time is not None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the same reason as above, let's move this asserts to outside the example (above "Start Causal Consistency Example 2"). Also I'm very confused how this assert helps at all since we're accessing s1.cluster_time
again below. From earlier conversations I would have expected mypy to not narrow the type here since each property access can theoretically return a different instance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed. Yeah, that does seem like a limitation.
as_utc = (aware - aware.utcoffset()).replace(tzinfo=utc) | ||
offset = aware.utcoffset() | ||
assert offset is not None | ||
as_utc = (aware - offset).replace(tzinfo=utc) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is disappointing. IIUC a datetime with a tzinfo should always return a non-None utcoffset(). I think it could be related to python/mypy#10067 but I think it's worth reporting as a separate issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the only way to do it is to do what I suggested in python/mypy#10067 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just one minor change
test/test_examples.py
Outdated
# Start Causal Consistency Example 2 | ||
with client.start_session(causal_consistency=True) as s2: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unneeded blank line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
Merging since the failure is fixed by #848 |
No description provided.