From 7bda2766db0e7d4a9c6e29509ed4ef88766d4694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20P=2E=20Santos?= Date: Wed, 18 Oct 2023 22:47:41 -0300 Subject: [PATCH] Allow both `int` and `str` for limit and offset Update the `validate_input` function in `utils.py` to allow for both `int` and `str` types for the `limit` and `offset` parameters in the `search`. --- pyradios/radios.py | 1 - pyradios/utils.py | 77 +++++++++++++++++++++++++-------------------- tests/test_utils.py | 45 +++++++++++++++++++++----- 3 files changed, 81 insertions(+), 42 deletions(-) diff --git a/pyradios/radios.py b/pyradios/radios.py index e0c40d7..98c0cb9 100644 --- a/pyradios/radios.py +++ b/pyradios/radios.py @@ -466,7 +466,6 @@ def search(self, **kwargs): limit (int, optional): Number of returned datarows (stations) starting with offset (default 100000) hidebroken (bool, optional): do list/not list broken stations. - Note: Not documented in the "Advanced Station Search". Returns: list: Stations. diff --git a/pyradios/utils.py b/pyradios/utils.py index 53ced5d..22819b5 100644 --- a/pyradios/utils.py +++ b/pyradios/utils.py @@ -2,35 +2,35 @@ types = { - "search": { - "name": str, - "name_exact": bool, - "codec": str, - "codec_exact": bool, - "country": str, - "country_exact": bool, - "countrycode": str, - "state": str, - "state_exact": bool, - "language": str, - "language_exact": bool, - "tag": str, - "tag_exact": bool, - "tag_list": str, - "bitrate_min": int, - "bitrate_max": int, - "order": str, - "reverse": bool, - "offset": int, - "limit": int, - "hidebroken": bool, # Not documented in the "Advanced Station Search" + 'search': { + 'name': str, + 'name_exact': bool, + 'codec': str, + 'codec_exact': bool, + 'country': str, + 'country_exact': bool, + 'countrycode': str, + 'state': str, + 'state_exact': bool, + 'language': str, + 'language_exact': bool, + 'tag': str, + 'tag_exact': bool, + 'tag_list': str, + 'bitrate_min': int, + 'bitrate_max': int, + 'order': str, + 'reverse': bool, + 'offset': (int, str), + 'limit': (int, str), + 'hidebroken': bool, }, - "countries": {"code": str}, - "countrycodes": {"code": str}, - "codecs": {"codec": str}, - "states": {"country": str, "state": str}, - "languages": {"language": str}, - "tags": {"tag": str}, + 'countries': {'code': str}, + 'countrycodes': {'code': str}, + 'codecs': {'codec': str}, + 'states': {'country': str, 'state': str}, + 'languages': {'language': str}, + 'tags': {'tag': str}, } @@ -59,14 +59,14 @@ def bool_to_string(b): str: String representation of a bool type. """ s = str(b).lower() - if s in ["true", "false"]: + if s in ['true', 'false']: return s - raise TypeError("Value must be True or False.") + raise TypeError('Value must be True or False.') def snake_to_camel(s): - first, *others = s.split("_") - return "".join([first.lower(), *map(str.title, others)]) + first, *others = s.split('_') + return ''.join([first.lower(), *map(str.title, others)]) def radio_browser_adapter(**kwargs): @@ -88,10 +88,19 @@ def validate_input(types, input_data): raise IllegalArgumentError( "There is no paramter named '{}'".format(exc.args[0]) ) - else: + if key in ("limit", "offset"): + if not str(value).isdigit(): + raise TypeError( + 'Argument {!r} must be {}, not {}'.format( + key, + "`int` or `str`", + type(value).__name__, + ) + ) + elif not isinstance(value, type_): if not isinstance(value, type_): raise TypeError( - "Argument {!r} must be {}, not {}".format( + 'Argument {!r} must be {}, not {}'.format( key, type_.__name__, type(value).__name__, diff --git a/tests/test_utils.py b/tests/test_utils.py index 572e7cd..1362b8a 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,24 +1,55 @@ import pytest +from pyradios.utils import types from pyradios.utils import bool_to_string +from pyradios.utils import validate_input -@pytest.mark.parametrize( - "inp, expected", - [(True, "true"), (False, "false")] -) +@pytest.mark.parametrize('inp, expected', [(True, 'true'), (False, 'false')]) def test_bool_to_string(inp, expected): assert bool_to_string(inp) == expected @pytest.mark.parametrize( - "inp, expected", + 'inp, expected', [ - ("a", pytest.raises(TypeError)), + ('a', pytest.raises(TypeError)), (1, pytest.raises(TypeError)), ], ) def test_bool_to_string_value_error(inp, expected): with expected as exc_info: bool_to_string(inp) - assert "Value must be True or False." == str(exc_info.value) + assert 'Value must be True or False.' == str(exc_info.value) + + +@pytest.mark.parametrize( + 'input_data', + [{'limit': 10, 'offset': 0}, {'limit': '10', 'offset': '0'}], +) +def test_validate_input(input_data): + """ + Allow `str` and `int` for limit and offset + """ + validate_input(types['search'], input_data) + + +@pytest.mark.parametrize( + 'input_data, expected', + [ + ({'limit': 10, 'offset': 'a'}, ('offset', '`int` or `str`', 'str')), + ({'limit': 10.1}, ('limit', '`int` or `str`', 'float')), + ( + {'offset': True, 'limit': None}, + ('offset', '`int` or `str`', 'bool'), + ), + ({'limit': None}, ('limit', '`int` or `str`', 'NoneType')), + ], +) +def test_validate_input_with_invalid_limit_and_offset(input_data, expected): + with pytest.raises(TypeError) as exc: + validate_input(types['search'], input_data) + + assert str(exc.value) == 'Argument {!r} must be {}, not {}'.format( + *expected + )