-
-
Notifications
You must be signed in to change notification settings - Fork 939
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ContentType
generic to Response
#2547
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,7 @@ | |
# Testing | ||
coverage==7.4.3 | ||
importlib-metadata==7.0.1 | ||
mypy==1.8.0 | ||
mypy==1.9.0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Last release supports |
||
ruff==0.1.15 | ||
typing_extensions==4.10.0 | ||
types-contextvars==2.4.7.3 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ | |
import json | ||
import os | ||
import stat | ||
import sys | ||
import typing | ||
import warnings | ||
from datetime import datetime | ||
|
@@ -21,14 +22,21 @@ | |
from starlette.datastructures import URL, MutableHeaders | ||
from starlette.types import Receive, Scope, Send | ||
|
||
if sys.version_info >= (3, 13): # pragma: no cover | ||
from typing import TypeVar | ||
else: | ||
from typing_extensions import TypeVar | ||
|
||
class Response: | ||
Content = TypeVar("Content", default=typing.Any) | ||
|
||
|
||
class Response(typing.Generic[Content]): | ||
Comment on lines
+30
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to me like the reality is that the default should be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not changing this in this PR. There was a previous attempt to change the type to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok. It would be good to find that reason. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok I see. So yes the right thing to do would be to make a _ResponseDataT = TypeVar('_ResponseDataT')
class BaseResponse(Generic[_ResponseDataT]):
def render(self, data: _ResponseDataT | None) -> bytes:
raise NotImplementedError
class Response(BaseResponse[str | bytes]):
...
_JsonData = str | bytes | float | int | None | ...
class JsonResponse(BaseResponse[_JsonData]):
... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is also what I think is unreasonable about Starlette. For example, StreamResponse and FileReponse do not use render, but they inherit this method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are you suggesting adding a breaking change then? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. 😊 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we do some naming to make it a non-breaking change or minimize the breakage? We could leave the render() method on the base class but mark it as deprecated vi a warning. Adding a BaseResponse class is not a breaking change. I think removing the render() method from eg StreamingResponse (because it inherits from BaseResponse and not Response) would also be okay. |
||
media_type = None | ||
charset = "utf-8" | ||
|
||
def __init__( | ||
self, | ||
content: typing.Any = None, | ||
content: Content | None = None, | ||
status_code: int = 200, | ||
headers: typing.Mapping[str, str] | None = None, | ||
media_type: str | None = None, | ||
|
@@ -41,7 +49,7 @@ def __init__( | |
self.body = self.render(content) | ||
self.init_headers(headers) | ||
|
||
def render(self, content: typing.Any) -> bytes: | ||
def render(self, content: Content | None) -> bytes: | ||
Kludex marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if content is None: | ||
return b"" | ||
if isinstance(content, bytes): | ||
|
@@ -170,20 +178,20 @@ class PlainTextResponse(Response): | |
media_type = "text/plain" | ||
|
||
|
||
class JSONResponse(Response): | ||
class JSONResponse(Response[Content]): | ||
media_type = "application/json" | ||
|
||
def __init__( | ||
self, | ||
content: typing.Any, | ||
content: Content, | ||
status_code: int = 200, | ||
headers: typing.Mapping[str, str] | None = None, | ||
media_type: str | None = None, | ||
background: BackgroundTask | None = None, | ||
) -> None: | ||
super().__init__(content, status_code, headers, media_type, background) | ||
|
||
def render(self, content: typing.Any) -> bytes: | ||
def render(self, content: Content | None) -> bytes: | ||
return json.dumps( | ||
content, | ||
ensure_ascii=False, | ||
|
@@ -207,9 +215,9 @@ def __init__( | |
self.headers["location"] = quote(str(url), safe=":/%#?=@[]!$&'()*+,;") | ||
|
||
|
||
Content = typing.Union[str, bytes] | ||
SyncContentStream = typing.Iterable[Content] | ||
AsyncContentStream = typing.AsyncIterable[Content] | ||
_Content = typing.Union[str, bytes] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to confirm, this is a breaking change right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh yeah, it is... |
||
SyncContentStream = typing.Iterable[_Content] | ||
AsyncContentStream = typing.AsyncIterable[_Content] | ||
ContentStream = typing.Union[AsyncContentStream, SyncContentStream] | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
default=
was introduced in this version: https://typing-extensions.readthedocs.io/en/latest/#typing_extensions.TypeVar