Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-starlette for openSUSE:Factory checked in at 2023-03-15 18:53:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-starlette (Old) and /work/SRC/openSUSE:Factory/.python-starlette.new.31432 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-starlette" Wed Mar 15 18:53:38 2023 rev:18 rq:1071792 version:0.26.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-starlette/python-starlette.changes 2023-02-25 19:55:05.843261288 +0100 +++ /work/SRC/openSUSE:Factory/.python-starlette.new.31432/python-starlette.changes 2023-03-15 18:53:47.100152746 +0100 @@ -1,0 +2,24 @@ +Tue Mar 14 09:11:16 UTC 2023 - David Anes <david.a...@suse.com> + +- Update to 0.26.1: + * Fixed + - Fix typing of Lifespan to allow subclasses of Starlette #2077. + +------------------------------------------------------------------- +Fri Mar 10 11:36:42 UTC 2023 - David Anes <david.a...@suse.com> + +- Update to 0.26.0.post1: + * Fixed + - Replace reference from Events to Lifespan on the mkdocs.yml #2072. + +- Update to 0.26.0: + * Added + - Support lifespan state #2060, #2065 and #2064. + * Changed + - Change url_for signature to return a URL instance #1385. + * Fixed + - Allow "name" argument on url_for() and url_path_for() #2050. + * Deprecated + - Deprecate on_startup and on_shutdown events #2070. + +------------------------------------------------------------------- Old: ---- starlette-0.25.0.tar.gz New: ---- starlette-0.26.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-starlette.spec ++++++ --- /var/tmp/diff_new_pack.ksTc2u/_old 2023-03-15 18:53:47.880156896 +0100 +++ /var/tmp/diff_new_pack.ksTc2u/_new 2023-03-15 18:53:47.884156917 +0100 @@ -27,7 +27,7 @@ %define skip_python2 1 Name: python-starlette%{psuffix} -Version: 0.25.0 +Version: 0.26.1 Release: 0 Summary: Lightweight ASGI framework/toolkit License: BSD-3-Clause ++++++ starlette-0.25.0.tar.gz -> starlette-0.26.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/README.md new/starlette-0.26.1/README.md --- old/starlette-0.25.0/README.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/README.md 2023-03-13 19:08:31.000000000 +0100 @@ -135,7 +135,7 @@ [asgi]: https://asgi.readthedocs.io/en/latest/ [httpx]: https://www.python-httpx.org/ -[jinja2]: http://jinja.pocoo.org/ +[jinja2]: https://jinja.palletsprojects.com/ [python-multipart]: https://andrew-d.github.io/python-multipart/ [itsdangerous]: https://pythonhosted.org/itsdangerous/ [sqlalchemy]: https://www.sqlalchemy.org diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/config.md new/starlette-0.26.1/docs/config.md --- old/starlette-0.25.0/docs/config.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/docs/config.md 2023-03-13 19:08:31.000000000 +0100 @@ -240,7 +240,7 @@ Make a 'client' fixture available to test cases. """ # Our fixture is created within a context manager. This ensures that - # application startup and shutdown run for every test case. + # application lifespan runs for every test case. with TestClient(app) as test_client: yield test_client ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/database.md new/starlette-0.26.1/docs/database.md --- old/starlette-0.25.0/docs/database.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/docs/database.md 2023-03-13 19:08:31.000000000 +0100 @@ -18,6 +18,8 @@ **app.py** ```python +import contextlib + import databases import sqlalchemy from starlette.applications import Starlette @@ -44,6 +46,11 @@ database = databases.Database(DATABASE_URL) +@contextlib.asynccontextmanager +async def lifespan(app): + await database.connect() + yield + await database.disconnect() # Main application code. async def list_notes(request): @@ -77,8 +84,7 @@ app = Starlette( routes=routes, - on_startup=[database.connect], - on_shutdown=[database.disconnect] + lifespan=lifespan, ) ``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/events.md new/starlette-0.26.1/docs/events.md --- old/starlette-0.25.0/docs/events.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/docs/events.md 1970-01-01 01:00:00.000000000 +0100 @@ -1,85 +0,0 @@ - -Starlette applications can register multiple event handlers for dealing with -code that needs to run before the application starts up, or when the application -is shutting down. - -## Registering events - -These event handlers can either be `async` coroutines, or regular synchronous -functions. - -The event handlers should be included on the application like so: - -```python -from starlette.applications import Starlette - - -async def some_startup_task(): - pass - -async def some_shutdown_task(): - pass - -routes = [ - ... -] - -app = Starlette( - routes=routes, - on_startup=[some_startup_task], - on_shutdown=[some_shutdown_task] -) -``` - -Starlette will not start serving any incoming requests until all of the -registered startup handlers have completed. - -The shutdown handlers will run once all connections have been closed, and -any in-process background tasks have completed. - -A single lifespan asynccontextmanager handler can be used instead of -separate startup and shutdown handlers: - -```python -import contextlib -import anyio -from starlette.applications import Starlette - - -@contextlib.asynccontextmanager -async def lifespan(app): - async with some_async_resource(): - yield - - -routes = [ - ... -] - -app = Starlette(routes=routes, lifespan=lifespan) -``` - -Consider using [`anyio.create_task_group()`](https://anyio.readthedocs.io/en/stable/tasks.html) -for managing asynchronous tasks. - -## Running event handlers in tests - -You might want to explicitly call into your event handlers in any test setup -or test teardown code. - -Alternatively, you can use `TestClient` as a context manager, to ensure that -startup and shutdown events are called. - -```python -from example import app -from starlette.testclient import TestClient - - -def test_homepage(): - with TestClient(app) as client: - # Application 'on_startup' handlers are called on entering the block. - response = client.get("/") - assert response.status_code == 200 - - # Application 'on_shutdown' handlers are called on exiting the block. -``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/index.md new/starlette-0.26.1/docs/index.md --- old/starlette-0.25.0/docs/index.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/docs/index.md 2023-03-13 19:08:31.000000000 +0100 @@ -131,7 +131,7 @@ [asgi]: https://asgi.readthedocs.io/en/latest/ [httpx]: https://www.python-httpx.org/ -[jinja2]: http://jinja.pocoo.org/ +[jinja2]: https://jinja.palletsprojects.com/ [python-multipart]: https://andrew-d.github.io/python-multipart/ [itsdangerous]: https://pythonhosted.org/itsdangerous/ [sqlalchemy]: https://www.sqlalchemy.org diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/lifespan.md new/starlette-0.26.1/docs/lifespan.md --- old/starlette-0.25.0/docs/lifespan.md 1970-01-01 01:00:00.000000000 +0100 +++ new/starlette-0.26.1/docs/lifespan.md 2023-03-13 19:08:31.000000000 +0100 @@ -0,0 +1,91 @@ + +Starlette applications can register a lifespan handler for dealing with +code that needs to run before the application starts up, or when the application +is shutting down. + +```python +import contextlib + +from starlette.applications import Starlette + + +@contextlib.asynccontextmanager +async def lifespan(app): + async with some_async_resource(): + print("Run at startup!") + yield + print("Run on shutdown!") + + +routes = [ + ... +] + +app = Starlette(routes=routes, lifespan=lifespan) +``` + +Starlette will not start serving any incoming requests until the lifespan has been run. + +The lifespan teardown will run once all connections have been closed, and +any in-process background tasks have completed. + +Consider using [`anyio.create_task_group()`](https://anyio.readthedocs.io/en/stable/tasks.html) +for managing asynchronous tasks. + +## Lifespan State + +The lifespan has the concept of `state`, which is a dictionary that +can be used to share the objects between the lifespan, and the requests. + +```python +import contextlib +from typing import TypedDict + +import httpx +from starlette.applications import Starlette +from starlette.responses import PlainTextResponse +from starlette.routing import Route + + +class State(TypedDict): + http_client: httpx.AsyncClient + + +@contextlib.asynccontextmanager +async def lifespan(app: Starlette) -> State: + async with httpx.AsyncClient() as client: + yield {"http_client": client} + + +async def homepage(request): + client = request.state.http_client + response = await client.get("https://www.example.com") + return PlainTextResponse(response.text) + + +app = Starlette( + lifespan=lifespan, + routes=[Route("/", homepage)] +) +``` + +The `state` received on the requests is a **shallow** copy of the state received on the +lifespan handler. + +## Running lifespan in tests + +You should use `TestClient` as a context manager, to ensure that the lifespan is called. + +```python +from example import app +from starlette.testclient import TestClient + + +def test_homepage(): + with TestClient(app) as client: + # Application's lifespan is called on entering the block. + response = client.get("/") + assert response.status_code == 200 + + # And the lifespan's teardown is run when exiting the block. +``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/middleware.md new/starlette-0.26.1/docs/middleware.md --- old/starlette-0.25.0/docs/middleware.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/docs/middleware.md 2023-03-13 19:08:31.000000000 +0100 @@ -773,10 +773,6 @@ A middleware class for logging exceptions, errors, and log messages to [Rollbar](https://www.rollbar.com). -#### [SentryMiddleware](https://github.com/encode/sentry-asgi) - -A middleware class for logging exceptions to [Sentry](https://sentry.io/). - #### [StarletteOpentracing](https://github.com/acidjunk/starlette-opentracing) A middleware class that emits tracing info to [OpenTracing.io](https://opentracing.io/) compatible tracers and diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/release-notes.md new/starlette-0.26.1/docs/release-notes.md --- old/starlette-0.25.0/docs/release-notes.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/docs/release-notes.md 2023-03-13 19:08:31.000000000 +0100 @@ -1,3 +1,34 @@ +## 0.26.1 + +March 13, 2023 + +### Fixed +* Fix typing of Lifespan to allow subclasses of Starlette [#2077](https://github.com/encode/starlette/pull/2077). + +## 0.26.0.post1 + +March 9, 2023 + +### Fixed +* Replace reference from Events to Lifespan on the mkdocs.yml [#2072](https://github.com/encode/starlette/pull/2072). + +## 0.26.0 + +March 9, 2023 + +### Added +* Support [lifespan state](/lifespan/) [#2060](https://github.com/encode/starlette/pull/2060), + [#2065](https://github.com/encode/starlette/pull/2065) and [#2064](https://github.com/encode/starlette/pull/2064). + +### Changed +* Change `url_for` signature to return a `URL` instance [#1385](https://github.com/encode/starlette/pull/1385). + +### Fixed +* Allow "name" argument on `url_for()` and `url_path_for()` [#2050](https://github.com/encode/starlette/pull/2050). + +### Deprecated +* Deprecate `on_startup` and `on_shutdown` events [#2070](https://github.com/encode/starlette/pull/2070). + ## 0.25.0 February 14, 2023 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/routing.md new/starlette-0.26.1/docs/routing.md --- old/starlette-0.25.0/docs/routing.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/docs/routing.md 2023-03-13 19:08:31.000000000 +0100 @@ -151,6 +151,8 @@ You'll often want to be able to generate the URL for a particular route, such as in cases where you need to return a redirect response. +* Signature: `url_for(name, **path_params) -> URL` + ```python routes = [ Route("/", homepage, name="homepage") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/testclient.md new/starlette-0.26.1/docs/testclient.md --- old/starlette-0.25.0/docs/testclient.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/docs/testclient.md 2023-03-13 19:08:31.000000000 +0100 @@ -64,10 +64,10 @@ !!! note - If you want the `TestClient` to run `lifespan` events (`on_startup`, `on_shutdown`, or `lifespan`), - you will need to use the `TestClient` as a context manager. Otherwise, the events - will not be triggered when the `TestClient` is instantiated. You can learn more about it - [here](/events/#running-event-handlers-in-tests). + If you want the `TestClient` to run the `lifespan` handler, + you will need to use the `TestClient` as a context manager. It will + not be triggered when the `TestClient` is instantiated. You can learn more about it + [here](/lifespan/#running-lifespan-in-tests). ### Selecting the Async backend diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/docs/third-party-packages.md new/starlette-0.26.1/docs/third-party-packages.md --- old/starlette-0.25.0/docs/third-party-packages.md 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/docs/third-party-packages.md 2023-03-13 19:08:31.000000000 +0100 @@ -170,12 +170,12 @@ ### Flama -<a href="https://github.com/perdy/flama/" target="_blank">GitHub</a> | -<a href="https://flama.perdy.io/" target="_blank">Documentation</a> +<a href="https://github.com/vortico/flama" target="_blank">GitHub</a> | +<a href="https://flama.dev/" target="_blank">Documentation</a> -Formerly Starlette API. +Flama is a **data-science oriented framework** to rapidly build modern and robust **machine learning** (ML) APIs. The main aim of the framework is to make ridiculously simple the deployment of ML APIs. With Flama, data scientists can now quickly turn their ML models into asynchronous, auto-documented APIs with just a single line of code. All in just few seconds! -Flama aims to bring a layer on top of Starlette to provide an **easy to learn** and **fast to develop** approach for building **highly performant** GraphQL and REST APIs. In the same way of Starlette is, Flama is a perfect option for developing **asynchronous** and **production-ready** services. +Flama comes with an intuitive CLI, and provides an easy-to-learn philosophy to speed up the building of **highly performant** GraphQL, REST, and ML APIs. Besides, it comprises an ideal solution for the development of asynchronous and **production-ready** services, offering **automatic deployment** for ML models. ### Greppo diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/mkdocs.yml new/starlette-0.26.1/mkdocs.yml --- old/starlette-0.25.0/mkdocs.yml 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/mkdocs.yml 2023-03-13 19:08:31.000000000 +0100 @@ -39,7 +39,7 @@ - GraphQL: 'graphql.md' - Authentication: 'authentication.md' - API Schemas: 'schemas.md' - - Events: 'events.md' + - Lifespan: 'lifespan.md' - Background Tasks: 'background.md' - Server Push: 'server-push.md' - Exceptions: 'exceptions.md' @@ -55,7 +55,7 @@ - pymdownx.highlight - pymdownx.superfences - pymdownx.tabbed: - alternate_style: true + alternate_style: true extra_javascript: - 'js/chat.js' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/requirements.txt new/starlette-0.26.1/requirements.txt --- old/starlette-0.25.0/requirements.txt 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/requirements.txt 2023-03-13 19:08:31.000000000 +0100 @@ -8,19 +8,19 @@ flake8==3.9.2 importlib-metadata==4.13.0 isort==5.10.1 -mypy==0.991 +mypy==1.0.1 typing_extensions==4.4.0 -types-contextvars==2.4.7 +types-contextvars==2.4.7.1 types-PyYAML==6.0.12.3 types-dataclasses==0.6.6 -pytest==7.2.0 +pytest==7.2.1 trio==0.21.0 # Documentation mkdocs==1.4.2 -mkdocs-material==8.5.7 +mkdocs-material==9.0.15 mkautodoc==0.2.0 # Packaging -build==0.9.0 +build==0.10.0 twine==4.0.2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/starlette/__init__.py new/starlette-0.26.1/starlette/__init__.py --- old/starlette-0.25.0/starlette/__init__.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/starlette/__init__.py 2023-03-13 19:08:31.000000000 +0100 @@ -1 +1 @@ -__version__ = "0.25.0" +__version__ = "0.26.1" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/starlette/applications.py new/starlette-0.26.1/starlette/applications.py --- old/starlette-0.25.0/starlette/applications.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/starlette/applications.py 2023-03-13 19:08:31.000000000 +0100 @@ -9,7 +9,9 @@ from starlette.requests import Request from starlette.responses import Response from starlette.routing import BaseRoute, Router -from starlette.types import ASGIApp, Receive, Scope, Send +from starlette.types import ASGIApp, Lifespan, Receive, Scope, Send + +AppType = typing.TypeVar("AppType", bound="Starlette") class Starlette: @@ -37,10 +39,13 @@ * **on_shutdown** - A list of callables to run on application shutdown. Shutdown handler callables do not take any arguments, and may be be either standard functions, or async functions. + * **lifespan** - A lifespan context function, which can be used to perform + startup and shutdown tasks. This is a newer style that replaces the + `on_startup` and `on_shutdown` handlers. Use one or the other, not both. """ def __init__( - self, + self: "AppType", debug: bool = False, routes: typing.Optional[typing.Sequence[BaseRoute]] = None, middleware: typing.Optional[typing.Sequence[Middleware]] = None, @@ -55,9 +60,7 @@ ] = None, on_startup: typing.Optional[typing.Sequence[typing.Callable]] = None, on_shutdown: typing.Optional[typing.Sequence[typing.Callable]] = None, - lifespan: typing.Optional[ - typing.Callable[["Starlette"], typing.AsyncContextManager] - ] = None, + lifespan: typing.Optional[Lifespan["AppType"]] = None, ) -> None: # The lifespan context function is a newer style that replaces # on_startup / on_shutdown handlers. Use one or the other, not both. @@ -108,8 +111,9 @@ def routes(self) -> typing.List[BaseRoute]: return self.router.routes - def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath: - return self.router.url_path_for(name, **path_params) + # TODO: Make `__name` a positional-only argument when we drop Python 3.7 support. + def url_path_for(self, __name: str, **path_params: typing.Any) -> URLPath: + return self.router.url_path_for(__name, **path_params) async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: scope["app"] = self diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/starlette/datastructures.py new/starlette-0.26.1/starlette/datastructures.py --- old/starlette-0.25.0/starlette/datastructures.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/starlette/datastructures.py 2023-03-13 19:08:31.000000000 +0100 @@ -187,7 +187,7 @@ self.protocol = protocol self.host = host - def make_absolute_url(self, base_url: typing.Union[str, URL]) -> str: + def make_absolute_url(self, base_url: typing.Union[str, URL]) -> URL: if isinstance(base_url, str): base_url = URL(base_url) if self.protocol: @@ -200,7 +200,7 @@ netloc = self.host or base_url.netloc path = base_url.path.rstrip("/") + str(self) - return str(URL(scheme=scheme, netloc=netloc, path=path)) + return URL(scheme=scheme, netloc=netloc, path=path) class Secret: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/starlette/requests.py new/starlette-0.26.1/starlette/requests.py --- old/starlette-0.25.0/starlette/requests.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/starlette/requests.py 2023-03-13 19:08:31.000000000 +0100 @@ -173,9 +173,9 @@ self._state = State(self.scope["state"]) return self._state - def url_for(self, name: str, **path_params: typing.Any) -> str: + def url_for(self, __name: str, **path_params: typing.Any) -> URL: router: Router = self.scope["router"] - url_path = router.url_path_for(name, **path_params) + url_path = router.url_path_for(__name, **path_params) return url_path.make_absolute_url(base_url=self.base_url) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/starlette/routing.py new/starlette-0.26.1/starlette/routing.py --- old/starlette-0.25.0/starlette/routing.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/starlette/routing.py 2023-03-13 19:08:31.000000000 +0100 @@ -17,7 +17,7 @@ from starlette.middleware import Middleware from starlette.requests import Request from starlette.responses import PlainTextResponse, RedirectResponse -from starlette.types import ASGIApp, Receive, Scope, Send +from starlette.types import ASGIApp, Lifespan, Receive, Scope, Send from starlette.websockets import WebSocket, WebSocketClose @@ -170,7 +170,7 @@ def matches(self, scope: Scope) -> typing.Tuple[Match, Scope]: raise NotImplementedError() # pragma: no cover - def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath: + def url_path_for(self, __name: str, **path_params: typing.Any) -> URLPath: raise NotImplementedError() # pragma: no cover async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: @@ -249,12 +249,12 @@ return Match.FULL, child_scope return Match.NONE, {} - def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath: + def url_path_for(self, __name: str, **path_params: typing.Any) -> URLPath: seen_params = set(path_params.keys()) expected_params = set(self.param_convertors.keys()) - if name != self.name or seen_params != expected_params: - raise NoMatchFound(name, path_params) + if __name != self.name or seen_params != expected_params: + raise NoMatchFound(__name, path_params) path, remaining_params = replace_params( self.path_format, self.param_convertors, path_params @@ -324,12 +324,12 @@ return Match.FULL, child_scope return Match.NONE, {} - def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath: + def url_path_for(self, __name: str, **path_params: typing.Any) -> URLPath: seen_params = set(path_params.keys()) expected_params = set(self.param_convertors.keys()) - if name != self.name or seen_params != expected_params: - raise NoMatchFound(name, path_params) + if __name != self.name or seen_params != expected_params: + raise NoMatchFound(__name, path_params) path, remaining_params = replace_params( self.path_format, self.param_convertors, path_params @@ -406,8 +406,8 @@ return Match.FULL, child_scope return Match.NONE, {} - def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath: - if self.name is not None and name == self.name and "path" in path_params: + def url_path_for(self, __name: str, **path_params: typing.Any) -> URLPath: + if self.name is not None and __name == self.name and "path" in path_params: # 'name' matches "<mount_name>". path_params["path"] = path_params["path"].lstrip("/") path, remaining_params = replace_params( @@ -415,13 +415,13 @@ ) if not remaining_params: return URLPath(path=path) - elif self.name is None or name.startswith(self.name + ":"): + elif self.name is None or __name.startswith(self.name + ":"): if self.name is None: # No mount name. - remaining_name = name + remaining_name = __name else: # 'name' matches "<mount_name>:<child_name>". - remaining_name = name[len(self.name) + 1 :] + remaining_name = __name[len(self.name) + 1 :] path_kwarg = path_params.get("path") path_params["path"] = "" path_prefix, remaining_params = replace_params( @@ -437,7 +437,7 @@ ) except NoMatchFound: pass - raise NoMatchFound(name, path_params) + raise NoMatchFound(__name, path_params) async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: await self.app(scope, receive, send) @@ -484,8 +484,8 @@ return Match.FULL, child_scope return Match.NONE, {} - def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath: - if self.name is not None and name == self.name and "path" in path_params: + def url_path_for(self, __name: str, **path_params: typing.Any) -> URLPath: + if self.name is not None and __name == self.name and "path" in path_params: # 'name' matches "<mount_name>". path = path_params.pop("path") host, remaining_params = replace_params( @@ -493,13 +493,13 @@ ) if not remaining_params: return URLPath(path=path, host=host) - elif self.name is None or name.startswith(self.name + ":"): + elif self.name is None or __name.startswith(self.name + ":"): if self.name is None: # No mount name. - remaining_name = name + remaining_name = __name else: # 'name' matches "<mount_name>:<child_name>". - remaining_name = name[len(self.name) + 1 :] + remaining_name = __name[len(self.name) + 1 :] host, remaining_params = replace_params( self.host_format, self.param_convertors, path_params ) @@ -509,7 +509,7 @@ return URLPath(path=str(url), protocol=url.protocol, host=host) except NoMatchFound: pass - raise NoMatchFound(name, path_params) + raise NoMatchFound(__name, path_params) async def handle(self, scope: Scope, receive: Receive, send: Send) -> None: await self.app(scope, receive, send) @@ -580,9 +580,9 @@ default: typing.Optional[ASGIApp] = None, on_startup: typing.Optional[typing.Sequence[typing.Callable]] = None, on_shutdown: typing.Optional[typing.Sequence[typing.Callable]] = None, - lifespan: typing.Optional[ - typing.Callable[[typing.Any], typing.AsyncContextManager] - ] = None, + # the generic to Lifespan[AppType] is the type of the top level application + # which the router cannot know statically, so we use typing.Any + lifespan: typing.Optional[Lifespan[typing.Any]] = None, ) -> None: self.routes = [] if routes is None else list(routes) self.redirect_slashes = redirect_slashes @@ -590,10 +590,16 @@ self.on_startup = [] if on_startup is None else list(on_startup) self.on_shutdown = [] if on_shutdown is None else list(on_shutdown) + if on_startup or on_shutdown: + warnings.warn( + "The on_startup and on_shutdown parameters are deprecated, and they " + "will be removed on version 1.0. Use the lifespan parameter instead. " + "See more about it on https://www.starlette.io/lifespan/.", + DeprecationWarning, + ) + if lifespan is None: - self.lifespan_context: typing.Callable[ - [typing.Any], typing.AsyncContextManager - ] = _DefaultLifespan(self) + self.lifespan_context: Lifespan = _DefaultLifespan(self) elif inspect.isasyncgenfunction(lifespan): warnings.warn( @@ -631,13 +637,13 @@ response = PlainTextResponse("Not Found", status_code=404) await response(scope, receive, send) - def url_path_for(self, name: str, **path_params: typing.Any) -> URLPath: + def url_path_for(self, __name: str, **path_params: typing.Any) -> URLPath: for route in self.routes: try: - return route.url_path_for(name, **path_params) + return route.url_path_for(__name, **path_params) except NoMatchFound: pass - raise NoMatchFound(name, path_params) + raise NoMatchFound(__name, path_params) async def startup(self) -> None: """ @@ -665,10 +671,16 @@ startup and shutdown events. """ started = False - app = scope.get("app") + app: typing.Any = scope.get("app") await receive() try: - async with self.lifespan_context(app): + async with self.lifespan_context(app) as maybe_state: + if maybe_state is not None: + if "state" not in scope: + raise RuntimeError( + 'The server does not support "state" in the lifespan scope.' + ) + scope["state"].update(maybe_state) await send({"type": "lifespan.startup.complete"}) started = True await receive() @@ -839,7 +851,7 @@ def on_event(self, event_type: str) -> typing.Callable: warnings.warn( "The `on_event` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501 - "Refer to https://www.starlette.io/events/#registering-events for recommended approach.", # noqa: E501 + "Refer to https://www.starlette.io/lifespan/ for recommended approach.", DeprecationWarning, ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/starlette/templating.py new/starlette-0.26.1/starlette/templating.py --- old/starlette-0.25.0/starlette/templating.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/starlette/templating.py 2023-03-13 19:08:31.000000000 +0100 @@ -2,6 +2,7 @@ from os import PathLike from starlette.background import BackgroundTask +from starlette.datastructures import URL from starlette.requests import Request from starlette.responses import Response from starlette.types import Receive, Scope, Send @@ -77,7 +78,7 @@ self, directory: typing.Union[str, PathLike], **env_options: typing.Any ) -> "jinja2.Environment": @pass_context - def url_for(context: dict, name: str, **path_params: typing.Any) -> str: + def url_for(context: dict, name: str, **path_params: typing.Any) -> URL: request = context["request"] return request.url_for(name, **path_params) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/starlette/testclient.py new/starlette-0.26.1/starlette/testclient.py --- old/starlette-0.25.0/starlette/testclient.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/starlette/testclient.py 2023-03-13 19:08:31.000000000 +0100 @@ -188,11 +188,14 @@ portal_factory: _PortalFactoryType, raise_server_exceptions: bool = True, root_path: str = "", + *, + app_state: typing.Dict[str, typing.Any], ) -> None: self.app = app self.raise_server_exceptions = raise_server_exceptions self.root_path = root_path self.portal_factory = portal_factory + self.app_state = app_state def handle_request(self, request: httpx.Request) -> httpx.Response: scheme = request.url.scheme @@ -243,6 +246,7 @@ "client": ["testclient", 50000], "server": [host, port], "subprotocols": subprotocols, + "state": self.app_state.copy(), } session = WebSocketTestSession(self.app, scope, self.portal_factory) raise _Upgrade(session) @@ -260,6 +264,7 @@ "client": ["testclient", 50000], "server": [host, port], "extensions": {"http.response.debug": {}}, + "state": self.app_state.copy(), } request_complete = False @@ -380,11 +385,13 @@ app = typing.cast(ASGI2App, app) # type: ignore[assignment] asgi_app = _WrapASGI2(app) # type: ignore[arg-type] self.app = asgi_app + self.app_state: typing.Dict[str, typing.Any] = {} transport = _TestClientTransport( self.app, portal_factory=self._portal_factory, raise_server_exceptions=raise_server_exceptions, root_path=root_path, + app_state=self.app_state, ) if headers is None: headers = {} @@ -749,7 +756,7 @@ self.exit_stack.close() async def lifespan(self) -> None: - scope = {"type": "lifespan"} + scope = {"type": "lifespan", "state": self.app_state} try: await self.app(scope, self.stream_receive.receive, self.stream_send.send) finally: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/starlette/types.py new/starlette-0.26.1/starlette/types.py --- old/starlette-0.25.0/starlette/types.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/starlette/types.py 2023-03-13 19:08:31.000000000 +0100 @@ -1,5 +1,7 @@ import typing +AppType = typing.TypeVar("AppType") + Scope = typing.MutableMapping[str, typing.Any] Message = typing.MutableMapping[str, typing.Any] @@ -7,3 +9,9 @@ Send = typing.Callable[[Message], typing.Awaitable[None]] ASGIApp = typing.Callable[[Scope, Receive, Send], typing.Awaitable[None]] + +StatelessLifespan = typing.Callable[[AppType], typing.AsyncContextManager[None]] +StatefulLifespan = typing.Callable[ + [AppType], typing.AsyncContextManager[typing.Mapping[str, typing.Any]] +] +Lifespan = typing.Union[StatelessLifespan[AppType], StatefulLifespan[AppType]] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/tests/test_applications.py new/starlette-0.26.1/tests/test_applications.py --- old/starlette-0.25.0/tests/test_applications.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/tests/test_applications.py 2023-03-13 19:08:31.000000000 +0100 @@ -1,6 +1,6 @@ import os from contextlib import asynccontextmanager -from typing import Any, Callable +from typing import Any, AsyncIterator, Callable import anyio import httpx @@ -345,10 +345,13 @@ nonlocal cleanup_complete cleanup_complete = True - app = Starlette( - on_startup=[run_startup], - on_shutdown=[run_cleanup], - ) + with pytest.deprecated_call( + match="The on_startup and on_shutdown parameters are deprecated" + ): + app = Starlette( + on_startup=[run_startup], + on_shutdown=[run_cleanup], + ) assert not startup_complete assert not cleanup_complete @@ -531,3 +534,17 @@ test_client_factory(app).get("/foo") assert SimpleInitializableMiddleware.counter == 2 + + +def test_lifespan_app_subclass(): + # This test exists to make sure that subclasses of Starlette + # (like FastAPI) are compatible with the types hints for Lifespan + + class App(Starlette): + pass + + @asynccontextmanager + async def lifespan(app: App) -> AsyncIterator[None]: # pragma: no cover + yield + + App(lifespan=lifespan) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/tests/test_routing.py new/starlette-0.26.1/tests/test_routing.py --- old/starlette-0.25.0/tests/test_routing.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/tests/test_routing.py 2023-03-13 19:08:31.000000000 +0100 @@ -1,7 +1,14 @@ +import contextlib import functools +import sys import typing import uuid +if sys.version_info < (3, 8): + from typing_extensions import TypedDict # pragma: no cover +else: + from typing import TypedDict # pragma: no cover + import pytest from starlette.applications import Starlette @@ -521,8 +528,8 @@ async def echo_urls(request): return JSONResponse( { - "index": request.url_for("index"), - "submount": request.url_for("mount:submount"), + "index": str(request.url_for("index")), + "submount": str(request.url_for("mount:submount")), } ) @@ -622,11 +629,14 @@ nonlocal shutdown_complete shutdown_complete = True - app = Router( - on_startup=[run_startup], - on_shutdown=[run_shutdown], - routes=[Route("/", hello_world)], - ) + with pytest.deprecated_call( + match="The on_startup and on_shutdown parameters are deprecated" + ): + app = Router( + on_startup=[run_startup], + on_shutdown=[run_shutdown], + routes=[Route("/", hello_world)], + ) assert not startup_complete assert not shutdown_complete @@ -653,9 +663,79 @@ nonlocal shutdown_complete shutdown_complete = True + with pytest.deprecated_call( + match="The on_startup and on_shutdown parameters are deprecated" + ): + app = Router( + on_startup=[run_startup], + on_shutdown=[run_shutdown], + routes=[Route("/", hello_world)], + ) + + assert not startup_complete + assert not shutdown_complete + with test_client_factory(app) as client: + assert startup_complete + assert not shutdown_complete + client.get("/") + assert startup_complete + assert shutdown_complete + + +def test_lifespan_state_unsupported(test_client_factory): + @contextlib.asynccontextmanager + async def lifespan(app): + yield {"foo": "bar"} + app = Router( - on_startup=[run_startup], - on_shutdown=[run_shutdown], + lifespan=lifespan, + routes=[Mount("/", PlainTextResponse("hello, world"))], + ) + + async def no_state_wrapper(scope, receive, send): + del scope["state"] + await app(scope, receive, send) + + with pytest.raises( + RuntimeError, match='The server does not support "state" in the lifespan scope' + ): + with test_client_factory(no_state_wrapper): + raise AssertionError("Should not be called") # pragma: no cover + + +def test_lifespan_state_async_cm(test_client_factory): + startup_complete = False + shutdown_complete = False + + class State(TypedDict): + count: int + items: typing.List[int] + + async def hello_world(request: Request) -> Response: + # modifications to the state should not leak across requests + assert request.state.count == 0 + # modify the state, this should not leak to the lifespan or other requests + request.state.count += 1 + # since state.items is a mutable object this modification _will_ leak across + # requests and to the lifespan + request.state.items.append(1) + return PlainTextResponse("hello, world") + + @contextlib.asynccontextmanager + async def lifespan(app: Starlette) -> typing.AsyncIterator[State]: + nonlocal startup_complete, shutdown_complete + startup_complete = True + state = State(count=0, items=[]) + yield state + shutdown_complete = True + # modifications made to the state from a request do not leak to the lifespan + assert state["count"] == 0 + # unless of course the request mutates a mutable object that is referenced + # via state + assert state["items"] == [1, 1] + + app = Router( + lifespan=lifespan, routes=[Route("/", hello_world)], ) @@ -665,6 +745,8 @@ assert startup_complete assert not shutdown_complete client.get("/") + # Calling it a second time to ensure that the state is preserved. + client.get("/") assert startup_complete assert shutdown_complete @@ -673,7 +755,10 @@ def run_startup(): raise RuntimeError() - router = Router(on_startup=[run_startup]) + with pytest.deprecated_call( + match="The on_startup and on_shutdown parameters are deprecated" + ): + router = Router(on_startup=[run_startup]) startup_failed = False async def app(scope, receive, send): @@ -695,7 +780,10 @@ def run_shutdown(): raise RuntimeError() - app = Router(on_shutdown=[run_shutdown]) + with pytest.deprecated_call( + match="The on_startup and on_shutdown parameters are deprecated" + ): + app = Router(on_shutdown=[run_shutdown]) with pytest.raises(RuntimeError): with test_client_factory(app): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/starlette-0.25.0/tests/test_testclient.py new/starlette-0.26.1/tests/test_testclient.py --- old/starlette-0.25.0/tests/test_testclient.py 2023-02-14 10:06:03.000000000 +0100 +++ new/starlette-0.26.1/tests/test_testclient.py 2023-03-13 19:08:31.000000000 +0100 @@ -45,9 +45,6 @@ raise RuntimeError() -startup_error_app = Starlette(on_startup=[startup]) - - def test_use_testclient_in_endpoint(test_client_factory): """ We should be able to use the test client within applications. @@ -166,6 +163,11 @@ def test_error_on_startup(test_client_factory): + with pytest.deprecated_call( + match="The on_startup and on_shutdown parameters are deprecated" + ): + startup_error_app = Starlette(on_startup=[startup]) + with pytest.raises(RuntimeError): with test_client_factory(startup_error_app): pass # pragma: no cover