From 05e049d976ce551b7d8dc474c54f418be3408d1e Mon Sep 17 00:00:00 2001 From: Carter Tinney Date: Mon, 12 Feb 2024 10:55:22 -0800 Subject: [PATCH] Removed 3.6 infrastructure (#1170) --- .devcontainer/Dockerfile | 2 +- .devcontainer/devcontainer.json | 2 +- .../azure/iot/device/common/async_adapter.py | 6 +- .../azure/iot/device/common/asyncio_compat.py | 61 -------- requirements_test.txt | 4 +- samples/async-edge-scenarios/README.md | 11 -- .../invoke_method_on_module.py | 5 - samples/async-edge-scenarios/send_message.py | 5 - .../send_message_downstream.py | 5 - .../send_message_to_output.py | 5 - .../update_twin_reported_properties.py | 5 - samples/async-hub-scenarios/README.md | 11 -- samples/async-hub-scenarios/get_twin.py | 5 - .../provision_symmetric_key.py | 5 - .../provision_symmetric_key_group.py | 5 - .../provision_symmetric_key_with_payload.py | 5 - samples/async-hub-scenarios/provision_x509.py | 5 - .../receive_direct_method.py | 5 - .../async-hub-scenarios/receive_message.py | 5 - .../receive_message_x509.py | 5 - .../receive_twin_desired_properties_patch.py | 5 - samples/async-hub-scenarios/send_message.py | 5 - .../send_message_over_websockets.py | 5 - .../send_message_via_module_x509.py | 5 - .../send_message_via_proxy.py | 5 - .../async-hub-scenarios/send_message_x509.py | 5 - .../update_twin_reported_properties.py | 5 - .../use_custom_sastoken.py | 5 - samples/pnp/simple_thermostat.py | 5 - .../pnp/temp_controller_with_thermostats.py | 5 - samples/simple_send_message.py | 5 - tests/unit/common/test_asyncio_compat.py | 146 ------------------ tests/unit/iothub/aio/test_async_clients.py | 11 +- 33 files changed, 9 insertions(+), 365 deletions(-) delete mode 100644 azure-iot-device/azure/iot/device/common/asyncio_compat.py delete mode 100644 tests/unit/common/test_asyncio_compat.py diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 9308a4157..173a06a0c 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,6 +1,6 @@ # See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.192.0/containers/python-3/.devcontainer/base.Dockerfile -# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6 +# [Choice] Python version: 3, 3.9, 3.8, 3.7 ARG VARIANT="3.9" FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 812f68cc4..545834d34 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,7 @@ "dockerfile": "Dockerfile", "context": "..", "args": { - // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9 + // Update 'VARIANT' to pick a Python version: 3, 3.7, 3.8, 3.9 "VARIANT": "3", // Options "NODE_VERSION": "lts/*" diff --git a/azure-iot-device/azure/iot/device/common/async_adapter.py b/azure-iot-device/azure/iot/device/common/async_adapter.py index ca232822a..19d15fe91 100644 --- a/azure-iot-device/azure/iot/device/common/async_adapter.py +++ b/azure-iot-device/azure/iot/device/common/async_adapter.py @@ -5,10 +5,10 @@ # -------------------------------------------------------------------------- """This module contains tools for adapting sync code for use in async coroutines.""" +import asyncio import functools import logging import traceback -import azure.iot.device.common.asyncio_compat as asyncio_compat logger = logging.getLogger(__name__) @@ -25,7 +25,7 @@ def emulate_async(fn): @functools.wraps(fn) async def async_fn_wrapper(*args, **kwargs): - loop = asyncio_compat.get_running_loop() + loop = asyncio.get_running_loop() # Run fn in default ThreadPoolExecutor (CPU * 5 threads) return await loop.run_in_executor(None, functools.partial(fn, *args, **kwargs)) @@ -44,7 +44,7 @@ class AwaitableCallback(object): if return_arg_name and not isinstance(return_arg_name, str): raise TypeError("internal error: return_arg_name must be a string") - loop = asyncio_compat.get_running_loop() + loop = asyncio.get_running_loop() self.future = loop.create_future() def wrapping_callback(*args, **kwargs): diff --git a/azure-iot-device/azure/iot/device/common/asyncio_compat.py b/azure-iot-device/azure/iot/device/common/asyncio_compat.py deleted file mode 100644 index 0ab3f4b21..000000000 --- a/azure-iot-device/azure/iot/device/common/asyncio_compat.py +++ /dev/null @@ -1,61 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -"""This module contains compatibility tools for bridging different versions of asyncio""" - -import asyncio -import sys - - -def get_running_loop(): - """Gets the currently running event loop - - Uses asyncio.get_running_loop() if available (Python 3.7+) or a back-ported - version of the same function in 3.6. - """ - try: - loop = asyncio.get_running_loop() - except AttributeError: - loop = asyncio._get_running_loop() - if loop is None: - raise RuntimeError("no running event loop") - return loop - - -def create_task(coro): - """Creates a Task object. - - If available (Python 3.7+), use asyncio.create_task, which is preferred as it is - more specific for the goal of immediately scheduling a task from a coroutine. If - not available, use the more general purpose asyncio.ensure_future. - - :returns: A new Task object. - """ - try: - task = asyncio.create_task(coro) - except AttributeError: - task = asyncio.ensure_future(coro) - return task - - -def run(coro): - """Execute the coroutine coro and return the result. - - It creates a new event loop and closes it at the end. - Cannot be called when another asyncio event loop is running in the same thread. - - If available (Python 3.7+) use asyncio.run. If not available, use a custom implementation - that achieves the same thing - """ - if sys.version_info >= (3, 7): - return asyncio.run(coro) - else: - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) - try: - return loop.run_until_complete(coro) - finally: - loop.close() - asyncio.set_event_loop(None) diff --git a/requirements_test.txt b/requirements_test.txt index 259deff86..ea6d086a5 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,6 +1,6 @@ -pytest < 8.0.0 # lazy_fixture currently broken in 8 +pytest < 8.0.0 +pytest-asyncio <= 0.16 pytest-mock -pytest-asyncio <= 0.16 # Can remove this once Python 3.6 support is dropped pytest-testdox>=1.1.1 pytest-cov pytest-timeout diff --git a/samples/async-edge-scenarios/README.md b/samples/async-edge-scenarios/README.md index 59f6644ba..8a420b65d 100644 --- a/samples/async-edge-scenarios/README.md +++ b/samples/async-edge-scenarios/README.md @@ -4,17 +4,6 @@ This directory contains samples showing how to use the various features of Azure **Please note** that IoT Edge solutions are scoped to Linux containers and devices, documented [here](https://docs.microsoft.com/en-us/azure/iot-edge/tutorial-python-module#solution-scope). Please see [this blog post](https://techcommunity.microsoft.com/t5/internet-of-things/linux-modules-with-azure-iot-edge-on-windows-10-iot-enterprise/ba-p/1407066) to learn more about using Linux containers for IoT Edge on Windows devices. -**These samples are written to run in Python 3.7+**, but can be made to work with Python 3.6 with a slight modification as noted in each sample: - -```python -if __name__ == "__main__": - asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() -``` In order to use these samples, they **must** be run from inside an Edge container. diff --git a/samples/async-edge-scenarios/invoke_method_on_module.py b/samples/async-edge-scenarios/invoke_method_on_module.py index 15c7b5446..e2e76c33a 100644 --- a/samples/async-edge-scenarios/invoke_method_on_module.py +++ b/samples/async-edge-scenarios/invoke_method_on_module.py @@ -34,8 +34,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-edge-scenarios/send_message.py b/samples/async-edge-scenarios/send_message.py index b3e36a596..a1c8acb47 100644 --- a/samples/async-edge-scenarios/send_message.py +++ b/samples/async-edge-scenarios/send_message.py @@ -37,8 +37,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-edge-scenarios/send_message_downstream.py b/samples/async-edge-scenarios/send_message_downstream.py index 3620e170d..d484d0334 100644 --- a/samples/async-edge-scenarios/send_message_downstream.py +++ b/samples/async-edge-scenarios/send_message_downstream.py @@ -51,8 +51,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-edge-scenarios/send_message_to_output.py b/samples/async-edge-scenarios/send_message_to_output.py index 45bd4c223..9656ce656 100644 --- a/samples/async-edge-scenarios/send_message_to_output.py +++ b/samples/async-edge-scenarios/send_message_to_output.py @@ -38,8 +38,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-edge-scenarios/update_twin_reported_properties.py b/samples/async-edge-scenarios/update_twin_reported_properties.py index 72b66c1d1..ece660a75 100644 --- a/samples/async-edge-scenarios/update_twin_reported_properties.py +++ b/samples/async-edge-scenarios/update_twin_reported_properties.py @@ -27,8 +27,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/README.md b/samples/async-hub-scenarios/README.md index 111275aac..b99c402d4 100644 --- a/samples/async-hub-scenarios/README.md +++ b/samples/async-hub-scenarios/README.md @@ -2,17 +2,6 @@ This directory contains samples showing how to use the various features of Azure IoT Hub Device SDK with the Azure IoT Hub. -**These samples are written to run in Python 3.7+**, but can be made to work with Python 3.6 with a slight modification as noted in each sample: - -```python -if __name__ == "__main__": - asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() -``` ## Included Samples diff --git a/samples/async-hub-scenarios/get_twin.py b/samples/async-hub-scenarios/get_twin.py index cf521044b..12b89a773 100644 --- a/samples/async-hub-scenarios/get_twin.py +++ b/samples/async-hub-scenarios/get_twin.py @@ -27,8 +27,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/provision_symmetric_key.py b/samples/async-hub-scenarios/provision_symmetric_key.py index 7b99f458b..381a43558 100644 --- a/samples/async-hub-scenarios/provision_symmetric_key.py +++ b/samples/async-hub-scenarios/provision_symmetric_key.py @@ -60,8 +60,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/provision_symmetric_key_group.py b/samples/async-hub-scenarios/provision_symmetric_key_group.py index 904625c1a..f177b15c0 100644 --- a/samples/async-hub-scenarios/provision_symmetric_key_group.py +++ b/samples/async-hub-scenarios/provision_symmetric_key_group.py @@ -131,8 +131,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/provision_symmetric_key_with_payload.py b/samples/async-hub-scenarios/provision_symmetric_key_with_payload.py index a56a45343..ab7988214 100644 --- a/samples/async-hub-scenarios/provision_symmetric_key_with_payload.py +++ b/samples/async-hub-scenarios/provision_symmetric_key_with_payload.py @@ -70,8 +70,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/provision_x509.py b/samples/async-hub-scenarios/provision_x509.py index 5312c2392..9a909a44a 100644 --- a/samples/async-hub-scenarios/provision_x509.py +++ b/samples/async-hub-scenarios/provision_x509.py @@ -66,8 +66,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/receive_direct_method.py b/samples/async-hub-scenarios/receive_direct_method.py index c16f21237..f206de64f 100644 --- a/samples/async-hub-scenarios/receive_direct_method.py +++ b/samples/async-hub-scenarios/receive_direct_method.py @@ -64,8 +64,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/receive_message.py b/samples/async-hub-scenarios/receive_message.py index 5c966acd1..5de63d50f 100644 --- a/samples/async-hub-scenarios/receive_message.py +++ b/samples/async-hub-scenarios/receive_message.py @@ -52,8 +52,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/receive_message_x509.py b/samples/async-hub-scenarios/receive_message_x509.py index bd01e92d6..7bc87226d 100644 --- a/samples/async-hub-scenarios/receive_message_x509.py +++ b/samples/async-hub-scenarios/receive_message_x509.py @@ -60,8 +60,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/receive_twin_desired_properties_patch.py b/samples/async-hub-scenarios/receive_twin_desired_properties_patch.py index e3c7e05b9..1e2574dc2 100644 --- a/samples/async-hub-scenarios/receive_twin_desired_properties_patch.py +++ b/samples/async-hub-scenarios/receive_twin_desired_properties_patch.py @@ -47,8 +47,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/send_message.py b/samples/async-hub-scenarios/send_message.py index 236fe2b01..c903d8f2c 100644 --- a/samples/async-hub-scenarios/send_message.py +++ b/samples/async-hub-scenarios/send_message.py @@ -43,8 +43,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/send_message_over_websockets.py b/samples/async-hub-scenarios/send_message_over_websockets.py index b563ae672..43b8bd3d1 100644 --- a/samples/async-hub-scenarios/send_message_over_websockets.py +++ b/samples/async-hub-scenarios/send_message_over_websockets.py @@ -29,8 +29,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/send_message_via_module_x509.py b/samples/async-hub-scenarios/send_message_via_module_x509.py index 6d6e1ee8c..39eb5afb2 100644 --- a/samples/async-hub-scenarios/send_message_via_module_x509.py +++ b/samples/async-hub-scenarios/send_message_via_module_x509.py @@ -55,8 +55,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/send_message_via_proxy.py b/samples/async-hub-scenarios/send_message_via_proxy.py index ab25b00e9..93bd66514 100644 --- a/samples/async-hub-scenarios/send_message_via_proxy.py +++ b/samples/async-hub-scenarios/send_message_via_proxy.py @@ -49,8 +49,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/send_message_x509.py b/samples/async-hub-scenarios/send_message_x509.py index cbeba73b7..364e6003c 100644 --- a/samples/async-hub-scenarios/send_message_x509.py +++ b/samples/async-hub-scenarios/send_message_x509.py @@ -53,8 +53,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/update_twin_reported_properties.py b/samples/async-hub-scenarios/update_twin_reported_properties.py index 507300e43..76e5b77c1 100644 --- a/samples/async-hub-scenarios/update_twin_reported_properties.py +++ b/samples/async-hub-scenarios/update_twin_reported_properties.py @@ -28,8 +28,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/async-hub-scenarios/use_custom_sastoken.py b/samples/async-hub-scenarios/use_custom_sastoken.py index 656d7f5f1..2d8b7f5d0 100644 --- a/samples/async-hub-scenarios/use_custom_sastoken.py +++ b/samples/async-hub-scenarios/use_custom_sastoken.py @@ -68,8 +68,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/pnp/simple_thermostat.py b/samples/pnp/simple_thermostat.py index b935d7a6b..099fbfd66 100644 --- a/samples/pnp/simple_thermostat.py +++ b/samples/pnp/simple_thermostat.py @@ -335,8 +335,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/pnp/temp_controller_with_thermostats.py b/samples/pnp/temp_controller_with_thermostats.py index 79310d641..9d8966431 100644 --- a/samples/pnp/temp_controller_with_thermostats.py +++ b/samples/pnp/temp_controller_with_thermostats.py @@ -417,8 +417,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/samples/simple_send_message.py b/samples/simple_send_message.py index 5d7deda1d..fac8583d4 100644 --- a/samples/simple_send_message.py +++ b/samples/simple_send_message.py @@ -30,8 +30,3 @@ async def main(): if __name__ == "__main__": asyncio.run(main()) - - # If using Python 3.6 use the following code instead of asyncio.run(main()): - # loop = asyncio.get_event_loop() - # loop.run_until_complete(main()) - # loop.close() diff --git a/tests/unit/common/test_asyncio_compat.py b/tests/unit/common/test_asyncio_compat.py deleted file mode 100644 index 6b3890e4f..000000000 --- a/tests/unit/common/test_asyncio_compat.py +++ /dev/null @@ -1,146 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- - -import pytest -import asyncio -import sys -import logging -from azure.iot.device.common import asyncio_compat - -logging.basicConfig(level=logging.DEBUG) - -pytestmark = pytest.mark.asyncio - - -@pytest.mark.describe("get_running_loop()") -class TestGetRunningLoop(object): - @pytest.mark.it("Returns the currently running Event Loop in Python 3.7 or higher") - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Requires Python 3.7+") - async def test_returns_currently_running_event_loop_(self, mocker, event_loop): - spy_get_running_loop = mocker.spy(asyncio, "get_running_loop") - result = asyncio_compat.get_running_loop() - assert result == event_loop - assert spy_get_running_loop.call_count == 1 - assert spy_get_running_loop.call_args == mocker.call() - - @pytest.mark.it( - "Raises a RuntimeError if there is no running Event Loop in Python 3.7 or higher" - ) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Requires Python 3.7+") - async def test_raises_runtime_error_if_no_running_event_loop(self, mocker): - mocker.patch.object(asyncio, "get_running_loop", side_effect=RuntimeError) - with pytest.raises(RuntimeError): - asyncio_compat.get_running_loop() - - @pytest.mark.it("Returns the currently running Event Loop in Python 3.6") - @pytest.mark.skipif(sys.version_info >= (3, 7), reason="Requires Python 3.6") - async def test_returns_currently_running_event_loop_py36orless_compat(self, mocker, event_loop): - spy_get_event_loop = mocker.spy(asyncio, "_get_running_loop") - result = asyncio_compat.get_running_loop() - assert result == event_loop - assert spy_get_event_loop.call_count == 1 - assert spy_get_event_loop.call_args == mocker.call() - - @pytest.mark.it("Raises a RuntimeError if there is no running Event Loop in Python 3.6") - @pytest.mark.skipif(sys.version_info >= (3, 7), reason="Requires Python 3.6") - async def test_raises_runtime_error_if_no_running_event_loop_py36orless_compat(self, mocker): - mocker.patch.object(asyncio, "_get_running_loop", return_value=None) - with pytest.raises(RuntimeError): - asyncio_compat.get_running_loop() - - -@pytest.mark.describe("create_task()") -class TestCreateTask(object): - @pytest.fixture - def dummy_coroutine(self): - async def coro(): - return - - return coro - - @pytest.mark.it( - "Returns a Task that wraps a given coroutine, and schedules its execution, in Python 3.7 or higher" - ) - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Requires Python 3.7+") - async def test_returns_task_wrapping_given_coroutine(self, mocker, dummy_coroutine): - spy_create_task = mocker.spy(asyncio, "create_task") - coro_obj = dummy_coroutine() - result = asyncio_compat.create_task(coro_obj) - assert isinstance(result, asyncio.Task) - assert spy_create_task.call_count == 1 - assert spy_create_task.call_args == mocker.call(coro_obj) - - @pytest.mark.it( - "Returns a Task that wraps a given coroutine, and schedules its execution, in Python 3.6" - ) - @pytest.mark.skipif(sys.version_info >= (3, 7), reason="Requires Python 3.6") - async def test_returns_task_wrapping_given_coroutine_py36orless_compat( - self, mocker, dummy_coroutine - ): - spy_ensure_future = mocker.spy(asyncio, "ensure_future") - coro_obj = dummy_coroutine() - result = asyncio_compat.create_task(coro_obj) - assert isinstance(result, asyncio.Task) - assert spy_ensure_future.call_count == 1 - assert spy_ensure_future.call_args == mocker.call(coro_obj) - - -@pytest.mark.describe("run()") -class TestRun(object): - @pytest.mark.it("Runs the given coroutine on a new event loop in Python 3.7 or higher") - @pytest.mark.skipif(sys.version_info < (3, 7), reason="Requires Python 3.7+") - def test_run_37_or_greater(self, mocker): - mock_asyncio_run = mocker.patch.object(asyncio, "run") - mock_coro = mocker.MagicMock() - result = asyncio_compat.run(mock_coro) - assert mock_asyncio_run.call_count == 1 - assert mock_asyncio_run.call_args == mocker.call(mock_coro) - assert result == mock_asyncio_run.return_value - - @pytest.mark.it("Runs the given coroutine on a new event loop in Python 3.6") - @pytest.mark.skipif(sys.version_info >= (3, 7), reason="Requires Python 3.6") - def test_run_36orless_compat(self, mocker): - mock_new_event_loop = mocker.patch.object(asyncio, "new_event_loop") - mock_set_event_loop = mocker.patch.object(asyncio, "set_event_loop") - mock_loop = mock_new_event_loop.return_value - - mock_coro = mocker.MagicMock() - result = asyncio_compat.run(mock_coro) - - # New event loop was created and set - assert mock_new_event_loop.call_count == 1 - assert mock_new_event_loop.call_args == mocker.call() - assert mock_set_event_loop.call_count == 2 # (second time at the end) - assert mock_set_event_loop.call_args_list[0] == mocker.call(mock_loop) - # Coroutine was run on the event loop, with the result returned - assert mock_loop.run_until_complete.call_count == 1 - assert mock_loop.run_until_complete.call_args == mocker.call(mock_coro) - assert result == mock_loop.run_until_complete.return_value - # Loop was closed after completion - assert mock_loop.close.call_count == 1 - assert mock_loop.close.call_args == mocker.call() - # Event loop was set back to None - assert mock_set_event_loop.call_args_list[1] == mocker.call(None) - - @pytest.mark.it( - "Closes the event loop and resets to None, even if an error occurs running the coroutine, in Python 3.6" - ) - @pytest.mark.skipif(sys.version_info >= (3, 7), reason="Requires Python 3.6") - def test_error_running_36orless_compat(self, mocker, arbitrary_exception): - # NOTE: This test is not necessary for 3.7 because asyncio.run() does this for us - mock_new_event_loop = mocker.patch.object(asyncio, "new_event_loop") - mock_set_event_loop = mocker.patch.object(asyncio, "set_event_loop") - mock_loop = mock_new_event_loop.return_value - mock_loop.run_until_complete.side_effect = arbitrary_exception - - mock_coro = mocker.MagicMock() - with pytest.raises(type(arbitrary_exception)): - asyncio_compat.run(mock_coro) - - assert mock_loop.close.call_count == 1 - assert mock_set_event_loop.call_count == 2 # Once set, once to unset - assert mock_set_event_loop.call_args_list[0] == mocker.call(mock_loop) - assert mock_set_event_loop.call_args_list[1] == mocker.call(None) diff --git a/tests/unit/iothub/aio/test_async_clients.py b/tests/unit/iothub/aio/test_async_clients.py index 299222883..9ca3df1a9 100644 --- a/tests/unit/iothub/aio/test_async_clients.py +++ b/tests/unit/iothub/aio/test_async_clients.py @@ -6,7 +6,6 @@ import logging import pytest -import pytest_asyncio import asyncio import time import urllib @@ -47,12 +46,6 @@ from ..shared_client_tests import ( pytestmark = pytest.mark.asyncio logging.basicConfig(level=logging.DEBUG) -# Python 3.6 only supports pytest_asyncio==0.16.0 which doesn't have pytest_asyncio.fixture. -try: - asyncio_fixture = pytest_asyncio.fixture -except AttributeError: - asyncio_fixture = pytest.fixture - async def create_completed_future(result=None): f = asyncio.Future() @@ -1259,7 +1252,7 @@ class IoTHubDeviceClientTestsConfig(object): def client_class(self): return IoTHubDeviceClient - @asyncio_fixture + @pytest.fixture async def client(self, mqtt_pipeline, http_pipeline): """This client automatically resolves callbacks sent to the pipeline. It should be used for the majority of tests. @@ -1781,7 +1774,7 @@ class IoTHubModuleClientTestsConfig(object): def client_class(self): return IoTHubModuleClient - @asyncio_fixture + @pytest.fixture async def client(self, mqtt_pipeline, http_pipeline): """This client automatically resolves callbacks sent to the pipeline. It should be used for the majority of tests.