From eaddc6fe9f37b0c6345dfdb4b82569b394cdac26 Mon Sep 17 00:00:00 2001 From: Exa Date: Sat, 8 Aug 2020 16:50:28 +0300 Subject: [PATCH 1/7] Allow int Subclasses in Query I have found myself having to do explicit type conversions of `int` subclasses that should definitely work. This new check allows all `int` subclasses through except `bool`, which should obviously be rejected. --- yarl/_url.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarl/_url.py b/yarl/_url.py index 563b53d2a..ae13fffa5 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -904,7 +904,7 @@ def _query_seq_pairs(cls, quoter, pairs): def _query_var(v): if isinstance(v, str): return v - if type(v) is int: # no subclasses like bool + if isinstance(v, int) and not isinstance(v, bool): # no subclasses like bool return str(v) raise TypeError( "Invalid variable type: value " From de3e902aa008cbacbcb10fd66282a80fe3425285 Mon Sep 17 00:00:00 2001 From: Exahilosys Date: Mon, 17 Aug 2020 12:51:48 +0300 Subject: [PATCH 2/7] Allow float Subclasses in Query --- yarl/_url.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarl/_url.py b/yarl/_url.py index ae13fffa5..595a70e7d 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -904,11 +904,11 @@ def _query_seq_pairs(cls, quoter, pairs): def _query_var(v): if isinstance(v, str): return v - if isinstance(v, int) and not isinstance(v, bool): # no subclasses like bool + if isinstance(v, (int, float)) and not isinstance(v, bool): return str(v) raise TypeError( "Invalid variable type: value " - "should be str or int, got {!r} " + "should be str, int or float, got {!r} " "of type {}".format(v, type(v)) ) From 3ba730ee65279218d5628381185ef404a15795d0 Mon Sep 17 00:00:00 2001 From: Exahilosys Date: Mon, 17 Aug 2020 12:52:27 +0300 Subject: [PATCH 3/7] Add Tests for New Query Types --- tests/test_update_query.py | 63 +++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 9f48c0861..fdc0edc9f 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -155,34 +155,69 @@ def test_with_query_sequence_invalid_use(query): url.with_query(query) -def test_with_query_non_str(): - url = URL("http://example.com") - with pytest.raises(TypeError): - url.with_query({"a": 1.1}) +class _CStr(str): pass +class _CInt(int): pass +class _CFloat(float): pass -def test_with_query_bool(): +@pytest.mark.parametrize( + ("value", "expected"), + [ + pytest.param("1", "1", id="str"), + pytest.param(_CStr("1"), "1", id="custom str"), + pytest.param(1, "1", id="int"), + pytest.param(_CInt(1), "1", id="custom int"), + pytest.param(1.1, "1.1", id="float"), + pytest.param(_CFloat(1.1), "1.1", id="custom float") + ] +) +def test_with_query_valid_type(value, expected): url = URL("http://example.com") - with pytest.raises(TypeError): - url.with_query({"a": True}) + expected = "http://example.com/?a={expected}".format_map(locals()) + assert str(url.with_query({"a": value})) == expected -def test_with_query_none(): +@pytest.mark.parametrize( + ("value"), + [ + pytest.param(True, id="bool"), + pytest.param(None, id="none") + ] +) +def test_with_query_invalid_type(value): url = URL("http://example.com") with pytest.raises(TypeError): - url.with_query({"a": None}) + url.with_query({"a": value}) -def test_with_query_list_non_str(): +@pytest.mark.parametrize( + ("value", "expected"), + [ + pytest.param("1", "1", id="str"), + pytest.param(_CStr("1"), "1", id="custom str"), + pytest.param(1, "1", id="int"), + pytest.param(_CInt(1), "1", id="custom int"), + pytest.param(1.1, "1.1", id="float"), + pytest.param(_CFloat(1.1), "1.1", id="custom float") + ] +) +def test_with_query_list_valid_type(value, expected): url = URL("http://example.com") - with pytest.raises(TypeError): - url.with_query([("a", 1.0)]) + expected = "http://example.com/?a={expected}".format_map(locals()) + assert str(url.with_query([("a", value)])) == expected -def test_with_query_list_bool(): +@pytest.mark.parametrize( + ("value"), + [ + pytest.param(True, id="bool"), + pytest.param(None, id="none") + ] +) +def test_with_query_list_invalid_type(value): url = URL("http://example.com") with pytest.raises(TypeError): - url.with_query([("a", False)]) + url.with_query([("a", value)]) def test_with_query_multidict(): From 83be6981363bf957852d9b4630035702e443a416 Mon Sep 17 00:00:00 2001 From: Exahilosys Date: Mon, 17 Aug 2020 12:59:01 +0300 Subject: [PATCH 4/7] Added News Fragment --- CHANGES/492.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 CHANGES/492.feature diff --git a/CHANGES/492.feature b/CHANGES/492.feature new file mode 100644 index 000000000..4dfc96846 --- /dev/null +++ b/CHANGES/492.feature @@ -0,0 +1 @@ +Allow for int and float subclasses in query, while still denying bool. From e7c089fa4120d2dfb43ac595a7c7fbab79fa26cc Mon Sep 17 00:00:00 2001 From: Exahilosys Date: Mon, 17 Aug 2020 13:06:41 +0300 Subject: [PATCH 5/7] Linted Code --- tests/test_update_query.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index fdc0edc9f..54613dc11 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -155,9 +155,16 @@ def test_with_query_sequence_invalid_use(query): url.with_query(query) -class _CStr(str): pass -class _CInt(int): pass -class _CFloat(float): pass +class _CStr(str): + pass + + +class _CInt(int): + pass + + +class _CFloat(float): + pass @pytest.mark.parametrize( @@ -168,8 +175,8 @@ class _CFloat(float): pass pytest.param(1, "1", id="int"), pytest.param(_CInt(1), "1", id="custom int"), pytest.param(1.1, "1.1", id="float"), - pytest.param(_CFloat(1.1), "1.1", id="custom float") - ] + pytest.param(_CFloat(1.1), "1.1", id="custom float"), + ], ) def test_with_query_valid_type(value, expected): url = URL("http://example.com") @@ -178,11 +185,7 @@ def test_with_query_valid_type(value, expected): @pytest.mark.parametrize( - ("value"), - [ - pytest.param(True, id="bool"), - pytest.param(None, id="none") - ] + ("value"), [pytest.param(True, id="bool"), pytest.param(None, id="none")] ) def test_with_query_invalid_type(value): url = URL("http://example.com") @@ -198,8 +201,8 @@ def test_with_query_invalid_type(value): pytest.param(1, "1", id="int"), pytest.param(_CInt(1), "1", id="custom int"), pytest.param(1.1, "1.1", id="float"), - pytest.param(_CFloat(1.1), "1.1", id="custom float") - ] + pytest.param(_CFloat(1.1), "1.1", id="custom float"), + ], ) def test_with_query_list_valid_type(value, expected): url = URL("http://example.com") @@ -208,11 +211,7 @@ def test_with_query_list_valid_type(value, expected): @pytest.mark.parametrize( - ("value"), - [ - pytest.param(True, id="bool"), - pytest.param(None, id="none") - ] + ("value"), [pytest.param(True, id="bool"), pytest.param(None, id="none")] ) def test_with_query_list_invalid_type(value): url = URL("http://example.com") From 262b486b742a6fc74fb6e2afe7400db15d417a3f Mon Sep 17 00:00:00 2001 From: Exahilosys Date: Mon, 17 Aug 2020 13:25:47 +0300 Subject: [PATCH 6/7] Use cls and int.__str__ --- yarl/_url.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/yarl/_url.py b/yarl/_url.py index 595a70e7d..648214352 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -902,14 +902,15 @@ def _query_seq_pairs(cls, quoter, pairs): @staticmethod def _query_var(v): - if isinstance(v, str): + cls = type(v) + if issubclass(cls, str): return v - if isinstance(v, (int, float)) and not isinstance(v, bool): - return str(v) + if issubclass(cls, (int, float)) and not cls is bool: + return int.__str__(v) # same as float.__str__ raise TypeError( "Invalid variable type: value " "should be str, int or float, got {!r} " - "of type {}".format(v, type(v)) + "of type {}".format(v, cls) ) def _get_str_query(self, *args, **kwargs): From f1038da3d3448e7d9f625fbfefc681fc676aa6dc Mon Sep 17 00:00:00 2001 From: Exahilosys Date: Mon, 17 Aug 2020 13:35:39 +0300 Subject: [PATCH 7/7] Added Weird __str__ and Finity Checks and Tests --- tests/test_update_query.py | 16 +++++++++++++--- yarl/_url.py | 8 ++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/test_update_query.py b/tests/test_update_query.py index 54613dc11..5e6c4d08c 100644 --- a/tests/test_update_query.py +++ b/tests/test_update_query.py @@ -159,11 +159,16 @@ class _CStr(str): pass -class _CInt(int): +class _EmptyStrEr: + def __str__(self): + return "" + + +class _CInt(int, _EmptyStrEr): pass -class _CFloat(float): +class _CFloat(float, _EmptyStrEr): pass @@ -185,7 +190,12 @@ def test_with_query_valid_type(value, expected): @pytest.mark.parametrize( - ("value"), [pytest.param(True, id="bool"), pytest.param(None, id="none")] + ("value"), + [ + pytest.param(True, id="bool"), + pytest.param(None, id="none"), + pytest.param(float("inf"), id="non-finite float"), + ], ) def test_with_query_invalid_type(value): url = URL("http://example.com") diff --git a/yarl/_url.py b/yarl/_url.py index 648214352..b349a44ef 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -8,6 +8,8 @@ from multidict import MultiDict, MultiDictProxy import idna +import math + from ._quoting import _Quoter, _Unquoter @@ -905,8 +907,10 @@ def _query_var(v): cls = type(v) if issubclass(cls, str): return v - if issubclass(cls, (int, float)) and not cls is bool: - return int.__str__(v) # same as float.__str__ + if issubclass(cls, (int, float)) and cls is not bool: + if not math.isfinite(v): + raise TypeError("Value should be finite") + return int.__str__(v) # same as float.__str__ raise TypeError( "Invalid variable type: value " "should be str, int or float, got {!r} "