prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Path encoding broken for non-ASCII in WSGI mode
  ### Describe the bug
  
  When using httpx to call into a WSGI application (my preferred way to test Flask app nowadays), the web view receives urlencoded paths.
  
  I'm not sure where this really belongs to (is it a bug in httpx or Flask!?), but I've been working around it for months, so I guess I'll make myself unpopular and try it here first. If it turns out to be Flask's fault, I'l headl over and bother the Pallets folks.
  
  ### To reproduce
  
  ```python
  from flask import Flask, request
  
  import httpx
  
  
  app = Flask(__name__)
  
  @app.route('/<path>')
  def hello_world(path):
      return f"path: { path }, query: { request.args['a'] }, url: { request.url }"
  
  
  if __name__ == "__main__":
      with httpx.Client(app=app, base_url="http://testserver") as client:
          resp = client.get("/ä", params={"a": "ä"})
  
      print("httpx", resp.text)
  
      with app.test_client() as client:
          resp = client.get("/ä?a=%C3%A4")
  
      print("flask", resp.get_data().decode("utf-8"))
  ```
  
  ### Expected behavior
  
  ```
  httpx path: ä, query: ä, url: http://testserver/ä?a=ä
  flask path: ä, query: ä, url: http://localhost/ä?a=ä
  ```
  
  ### Actual behavior
  
  ```
  httpx path: %C3%A4, query: ä, url: http://testserver/%25C3%25A4?a=ä
  flask path: ä, query: ä, url: http://localhost/ä?a=ä
  ```
  
  NB
  
  -  it seems to handle query parameters just fine.
  - `%25` is the ASCII code of the percent sign
  
  ### Environment
  
  - OS: macOS
  - Python version: 3.8.6
  - HTTPX version: 0.16.1
  - Async environment: n/a
  - HTTP proxy: n/a
  - Custom certificates: no
  
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `httpx/_transports/wsgi.py`
  Content:
  ```
  1 import io
  2 import itertools
  3 import typing
  4 
  5 import httpcore
  6 
  7 
  8 def _skip_leading_empty_chunks(body: typing.Iterable) -> typing.Iterable:
  9     body = iter(body)
  10     for chunk in body:
  11         if chunk:
  12             return itertools.chain([chunk], body)
  13     return []
  14 
  15 
  16 class WSGITransport(httpcore.SyncHTTPTransport):
  17     """
  18     A custom transport that handles sending requests directly to an WSGI app.
  19     The simplest way to use this functionality is to use the `app` argument.
  20 
  21     ```
  22     client = httpx.Client(app=app)
  23     ```
  24 
  25     Alternatively, you can setup the transport instance explicitly.
  26     This allows you to include any additional configuration arguments specific
  27     to the WSGITransport class:
  28 
  29     ```
  30     transport = httpx.WSGITransport(
  31         app=app,
  32         script_name="/submount",
  33         remote_addr="1.2.3.4"
  34     )
  35     client = httpx.Client(transport=transport)
  36     ```
  37 
  38     Arguments:
  39 
  40     * `app` - The ASGI application.
  41     * `raise_app_exceptions` - Boolean indicating if exceptions in the application
  42        should be raised. Default to `True`. Can be set to `False` for use cases
  43        such as testing the content of a client 500 response.
  44     * `script_name` - The root path on which the ASGI application should be mounted.
  45     * `remote_addr` - A string indicating the client IP of incoming requests.
  46     ```
  47     """
  48 
  49     def __init__(
  50         self,
  51         app: typing.Callable,
  52         raise_app_exceptions: bool = True,
  53         script_name: str = "",
  54         remote_addr: str = "127.0.0.1",
  55     ) -> None:
  56         self.app = app
  57         self.raise_app_exceptions = raise_app_exceptions
  58         self.script_name = script_name
  59         self.remote_addr = remote_addr
  60 
  61     def request(
  62         self,
  63         method: bytes,
  64         url: typing.Tuple[bytes, bytes, typing.Optional[int], bytes],
  65         headers: typing.List[typing.Tuple[bytes, bytes]] = None,
  66         stream: httpcore.SyncByteStream = None,
  67         ext: dict = None,
  68     ) -> typing.Tuple[
  69         int, typing.List[typing.Tuple[bytes, bytes]], httpcore.SyncByteStream, dict
  70     ]:
  71         headers = [] if headers is None else headers
  72         stream = httpcore.PlainByteStream(content=b"") if stream is None else stream
  73 
  74         scheme, host, port, full_path = url
  75         path, _, query = full_path.partition(b"?")
  76         environ = {
  77             "wsgi.version": (1, 0),
  78             "wsgi.url_scheme": scheme.decode("ascii"),
  79             "wsgi.input": io.BytesIO(b"".join(stream)),
  80             "wsgi.errors": io.BytesIO(),
  81             "wsgi.multithread": True,
  82             "wsgi.multiprocess": False,
  83             "wsgi.run_once": False,
  84             "REQUEST_METHOD": method.decode(),
  85             "SCRIPT_NAME": self.script_name,
  86             "PATH_INFO": path.decode("ascii"),
  87             "QUERY_STRING": query.decode("ascii"),
  88             "SERVER_NAME": host.decode("ascii"),
  89             "SERVER_PORT": str(port),
  90             "REMOTE_ADDR": self.remote_addr,
  91         }
  92         for header_key, header_value in headers:
  93             key = header_key.decode("ascii").upper().replace("-", "_")
  94             if key not in ("CONTENT_TYPE", "CONTENT_LENGTH"):
  95                 key = "HTTP_" + key
  96             environ[key] = header_value.decode("ascii")
  97 
  98         seen_status = None
  99         seen_response_headers = None
  100         seen_exc_info = None
  101 
  102         def start_response(
  103             status: str, response_headers: list, exc_info: typing.Any = None
  104         ) -> None:
  105             nonlocal seen_status, seen_response_headers, seen_exc_info
  106             seen_status = status
  107             seen_response_headers = response_headers
  108             seen_exc_info = exc_info
  109 
  110         result = self.app(environ, start_response)
  111         # This is needed because the status returned by start_response
  112         # shouldn't be used until the first non-empty chunk has been served.
  113         result = _skip_leading_empty_chunks(result)
  114 
  115         assert seen_status is not None
  116         assert seen_response_headers is not None
  117         if seen_exc_info and self.raise_app_exceptions:
  118             raise seen_exc_info[1]
  119 
  120         status_code = int(seen_status.split()[0])
  121         headers = [
  122             (key.encode("ascii"), value.encode("ascii"))
  123             for key, value in seen_response_headers
  124         ]
  125         stream = httpcore.IteratorByteStream(iterator=result)
  126         ext = {}
  127 
  128         return (status_code, headers, stream, ext)
  129
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/httpx/_transports/wsgi.py b/httpx/_transports/wsgi.py
  --- a/httpx/_transports/wsgi.py
  +++ b/httpx/_transports/wsgi.py
  @@ -1,6 +1,7 @@
   import io
   import itertools
   import typing
  +from urllib.parse import unquote
   
   import httpcore
   
  @@ -83,7 +84,7 @@
               "wsgi.run_once": False,
               "REQUEST_METHOD": method.decode(),
               "SCRIPT_NAME": self.script_name,
  -            "PATH_INFO": path.decode("ascii"),
  +            "PATH_INFO": unquote(path.decode("ascii")),
               "QUERY_STRING": query.decode("ascii"),
               "SERVER_NAME": host.decode("ascii"),
               "SERVER_PORT": str(port),
  
verification_info: {"golden_diff": "diff --git a/httpx/_transports/wsgi.py b/httpx/_transports/wsgi.py\n--- a/httpx/_transports/wsgi.py\n+++ b/httpx/_transports/wsgi.py\n@@ -1,6 +1,7 @@\n import io\n import itertools\n import typing\n+from urllib.parse import unquote\n \n import httpcore\n \n@@ -83,7 +84,7 @@\n             \"wsgi.run_once\": False,\n             \"REQUEST_METHOD\": method.decode(),\n             \"SCRIPT_NAME\": self.script_name,\n-            \"PATH_INFO\": path.decode(\"ascii\"),\n+            \"PATH_INFO\": unquote(path.decode(\"ascii\")),\n             \"QUERY_STRING\": query.decode(\"ascii\"),\n             \"SERVER_NAME\": host.decode(\"ascii\"),\n             \"SERVER_PORT\": str(port),\n", "issue": "Path encoding broken for non-ASCII in WSGI mode\n### Describe the bug\r\n\r\nWhen using httpx to call into a WSGI application (my preferred way to test Flask app nowadays), the web view receives urlencoded paths.\r\n\r\nI'm not sure where this really belongs to (is it a bug in httpx or Flask!?), but I've been working around it for months, so I guess I'll make myself unpopular and try it here first. If it turns out to be Flask's fault, I'l headl over and bother the Pallets folks.\r\n\r\n### To reproduce\r\n\r\n```python\r\nfrom flask import Flask, request\r\n\r\nimport httpx\r\n\r\n\r\napp = Flask(__name__)\r\n\r\n@app.route('/<path>')\r\ndef hello_world(path):\r\n    return f\"path: { path }, query: { request.args['a'] }, url: { request.url }\"\r\n\r\n\r\nif __name__ == \"__main__\":\r\n    with httpx.Client(app=app, base_url=\"http://testserver\") as client:\r\n        resp = client.get(\"/\u00e4\", params={\"a\": \"\u00e4\"})\r\n\r\n    print(\"httpx\", resp.text)\r\n\r\n    with app.test_client() as client:\r\n        resp = client.get(\"/\u00e4?a=%C3%A4\")\r\n\r\n    print(\"flask\", resp.get_data().decode(\"utf-8\"))\r\n```\r\n\r\n### Expected behavior\r\n\r\n```\r\nhttpx path: \u00e4, query: \u00e4, url: http://testserver/\u00e4?a=\u00e4\r\nflask path: \u00e4, query: \u00e4, url: http://localhost/\u00e4?a=\u00e4\r\n```\r\n\r\n### Actual behavior\r\n\r\n```\r\nhttpx path: %C3%A4, query: \u00e4, url: http://testserver/%25C3%25A4?a=\u00e4\r\nflask path: \u00e4, query: \u00e4, url: http://localhost/\u00e4?a=\u00e4\r\n```\r\n\r\nNB\r\n\r\n-  it seems to handle query parameters just fine.\r\n- `%25` is the ASCII code of the percent sign\r\n\r\n### Environment\r\n\r\n- OS: macOS\r\n- Python version: 3.8.6\r\n- HTTPX version: 0.16.1\r\n- Async environment: n/a\r\n- HTTP proxy: n/a\r\n- Custom certificates: no\r\n\n", "before_files": [{"content": "import io\nimport itertools\nimport typing\n\nimport httpcore\n\n\ndef _skip_leading_empty_chunks(body: typing.Iterable) -> typing.Iterable:\n    body = iter(body)\n    for chunk in body:\n        if chunk:\n            return itertools.chain([chunk], body)\n    return []\n\n\nclass WSGITransport(httpcore.SyncHTTPTransport):\n    \"\"\"\n    A custom transport that handles sending requests directly to an WSGI app.\n    The simplest way to use this functionality is to use the `app` argument.\n\n    ```\n    client = httpx.Client(app=app)\n    ```\n\n    Alternatively, you can setup the transport instance explicitly.\n    This allows you to include any additional configuration arguments specific\n    to the WSGITransport class:\n\n    ```\n    transport = httpx.WSGITransport(\n        app=app,\n        script_name=\"/submount\",\n        remote_addr=\"1.2.3.4\"\n    )\n    client = httpx.Client(transport=transport)\n    ```\n\n    Arguments:\n\n    * `app` - The ASGI application.\n    * `raise_app_exceptions` - Boolean indicating if exceptions in the application\n       should be raised. Default to `True`. Can be set to `False` for use cases\n       such as testing the content of a client 500 response.\n    * `script_name` - The root path on which the ASGI application should be mounted.\n    * `remote_addr` - A string indicating the client IP of incoming requests.\n    ```\n    \"\"\"\n\n    def __init__(\n        self,\n        app: typing.Callable,\n        raise_app_exceptions: bool = True,\n        script_name: str = \"\",\n        remote_addr: str = \"127.0.0.1\",\n    ) -> None:\n        self.app = app\n        self.raise_app_exceptions = raise_app_exceptions\n        self.script_name = script_name\n        self.remote_addr = remote_addr\n\n    def request(\n        self,\n        method: bytes,\n        url: typing.Tuple[bytes, bytes, typing.Optional[int], bytes],\n        headers: typing.List[typing.Tuple[bytes, bytes]] = None,\n        stream: httpcore.SyncByteStream = None,\n        ext: dict = None,\n    ) -> typing.Tuple[\n        int, typing.List[typing.Tuple[bytes, bytes]], httpcore.SyncByteStream, dict\n    ]:\n        headers = [] if headers is None else headers\n        stream = httpcore.PlainByteStream(content=b\"\") if stream is None else stream\n\n        scheme, host, port, full_path = url\n        path, _, query = full_path.partition(b\"?\")\n        environ = {\n            \"wsgi.version\": (1, 0),\n            \"wsgi.url_scheme\": scheme.decode(\"ascii\"),\n            \"wsgi.input\": io.BytesIO(b\"\".join(stream)),\n            \"wsgi.errors\": io.BytesIO(),\n            \"wsgi.multithread\": True,\n            \"wsgi.multiprocess\": False,\n            \"wsgi.run_once\": False,\n            \"REQUEST_METHOD\": method.decode(),\n            \"SCRIPT_NAME\": self.script_name,\n            \"PATH_INFO\": path.decode(\"ascii\"),\n            \"QUERY_STRING\": query.decode(\"ascii\"),\n            \"SERVER_NAME\": host.decode(\"ascii\"),\n            \"SERVER_PORT\": str(port),\n            \"REMOTE_ADDR\": self.remote_addr,\n        }\n        for header_key, header_value in headers:\n            key = header_key.decode(\"ascii\").upper().replace(\"-\", \"_\")\n            if key not in (\"CONTENT_TYPE\", \"CONTENT_LENGTH\"):\n                key = \"HTTP_\" + key\n            environ[key] = header_value.decode(\"ascii\")\n\n        seen_status = None\n        seen_response_headers = None\n        seen_exc_info = None\n\n        def start_response(\n            status: str, response_headers: list, exc_info: typing.Any = None\n        ) -> None:\n            nonlocal seen_status, seen_response_headers, seen_exc_info\n            seen_status = status\n            seen_response_headers = response_headers\n            seen_exc_info = exc_info\n\n        result = self.app(environ, start_response)\n        # This is needed because the status returned by start_response\n        # shouldn't be used until the first non-empty chunk has been served.\n        result = _skip_leading_empty_chunks(result)\n\n        assert seen_status is not None\n        assert seen_response_headers is not None\n        if seen_exc_info and self.raise_app_exceptions:\n            raise seen_exc_info[1]\n\n        status_code = int(seen_status.split()[0])\n        headers = [\n            (key.encode(\"ascii\"), value.encode(\"ascii\"))\n            for key, value in seen_response_headers\n        ]\n        stream = httpcore.IteratorByteStream(iterator=result)\n        ext = {}\n\n        return (status_code, headers, stream, ext)\n", "path": "httpx/_transports/wsgi.py"}], "after_files": [{"content": "import io\nimport itertools\nimport typing\nfrom urllib.parse import unquote\n\nimport httpcore\n\n\ndef _skip_leading_empty_chunks(body: typing.Iterable) -> typing.Iterable:\n    body = iter(body)\n    for chunk in body:\n        if chunk:\n            return itertools.chain([chunk], body)\n    return []\n\n\nclass WSGITransport(httpcore.SyncHTTPTransport):\n    \"\"\"\n    A custom transport that handles sending requests directly to an WSGI app.\n    The simplest way to use this functionality is to use the `app` argument.\n\n    ```\n    client = httpx.Client(app=app)\n    ```\n\n    Alternatively, you can setup the transport instance explicitly.\n    This allows you to include any additional configuration arguments specific\n    to the WSGITransport class:\n\n    ```\n    transport = httpx.WSGITransport(\n        app=app,\n        script_name=\"/submount\",\n        remote_addr=\"1.2.3.4\"\n    )\n    client = httpx.Client(transport=transport)\n    ```\n\n    Arguments:\n\n    * `app` - The ASGI application.\n    * `raise_app_exceptions` - Boolean indicating if exceptions in the application\n       should be raised. Default to `True`. Can be set to `False` for use cases\n       such as testing the content of a client 500 response.\n    * `script_name` - The root path on which the ASGI application should be mounted.\n    * `remote_addr` - A string indicating the client IP of incoming requests.\n    ```\n    \"\"\"\n\n    def __init__(\n        self,\n        app: typing.Callable,\n        raise_app_exceptions: bool = True,\n        script_name: str = \"\",\n        remote_addr: str = \"127.0.0.1\",\n    ) -> None:\n        self.app = app\n        self.raise_app_exceptions = raise_app_exceptions\n        self.script_name = script_name\n        self.remote_addr = remote_addr\n\n    def request(\n        self,\n        method: bytes,\n        url: typing.Tuple[bytes, bytes, typing.Optional[int], bytes],\n        headers: typing.List[typing.Tuple[bytes, bytes]] = None,\n        stream: httpcore.SyncByteStream = None,\n        ext: dict = None,\n    ) -> typing.Tuple[\n        int, typing.List[typing.Tuple[bytes, bytes]], httpcore.SyncByteStream, dict\n    ]:\n        headers = [] if headers is None else headers\n        stream = httpcore.PlainByteStream(content=b\"\") if stream is None else stream\n\n        scheme, host, port, full_path = url\n        path, _, query = full_path.partition(b\"?\")\n        environ = {\n            \"wsgi.version\": (1, 0),\n            \"wsgi.url_scheme\": scheme.decode(\"ascii\"),\n            \"wsgi.input\": io.BytesIO(b\"\".join(stream)),\n            \"wsgi.errors\": io.BytesIO(),\n            \"wsgi.multithread\": True,\n            \"wsgi.multiprocess\": False,\n            \"wsgi.run_once\": False,\n            \"REQUEST_METHOD\": method.decode(),\n            \"SCRIPT_NAME\": self.script_name,\n            \"PATH_INFO\": unquote(path.decode(\"ascii\")),\n            \"QUERY_STRING\": query.decode(\"ascii\"),\n            \"SERVER_NAME\": host.decode(\"ascii\"),\n            \"SERVER_PORT\": str(port),\n            \"REMOTE_ADDR\": self.remote_addr,\n        }\n        for header_key, header_value in headers:\n            key = header_key.decode(\"ascii\").upper().replace(\"-\", \"_\")\n            if key not in (\"CONTENT_TYPE\", \"CONTENT_LENGTH\"):\n                key = \"HTTP_\" + key\n            environ[key] = header_value.decode(\"ascii\")\n\n        seen_status = None\n        seen_response_headers = None\n        seen_exc_info = None\n\n        def start_response(\n            status: str, response_headers: list, exc_info: typing.Any = None\n        ) -> None:\n            nonlocal seen_status, seen_response_headers, seen_exc_info\n            seen_status = status\n            seen_response_headers = response_headers\n            seen_exc_info = exc_info\n\n        result = self.app(environ, start_response)\n        # This is needed because the status returned by start_response\n        # shouldn't be used until the first non-empty chunk has been served.\n        result = _skip_leading_empty_chunks(result)\n\n        assert seen_status is not None\n        assert seen_response_headers is not None\n        if seen_exc_info and self.raise_app_exceptions:\n            raise seen_exc_info[1]\n\n        status_code = int(seen_status.split()[0])\n        headers = [\n            (key.encode(\"ascii\"), value.encode(\"ascii\"))\n            for key, value in seen_response_headers\n        ]\n        stream = httpcore.IteratorByteStream(iterator=result)\n        ext = {}\n\n        return (status_code, headers, stream, ext)\n", "path": "httpx/_transports/wsgi.py"}]}