From 9e703b9345cffd90d723e0ce5c180a385c75c62e Mon Sep 17 00:00:00 2001
From: Tim Burke <tim.burke@gmail.com>
Date: Mon, 20 Mar 2023 11:27:50 -0700
Subject: [PATCH] shell: Allow timeouts to have units

Related-Change: Ibbe7e5aa8aa8e54935da76109c2ea13fb83bc7ab
Change-Id: Ifeaaea790d1dadc84b157a7cf2be7590949c70f0
---
 swiftclient/shell.py    |  4 ++--
 swiftclient/utils.py    | 14 ++++++++++++++
 test/unit/test_shell.py | 21 +++++++++++++++++++++
 3 files changed, 37 insertions(+), 2 deletions(-)

diff --git a/swiftclient/shell.py b/swiftclient/shell.py
index 882a1c08..dc68aa9c 100755
--- a/swiftclient/shell.py
+++ b/swiftclient/shell.py
@@ -31,7 +31,7 @@ from time import gmtime, strftime
 
 from swiftclient import RequestException
 from swiftclient.utils import config_true_value, generate_temp_url, \
-    prt_bytes, JSONableIterable
+    prt_bytes, parse_timeout, JSONableIterable
 from swiftclient.multithreading import OutputManager
 from swiftclient.exceptions import ClientException
 from swiftclient import __version__ as client_version
@@ -1746,7 +1746,7 @@ def add_default_args(parser):
     parser.add_argument('-K', '--key', dest='key',
                         default=environ.get('ST_KEY'),
                         help='Key for obtaining an auth token.')
-    parser.add_argument('-T', '--timeout', type=int, dest='timeout',
+    parser.add_argument('-T', '--timeout', type=parse_timeout, dest='timeout',
                         default=None,
                         help='Timeout in seconds to wait for response.')
     parser.add_argument('-R', '--retries', type=int, default=5, dest='retries',
diff --git a/swiftclient/utils.py b/swiftclient/utils.py
index 0a675374..39481e49 100644
--- a/swiftclient/utils.py
+++ b/swiftclient/utils.py
@@ -70,6 +70,20 @@ def prt_bytes(num_bytes, human_flag):
         return '%.1f%s' % (num, suffix)
 
 
+def parse_timeout(value):
+    for suffix, multiplier in (
+        ('s', 1),
+        ('m', 60),
+        ('min', 60),
+        ('h', 60 * 60),
+        ('hr', 60 * 60),
+        ('d', 24 * 60 * 60),
+    ):
+        if value.endswith(suffix):
+            return multiplier * float(value[:-len(suffix)])
+    return float(value)
+
+
 def parse_timestamp(seconds, absolute=False):
     try:
         try:
diff --git a/test/unit/test_shell.py b/test/unit/test_shell.py
index 98d73e9a..f1032466 100644
--- a/test/unit/test_shell.py
+++ b/test/unit/test_shell.py
@@ -2424,6 +2424,27 @@ class TestDebugAndInfoOptions(unittest.TestCase):
                           % (mock_logging.call_args_list, argv))
 
 
+@mock.patch.dict(os.environ, mocked_os_environ)
+class TestTimeoutOption(unittest.TestCase):
+    @mock.patch('swiftclient.service.Connection')
+    def test_timeout_parsing(self, connection):
+        for timeout, expected in (
+            ("12", 12),
+            ("12.3", 12.3),
+            ("5s", 5),
+            ("25.6s", 25.6),
+            ("2m", 120),
+            ("2.5min", 150),
+            ("1h", 3600),
+            (".5hr", 1800),
+        ):
+            connection.reset_mock()
+            with self.subTest(timeout=timeout):
+                swiftclient.shell.main(["", "stat", "--timeout", timeout])
+                self.assertEqual(connection.mock_calls[0].kwargs['timeout'],
+                                 expected)
+
+
 class TestBase(unittest.TestCase):
     """
     Provide some common methods to subclasses