diff --git a/CHANGES/1010.feature.rst b/CHANGES/1010.feature.rst new file mode 120000 index 000000000..f6c34fe97 --- /dev/null +++ b/CHANGES/1010.feature.rst @@ -0,0 +1 @@ +898.feature.rst \ No newline at end of file diff --git a/CHANGES/774.feature.rst b/CHANGES/774.feature.rst new file mode 120000 index 000000000..f6c34fe97 --- /dev/null +++ b/CHANGES/774.feature.rst @@ -0,0 +1 @@ +898.feature.rst \ No newline at end of file diff --git a/CHANGES/898.feature.rst b/CHANGES/898.feature.rst new file mode 100644 index 000000000..15216245e --- /dev/null +++ b/CHANGES/898.feature.rst @@ -0,0 +1 @@ +Added :meth:`URL.without_query_params() ` method, to drop some parameters from query string -- by :user:`hongquan`. diff --git a/docs/api.rst b/docs/api.rst index 84c009dd2..05a4db613 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -733,6 +733,16 @@ section generates a new :class:`URL` instance. Support subclasses of :class:`int` (except :class:`bool`) and :class:`float` as a query parameter value. +.. method:: URL.without_query_params(*query_params) + + Return a new URL whose *query* part does not contain specified ``query_params``. + + Accepts :class:`str` for ``query_params``. + + It does nothing if none of specified ``query_params`` are present in the query. + + .. versionadded:: 1.10.0 + .. method:: URL.with_fragment(fragment) Return a new URL with *fragment* replaced, auto-encode *fragment* if needed. diff --git a/tests/test_url_query.py b/tests/test_url_query.py index 0a6fe3fd0..6772ef342 100644 --- a/tests/test_url_query.py +++ b/tests/test_url_query.py @@ -1,4 +1,4 @@ -from typing import List, Tuple +from typing import List, Sequence, Tuple from urllib.parse import parse_qs, urlencode import pytest @@ -171,3 +171,41 @@ def test_query_from_empty_update_query( if "b" in original_url.query: assert new_url.query["b"] == original_url.query["b"] + + +@pytest.mark.parametrize( + ("original_query_string", "keys_to_drop", "expected_query_string"), + [ + ("a=10&b=M%C3%B9a+xu%C3%A2n&u%E1%BB%91ng=cafe", ["a"], "b=Mùa xuân&uống=cafe"), + ("a=10&b=M%C3%B9a+xu%C3%A2n", ["b"], "a=10"), + ("a=10&b=M%C3%B9a+xu%C3%A2n&c=30", ["b"], "a=10&c=30"), + ( + "a=10&b=M%C3%B9a+xu%C3%A2n&u%E1%BB%91ng=cafe", + ["uống"], + "a=10&b=Mùa xuân", + ), + ("a=10&b=M%C3%B9a+xu%C3%A2n", ["a", "b"], ""), + ], +) +def test_without_query_params( + original_query_string: str, keys_to_drop: Sequence[str], expected_query_string: str +) -> None: + url = URL(f"http://example.com?{original_query_string}") + new_url = url.without_query_params(*keys_to_drop) + assert new_url.query_string == expected_query_string + assert new_url is not url + + +@pytest.mark.parametrize( + ("original_query_string", "keys_to_drop"), + [ + ("a=10&b=M%C3%B9a+xu%C3%A2n&c=30", ["invalid_key"]), + ("a=10&b=M%C3%B9a+xu%C3%A2n", []), + ], +) +def test_skip_dropping_query_params( + original_query_string: str, keys_to_drop: Sequence[str] +) -> None: + url = URL(f"http://example.com?{original_query_string}") + new_url = url.without_query_params(*keys_to_drop) + assert new_url is url diff --git a/yarl/_url.py b/yarl/_url.py index dd8b0bbc8..3e19caeda 100644 --- a/yarl/_url.py +++ b/yarl/_url.py @@ -1162,6 +1162,19 @@ def update_query(self, *args: Any, **kwargs: Any) -> "URL": self._val._replace(query=self._get_str_query(query) or ""), encoded=True ) + def without_query_params(self, *query_params: str) -> "URL": + """Remove some keys from query part and return new URL.""" + params_to_remove = set(query_params) & self.query.keys() + if not params_to_remove: + return self + return self.with_query( + tuple( + (name, value) + for name, value in self.query.items() + if name not in params_to_remove + ) + ) + def with_fragment(self, fragment: Union[str, None]) -> "URL": """Return a new URL with fragment replaced.