nebius/SWE-bench-extra huggingface dataset:
Repository: encode/httpx

Issue:
API design question - `Response.url`
Currently our `Response.url` attribute exposes a `URL` instance.

This is a breaking change from the requests API where it just exposes a plain string.

It's feasible that we should instead only be exposing plain string URLs, in order to aim for drop-in replacement API compatibility w/ requests, *and* in order to keep the API surface area low.

Options here are:

* Expose `request.url` as a URL instance. (Richer information, URL class is also useful in its own right.)
* Expose `request.url` as a str. (Better API compat. Lower API surface area to maintain.)
* Expose `request.url` as a str, and `request.urlinfo` as a URL instance. (Better API compat. High API surface area.)


Comments:

Patch:
diff --git a/httpx/models.py b/httpx/models.py
index 710c030..9cbdc25 100644
--- a/httpx/models.py
+++ b/httpx/models.py
@@ -204,7 +204,7 @@ class URL:
         return hash(str(self))
 
     def __eq__(self, other: typing.Any) -> bool:
-        return isinstance(other, URL) and str(self) == str(other)
+        return isinstance(other, (URL, str)) and str(self) == str(other)
 
     def __str__(self) -> str:
         return self.components.unsplit()


Test patch:
diff --git a/tests/models/test_url.py b/tests/models/test_url.py
index 5f5208c..70089e0 100644
--- a/tests/models/test_url.py
+++ b/tests/models/test_url.py
@@ -25,6 +25,12 @@ def test_url():
     assert new.scheme == "http"
 
 
+def test_url_eq_str():
+    url = URL("https://example.org:123/path/to/somewhere?abc=123#anchor")
+    assert url == "https://example.org:123/path/to/somewhere?abc=123#anchor"
+    assert str(url) == url
+
+
 def test_url__params():
     url = URL("https://example.org:123/path/to/somewhere", params={"a": "123"})
     assert str(url) == "https://example.org:123/path/to/somewhere?a=123"
Repository: encode/httpx

Issue:
DigestAuth should raise a clear error if the request cannot replay.
Our DigestAuth implementation cannot work with non-replayable requests.
We ought to raise a nice clear error if `request.stream.is_replayable` is not True.

Comments:

Patch:
diff --git a/httpx/auth.py b/httpx/auth.py
index e0ef50c..e412c57 100644
--- a/httpx/auth.py
+++ b/httpx/auth.py
@@ -6,7 +6,7 @@ import typing
 from base64 import b64encode
 from urllib.request import parse_http_list
 
-from .exceptions import ProtocolError
+from .exceptions import ProtocolError, RequestBodyUnavailable
 from .models import Request, Response
 from .utils import to_bytes, to_str, unquote
 
@@ -104,6 +104,8 @@ class DigestAuth(Auth):
         self.password = to_bytes(password)
 
     def __call__(self, request: Request) -> AuthFlow:
+        if not request.stream.can_replay():
+            raise RequestBodyUnavailable("Request body is no longer available.")
         response = yield request
 
         if response.status_code != 401 or "www-authenticate" not in response.headers:


Test patch:
diff --git a/tests/client/test_auth.py b/tests/client/test_auth.py
index ea6ff8a..34ec77e 100644
--- a/tests/client/test_auth.py
+++ b/tests/client/test_auth.py
@@ -5,7 +5,15 @@ import typing
 
 import pytest
 
-from httpx import URL, AsyncClient, DigestAuth, ProtocolError, Request, Response
+from httpx import (
+    URL,
+    AsyncClient,
+    DigestAuth,
+    ProtocolError,
+    Request,
+    RequestBodyUnavailable,
+    Response,
+)
 from httpx.auth import Auth, AuthFlow
 from httpx.config import CertTypes, TimeoutTypes, VerifyTypes
 from httpx.dispatch.base import Dispatcher
@@ -442,3 +450,16 @@ async def test_auth_history() -> None:
     assert resp2.history == [resp1]
 
     assert len(resp1.history) == 0
+
+
+@pytest.mark.asyncio
+async def test_digest_auth_unavailable_streaming_body():
+    url = "https://example.org/"
+    auth = DigestAuth(username="tomchristie", password="password123")
+    client = AsyncClient(dispatch=MockDispatch())
+
+    async def streaming_body():
+        yield b"Example request body"
+
+    with pytest.raises(RequestBodyUnavailable):
+        await client.post(url, data=streaming_body(), auth=auth)
Repository: encode/starlette

