Skip to content
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

Should the return type of decorated contextlib.contextmanager be AbstractContextManager? #6520

Closed
mcous opened this issue Dec 6, 2021 · 3 comments

Comments

@mcous
Copy link

mcous commented Dec 6, 2021

Overview

Hello! I'm working on a mocking library that aims to bring type-safety to Python mocking, and I'm running into an issue with the typeshed types for contextlib.contextmanager.

Current behavior

A function decorated with @contextlib.contextmanager has the following signature:

Callable[_P, _GeneratorContextManager[_T]]

I think this is difficult to work with in general, because _GeneratorContextManager is a "private" implementation detail of contextlib. In my specific case, I find it difficult to work with because I would like to mock return values from a @contextmanager decorated method.

Returning a value wrapped in a contextlib.nullcontext from the mock works perfectly at runtime. However, a nullcontext[_T] does not satisfy the typing requirement of _GeneratorContextManager[_T],resulting in the following typing error from mypy:

Argument 1 to "then_return" of "Stub" has incompatible type "ContextManager[_ValueReader]";
expected "_GeneratorContextManager[_ValueReader]"  [arg-type]

Expected behavior

A function decorated with @contextlib.contextmanager should have the signature:

Callable[_P, AbstractContextManager[_T]]

contextlib.AbstractContextManager is a "public" interface, and in my opinion better communicates the intent of the decorator.

Additional thoughts

@contextlib.asynccontextmanager is already typed in the manner proposed above This was changed to support Python 3.10

@JelleZijlstra
Copy link
Member

I ran into a similar recently when a base method was decorated with @contextmanager and the overriding method just returned a ContextManager directly.

Our _GeneratorContextManager provides an additional __call__ method, but it's not clear to me when you'd need that as a user.

@AlexWaygood
Copy link
Member

Additional thoughts

@contextlib.asynccontextmanager is already typed in the manner proposed above:

def asynccontextmanager(func: Callable[_P, AsyncIterator[_T]]) -> Callable[_P, AbstractAsyncContextManager[_T]]: ... # type: ignore[misc]

It's probably worth noting that this is no longer true 😕 — I changed the return type of asynccontextmanager in #6634 to fix an issue caused by the return type not being callable.

@mcous
Copy link
Author

mcous commented Dec 21, 2021

After seeing #6521 I think this is not a typeshed issue. The __call__ methods added to the generator context managers are very much public interfaces to support usage as a decorator, while _GeneratorContextManager and _AsyncGeneratorContextManager are "private" in actual contextlib source (underscore named, not included in __all__). Typeshed can't (and shouldn't) do anything about this discrepancy.

Gonna close this issue because the answer to my question is "no" 🙃

@mcous mcous closed this as completed Dec 21, 2021
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 a pull request may close this issue.

3 participants