diff --git a/docs/api/juju.loop.rst b/docs/api/juju.loop.rst deleted file mode 100644 index 4f175e923..000000000 --- a/docs/api/juju.loop.rst +++ /dev/null @@ -1,13 +0,0 @@ -juju.loop -========= - -.. rubric:: Summary - -.. automembersummary:: juju.loop - -.. rubric:: Reference - -.. automodule:: juju.loop - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/api/modules.rst b/docs/api/modules.rst index fecaf3e9a..f7057dd4f 100644 --- a/docs/api/modules.rst +++ b/docs/api/modules.rst @@ -2,7 +2,6 @@ Public APIs =========== It is recommended that you start with :doc:`juju.model` or :doc:`juju.controller`. -If you need helpers to manage the asyncio loop, try :doc:`juju.loop`. .. toctree:: @@ -16,7 +15,6 @@ If you need helpers to manage the asyncio loop, try :doc:`juju.loop`. juju.errors juju.exceptions juju.juju - juju.loop juju.machine juju.model juju.placement diff --git a/examples/action.py b/examples/action.py index e1023377b..1e3866914 100644 --- a/examples/action.py +++ b/examples/action.py @@ -10,9 +10,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.model import Model @@ -50,4 +50,4 @@ async def main(): logging.basicConfig(level=logging.DEBUG) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/add_k8s.py b/examples/add_k8s.py index fdd67ac6d..cb72408a3 100644 --- a/examples/add_k8s.py +++ b/examples/add_k8s.py @@ -10,13 +10,13 @@ """ +import asyncio import base64 import logging import os import yaml -from juju import jasyncio from juju.client import client from juju.controller import Controller @@ -58,4 +58,4 @@ async def main(): logging.basicConfig(level=logging.DEBUG) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/add_machine.py b/examples/add_machine.py index 589fcf5ad..d06e02033 100755 --- a/examples/add_machine.py +++ b/examples/add_machine.py @@ -11,9 +11,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.model import Model MB = 1 @@ -69,4 +69,4 @@ async def main(): ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/add_model.py b/examples/add_model.py index 892e1c6e1..98ee7d6bc 100644 --- a/examples/add_model.py +++ b/examples/add_model.py @@ -13,7 +13,7 @@ import uuid from logging import getLogger -from juju import jasyncio, utils +from juju import utils from juju.controller import Controller LOG = getLogger(__name__) @@ -65,4 +65,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/add_secrets_backend.py b/examples/add_secrets_backend.py index 55ed4d596..264b6234b 100644 --- a/examples/add_secrets_backend.py +++ b/examples/add_secrets_backend.py @@ -1,9 +1,10 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio + import hvac -from juju import jasyncio from juju.model import Model @@ -75,4 +76,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/allwatcher.py b/examples/allwatcher.py index cc74f4f94..f628711d0 100644 --- a/examples/allwatcher.py +++ b/examples/allwatcher.py @@ -10,9 +10,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.client import client from juju.model import Model @@ -34,4 +34,4 @@ async def watch(): ws_logger.setLevel(logging.INFO) # Run loop until the process is manually stopped (watch will loop # forever). - jasyncio.run(watch()) + asyncio.run(watch()) diff --git a/examples/charmhub_deploy_k8s.py b/examples/charmhub_deploy_k8s.py index cac858522..0ea4106c6 100644 --- a/examples/charmhub_deploy_k8s.py +++ b/examples/charmhub_deploy_k8s.py @@ -9,7 +9,8 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -40,4 +41,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/charmhub_deploy_machine.py b/examples/charmhub_deploy_machine.py index b48811119..fb688e19b 100644 --- a/examples/charmhub_deploy_machine.py +++ b/examples/charmhub_deploy_machine.py @@ -9,7 +9,8 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -37,4 +38,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/charmhub_find.py b/examples/charmhub_find.py index 2010962bb..b9e4f6927 100644 --- a/examples/charmhub_find.py +++ b/examples/charmhub_find.py @@ -5,9 +5,9 @@ repository for charms. """ +import asyncio import logging -from juju import jasyncio from juju.model import Model log = logging.getLogger(__name__) @@ -33,4 +33,4 @@ async def main(): if __name__ == "__main__": logging.basicConfig(level=logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/charmhub_info.py b/examples/charmhub_info.py index 11f563893..bf89bd151 100644 --- a/examples/charmhub_info.py +++ b/examples/charmhub_info.py @@ -5,9 +5,9 @@ repository for information about a given charm. """ +import asyncio import logging -from juju import jasyncio from juju.model import Model log = logging.getLogger(__name__) @@ -29,4 +29,4 @@ async def main(): if __name__ == "__main__": logging.basicConfig(level=logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/cloud.py b/examples/cloud.py index d818df9c6..c972a6963 100644 --- a/examples/cloud.py +++ b/examples/cloud.py @@ -9,9 +9,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.controller import Controller @@ -28,4 +28,4 @@ async def main(): logging.basicConfig(level=logging.DEBUG) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/clouds.py b/examples/clouds.py index 1dee35b16..21b0b3787 100644 --- a/examples/clouds.py +++ b/examples/clouds.py @@ -9,9 +9,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.controller import Controller @@ -28,4 +28,4 @@ async def main(): logging.basicConfig(level=logging.DEBUG) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/config.py b/examples/config.py index b6388b938..ca8f523f4 100644 --- a/examples/config.py +++ b/examples/config.py @@ -9,9 +9,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.model import Model log = logging.getLogger(__name__) @@ -59,4 +59,4 @@ async def main(): # logging.basicConfig(level=logging.DEBUG) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/connect_current_model.py b/examples/connect_current_model.py index c8996b1d0..6c9db6398 100644 --- a/examples/connect_current_model.py +++ b/examples/connect_current_model.py @@ -5,9 +5,9 @@ and prints the number of applications deployed to it. """ +import asyncio import logging -from juju import jasyncio from juju.model import Model log = logging.getLogger(__name__) @@ -27,4 +27,4 @@ async def main(): if __name__ == "__main__": logging.basicConfig(level=logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/controller.py b/examples/controller.py index d269a16a2..d377b40fc 100644 --- a/examples/controller.py +++ b/examples/controller.py @@ -11,9 +11,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.controller import Controller @@ -41,4 +41,4 @@ async def main(): logging.basicConfig(level=logging.DEBUG) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/credential.py b/examples/credential.py index 257462736..cb76303f0 100644 --- a/examples/credential.py +++ b/examples/credential.py @@ -1,9 +1,9 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio import sys -from juju import jasyncio from juju.controller import Controller @@ -47,4 +47,4 @@ async def main(cloud_name, credential_name): if __name__ == "__main__": assert len(sys.argv) > 2, "Please provide a cloud and credential name" - jasyncio.run(main(sys.argv[1], sys.argv[2])) + asyncio.run(main(sys.argv[1], sys.argv[2])) diff --git a/examples/crossmodel.py b/examples/crossmodel.py index 226ef4e77..736b86e79 100644 --- a/examples/crossmodel.py +++ b/examples/crossmodel.py @@ -11,10 +11,10 @@ """ +import asyncio import tempfile from logging import getLogger -from juju import jasyncio from juju.controller import Controller log = getLogger(__name__) @@ -83,4 +83,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/crossmodel_bundle.py b/examples/crossmodel_bundle.py index 06e85d4d9..1113a6089 100644 --- a/examples/crossmodel_bundle.py +++ b/examples/crossmodel_bundle.py @@ -13,11 +13,11 @@ """ +import asyncio import time from logging import getLogger from pathlib import Path -from juju import jasyncio from juju.controller import Controller log = getLogger(__name__) @@ -87,4 +87,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/crossmodel_controller.py b/examples/crossmodel_controller.py index ec12d02be..69a7b3c3d 100644 --- a/examples/crossmodel_controller.py +++ b/examples/crossmodel_controller.py @@ -15,10 +15,10 @@ 10. Destroys models and disconnects """ +import asyncio import tempfile from logging import getLogger -from juju import jasyncio from juju.controller import Controller log = getLogger(__name__) @@ -92,4 +92,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/crossmodel_relation.py b/examples/crossmodel_relation.py index 80ffb3a14..c1417993c 100644 --- a/examples/crossmodel_relation.py +++ b/examples/crossmodel_relation.py @@ -13,11 +13,11 @@ """ +import asyncio import tempfile import time from logging import getLogger -from juju import jasyncio from juju.controller import Controller log = getLogger(__name__) @@ -106,4 +106,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/debug-log.py b/examples/debug-log.py index b7e712063..3761088ee 100644 --- a/examples/debug-log.py +++ b/examples/debug-log.py @@ -3,7 +3,8 @@ """This example demonstrate how debug-log works""" -from juju import jasyncio +import asyncio + from juju.model import Model @@ -34,4 +35,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy.py b/examples/deploy.py index 831ae85d2..449c1ff23 100644 --- a/examples/deploy.py +++ b/examples/deploy.py @@ -9,7 +9,8 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -39,4 +40,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy_bundle.py b/examples/deploy_bundle.py index dad615eec..06703c809 100644 --- a/examples/deploy_bundle.py +++ b/examples/deploy_bundle.py @@ -9,7 +9,8 @@ """ -from juju import jasyncio +import asyncio + from juju.controller import Controller @@ -59,4 +60,4 @@ async def deploy_and_wait_for_bundle(model, url, channel=None): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy_bundle_charmhub.py b/examples/deploy_bundle_charmhub.py index f3f63b154..5656e1394 100644 --- a/examples/deploy_bundle_charmhub.py +++ b/examples/deploy_bundle_charmhub.py @@ -7,7 +7,8 @@ 3. Destroys the unit and application """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -33,4 +34,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy_bundle_with_trust.py b/examples/deploy_bundle_with_trust.py index 0a122e021..4ed57ed4b 100644 --- a/examples/deploy_bundle_with_trust.py +++ b/examples/deploy_bundle_with_trust.py @@ -9,7 +9,8 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -44,4 +45,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy_constraints.py b/examples/deploy_constraints.py index dd876c58a..a5dad040f 100644 --- a/examples/deploy_constraints.py +++ b/examples/deploy_constraints.py @@ -9,7 +9,8 @@ """ -from juju import jasyncio +import asyncio + from juju.controller import Controller @@ -35,4 +36,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy_local_big_k8s_bundle.py b/examples/deploy_local_big_k8s_bundle.py index 447206ff5..8e421a508 100644 --- a/examples/deploy_local_big_k8s_bundle.py +++ b/examples/deploy_local_big_k8s_bundle.py @@ -9,7 +9,8 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -33,4 +34,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy_local_bundle_with_resources.py b/examples/deploy_local_bundle_with_resources.py index c35382c5c..9eb51fc05 100644 --- a/examples/deploy_local_bundle_with_resources.py +++ b/examples/deploy_local_bundle_with_resources.py @@ -9,7 +9,8 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -44,4 +45,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy_local_file_resource.py b/examples/deploy_local_file_resource.py index 3aa2b5ccf..aa4f812ab 100644 --- a/examples/deploy_local_file_resource.py +++ b/examples/deploy_local_file_resource.py @@ -10,9 +10,9 @@ """ +import asyncio from pathlib import Path -from juju import jasyncio from juju.model import Model @@ -49,4 +49,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy_local_resource.py b/examples/deploy_local_resource.py index cbe086a40..06dbb135d 100644 --- a/examples/deploy_local_resource.py +++ b/examples/deploy_local_resource.py @@ -10,9 +10,9 @@ """ +import asyncio from pathlib import Path -from juju import jasyncio from juju.model import Model @@ -46,4 +46,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/deploy_with_revision.py b/examples/deploy_with_revision.py index f7ce3b11d..36f28adb4 100644 --- a/examples/deploy_with_revision.py +++ b/examples/deploy_with_revision.py @@ -1,7 +1,8 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. -from juju import jasyncio +import asyncio + from juju.model import Model @@ -33,4 +34,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/expose-application.py b/examples/expose-application.py index b43a2c679..aa3095cf8 100644 --- a/examples/expose-application.py +++ b/examples/expose-application.py @@ -11,7 +11,8 @@ NOTE: this test must be run against a 2.9 controller. """ -from juju import jasyncio +import asyncio + from juju.application import ExposedEndpoint from juju.model import Model @@ -81,4 +82,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/formatted_status.py b/examples/formatted_status.py index d772d7923..3f39610d7 100644 --- a/examples/formatted_status.py +++ b/examples/formatted_status.py @@ -6,12 +6,12 @@ check examples/fullstatus.py """ +import asyncio import logging import sys import tempfile from logging import getLogger -from juju import jasyncio from juju.model import Model from juju.status import formatted_status @@ -30,7 +30,7 @@ async def main(): channel="stable", ) - await jasyncio.sleep(10) + await asyncio.sleep(10) tmp = tempfile.NamedTemporaryFile(delete=False) # noqa: SIM115 LOG.info("status dumped to %s", tmp.name) with open(tmp.name, "w") as f: @@ -40,10 +40,10 @@ async def main(): # await formatted_status(model, target=sys.stdout) await formatted_status(model, target=f) f.write("-----------\n") - await jasyncio.sleep(1) + await asyncio.sleep(1) await application.remove() await model.disconnect() if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/fullstatus.py b/examples/fullstatus.py index 2cee31210..25ff990c0 100644 --- a/examples/fullstatus.py +++ b/examples/fullstatus.py @@ -1,7 +1,8 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. -from juju import jasyncio +import asyncio + from juju.model import Model @@ -20,4 +21,4 @@ async def status(): if __name__ == "__main__": - jasyncio.run(status()) + asyncio.run(status()) diff --git a/examples/future.py b/examples/future.py index f69775522..4ed95e631 100644 --- a/examples/future.py +++ b/examples/future.py @@ -3,9 +3,9 @@ """This example doesn't work - it demonstrates features that don't exist yet.""" +import asyncio import logging -from juju import jasyncio from juju.model import Model @@ -44,4 +44,4 @@ async def main(): logging.basicConfig(level=logging.DEBUG) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/get_cloud.py b/examples/get_cloud.py index 657aac556..eab2adc55 100644 --- a/examples/get_cloud.py +++ b/examples/get_cloud.py @@ -9,9 +9,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.controller import Controller @@ -29,4 +29,4 @@ async def main(): logging.basicConfig(level=logging.DEBUG) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/leadership.py b/examples/leadership.py index ddd6bddb6..d750943e8 100644 --- a/examples/leadership.py +++ b/examples/leadership.py @@ -9,7 +9,8 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -26,4 +27,4 @@ async def report_leadership(): if __name__ == "__main__": - jasyncio.run(report_leadership()) + asyncio.run(report_leadership()) diff --git a/examples/list_secrets.py b/examples/list_secrets.py index 0aabc70d9..5b0ab205a 100644 --- a/examples/list_secrets.py +++ b/examples/list_secrets.py @@ -1,7 +1,8 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. -from juju import jasyncio +import asyncio + from juju.model import Model @@ -15,4 +16,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/livemodel.py b/examples/livemodel.py index 268ac4a0d..472085d4b 100644 --- a/examples/livemodel.py +++ b/examples/livemodel.py @@ -9,15 +9,17 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model -async def on_model_change(delta, old, new, model): # noqa: RUF029 +async def on_model_change(delta, old, new, model): print(delta.entity, delta.type, delta.data) print(old) print(new) print(model) + await asyncio.sleep(0) async def watch_model(): @@ -31,4 +33,4 @@ async def watch_model(): if __name__ == "__main__": # Run loop until the process is manually stopped (watch_model will loop # forever). - jasyncio.run(watch_model()) + asyncio.run(watch_model()) diff --git a/examples/local_refresh.py b/examples/local_refresh.py index 68e581275..3366217d8 100644 --- a/examples/local_refresh.py +++ b/examples/local_refresh.py @@ -8,7 +8,8 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -30,4 +31,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/localcharm.py b/examples/localcharm.py index dd2489bad..f58b0eff1 100644 --- a/examples/localcharm.py +++ b/examples/localcharm.py @@ -9,9 +9,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.model import Model @@ -33,4 +33,4 @@ async def main(): logging.basicConfig(level=logging.DEBUG) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/machine_hostname.py b/examples/machine_hostname.py index 5ac74d37b..3baa86b44 100644 --- a/examples/machine_hostname.py +++ b/examples/machine_hostname.py @@ -12,9 +12,9 @@ NOTE: this example requires a 2.8.10+ controller. """ +import asyncio import logging -from juju import jasyncio from juju.model import Model MB = 1 @@ -44,4 +44,4 @@ async def main(): ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/model.py b/examples/model.py index 8a53f6973..e0a5996a5 100755 --- a/examples/model.py +++ b/examples/model.py @@ -11,7 +11,8 @@ """ -from juju import jasyncio +import asyncio + from juju.errors import JujuEntityNotFoundError from juju.model import Model @@ -32,4 +33,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/modelsummaries.py b/examples/modelsummaries.py index 0d1568a0b..73b96b786 100644 --- a/examples/modelsummaries.py +++ b/examples/modelsummaries.py @@ -13,7 +13,6 @@ import asyncio import logging -from juju import jasyncio from juju.controller import Controller @@ -41,4 +40,4 @@ def callback(summary): logging.getLogger("juju.client.connection").setLevel(logging.INFO) # Run loop until the process is manually stopped (watch will loop # forever). - jasyncio.run(watch()) + asyncio.run(watch()) diff --git a/examples/relate.py b/examples/relate.py index 1d5f43814..1fbc887d1 100644 --- a/examples/relate.py +++ b/examples/relate.py @@ -13,7 +13,6 @@ import asyncio import logging -from juju import jasyncio from juju.model import Model, ModelObserver @@ -120,4 +119,4 @@ async def main(): logging.basicConfig(level=logging.INFO) ws_logger = logging.getLogger("websockets.protocol") ws_logger.setLevel(logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/run_action.py b/examples/run_action.py index 2f1cf83bc..5244d77bf 100644 --- a/examples/run_action.py +++ b/examples/run_action.py @@ -1,7 +1,8 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. -from juju import jasyncio +import asyncio + from juju.model import Model # logging.basicConfig(level='DEBUG') @@ -38,4 +39,4 @@ async def _get_password(): if __name__ == "__main__": - jasyncio.run(_get_password()) + asyncio.run(_get_password()) diff --git a/examples/scp.py b/examples/scp.py index 9660282f2..eddcc40b3 100644 --- a/examples/scp.py +++ b/examples/scp.py @@ -8,9 +8,9 @@ from a pylibjuju perspective. """ +import asyncio import logging -from juju import jasyncio from juju.model import Model log = logging.getLogger(__name__) @@ -35,4 +35,4 @@ async def main(): if __name__ == "__main__": logging.basicConfig(level=logging.INFO) - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/status.py b/examples/status.py index ba617f66d..236351f1b 100644 --- a/examples/status.py +++ b/examples/status.py @@ -3,11 +3,11 @@ """This example demonstrate how status works""" +import asyncio import logging import sys from logging import getLogger -from juju import jasyncio from juju.model import Model from juju.status import formatted_status @@ -25,7 +25,7 @@ async def main(): series="jammy", channel="stable", ) - await jasyncio.sleep(10) + await asyncio.sleep(10) # Print the status to observe the evolution # during a minute for _ in range(12): @@ -38,7 +38,7 @@ async def main(): print(status) except Exception as e: print(e) - await jasyncio.sleep(5) + await asyncio.sleep(5) print("Removing ubuntu") await application.remove() @@ -48,4 +48,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/unitrun.py b/examples/unitrun.py index 3ca761466..1662f5a51 100644 --- a/examples/unitrun.py +++ b/examples/unitrun.py @@ -10,9 +10,9 @@ """ +import asyncio import logging -from juju import jasyncio from juju.model import Model @@ -66,4 +66,4 @@ async def main(): DEBUG:root:Action status: completed DEBUG:root:Action results: {'return-code': 0, 'stdout': '10.42.51.101\n'} """ - jasyncio.run(main()) + asyncio.run(main()) diff --git a/examples/upgrade_local_charm_k8s.py b/examples/upgrade_local_charm_k8s.py index bfec519d7..727756734 100644 --- a/examples/upgrade_local_charm_k8s.py +++ b/examples/upgrade_local_charm_k8s.py @@ -10,7 +10,8 @@ """ -from juju import jasyncio +import asyncio + from juju.model import Model @@ -46,4 +47,4 @@ async def main(): if __name__ == "__main__": - jasyncio.run(main()) + asyncio.run(main()) diff --git a/juju/_jasyncio.py b/juju/_jasyncio.py new file mode 100644 index 000000000..580230dbf --- /dev/null +++ b/juju/_jasyncio.py @@ -0,0 +1,68 @@ +# Copyright 2023 Canonical Ltd. +# Licensed under the Apache V2, see LICENCE file for details. + +from __future__ import annotations + +import asyncio +import functools +import logging +from asyncio import CancelledError, Task +from typing import Any, Coroutine + +import websockets + +ROOT_LOGGER = logging.getLogger() + + +def create_task_with_handler( + coro: Coroutine[Any, Any, Any], task_name: str, logger: logging.Logger = ROOT_LOGGER +) -> Task[Any]: + """Wrapper around "asyncio.create_task" to make sure the task + exceptions are handled properly. + + asyncio loop event_handler is only called on task exceptions when + the Task object is cleared from memory. But the GC doesn't clear + the Task if we keep a reference for it (e.g. _pinger_task in + connection.py) until the very end. + + This makes sure the exceptions are retrieved and properly + handled/logged whenever the Task is destroyed. + """ + + def _task_result_exp_handler( + task: Task[Any], task_name: str = task_name, logger: logging.Logger = logger + ): + try: + task.result() + except CancelledError: + pass + except websockets.exceptions.ConnectionClosed: + return + except Exception as e: + # This really is an arbitrary exception we need to catch + # + # No need to re-raise, though, because after this point + # the only thing that can catch this is asyncio loop base + # event_handler, which won't do anything but yell 'Task + # exception was never retrieved' anyways. + logger.exception("Task %s raised an exception: %s" % (task_name, e)) + + task = asyncio.create_task(coro) + task.add_done_callback( + functools.partial(_task_result_exp_handler, task_name=task_name, logger=logger) + ) + return task + + +class SingletonEventLoop: + """Single instance containing an event loop to be reused.""" + + loop: asyncio.AbstractEventLoop + instance: SingletonEventLoop + + def __new__(cls): + if not hasattr(cls, "instance"): + cls.instance = super().__new__(cls) + cls.instance.loop = asyncio.new_event_loop() + + return cls.instance diff --git a/juju/application.py b/juju/application.py index a57e336c6..c0f8f8e82 100644 --- a/juju/application.py +++ b/juju/application.py @@ -1,6 +1,7 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio import hashlib import json import logging @@ -9,7 +10,7 @@ from typing_extensions import deprecated -from . import jasyncio, model, tag, utils +from . import model, tag, utils from .annotationhelper import _get_annotations, _set_annotations from .bundle import get_charm_series, is_local_charm from .client import _definitions, client @@ -207,9 +208,8 @@ async def add_unit(self, count=1, to=None, attach_storage=[]): attach_storage=attach_storage, ) - return await jasyncio.gather(*[ - jasyncio.ensure_future(self.model._wait_for_new("unit", unit_id)) - for unit_id in result.units + return await asyncio.gather(*[ + self.model._wait_for_new("unit", unit_id) for unit_id in result.units ]) add_units = add_unit diff --git a/juju/bundle.py b/juju/bundle.py index ad6600b36..ba81ff58b 100644 --- a/juju/bundle.py +++ b/juju/bundle.py @@ -2,6 +2,7 @@ # Licensed under the Apache V2, see LICENCE file for details. from __future__ import annotations +import asyncio import base64 import io import logging @@ -15,7 +16,7 @@ import yaml from toposort import toposort_flatten -from . import jasyncio, utils +from . import utils from .client import client from .constraints import parse as parse_constraints from .errors import JujuError @@ -156,7 +157,7 @@ async def _handle_local_charms(self, bundle, bundle_dir): if apps: # If we have apps to update, spawn all the coroutines concurrently # and wait for them to finish. - charm_urls = await jasyncio.gather(*[ + charm_urls = await asyncio.gather(*[ self.model.add_local_charm_dir(*params) for params in args ]) diff --git a/juju/charmhub.py b/juju/charmhub.py index 61cbfcca6..cf2fdcc32 100644 --- a/juju/charmhub.py +++ b/juju/charmhub.py @@ -1,12 +1,11 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio import json import requests -from juju import jasyncio - from .client import client from .errors import JujuError @@ -24,7 +23,7 @@ async def request_charmhub_with_retry(self, url, retries): _response = requests.get(url) # noqa: S113 if _response.status_code == 200: return _response - await jasyncio.sleep(5) + await asyncio.sleep(5) raise JujuError(f"Got {_response.status_code} from {url}") async def get_charm_id(self, charm_name): diff --git a/juju/client/connection.py b/juju/client/connection.py index b06c81cbc..0f5c2d3e1 100644 --- a/juju/client/connection.py +++ b/juju/client/connection.py @@ -2,6 +2,7 @@ # Licensed under the Apache V2, see LICENCE file for details. from __future__ import annotations +import asyncio import base64 import json import logging @@ -18,7 +19,7 @@ from dateutil.parser import parse from typing_extensions import Self, TypeAlias, overload -from juju import errors, jasyncio, tag, utils +from juju import errors, tag, utils from juju.client import client from juju.utils import IdQueue from juju.version import CLIENT_VERSION @@ -54,8 +55,8 @@ class Monitor: def __init__(self, connection: Connection): self.connection = weakref.ref(connection) - self.reconnecting = jasyncio.Lock() - self.close_called = jasyncio.Event() + self.reconnecting = asyncio.Lock() + self.close_called = asyncio.Event() @property def status(self): @@ -364,8 +365,8 @@ async def close(self, to_reconnect: bool = False): if not to_reconnect: try: log.debug("Gathering all tasks for connection close") - await jasyncio.gather(*tasks_need_to_be_gathered) - except jasyncio.CancelledError: + await asyncio.gather(*tasks_need_to_be_gathered) + except asyncio.CancelledError: pass except websockets.exceptions.ConnectionClosed: pass @@ -452,26 +453,26 @@ async def _debug_logger(self): self.debug_log_shown_lines += number_of_lines_written if self.debug_log_shown_lines >= self.debug_log_params["limit"]: - jasyncio.create_task(self.close(), name="Task_Close") + asyncio.create_task(self.close(), name="Task_Close") # noqa: RUF006 return except KeyError as e: log.exception("Unexpected debug line -- %s" % e) - jasyncio.create_task(self.close(), name="Task_Close") + asyncio.create_task(self.close(), name="Task_Close") # noqa: RUF006 raise - except jasyncio.CancelledError: - jasyncio.create_task(self.close(), name="Task_Close") + except asyncio.CancelledError: + asyncio.create_task(self.close(), name="Task_Close") # noqa: RUF006 raise except websockets.exceptions.ConnectionClosed: log.warning("Debug Logger: Connection closed, reconnecting") # the reconnect has to be done as a task because the receiver will # be cancelled by the reconnect and we don't want the reconnect # to be aborted half-way through - jasyncio.ensure_future(self.reconnect()) + asyncio.ensure_future(self.reconnect()) # noqa: RUF006 return except Exception as e: log.exception("Error in debug logger : %s" % e) - jasyncio.create_task(self.close(), name="Task_Close") + asyncio.create_task(self.close(), name="Task_Close") # noqa: RUF006 raise async def _receiver(self): @@ -485,7 +486,7 @@ async def _receiver(self): if result is not None: result = json.loads(result) await self.messages.put(result["request-id"], result) - except jasyncio.CancelledError: + except asyncio.CancelledError: log.debug("Receiver: Cancelled") pass except websockets.exceptions.ConnectionClosed as e: @@ -494,7 +495,7 @@ async def _receiver(self): # the reconnect has to be done as a task because the receiver will # be cancelled by the reconnect and we don't want the reconnect # to be aborted half-way through - jasyncio.ensure_future(self.reconnect()) + asyncio.ensure_future(self.reconnect()) # noqa: RUF006 return except Exception as e: log.exception("Error in receiver") @@ -514,7 +515,7 @@ async def _do_ping(): try: log.debug(f"Pinger {self._pinger_task}: pinging") await pinger_facade.Ping() - except jasyncio.CancelledError: + except asyncio.CancelledError: raise pinger_facade = client.PingerFacade.from_connection(self) @@ -525,8 +526,8 @@ async def _do_ping(): ) if self.monitor.close_called.is_set(): break - await jasyncio.sleep(10) - except jasyncio.CancelledError: + await asyncio.sleep(10) + except asyncio.CancelledError: log.debug("Pinger: Cancelled") pass except websockets.exceptions.ConnectionClosed: @@ -583,7 +584,7 @@ async def rpc( # if it is triggered by the pinger, then this RPC call will # be cancelled when the pinger is cancelled by the reconnect, # and we don't want the reconnect to be aborted halfway through - await jasyncio.wait([jasyncio.create_task(self.reconnect())]) + await asyncio.wait([asyncio.create_task(self.reconnect())]) if self.monitor.status != Monitor.CONNECTED: # reconnect failed; abort and shutdown log.error("RPC: Automatic reconnect failed") @@ -715,7 +716,7 @@ async def reconnect(self): self._build_facades(res.get("facades", {})) if not self._pinger_task: log.debug("reconnect: scheduling a pinger task") - self._pinger_task = jasyncio.create_task( + self._pinger_task = asyncio.create_task( self._pinger(), name="Task_Pinger" ) @@ -727,20 +728,20 @@ async def _try_endpoint( endpoint, cacert, delay ) -> tuple[_WebSocket, str, str, str]: if delay: - await jasyncio.sleep(delay) + await asyncio.sleep(delay) return await self._open(endpoint, cacert) # Try all endpoints in parallel, with slight increasing delay (+100ms # for each subsequent endpoint); the delay allows us to prefer the # earlier endpoints over the latter. Use first successful connection. tasks = [ - jasyncio.ensure_future(_try_endpoint(endpoint, cacert, 0.1 * i)) + asyncio.ensure_future(_try_endpoint(endpoint, cacert, 0.1 * i)) for i, (endpoint, cacert) in enumerate(endpoints) ] result: tuple[_WebSocket, str, str, str] | None = None for attempt in range(self._retries + 1): - for task in jasyncio.as_completed(tasks): + for task in asyncio.as_completed(tasks): try: result = await task break @@ -752,7 +753,7 @@ async def _try_endpoint( log.debug( f"Retrying connection to endpoints: {_endpoints_str}; attempt {attempt + 1} of {self._retries + 1}" ) - await jasyncio.sleep((attempt + 1) * self._retry_backoff) + await asyncio.sleep((attempt + 1) * self._retry_backoff) continue else: raise errors.JujuConnectionError( @@ -775,7 +776,7 @@ async def _try_endpoint( # If this is a debug-log connection, and the _debug_log_task # is not created yet, then go ahead and schedule it if self.is_debug_log_connection and not self._debug_log_task: - self._debug_log_task = jasyncio.create_task( + self._debug_log_task = asyncio.create_task( self._debug_logger(), name="Task_Debug_Log" ) @@ -783,7 +784,7 @@ async def _try_endpoint( # receiver_task yet, then schedule a _receiver_task elif not self.is_debug_log_connection and not self._receiver_task: log.debug("_connect: scheduling a receiver task") - self._receiver_task = jasyncio.create_task( + self._receiver_task = asyncio.create_task( self._receiver(), name="Task_Receiver" ) @@ -840,7 +841,7 @@ async def _connect_with_redirect(self, endpoints): self._build_facades(login_result.get("facades", {})) if not self._pinger_task: log.debug("_connect_with_redirect: scheduling a pinger task") - self._pinger_task = jasyncio.create_task(self._pinger(), name="Task_Pinger") + self._pinger_task = asyncio.create_task(self._pinger(), name="Task_Pinger") # _build_facades takes the facade list that comes from the connection with the controller, # validates that the client knows about them (client_facade_versions) and builds the facade list diff --git a/juju/controller.py b/juju/controller.py index c5b077daa..9228edbd5 100644 --- a/juju/controller.py +++ b/juju/controller.py @@ -1,6 +1,7 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio import json import logging from concurrent.futures import CancelledError @@ -8,7 +9,7 @@ import websockets -from . import errors, jasyncio, tag, utils +from . import errors, tag, utils from .client import client, connector from .errors import JujuAPIError from .offerendpoints import ParseError as OfferParseError @@ -841,7 +842,7 @@ async def watch_model_summaries(self, callback, as_admin=False): all models in the controller. If the user isn't a superuser they will get a permission error. """ - stop_event = jasyncio.Event() + stop_event = asyncio.Event() async def _watcher(stop_event): try: @@ -882,7 +883,7 @@ async def _watcher(stop_event): raise log.debug("Starting watcher task for model summaries") - jasyncio.ensure_future(_watcher(stop_event)) + asyncio.ensure_future(_watcher(stop_event)) # noqa: RUF006 return stop_event async def add_secret_backends(self, id_, name, backend_type, config): diff --git a/juju/jasyncio.py b/juju/jasyncio.py index 49d499142..81a5220ef 100644 --- a/juju/jasyncio.py +++ b/juju/jasyncio.py @@ -9,22 +9,15 @@ # Any module that needs to use the asyncio should get the binding from # this layer. -import asyncio -import functools import logging import signal +import warnings from asyncio import ( ALL_COMPLETED as ALL_COMPLETED, ) from asyncio import ( FIRST_COMPLETED as FIRST_COMPLETED, ) -from asyncio import ( - CancelledError, - Task, - create_task, - wait, -) # FIXME: integration tests don't use these, but some are used in this repo # Use primitives from asyncio within this repo and remove these re-exports @@ -37,9 +30,6 @@ from asyncio import ( Queue as Queue, ) -from asyncio import ( - TimeoutError as TimeoutError, # noqa: A004 -) from asyncio import ( all_tasks as all_tasks, ) @@ -76,62 +66,28 @@ from asyncio import ( subprocess as subprocess, ) +from asyncio import ( + wait, +) from asyncio import ( wait_for as wait_for, ) -import websockets - -ROOT_LOGGER = logging.getLogger() - - -def create_task_with_handler(coro, task_name, logger=ROOT_LOGGER) -> Task: - """Wrapper around "asyncio.create_task" to make sure the task - exceptions are handled properly. - - asyncio loop event_handler is only called on task exceptions when - the Task object is cleared from memory. But the GC doesn't clear - the Task if we keep a reference for it (e.g. _pinger_task in - connection.py) until the very end. - - This makes sure the exceptions are retrieved and properly - handled/logged whenever the Task is destroyed. - """ - - def _task_result_exp_handler(task, task_name=task_name, logger=logger): - try: - task.result() - except CancelledError: - pass - except websockets.exceptions.ConnectionClosed: - return - except Exception as e: - # This really is an arbitrary exception we need to catch - # - # No need to re-raise, though, because after this point - # the only thing that can catch this is asyncio loop base - # event_handler, which won't do anything but yell 'Task - # exception was never retrieved' anyways. - logger.exception("Task %s raised an exception: %s" % (task_name, e)) - - task = create_task(coro) - task.add_done_callback( - functools.partial(_task_result_exp_handler, task_name=task_name, logger=logger) - ) - return task - - -class SingletonEventLoop: - """Single instance containing an event loop to be reused.""" +from juju._jasyncio import ( + SingletonEventLoop as SingletonEventLoop, +) +from juju._jasyncio import ( + create_task_with_handler as create_task_with_handler, +) - loop = None +warnings.warn( + "juju.jasyncio module is being deprecated by 3.0, use asyncio or juju._jasyncio instead", + DeprecationWarning, + stacklevel=2, +) - def __new__(cls): - if not hasattr(cls, "instance"): - cls.instance = super().__new__(cls) - cls.instance.loop = asyncio.new_event_loop() - return cls.instance +ROOT_LOGGER = logging.getLogger() def run(*steps): diff --git a/juju/loop.py b/juju/loop.py index d9d2e8f8a..fa0da6d67 100644 --- a/juju/loop.py +++ b/juju/loop.py @@ -6,7 +6,7 @@ import warnings warnings.warn( - "juju.loop module is being deprecated by 3.0, use juju.jasyncio instead", + "juju.loop module is being deprecated by 3.0, use juju._jasyncio instead", DeprecationWarning, stacklevel=2, ) diff --git a/juju/machine.py b/juju/machine.py index 70b7327bd..3fdd43692 100644 --- a/juju/machine.py +++ b/juju/machine.py @@ -1,6 +1,7 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio import ipaddress import logging import typing @@ -9,7 +10,7 @@ from juju.utils import block_until, juju_ssh_key_paths -from . import jasyncio, model, tag +from . import model, tag from .annotationhelper import _get_annotations, _set_annotations from .client import client from .errors import JujuError @@ -158,11 +159,11 @@ async def _scp(self, source, destination, scp_opts): retry_backoff = 2 retries = 10 for _ in range(retries): - process = await jasyncio.create_subprocess_exec(*cmd) + process = await asyncio.create_subprocess_exec(*cmd) await process.wait() if process.returncode == 0: break - await jasyncio.sleep(retry_backoff) + await asyncio.sleep(retry_backoff) if process.returncode != 0: raise JujuError(f"command failed after {retries} attempts: {cmd}") @@ -211,13 +212,13 @@ async def ssh( retry_backoff = 2 retries = 10 for _ in range(retries): - process = await jasyncio.create_subprocess_exec( - *cmd, stdout=jasyncio.subprocess.PIPE, stderr=jasyncio.subprocess.PIPE + process = await asyncio.create_subprocess_exec( + *cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, stderr = await process.communicate() if process.returncode == 0: break - await jasyncio.sleep(retry_backoff) + await asyncio.sleep(retry_backoff) if process.returncode != 0: raise JujuError( f"command failed: {cmd} after {retries} attempts, with {stderr.decode()}" diff --git a/juju/model.py b/juju/model.py index c0f0758e9..31e939139 100644 --- a/juju/model.py +++ b/juju/model.py @@ -2,6 +2,7 @@ # Licensed under the Apache V2, see LICENCE file for details. from __future__ import annotations +import asyncio import base64 import collections import hashlib @@ -25,7 +26,7 @@ import yaml from typing_extensions import deprecated -from . import jasyncio, provisioner, tag, utils +from . import provisioner, tag, utils from .annotationhelper import _get_annotations, _set_annotations from .bundle import BundleHandler, get_charm_series, is_local_charm from .charmhub import CharmHub @@ -657,9 +658,9 @@ def __init__( self.state = ModelState(self) self._info = None self._mode = None - self._watch_stopping = jasyncio.Event() - self._watch_stopped = jasyncio.Event() - self._watch_received = jasyncio.Event() + self._watch_stopping = asyncio.Event() + self._watch_stopped = asyncio.Event() + self._watch_received = asyncio.Event() self._watch_stopped.set() self._charmhub = CharmHub(self) @@ -832,7 +833,7 @@ async def _after_connect(self, model_name=None, model_uuid=None): async def watch_received_waiter(): await self._watch_received.wait() - waiter = jasyncio.create_task(watch_received_waiter()) + waiter = asyncio.create_task(watch_received_waiter()) # If we just wait for the _watch_received event and the _all_watcher task # fails (e.g. because API fails like migration is in progress), then @@ -841,8 +842,8 @@ async def watch_received_waiter(): # If _all_watcher is done before the _watch_received, then we should see # (and raise) an exception coming from the _all_watcher # Otherwise (i.e. _watch_received is set), then we're good to go - done, _pending = await jasyncio.wait( - [waiter, self._watcher_task], return_when=jasyncio.FIRST_COMPLETED + done, _pending = await asyncio.wait( + [waiter, self._watcher_task], return_when=asyncio.FIRST_COMPLETED ) if self._watcher_task in done: # Cancel the _watch_received.wait @@ -902,7 +903,7 @@ async def add_local_charm_dir(self, charm_dir, series): CharmArchiveGenerator(str(charm_dir)).make_archive(fn) with open(str(fn), "rb") as fh: func = partial(self.add_local_charm, fh, series, os.stat(str(fn)).st_size) - loop = jasyncio.get_running_loop() + loop = asyncio.get_running_loop() charm_url = await loop.run_in_executor(None, func) log.debug("Uploaded local charm: %s -> %s", charm_dir, charm_url) @@ -1377,7 +1378,7 @@ async def _all_watcher(): self._watch_received.clear() self._watch_stopping.clear() self._watch_stopped.clear() - self._watcher_task = jasyncio.create_task(_all_watcher()) + self._watcher_task = asyncio.create_task(_all_watcher()) async def _notify_observers(self, delta, old_obj, new_obj): """Call observing callbacks, notifying them of a change in model state @@ -1397,7 +1398,7 @@ async def _notify_observers(self, delta, old_obj, new_obj): for o in self._observers: if o.cares_about(delta): - jasyncio.ensure_future(o(delta, old_obj, new_obj, self)) + asyncio.ensure_future(o(delta, old_obj, new_obj, self)) # noqa: RUF006 async def _wait(self, entity_type, entity_id, action, predicate=None): """Block the calling routine until a given action has happened to the @@ -1413,7 +1414,7 @@ async def _wait(self, entity_type, entity_id, action, predicate=None): has a 'completed' status. See the _Observer class for details. """ - q = jasyncio.Queue() + q = asyncio.Queue() async def callback(delta, old, new, model): await q.put(delta.get_id()) @@ -1900,8 +1901,8 @@ async def deploy( if pending_apps: # new apps will usually be in the model by now, but if some # haven't made it yet we'll need to wait on them to be added - await jasyncio.gather(*[ - jasyncio.ensure_future(self._wait_for_new("application", app_name)) + await asyncio.gather(*[ + self._wait_for_new("application", app_name) for app_name in pending_apps ]) return [ @@ -2628,9 +2629,9 @@ async def _wait_for_action_status(): if action_output.results[0].status in ("completed", "failed"): return else: - await jasyncio.sleep(1) + await asyncio.sleep(1) - await jasyncio.wait_for(_wait_for_action_status(), timeout=wait) + await asyncio.wait_for(_wait_for_action_status(), timeout=wait) action_results = await action_facade.Actions(entities=entity) return action_results.results[0] @@ -3206,11 +3207,11 @@ def _raise_for_status(entities: dict[str, list[str]], status: Any): break busy = "\n ".join(busy) if timeout is not None and datetime.now() - start_time > timeout: - raise jasyncio.TimeoutError("Timed out waiting for model:\n" + busy) + raise asyncio.TimeoutError("Timed out waiting for model:\n" + busy) if last_log_time is None or datetime.now() - last_log_time > log_interval: log.info("Waiting for model:\n " + busy) last_log_time = datetime.now() - await jasyncio.sleep(check_freq) + await asyncio.sleep(check_freq) def _create_consume_args(offer, macaroon, controller_info): diff --git a/juju/utils.py b/juju/utils.py index 710fcc56e..4ab098f93 100644 --- a/juju/utils.py +++ b/juju/utils.py @@ -9,18 +9,20 @@ import zipfile from collections import defaultdict from pathlib import Path -from typing import Any +from typing import Any, Awaitable, Callable import yaml from pyasn1.codec.der.encoder import encode from pyasn1.type import char, univ -from . import errors, jasyncio, origin +from juju import _jasyncio + +from . import errors, origin from .client import client from .errors import JujuError -async def execute_process(*cmd, log=None): +async def execute_process(*cmd, log=None) -> bool: """Wrapper around asyncio.create_subprocess_exec.""" p = await asyncio.create_subprocess_exec( *cmd, @@ -38,7 +40,7 @@ async def execute_process(*cmd, log=None): return p.returncode == 0 -def juju_config_dir(): +def juju_config_dir() -> str: """Resolves and returns the path string to the juju configuration folder for the juju CLI tool. Of the following items, returns the first option that works (top to bottom): @@ -60,7 +62,7 @@ def juju_config_dir(): return str(config_dir.expanduser().resolve()) -def juju_ssh_key_paths(): +def juju_ssh_key_paths() -> tuple[str, str]: """Resolves and returns the path strings for public and private ssh keys for juju CLI. """ @@ -71,7 +73,7 @@ def juju_ssh_key_paths(): return public_key_path, private_key_path -def _read_ssh_key(): +def _read_ssh_key() -> str: """Inner function for read_ssh_key, suitable for passing to our Executor. """ @@ -82,7 +84,7 @@ def _read_ssh_key(): return ssh_key -async def read_ssh_key(): +async def read_ssh_key() -> str: """Attempt to read the local juju admin's public ssh key, so that it can be passed on to a model. """ @@ -124,7 +126,11 @@ async def put_all(self, value: Exception): await queue.put(value) -async def block_until(*conditions, timeout=None, wait_period=0.5): +async def block_until( + *conditions: Callable[[], bool], + timeout: float | None = None, + wait_period: float = 0.5, +): """Return only after all conditions are true. If a timeout occurs, it cancels the task and raises @@ -139,7 +145,9 @@ async def _block(): async def block_until_with_coroutine( - condition_coroutine, timeout=None, wait_period=0.5 + condition_coroutine: Callable[[], Awaitable[bool]], + timeout: float | None = None, + wait_period: float = 0.5, ): """Return only after the given coroutine returns True. @@ -169,12 +177,12 @@ async def wait_for_bundle(model, bundle: str | Path, **kwargs) -> None: bundle = bundle_path / "bundle.yaml" except OSError: pass - content: dict[str, Any] = yaml.safe_load(textwrap.dedent(bundle).strip()) + content: dict[str, Any] = yaml.safe_load(textwrap.dedent(str(bundle)).strip()) apps = list(content.get("applications", content.get("services")).keys()) await model.wait_for_idle(apps, **kwargs) -async def run_with_interrupt(task, *events, log=None): +async def run_with_interrupt(task, *events: asyncio.Event, log=None): """Awaits a task while allowing it to be interrupted by one or more `asyncio.Event`s. @@ -186,17 +194,17 @@ async def run_with_interrupt(task, *events, log=None): :param events: One or more `asyncio.Event`s which, if set, will interrupt `task` and cause it to be cancelled. """ - task = jasyncio.create_task_with_handler(task, "tmp", log) - event_tasks = [jasyncio.ensure_future(event.wait()) for event in events] - done, pending = await jasyncio.wait( - [task, *event_tasks], return_when=jasyncio.FIRST_COMPLETED + task = _jasyncio.create_task_with_handler(task, "tmp", log) + event_tasks = [asyncio.ensure_future(event.wait()) for event in events] + done, pending = await asyncio.wait( + [task, *event_tasks], return_when=asyncio.FIRST_COMPLETED ) for f in pending: f.cancel() # cancel unfinished tasks for f in pending: try: await f - except jasyncio.CancelledError: + except asyncio.CancelledError: pass for f in done: f.exception() # prevent "exception was not retrieved" errors @@ -228,7 +236,7 @@ class RegistrationInfo(univ.Sequence): def generate_user_controller_access_token( - username, controller_endpoints, secret_key, controller_name + username: str, controller_endpoints, secret_key: str, controller_name ): """Implement in python what is currently done in GO. @@ -258,14 +266,12 @@ def generate_user_controller_access_token( return base64.urlsafe_b64encode(registration_string) -def get_local_charm_data(path, yaml_file): +def get_local_charm_data(path: str | Path, yaml_file: str) -> dict[str, Any]: """Retrieve Metadata of a Charm from its path. - :patam str path: Path of charm directory or .charm file :patam str - yaml_ - file: - name of the yaml file, can be either "metadata.yaml", or - "manifest.yaml", or "charmcraft.yaml" + :param str path: Path of charm directory or .charm file + :param str yaml_file: name of the yaml file, can be either + "metadata.yaml", or "manifest.yaml", or "charmcraft.yaml" :return: Object of charm metadata """ @@ -282,15 +288,15 @@ def get_local_charm_data(path, yaml_file): return metadata -def get_local_charm_metadata(path): +def get_local_charm_metadata(path: str | Path) -> dict[str, Any]: return get_local_charm_data(path, "metadata.yaml") -def get_local_charm_manifest(path): +def get_local_charm_manifest(path: str | Path) -> dict[str, Any]: return get_local_charm_data(path, "manifest.yaml") -def get_local_charm_charmcraft_yaml(path): +def get_local_charm_charmcraft_yaml(path: str | Path) -> dict[str, Any]: return get_local_charm_data(path, "charmcraft.yaml") @@ -354,7 +360,7 @@ def get_local_charm_charmcraft_yaml(path): ALL_SERIES_VERSIONS = {**UBUNTU_SERIES, **KUBERNETES_SERIES} -def get_series_version(series_name): +def get_series_version(series_name: str) -> str: """get_series_version outputs the version of the OS based on the given series e.g. jammy -> 22.04, kubernetes -> kubernetes. @@ -366,7 +372,7 @@ def get_series_version(series_name): return ALL_SERIES_VERSIONS[series_name] -def get_version_series(version): +def get_version_series(version: str) -> str: """get_version_series is the opposite of the get_series_version. It outputs the series based on given OS version. @@ -378,7 +384,7 @@ def get_version_series(version): return list(UBUNTU_SERIES.keys())[list(UBUNTU_SERIES.values()).index(version)] -def get_local_charm_base(series, charm_path, base_class): +def get_local_charm_base(series: str, charm_path: str, base_class: type): """Deduce the base [channel/osname] of a local charm based on what we know already. @@ -428,7 +434,7 @@ def get_local_charm_base(series, charm_path, base_class): return base_class(channel_for_base, os_name_for_base) -def base_channel_to_series(channel): +def base_channel_to_series(channel: str) -> str: """Returns the series string using the track inside the base channel. :param str channel: is track/risk (e.g. 20.04/stable) @@ -437,7 +443,7 @@ def base_channel_to_series(channel): return get_version_series(origin.Channel.parse(channel).track) -def parse_base_arg(base): +def parse_base_arg(base: str) -> client.Base: """Parses a given base into a Client.Base object :param base str : The base to deploy a charm (e.g. ubuntu@22.04) """ @@ -463,7 +469,7 @@ def base_channel_from_series(track, risk, series): ) -def get_os_from_series(series): +def get_os_from_series(series: str) -> str: if series in UBUNTU_SERIES: return "ubuntu" raise JujuError(f"os for the series {series} needs to be added") @@ -479,7 +485,7 @@ def get_base_from_origin_or_channel(origin_or_channel, series=None): return client.Base(channel=channel, name=os_name) -def series_for_charm(requested_series, supported_series): +def series_for_charm(requested_series: str, supported_series: list[str]) -> str: """series_for_charm takes a requested series and a list of series supported by a charm and returns the series which is relevant. @@ -506,7 +512,7 @@ def series_for_charm(requested_series, supported_series): ) -def user_requested(series_arg, supported_series, force): +def user_requested(series_arg: str, supported_series: list[str], force: bool) -> str: series = series_for_charm(series_arg, supported_series) if force: series = series_arg @@ -516,8 +522,12 @@ def user_requested(series_arg, supported_series, force): def series_selector( - series_arg="", charm_url=None, model_config=None, supported_series=[], force=False -): + series_arg: str = "", + charm_url=None, + model_config=None, + supported_series: list[str] = [], + force: bool = False, +) -> str: """Select series to deploy on. series_selector corresponds to the CharmSeries() in @@ -563,7 +573,9 @@ def series_selector( return DEFAULT_SUPPORTED_LTS -def should_upgrade_resource(available_resource, existing_resources, arg_resources): +def should_upgrade_resource( + available_resource: dict[str, str], existing_resources, arg_resources +) -> bool: """Determine if the given resource should be upgraded. Called in the context of upgrade_charm. Given a resource R, takes a look @@ -571,11 +583,11 @@ def should_upgrade_resource(available_resource, existing_resources, arg_resource :param dict[str] available_resource: The dict representing the client.Resource coming from the charmhub api. We're considering if - we need to refresh this during upgrade_charm. :param dict[str] - existing_resources: The dict coming from + we need to refresh this during upgrade_charm. + :param dict[str] existing_resources: The dict coming from resources_facade.ListResources representing the resources of the - currently deployed charm. :param dict[str] arg_resources: user - provided resources to be refreshed + currently deployed charm. + :param dict[str] arg_resources: user provided resources to be refreshed :result bool: The decision to refresh the given resource """ diff --git a/scripts/gendoc b/scripts/gendoc index 3ef628ef9..140046710 100755 --- a/scripts/gendoc +++ b/scripts/gendoc @@ -10,7 +10,6 @@ packages=( juju.errors juju.exceptions juju.juju - juju.loop juju.machine juju.model juju.placement diff --git a/tests/charm-secret/pyproject.toml b/tests/charm-secret/pyproject.toml index bfbb0be6e..9d43d8122 100644 --- a/tests/charm-secret/pyproject.toml +++ b/tests/charm-secret/pyproject.toml @@ -18,7 +18,7 @@ target-version = ["py38"] [tool.ruff] line-length = 99 select = ["E", "W", "F", "C", "N", "D", "I001"] -extend-ignore = [ +lint.extend-ignore = [ "D204", "D215", "D400", diff --git a/tests/integration/test_application.py b/tests/integration/test_application.py index 06b48cd2d..07c6935a7 100644 --- a/tests/integration/test_application.py +++ b/tests/integration/test_application.py @@ -7,7 +7,7 @@ import pytest -from juju import errors, jasyncio +from juju import errors from juju.application import Application from juju.client import client from juju.url import URL, Schema @@ -24,7 +24,7 @@ async def test_action(): async with base.CleanModel() as model: app = await model.deploy("juju-qa-test") - await jasyncio.sleep(10) + await asyncio.sleep(10) actions = await app.get_actions(schema=True) assert "fortune" in actions diff --git a/tests/integration/test_charmhub.py b/tests/integration/test_charmhub.py index b09dda053..5f2fe744b 100644 --- a/tests/integration/test_charmhub.py +++ b/tests/integration/test_charmhub.py @@ -1,9 +1,10 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio + import pytest -from juju import jasyncio from juju.errors import JujuError from .. import base @@ -103,11 +104,11 @@ async def test_subordinate_charm_zero_units(): async with base.CleanModel() as model: # rsyslog-forwarder-ha is a subordinate charm app = await model.deploy("rsyslog-forwarder-ha") - await jasyncio.sleep(5) + await asyncio.sleep(5) assert len(app.units) == 0 await app.destroy() - await jasyncio.sleep(5) + await asyncio.sleep(5) # note that it'll error if the user tries to use num_units with pytest.raises(JujuError): @@ -116,7 +117,7 @@ async def test_subordinate_charm_zero_units(): # (full disclosure: it'll quietly switch to 0 if user enters # num_units=1, instead of erroring) app2 = await model.deploy("rsyslog-forwarder-ha", num_units=1) - await jasyncio.sleep(5) + await asyncio.sleep(5) assert len(app2.units) == 0 diff --git a/tests/integration/test_connection.py b/tests/integration/test_connection.py index a47f96d0c..ac3d5f585 100644 --- a/tests/integration/test_connection.py +++ b/tests/integration/test_connection.py @@ -1,6 +1,6 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. - +import asyncio import http import logging import socket @@ -11,7 +11,6 @@ import pytest import websockets -from juju import jasyncio from juju.client import client from juju.client.connection import Connection from juju.client.jujudata import FileJujuData @@ -46,7 +45,7 @@ async def test_monitor_catches_error(): await conn._ws.close() # this could be racy with reconnect # if auto-reconnect is not disabled by lock, force this # test to fail by deferring to the reconnect task via sleep - await jasyncio.sleep(0.1) + await asyncio.sleep(0.1) assert conn.monitor.status == "error" finally: await conn.close() @@ -74,7 +73,7 @@ async def test_reconnect(): kwargs = model.connection().connect_params() conn = await Connection.connect(**kwargs) try: - await jasyncio.sleep(0.1) + await asyncio.sleep(0.1) assert conn.is_open await conn._ws.close() assert not conn.is_open @@ -133,12 +132,12 @@ async def test_redirect(): class RedirectServer: def __init__(self, destination): self.destination = destination - self._start = jasyncio.Event() - self._stop = jasyncio.Event() - self._terminate = jasyncio.Event() - self.running = jasyncio.Event() - self.stopped = jasyncio.Event() - self.terminated = jasyncio.Event() + self._start = asyncio.Event() + self._stop = asyncio.Event() + self._terminate = asyncio.Event() + self.running = asyncio.Event() + self.stopped = asyncio.Event() + self.terminated = asyncio.Event() if hasattr(ssl, "PROTOCOL_TLS_SERVER"): # python 3.6+ protocol = ssl.PROTOCOL_TLS_SERVER @@ -148,7 +147,7 @@ def __init__(self, destination): self.ssl_context.load_cert_chain(str(crt_file), str(key_file)) self.status = None self.port = None - self._task = jasyncio.create_task(self.run()) + self._task = asyncio.create_task(self.run()) def start(self, status): self.status = status @@ -166,7 +165,7 @@ def terminate(self): def exception(self): try: return self._task.exception() - except (jasyncio.CancelledError, jasyncio.InvalidStateError): + except (asyncio.CancelledError, asyncio.InvalidStateError): return None async def run(self): @@ -192,16 +191,16 @@ async def redirect(path, request_headers): host="localhost", port=self.port, ssl=self.ssl_context, - loop=jasyncio.get_running_loop(), + loop=asyncio.get_running_loop(), ): self.stopped.clear() self.running.set() logger.debug("server: started") while not self._stop.is_set(): - await run_with_interrupt(jasyncio.sleep(1), self._stop) + await run_with_interrupt(asyncio.sleep(1), self._stop) logger.debug("server: tick") logger.debug("server: stopping") - except jasyncio.CancelledError: + except asyncio.CancelledError: break finally: self.stopped.set() @@ -209,7 +208,7 @@ async def redirect(path, request_headers): self.running.clear() logger.debug("server: stopped") logger.debug("server: terminating") - except jasyncio.CancelledError: + except asyncio.CancelledError: pass finally: self._start.clear() diff --git a/tests/integration/test_crossmodel.py b/tests/integration/test_crossmodel.py index 6d2eef437..444826a4b 100644 --- a/tests/integration/test_crossmodel.py +++ b/tests/integration/test_crossmodel.py @@ -1,13 +1,12 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio import tempfile from pathlib import Path import pytest -from juju import jasyncio - from .. import base @@ -86,7 +85,7 @@ async def test_remove_saas(): await model_2.consume(f"admin/{model_1.name}.ubuntu") await model_2.remove_saas("ubuntu") - await jasyncio.sleep(5) + await asyncio.sleep(5) status = await model_2.get_status() if "ubuntu" in status.remote_applications: @@ -137,7 +136,7 @@ async def test_relate_with_offer(): raise Exception("Expected postgresql in saas") await model_2.remove_saas("postgresql") - await jasyncio.sleep(5) + await asyncio.sleep(5) status = await model_2.get_status() if "postgresql" in status.remote_applications: diff --git a/tests/integration/test_model.py b/tests/integration/test_model.py index 2bc7c7393..972036047 100644 --- a/tests/integration/test_model.py +++ b/tests/integration/test_model.py @@ -1,6 +1,6 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. - +import asyncio import json import os import random @@ -12,7 +12,7 @@ import paramiko import pytest -from juju import jasyncio, tag, url +from juju import tag, url from juju.client import client from juju.client._definitions import FullStatus from juju.errors import JujuConnectionError, JujuError, JujuModelError, JujuUnitError @@ -336,7 +336,7 @@ async def test_wait_local_charm_waiting_timeout(): await model.deploy(str(charm_path), config={"status": "waiting"}) assert "charm" in model.applications await model.wait_for_idle() - with pytest.raises(jasyncio.TimeoutError): + with pytest.raises(asyncio.TimeoutError): await model.wait_for_idle(status="active", timeout=30) @@ -752,14 +752,14 @@ async def test_relate(): num_units=0, ) - relation_added = jasyncio.Event() - timeout = jasyncio.Event() + relation_added = asyncio.Event() + timeout = asyncio.Event() class TestObserver(ModelObserver): async def on_relation_add(self, delta, old, new, model): if set(new.key.split()) == {"nrpe:general-info", "ubuntu:juju-info"}: relation_added.set() - jasyncio.get_running_loop().call_later(10, timeout.set) + asyncio.get_running_loop().call_later(10, timeout.set) model.add_observer(TestObserver()) @@ -936,7 +936,7 @@ async def test_wait_for_idle_without_units(): channel="stable", num_units=0, ) - with pytest.raises(jasyncio.TimeoutError): + with pytest.raises(asyncio.TimeoutError): await model.wait_for_idle(timeout=10) @@ -950,7 +950,7 @@ async def test_wait_for_idle_with_not_enough_units(): channel="stable", num_units=2, ) - with pytest.raises(jasyncio.TimeoutError): + with pytest.raises(asyncio.TimeoutError): await model.wait_for_idle(timeout=5 * 60, wait_for_at_least_units=3) @@ -1320,7 +1320,7 @@ async def test_model_attach_storage_at_deploy(): storage_id = ret[0] await unit.detach_storage(storage_id, force=True) - await jasyncio.sleep(10) + await asyncio.sleep(10) storages1 = await model.list_storage() assert any([storage_id in s["storage-tag"] for s in storages1]) @@ -1328,7 +1328,7 @@ async def test_model_attach_storage_at_deploy(): # juju remove-application # actually removes the storage even though the destroy_storage=false await app.destroy(destroy_storage=False) - await jasyncio.sleep(10) + await asyncio.sleep(10) storages2 = await model.list_storage() assert any([storage_id in s["storage-tag"] for s in storages2]) @@ -1349,14 +1349,14 @@ async def test_detach_storage(): unit = app.units[0] storage_ids = await unit.add_storage("pgdata") storage_id = storage_ids[0] - await jasyncio.sleep(5) + await asyncio.sleep(5) _storage_details_1 = await model.show_storage_details(storage_id) storage_details_1 = _storage_details_1[0] assert "unit-postgresql-0" in storage_details_1["attachments"] await unit.detach_storage(storage_id, force=True) - await jasyncio.sleep(20) + await asyncio.sleep(20) _storage_details_2 = await model.show_storage_details(storage_id) storage_details_2 = _storage_details_2[0] @@ -1366,7 +1366,7 @@ async def test_detach_storage(): # remove_storage await model.remove_storage(storage_id, force=True) - await jasyncio.sleep(10) + await asyncio.sleep(10) storages = await model.list_storage() assert all([storage_id not in s["storage-tag"] for s in storages]) @@ -1381,7 +1381,7 @@ async def test_add_and_list_storage(): # All we need is to make sure a unit is up, doesn't even need to # be in 'active' or 'idle', i.e. # await model.wait_for_idle(status="waiting", wait_for_exact_units=1) - await jasyncio.sleep(5) + await asyncio.sleep(5) unit = app.units[0] await unit.add_storage("pgdata", size=512) storages = await model.list_storage() @@ -1403,6 +1403,6 @@ async def test_storage_pools_on_lxd(): assert "test-pool" in [p["name"] for p in pools] await model.remove_storage_pool("test-pool") - await jasyncio.sleep(5) + await asyncio.sleep(5) pools = await model.list_storage_pools() assert "test-pool" not in [p["name"] for p in pools] diff --git a/tests/integration/test_unit.py b/tests/integration/test_unit.py index 4f7be5468..e9244ae74 100644 --- a/tests/integration/test_unit.py +++ b/tests/integration/test_unit.py @@ -7,7 +7,7 @@ import pytest -from juju import jasyncio, utils +from juju import utils from .. import base @@ -162,11 +162,6 @@ def check_results(results, out): @base.bootstrapped async def test_scp(): - # ensure that asyncio.subprocess will work; - try: - asyncio.get_child_watcher().attach_loop(jasyncio.get_running_loop()) - except RuntimeError: - pytest.skip("test_scp will always fail outside of MainThread") async with base.CleanModel() as model: app = await model.deploy("ubuntu", channel="stable") @@ -197,7 +192,7 @@ async def test_scp(): async def test_ssh(): # ensure that asyncio.subprocess will work; try: - asyncio.get_child_watcher().attach_loop(jasyncio.get_running_loop()) + asyncio.get_child_watcher().attach_loop(asyncio.get_running_loop()) except RuntimeError: pytest.skip("test_ssh will always fail outside of MainThread") async with base.CleanModel() as model: diff --git a/tests/unit/test_loop.py b/tests/unit/test_loop.py index c0abda127..ae63e7b35 100644 --- a/tests/unit/test_loop.py +++ b/tests/unit/test_loop.py @@ -1,15 +1,14 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio import unittest -from juju import jasyncio - class TestLoop(unittest.TestCase): def setUp(self): # new event loop for each test - policy = jasyncio.get_event_loop_policy() + policy = asyncio.get_event_loop_policy() self.loop = policy.new_event_loop() policy.set_event_loop(self.loop) @@ -17,21 +16,15 @@ def tearDown(self): self.loop.close() async def test_run(self): - assert jasyncio.get_running_loop() == self.loop + assert asyncio.get_running_loop() == self.loop async def _test(): return "success" - self.assertEqual(jasyncio.run(_test()), "success") - - async def test_run_interrupt(self): - async def _test(): - jasyncio.run._sigint = True - - self.assertRaises(KeyboardInterrupt, jasyncio.run, _test()) + self.assertEqual(asyncio.run(_test()), "success") async def test_run_exception(self): async def _test(): raise ValueError() - self.assertRaises(ValueError, jasyncio.run, _test()) + self.assertRaises(ValueError, asyncio.run, _test()) diff --git a/tests/unit/test_model.py b/tests/unit/test_model.py index 0ec452d16..091043217 100644 --- a/tests/unit/test_model.py +++ b/tests/unit/test_model.py @@ -1,6 +1,7 @@ # Copyright 2023 Canonical Ltd. # Licensed under the Apache V2, see LICENCE file for details. +import asyncio import datetime import unittest from unittest import mock @@ -8,7 +9,6 @@ import pytest -from juju import jasyncio from juju.application import Application from juju.client.jujudata import FileJujuData from juju.errors import JujuConnectionError, JujuError @@ -267,7 +267,7 @@ async def test_apps_no_lst(self): async def test_timeout(self): m = Model() - with self.assertRaises(jasyncio.TimeoutError) as cm: + with self.assertRaises(asyncio.TimeoutError) as cm: # no apps so should timeout after timeout period await m.wait_for_idle(apps=["nonexisting_app"]) self.assertEqual( @@ -351,7 +351,7 @@ async def test_wait_for_active_units_waiting_application(self): mock_apps.return_value = apps m = Model() - with self.assertRaises(jasyncio.TimeoutError): + with self.assertRaises(asyncio.TimeoutError): await m.wait_for_idle(apps=["dummy_app"], status="active") mock_apps.assert_called_with()