Issue:
scope["server"] can be None
From https://asgi.readthedocs.io/en/latest/specs/www.html#connection-scope

> server: A two-item iterable of [host, port], where host is the listening address for this server as a unicode string, and port is the integer listening port. Optional, defaults to None.

https://github.com/encode/starlette/blob/master/starlette/datastructures.py#L11 doesn't handle that option, it assumes scope["server"] is always a two-pair



Comments:

Patch:
diff --git a/starlette/datastructures.py b/starlette/datastructures.py
index 558c8a9..2705fd3 100644
--- a/starlette/datastructures.py
+++ b/starlette/datastructures.py
@@ -7,16 +7,20 @@ class URL:
     def __init__(self, url: str = "", scope: Scope = None) -> None:
         if scope is not None:
             assert not url, 'Cannot set both "url" and "scope".'
-            scheme = scope["scheme"]
-            host, port = scope["server"]
+            scheme = scope.get("scheme", "http")
+            server = scope.get("server", None)
             path = scope.get("root_path", "") + scope["path"]
             query_string = scope["query_string"]
 
-            default_port = {"http": 80, "https": 443, "ws": 80, "wss": 443}[scheme]
-            if port == default_port:
-                url = "%s://%s%s" % (scheme, host, path)
+            if server is None:
+                url = path
             else:
-                url = "%s://%s:%s%s" % (scheme, host, port, path)
+                host, port = server
+                default_port = {"http": 80, "https": 443, "ws": 80, "wss": 443}[scheme]
+                if port == default_port:
+                    url = "%s://%s%s" % (scheme, host, path)
+                else:
+                    url = "%s://%s:%s%s" % (scheme, host, port, path)
 
             if query_string:
                 url += "?" + unquote(query_string.decode())
@@ -85,6 +89,9 @@ class URL:
     def __str__(self):
         return self._url
 
+    def __repr__(self):
+        return "%s(%s)" % (self.__class__.__name__, repr(self._url))
+
 
 # Type annotations for valid `__init__` values to QueryParams and Headers.
 StrPairs = typing.Sequence[typing.Tuple[str, str]]


Test patch:
diff --git a/tests/test_datastructures.py b/tests/test_datastructures.py
index d6aa62f..4312e1c 100644
--- a/tests/test_datastructures.py
+++ b/tests/test_datastructures.py
@@ -27,6 +27,23 @@ def test_url():
     assert new.hostname == "example.com"
 
 
+def test_url_from_scope():
+    u = URL(scope={"path": "/path/to/somewhere", "query_string": b"abc=123"})
+    assert u == "/path/to/somewhere?abc=123"
+    assert repr(u) == "URL('/path/to/somewhere?abc=123')"
+
+    u = URL(
+        scope={
+            "scheme": "https",
+            "server": ("example.org", 123),
+            "path": "/path/to/somewhere",
+            "query_string": b"abc=123",
+        }
+    )
+    assert u == "https://example.org:123/path/to/somewhere?abc=123"
+    assert repr(u) == "URL('https://example.org:123/path/to/somewhere?abc=123')"
+
+
 def test_headers():
     h = Headers([(b"a", b"123"), (b"a", b"456"), (b"b", b"789")])
     assert "a" in h
Repository: encode/starlette

Issue:
Asynchronous classmethod handlers not recognized as coroutines if wrapped in functools.partial
https://github.com/encode/starlette/blob/e4307065ea6dbe708fba9643d14fe7adfff06d46/starlette/routing.py#L38

Although the recent 0.14.0 version adds support for functools.partial for Python 3.6 and Python 3.7, that support appears to be incomplete, not working for classmethods in Python 3.8.

I was unaware of the fact that `inspect.iscoroutinefunction` does not properly detect a functools.partial as async if it wraps an async classmethod.

TLDR: the previous PR was too cautious, we can always unwrap the functools.partial object to be sure we check the underlying object.

Comments:

Patch:
diff --git a/starlette/routing.py b/starlette/routing.py
index ce5e4d1..1e6ae0b 100644
--- a/starlette/routing.py
+++ b/starlette/routing.py
@@ -2,7 +2,6 @@ import asyncio
 import functools
 import inspect
 import re
-import sys
 import traceback
 import typing
 from enum import Enum
@@ -33,11 +32,10 @@ class Match(Enum):
 def iscoroutinefunction_or_partial(obj: typing.Any) -> bool:
     """
     Correctly determines if an object is a coroutine function,
-    with a fix for partials on Python < 3.8.
+    including those wrapped in functools.partial objects.
     """
