Skip to content

Commit

Permalink
Merge branch 'master' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
Kludex committed Aug 6, 2024
2 parents f9aaed7 + e46165a commit 5080577
Show file tree
Hide file tree
Showing 41 changed files with 246 additions and 261 deletions.
35 changes: 35 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
## 0.38.2

July 27, 2024

#### Fixed

* Not assume all routines have `__name__` on `routing.get_name()` [#2648](https://github.com/encode/starlette/pull/2648).

## 0.38.1

July 23, 2024

#### Removed

* Revert "Add support for ASGI pathsend extension" [#2649](https://github.com/encode/starlette/pull/2649).

## 0.38.0

July 20, 2024

#### Added

* Allow use of `memoryview` in `StreamingResponse` and `Response` [#2576](https://github.com/encode/starlette/pull/2576)
and [#2577](https://github.com/encode/starlette/pull/2577).
* Send 404 instead of 500 when filename requested is too long on `StaticFiles` [#2583](https://github.com/encode/starlette/pull/2583).

#### Changed

* Fail fast on invalid `Jinja2Template` instantiation parameters [#2568](https://github.com/encode/starlette/pull/2568).
* Check endpoint handler is async only once [#2536](https://github.com/encode/starlette/pull/2536).

#### Fixed

* Add proper synchronization to `WebSocketTestSession` [#2597](https://github.com/encode/starlette/pull/2597).

## 0.37.2

March 5, 2024
Expand Down
20 changes: 20 additions & 0 deletions docs/third-party-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ Simple APISpec integration for Starlette.
Document your REST API built with Starlette by declaring OpenAPI (Swagger)
schemas in YAML format in your endpoint's docstrings.

### Starlette Compress

<a href="https://github.com/Zaczero/starlette-compress" target="_blank">GitHub</a>

Starlette-Compress is a fast and simple middleware for compressing responses in Starlette.
It adds ZStd, Brotli, and GZip compression support with sensible default configuration.

### Starlette Context

<a href="https://github.com/tomwojcik/starlette-context" target="_blank">GitHub</a>
Expand Down Expand Up @@ -148,6 +155,12 @@ Built with [Tabler](https://tabler.io/) and [Datatables](https://datatables.net/
to quickly generate fully customizable admin interface for your models. You can export your data to many formats (*CSV*, *PDF*,
*Excel*, etc), filter your data with complex query including `AND` and `OR` conditions, upload files, ...

### Vellox

<a href="https://github.com/junah201/vellox" target="_blank">GitHub</a>

Serverless ASGI adapter for GCP Cloud Functions.

## Starlette Bridge

<a href="https://github.com/tarsil/starlette-bridge" target="_blank">GitHub</a> |
Expand Down Expand Up @@ -247,3 +260,10 @@ Allows mounting [package resources](https://docs.python.org/3/library/importlib.
<a href="https://docs.sentry.io/platforms/python/guides/starlette/" target="_blank">Documentation</a>

Sentry is a software error detection tool. It offers actionable insights for resolving performance issues and errors, allowing users to diagnose, fix, and optimize Python debugging. Additionally, it integrates seamlessly with Starlette for Python application development. Sentry's capabilities include error tracking, performance insights, contextual information, and alerts/notifications.

### Shiny

<a href="https://github.com/posit-dev/py-shiny" target="_blank">GitHub</a> |
<a href="https://shiny.posit.co/py/" target="_blank">Documentation</a>

Leveraging Starlette and asyncio, Shiny allows developers to create effortless Python web applications using the power of reactive programming. Shiny eliminates the hassle of manual state management, automatically determining the best execution path for your app at runtime while simultaneously minimizing re-rendering. This means that Shiny can support everything from the simplest dashboard to full-featured web apps.
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ path = "starlette/__init__.py"

[tool.ruff.lint]
select = ["E", "F", "I", "FA", "UP"]
ignore = ["UP031"]

[tool.ruff.lint.isort]
combine-as-imports = true
Expand Down Expand Up @@ -78,6 +79,8 @@ filterwarnings = [
"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",
# TODO: This warning appeared when we bumped anyio to 4.4.0.
"ignore: Unclosed .MemoryObject(Send|Receive)Stream.:ResourceWarning",
]

[tool.coverage.run]
Expand Down
29 changes: 14 additions & 15 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
# Optionals
-e .[full]

# TODO: We need to delete the following line when fixing the test suite for anyio 4.4.0.
anyio==4.3.0

# Testing
coverage==7.4.3
importlib-metadata==7.0.1
mypy==1.8.0
ruff==0.1.15
typing_extensions==4.10.0
coverage==7.6.0
importlib-metadata==8.2.0
mypy==1.11.1
ruff==0.5.5
typing_extensions==4.12.2
types-contextvars==2.4.7.3
types-PyYAML==6.0.12.12
types-PyYAML==6.0.12.20240724
types-dataclasses==0.6.6
pytest==8.0.2
trio==0.24.0
pytest==8.3.2
trio==0.26.1
# TODO: Remove when trio is updated.
attrs==23.2.0

# Documentation
mkdocs==1.5.3
mkdocs-material==9.5.12
mkdocs==1.6.0
mkdocs-material==9.5.30
mkautodoc==0.2.0

# Packaging
build==1.1.1
twine==5.0.0
build==1.2.1
twine==5.1.1
2 changes: 1 addition & 1 deletion scripts/lint
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export SOURCE_FILES="starlette tests"
set -x

${PREFIX}ruff format $SOURCE_FILES
${PREFIX}ruff --fix $SOURCE_FILES
${PREFIX}ruff check --fix $SOURCE_FILES
2 changes: 1 addition & 1 deletion starlette/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.37.2"
__version__ = "0.38.2"
12 changes: 4 additions & 8 deletions starlette/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,11 @@


@typing.overload
def is_async_callable(obj: AwaitableCallable[T]) -> TypeGuard[AwaitableCallable[T]]:
...
def is_async_callable(obj: AwaitableCallable[T]) -> TypeGuard[AwaitableCallable[T]]: ...


@typing.overload
def is_async_callable(obj: typing.Any) -> TypeGuard[AwaitableCallable[typing.Any]]:
...
def is_async_callable(obj: typing.Any) -> TypeGuard[AwaitableCallable[typing.Any]]: ...


def is_async_callable(obj: typing.Any) -> typing.Any:
Expand All @@ -49,13 +47,11 @@ def is_async_callable(obj: typing.Any) -> typing.Any:

class AwaitableOrContextManager(
typing.Awaitable[T_co], typing.AsyncContextManager[T_co], typing.Protocol[T_co]
):
...
): ...


class SupportsAsyncClose(typing.Protocol):
async def close(self) -> None:
... # pragma: no cover
async def close(self) -> None: ... # pragma: no cover


SupportsAsyncCloseType = typing.TypeVar(
Expand Down
17 changes: 7 additions & 10 deletions starlette/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,29 +68,26 @@ def __init__(
self.file_values = self._read_file(env_file)

@typing.overload
def __call__(self, key: str, *, default: None) -> str | None:
...
def __call__(self, key: str, *, default: None) -> str | None: ...

@typing.overload
def __call__(self, key: str, cast: type[T], default: T = ...) -> T:
...
def __call__(self, key: str, cast: type[T], default: T = ...) -> T: ...

@typing.overload
def __call__(self, key: str, cast: type[str] = ..., default: str = ...) -> str:
...
def __call__(self, key: str, cast: type[str] = ..., default: str = ...) -> str: ...

@typing.overload
def __call__(
self,
key: str,
cast: typing.Callable[[typing.Any], T] = ...,
default: typing.Any = ...,
) -> T:
...
) -> T: ...

@typing.overload
def __call__(self, key: str, cast: type[str] = ..., default: T = ...) -> T | str:
...
def __call__(
self, key: str, cast: type[str] = ..., default: T = ...
) -> T | str: ...

def __call__(
self,
Expand Down
10 changes: 6 additions & 4 deletions starlette/middleware/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@


class _MiddlewareClass(Protocol[P]):
def __init__(self, app: ASGIApp, *args: P.args, **kwargs: P.kwargs) -> None:
... # pragma: no cover
def __init__(
self, app: ASGIApp, *args: P.args, **kwargs: P.kwargs
) -> None: ... # pragma: no cover

async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
... # pragma: no cover
async def __call__(
self, scope: Scope, receive: Receive, send: Send
) -> None: ... # pragma: no cover


class Middleware:
Expand Down
12 changes: 4 additions & 8 deletions starlette/responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def set_cookie(
value: str = "",
max_age: int | None = None,
expires: datetime | str | int | None = None,
path: str = "/",
path: str | None = "/",
domain: str | None = None,
secure: bool = False,
httponly: bool = False,
Expand Down Expand Up @@ -299,12 +299,10 @@ def __init__(
if self.filename is not None:
content_disposition_filename = quote(self.filename)
if content_disposition_filename != self.filename:
content_disposition = "{}; filename*=utf-8''{}".format(
content_disposition_type, content_disposition_filename
)
content_disposition = f"{content_disposition_type}; filename*=utf-8''{content_disposition_filename}" # noqa: E501
else:
content_disposition = '{}; filename="{}"'.format(
content_disposition_type, self.filename
content_disposition = (
f'{content_disposition_type}; filename="{self.filename}"'
)
self.headers.setdefault("content-disposition", content_disposition)
self.stat_result = stat_result
Expand Down Expand Up @@ -341,8 +339,6 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
)
if scope["method"].upper() == "HEAD":
await send({"type": "http.response.body", "body": b"", "more_body": False})
elif "extensions" in scope and "http.response.pathsend" in scope["extensions"]:
await send({"type": "http.response.pathsend", "path": str(self.path)})
else:
async with await anyio.open_file(self.path, mode="rb") as file:
more_body = True
Expand Down
4 changes: 1 addition & 3 deletions starlette/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@ async def app(scope: Scope, receive: Receive, send: Send) -> None:


def get_name(endpoint: typing.Callable[..., typing.Any]) -> str:
if inspect.isroutine(endpoint) or inspect.isclass(endpoint):
return endpoint.__name__
return endpoint.__class__.__name__
return getattr(endpoint, "__name__", endpoint.__class__.__name__)


def replace_params(
Expand Down
1 change: 1 addition & 0 deletions starlette/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
And RFC 2324 - https://tools.ietf.org/html/rfc2324
"""

from __future__ import annotations

import warnings
Expand Down
9 changes: 3 additions & 6 deletions starlette/templating.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ def __init__(
context_processors: list[typing.Callable[[Request], dict[str, typing.Any]]]
| None = None,
**env_options: typing.Any,
) -> None:
...
) -> None: ...

@typing.overload
def __init__(
Expand All @@ -81,8 +80,7 @@ def __init__(
env: jinja2.Environment,
context_processors: list[typing.Callable[[Request], dict[str, typing.Any]]]
| None = None,
) -> None:
...
) -> None: ...

def __init__(
self,
Expand Down Expand Up @@ -150,8 +148,7 @@ def TemplateResponse(
headers: typing.Mapping[str, str] | None = None,
media_type: str | None = None,
background: BackgroundTask | None = None,
) -> _TemplateResponse:
...
) -> _TemplateResponse: ...

@typing.overload
def TemplateResponse(
Expand Down
5 changes: 4 additions & 1 deletion starlette/testclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,8 @@ async def run_app(tg: anyio.abc.TaskGroup) -> None:

async def _asgi_receive(self) -> Message:
while self._receive_queue.empty():
await anyio.sleep(0)
self._queue_event = anyio.Event()
await self._queue_event.wait()
return self._receive_queue.get()

async def _asgi_send(self, message: Message) -> None:
Expand Down Expand Up @@ -189,6 +190,8 @@ def _raise_on_close(self, message: Message) -> None:

def send(self, message: Message) -> None:
self._receive_queue.put(message)
if hasattr(self, "_queue_event"):
self.portal.start_task_soon(self._queue_event.set)

def send_text(self, data: str) -> None:
self.send({"type": "websocket.receive", "text": data})
Expand Down
5 changes: 2 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
from __future__ import annotations

import functools
from typing import Any, Callable, Literal
from typing import Any, Literal

import pytest

from starlette.testclient import TestClient

TestClientFactory = Callable[..., TestClient]
from tests.types import TestClientFactory


@pytest.fixture
Expand Down
4 changes: 1 addition & 3 deletions tests/middleware/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
from typing import (
Any,
AsyncGenerator,
Callable,
Generator,
)

Expand All @@ -23,8 +22,7 @@
from starlette.testclient import TestClient
from starlette.types import ASGIApp, Message, Receive, Scope, Send
from starlette.websockets import WebSocket

TestClientFactory = Callable[[ASGIApp], TestClient]
from tests.types import TestClientFactory


class CustomMiddleware(BaseHTTPMiddleware):
Expand Down
7 changes: 1 addition & 6 deletions tests/middleware/test_cors.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
from typing import Callable

from starlette.applications import Starlette
from starlette.middleware import Middleware
from starlette.middleware.cors import CORSMiddleware
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.routing import Route
from starlette.testclient import TestClient
from starlette.types import ASGIApp

TestClientFactory = Callable[[ASGIApp], TestClient]
from tests.types import TestClientFactory


def test_cors_allow_all(
Expand Down
Loading

0 comments on commit 5080577

Please sign in to comment.