internlm/SWE-Fixer-Train-110K huggingface dataset:
Repository: encode/starlette

Issue:
Wildcard domains and TrustedhostMiddleware
Support for wildcard domains


Discussion:
@tomchristie Tests passed but the check fails   

Resolving patch:
diff --git a/starlette/middleware/trustedhost.py b/starlette/middleware/trustedhost.py
--- a/starlette/middleware/trustedhost.py
+++ b/starlette/middleware/trustedhost.py
@@ -4,10 +4,17 @@
 import typing
 
 
+ENFORCE_DOMAIN_WILDCARD = "Domain wildcard patterns must be like '*.example.com'."
+
+
 class TrustedHostMiddleware:
     def __init__(
         self, app: ASGIApp, allowed_hosts: typing.Sequence[str] = ["*"]
     ) -> None:
+        for pattern in allowed_hosts:
+            assert "*" not in pattern[1:], ENFORCE_DOMAIN_WILDCARD
+            if pattern.startswith("*") and pattern != "*":
+                assert pattern.startswith("*."), ENFORCE_DOMAIN_WILDCARD
         self.app = app
         self.allowed_hosts = allowed_hosts
         self.allow_any = "*" in allowed_hosts
@@ -15,8 +22,15 @@ def __init__(
     def __call__(self, scope: Scope) -> ASGIInstance:
         if scope["type"] in ("http", "websocket") and not self.allow_any:
             headers = Headers(scope=scope)
-            host = headers.get("host")
-            if host not in self.allowed_hosts:
+            host = headers.get("host", "").split(":")[0]
+            for pattern in self.allowed_hosts:
+                if (
+                    host == pattern
+                    or pattern.startswith("*")
+                    and host.endswith(pattern[1:])
+                ):
+                    break
+            else:
                 return PlainTextResponse("Invalid host header", status_code=400)
 
         return self.app(scope)


Test patch:
diff --git a/tests/middleware/test_trusted_host.py b/tests/middleware/test_trusted_host.py
--- a/tests/middleware/test_trusted_host.py
+++ b/tests/middleware/test_trusted_host.py
@@ -7,7 +7,9 @@
 def test_trusted_host_middleware():
     app = Starlette()
 
-    app.add_middleware(TrustedHostMiddleware, allowed_hosts=["testserver"])
+    app.add_middleware(
+        TrustedHostMiddleware, allowed_hosts=["testserver", "*.testserver"]
+    )
 
     @app.route("/")
     def homepage(request):
@@ -17,6 +19,10 @@ def homepage(request):
     response = client.get("/")
     assert response.status_code == 200
 
+    client = TestClient(app, base_url="http://subdomain.testserver")
+    response = client.get("/")
+    assert response.status_code == 200
+
     client = TestClient(app, base_url="http://invalidhost")
     response = client.get("/")
     assert response.status_code == 400
Repository: encode/starlette

Issue:
Routes that were working on 0.5.5 not working on 0.6.2
Greetings, thanks for the project. 

I am having issues after upgrading where it seems one of my routes is getting converted to a coroutine somewhere and not awaited and then is being passed down all the middleware.

```
Traceback (most recent call last):
  File "../lib/python3.7/site-packages/uvicorn/protocols/http/httptools_impl.py", line 387, in run_asgi
    result = await asgi(self.receive, self.send)
  File "../lib/python3.7/site-packages/uvicorn/middleware/message_logger.py", line 59, in __call__
    await self.inner(self.receive, self.send)
  File "../lib/python3.7/site-packages/uvicorn/middleware/debug.py", line 80, in __call__
    await asgi(receive, self.send)
  File "../lib/python3.7/site-packages/starlette/exceptions.py", line 69, in app
    raise exc from None
  File "../lib/python3.7/site-packages/starlette/exceptions.py", line 61, in app
    await instance(receive, sender)
TypeError: 'coroutine' object is not callable
```


Discussion:
Seems the issue is that `Route` is now using `inspect.isfunction(endpoint)` instead of the previous `inspect.isclass` that was wrapping everything into `request_response`. My functions were bound methods so they aren't returning true to `isfunction`.
Okay. PRs welcome, tho I’m not sure if bound methods should be treated as func(request) -> response or as ASGI by default. Could you give a reduced example, just for context?

Resolving patch:
diff --git a/starlette/routing.py b/starlette/routing.py
--- a/starlette/routing.py
+++ b/starlette/routing.py
@@ -84,7 +84,7 @@ def __init__(
         self.endpoint = endpoint
         self.name = get_name(endpoint)
 
-        if inspect.isfunction(endpoint):
+        if inspect.isfunction(endpoint) or inspect.ismethod(endpoint):
             self.app = request_response(endpoint)
             if methods is None:
                 methods = ["GET"]
@@ -135,7 +135,7 @@ def __init__(self, path: str, *, endpoint: typing.Callable) -> None:
         self.endpoint = endpoint
         self.name = get_name(endpoint)
 
-        if inspect.isfunction(endpoint):
+        if inspect.isfunction(endpoint) or inspect.ismethod(endpoint):
             self.app = websocket_session(endpoint)
         else:
             self.app = endpoint


Test patch:
Repository: encode/starlette

Issue:
Check directory exists when instantiating `StaticFiles`
The `StaticFiles` application should ensure that the directory exists at the point it is instantiated.

(With an optional switch to turn this behavior off)


Discussion:

Resolving patch:
diff --git a/starlette/staticfiles.py b/starlette/staticfiles.py
--- a/starlette/staticfiles.py
+++ b/starlette/staticfiles.py
@@ -8,7 +8,9 @@
 
 
 class StaticFiles:
-    def __init__(self, *, directory: str) -> None:
+    def __init__(self, *, directory: str, check_dir: bool = True) -> None:
+        if check_dir and not os.path.isdir(directory):
+            raise RuntimeError("Directory '%s' does not exist" % directory)
         self.directory = directory
         self.config_checked = False
 


Test patch:
diff --git a/tests/test_staticfiles.py b/tests/test_staticfiles.py
--- a/tests/test_staticfiles.py
+++ b/tests/test_staticfiles.py
@@ -54,9 +54,16 @@ def test_staticfiles_with_missing_file_returns_404(tmpdir):
     assert response.text == "Not Found"
 
 
+def test_staticfiles_instantiated_with_missing_directory(tmpdir):
+    with pytest.raises(RuntimeError) as exc:
+        path = os.path.join(tmpdir, "no_such_directory")
+        app = StaticFiles(directory=path)
+    assert "does not exist" in str(exc)
+
+
 def test_staticfiles_configured_with_missing_directory(tmpdir):
     path = os.path.join(tmpdir, "no_such_directory")
-    app = StaticFiles(directory=path)
+    app = StaticFiles(directory=path, check_dir=False)
     client = TestClient(app)
     with pytest.raises(RuntimeError) as exc:
         client.get("/example.txt")
@@ -68,7 +75,7 @@ def test_staticfiles_configured_with_file_instead_of_directory(tmpdir):
     with open(path, "w") as file:
         file.write("<file content>")
 
-    app = StaticFiles(directory=path)
+    app = StaticFiles(directory=path, check_dir=False)
     client = TestClient(app)
     with pytest.raises(RuntimeError) as exc:
         client.get("/example.txt")
Repository: encode/starlette

Issue:
Response's content default to b''
close https://github.com/encode/starlette/issues/201


Discussion:

Resolving patch:
diff --git a/starlette/responses.py b/starlette/responses.py
--- a/starlette/responses.py
+++ b/starlette/responses.py
@@ -31,13 +31,16 @@ class Response:
 
     def __init__(
         self,
-        content: typing.Any,
+        content: typing.Any = None,
         status_code: int = 200,
         headers: dict = None,
         media_type: str = None,
         background: BackgroundTask = None,
     ) -> None:
-        self.body = self.render(content)
+        if content is None:
+            self.body = b""
+        else:
+            self.body = self.render(content)
         self.status_code = status_code
         if media_type is not None:
             self.media_type = media_type
@@ -63,8 +66,8 @@ def init_headers(self, headers: typing.Mapping[str, str] = None) -> None:
             populate_content_length = b"content-length" in keys
             populate_content_type = b"content-type" in keys
 
-        body = getattr(self, "body", None)
-        if body is not None and populate_content_length:
+        body = getattr(self, "body", b"")
+        if body and populate_content_length:
             content_length = str(len(body))
             raw_headers.append((b"content-length", content_length.encode("latin-1")))
 


Test patch:
diff --git a/tests/test_responses.py b/tests/test_responses.py
--- a/tests/test_responses.py
+++ b/tests/test_responses.py
@@ -126,11 +126,11 @@ async def asgi(receive, send):
 
 def test_response_phrase():
     def app(scope):
-        return Response(b"", status_code=200)
+        return Response(status_code=204)
 
     client = TestClient(app)
     response = client.get("/")
-    assert response.reason == "OK"
+    assert response.reason == "No Content"
 
     def app(scope):
         return Response(b"", status_code=123)
Repository: encode/starlette

Issue:
Get client IP address
Hi, i started using **Starlette** recently and for my application i need to log the client's IP address. It's important to note that i use **Hypercorn** as my ASGI server and it works great.
I started searching on Google how to get the IP address from **Starlette**, and i couldn't  find it anywhere, so I searched the request object. I figured out that on the "_scope" property I could access the client IP address, but it's inside a protected member of the request object. 
I don't know if there is a better way of getting it currently available on **Starlette**, but if there isn't i think it would be nice to have something similar to **Flask**'s _request.remote_addr_.


Discussion:
Sure. As it happens `request['client']` will give you a two-tuple of host, port, but it might be nice for that to have a properly documented interface, rather than just accessing it through the scope like that.
I agree it would be nice to have a properly documented interface. 

Resolving patch:
diff --git a/starlette/datastructures.py b/starlette/datastructures.py
--- a/starlette/datastructures.py
+++ b/starlette/datastructures.py
@@ -1,8 +1,11 @@
 import typing
+from collections import namedtuple
 from urllib.parse import ParseResult, parse_qsl, urlencode, urlparse
 
 from starlette.types import Scope
 
+Address = namedtuple("Address", ["host", "port"])
+
 
 class URL:
     def __init__(
diff --git a/starlette/requests.py b/starlette/requests.py
--- a/starlette/requests.py
+++ b/starlette/requests.py
@@ -3,7 +3,7 @@
 import typing
 from collections.abc import Mapping
 
-from starlette.datastructures import URL, Headers, QueryParams
+from starlette.datastructures import URL, Address, Headers, QueryParams
 from starlette.formparsers import FormParser, MultiPartParser
 from starlette.types import Message, Receive, Scope
 
@@ -71,6 +71,11 @@ def cookies(self) -> typing.Dict[str, str]:
             self._cookies = cookies
         return self._cookies
 
+    @property
+    def client(self) -> Address:
+        host, port = self._scope.get("client") or (None, None)
+        return Address(host=host, port=port)
+
     @property
     def session(self) -> dict:
         assert (


Test patch:
diff --git a/tests/test_requests.py b/tests/test_requests.py
--- a/tests/test_requests.py
+++ b/tests/test_requests.py
@@ -63,6 +63,22 @@ async def asgi(receive, send):
     }
 
 
+def test_request_client():
+    def app(scope):
+        async def asgi(receive, send):
+            request = Request(scope, receive)
+            response = JSONResponse(
+                {"host": request.client.host, "port": request.client.port}
+            )
+            await response(receive, send)
+
+        return asgi
+
+    client = TestClient(app)
+    response = client.get("/")
+    assert response.json() == {"host": "testclient", "port": 50000}
+
+
 def test_request_body():
     def app(scope):
         async def asgi(receive, send):
Repository: encode/starlette

Issue:
Content-Type and Content-Length headers not automatically populated if headers is given
Hi! I was playing around with responses and stumbled upon what seems like a bug. I have already pushed a [fix](https://github.com/florimondmanca/starlette/commit/e6dc80d203b613f211003df63352a37550538e9a) on my fork and would be happy to open a PR. :)

---

**Expected behavior**
If the `headers` dictionary passed to `Response` does not contain the `Content-Type` or `Content-Length` headers, the resulting response should populate automatically said headers based on the given `media_type` and `content`.

**Actual behavior**
The `Content-Type` and `Content-Length` headers are not set.

**To reproduce**
```python
from starlette.responses import Response
from starlette.testclient import TestClient

def app(scope):
    return Response(content="hi", headers={}, media_type="text/plain")

client = TestClient(app)
response = client.get("/")
assert response.headers["content-length"] == "2"  # fails: KeyError
assert response.headers["content-type"] == "text/plain; charset=utf-8"  # fails: KeyError
```

**Possible solutions**
After inspecting the code in [responses.py], there seems to be a logical fallacy:
```python
if headers is None:
    # ...
else:
    raw_headers = [...]
    keys = [h[0] for h in raw_headers]
    populate_content_length = b"content-length" in keys  # (*)
    populate_content_type = b"content-type" in keys  # (*)
```

Lines (*) (66 and 67) mean that the headers are populate if they are *already* present in `headers`. They should be populated if they are *not* present => switching to `not in keys` there should fix the bug.

**Specs**

- macOS 10.12
- Python 3.7.1
- Starlette@master



Discussion:

Resolving patch:
diff --git a/starlette/responses.py b/starlette/responses.py
--- a/starlette/responses.py
+++ b/starlette/responses.py
@@ -63,8 +63,8 @@ def init_headers(self, headers: typing.Mapping[str, str] = None) -> None:
                 for k, v in headers.items()
             ]
             keys = [h[0] for h in raw_headers]
-            populate_content_length = b"content-length" in keys
-            populate_content_type = b"content-type" in keys
+            populate_content_length = b"content-length" not in keys
+            populate_content_type = b"content-type" not in keys
 
         body = getattr(self, "body", b"")
         if body and populate_content_length:


Test patch:
diff --git a/tests/test_responses.py b/tests/test_responses.py
--- a/tests/test_responses.py
+++ b/tests/test_responses.py
@@ -275,3 +275,15 @@ async def asgi(receive, send):
 def test_template_response_requires_request():
     with pytest.raises(ValueError):
         TemplateResponse(None, {})
+
+
+def test_populate_headers():
+    def app(scope):
+        headers = {}
+        return Response(content="hi", headers=headers, media_type="text/html")
+
+    client = TestClient(app)
+    response = client.get("/")
+    assert response.text == "hi"
+    assert response.headers["content-length"] == "2"
+    assert response.headers["content-type"] == "text/html; charset=utf-8"
Repository: encode/starlette

Issue:
Params dropped in test client
https://github.com/encode/starlette/blob/10b61f6cc28ec233ad3265b2822ae713dc26a68d/starlette/testclient.py#L70

params needs to be added back to path here, or urls like http://server/path;params will have the ;params chopped off. 




Discussion:

Resolving patch:
diff --git a/starlette/datastructures.py b/starlette/datastructures.py
--- a/starlette/datastructures.py
+++ b/starlette/datastructures.py
@@ -3,7 +3,7 @@
 from collections import namedtuple
 from collections.abc import Sequence
 from shlex import shlex
-from urllib.parse import ParseResult, parse_qsl, urlencode, urlparse
+from urllib.parse import SplitResult, parse_qsl, urlencode, urlsplit
 
 from starlette.concurrency import run_in_threadpool
 from starlette.types import Scope
@@ -50,9 +50,9 @@ def __init__(
         self._url = url
 
     @property
-    def components(self) -> ParseResult:
+    def components(self) -> SplitResult:
         if not hasattr(self, "_components"):
-            self._components = urlparse(self._url)
+            self._components = urlsplit(self._url)
         return self._components
 
     @property
@@ -67,10 +67,6 @@ def netloc(self) -> str:
     def path(self) -> str:
         return self.components.path
 
-    @property
-    def params(self) -> str:
-        return self.components.params
-
     @property
     def query(self) -> str:
         return self.components.query
diff --git a/starlette/testclient.py b/starlette/testclient.py
--- a/starlette/testclient.py
+++ b/starlette/testclient.py
@@ -6,7 +6,7 @@
 import threading
 import types
 import typing
-from urllib.parse import unquote, urljoin, urlparse
+from urllib.parse import unquote, urljoin, urlsplit
 
 import requests
 
@@ -67,9 +67,7 @@ def __init__(self, app: ASGIApp, raise_server_exceptions: bool = True) -> None:
     def send(  # type: ignore
         self, request: requests.PreparedRequest, *args: typing.Any, **kwargs: typing.Any
     ) -> requests.Response:
-        scheme, netloc, path, params, query, fragement = urlparse(  # type: ignore
-            request.url
-        )
+        scheme, netloc, path, query, fragement = urlsplit(request.url)  # type: ignore
 
         default_port = {"http": 80, "ws": 80, "https": 443, "wss": 443}[scheme]
 


Test patch:
diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py
--- a/tests/test_datastructures.py
+++ b/tests/test_datastructures.py
@@ -21,7 +21,6 @@ def test_url():
     assert u.password is None
     assert u.path == "/path/to/somewhere"
     assert u.query == "abc=123"
-    assert u.params == ""
     assert u.fragment == "anchor"
 
     new = u.replace(scheme="http")
Repository: encode/starlette

Issue:
Path prefix for PATH_INFO with WSGIMiddleware
When mounting an app (e.g. Django) through `WSGIMiddleware` it seems to be required to also adjust `PATH_INFO` accordingly.

With the following, Django would e.g. redirect to "/admin/" (without the outer "/dj" prefix).

```python
djapp = django.core.wsgi.get_wsgi_application()
app.mount("/dj", app=WSGIMiddleware(djapp))
```

I've got this working through an additional "path_prefix" argument for WSGIMiddleware, but I think it makes sense to either prepend the mount path in `build_environ` always (using `"PATH_INFO": scope["root_path"] + scope["path"]`, and making it visible to the mounted app though), or handle e.g. "Location" response headers by prepending it there.
The latter could likely result in redirect-loops, and does not help with Django POSTing to "/admin/login" then.

I've not investigated too much into this yet, what do you think?


Discussion:
(it works better when using ASGI via Django Channels - related code: https://github.com/django/channels/blob/a660d81899e1cfd030f20a499cdeae1cea3e0646/channels/http.py#L39-L65)
Prob just a case of needing to pass the ASGI root_path through to the WSGI PATH_INFO
Yes, just prepending it might do the trick - initially I've thought that the mounted app should not know about it, but there is not really a way around it.

What Channels does for Django itself is more complicated though..
“SCRIPT_NAME”: scope.get(“root_path”, “”) would resolve this

Resolving patch:
diff --git a/starlette/middleware/wsgi.py b/starlette/middleware/wsgi.py
--- a/starlette/middleware/wsgi.py
+++ b/starlette/middleware/wsgi.py
@@ -13,7 +13,7 @@ def build_environ(scope: Scope, body: bytes) -> dict:
     """
     environ = {
         "REQUEST_METHOD": scope["method"],
-        "SCRIPT_NAME": "",
+        "SCRIPT_NAME": scope.get("root_path", ""),
         "PATH_INFO": scope["path"],
         "QUERY_STRING": scope["query_string"].decode("ascii"),
         "SERVER_PROTOCOL": f"HTTP/{scope['http_version']}",


Test patch:
Repository: encode/starlette

Issue:
py.typed missing in published artifacts
I didn’t check for earlier versions, but at least 0.11.4 on PyPI does not include `py.typed`. I assume this is an oversight, given it is mentioned in `setup.py`?

https://github.com/encode/starlette/blob/77b84a08c1e4de0db64a197b58ac363a26c51d4f/setup.py#L49


Discussion:
No idea. Happy to take pull requests against the `setup.py`.
It was introduced here: https://github.com/encode/starlette/pull/253 but I've not looked in any detail into exactly what it ought to do.
It signifies that the package is typed, and mypy looks for this file in determine whether it should follow type hints in the package (and show type errors when calling it). I guess I’ll make a PR adding that file.

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -72,4 +72,5 @@ def get_packages(package):
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
     ],
+    zip_safe=False,
 )


Test patch:
Repository: encode/starlette

Issue:
Strip trailing slash ( / ) from URL route strings.
Hey all, an optional trailing slash seems to be pretty standard on most websites, but in Starlette, you must add separate routes to handle them. Example: 

```
routes = [
    Route('/', PlainTextResponse("home")),
    Route('/test', PlainTextResponse("test")),
    Route('/test/', PlainTextResponse("I need to be added separately?!"))
]

app = Starlette(routes=routes)
```

Could a `route = route.rstrip("/")` please be added to https://github.com/encode/starlette/blob/master/starlette/routing.py in `class Router` before running the match so these routes don't have to be manually added? Thanks!


Discussion:
Not sure about this myself - seems like a bit too much magic to have by default.

But you might want to create a PR with your suggested patch already, so this can be discussed at the code level.
btw: you can use `"/path/?"` to match an optional slash..

Resolving patch:
diff --git a/starlette/routing.py b/starlette/routing.py
--- a/starlette/routing.py
+++ b/starlette/routing.py
@@ -615,9 +615,14 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
             return
 
         if scope["type"] == "http" and self.redirect_slashes:
-            if not scope["path"].endswith("/"):
+            if scope["path"].endswith("/"):
+                redirect_path = scope["path"].rstrip("/")
+            else:
+                redirect_path = scope["path"] + "/"
+
+            if redirect_path:  # Note that we skip the "/" -> "" case.
                 redirect_scope = dict(scope)
-                redirect_scope["path"] += "/"
+                redirect_scope["path"] = redirect_path
 
                 for route in self.routes:
                     match, child_scope = route.matches(redirect_scope)


Test patch: