Skip to content

Update __exit__ parameters in stubs #9696

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 15 commits into from
Feb 25, 2023

Conversation

Avasam
Copy link
Collaborator

@Avasam Avasam commented Feb 9, 2023

Follow-up to #9519 for third-party stubs now that a new version of mypy has been released
Ref #9297

I could not validate psycopg2, hdbcli and _cffi_backend so I left those as-is

@github-actions

This comment has been minimized.

@srittau
Copy link
Collaborator

srittau commented Feb 9, 2023

I'm not sure that replacing proper __exit__ types with (essentially) object is the right course of action. I can't imagine a situation where you would want to pass some arbitrary into __exit__. In fact I'd say that's a sign of potential bug (even if __exit__ itself accepts it.) It also makes sub-classing more difficult, as an overridden __exit__ would also need to accept object, instead of the proper types.

@Avasam
Copy link
Collaborator Author

Avasam commented Feb 9, 2023

[...] I can't imagine a situation where you would want to pass some arbitrary into __exit__. In fact I'd say that's a sign of potential bug (even if __exit__ itself accepts it.)
It also makes sub-classing more difficult, as an overridden __exit__ would also need to accept object, instead of the proper types.

Should non-star __a?exit__ parameters always use their exact type then? (even if unused). Maybe except for mock.

@srittau
Copy link
Collaborator

srittau commented Feb 9, 2023

I'd say ideally all __exit__ signatures would use the following signature (with the exception of the return type), even those that just use *args in the implementation:

_E = TypeVar("_E", bound=BaseException)

class X:
    @overload
    def __exit__(self, __type: None, __value: None, __exc: None) -> None: ...
    @overload
    def __exit__(self, __type: type[_E], __value: type[_E], __exc: TracebackType) -> None: ...

But since that is unwieldy, we can use the best approximation in a current situation. So I'm actually fine to use Unused for new annotations if that's the fastest way to do it, but we shouldn't regress back from the ideal situation. But I'd be interested in what the other maintainers think.

@github-actions

This comment has been minimized.

@Avasam
Copy link
Collaborator Author

Avasam commented Feb 9, 2023

[...] So I'm actually fine to use Unused for new annotations if that's the fastest way to do it, but we shouldn't regress back from the ideal situation.

Ok, I think my last commit satisfies your comment. (now the PR only adds new Unused when it's unused, new object for *args params and changes *args: object to *args: Unused when unused.

I'd say ideally all __exit__ signatures would use the following signature (with the exception of the return type), even those that just use *args in the implementation: [...]
But since that is unwieldy, we can use the best approximation in a current situation.

Given this isn't a "stopgap", "urgent" or "in the meantime" PR. It's just splitting off a would-be bigger PR about semantically using Unused instead of object or _Unused for unused parameters.
Since it's focusing entirely on exit dunder methods. I don't mind changing it to do it "the proper way" everywhere (and even documenting in CONTRIBUTING.md, and/or update Flake8-pyi, any decision about typing exit dunder methods)

@srittau
Copy link
Collaborator

srittau commented Feb 9, 2023

Side note: Changing __exit__ to overloads could be disruptive or could even now work. I'd definitely would test it with a small explorative PR first.

@JelleZijlstra
Copy link
Member

This PR has a lot of conflicts now.

I'd be hesitant to use the overloaded signature @srittau suggests, though it is probably technically correct:

  • There may be rare instances where only some of the arguments are None, though recent changes to CPython make this less and less likely.
  • Using this signature will force users writing subclasses to also write the verbose signature with overloads, which feels like unnecessary busywork.

@Avasam
Copy link
Collaborator Author

Avasam commented Feb 22, 2023

This PR has a lot of conflicts now.

Expected, but not hard to fix :)

About the signature, should I leave it a-is, (can be done in a different PR and/or discussed further). At least get someway there? Or full-on change all signatures?

@github-actions

This comment has been minimized.

@AlexWaygood
Copy link
Member

My preference would be to generally type __(a)exit__ methods like this, if the method uses *args at runtime:

def __exit__(self, *args: Unused) -> None: ...

And to generally type __(a)exit__ methods like this, if the method includes all three parameters explicitly at runtime:

def __exit__(self, exc_typ: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None

(There will obviously be exceptions to these rules where doing something else makes sense.)


I agree with @srittau that using object or Unused for annotations with the three-argument form could make subclassing unnecessarily difficult, as subclasses will be obliged to use object for annotating those parameters in the subclass.

I also agree with @srittau that a truly precise signature for __(a)exit__ methods would require overloads, but that this would be pretty tedious to do everywhere, and probably wouldn't actually improve type safety significantly. And I agree with @JelleZijlstra that using overloads could also make it harder for people to subclass these classes, so has downsides as well.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@Avasam
Copy link
Collaborator Author

Avasam commented Feb 25, 2023

And to generally type __(a)exit__ methods like this, if the method includes all three parameters explicitly at runtime:

def __exit__(self, exc_typ: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None

I know you said there could be exceptions. But that sounds like something Flake8-PYI could check for and/or add in its existing __(a)exit__ checks, if the exceptions are rare enough.

@AlexWaygood
Copy link
Member

AlexWaygood commented Feb 25, 2023

I know you said there could be exceptions. But that sounds like something Flake8-PYI could check for and/or add in its existing __(a)exit__ checks, if the exceptions are rare enough.

Y036 actually used to be stricter, but Jelle persuaded me to relax it slightly in PyCQA/flake8-pyi#209 after we came across a situation in #7625 (comment) where there was a good case for doing something different. Maybe the exceptions that break the rule are rare enough that we could consider reverting that change... idk, I don't really have a strong opinion on exactly how strict flake8-pyi should be on this ¯\_(ツ)_/¯

@@ -50,7 +51,9 @@ class DAVClient:
ssl_cert: str | tuple[str, str] | None = ...,
) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None: ...
def __exit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

@@ -43,7 +44,9 @@ class InfluxDBClient(_BaseClient):
profilers: Incomplete | None = ...,
) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None: ...
def __exit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

