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

Version 1.0.0 #2384

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions docs/applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ Starlette includes an application class `Starlette` that nicely ties together al
its other functionality.

```python
from contextlib import asynccontextmanager

from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route, Mount, WebSocketRoute
Expand All @@ -25,8 +27,11 @@ async def websocket_endpoint(websocket):
await websocket.send_text('Hello, websocket!')
await websocket.close()

def startup():
@asynccontextmanager
async def lifespan(app: Starlette):
print('Ready to go')
yield
Kludex marked this conversation as resolved.
Show resolved Hide resolved
print('Shutting down')


routes = [
Expand All @@ -37,7 +42,7 @@ routes = [
Mount('/static', StaticFiles(directory="static")),
]

app = Starlette(debug=True, routes=routes, on_startup=[startup])
app = Starlette(debug=True, routes=routes, lifespan=lifespan)
```

### Instantiating the application
Expand Down
16 changes: 16 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## 1.0.0

The project versioning policy is now explicitly governed by SEMVER. See https://semver.org/.

### Removed

All deprecated features have been removed.

* Removed `WSGIMiddleware`. Please use [`a2wsgi`](https://github.com/abersheeran/a2wsgi) instead.
* Removed `run_until_first_complete`.
* Removed `on_startup` and `on_shutdown` events. Please use `lifespan` instead.
* Removed `iscoroutinefunction_or_partial`, which we have replaced by `_utils.is_async_callable`.
* Removed `WS_1004_NO_STATUS_RCVD` and `WS_1005_ABNORMAL_CLOSURE` from the `status` module.
* Removed `ExceptionMiddleware` from the `exceptions` module, it can now be found in the `middleware.exceptions` module.
* Removed multiple possible argument sequences from `TemplateResponse`.

## 0.38.1

July 23, 2024
Expand Down
25 changes: 4 additions & 21 deletions docs/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ Jinja2 provides an excellent choice.

### Jinja2Templates

Signature: `Jinja2Templates(directory, context_processors=None, **env_options)`
Signature: `Jinja2Templates(directory, context_processors=None, env=None)`

* `directory` - A string, [os.Pathlike][pathlike] or a list of strings or [os.Pathlike][pathlike] denoting a directory path.
* `context_processors` - A list of functions that return a dictionary to add to the template context.
* `**env_options` - Additional keyword arguments to pass to the Jinja2 environment.
* `env` - A preconfigured [`jinja2.Environment`](https://jinja.palletsprojects.com/en/3.0.x/api/#api) instance.

Starlette provides a simple way to get `jinja2` configured. This is probably
what you want to use by default.
Expand Down Expand Up @@ -58,11 +58,9 @@ templates = Jinja2Templates(directory='templates')
templates.env.filters['marked'] = marked_filter
```

## Using custom `jinja2.Environment` instance

## Using custom jinja2.Environment instance

Starlette also accepts a preconfigured [`jinja2.Environment`](https://jinja.palletsprojects.com/en/3.0.x/api/#api) instance.

Starlette accepts a preconfigured [`jinja2.Environment`](https://jinja.palletsprojects.com/en/3.0.x/api/#api) instance.

```python
import jinja2
Expand All @@ -72,7 +70,6 @@ env = jinja2.Environment(...)
templates = Jinja2Templates(env=env)
```


## Context processors

A context processor is a function that returns a dictionary to be merged into a template context.
Expand Down Expand Up @@ -126,20 +123,6 @@ def test_homepage():
assert "request" in response.context
```

## Customizing Jinja2 Environment

`Jinja2Templates` accepts all options supported by Jinja2 `Environment`.
This will allow more control over the `Environment` instance created by Starlette.

For the list of options available to `Environment` you can check Jinja2 documentation [here](https://jinja.palletsprojects.com/en/3.0.x/api/#jinja2.Environment)

```python
from starlette.templating import Jinja2Templates


templates = Jinja2Templates(directory='templates', autoescape=False, auto_reload=True)
```

## Asynchronous template rendering

Jinja2 supports async template rendering, however as a general rule
Expand Down
3 changes: 0 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,9 @@ xfail_strict = true
filterwarnings = [
# Turn warnings that aren't filtered into exceptions
"error",
"ignore: run_until_first_complete is deprecated and will be removed in a future version.:DeprecationWarning",
"ignore: starlette.middleware.wsgi is deprecated and will be removed in a future release.*:DeprecationWarning",
"ignore: Async generator 'starlette.requests.Request.stream' was garbage collected before it had been exhausted.*:ResourceWarning",
"ignore: path is deprecated.*:DeprecationWarning:certifi",
"ignore: Use 'content=<...>' to upload raw bytes/text content.:DeprecationWarning",
"ignore: The `allow_redirects` argument is deprecated. Use `follow_redirects` instead.:DeprecationWarning",
"ignore: 'cgi' is deprecated and slated for removal in Python 3.13:DeprecationWarning",
"ignore: You seem to already have a custom sys.excepthook handler installed. I'll skip installing Trio's custom handler, but this means MultiErrors will not show full tracebacks.:RuntimeWarning",
]
Expand Down
2 changes: 1 addition & 1 deletion starlette/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.38.1"
__version__ = "1.0.0"
123 changes: 2 additions & 121 deletions starlette/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import sys
import typing
import warnings

if sys.version_info >= (3, 10): # pragma: no cover
from typing import ParamSpec
Expand All @@ -11,7 +10,6 @@

from starlette.datastructures import State, URLPath
from starlette.middleware import Middleware, _MiddlewareClass
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.middleware.errors import ServerErrorMiddleware
from starlette.middleware.exceptions import ExceptionMiddleware
Copy link
Member

Choose a reason for hiding this comment

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

We need to remove ExceptionMiddleware from here. And maybe delete that file as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Where should we register the starlette.exception_handlers now?

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I guess we need to get rid of is this part:

await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)

Copy link
Member Author

@Kludex Kludex Dec 24, 2023

Choose a reason for hiding this comment

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

Ah! Just to confirm: you mean that the ExceptionMiddleware should just register the exception_handlers in the scope, but the wrap_app_handling_exceptions should NOT be called there, only on the other places we have them (request_response and websocket_session). Correct?

Copy link
Member

Choose a reason for hiding this comment

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

Yep!

Copy link
Member Author

Choose a reason for hiding this comment

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

I cannot come up with a solution for this, FYI.

Copy link
Member Author

Choose a reason for hiding this comment

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

@adriangb how do you want to proceed here? We talked in PyCon US a bit, but I don't recall how we left this.

This is the only blocker for 1.0.

The only thing is that I don't want to add any breaking change.

Copy link
Member

@adriangb adriangb Jul 23, 2024

Choose a reason for hiding this comment

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

If you want to do this without any breaking change we can just leave ExceptionMiddleware where it is and ignore this thread.

But conceptually I think the thing is that:

  1. After routing once we have a Request/Response exception handling can be done via a Request/Response (the current API).
  2. Outside of that (e.g. in other middleware or routing) we're in pure ASGI land. We could still let you catch HTTPException's but I think the API should essentially be an ASGI app HTTPException | int, Scope, Receive, Send or something like that. But that would be a breaking change. Otherwise I fear we're going to get bugs like "I wanted to catch the 404 HTTPException and log the request body but when I called .body() I got a StreamConsumed error".

To do that we'd have to replace ExceptionMiddleware with AppExceptionMiddleware or something like that with the new API and have two different arguments to Starlette for each type of exception handler.

Copy link
Member

Choose a reason for hiding this comment

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

Leaving the pure ASGI realm, I have a minimalistic and effective design. https://github.com/abersheeran/baize/blob/master/baize/asgi/shortcut.py#L59

Copy link
Member

Choose a reason for hiding this comment

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

Very nice :)

from starlette.requests import Request
Expand Down Expand Up @@ -43,15 +41,8 @@ class Starlette:
Exception handler callables should be of the form
`handler(request, exc) -> response` and may be either standard functions, or
async functions.
* **on_startup** - A list of callables to run on application startup.
Startup handler callables do not take any arguments, and may be either
standard functions, or async functions.
* **on_shutdown** - A list of callables to run on application shutdown.
Shutdown handler callables do not take any arguments, and may 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.
startup and shutdown tasks.
"""

def __init__(
Expand All @@ -60,21 +51,11 @@ def __init__(
routes: typing.Sequence[BaseRoute] | None = None,
middleware: typing.Sequence[Middleware] | None = None,
exception_handlers: typing.Mapping[typing.Any, ExceptionHandler] | None = None,
on_startup: typing.Sequence[typing.Callable[[], typing.Any]] | None = None,
on_shutdown: typing.Sequence[typing.Callable[[], typing.Any]] | None = None,
lifespan: Lifespan[AppType] | None = None,
) -> None:
# The lifespan context function is a newer style that replaces
# on_startup / on_shutdown handlers. Use one or the other, not both.
assert lifespan is None or (
on_startup is None and on_shutdown is None
), "Use either 'lifespan' or 'on_startup'/'on_shutdown', not both."

self.debug = debug
self.state = State()
self.router = Router(
routes, on_startup=on_startup, on_shutdown=on_shutdown, lifespan=lifespan
)
self.router = Router(routes, lifespan=lifespan)
self.exception_handlers = (
{} if exception_handlers is None else dict(exception_handlers)
)
Expand Down Expand Up @@ -122,9 +103,6 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
self.middleware_stack = self.build_middleware_stack()
await self.middleware_stack(scope, receive, send)

def on_event(self, event_type: str) -> typing.Callable: # type: ignore[type-arg]
return self.router.on_event(event_type) # pragma: nocover

def mount(self, path: str, app: ASGIApp, name: str | None = None) -> None:
self.router.mount(path, app=app, name=name) # pragma: no cover

Expand All @@ -148,13 +126,6 @@ def add_exception_handler(
) -> None: # pragma: no cover
self.exception_handlers[exc_class_or_status_code] = handler

def add_event_handler(
self,
event_type: str,
func: typing.Callable, # type: ignore[type-arg]
) -> None: # pragma: no cover
self.router.add_event_handler(event_type, func)

def add_route(
self,
path: str,
Expand All @@ -174,93 +145,3 @@ def add_websocket_route(
name: str | None = None,
) -> None: # pragma: no cover
self.router.add_websocket_route(path, route, name=name)

def exception_handler(
self, exc_class_or_status_code: int | type[Exception]
) -> typing.Callable: # type: ignore[type-arg]
warnings.warn(
"The `exception_handler` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
"Refer to https://www.starlette.io/exceptions/ for the recommended approach.", # noqa: E501
DeprecationWarning,
)

def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
self.add_exception_handler(exc_class_or_status_code, func)
return func

return decorator

def route(
self,
path: str,
methods: list[str] | None = None,
name: str | None = None,
include_in_schema: bool = True,
) -> typing.Callable: # type: ignore[type-arg]
"""
We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach:

>>> routes = [Route(path, endpoint=...), ...]
>>> app = Starlette(routes=routes)
"""
warnings.warn(
"The `route` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
"Refer to https://www.starlette.io/routing/ for the recommended approach.", # noqa: E501
DeprecationWarning,
)

def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
self.router.add_route(
path,
func,
methods=methods,
name=name,
include_in_schema=include_in_schema,
)
return func

return decorator

def websocket_route(self, path: str, name: str | None = None) -> typing.Callable: # type: ignore[type-arg]
"""
We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach:

>>> routes = [WebSocketRoute(path, endpoint=...), ...]
>>> app = Starlette(routes=routes)
"""
warnings.warn(
"The `websocket_route` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
"Refer to https://www.starlette.io/routing/#websocket-routing for the recommended approach.", # noqa: E501
DeprecationWarning,
)

def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
self.router.add_websocket_route(path, func, name=name)
return func

return decorator

def middleware(self, middleware_type: str) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
"""
We no longer document this decorator style API, and its usage is discouraged.
Instead you should use the following approach:

>>> middleware = [Middleware(...), ...]
>>> app = Starlette(middleware=middleware)
"""
warnings.warn(
"The `middleware` decorator is deprecated, and will be removed in version 1.0.0. " # noqa: E501
"Refer to https://www.starlette.io/middleware/#using-middleware for recommended approach.", # noqa: E501
DeprecationWarning,
)
assert (
middleware_type == "http"
), 'Currently only middleware("http") is supported.'

def decorator(func: typing.Callable) -> typing.Callable: # type: ignore[type-arg] # noqa: E501
self.add_middleware(BaseHTTPMiddleware, dispatch=func)
return func

return decorator
18 changes: 0 additions & 18 deletions starlette/concurrency.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import functools
import sys
import typing
import warnings

import anyio.to_thread

Expand All @@ -16,23 +15,6 @@
T = typing.TypeVar("T")


async def run_until_first_complete(*args: tuple[typing.Callable, dict]) -> None: # type: ignore[type-arg] # noqa: E501
warnings.warn(
"run_until_first_complete is deprecated "
"and will be removed in a future version.",
DeprecationWarning,
)

async with anyio.create_task_group() as task_group:

async def run(func: typing.Callable[[], typing.Coroutine]) -> None: # type: ignore[type-arg] # noqa: E501
await func()
task_group.cancel_scope.cancel()

for func, kwargs in args:
task_group.start_soon(run, functools.partial(func, **kwargs))


async def run_in_threadpool(
func: typing.Callable[P, T], *args: P.args, **kwargs: P.kwargs
) -> T:
Expand Down
23 changes: 0 additions & 23 deletions starlette/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from __future__ import annotations

import http
import typing
import warnings

__all__ = ("HTTPException", "WebSocketException")

Expand Down Expand Up @@ -39,24 +37,3 @@ def __str__(self) -> str:
def __repr__(self) -> str:
class_name = self.__class__.__name__
return f"{class_name}(code={self.code!r}, reason={self.reason!r})"


__deprecated__ = "ExceptionMiddleware"


def __getattr__(name: str) -> typing.Any: # pragma: no cover
if name == __deprecated__:
from starlette.middleware.exceptions import ExceptionMiddleware

warnings.warn(
f"{__deprecated__} is deprecated on `starlette.exceptions`. "
f"Import it from `starlette.middleware.exceptions` instead.",
category=DeprecationWarning,
stacklevel=3,
)
return ExceptionMiddleware
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")


def __dir__() -> list[str]:
return sorted(list(__all__) + [__deprecated__]) # pragma: no cover
Loading