Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork32k
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 ourterms of service andprivacy 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?
Conversation
Uh oh!
There was an error while loading.Please reload this page.
I agree with the backport labels, since we want to backport the test case, even if the functional change doesn't apply cleanly. |
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, but I've suggested some naming clarifications for the new test cases.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Co-authored-by: Alyssa Coghlan <ncoghlan@gmail.com>
@ncoghlan thanks a lot for the helpful suggestions! I applied all of them. |
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.
Looks ready to me!
Thanks! I will keep this open for a week or so, mostly waiting for@cjw296's feedback |
create_autospec(WithUnionAnnotation()), | ||
]: | ||
with self.subTest(mock=mock): | ||
self.assertIs(mock.narrow_default.__class__, int) |
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:
self.assertIs(mock.narrow_default.__class__,int) | |
self.assertIs(mock.narrow_default.__class__,int) | |
self.assertEqual(mock.narrow_default,30) |
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
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 comment
The reason will be displayed to describe this comment to others.Learn more.
Same with these two, but this one is particularly confusing.
How can an object be bothNone
and andint
at the same time?
I know it's tangential to this PR, but I have to admit I'm not sure what the intention ofinstance=True|False
is, the docs don't make it any clearer for me :-/
"You can use a class as the spec for an instance object by passing instance=True"
Does that mean:
create_autospec
will produce a mock instance based on treatingspec
as a class- you can produce a mockclass by passing in an instance as
spec
withinstance=True
- something else?
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.
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 thatdo 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 comment
The reason will be displayed to describe this comment to others.Learn more.
A potentially more conservative approach:
create a separate feature request for an "annotation=..." option to build mock specs from arbitrary runtime type annotations
in the meantime, restrict this feature to the following cases:
- concrete runtime types
- type unions that include None (mocked as None)
- annotations of the accepted types
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 comment
The reason will be displayed to describe this comment to others.Learn more.
annotations of the accepted types
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 comment
The reason will be displayed to describe this comment to others.Learn more.
For me, I'm looking to understand what this is:
self.assertIs(mock.no_default, ...what is this?...)
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.
@cjw296 this currently would be:
>>>int|Noneint|None>>> type(int|None)<class'types.UnionType'>
This is atyping
primitive. Docs:https://docs.python.org/3/library/types.html#types.UnionType
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.
Theinstance 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 comment
The 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 asNone
(type union that includesNone
as one of the permitted cases).
@sobolevn "Annotations of the accepted types" refers to things likeAnnotated[int, SomeAnnotation("such as a docstring")]
. They're specified as being purely informational, so we can just ignore the wrapper.
Example:
@dataclassclassExample:a:Annotated[int,"example"]
>>>fields(Example)[0].typetyping.Annotated[int,'example']>>>fields(Example)[0].type.__origin__<class'int'>
Uh oh!
There was an error while loading.Please reload this page.
See#124176 (comment)
@ncoghlan found that my change introduced a behavior change (or even a regression) to dataclasses with defaults.
Before my change
create_autospec
would use the default's type for_spec_class
.After my change it would use the annotation's type.
Which is not always correct. For example,
int | str
would produce a__class__
oftypes.UnionType
People might rely on that, so no need to break this.