-
Notifications
You must be signed in to change notification settings - Fork 150
Added context manager mix-in classes #905
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
Conversation
Open issues:
|
src/anyio/_core/_contextmanagers.py
Outdated
|
||
This class is designed to streamline the use of context management by | ||
requiring the implementation of the `__contextmanager__` method, which | ||
should yield instances of the class itself. It then wraps this generator |
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.
Well, there's no requirement for a context manager to return Self and the wrapper shouldn't enforce that. It should accept / forward whatever type __contextmanager__
is declared to return.
On the other hand, what this wrapper does not do is to ensure that the context is not entered twice, and that really is a 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.
Well, there's no requirement for a context manager to return Self and the wrapper shouldn't enforce that. It should accept / forward whatever type contextmanager is declared to return.
I've already updated the docstring before you posted this. This version was never intended to be pushed to GH.
On the other hand, what this wrapper does not do is to ensure that the context is not entered twice, and that really is a bug.
Hm, I see what you mean. But rather than raise an error here, perhaps reentrancy should be properly supported 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.
On second thought, I don't think it's feasible to support reentrancy, as there's no way to track which generator to use in __aexit__()
.
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've thus disallowed reentrancy.
I have been doing something like this in my own codebase for ages by now. One problem, though: in a subclass, how do I "cleanly" call the superclass's
which is … meh. Anyway, whichever way we come up with needs documentation. |
This is a very good point and we need to have an answer before going forward with this. |
I think we need to start by thinking what the ideal solution would look like, and then see if we can implement that. |
Right. Ideally we'd be able to simply do |
I pushed those changes, but I encountered a different problem: How can we override the type parameter when subclassing? The pre-commit checks fail for this reason, and I don't know how to fix this. |
Huh. pyright doesn't understand this either, which frankly surprises me somewhat. |
Doesn't understand what? The parent class pins the type variable to itself, and that class does not have the |
There appears to be no way to pin the type variable to |
The problem here is that If it was allowed/defined and possible we would want to parametrize it directly with class DummyContextManager(ContextManagerMixin[Self]):
...
@contextmanager
def __contextmanager__(self) -> Generator[Self]:
... What does work here is the old workaround with a typevar with a bound as: _TSelf = TypeVar("_TSelf", bound="DummyContextManager")
class DummyContextManager(ContextManagerMixin[_TSelf]):
def __init__(self, handle_exc: bool = False) -> None:
self.started = False
self.finished = False
self.handle_exc = handle_exc
@contextmanager
def __contextmanager__(self: _TSelf) -> Generator[_TSelf]:
self.started = True
try:
yield self
except RuntimeError:
if not self.handle_exc:
raise
self.finished = True That's of course not ideal, needing a declared typevar with correct bound for every class more or less, but the case of further extending the contextmanagers overriding the returned type maybe isn't that common either? |
By "does work" do you mean you get the correct type out of |
I don't think forcing context manager authors to use a typevar just to enable proper inheritance is a good solution to the problem. Let's see what the pyright folks have to say about this. |
As in I checked out your branch but there are some issues with Looking closer I can't get it to work without explicitly passing through the leaf-class as a typevar. I suspect that getting this to work without explicit annotation when inheriting is not really possible with current typing system. (The return type of Have added it as a pull request to your branch anyway but it's not really any better except maybe the default (and maybe demonstrating that it works if you specify the return type explicitly for every child) |
What license checks? Pre-commit has no issues running over here, nor on CI.
That was my conclusion too. |
I've worked around this by adding specialized versions of the mix-in classes for yielding |
(this is very possibly older versions of tox and/or problems with this specific environment and I can sort it out and report as a real issue when/if i figure out if is some requirement that needs to be bumped) Trying to run tox for me right now results in
|
Yeah, |
Which would be weird because tox, too uses one: https://github.com/tox-dev/tox/blob/main/pyproject.toml#L19 |
Yeah but the error seems to come from setuptools and tox itself uses hatchling as the build-backend which may work fine. |
It seems like this is a setuptools issue. When I manually updated setuptools in the tox env, it resolved the problem. |
Indeed. Updating the version in
|
tox still can't find it's settings from the
|
Which Python and tox version are you running? |
It was just an old version of tox (4.5) that was picked up from outside the venv due to vscode's restoration of env-vars that messed up some things. May still not hurt to add a requires in the tox section as specified here: https://tox.wiki/en/latest/config.html#pyproject-toml-native
|
I originally skipped specifying that for precisely that reason. |
* Enforced context manager decorators on the dunder methods * Fix contextmanager typing in tests * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Add exit type for contextmanager-mixins as well --------- Co-authored-by: Alex Grönholm <[email protected]> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
class MyAsyncContextManager: | ||
async def __aenter__(self): | ||
self._exitstack = AsyncExitStack() | ||
await self._exitstack.__aenter__() |
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 this was slightly wrong it should have used pop_all()
I think it's worth including an (Async)ExitStack instruction on how to do this
|
||
@final | ||
def __enter__(self: _SupportsCtxMgr[_T_co, bool | None]) -> _T_co: | ||
# Needed for mypy to assume self still has the __cm member |
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 you can alternatively put _ContextManagerMixin__cm on the _SupportsCtxMgr protocol
self.finished = True | ||
|
||
|
||
class DummyAsyncContextManager(AsyncContextManagerMixin): |
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.
might be worth a test for a class that implements both AsyncContextManagerMixin and ContextManagerMixin
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 why? The mix-in classes have no overlap whatsoever.
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 just realized: that's the problem. One could enter the CM synchronously AND asynchronously without first exiting.
Changes
This adds two new mix-in classes:
ContextManagerMixin
andAsyncContextManagerMixin
. They're meant to enable users to write context manager classes in the same way as@contextmanager
and@asynccontextmanager
, respectively. The aim is to enable users to more easily embed cancel scopes and task groups into other context manager classes.Checklist
If this is a user-facing code change, like a bugfix or a new feature, please ensure that
you've fulfilled the following conditions (where applicable):
tests/
) added which would fail without your patchdocs/
, in case of behavior changes or newfeatures)
docs/versionhistory.rst
).If this is a trivial change, like a typo fix or a code reformatting, then you can ignore
these instructions.
Updating the changelog
If there are no entries after the last release, use
**UNRELEASED**
as the version.If, say, your patch fixes issue #123, the entry should look like this:
If there's no issue linked, just link to your pull request instead by updating the
changelog after you've created the PR.