@@ -37,7 +38,9 @@ class InfluxDBClientAsync(_BaseClient):
profilers: Incomplete | None = ...,
) -> None: ...
async def __aenter__(self) -> Self: ...
async def __aexit__(self, exc_type: object, exc: object, tb: object) -> None: ...
async def __aexit__(
self, exc_type: type[BaseException] | None, exc: BaseException | None, tb: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

@@ -57,7 +58,9 @@ class BlockingConnection:
self, parameters: Parameters | Sequence[Parameters] | None = ..., _impl_class: Incomplete | None = ...
) -> None: ...
def __enter__(self) -> Self: ...
def __exit__(self, exc_type: object, value: object, traceback: object) -> None: ...
def __exit__(
self, exc_type: type[BaseException] | None, value: BaseException | None, traceback: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

@@ -462,7 +462,7 @@ class connection:
def tpc_rollback(self, __xid: str | bytes | Xid = ...) -> None: ...
def xid(self, format_id, gtrid, bqual) -> Xid: ...
def __enter__(self) -> Self: ...
def __exit__(self, __type: object, __name: object, __tb: object) -> None: ...
def __exit__(self, __type: type[BaseException] | None, __name: BaseException | None, __tb: TracebackType | None) -> None: ...
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

@@ -42,4 +43,6 @@ class ReaderThread(threading.Thread):
def close(self) -> None: ...
def connect(self) -> tuple[Self, Protocol]: ...
def __enter__(self) -> Protocol: ...
def __exit__(self, __exc_type: object, __exc_val: object, __exc_tb: object) -> None: ...
def __exit__(
self, __exc_type: type[BaseException] | None, __exc_val: BaseException | None, __exc_tb: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

@@ -76,7 +77,9 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, Generic[_StrType]): # T
async def initialize(self) -> Self: ...
async def close(self) -> None: ...
async def __aenter__(self) -> Self: ...
async def __aexit__(self, exc_type: object, exc_value: object, traceback: object) -> None: ...
async def __aexit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it still makes sense.

@@ -200,7 +201,9 @@ class tqdm(Generic[_T], Iterable[_T], Comparable):
def __reversed__(self) -> Iterator[_T]: ...
def __contains__(self, item: object) -> bool: ...
def __enter__(self) -> Self: ...
def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None: ...
def __exit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

@@ -9,4 +10,6 @@ class pipeline(Generic[_StrType]):
p: Pipeline[_StrType]
def __init__(self, redis_obj: Redis[_StrType]) -> None: ...
async def __aenter__(self) -> Pipeline[_StrType]: ...
async def __aexit__(self, exc_type: object, exc_value: object, traceback: object) -> None: ...
async def __aexit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

@@ -146,10 +149,14 @@ class ClusterPipeline(AbstractRedis, AbstractRedisCluster, Generic[_StrType]):
def __init__(self, client: RedisCluster[_StrType]) -> None: ...
async def initialize(self) -> Self: ...
async def __aenter__(self) -> Self: ...
async def __aexit__(self, exc_type: object, exc_value: object, traceback: object) -> None: ...
async def __aexit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

def __await__(self) -> Awaitable[Self]: ...
def __enter__(self) -> Self: ...
def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> None: ...
def __exit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
Copy link
Collaborator Author

@Avasam Avasam Feb 25, 2023

Choose a reason for hiding this comment

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

This used to be typed with objects. Please validate it's still correct.

@Avasam
Copy link
Collaborator Author

Avasam commented Feb 25, 2023

[...] I don't really have a strong opinion on exactly how strict flake8-pyi should be on this ¯\_(ツ)_/¯

Me neither, just throwing suggestions :)

Just in case, I flagged all the 3 parameters definitions that used to all be objects.

@github-actions

This comment has been minimized.

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

All the places you flagged look fine -- just one suggestion

Copy link
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

Thanks!

@github-actions
Copy link
Contributor

According to mypy_primer, this change has no effect on the checked open source code. 🤖🎉

@AlexWaygood AlexWaygood merged commit 52ec44f into python:main Feb 25, 2023
@Avasam Avasam deleted the __exit__-parameters-stubs branch February 25, 2023 22:00
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.

4 participants