diff --git a/awscli/arguments.py b/awscli/arguments.py index 77639cf..3ab060b 100644 --- a/awscli/arguments.py +++ b/awscli/arguments.py @@ -451,7 +451,7 @@ class CLIArgument(BaseCLIArgument): cli_name = self.cli_name parser.add_argument( cli_name, - help=self.documentation, + help=self.documentation.replace("%", "%%"), type=self.cli_type, required=self.required, ) @@ -602,7 +602,7 @@ class BooleanArgument(CLIArgument): def add_to_parser(self, parser): parser.add_argument( self.cli_name, - help=self.documentation, + help=self.documentation.replace("%", "%%"), action=self._action, default=self._default, dest=self._destination, diff --git a/awscli/customizations/logs/startlivetail.py b/awscli/customizations/logs/startlivetail.py index cc5849b..bc71a3d 100644 --- a/awscli/customizations/logs/startlivetail.py +++ b/awscli/customizations/logs/startlivetail.py @@ -833,7 +833,7 @@ class InteractiveUI(BaseLiveTailUI): await self._application.run_async() def run(self): - asyncio.get_event_loop().run_until_complete(self._run_ui()) + asyncio.run(self._run_ui()) class PrintOnlyPrinter(BaseLiveTailPrinter): diff --git a/awscli/customizations/wizard/app.py b/awscli/customizations/wizard/app.py index c813b97..028fcaa 100644 --- a/awscli/customizations/wizard/app.py +++ b/awscli/customizations/wizard/app.py @@ -10,9 +10,9 @@ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. +import asyncio import json import os -from asyncio import new_event_loop, set_event_loop from collections.abc import MutableMapping from prompt_toolkit.application import Application @@ -77,14 +77,12 @@ class WizardApp(Application): ) def run(self, pre_run=None, **kwargs): - loop = new_event_loop() - try: - set_event_loop(loop) + def create_event_loop(): + loop = asyncio.new_event_loop() loop.set_exception_handler(self._handle_exception) - f = self.run_async(pre_run=pre_run, set_exception_handler=False) - return loop.run_until_complete(f) - finally: - loop.close() + return loop + f = self.run_async(pre_run=pre_run, set_exception_handler=False) + return asyncio.run(f, loop_factory=create_event_loop) def _handle_exception(self, loop, context): self.exit(exception=UnexpectedWizardException(context['exception'])) diff --git a/tests/__init__.py b/tests/__init__.py index db70912..0ab2e4a 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -471,16 +471,9 @@ class PromptToolkitAppRunner: # This is the function that will be passed to our thread to # actually run the application try: - # When we run the app in a separate thread, there is no - # default event loop. This ensures we create one as it is - # likely the application will try to grab the default loop - loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) app_run_context.return_value = target(*target_args) except BaseException as e: app_run_context.raised_exception = e - finally: - loop.close() def _wait_until_app_is_done_updating(self): self._wait_until_app_is_done_rendering() diff --git a/tests/functional/test_utils.py b/tests/functional/test_utils.py --- a/tests/functional/test_utils.py +++ b/tests/functional/test_utils.py @@ -18,7 +18,7 @@ class TestWriteException(unittest.TestCa def test_write_exception(self): error_message = "Some error message." ex = Exception(error_message) - with codecs.open(self.outfile, 'w+', encoding='utf-8') as outfile: + with open(self.outfile, 'w+', encoding='utf-8') as outfile: write_exception(ex, outfile) outfile.seek(0) diff --git a/tests/unit/botocore/test_utils.py b/tests/unit/botocore/test_utils.py --- a/tests/unit/botocore/test_utils.py +++ b/tests/unit/botocore/test_utils.py @@ -3646,20 +3646,28 @@ def test_lru_cache_weakref(): cls2 = ClassWithCachedMethod() assert cls1.cached_fn.cache_info().currsize == 0 - assert getrefcount(cls1) == 2 - assert getrefcount(cls2) == 2 + refcount1 = getrefcount(cls1) + refcount2 = getrefcount(cls2) + assert refcount1 in (1, 2) + assert refcount2 in (1, 2) # "The count returned is generally one higher than you might expect, because # it includes the (temporary) reference as an argument to getrefcount()." # https://docs.python.org/3.8/library/sys.html#getrefcount + # However, as of 3.14: "The interpreter internally avoids some reference + # count modifications when loading objects onto the operands stack by + # borrowing references when possible. This can lead to smaller reference + # count values compared to previous Python versions." + # https://docs.python.org/3.14/whatsnew/3.14.html#whatsnew314-refcount + cls1.cached_fn(1, 1) cls2.cached_fn(1, 1) # The cache now has two entries, but the reference count remains the same as # before. assert cls1.cached_fn.cache_info().currsize == 2 - assert getrefcount(cls1) == 2 - assert getrefcount(cls2) == 2 + assert getrefcount(cls1) == refcount1 + assert getrefcount(cls2) == refcount2 # Deleting one of the objects does not interfere with the cache entries # related to the other object.