-    if sys.version_info < (3, 8):  # pragma: no cover
-        while isinstance(obj, functools.partial):
-            obj = obj.func
+    while isinstance(obj, functools.partial):
+        obj = obj.func
     return inspect.iscoroutinefunction(obj)
 
 


Test patch:
diff --git a/tests/test_routing.py b/tests/test_routing.py
index 27640ef..8927c60 100644
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -590,16 +590,35 @@ def test_raise_on_shutdown():
             pass  # pragma: nocover
 
 
+class AsyncEndpointClassMethod:
+    @classmethod
+    async def async_endpoint(cls, arg, request):
+        return JSONResponse({"arg": arg})
+
+
 async def _partial_async_endpoint(arg, request):
     return JSONResponse({"arg": arg})
 
 
 partial_async_endpoint = functools.partial(_partial_async_endpoint, "foo")
+partial_cls_async_endpoint = functools.partial(
+    AsyncEndpointClassMethod.async_endpoint, "foo"
+)
 
-partial_async_app = Router(routes=[Route("/", partial_async_endpoint)])
+partial_async_app = Router(
+    routes=[
+        Route("/", partial_async_endpoint),
+        Route("/cls", partial_cls_async_endpoint),
+    ]
+)
 
 
 def test_partial_async_endpoint():
-    response = TestClient(partial_async_app).get("/")
+    test_client = TestClient(partial_async_app)
+    response = test_client.get("/")
     assert response.status_code == 200
     assert response.json() == {"arg": "foo"}
+
+    cls_method_response = test_client.get("/cls")
+    assert cls_method_response.status_code == 200
+    assert cls_method_response.json() == {"arg": "foo"}
Repository: encode/starlette

Issue:
Session cookie should use root path
The session cookie currently uses '/'.
It should really use the ASGI root path instead, in case the application is submounted.

Comments:

Patch:
diff --git a/starlette/middleware/sessions.py b/starlette/middleware/sessions.py
index d1b1d5a..a13ec5c 100644
--- a/starlette/middleware/sessions.py
+++ b/starlette/middleware/sessions.py
@@ -49,14 +49,16 @@ class SessionMiddleware:
 
         async def send_wrapper(message: Message) -> None:
             if message["type"] == "http.response.start":
+                path = scope.get("root_path", "") or "/"
                 if scope["session"]:
                     # We have session data to persist.
                     data = b64encode(json.dumps(scope["session"]).encode("utf-8"))
                     data = self.signer.sign(data)
                     headers = MutableHeaders(scope=message)
-                    header_value = "%s=%s; path=/; Max-Age=%d; %s" % (
+                    header_value = "%s=%s; path=%s; Max-Age=%d; %s" % (
                         self.session_cookie,
                         data.decode("utf-8"),
+                        path,
                         self.max_age,
                         self.security_flags,
                     )
@@ -66,7 +68,7 @@ class SessionMiddleware:
                     headers = MutableHeaders(scope=message)
                     header_value = "{}={}; {}".format(
                         self.session_cookie,
-                        "null; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT;",
+                        f"null; path={path}; expires=Thu, 01 Jan 1970 00:00:00 GMT;",
                         self.security_flags,
                     )
                     headers.append("Set-Cookie", header_value)


Test patch:
diff --git a/tests/middleware/test_session.py b/tests/middleware/test_session.py
index 3f71232..68cf36d 100644
--- a/tests/middleware/test_session.py
+++ b/tests/middleware/test_session.py
@@ -101,3 +101,15 @@ def test_secure_session():
 
     response = secure_client.get("/view_session")
     assert response.json() == {"session": {}}
+
+
+def test_session_cookie_subpath():
+    app = create_app()
+    second_app = create_app()
+    second_app.add_middleware(SessionMiddleware, secret_key="example")
+    app.mount("/second_app", second_app)
+    client = TestClient(app, base_url="http://testserver/second_app")
+    response = client.post("second_app/update_session", json={"some": "data"})
+    cookie = response.headers["set-cookie"]
+    cookie_path = re.search(r"; path=(\S+);", cookie).groups()[0]
+    assert cookie_path == "/second_app"
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)

Comments:

Patch:
diff --git a/docs/staticfiles.md b/docs/staticfiles.md
index 448cc3b..cc8db5e 100644
--- a/docs/staticfiles.md
+++ b/docs/staticfiles.md
@@ -1,7 +1,12 @@
 
-Starlette also includes a `StaticFiles` class for serving a specific directory:
+Starlette also includes a `StaticFiles` class for serving files in a given directory:
 
