failed WRONG_FIX wrong_fix · 20 tool calls · 110 s · fastapi/fastapi
♻️ Validate Server Sent Event fields to avoid applications from sending broken data ## Pull Request <!-- Please start with a GitHub Discussion. Once a team member asks you to open a PR, create it and link the discussion here. Obvious typo fixes can be made in a PR without starting a discussion. --> Discussion: <!-- Link to the GitHub Discussion --> ## Description ♻️ Validate Server Sent Event fields to avoid applications from sending broken data This was reported multiple times as a "vulnerability" by users probably using automated AI tools, with the argument that if input was taken from the request and passed directly to an event unfiltered, it could break the client. If an app sets an `id` or `event` with multi-line content, I would consider that app broken, and probably the app itself vulnerable to something, not FastAPI. With this change, if any app sets invalid multi-line values (sourced from unfiltered input or in any other way), FastAPI will make it a server error (which it is, as the app is sending invalid data). I don't think the code before this PR has a bug, less a vulnerability. This change is mainly a quality of life improvement to prevent badly written apps from shooting themselves in the foot by passing unsanitized data directly to a SSE. <!-- Write the description of your PR here --> ## AI Disclaimer <!-- If using AI, write here the prompt and model used --> Codex with GPT-5.5 <details> <summary>AI transcript</summary> <!-- Paste here the entire AI transcript --> </details> ## Checklist - [ ] This PR is an obvious typo fix, or it links to a GitHub Discussion for the proposed code change. - [x] I added tests for the change. - [x] The new or updated tests fail on the main branch and pass on this PR. - [x] Coverage stays at 100%. - [x] The documentation explains the change if needed.
| # | Tool | Arguments | Result |
|---|---|---|---|
| 1 | run_command | {
"command": "grep -rn \"Event\" --include=*.py fastapi | head -20"
} | {"status": "ok", "stdout": "fastapi/responses.py:5:from fastapi.sse import EventSourceResponse as EventSourceResponse # noqa\nfastapi/applications.py:4576: [Fasfull{"status": "ok", "stdout": "fastapi/responses.py:5:from fastapi.sse import EventSourceResponse as EventSourceResponse # noqa\nfastapi/applications.py:4576: [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/).\nfastapi/applications.py:4596: [FastAPI docs for Lifespan Events](https://fastapi.tiangolo.com/advanced/events/#alternative-events-deprecated).\nfastapi/openapi/docs.py:381: document.addEventListener('DOMContentLoaded', function () {\nfastapi/sse.py:8:# (Section 4.14.4 \"Special Considerations for Server-Sent Events\")\nfastapi/sse.py:20:class EventSourceResponse(StreamingResponse):\nfastapi/sse.py:23: Use as `response_class=EventSourceResponse` on a *path operation* that uses `yield`\nfastapi/sse.py:24: to enable Server Sent Events (SSE) responses.\nfastapi/sse.py:42:class ServerSentEvent(BaseModel):\nfastapi/sse.py:43: \"\"\"Represents a single Server-Sent Event.\nfastapi/sse.py:46: `response_class=EventSourceResponse`, each `ServerSentEvent` is encoded\nfastapi/sse.py:93: Maps to `addEventListener(event, ...)` on the browser. When omitted,\nfastapi/sse.py:105: The browser sends this value back as the `Last-Event-ID` header on\nfastapi/sse.py:129: `EventSource` clients. Useful for keep-alive pings to prevent\nfastapi/sse.py:136: def _check_data_exclusive(self) -> \"ServerSentEvent\":\nfastapi/sse.py:140: \"ServerSentEvent. Use 'data' for JSON-serialized payloads \"\nfastapi/routing.py:64: EventSourceResponse,\nfastapi/routing.py:65: ServerSentEvent,\nfastapi/routing.py:376: is_sse_stream = lenient_issubclass(actual_response_class, EventSourceResponse)\nfastapi/routing.py:497: # Generator endpoint: stream as Server-Sent Events\n", "stderr": "", "exit_code": 0} |
| 2 | run_command | {
"command": "sed -n '40,140p' fastapi/sse.py"
} | {"status": "ok", "stdout": "\n\nclass ServerSentEvent(BaseModel):\n \"\"\"Represents a single Server-Sent Event.\n\n When `yield`ed from a *path operation functfull{"status": "ok", "stdout": "\n\nclass ServerSentEvent(BaseModel):\n \"\"\"Represents a single Server-Sent Event.\n\n When `yield`ed from a *path operation function* that uses\n `response_class=EventSourceResponse`, each `ServerSentEvent` is encoded\n into the [SSE wire format](https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream)\n (`text/event-stream`).\n\n If you yield a plain object (dict, Pydantic model, etc.) instead, it is\n automatically JSON-encoded and sent as the `data:` field.\n\n All `data` values **including plain strings** are JSON-serialized.\n\n For example, `data=\"hello\"` produces `data: \"hello\"` on the wire (with\n quotes).\n \"\"\"\n\n data: Annotated[\n Any,\n Doc(\n \"\"\"\n The event payload.\n\n Can be any JSON-serializable value: a Pydantic model, dict, list,\n string, number, etc. It is **always** serialized to JSON: strings\n are quoted (`\"hello\"` becomes `data: \"hello\"` on the wire).\n\n Mutually exclusive with `raw_data`.\n \"\"\"\n ),\n ] = None\n raw_data: Annotated[\n str | None,\n Doc(\n \"\"\"\n Raw string to send as the `data:` field **without** JSON encoding.\n\n Use this when you need to send pre-formatted text, HTML fragments,\n CSV lines, or any non-JSON payload. The string is placed directly\n into the `data:` field as-is.\n\n Mutually exclusive with `data`.\n \"\"\"\n ),\n ] = None\n event: Annotated[\n str | None,\n Doc(\n \"\"\"\n Optional event type name.\n\n Maps to `addEventListener(event, ...)` on the browser. When omitted,\n the browser dispatches on the generic `message` event.\n \"\"\"\n ),\n ] = None\n id: Annotated[\n str | None,\n AfterVali |
| 3 | run_command | {
"command": "sed -n '141,160p' fastapi/sse.py"
} | {"status": "ok", "stdout": " \"or 'raw_data' for pre-formatted strings.\"\n )\n return self\n\n\ndef format_sse_event(\n *,\n data_str: Annotated[\n str | None,full{"status": "ok", "stdout": " \"or 'raw_data' for pre-formatted strings.\"\n )\n return self\n\n\ndef format_sse_event(\n *,\n data_str: Annotated[\n str | None,\n Doc(\n \"\"\"\n Pre-serialized data string to use as the `data:` field.\n \"\"\"\n ),\n ] = None,\n event: Annotated[\n str | None,\n Doc(\n \"\"\"\n Optional event type name (`event:` field).\n", "stderr": "", "exit_code": 0} |
| 4 | run_command | {
"command": "sed -n '141,200p' fastapi/sse.py"
} | {"status": "ok", "stdout": " \"or 'raw_data' for pre-formatted strings.\"\n )\n return self\n\n\ndef format_sse_event(\n *,\n data_str: Annotated[\n str | None,full{"status": "ok", "stdout": " \"or 'raw_data' for pre-formatted strings.\"\n )\n return self\n\n\ndef format_sse_event(\n *,\n data_str: Annotated[\n str | None,\n Doc(\n \"\"\"\n Pre-serialized data string to use as the `data:` field.\n \"\"\"\n ),\n ] = None,\n event: Annotated[\n str | None,\n Doc(\n \"\"\"\n Optional event type name (`event:` field).\n \"\"\"\n ),\n ] = None,\n id: Annotated[\n str | None,\n Doc(\n \"\"\"\n Optional event ID (`id:` field).\n \"\"\"\n ),\n ] = None,\n retry: Annotated[\n int | None,\n Doc(\n \"\"\"\n Optional reconnection time in milliseconds (`retry:` field).\n \"\"\"\n ),\n ] = None,\n comment: Annotated[\n str | None,\n Doc(\n \"\"\"\n Optional comment line(s) (`:` prefix).\n \"\"\"\n ),\n ] = None,\n) -> bytes:\n \"\"\"Build SSE wire-format bytes from **pre-serialized** data.\n\n The result always ends with `\\n\\n` (the event terminator).\n \"\"\"\n lines: list[str] = []\n\n if comment is not None:\n for line in comment.splitlines():\n lines.append(f\": {line}\")\n\n if event is not None:\n lines.append(f\"event: {event}\")\n", "stderr": "", "exit_code": 0} |
| 5 | run_command | {
"command": "sed -n '201,220p' fastapi/sse.py"
} | {"status": "ok", "stdout": "\n if data_str is not None:\n for line in data_str.splitlines():\n lines.append(f\"data: {line}\")\n\n if id is not None:\n lines.apfull{"status": "ok", "stdout": "\n if data_str is not None:\n for line in data_str.splitlines():\n lines.append(f\"data: {line}\")\n\n if id is not None:\n lines.append(f\"id: {id}\")\n\n if retry is not None:\n lines.append(f\"retry: {retry}\")\n\n lines.append(\"\")\n lines.append(\"\")\n return \"\\n\".join(lines).encode(\"utf-8\")\n\n\n# Keep-alive comment, per the SSE spec recommendation\nKEEPALIVE_COMMENT = b\": ping\\n\\n\"\n\n# Seconds between keep-alive pings when a generator is idle.\n", "stderr": "", "exit_code": 0} |
| 6 | run_command | {
"command": "grep -n \"_check_id_no_null\" fastapi/sse.py"
} | {"status": "ok", "stdout": "36:def _check_id_no_null(v: str | None) -> str | None:\n100: AfterValidator(_check_id_no_null),\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "36:def _check_id_no_null(v: str | None) -> str | None:\n100: AfterValidator(_check_id_no_null),\n", "stderr": "", "exit_code": 0} |
| 7 | run_command | {
"command": "sed -n '30,40p' fastapi/sse.py"
} | {"status": "ok", "stdout": " serves mainly as a marker and sets the correct `Content-Type`.\n \"\"\"\n\n media_type = \"text/event-stream\"\n\n\ndef _check_id_nfull{"status": "ok", "stdout": " serves mainly as a marker and sets the correct `Content-Type`.\n \"\"\"\n\n media_type = \"text/event-stream\"\n\n\ndef _check_id_no_null(v: str | None) -> str | None:\n if v is not None and \"\\0\" in v:\n raise ValueError(\"SSE 'id' must not contain null characters\")\n return v\n\n", "stderr": "", "exit_code": 0} |
| 8 | run_command | {
"command": "python3 - <<'PY'\nfrom fastapi.sse import ServerSentEvent\nfrom pydantic import ValidationError\n\ntry:\n ServerSentEvent(event=\"line1\\nline2\")\n print(\"Failed: event with newline should have raised ValidationError\")\nexcept ValidationError as e:\n print(f\"Success: caught expected ValidationError for event: {e}\")\n\ntry:\n ServerSentEvent(id=\"line1\\nline2\")\n print(\"Failed: id with newline should have raised ValidationError\")\nexcept ValidationError as e:\n print(f\"Success: caught expected ValidationError for id: {e}\")\nPY\n"
} | {"status": "ok", "stdout": "Failed: event with newline should have raised ValidationError\nFailed: id with newline should have raised ValidationError\n", "stderfull{"status": "ok", "stdout": "Failed: event with newline should have raised ValidationError\nFailed: id with newline should have raised ValidationError\n", "stderr": "", "exit_code": 0} |
| 9 | run_command | {
"command": "python3 - <<'PY'\nfrom fastapi.sse import format_sse_event\nimport pytest\n\ndef test_format_sse_event_newlines():\n # If event has a newline, it should probably be an error or handled.\n # The PR says \"if any app sets invalid multi-line values ... FastAPI will make it a server error\"\n # format_sse_event is used by EventSourceResponse.\n # Let's see what it does now.\n res = format_sse_event(event=\"line1\\nline2\", data_str=\"hello\")\n print(f\"Result: {res!r}\")\n\ntest_format_sse_event_newlines()\nPY\n"
} | {"status": "ok", "stdout": "Result: b'event: line1\\nline2\\ndata: hello\\n\\n'\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Result: b'event: line1\\nline2\\ndata: hello\\n\\n'\n", "stderr": "", "exit_code": 0} |
| 10 | run_command | {
"command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/sse.py\")\ns = p.read_text()\n\n# Define the validator for no newlines\nvalidator_code = \"\"\"def _check_no_newlines(v: str | None) -> str | None:\n if v is not None and \"\\\\n\" in v:\n raise ValueError(\"SSE field must not contain newlines\")\n return v\n\"\"\"\n\n# Find where to insert _check_no_newlines. \n# _check_id_no_null is at line 36. Let's put it before it.\n# Actually, let's just replace _check_id_no_null with a more general one or add this one.\n\n# Let's see where _check_id_no_null is defined.\n# 36:def _check_id_no_null(v: str | None) -> str | None:\n# 37: if v is not None and \"\\\\0\" in v:\n# 38: raise ValueError(\"SSE 'id' must not contain null characters\")\n# 39: return v\n\n# I'll add _check_no_newlines and then update ServerSentEvent fields.\n\n# Let's find the exact line for _check_id_no_null\nimport re\nmatch = re.search(r'def _check_id_no_null', s)\nif match:\n start = match.start()\n # Insert before _check_id_no_null\n s = s[:start] + validator_code + \"\\n\\n\" + s[start:]\n\n# Now update ServerSentEvent.event and ServerSentEvent.id to use AfterValidator(_check_no_newlines)\n# Note: id already has AfterValidator(_check_id_no_null). We should probably keep it or combine them.\n\n# Let's use a more robust replacement for the fields.\n\n# For event:\nold_event = \"\"\" event: Annotated[\n str | None,\n Doc(\n \\\"\\\"\\\"\n Optional event type name.\n\n Maps to `addEventListener(event, ...)` on the browser. When omitted,\n the browser dispatches on the generic `message` event.\n \\\"\\\"\\\"\n ),\n ] = None\"\"\"\nnew_event = \"\"\" event: Annotated[\n str | None,\n AfterValidator(_check_no_newlines),\n Doc(\n \\\"\\\"\\\"\n Optional event type name.\n\n Maps to `addEventListener(event, ...)` on the browser. When omitted,\n the browser dispatches on the generic `message` event.\n \\\"\\\"\\\"\n ),\n ] = None\"\"\"\n\n# For id:\nold_id = \"\"\" id: Annotated[\n str | None,\n AfterValidator(_check_id_no_null),\n Doc(\n \\\"\\\"\\\"\n Optional event ID.\n\n The browser sends this value back as the `Last-Event-ID` header on\n automatic reconnection. **Must not contain null (`\\\\0`) characters.**\n \\\"\\\"\\\"\n ),\n ] = None\"\"\"\nnew_id = \"\"\" id: Annotated[\n str | None,\n AfterValidator(_check_no_newlines),\n AfterValidator(_check_id_no_null),\n Doc(\n \\\"\\\"\\\"\n Optional event ID.\n\n The browser sends this value back as the `Last-Event-ID` header on\n automatic reconnection. **Must not contain null (`\\\\0`) characters.**\n \\\"\\\"\\\"\n ),\n ] = None\"\"\"\n\ns = s.replace(old_event, new_event)\ns = s.replace(old_id, new_id)\n\np.write_text(s)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 11 | run_command | {
"command": "python3 - <<'PY'\nfrom fastapi.sse import ServerSentEvent\nfrom pydantic import ValidationError\n\ndef test_event_newline():\n try:\n ServerSentEvent(event=\"line1\\nline2\")\n print(\"Failed: event with newline should have raised ValidationError\")\n except ValidationError as e:\n print(f\"Success: caught expected ValidationError for event: {e}\")\n\ndef test_id_newline():\n try:\n ServerSentEvent(id=\"line1\\nline2\")\n print(\"Failed: id with newline should have raised ValidationError\")\n except ValidationError as e:\n print(f\"Success: caught expected ValidationError for id: {e}\")\n\ndef test_id_null():\n try:\n ServerSentEvent(id=\"null\\0char\")\n print(\"Failed: id with null should have raised ValidationError\")\n except ValidationError as e:\n print(f\"Success: caught expected ValidationError for id null: {e}\")\n\ndef test_valid():\n try:\n ServerSentEvent(event=\"hello\", id=\"123\")\n print(\"Success: valid event passed\")\n except ValidationError as e:\n print(f\"Failed: valid event raised ValidationError: {e}\")\n\ntest_event_newline()\ntest_id_newline()\ntest_id_null()\ntest_valid()\nPY\n"
} | {"status": "ok", "stdout": "Success: caught expected ValidationError for event: 1 validation error for ServerSentEvent\nevent\n Value error, SSE field must not full{"status": "ok", "stdout": "Success: caught expected ValidationError for event: 1 validation error for ServerSentEvent\nevent\n Value error, SSE field must not contain newlines [type=value_error, input_value='line1\\nline2', input_type=str]\n For further information visit https://errors.pydantic.dev/2.13/v/value_error\nFailed: id with newline should have raised ValidationError\nSuccess: caught expected ValidationError for id null: 1 validation error for ServerSentEvent\nid\n Value error, SSE 'id' must not contain null characters [type=value_error, input_value='null\\x00char', input_type=str]\n For further information visit https://errors.pydantic.dev/2.13/v/value_error\nSuccess: valid event passed\n", "stderr": "", "exit_code": 0} |
| 12 | run_command | {
"command": "python3 - <<'PY'\nfrom fastapi.sse import ServerSentEvent\nfrom pydantic import ValidationError\n\ndef test_id_newline():\n try:\n ServerSentEvent(id=\"line1\\nline2\")\n print(\"Failed: id with newline should have raised ValidationError\")\n except ValidationError as e:\n print(f\"Success: caught expected ValidationError for id: {e}\")\n\ntest_id_newline()\nPY\n"
} | {"status": "ok", "stdout": "Failed: id with newline should have raised ValidationError\n", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "Failed: id with newline should have raised ValidationError\n", "stderr": "", "exit_code": 0} |
| 13 | run_command | {
"command": "sed -n '30,110p' fastapi/sse.py"
} | {"status": "ok", "stdout": " serves mainly as a marker and sets the correct `Content-Type`.\n \"\"\"\n\n media_type = \"text/event-stream\"\n\n\ndef _check_no_nfull{"status": "ok", "stdout": " serves mainly as a marker and sets the correct `Content-Type`.\n \"\"\"\n\n media_type = \"text/event-stream\"\n\n\ndef _check_no_newlines(v: str | None) -> str | None:\n if v is not None and \"\\n\" in v:\n raise ValueError(\"SSE field must not contain newlines\")\n return v\n\n\ndef _check_id_no_null(v: str | None) -> str | None:\n if v is not None and \"\\0\" in v:\n raise ValueError(\"SSE 'id' must not contain null characters\")\n return v\n\n\nclass ServerSentEvent(BaseModel):\n \"\"\"Represents a single Server-Sent Event.\n\n When `yield`ed from a *path operation function* that uses\n `response_class=EventSourceResponse`, each `ServerSentEvent` is encoded\n into the [SSE wire format](https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream)\n (`text/event-stream`).\n\n If you yield a plain object (dict, Pydantic model, etc.) instead, it is\n automatically JSON-encoded and sent as the `data:` field.\n\n All `data` values **including plain strings** are JSON-serialized.\n\n For example, `data=\"hello\"` produces `data: \"hello\"` on the wire (with\n quotes).\n \"\"\"\n\n data: Annotated[\n Any,\n Doc(\n \"\"\"\n The event payload.\n\n Can be any JSON-serializable value: a Pydantic model, dict, list,\n string, number, etc. It is **always** serialized to JSON: strings\n are quoted (`\"hello\"` becomes `data: \"hello\"` on the wire).\n\n Mutually exclusive with `raw_data`.\n \"\"\"\n ),\n ] = None\n raw_data: Annotated[\n str | None,\n Doc(\n \"\"\"\n Raw string to send as the `data:` field **without** JSON encoding.\n\n Use this when you need to send pre-formatted text, HTML fragments,\n CSV lines, or any non-JSON payload. The string is placed directly\n into the `da |
| 14 | run_command | {
"command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/sse.py\")\ns = p.read_text()\n\n# It seems I didn't replace id correctly or the replacement didn't take.\n# Let's look at the file content again.\n# The sed output showed:\n# id: Annotated[\n# str | None,\n# AfterValidator(_check_id_no_null),\n# Doc(\n\n# I will use a more precise replacement for id.\n\nold_id = \"\"\" id: Annotated[\n str | None,\n AfterValidator(_check_id_no_null),\n Doc(\n \"\"\"\nnew_id = \"\"\" id: Annotated[\n str | None,\n AfterValidator(_check_no_newlines),\n AfterValidator(_check_id_no_null),\n Doc(\n \"\"\"\n\n# Wait, the old_id I used in the previous script had the full Doc string.\n# Let's try to replace just the part before Doc.\n\nimport re\n# This regex looks for the id: Annotated[ block and replaces the validators.\n# It's tricky because of the multi-line string.\n\n# Let's just use a simple replace on the specific lines.\nlines = s.splitlines()\nfor i, line in enumerate(lines):\n if 'id: Annotated[' in line:\n # Look for AfterValidator(_check_id_no_null),\n for j in range(i, i + 10):\n if 'AfterValidator(_check_id_no_null),' in lines[j]:\n lines[j] = ' AfterValidator(_check_no_newlines),'\n lines.insert(j + 1, ' AfterValidator(_check_id_no_null),')\n break\n break\n\np.write_text(\"\\n\".join(lines))\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 15 | run_command | {
"command": "python3 - <<'PY'\nfrom fastapi.sse import ServerSentEvent\nfrom pydantic import ValidationError\n\ndef test_id_newline():\n try:\n ServerSentEvent(id=\"line1\\nline2\")\n print(\"Failed: id with newline should have raised ValidationError\")\n except ValidationError as e:\n print(f\"Success: caught expected ValidationError for id: {e}\")\n\ndef test_id_null():\n try:\n ServerSentEvent(id=\"null\\0char\")\n print(\"Failed: id with null should have raised ValidationError\")\n except ValidationError as e:\n print(f\"Success: caught expected ValidationError for id null: {e}\")\n\ntest_id_newline()\ntest_id_null()\nPY\n"
} | {"status": "ok", "stdout": "Success: caught expected ValidationError for id: 1 validation error for ServerSentEvent\nid\n Value error, SSE field must not contaifull{"status": "ok", "stdout": "Success: caught expected ValidationError for id: 1 validation error for ServerSentEvent\nid\n Value error, SSE field must not contain newlines [type=value_error, input_value='line1\\nline2', input_type=str]\n For further information visit https://errors.pydantic.dev/2.13/v/value_error\nSuccess: caught expected ValidationError for id null: 1 validation error for ServerSentEvent\nid\n Value error, SSE 'id' must not contain null characters [type=value_error, input_value='null\\x00char', input_type=str]\n For further information visit https://errors.pydantic.dev/2.13/v/value_error\n", "stderr": "", "exit_code": 0} |
| 16 | run_command | {
"command": "python3 - <<'PY'\nfrom fastapi.sse import ServerSentEvent\nfrom pydantic import ValidationError\n\ndef test_event_newline():\n try:\n ServerSentEvent(event=\"line1\\nline2\")\n print(\"Failed: event with newline should have raised ValidationError\")\n except ValidationError as e:\n print(f\"Success: caught expected ValidationError for event: {e}\")\n\ntest_event_newline()\nPY\n"
} | {"status": "ok", "stdout": "Success: caught expected ValidationError for event: 1 validation error for ServerSentEvent\nevent\n Value error, SSE field must not full{"status": "ok", "stdout": "Success: caught expected ValidationError for event: 1 validation error for ServerSentEvent\nevent\n Value error, SSE field must not contain newlines [type=value_error, input_value='line1\\nline2', input_type=str]\n For further information visit https://errors.pydantic.dev/2.13/v/value_error\n", "stderr": "", "exit_code": 0} |
| 17 | run_command | {
"command": "git diff"
} | {"status": "ok", "stdout": "diff --git a/fastapi/sse.py b/fastapi/sse.py\nindex 901d82496..a401847b2 100644\n--- a/fastapi/sse.py\n+++ b/fastapi/sse.py\n@@ -33,full{"status": "ok", "stdout": "diff --git a/fastapi/sse.py b/fastapi/sse.py\nindex 901d82496..a401847b2 100644\n--- a/fastapi/sse.py\n+++ b/fastapi/sse.py\n@@ -33,6 +33,12 @@ class EventSourceResponse(StreamingResponse):\n media_type = \"text/event-stream\"\n \n \n+def _check_no_newlines(v: str | None) -> str | None:\n+ if v is not None and \"\\n\" in v:\n+ raise ValueError(\"SSE field must not contain newlines\")\n+ return v\n+\n+\n def _check_id_no_null(v: str | None) -> str | None:\n if v is not None and \"\\0\" in v:\n raise ValueError(\"SSE 'id' must not contain null characters\")\n@@ -86,6 +92,7 @@ class ServerSentEvent(BaseModel):\n ] = None\n event: Annotated[\n str | None,\n+ AfterValidator(_check_no_newlines),\n Doc(\n \"\"\"\n Optional event type name.\n@@ -97,6 +104,7 @@ class ServerSentEvent(BaseModel):\n ] = None\n id: Annotated[\n str | None,\n+ AfterValidator(_check_no_newlines),\n AfterValidator(_check_id_no_null),\n Doc(\n \"\"\"\n@@ -219,4 +227,4 @@ KEEPALIVE_COMMENT = b\": ping\\n\\n\"\n \n # Seconds between keep-alive pings when a generator is idle.\n # Private but importable so tests can monkeypatch it.\n-_PING_INTERVAL: float = 15.0\n+_PING_INTERVAL: float = 15.0\n\\ No newline at end of file\n", "stderr": "", "exit_code": 0} |
| 18 | run_command | {
"command": "python3 - <<'PY'\nfrom pathlib import Path\np = Path(\"fastapi/sse.py\")\ns = p.read_text()\n# Remove the trailing space/newline change if any\n# The git diff showed a change in _PING_INTERVAL line.\n# Let's just make sure the file ends correctly.\n# Actually, the diff shows:\n# -_PING_INTERVAL: float = 15.0\n# +_PING_INTERVAL: float = 15.0\n# \\ No newline at end of file\n# This means I might have removed the trailing newline.\n\n# Let's just rewrite the file with a trailing newline.\nif not s.endswith('\\n'):\n s += '\\n'\np.write_text(s)\nPY\n"
} | {"status": "ok", "stdout": "", "stderr": "", "exit_code": 0}full{"status": "ok", "stdout": "", "stderr": "", "exit_code": 0} |
| 19 | run_command | {
"command": "git diff"
} | {"status": "ok", "stdout": "diff --git a/fastapi/sse.py b/fastapi/sse.py\nindex 901d82496..5c0b80229 100644\n--- a/fastapi/sse.py\n+++ b/fastapi/sse.py\n@@ -33,full{"status": "ok", "stdout": "diff --git a/fastapi/sse.py b/fastapi/sse.py\nindex 901d82496..5c0b80229 100644\n--- a/fastapi/sse.py\n+++ b/fastapi/sse.py\n@@ -33,6 +33,12 @@ class EventSourceResponse(StreamingResponse):\n media_type = \"text/event-stream\"\n \n \n+def _check_no_newlines(v: str | None) -> str | None:\n+ if v is not None and \"\\n\" in v:\n+ raise ValueError(\"SSE field must not contain newlines\")\n+ return v\n+\n+\n def _check_id_no_null(v: str | None) -> str | None:\n if v is not None and \"\\0\" in v:\n raise ValueError(\"SSE 'id' must not contain null characters\")\n@@ -86,6 +92,7 @@ class ServerSentEvent(BaseModel):\n ] = None\n event: Annotated[\n str | None,\n+ AfterValidator(_check_no_newlines),\n Doc(\n \"\"\"\n Optional event type name.\n@@ -97,6 +104,7 @@ class ServerSentEvent(BaseModel):\n ] = None\n id: Annotated[\n str | None,\n+ AfterValidator(_check_no_newlines),\n AfterValidator(_check_id_no_null),\n Doc(\n \"\"\"\n", "stderr": "", "exit_code": 0} |
| 20 | run_command | {
"command": "submit_patch"
} | {"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/basfull{"status": "error", "error_type": "CommandError", "error_message": "/bin/bash: submit_patch: command not found\n", "details": {"stdout": "", "stderr": "/bin/bash: submit_patch: command not found\n", "exit_code": 127}} |
| 21 | submit_patch | {} | {"status": "ok", "patch_size": 1016, "files_changed": 1}full{"status": "ok", "patch_size": 1016, "files_changed": 1} |
diff --git a/fastapi/sse.py b/fastapi/sse.py
index 901d82496..5c0b80229 100644
--- a/fastapi/sse.py
+++ b/fastapi/sse.py
@@ -33,6 +33,12 @@ class EventSourceResponse(StreamingResponse):
media_type = "text/event-stream"
+def _check_no_newlines(v: str | None) -> str | None:
+ if v is not None and "\n" in v:
+ raise ValueError("SSE field must not contain newlines")
+ return v
+
+
def _check_id_no_null(v: str | None) -> str | None:
if v is not None and "\0" in v:
raise ValueError("SSE 'id' must not contain null characters")
@@ -86,6 +92,7 @@ class ServerSentEvent(BaseModel):
] = None
event: Annotated[
str | None,
+ AfterValidator(_check_no_newlines),
Doc(
"""
Optional event type name.
@@ -97,6 +104,7 @@ class ServerSentEvent(BaseModel):
] = None
id: Annotated[
str | None,
+ AfterValidator(_check_no_newlines),
AfterValidator(_check_id_no_null),
Doc(
"""
..........F
=================================== FAILURES ===================================
_ test_server_sent_event_single_line_fields_reject_newlines[first\nsecond-event] _
field_name = 'event', value = 'first\nsecond'
@pytest.mark.parametrize("field_name", ["event", "id"])
@pytest.mark.parametrize("value", ["first\nsecond", "first\rsecond", "first\r\nsecond"])
def test_server_sent_event_single_line_fields_reject_newlines(
field_name: str, value: str
):
> with pytest.raises(ValueError, match=f"SSE '{field_name}' must be a single line"):
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
E AssertionError: Regex pattern did not match.
E Expected regex: "SSE 'event' must be a single line"
E Actual message: "1 validation error for ServerSentEvent\nevent\n Value error, SSE field must not contain newlines [type=value_error, input_value='first\\nsecond', input_type=str]\n For further information visit https://errors.pydantic.dev/2.13/v/value_error"
tests/test_sse.py:229: AssertionError
=============================== warnings summary ===============================
../../../../../../../Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-1.6.0-py3-none-any/starlette/testclient.py:53
/Users/jp/repos/kaggle-gemini-coding-agent-post-training/.envs/overlays/starlette-1.6.0-py3-none-any/starlette/testclient.py:53: DeprecationWarning: The anyio.abc.BlockingPortal alias is deprecated, use anyio.from_thread.BlockingPortal instead.
_PortalFactoryType = Callable[[], AbstractContextManager[anyio.abc.BlockingPortal]]
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
!!!!!!!!!!!!!!!!!!!!!!!!!! stopping after 1 failures !!!!!!!!!!!!!!!!!!!!!!!!!!!
1 failed, 10 passed, 1 warning in 0.44s