Skip to content
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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

Kludex
Copy link
Sponsor Member

@Kludex Kludex commented Mar 19, 2024

At the time that PR was implemented the default= parameter was not available on TypeVar, and mypy didn't support it.

Now it's possible to achieve what that PR intended.

@@ -27,7 +27,7 @@ classifiers = [
]
dependencies = [
"anyio>=3.4.0,<5",
"typing_extensions>=3.10.0; python_version < '3.10'",
"typing_extensions>=4.4.0; python_version < '3.13'",
Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kludex Kludex requested a review from a team March 19, 2024 21:21
@Kludex Kludex force-pushed the use-default-response-content branch from afcbdb0 to ba8a89b Compare March 19, 2024 21:21
@@ -4,7 +4,7 @@
# Testing
coverage==7.4.3
importlib-metadata==7.0.1
mypy==1.8.0
mypy==1.9.0
Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last release supports default=.

@@ -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:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

| was introduced in 3.10.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use __future__.annotations, and the pipeline passes. 👀

@Kludex Kludex requested a review from a team March 20, 2024 13:19
Copy link
Member

@adriangb adriangb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think JsonResponse and others don’t actually accept Any, they accept a limited set of types. Is that codified in this PR? If it is I’m missing it.

Content = typing.Union[str, bytes]
SyncContentStream = typing.Iterable[Content]
AsyncContentStream = typing.AsyncIterable[Content]
_Content = typing.Union[str, bytes]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to confirm, this is a breaking change right?

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, it is...

@Kludex
Copy link
Sponsor Member Author

Kludex commented Mar 21, 2024

I think JsonResponse and others don’t actually accept Any, they accept a limited set of types. Is that codified in this PR? If it is I’m missing it.

You are right, but I'm not changing that in this PR. And I think there was an attempt to change the type, but it failed, and we had to revert.

Comment on lines +30 to +33
Content = TypeVar("Content", default=typing.Any)


class Response(typing.Generic[Content]):
Copy link
Member

Choose a reason for hiding this comment

The 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 str | bytes given what Response accepts. Then subclasses like StreamingResponse should override this generic with whatever they accept. If we make the default typing.Any then Response is not correctly typed. Since we're setting the default in this PR we might as well choose an appropriate value.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The 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 str | bytes | None, and it was reverted - I don't remember the reason.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. It would be good to find that reason.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The 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 BaseResponse and make Response just one of the many concrete classes:

_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]):
    ...

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But the Response class does have the .render() method... So I don't see how I'd that? I can't make the classes stop inheriting Response, because that would be a breaking change.

Copy link
Member

Choose a reason for hiding this comment

The 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.

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you suggesting adding a breaking change then?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. 😊

Copy link
Member

Choose a reason for hiding this comment

The 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants