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
Replace pytest-asyncio
marker by anyio
#1155
Conversation
b7503a5
to
7223d4d
Compare
It doesn't seem like we "already" depend on anyio, do we? We're adding it as a new dependency here. If so, would the rationale better be to prepare the ground for future trio support? |
no the anyio dep is already present |
@graingert where is it present? 🤔 |
The test suite depends on httpx depends on anyio |
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.
@euri10 Please check if you agree with this PR as well.
I've removed all the unnecessary changes from this PR, and focused on what it proposes, jfyk @graingert
tests/protocols/test_http.py
Outdated
@pytest.fixture | ||
def event_loop(): | ||
with contextlib.closing(asyncio.new_event_loop()) as loop: | ||
yield loop |
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.
@euri10 this is introduced on this PR, jfyk
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.
the corresponding pytest-asyncio fixture event_loop
is here
With this change, we don't close the loop after the yield, I'm surprised then pytest does not raise Resources not closed warnings, would be probably better to do it anyways so we dont get caught further down the road with such warning that are insanely hard to debug
and the scope is different, I'm not sure we need a loop for each test in that module, do we ?
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.
With this change, we don't close the loop after the yield ...
We do, the contextlib.closing
calls loop.close()
. Is that what you mean? 🤔
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.
What is this fixture even used for here? It feels like an anti-pattern.
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.
We do, the
contextlib.closing
callsloop.close()
. Is that what you mean? thinking
yes, well if it passes tests then the ressources might be closed properly !
What is this fixture even used for here? It feels like an anti-pattern.
iirc we use this pytest_asyncio fixture only in those "protocol" tests to create a bunch of mocks, mocked loop, mocked transport and then we create a protocol we interact with and look at its buffer,
is it an anti-pattern, idk :)
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 might be misunderstanding something, we use pytest-asyncio event_loop fixture, which is to me a real event loop
@pytest.fixture
def event_loop(request):
"""Create an instance of the default event loop for each test case."""
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
we could avoid using the MockLoop
doing something like this, is that what you mean ?
@@ -175,18 +175,15 @@ class MockTask:
@contextlib.contextmanager
def get_connected_protocol(app, protocol_cls, event_loop, **kwargs):
- loop = MockLoop(event_loop)
- asyncio._set_running_loop(loop)
transport = MockTransport()
config = Config(app=app, **kwargs)
server_state = ServerState()
- protocol = protocol_cls(config=config, server_state=server_state, _loop=loop)
+ protocol = protocol_cls(config=config, server_state=server_state, _loop=event_loop)
protocol.connection_made(transport)
try:
yield protocol
finally:
protocol.loop.close()
- asyncio._set_running_loop(None)
@pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS)
@@ -195,7 +192,8 @@ def test_get_request(protocol_cls, event_loop):
with get_connected_protocol(app, protocol_cls, event_loop) as protocol:
protocol.data_received(SIMPLE_GET_REQUEST)
- protocol.loop.run_one()
+ tasks = asyncio.all_tasks(loop=event_loop)
+ protocol.loop.run_until_complete(*tasks)
assert b"HTTP/1.1 200 OK" in protocol.transport.buffer
assert b"Hello, world" in protocol.transport.buffer
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.
The loop is closed twice here (once in the fixture, once in get_connected_protocol()
. Looks like the latter could be made into a fixture instead.
Does the protocol create tasks?
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.
yes
uvicorn/uvicorn/protocols/http/h11_impl.py
Lines 234 to 237 in 4be1085
task = self.loop.create_task(self.cycle.run_asgi(app)) | |
task.add_done_callback(self.tasks.discard) | |
self.tasks.add(task) | |
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.
@euri10 I just pushed a fix to make MockLoop wrap the currently running loop get_connected_protocol
can be made into a plain function but it makes the PR more difficult to review
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.
as promised, here's the followup: #1520
946fc6e
to
873217d
Compare
tests/protocols/test_http.py
Outdated
@pytest.fixture | ||
def event_loop(): | ||
with contextlib.closing(asyncio.new_event_loop()) as loop: | ||
yield loop |
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.
We do, the
contextlib.closing
callsloop.close()
. Is that what you mean? thinking
yes, well if it passes tests then the ressources might be closed properly !
What is this fixture even used for here? It feels like an anti-pattern.
iirc we use this pytest_asyncio fixture only in those "protocol" tests to create a bunch of mocks, mocked loop, mocked transport and then we create a protocol we interact with and look at its buffer,
is it an anti-pattern, idk :)
eaee250
to
bdfc9f1
Compare
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 like those changes
it's just test_http
whose diff is larger, but in any case I find it way cleaner than before.
I think we can give it a shot this way, all good on my side, if you prefer to separate the rework on the MockLoop from the anyio switch I'd be ok too but this way is also ok to me :)
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 👍
Looks like I had already viewed this back when it was initially opened. Changes outside test_http.py
are straightforward. I reviewed that file as well and the changes are in fact pretty systematic too, so giving my LGTM, though I haven't checked if there's nitty-gritty discussion still ongoing. Nice one!
@@ -22,8 +22,6 @@ cryptography==3.4.8 | |||
coverage==6.4 | |||
coverage-conditional-plugin==0.5.0 | |||
httpx==1.0.0b0 | |||
pytest-asyncio==0.15.1 |
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.
Should we add an explicit test dependency on anyio
, since we now explicitly import it?
anyio==3.6.1
* drop pytest-asyncio * add event_loop fixture for MockLoop tests * REVERTME: install the latest pypa toolchain * use a patched anyio that grabs an excgroup of failed tasks * setuptools_scm!!! * python -m pip * python3 isn't installed on GHA? * ah it's bash quoting * log task that raised the exception * Add missing markers * simplify fixture * avoid hacking asyncio._set_running_loop in test_http.py Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
…ncode#1155 cleanups (encode#1520) * make test_http.get_connected_protocol a plain function * remove MockLoop.is_running method this was only needed for the asyncio._set_running_loop hack
* drop pytest-asyncio * add event_loop fixture for MockLoop tests * REVERTME: install the latest pypa toolchain * use a patched anyio that grabs an excgroup of failed tasks * setuptools_scm!!! * python -m pip * python3 isn't installed on GHA? * ah it's bash quoting * log task that raised the exception * Add missing markers * simplify fixture * avoid hacking asyncio._set_running_loop in test_http.py Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
uvicorn currently depends on both pytest-asyncio and anyio in tests, so we can save a dep by swapping pytest-asyncio for anyio.