From 62bf444c60ee9b0f54b46c0b5843afd605724f3f Mon Sep 17 00:00:00 2001 From: Michael Seifert Date: Mon, 23 Oct 2023 10:12:16 +0200 Subject: [PATCH] [docs] Extracted most Python code examples into separate files. This allows for auto-formatting using the usual tools and enables running the code examples as tests to ensure they work. Tests that perform network operations or require additional test dependencies have been excluded. Signed-off-by: Michael Seifert --- .../decorators/fixture_strict_mode_example.py | 14 ++ .../{decorators.rst => decorators/index.rst} | 16 +- .../reference/fixtures/event_loop_example.py | 5 + .../{fixtures.rst => fixtures/index.rst} | 8 +- docs/source/reference/index.rst | 6 +- docs/source/reference/markers.rst | 180 ------------------ .../class_scoped_loop_auto_mode_example.py | 14 ++ ...oop_custom_policies_strict_mode_example.py | 15 ++ ..._loop_custom_policy_strict_mode_example.py | 14 ++ .../class_scoped_loop_strict_mode_example.py | 16 ++ ...d_loop_with_fixture_strict_mode_example.py | 18 ++ docs/source/reference/markers/index.rst | 65 +++++++ .../module_scoped_loop_auto_mode_example.py | 23 +++ .../pytestmark_asyncio_strict_mode_example.py | 11 ++ 14 files changed, 202 insertions(+), 203 deletions(-) create mode 100644 docs/source/reference/decorators/fixture_strict_mode_example.py rename docs/source/reference/{decorators.rst => decorators/index.rst} (66%) create mode 100644 docs/source/reference/fixtures/event_loop_example.py rename docs/source/reference/{fixtures.rst => fixtures/index.rst} (90%) delete mode 100644 docs/source/reference/markers.rst create mode 100644 docs/source/reference/markers/class_scoped_loop_auto_mode_example.py create mode 100644 docs/source/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py create mode 100644 docs/source/reference/markers/class_scoped_loop_custom_policy_strict_mode_example.py create mode 100644 docs/source/reference/markers/class_scoped_loop_strict_mode_example.py create mode 100644 docs/source/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py create mode 100644 docs/source/reference/markers/index.rst create mode 100644 docs/source/reference/markers/module_scoped_loop_auto_mode_example.py create mode 100644 docs/source/reference/markers/pytestmark_asyncio_strict_mode_example.py diff --git a/docs/source/reference/decorators/fixture_strict_mode_example.py b/docs/source/reference/decorators/fixture_strict_mode_example.py new file mode 100644 index 00000000..6442c103 --- /dev/null +++ b/docs/source/reference/decorators/fixture_strict_mode_example.py @@ -0,0 +1,14 @@ +import asyncio + +import pytest_asyncio + + +@pytest_asyncio.fixture +async def async_gen_fixture(): + await asyncio.sleep(0.1) + yield "a value" + + +@pytest_asyncio.fixture(scope="module") +async def async_fixture(): + return await asyncio.sleep(0.1) diff --git a/docs/source/reference/decorators.rst b/docs/source/reference/decorators/index.rst similarity index 66% rename from docs/source/reference/decorators.rst rename to docs/source/reference/decorators/index.rst index 977ed6b8..5c96cf4b 100644 --- a/docs/source/reference/decorators.rst +++ b/docs/source/reference/decorators/index.rst @@ -3,20 +3,8 @@ Decorators ========== Asynchronous fixtures are defined just like ordinary pytest fixtures, except they should be decorated with ``@pytest_asyncio.fixture``. -.. code-block:: python3 - - import pytest_asyncio - - - @pytest_asyncio.fixture - async def async_gen_fixture(): - await asyncio.sleep(0.1) - yield "a value" - - - @pytest_asyncio.fixture(scope="module") - async def async_fixture(): - return await asyncio.sleep(0.1) +.. include:: fixture_strict_mode_example.py + :code: python All scopes are supported, but if you use a non-function scope you will need to redefine the ``event_loop`` fixture to have the same or broader scope. diff --git a/docs/source/reference/fixtures/event_loop_example.py b/docs/source/reference/fixtures/event_loop_example.py new file mode 100644 index 00000000..b5a82b62 --- /dev/null +++ b/docs/source/reference/fixtures/event_loop_example.py @@ -0,0 +1,5 @@ +import asyncio + + +def test_event_loop_fixture(event_loop): + event_loop.run_until_complete(asyncio.sleep(0)) diff --git a/docs/source/reference/fixtures.rst b/docs/source/reference/fixtures/index.rst similarity index 90% rename from docs/source/reference/fixtures.rst rename to docs/source/reference/fixtures/index.rst index d5032ba9..98fe5382 100644 --- a/docs/source/reference/fixtures.rst +++ b/docs/source/reference/fixtures/index.rst @@ -9,12 +9,8 @@ is available as the return value of this fixture or via `asyncio.get_running_loo The event loop is closed when the fixture scope ends. The fixture scope defaults to ``function`` scope. -.. code-block:: python - - def test_http_client(event_loop): - url = "http://httpbin.org/get" - resp = event_loop.run_until_complete(http_client(url)) - assert b"HTTP/1.1 200 OK" in resp +.. include:: event_loop_example.py + :code: python Note that, when using the ``event_loop`` fixture, you need to interact with the event loop using methods like ``event_loop.run_until_complete``. If you want to *await* code inside your test function, you need to write a coroutine and use it as a test function. The `asyncio <#pytest-mark-asyncio>`__ marker is used to mark coroutines that should be treated as test functions. diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index c07d0e19..5fdc2724 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -6,9 +6,9 @@ Reference :hidden: configuration - fixtures - markers - decorators + fixtures/index + markers/index + decorators/index changelog This section of the documentation provides descriptions of the individual parts provided by pytest-asyncio. diff --git a/docs/source/reference/markers.rst b/docs/source/reference/markers.rst deleted file mode 100644 index 68d5efd3..00000000 --- a/docs/source/reference/markers.rst +++ /dev/null @@ -1,180 +0,0 @@ -======= -Markers -======= - -``pytest.mark.asyncio`` -======================= -A coroutine or async generator with this marker will be treated as a test function by pytest. The marked function will be executed as an -asyncio task in the event loop provided by the ``event_loop`` fixture. - -In order to make your test code a little more concise, the pytest |pytestmark|_ -feature can be used to mark entire modules or classes with this marker. -Only test coroutines will be affected (by default, coroutines prefixed by -``test_``), so, for example, fixtures are safe to define. - -.. code-block:: python - - import asyncio - - import pytest - - # All test coroutines will be treated as marked. - pytestmark = pytest.mark.asyncio - - - async def test_example(event_loop): - """No marker!""" - await asyncio.sleep(0, loop=event_loop) - -In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted, the marker is added -automatically to *async* test functions. - - -``pytest.mark.asyncio_event_loop`` -================================== -Test classes or modules with this mark provide a class-scoped or module-scoped asyncio event loop. - -This functionality is orthogonal to the `asyncio` mark. -That means the presence of this mark does not imply that async test functions inside the class or module are collected by pytest-asyncio. -The collection happens automatically in `auto` mode. -However, if you're using strict mode, you still have to apply the `asyncio` mark to your async test functions. - -The following code example uses the `asyncio_event_loop` mark to provide a shared event loop for all tests in `TestClassScopedLoop`: - -.. code-block:: python - - import asyncio - - import pytest - - - @pytest.mark.asyncio_event_loop - class TestClassScopedLoop: - loop: asyncio.AbstractEventLoop - - @pytest.mark.asyncio - async def test_remember_loop(self): - TestClassScopedLoop.loop = asyncio.get_running_loop() - - @pytest.mark.asyncio - async def test_this_runs_in_same_loop(self): - assert asyncio.get_running_loop() is TestClassScopedLoop.loop - -In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted: - -.. code-block:: python - - import asyncio - - import pytest - - - @pytest.mark.asyncio_event_loop - class TestClassScopedLoop: - loop: asyncio.AbstractEventLoop - - async def test_remember_loop(self): - TestClassScopedLoop.loop = asyncio.get_running_loop() - - async def test_this_runs_in_same_loop(self): - assert asyncio.get_running_loop() is TestClassScopedLoop.loop - -Similarly, a module-scoped loop is provided when adding the `asyncio_event_loop` mark to the module: - -.. code-block:: python - - import asyncio - - import pytest - - pytestmark = pytest.mark.asyncio_event_loop - - loop: asyncio.AbstractEventLoop - - - async def test_remember_loop(): - global loop - loop = asyncio.get_running_loop() - - - async def test_this_runs_in_same_loop(): - global loop - assert asyncio.get_running_loop() is loop - - - class TestClassA: - async def test_this_runs_in_same_loop(self): - global loop - assert asyncio.get_running_loop() is loop - -The `asyncio_event_loop` mark supports an optional `policy` keyword argument to set the asyncio event loop policy. - -.. code-block:: python - - import asyncio - - import pytest - - - class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): - pass - - - @pytest.mark.asyncio_event_loop(policy=CustomEventLoopPolicy()) - class TestUsesCustomEventLoopPolicy: - @pytest.mark.asyncio - async def test_uses_custom_event_loop_policy(self): - assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy) - - -The ``policy`` keyword argument may also take an iterable of event loop policies. This causes tests under by the `asyncio_event_loop` mark to be parametrized with different policies: - -.. code-block:: python - - import asyncio - - import pytest - - import pytest_asyncio - - - @pytest.mark.asyncio_event_loop( - policy=[ - asyncio.DefaultEventLoopPolicy(), - uvloop.EventLoopPolicy(), - ] - ) - class TestWithDifferentLoopPolicies: - @pytest.mark.asyncio - async def test_parametrized_loop(self): - pass - - -If no explicit policy is provided, the mark will use the loop policy returned by ``asyncio.get_event_loop_policy()``. - -Fixtures and tests sharing the same `asyncio_event_loop` mark are executed in the same event loop: - -.. code-block:: python - - import asyncio - - import pytest - - import pytest_asyncio - - - @pytest.mark.asyncio_event_loop - class TestClassScopedLoop: - loop: asyncio.AbstractEventLoop - - @pytest_asyncio.fixture - async def my_fixture(self): - TestClassScopedLoop.loop = asyncio.get_running_loop() - - @pytest.mark.asyncio - async def test_runs_is_same_loop_as_fixture(self, my_fixture): - assert asyncio.get_running_loop() is TestClassScopedLoop.loop - - -.. |pytestmark| replace:: ``pytestmark`` -.. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules diff --git a/docs/source/reference/markers/class_scoped_loop_auto_mode_example.py b/docs/source/reference/markers/class_scoped_loop_auto_mode_example.py new file mode 100644 index 00000000..a839e571 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_auto_mode_example.py @@ -0,0 +1,14 @@ +import asyncio + +import pytest + + +@pytest.mark.asyncio_event_loop +class TestClassScopedLoop: + loop: asyncio.AbstractEventLoop + + async def test_remember_loop(self): + TestClassScopedLoop.loop = asyncio.get_running_loop() + + async def test_this_runs_in_same_loop(self): + assert asyncio.get_running_loop() is TestClassScopedLoop.loop diff --git a/docs/source/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py b/docs/source/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py new file mode 100644 index 00000000..85ccc3a1 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_custom_policies_strict_mode_example.py @@ -0,0 +1,15 @@ +import asyncio + +import pytest + + +@pytest.mark.asyncio_event_loop( + policy=[ + asyncio.DefaultEventLoopPolicy(), + asyncio.DefaultEventLoopPolicy(), + ] +) +class TestWithDifferentLoopPolicies: + @pytest.mark.asyncio + async def test_parametrized_loop(self): + pass diff --git a/docs/source/reference/markers/class_scoped_loop_custom_policy_strict_mode_example.py b/docs/source/reference/markers/class_scoped_loop_custom_policy_strict_mode_example.py new file mode 100644 index 00000000..b4525ca4 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_custom_policy_strict_mode_example.py @@ -0,0 +1,14 @@ +import asyncio + +import pytest + + +class CustomEventLoopPolicy(asyncio.DefaultEventLoopPolicy): + pass + + +@pytest.mark.asyncio_event_loop(policy=CustomEventLoopPolicy()) +class TestUsesCustomEventLoopPolicy: + @pytest.mark.asyncio + async def test_uses_custom_event_loop_policy(self): + assert isinstance(asyncio.get_event_loop_policy(), CustomEventLoopPolicy) diff --git a/docs/source/reference/markers/class_scoped_loop_strict_mode_example.py b/docs/source/reference/markers/class_scoped_loop_strict_mode_example.py new file mode 100644 index 00000000..c33b34b8 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_strict_mode_example.py @@ -0,0 +1,16 @@ +import asyncio + +import pytest + + +@pytest.mark.asyncio_event_loop +class TestClassScopedLoop: + loop: asyncio.AbstractEventLoop + + @pytest.mark.asyncio + async def test_remember_loop(self): + TestClassScopedLoop.loop = asyncio.get_running_loop() + + @pytest.mark.asyncio + async def test_this_runs_in_same_loop(self): + assert asyncio.get_running_loop() is TestClassScopedLoop.loop diff --git a/docs/source/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py b/docs/source/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py new file mode 100644 index 00000000..c70a4bc6 --- /dev/null +++ b/docs/source/reference/markers/class_scoped_loop_with_fixture_strict_mode_example.py @@ -0,0 +1,18 @@ +import asyncio + +import pytest + +import pytest_asyncio + + +@pytest.mark.asyncio_event_loop +class TestClassScopedLoop: + loop: asyncio.AbstractEventLoop + + @pytest_asyncio.fixture + async def my_fixture(self): + TestClassScopedLoop.loop = asyncio.get_running_loop() + + @pytest.mark.asyncio + async def test_runs_is_same_loop_as_fixture(self, my_fixture): + assert asyncio.get_running_loop() is TestClassScopedLoop.loop diff --git a/docs/source/reference/markers/index.rst b/docs/source/reference/markers/index.rst new file mode 100644 index 00000000..6c3e5253 --- /dev/null +++ b/docs/source/reference/markers/index.rst @@ -0,0 +1,65 @@ +======= +Markers +======= + +``pytest.mark.asyncio`` +======================= +A coroutine or async generator with this marker will be treated as a test function by pytest. The marked function will be executed as an +asyncio task in the event loop provided by the ``event_loop`` fixture. + +In order to make your test code a little more concise, the pytest |pytestmark|_ +feature can be used to mark entire modules or classes with this marker. +Only test coroutines will be affected (by default, coroutines prefixed by +``test_``), so, for example, fixtures are safe to define. + +.. include:: pytestmark_asyncio_strict_mode_example.py + :code: python + +In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted, the marker is added +automatically to *async* test functions. + + +``pytest.mark.asyncio_event_loop`` +================================== +Test classes or modules with this mark provide a class-scoped or module-scoped asyncio event loop. + +This functionality is orthogonal to the `asyncio` mark. +That means the presence of this mark does not imply that async test functions inside the class or module are collected by pytest-asyncio. +The collection happens automatically in `auto` mode. +However, if you're using strict mode, you still have to apply the `asyncio` mark to your async test functions. + +The following code example uses the `asyncio_event_loop` mark to provide a shared event loop for all tests in `TestClassScopedLoop`: + +.. include:: class_scoped_loop_strict_mode_example.py + :code: python + +In *auto* mode, the ``pytest.mark.asyncio`` marker can be omitted: + +.. include:: class_scoped_loop_auto_mode_example.py + :code: python + +Similarly, a module-scoped loop is provided when adding the `asyncio_event_loop` mark to the module: + +.. include:: module_scoped_loop_auto_mode_example.py + :code: python + +The `asyncio_event_loop` mark supports an optional `policy` keyword argument to set the asyncio event loop policy. + +.. include:: class_scoped_loop_custom_policy_strict_mode_example.py + :code: python + + +The ``policy`` keyword argument may also take an iterable of event loop policies. This causes tests under by the `asyncio_event_loop` mark to be parametrized with different policies: + +.. include:: class_scoped_loop_custom_policies_strict_mode_example.py + :code: python + +If no explicit policy is provided, the mark will use the loop policy returned by ``asyncio.get_event_loop_policy()``. + +Fixtures and tests sharing the same `asyncio_event_loop` mark are executed in the same event loop: + +.. include:: class_scoped_loop_with_fixture_strict_mode_example.py + :code: python + +.. |pytestmark| replace:: ``pytestmark`` +.. _pytestmark: http://doc.pytest.org/en/latest/example/markers.html#marking-whole-classes-or-modules diff --git a/docs/source/reference/markers/module_scoped_loop_auto_mode_example.py b/docs/source/reference/markers/module_scoped_loop_auto_mode_example.py new file mode 100644 index 00000000..e38bdeff --- /dev/null +++ b/docs/source/reference/markers/module_scoped_loop_auto_mode_example.py @@ -0,0 +1,23 @@ +import asyncio + +import pytest + +pytestmark = pytest.mark.asyncio_event_loop + +loop: asyncio.AbstractEventLoop + + +async def test_remember_loop(): + global loop + loop = asyncio.get_running_loop() + + +async def test_this_runs_in_same_loop(): + global loop + assert asyncio.get_running_loop() is loop + + +class TestClassA: + async def test_this_runs_in_same_loop(self): + global loop + assert asyncio.get_running_loop() is loop diff --git a/docs/source/reference/markers/pytestmark_asyncio_strict_mode_example.py b/docs/source/reference/markers/pytestmark_asyncio_strict_mode_example.py new file mode 100644 index 00000000..f1465728 --- /dev/null +++ b/docs/source/reference/markers/pytestmark_asyncio_strict_mode_example.py @@ -0,0 +1,11 @@ +import asyncio + +import pytest + +# All test coroutines will be treated as marked. +pytestmark = pytest.mark.asyncio + + +async def test_example(): + """No marker!""" + await asyncio.sleep(0)