Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into kristjan/reject
Browse files Browse the repository at this point in the history
  • Loading branch information
kristjanvalur committed Jun 17, 2023
2 parents bb27af4 + 8d7a1ca commit 2882d32
Show file tree
Hide file tree
Showing 32 changed files with 1,093 additions and 173 deletions.
13 changes: 9 additions & 4 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
The starting point for contributions should usually be [a discussion](https://github.com/encode/starlette/discussions)
<!-- Thanks for contributing to Starlette! 💚
Given this is a project maintained by volunteers, please read this template to not waste your time, or ours! 😁 -->

Simple documentation typos may be raised as stand-alone pull requests, but otherwise please ensure you've discussed your proposal prior to issuing a pull request.
# Summary

This will help us direct work appropriately, and ensure that any suggested changes have been okayed by the maintainers.
<!-- Write a small summary about what is happening here. -->

- [ ] Initially raised as discussion #...
# Checklist

- [ ] I understand that this PR may be closed in case there was no previous discussion. (This doesn't apply to typos!)
- [ ] I've added a test for each change that was introduced, and I tried as much as possible to make a single atomic change.
- [ ] I've updated the documentation accordingly.
5 changes: 3 additions & 2 deletions docs/lifespan.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ from typing import TypedDict

import httpx
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import PlainTextResponse
from starlette.routing import Route

Expand All @@ -52,12 +53,12 @@ class State(TypedDict):


@contextlib.asynccontextmanager
async def lifespan(app: Starlette) -> State:
async def lifespan(app: Starlette) -> typing.AsyncIterator[State]:
async with httpx.AsyncClient() as client:
yield {"http_client": client}


async def homepage(request):
async def homepage(request: Request) -> PlainTextResponse:
client = request.state.http_client
response = await client.get("https://www.example.com")
return PlainTextResponse(response.text)
Expand Down
5 changes: 5 additions & 0 deletions docs/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,11 @@ A middleware class for logging exceptions, errors, and log messages to [Rollbar]
A middleware class that emits tracing info to [OpenTracing.io](https://opentracing.io/) compatible tracers and
can be used to profile and monitor distributed applications.

#### [SecureCookiesMiddleware](https://github.com/thearchitector/starlette-securecookies)

Customizable middleware for adding automatic cookie encryption and decryption to Starlette applications, with
extra support for existing cookie-based middleware.

#### [TimingMiddleware](https://github.com/steinnes/timing-asgi)

A middleware class to emit timing information (cpu and wall time) for each request which
Expand Down
30 changes: 30 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@
## 0.28.0

June 7, 2023

### Changed
* Reuse `Request`'s body buffer for call_next in `BaseHTTPMiddleware` [#1692](https://github.com/encode/starlette/pull/1692).
* Move exception handling logic to `Route` [#2026](https://github.com/encode/starlette/pull/2026).

### Added
* Add `env` parameter to `Jinja2Templates`, and deprecate `**env_options` [#2159](https://github.com/encode/starlette/pull/2159).
* Add clear error message when `httpx` is not installed [#2177](https://github.com/encode/starlette/pull/2177).

### Fixed
* Allow "name" argument on `templates url_for()` [#2127](https://github.com/encode/starlette/pull/2127).

## 0.27.0

May 16, 2023

This release fixes a path traversal vulnerability in `StaticFiles`. You can view the full security advisory:
https://github.com/encode/starlette/security/advisories/GHSA-v5gw-mw7f-84px

### Added
* Minify JSON websocket data via `send_json` https://github.com/encode/starlette/pull/2128

### Fixed
* Replace `commonprefix` by `commonpath` on `StaticFiles` [1797de4](https://github.com/encode/starlette/commit/1797de464124b090f10cf570441e8292936d63e3).
* Convert ImportErrors into ModuleNotFoundError [#2135](https://github.com/encode/starlette/pull/2135).
* Correct the RuntimeError message content in websockets [#2141](https://github.com/encode/starlette/pull/2141).

## 0.26.1

March 13, 2023
Expand Down
24 changes: 24 additions & 0 deletions docs/templates.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
Starlette is not _strictly_ coupled to any particular templating engine, but
Jinja2 provides an excellent choice.

### Jinja2Templates

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

* `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.

Starlette provides a simple way to get `jinja2` configured. This is probably
what you want to use by default.

Expand Down Expand Up @@ -50,6 +58,21 @@ templates = Jinja2Templates(directory='templates')
templates.env.filters['marked'] = marked_filter
```


## Using custom jinja2.Environment instance

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


```python
import jinja2
from starlette.templating import Jinja2Templates

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 @@ -128,3 +151,4 @@ for example, strictly evaluate any database queries within the view and
include the final results in the context.

[jinja2]: https://jinja.palletsprojects.com/en/3.0.x/api/?highlight=environment#writing-filters
[pathlike]: https://docs.python.org/3/library/os.html#os.PathLike
50 changes: 45 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,53 @@ Source = "https://github.com/encode/starlette"
[tool.hatch.version]
path = "starlette/__init__.py"

[tool.hatch.build.targets.sdist]
include = [
"/starlette",
]

[tool.ruff]
select = ["E", "F", "I"]

[tool.ruff.isort]
combine-as-imports = true

[tool.mypy]
disallow_untyped_defs = true
ignore_missing_imports = true
no_implicit_optional = true
show_error_codes = true

[[tool.mypy.overrides]]
module = "starlette.testclient.*"
no_implicit_optional = false

[[tool.mypy.overrides]]
module = "tests.*"
disallow_untyped_defs = false
check_untyped_defs = true

[tool.pytest.ini_options]
addopts = "-rxXs --strict-config --strict-markers"
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",
]

[tool.coverage.run]
source_pkgs = [
"starlette",
"tests",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"pragma: nocover",
"if typing.TYPE_CHECKING:",
"@typing.overload",
]
16 changes: 8 additions & 8 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@

# Testing
black==23.3.0
coverage==7.1.0
importlib-metadata==4.13.0
mypy==1.0.1
ruff==0.0.260
coverage==7.2.5
importlib-metadata==6.6.0
mypy==1.3.0
ruff==0.0.263
typing_extensions==4.5.0
types-contextvars==2.4.7.2
types-PyYAML==6.0.12.9
types-PyYAML==6.0.12.10
types-dataclasses==0.6.6
pytest==7.2.2
pytest==7.3.1
trio==0.21.0

# Documentation
mkdocs==1.4.2
mkdocs-material==9.0.15
mkdocs==1.4.3
mkdocs-material==9.1.15
mkautodoc==0.2.0

# Packaging
Expand Down
2 changes: 1 addition & 1 deletion scripts/check
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ set -x
./scripts/sync-version
${PREFIX}black --check --diff $SOURCE_FILES
${PREFIX}mypy $SOURCE_FILES
${PREFIX}ruff --diff $SOURCE_FILES
${PREFIX}ruff check $SOURCE_FILES
2 changes: 1 addition & 1 deletion scripts/lint
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ export SOURCE_FILES="starlette tests"

set -x

${PREFIX}ruff --fix $SOURCE_FILES
${PREFIX}black $SOURCE_FILES
${PREFIX}ruff --fix $SOURCE_FILES
41 changes: 0 additions & 41 deletions setup.cfg

This file was deleted.

2 changes: 1 addition & 1 deletion starlette/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "0.26.1"
__version__ = "0.28.0"
76 changes: 76 additions & 0 deletions starlette/_exception_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import typing

from starlette._utils import is_async_callable
from starlette.concurrency import run_in_threadpool
from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import ASGIApp, Message, Receive, Scope, Send
from starlette.websockets import WebSocket

Handler = typing.Callable[..., typing.Any]
ExceptionHandlers = typing.Dict[typing.Any, Handler]
StatusHandlers = typing.Dict[int, Handler]


def _lookup_exception_handler(
exc_handlers: ExceptionHandlers, exc: Exception
) -> typing.Optional[Handler]:
for cls in type(exc).__mro__:
if cls in exc_handlers:
return exc_handlers[cls]
return None


def wrap_app_handling_exceptions(
app: ASGIApp, conn: typing.Union[Request, WebSocket]
) -> ASGIApp:
exception_handlers: ExceptionHandlers
status_handlers: StatusHandlers
try:
exception_handlers, status_handlers = conn.scope["starlette.exception_handlers"]
except KeyError:
exception_handlers, status_handlers = {}, {}

async def wrapped_app(scope: Scope, receive: Receive, send: Send) -> None:
response_started = False

async def sender(message: Message) -> None:
nonlocal response_started

if message["type"] == "http.response.start":
response_started = True
await send(message)

try:
await app(scope, receive, sender)
except Exception as exc:
handler = None

if isinstance(exc, HTTPException):
handler = status_handlers.get(exc.status_code)

if handler is None:
handler = _lookup_exception_handler(exception_handlers, exc)

if handler is None:
raise exc

if response_started:
msg = "Caught handled exception, but response already started."
raise RuntimeError(msg) from exc

if scope["type"] == "http":
response: Response
if is_async_callable(handler):
response = await handler(conn, exc)
else:
response = await run_in_threadpool(handler, conn, exc)
await response(scope, receive, sender)
elif scope["type"] == "websocket":
if is_async_callable(handler):
await handler(conn, exc)
else:
await run_in_threadpool(handler, conn, exc)

return wrapped_app
2 changes: 1 addition & 1 deletion starlette/applications.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class Starlette:
* **exception_handlers** - A mapping of either integer status codes,
or exception class types onto callables which handle the exceptions.
Exception handler callables should be of the form
`handler(request, exc) -> response` and may be be either standard functions, or
`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 be either
Expand Down
2 changes: 1 addition & 1 deletion starlette/formparsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
try:
import multipart
from multipart.multipart import parse_options_header
except ImportError: # pragma: nocover
except ModuleNotFoundError: # pragma: nocover
parse_options_header = None
multipart = None

Expand Down

0 comments on commit 2882d32

Please sign in to comment.