-
-
Notifications
You must be signed in to change notification settings - Fork 32.2k
gh-124176: create_autospec
must not change how dataclass defaults are mocked
#124724
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
base: main
Are you sure you want to change the base?
Changes from 4 commits
bd73cda
81b4e00
bfa9385
dd1fe12
71f355e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1107,6 +1107,32 @@ class WithNonFields: | |
with self.assertRaisesRegex(AttributeError, msg): | ||
mock.b | ||
|
||
def test_dataclass_default_value_type_overrides_field_annotation(self): | ||
# If field defines an actual default, we don't need to change | ||
# the default type. Since this is how it used to work before #124176 | ||
@dataclass | ||
class WithUnionAnnotation: | ||
narrow_default: int | None = field(default=30) | ||
|
||
for mock in [ | ||
create_autospec(WithUnionAnnotation, instance=True), | ||
create_autospec(WithUnionAnnotation()), | ||
]: | ||
with self.subTest(mock=mock): | ||
self.assertIs(mock.narrow_default.__class__, int) | ||
|
||
def test_dataclass_field_with_no_default_value(self): | ||
@dataclass | ||
class WithUnionAnnotation: | ||
no_default: int | None | ||
|
||
mock = create_autospec(WithUnionAnnotation, instance=True) | ||
self.assertIs(mock.no_default.__class__, type(int | None)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same with these two, but this one is particularly confusing. I know it's tangential to this PR, but I have to admit I'm not sure what the intention of "You can use a class as the spec for an instance object by passing instance=True" Does that mean:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some objects need real runtime resources to instantiate, so deriving an instance mock spec from an actual instance isn't practical. "instance = True" tells the module to do the best it can to mock an instance based on a class definition without needing to make a real instance. 3.14 is getting an enhancement to mock data class fields with no defaults based on their type annotations, but it has the same limitation in the absence of a default value as type checkers do: it can only narrow the field type for lazily populated fields down to a union, not to a concrete type. This PR fixes a compatibility issue with the handling of data class fields that do have defaults (they get a mocked spec based on their default value instead of their declared union type). I admit the handling of complex annotations isn't great (they are speced based on the annotation itself). Maybe we should treat the field annotation as an unconstrained mock (effectively Any) if the annotation doesn't correspond to a true runtime type? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A potentially more conservative approach:
Any other field would become an unconstrained mock. This would still mean the field existed though (even in strict mode), and hence users could set it to something more specific in their test code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sorry, I didn't get this part. Can you please provide an example? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For me, I'm looking to understand what this is:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cjw296 this currently would be: >>> int | None
int | None
>>> type(int | None)
<class 'types.UnionType'> This is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The instance attribute of the mock instance would be a type? That doesn't make any sense on the face of it, what am I missing? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cjw296 You're not missing anything, that's a case I'm suggesting we should change to instead be mocked as @sobolevn "Annotations of the accepted types" refers to things like Example: @dataclass
class Example:
a: Annotated[int, "example"] >>> fields(Example)[0].type
typing.Annotated[int, 'example']
>>> fields(Example)[0].type.__origin__
<class 'int'> |
||
|
||
mock = create_autospec(WithUnionAnnotation(1)) | ||
self.assertIs(mock.no_default.__class__, int) | ||
|
||
|
||
class TestCallList(unittest.TestCase): | ||
|
||
def test_args_list_contains_call_list(self): | ||
|
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 feel both of these tests would be clearer if they actually showed what the thing ends up being, not just it's type, something like:
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.
Adding something along these lines would also distinguish the cases where we're expecting to mock a specific value from those where we're deriving an instance spec from a declared runtime type