Repository: sanic-org/sanic

Issue:
request.args.pop removes parameters inconsistently
Environment : 20.12.3, presumably other environments too


Code to reproduce:

send this request to this app
`GET http://localhost:8000/?hello=a&world=4`
```python
from sanic import Sanic


app = Sanic('test')

@app.get('/')
def _(request):
    a = request.args.pop('hello')
    b = request.args.pop('world')
    c = request.args.pop('world')
    d = request.args.pop('world')   << crashes here

app.run()
```

Expected:
crashes at line second call of .pop('world'), or not at all


Discussion:
I have no ideas about this since the RequestParameters seems to be a really straightforward wrapper around the `dict`, would be interested if anyone can reproduce it ... 
Started to look at this a bit more it seems that the problem is not with the implementation of RequestParameters but rather with this somewhere

`    args = property(get_args)`

vs 18.12 where it still works as expected

```
    @property
    def args(self):
        if self.parsed_args is None:
            if self.query_string:
                self.parsed_args = RequestParameters(
                    parse_qs(self.query_string)
                )
            else:
                self.parsed_args = RequestParameters()
        return self.parsed_args
```




Ah I worked it out!

Because parsed_args is a defaultdict, once the request.args is exhausted, it will evaluate as False again, and then a fresh parsing of the query will occur.

Resolving patch:
diff --git a/sanic/request.py b/sanic/request.py
--- a/sanic/request.py
+++ b/sanic/request.py
@@ -378,9 +378,12 @@ def get_args(
         :type errors: str
         :return: RequestParameters
         """
-        if not self.parsed_args[
-            (keep_blank_values, strict_parsing, encoding, errors)
-        ]:
+        if (
+            keep_blank_values,
+            strict_parsing,
+            encoding,
+            errors,
+        ) not in self.parsed_args:
             if self.query_string:
                 self.parsed_args[
                     (keep_blank_values, strict_parsing, encoding, errors)
@@ -434,9 +437,12 @@ def get_query_args(
         :type errors: str
         :return: list
         """
-        if not self.parsed_not_grouped_args[
-            (keep_blank_values, strict_parsing, encoding, errors)
-        ]:
+        if (
+            keep_blank_values,
+            strict_parsing,
+            encoding,
+            errors,
+        ) not in self.parsed_not_grouped_args:
             if self.query_string:
                 self.parsed_not_grouped_args[
                     (keep_blank_values, strict_parsing, encoding, errors)


Test patch:
diff --git a/tests/test_requests.py b/tests/test_requests.py
--- a/tests/test_requests.py
+++ b/tests/test_requests.py
@@ -291,6 +291,17 @@ async def handler(request):
     assert request.args.getlist("test1") == ["1"]
     assert request.args.get("test3", default="My value") == "My value"
 
+def test_popped_stays_popped(app):
+    @app.route("/")
+    async def handler(request):
+        return text("OK")
+
+    request, response = app.test_client.get(
+        "/", params=[("test1", "1")]
+    )
+
+    assert request.args.pop("test1") == ["1"]
+    assert "test1" not in request.args
 
 @pytest.mark.asyncio
 async def test_query_string_asgi(app):