-* `StaticFiles(directory)` - Serve any files in the given `directory`.
+### StaticFiles
+
+Signature: `StaticFiles(directory, check_dir=True)`
+
+* `directory` - A string denoting the directory path
+* `check_dir` - Ensure that the directory exists upon instantiation. Defaults to `True`
 
 You can combine this ASGI application with Starlette's routing to provide
 comprehensive static file serving.
diff --git a/starlette/staticfiles.py b/starlette/staticfiles.py
index 8a7527a..ec2418e 100644
--- a/starlette/staticfiles.py
+++ b/starlette/staticfiles.py
@@ -8,7 +8,9 @@ from starlette.types import ASGIInstance, Receive, Scope, Send
 
 
 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
index 9d12de8..84f3368 100644
--- 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:
the raw "Response" should'nt mandatory a content ... no ?
I need to manage myself the etag/304 response ...

to send a 304 if-none-match, I use : 

    Response(status_code=304,content="",headers=rheaders)

But the body content should not be written ... here the content is mandatory : None cause troubble, and empty string is an empty string ...



Comments:

Patch:
diff --git a/starlette/responses.py b/starlette/responses.py
index a6ebe8c..4390c29 100644
--- 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 @@ class Response:
             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
index 7e23e29..e68958e 100644
--- a/tests/test_responses.py
+++ b/tests/test_responses.py
@@ -126,11 +126,11 @@ def test_response_headers():
 
 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:
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


Comments:

Patch:
diff --git a/starlette/responses.py b/starlette/responses.py
index 542e800..3b8d275 100644
--- a/starlette/responses.py
+++ b/starlette/responses.py
@@ -63,8 +63,8 @@ class Response:
                 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
index 0b79051..d4bafaf 100644
--- a/tests/test_responses.py
+++ b/tests/test_responses.py
@@ -275,3 +275,15 @@ def test_template_response():
 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:
Mounts without a name strips the path arg from calls to child routes in url_path_for
I'm using an unnamed Mount which contains a mount to a StaticFiles instance and when using "url_for" it is unable to find the matching file. After a bit of digging it seems to be directly related to url_for_path in Mount clearing the path arg whenever the mount does not have a name set. 

https://github.com/encode/starlette/blob/master/starlette/routing.py#L337

Calling url_path_for in the child router works as expected. I'm unclear as to why you'd want to strip this arg just because the mount is not named.

```
app.mount('/stuff', app=Router(routes=[
    Mount('/static', app=StaticFiles(directory='static'), name='static'),
]))
```

```
In [6]: app.url_path_for('static', path='css/bootstap.min.css')
---------------------------------------------------------------------------
NoMatchFound
...
In [7]: app.routes[0].routes[0].url_path_for('static', path='css/bootstap.min.css')
Out[7]: '/static/css/bootstap.min.css'
```

Comments:

Patch:
diff --git a/starlette/routing.py b/starlette/routing.py
index 7a0d7ef..a1e4619 100644
--- a/starlette/routing.py
+++ b/starlette/routing.py
@@ -336,15 +336,18 @@ class Mount(BaseRoute):
             else:
                 # 'name' matches "<mount_name>:<child_name>".
                 remaining_name = name[len(self.name) + 1 :]
+            path_kwarg = path_params.get("path")
             path_params["path"] = ""
-            path, remaining_params = replace_params(
+            path_prefix, remaining_params = replace_params(
                 self.path_format, self.param_convertors, path_params
             )
+            if path_kwarg is not None:
+                remaining_params["path"] = path_kwarg
             for route in self.routes or []:
                 try:
                     url = route.url_path_for(remaining_name, **remaining_params)
                     return URLPath(
-                        path=path.rstrip("/") + str(url), protocol=url.protocol
+                        path=path_prefix.rstrip("/") + str(url), protocol=url.protocol
                     )
                 except NoMatchFound:
                     pass


Test patch:
diff --git a/tests/test_routing.py b/tests/test_routing.py
index 9723e46..30645ef 100644
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -400,3 +400,14 @@ def test_url_for_with_root_path():
         "index": "https://www.example.org/sub_path/",
         "submount": "https://www.example.org/sub_path/submount/",
     }
+
+
+double_mount_routes = [
+    Mount("/mount", name="mount", routes=[Mount("/static", ..., name="static")],),
+]
+
+
+def test_url_for_with_double_mount():
+    app = Starlette(routes=double_mount_routes)
+    url = app.url_path_for("mount:static", path="123")
+    assert url == "/mount/static/123"