-
-
Notifications
You must be signed in to change notification settings - Fork 930
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
Support lifespan state #2060
Support lifespan state #2060
Conversation
""" | ||
Run any `.on_startup` event handlers. | ||
""" | ||
for handler in self.on_startup: | ||
sig = inspect.signature(handler) | ||
if len(sig.parameters) == 1 and state is not None: |
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.
If the state is None
, it means that the server doesn't support it, so we'll maintain the same error message.
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.
Overall looks good! I have some questions about deprecations, unless it makes the PR a lot more complicated I would suggest doing the deprecations in another PR. You could always support this new feature only for async context manager dependencies to avoid having to implement things for all of the other types of lifespans / startup/shutdown events we support. But not sure how that works out code wise.
def __call__( | ||
self: _TDefaultLifespan, | ||
app: object, | ||
state: typing.Optional[typing.Dict[str, typing.Any]], | ||
) -> _TDefaultLifespan: | ||
self._state = state | ||
return self |
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.
At first glance I'm a bit confused about this and the need for a TypeVar.
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.
The reason for the TypeVar
is that it needed to be bound, as the _state
was introduced here, since the self
is annotated.
starlette/testclient.py
Outdated
@@ -243,6 +245,7 @@ def handle_request(self, request: httpx.Request) -> httpx.Response: | |||
"client": ["testclient", 50000], | |||
"server": [host, port], | |||
"subprotocols": subprotocols, | |||
"state": self.state, |
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.
I'm not seeing where this dict is copied. Is it being copied before being passed into each request?
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.
Ups 👀
tests/test_routing.py
Outdated
async def run_startup(state): | ||
nonlocal startup_complete | ||
startup_complete = True | ||
state["startup"] = True | ||
|
||
async def run_shutdown(state): | ||
nonlocal shutdown_complete | ||
shutdown_complete = True | ||
assert state["startup"] |
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.
Do we want to support state in on_startup
and on_shutdown
? Don't we want to deprecate that in 1.0? If we were not planning on it, we should be. I think we should only support async context manager lifespans in 1.0.
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.
Do we want to support state in
on_startup
andon_shutdown
?
I don't think we discussed it.
What are the problems we have there besides "multiple ways to do things"?
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.
I'd say just that: multiple ways to do things.
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.
I really like the on_startup
and on_shutdown
parameters, and given that it would be one more thing to do for 1.0, I'm not in favor of doing it.
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.
Okay fair enough, it's besides the point of this PR. Not sure why you like them but I'm fairly certain the team decided to promote context manager lifespans as the way forward, so can we at least write the tests using that?
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.
Do you know where it was discussed?
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.
Not specifically, just my recollection. Maybe @graingert does, I know they advocated heavilty for the async context manager approach.
Co-authored-by: Adrian Garcia Badaracco <1755071+adriangb@users.noreply.github.com>
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.
server_supports_state = state is not None | ||
if lifespan_needs_state and not server_supports_state: | ||
raise RuntimeError( | ||
'The server does not support "state" in the lifespan scope.' |
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.
I removed the "please change server", and use the created the state
variable to save one line below... 😬
Before we release this, just wondering if this API might be better: Lifespan = Callable[[Starlette], AsyncContextManager[None | Dict[str, Any]]]
class MyStateType(TypedDict):
client: httpx.AsyncClient
@asynccontextmanager
async def lifespan(app: Starlette) -> MyStateType:
async with httpx.AsyncClient() as client:
yield {"client": client} I think it would also be even less changes in Starlette since we could do: async with lifespan as maybe_state:
if maybe_state:
scope["state"].update(maybe_state) And not have to introspect the function signature or anything. |
We'd still have to introspect in the |
Not sure how to handle |
My question was more about what you said:
Considering that, we'd still need to introspect for That said... I think what you propose looks better for |
This reverts commit da6461b.
I discovered this yesterday after upgrading from an older version and just wanted to say that I'm delighted by it. It's so much nicer than using globals! Thanks for the work that went into this 🔥 |
Changes
How to review
You need to have both this branch and the one from encode/uvicorn#1818 on your virtual environment, and then you can use this application to test the behavior:
Play with the comments above. 👀