]> kaliko git repositories - python-musicpd.git/commitdiff
Improved Range object to deal with window parameter
authorKaliko Jack <kaliko@azylum.org>
Sat, 8 Apr 2023 17:00:24 +0000 (19:00 +0200)
committerKaliko Jack <kaliko@azylum.org>
Sat, 8 Apr 2023 17:00:24 +0000 (19:00 +0200)
Add unittest for Range object

CHANGES.txt
doc/source/use.rst
musicpd.py
test.py

index fc44e05610e72cc60ddae5330195f50c81c65604..31706e2eb8b768971f0bcf157d09e3d930a652f0 100644 (file)
@@ -7,6 +7,7 @@ Changes in 0.9.0
  * Use right SPDX identifier for license headers
  * mpd_version attribute init to empty string instead of None
  * Fixed send_noidle (introduced with e8daa719)
+ * Improved Range object to deal with window parameter
 
 Changes in 0.8.0
 ----------------
index 86c3e724416db7ef13eb3cd7cfbbf5e22c8419c8..213491ed7d29b39097c90ba8d4bb300cd73bad31 100644 (file)
@@ -1,4 +1,4 @@
-.. SPDX-FileCopyrightText: 2018-2021  kaliko <kaliko@azylum.org>
+.. SPDX-FileCopyrightText: 2018-2023  kaliko <kaliko@azylum.org>
 .. SPDX-License-Identifier: LGPL-3.0-or-later
 
 Using the client library
@@ -86,15 +86,18 @@ Command lists are also supported using `command_list_ok_begin()` and
 Ranges
 ------
 
-Provide a 2-tuple as argument for command supporting ranges (cf. `MPD protocol documentation`_ for more details).
-Possible ranges are: "START:END", "START:" and ":" :
+Some commands (e.g. delete) allow specifying a range in the form `"START:END"` (cf. `MPD protocol documentation`_ for more details).
+
+Possible ranges are: `"START:END"`, `"START:"` and `":"` :
+
+Instead of giving the plain string as `"START:END"`, you **can** provide a :py:obj:`tuple` as `(START,END)`. The module is then ensuring the format is correct and raises an :py:obj:`musicpd.CommandError` exception otherwise. Empty start or end can be specified as en empty string `''` or :py:obj:`None`.
 
 .. code-block:: python
 
     # An intelligent clear
     # clears played track in the queue, currentsong included
     pos = client.currentsong().get('pos', 0)
-    # the 2-tuple range object accepts str, no need to convert to int
+    # the range object accepts str, no need to convert to int
     client.delete((0, pos))
     # missing end interpreted as highest value possible, pay attention still need a tuple.
     client.delete((pos,))  # purge queue from current to the end
@@ -109,6 +112,8 @@ as a single colon as argument (i.e. sending just ":"):
 
 Empty start in range (i.e. ":END") are not possible and will raise a CommandError.
 
+Remember the of the tuple is optional, range can still be specified as single string `START:END`. In case of malformed range a CommandError is still raised.
+
 Iterators
 ----------
 
index 25abfd9cbbcbc8c380e683e2fc578bf155cfaa6e..7d0918c9e0f33277835452aab1509b4b9967dc00 100644 (file)
@@ -75,28 +75,42 @@ class Range:
 
     def __init__(self, tpl):
         self.tpl = tpl
+        self.lower = ''
+        self.upper = ''
         self._check()
 
     def __str__(self):
-        if len(self.tpl) == 0:
-            return ':'
-        if len(self.tpl) == 1:
-            return '{0}:'.format(self.tpl[0])
-        return '{0[0]}:{0[1]}'.format(self.tpl)
+        return f'{self.lower}:{self.upper}'
 
     def __repr__(self):
-        return 'Range({0})'.format(self.tpl)
+        return f'Range({self.tpl})'
+
+    def _check_element(self, item):
+        if item is None or item == '':
+            return ''
+        try:
+            return str(int(item))
+        except (TypeError, ValueError) as err:
+            raise CommandError(f'Not an integer: "{item}"') from err
+        return item
 
     def _check(self):
         if not isinstance(self.tpl, tuple):
             raise CommandError('Wrong type, provide a tuple')
-        if len(self.tpl) not in [0, 1, 2]:
-            raise CommandError('length not in [0, 1, 2]')
-        for index in self.tpl:
-            try:
-                index = int(index)
-            except (TypeError, ValueError) as err:
-                raise CommandError('Not a tuple of int') from err
+        if len(self.tpl) == 0:
+            return
+        if len(self.tpl) == 1:
+            self.lower = self._check_element(self.tpl[0])
+            return
+        if len(self.tpl) != 2:
+            raise CommandError('Range wrong size (0, 1 or 2 allowed)')
+        self.lower = self._check_element(self.tpl[0])
+        self.upper = self._check_element(self.tpl[1])
+        if self.lower == '' and self.upper != '':
+            raise CommandError(f'Integer expected to start the range: {self.tpl}')
+        if self.upper.isdigit() and self.lower.isdigit():
+            if int(self.lower) > int(self.upper):
+                raise CommandError(f'Wrong range: {self.lower} > {self.upper}')
 
 
 class _NotConnected:
diff --git a/test.py b/test.py
index d90bd40962002456c96cf01781d713d8c2ec6b2b..0677905a800f319e4cf89318f910007df8061607 100755 (executable)
--- a/test.py
+++ b/test.py
@@ -614,5 +614,35 @@ class testContextManager(unittest.TestCase):
                 sock.close.assert_not_called()
             sock.close.assert_called()
 
+class testRange(unittest.TestCase):
+
+    def test_range(self):
+        tests = [
+                ((),          ':'),
+                ((None,None), ':'),
+                (('',''),     ':'),
+                (('',),       ':'),
+                ((42,42),     '42:42'),
+                ((42,),       '42:'),
+                (('42',),     '42:'),
+                (('42',None), '42:'),
+                (('42',''),   '42:'),
+        ]
+        for tpl, result in tests:
+            self.assertEqual(str(musicpd.Range(tpl)), result)
+        with self.assertRaises(musicpd.CommandError):
+            #CommandError: Integer expected to start the range: (None, 42)
+            musicpd.Range((None,'42'))
+        with self.assertRaises(musicpd.CommandError):
+            # CommandError: Not an integer: "foo"
+            musicpd.Range(('foo',))
+        with self.assertRaises(musicpd.CommandError):
+            # CommandError: Wrong range: 42 > 41
+            musicpd.Range(('42',41))
+        with self.assertRaises(musicpd.CommandError):
+            # CommandError: Wrong range: 42 > 41
+            musicpd.Range(('42','42','42'))
+
+
 if __name__ == '__main__':
     unittest.main()