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 2021-12-12 21:27:45
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-starlette (Old)
and /work/SRC/openSUSE:Factory/.python-starlette.new.2520 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-starlette"
Sun Dec 12 21:27:45 2021 rev:4 rq:940017 version:0.17.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-starlette/python-starlette.changes
2021-09-27 20:09:26.782497669 +0200
+++
/work/SRC/openSUSE:Factory/.python-starlette.new.2520/python-starlette.changes
2021-12-12 21:28:30.176373296 +0100
@@ -1,0 +2,25 @@
+Wed Dec 8 15:53:03 UTC 2021 - Torsten Gruner <[email protected]>
+
+- update to version 0.17.1
+ * Fix IndexError in authentication requires when wrapped function
+ arguments are distributed between *args and **kwargs #1335.
+- version 17.0
+ * Added
+ - Response.delete_cookie now accepts the same parameters as
+ Response.set_cookie #1228.
+ - Update the Jinja2Templates constructor to allow PathLike #1292.
+ * Fixed
+ - Fix BadSignature exception handling in SessionMiddleware #1264.
+ - Change HTTPConnection.__getitem__ return type from str to
+ typing.Any #1118.
+ - Change ImmutableMultiDict.getlist return type from typing.List[str]
+ to typing.List[typing.Any] #1235.
+ - Handle OSError exceptions on StaticFiles #1220.
+ - Fix StaticFiles 404.html in HTML mode #1314.
+ - Prevent anyio.ExceptionGroup in error views under a
+ BaseHTTPMiddleware #1262.
+ *Removed
+ - Remove GraphQL support #1198.
+- Remove py39-ignore-loop-deprecation.patch
+
+-------------------------------------------------------------------
Old:
----
py39-ignore-loop-deprecation.patch
starlette-0.16.0.tar.gz
New:
----
starlette-0.17.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-starlette.spec ++++++
--- /var/tmp/diff_new_pack.8o1Les/_old 2021-12-12 21:28:30.600373547 +0100
+++ /var/tmp/diff_new_pack.8o1Les/_new 2021-12-12 21:28:30.600373547 +0100
@@ -19,16 +19,13 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-starlette
-Version: 0.16.0
+Version: 0.17.1
Release: 0
Summary: Lightweight ASGI framework/toolkit
License: BSD-3-Clause
Group: Development/Languages/Python
URL: https://github.com/encode/starlette
Source:
https://github.com/encode/starlette/archive/refs/tags/%{version}.tar.gz#/starlette-%{version}.tar.gz
-# PATCH-FIX-UPSTREAM py39-ignore-loop-deprecation.patch
gh#encode/starlette#1293 [email protected]
-# Ignore loop deprecation warnings originating inside asyncio; bpo#45097
-Patch0: py39-ignore-loop-deprecation.patch
BuildRequires: %{python_module Jinja2}
BuildRequires: %{python_module PyYAML}
BuildRequires: %{python_module aiofiles}
@@ -58,7 +55,7 @@
building high performance asyncio services.
%prep
-%autosetup -p1 -n starlette-%{version}
+%autosetup -n starlette-%{version}
%build
%python_build
@@ -69,7 +66,7 @@
%check
# Deprecate built-in GraphQL support gh#encode/starlette#1135
-rm tests/test_graphql.py
+# rm tests/test_graphql.py
# Remove unrecognized arguments: --strict-config --strict-markers
sed -i "s|--strict-config||" setup.cfg
sed -i "s|--strict-markers||" setup.cfg
++++++ starlette-0.16.0.tar.gz -> starlette-0.17.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/.github/workflows/test-suite.yml
new/starlette-0.17.1/.github/workflows/test-suite.yml
--- old/starlette-0.16.0/.github/workflows/test-suite.yml 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/.github/workflows/test-suite.yml 2021-11-17
17:10:27.000000000 +0100
@@ -14,7 +14,7 @@
strategy:
matrix:
- python-version: ["3.6", "3.7", "3.8", "3.9", "3.10.0-beta.3"]
+ python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
steps:
- uses: "actions/checkout@v2"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/README.md
new/starlette-0.17.1/README.md
--- old/starlette-0.16.0/README.md 2021-07-19 09:08:36.000000000 +0200
+++ new/starlette-0.17.1/README.md 2021-11-17 17:10:27.000000000 +0100
@@ -28,7 +28,6 @@
* Seriously impressive performance.
* WebSocket support.
-* GraphQL support.
* In-process background tasks.
* Startup and shutdown events.
* Test client built on `requests`.
@@ -92,7 +91,6 @@
* [`python-multipart`][python-multipart] - Required if you want to support
form parsing, with `request.form()`.
* [`itsdangerous`][itsdangerous] - Required for `SessionMiddleware` support.
* [`pyyaml`][pyyaml] - Required for `SchemaGenerator` support.
-* [`graphene`][graphene] - Required for `GraphQLApp` support.
You can install all of these with `pip3 install starlette[full]`.
@@ -169,7 +167,6 @@
[requests]: http://docs.python-requests.org/en/master/
[jinja2]: http://jinja.pocoo.org/
[python-multipart]: https://andrew-d.github.io/python-multipart/
-[graphene]: https://graphene-python.org/
[itsdangerous]: https://pythonhosted.org/itsdangerous/
[sqlalchemy]: https://www.sqlalchemy.org
[pyyaml]: https://pyyaml.org/wiki/PyYAMLDocumentation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/config.md
new/starlette-0.17.1/docs/config.md
--- old/starlette-0.16.0/docs/config.md 2021-07-19 09:08:36.000000000 +0200
+++ new/starlette-0.17.1/docs/config.md 2021-11-17 17:10:27.000000000 +0100
@@ -160,7 +160,7 @@
```python
from starlette.applications import Starlette
from starlette.middleware import Middleware
-from starlette.middleware.session import SessionMiddleware
+from starlette.middleware.sessions import SessionMiddleware
from starlette.routing import Route
from myproject import settings
@@ -192,7 +192,7 @@
from starlette.config import environ
from starlette.testclient import TestClient
from sqlalchemy import create_engine
-from sqlalchemy_utils import database_exists, create_database
+from sqlalchemy_utils import create_database, database_exists, drop_database
# This line would raise an error if we use it after 'settings' has been
imported.
environ['TESTING'] = 'TRUE'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/database.md
new/starlette-0.17.1/docs/database.md
--- old/starlette-0.16.0/docs/database.md 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/docs/database.md 2021-11-17 17:10:27.000000000
+0100
@@ -142,10 +142,10 @@
await database.execute(query)
raise RuntimeError()
except:
- transaction.rollback()
+ await transaction.rollback()
raise
else:
- transaction.commit()
+ await transaction.commit()
```
## Test isolation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/graphql.md
new/starlette-0.17.1/docs/graphql.md
--- old/starlette-0.16.0/docs/graphql.md 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/docs/graphql.md 2021-11-17 17:10:27.000000000
+0100
@@ -1,116 +1,9 @@
+GraphQL support in Starlette was deprecated in version 0.15.0, and removed in
version 0.17.0.
-!!! Warning
+Although GraphQL support is no longer built in to Starlette, you can still use
GraphQL with Starlette via 3rd party libraries. These libraries all have
Starlette-specific guides to help you do just that:
- GraphQL support in Starlette is **deprecated** as of version 0.15 and will
- be removed in a future release. It is also incompatible with Python 3.10+.
- Please consider using a third-party library to provide GraphQL support.
This
- is usually done by mounting a GraphQL ASGI application.
- See [#619](https://github.com/encode/starlette/issues/619).
- Some example libraries are:
+- [Ariadne](https://ariadnegraphql.org/docs/starlette-integration.html)
+-
[`starlette-graphene3`](https://github.com/ciscorn/starlette-graphene3#example)
+- [Strawberry](https://strawberry.rocks/docs/integrations/starlette)
+-
[`tartiflette-asgi`](https://tartiflette.github.io/tartiflette-asgi/usage/#starlette)
- * [Ariadne](https://ariadnegraphql.org/docs/asgi)
- * [`tartiflette-asgi`](https://tartiflette.github.io/tartiflette-asgi/)
- * [Strawberry](https://strawberry.rocks/docs/integrations/asgi)
- * [`starlette-graphene3`](https://github.com/ciscorn/starlette-graphene3)
-
-Starlette includes optional support for GraphQL, using the `graphene` library.
-
-Here's an example of integrating the support into your application.
-
-```python
-from starlette.applications import Starlette
-from starlette.routing import Route
-from starlette.graphql import GraphQLApp
-import graphene
-
-
-class Query(graphene.ObjectType):
- hello = graphene.String(name=graphene.String(default_value="stranger"))
-
- def resolve_hello(self, info, name):
- return "Hello " + name
-
-routes = [
- Route('/', GraphQLApp(schema=graphene.Schema(query=Query)))
-]
-
-app = Starlette(routes=routes)
-```
-
-If you load up the page in a browser, you'll be served the GraphiQL tool,
-which you can use to interact with your GraphQL API.
-
-
-
-
-## Accessing request information
-
-The current request is available in the context.
-
-```python
-class Query(graphene.ObjectType):
- user_agent = graphene.String()
-
- def resolve_user_agent(self, info):
- """
- Return the User-Agent of the incoming request.
- """
- request = info.context["request"]
- return request.headers.get("User-Agent", "<unknown>")
-```
-
-## Adding background tasks
-
-You can add background tasks to run once the response has been sent.
-
-```python
-class Query(graphene.ObjectType):
- user_agent = graphene.String()
-
- def resolve_user_agent(self, info):
- """
- Return the User-Agent of the incoming request.
- """
- user_agent = request.headers.get("User-Agent", "<unknown>")
- background = info.context["background"]
- background.add_task(log_user_agent, user_agent=user_agent)
- return user_agent
-
-async def log_user_agent(user_agent):
- ...
-```
-
-## Sync or Async executors
-
-If you're working with a standard ORM, then just use regular function calls for
-your "resolve" methods, and Starlette will manage running the GraphQL query
within a
-separate thread.
-
-If you want to use an asynchronous ORM, then use "async resolve" methods, and
-make sure to setup Graphene's AsyncioExecutor using the `executor` argument.
-
-```python
-from graphql.execution.executors.asyncio import AsyncioExecutor
-from starlette.applications import Starlette
-from starlette.graphql import GraphQLApp
-from starlette.routing import Route
-import graphene
-
-
-class Query(graphene.ObjectType):
- hello = graphene.String(name=graphene.String(default_value="stranger"))
-
- async def resolve_hello(self, info, name):
- # We can make asynchronous network calls here.
- return "Hello " + name
-
-routes = [
- # We're using `executor_class=AsyncioExecutor` here.
- Route('/', GraphQLApp(
- schema=graphene.Schema(query=Query),
- executor_class=AsyncioExecutor
- ))
-]
-
-app = Starlette(routes=routes)
-```
Binary files old/starlette-0.16.0/docs/img/graphiql.png and
new/starlette-0.17.1/docs/img/graphiql.png differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/index.md
new/starlette-0.17.1/docs/index.md
--- old/starlette-0.16.0/docs/index.md 2021-07-19 09:08:36.000000000 +0200
+++ new/starlette-0.17.1/docs/index.md 2021-11-17 17:10:27.000000000 +0100
@@ -162,7 +162,6 @@
[requests]: http://docs.python-requests.org/en/master/
[jinja2]: http://jinja.pocoo.org/
[python-multipart]: https://andrew-d.github.io/python-multipart/
-[graphene]: https://graphene-python.org/
[itsdangerous]: https://pythonhosted.org/itsdangerous/
[sqlalchemy]: https://www.sqlalchemy.org
[pyyaml]: https://pyyaml.org/wiki/PyYAMLDocumentation
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/middleware.md
new/starlette-0.17.1/docs/middleware.md
--- old/starlette-0.16.0/docs/middleware.md 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/docs/middleware.md 2021-11-17 17:10:27.000000000
+0100
@@ -183,6 +183,8 @@
`async def dispatch(request, call_next)` method.
```python
+from starlette.middleware.base import BaseHTTPMiddleware
+
class CustomHeaderMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
@@ -256,7 +258,7 @@
A middleware class to emit timing information (cpu and wall time) for each
request which
passes through it. Includes examples for how to emit these timings as statsd
metrics.
-#### [datasette-auth-github](https://github.com/simonw/datasette-auth-github)
+#### [asgi-auth-github](https://github.com/simonw/asgi-auth-github)
This middleware adds authentication to any ASGI application, requiring users
to sign in
using their GitHub account (via
[OAuth](https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/)).
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/overrides/partials/nav.html
new/starlette-0.17.1/docs/overrides/partials/nav.html
--- old/starlette-0.16.0/docs/overrides/partials/nav.html 1970-01-01
01:00:00.000000000 +0100
+++ new/starlette-0.17.1/docs/overrides/partials/nav.html 2021-11-17
17:10:27.000000000 +0100
@@ -0,0 +1,52 @@
+<!-- Determine class according to configuration -->
+ {% set class = "md-nav md-nav--primary" %}
+ {% if "navigation.tabs" in features %}
+ {% set class = class ~ " md-nav--lifted" %}
+ {% endif %}
+ {% if "toc.integrate" in features %}
+ {% set class = class ~ " md-nav--integrated" %}
+ {% endif %}
+
+ <!-- Main navigation -->
+ <nav
+ class="{{ class }}"
+ aria-label="{{ lang.t('nav.title') }}"
+ data-md-level="0"
+ >
+
+ <!-- Site title -->
+ <label class="md-nav__title" for="__drawer">
+ <a
+ href="{{ config.extra.homepage | d(nav.homepage.url, true) | url }}"
+ title="{{ config.site_name | e }}"
+ class="md-nav__button md-logo"
+ aria-label="{{ config.site_name }}"
+ data-md-component="logo"
+ >
+ {% include "partials/logo.html" %}
+ </a>
+ {{ config.site_name }}
+ </label>
+
+ <!-- Repository information -->
+ {% if config.repo_url %}
+ <div class="md-nav__source">
+ {% include "partials/source.html" %}
+ </div>
+ {% endif %}
+
+ <!-- Render item list -->
+ <ul class="md-nav__list" data-md-scrollfix>
+ {% for nav_item in nav %}
+ {% set path = "__nav_" ~ loop.index %}
+ {% set level = 1 %}
+ {% include "partials/nav-item.html" %}
+ {% endfor %}
+ </ul>
+
+ <ul class="md-nav__list" data-md-scrollfix style="padding-top: 15px;
padding-left: 10px">
+ <div>
+ <a href="https://fastapi.tiangolo.com"><img src="/sponsors/fastapi.png"
width=150px style=></img></a>
+ </div>
+ </ul>
+ </nav>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/release-notes.md
new/starlette-0.17.1/docs/release-notes.md
--- old/starlette-0.16.0/docs/release-notes.md 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/docs/release-notes.md 2021-11-17 17:10:27.000000000
+0100
@@ -1,3 +1,29 @@
+## 0.17.1
+
+November 17, 2021
+
+### Fixed
+* Fix `IndexError` in authentication `requires` when wrapped function
arguments are distributed between `*args` and `**kwargs`
[#1335](https://github.com/encode/starlette/pull/1335).
+
+## 0.17.0
+
+November 4, 2021
+
+### Added
+* `Response.delete_cookie` now accepts the same parameters as
`Response.set_cookie` [#1228](https://github.com/encode/starlette/pull/1228).
+* Update the `Jinja2Templates` constructor to allow `PathLike`
[#1292](https://github.com/encode/starlette/pull/1292).
+
+### Fixed
+* Fix BadSignature exception handling in SessionMiddleware
[#1264](https://github.com/encode/starlette/pull/1264).
+* Change `HTTPConnection.__getitem__` return type from `str` to `typing.Any`
[#1118](https://github.com/encode/starlette/pull/1118).
+* Change `ImmutableMultiDict.getlist` return type from `typing.List[str]` to
`typing.List[typing.Any]`
[#1235](https://github.com/encode/starlette/pull/1235).
+* Handle `OSError` exceptions on `StaticFiles`
[#1220](https://github.com/encode/starlette/pull/1220).
+* Fix `StaticFiles` 404.html in HTML mode
[#1314](https://github.com/encode/starlette/pull/1314).
+* Prevent anyio.ExceptionGroup in error views under a BaseHTTPMiddleware
[#1262](https://github.com/encode/starlette/pull/1262).
+
+### Removed
+* Remove GraphQL support
[#1198](https://github.com/encode/starlette/pull/1198).
+
## 0.16.0
July 19, 2021
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/responses.md
new/starlette-0.17.1/docs/responses.md
--- old/starlette-0.16.0/docs/responses.md 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/docs/responses.md 2021-11-17 17:10:27.000000000
+0100
@@ -182,9 +182,8 @@
await response(scope, receive, send)
```
-## Third party middleware
+## Third party responses
-### [SSEResponse(EventSourceResponse)](https://github.com/sysid/sse-starlette)
+#### [EventSourceResponse](https://github.com/sysid/sse-starlette)
-Server Sent Response implements the ServerSentEvent Protocol:
https://www.w3.org/TR/2009/WD-eventsource-20090421.
-It enables event streaming from the server to the client without the
complexity of websockets.
+A response class that implements [Server-Sent
Events](https://html.spec.whatwg.org/multipage/server-sent-events.html). It
enables event streaming from the server to the client without the complexity of
websockets.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/routing.md
new/starlette-0.17.1/docs/routing.md
--- old/starlette-0.16.0/docs/routing.md 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/docs/routing.md 2021-11-17 17:10:27.000000000
+0100
@@ -178,6 +178,50 @@
url = app.url_path_for("user_detail", username=...)
```
+## Host-based routing
+
+If you want to use different routes for the same path based on the `Host`
header.
+
+Note that port is removed from the `Host` header when matching.
+For example, `Host (host='example.org:3600', ...)` will not be processed
+even if the `Host` header is `example.org:3600`.
+Therefore, specify only the domain or IP address
+
+There are several ways to connect host-based routes to your application
+
+```python
+site = Router() # Use eg. `@site.route()` to configure this.
+api = Router() # Use eg. `@api.route()` to configure this.
+news = Router() # Use eg. `@news.route()` to configure this.
+
+routes = [
+ Host('api.example.org', api, name="site_api")
+]
+
+app = Starlette(routes=routes)
+
+app.host('www.example.org', site, name="main_site")
+
+news_host = Host('news.example.org', news)
+app.router.routes.append(news_host)
+```
+
+URL lookups can include host parameters just like path parameters
+
+```python
+routes = [
+ Host("{subdomain}.example.org", name="sub", app=Router(routes=[
+ Mount("/users", name="users", routes=[
+ Route("/", user, name="user_list"),
+ Route("/{username}", user, name="user_detail")
+ ])
+ ]))
+]
+...
+url = request.url_for("sub:users:user_detail", username=..., subdomain=...)
+url = request.url_for("sub:users:user_list", subdomain=...)
+```
+
## Route priority
Incoming paths are matched against each `Route` in order.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/schemas.md
new/starlette-0.17.1/docs/schemas.md
--- old/starlette-0.16.0/docs/schemas.md 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/docs/schemas.md 2021-11-17 17:10:27.000000000
+0100
@@ -51,7 +51,7 @@
Route("/schema", endpoint=openapi_schema, include_in_schema=False)
]
-app = Starlette()
+app = Starlette(routes=routes)
```
We can now access an OpenAPI schema at the "/schema" endpoint.
Binary files old/starlette-0.16.0/docs/sponsors/fastapi.png and
new/starlette-0.17.1/docs/sponsors/fastapi.png differ
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/docs/third-party-packages.md
new/starlette-0.17.1/docs/third-party-packages.md
--- old/starlette-0.16.0/docs/third-party-packages.md 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/docs/third-party-packages.md 2021-11-17
17:10:27.000000000 +0100
@@ -20,7 +20,7 @@
Document your REST API built with Starlette by declaring OpenAPI (Swagger)
schemas in YAML format in your endpoint's docstrings.
-### SpecTree
+### SpecTree
<a href="https://github.com/0b01001001/spectree" target="_blank">GitHub</a>
@@ -43,8 +43,8 @@
<a href="https://github.com/Sobolev5/channel-box" target="_blank">GitHub</a>
-Another solution for websocket broadcast. Send messages to channel groups from
any part of your code.
-Checkout <a href="https://svue-backend.andrey-sobolev.ru/chat/chat1/"
target="_blank">channel-box-chat</a>, a simple chat application built using
`channel-box` and `starlette`.
+Another solution for websocket broadcast. Send messages to channel groups from
any part of your code.
+Checkout <a href="https://channel-box.andrey-sobolev.ru/"
target="_blank">MySimpleChat</a>, a simple chat application built using
`channel-box` and `starlette`.
### Scout APM
@@ -90,6 +90,28 @@
Middleware for Starlette that allows you to store and access the context data
of a request.
Can be used with logging so logs automatically use request headers such as
x-request-id or x-correlation-id.
+
+### Starsessions
+
+<a href="https://github.com/alex-oleshkevich/starsessions"
target="_blank">GitHub</a>
+
+An alternate session support implementation with customizable storage backends.
+
+
+### Starlette Cramjam
+
+<a href="https://github.com/developmentseed/starlette-cramjam"
target="_blank">GitHub</a>
+
+A Starlette middleware that allows **brotli**, **gzip** and **deflate**
compression algorithm with a minimal requirements.
+
+
+### Imia
+
+<a href="https://github.com/alex-oleshkevich/imia" target="_blank">GitHub</a>
+
+An authentication framework for Starlette with pluggable authenticators and
login/logout flow.
+
+
## Frameworks
### Responder
@@ -116,3 +138,9 @@
Formerly Starlette API.
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.
+
+### Starlette-apps
+
+Roll your own framework with a simple app system, like
[Django-GDAPS](https://gdaps.readthedocs.io/en/latest/) or
[CakePHP](https://cakephp.org/).
+
+<a href="https://github.com/yourlabs/starlette-apps" target="_blank">GitHub</a>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/mkdocs.yml
new/starlette-0.17.1/mkdocs.yml
--- old/starlette-0.16.0/mkdocs.yml 2021-07-19 09:08:36.000000000 +0200
+++ new/starlette-0.17.1/mkdocs.yml 2021-11-17 17:10:27.000000000 +0100
@@ -4,6 +4,19 @@
theme:
name: 'material'
+ custom_dir: docs/overrides
+ palette:
+ - scheme: 'default'
+ media: '(prefers-color-scheme: light)'
+ toggle:
+ icon: 'material/lightbulb'
+ name: "Switch to dark mode"
+ - scheme: 'slate'
+ media: '(prefers-color-scheme: dark)'
+ primary: 'blue'
+ toggle:
+ icon: 'material/lightbulb-outline'
+ name: 'Switch to light mode'
repo_name: encode/starlette
repo_url: https://github.com/encode/starlette
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/requirements.txt
new/starlette-0.17.1/requirements.txt
--- old/starlette-0.16.0/requirements.txt 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/requirements.txt 2021-11-17 17:10:27.000000000
+0100
@@ -1,5 +1,4 @@
# Optionals
-graphene; python_version<'3.10'
itsdangerous
jinja2
python-multipart
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/setup.cfg
new/starlette-0.17.1/setup.cfg
--- old/starlette-0.16.0/setup.cfg 2021-07-19 09:08:36.000000000 +0200
+++ new/starlette-0.17.1/setup.cfg 2021-11-17 17:10:27.000000000 +0100
@@ -24,16 +24,11 @@
filterwarnings=
# Turn warnings that aren't filtered into exceptions
error
- # Deprecated GraphQL (including
https://github.com/graphql-python/graphene/issues/1055)
- ignore: GraphQLApp is deprecated and will be removed in a future
release\..*:DeprecationWarning
ignore: Using or importing the ABCs from 'collections' instead of from
'collections\.abc' is deprecated.*:DeprecationWarning
ignore: The 'context' alias has been deprecated. Please use
'context_value' instead\.:DeprecationWarning
ignore: The 'variables' alias has been deprecated. Please use
'variable_values' instead\.:DeprecationWarning
+ # Workaround for Python 3.9.7 (see https://bugs.python.org/issue45097)
+ ignore:The loop argument is deprecated since Python 3\.8, and scheduled
for removal in Python 3\.10\.:DeprecationWarning:asyncio
[coverage:run]
source_pkgs = starlette, tests
-# GraphQLApp incompatible with and untested on Python 3.10. It's deprecated,
let's just ignore
-# coverage for it until it's gone.
-omit =
- starlette/graphql.py
- tests/test_graphql.py
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/setup.py
new/starlette-0.17.1/setup.py
--- old/starlette-0.16.0/setup.py 2021-07-19 09:08:36.000000000 +0200
+++ new/starlette-0.17.1/setup.py 2021-11-17 17:10:27.000000000 +0100
@@ -44,7 +44,6 @@
],
extras_require={
"full": [
- "graphene; python_version<'3.10'",
"itsdangerous",
"jinja2",
"python-multipart",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/__init__.py
new/starlette-0.17.1/starlette/__init__.py
--- old/starlette-0.16.0/starlette/__init__.py 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/starlette/__init__.py 2021-11-17 17:10:27.000000000
+0100
@@ -1 +1 @@
-__version__ = "0.16.0"
+__version__ = "0.17.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/authentication.py
new/starlette-0.17.1/starlette/authentication.py
--- old/starlette-0.16.0/starlette/authentication.py 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/starlette/authentication.py 2021-11-17
17:10:27.000000000 +0100
@@ -40,7 +40,9 @@
async def websocket_wrapper(
*args: typing.Any, **kwargs: typing.Any
) -> None:
- websocket = kwargs.get("websocket", args[idx] if args else
None)
+ websocket = kwargs.get(
+ "websocket", args[idx] if idx < len(args) else None
+ )
assert isinstance(websocket, WebSocket)
if not has_required_scope(websocket, scopes_list):
@@ -56,7 +58,7 @@
async def async_wrapper(
*args: typing.Any, **kwargs: typing.Any
) -> Response:
- request = kwargs.get("request", args[idx] if args else None)
+ request = kwargs.get("request", args[idx] if idx < len(args)
else None)
assert isinstance(request, Request)
if not has_required_scope(request, scopes_list):
@@ -73,7 +75,7 @@
# Handle sync request/response functions.
@functools.wraps(func)
def sync_wrapper(*args: typing.Any, **kwargs: typing.Any) ->
Response:
- request = kwargs.get("request", args[idx] if args else None)
+ request = kwargs.get("request", args[idx] if idx < len(args)
else None)
assert isinstance(request, Request)
if not has_required_scope(request, scopes_list):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/config.py
new/starlette-0.17.1/starlette/config.py
--- old/starlette-0.16.0/starlette/config.py 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/starlette/config.py 2021-11-17 17:10:27.000000000
+0100
@@ -46,6 +46,8 @@
environ = Environ()
+T = typing.TypeVar("T")
+
class Config:
def __init__(
@@ -58,6 +60,24 @@
if env_file is not None and os.path.isfile(env_file):
self.file_values = self._read_file(env_file)
+ @typing.overload
+ def __call__(
+ self, key: str, cast: typing.Type[T], default: T = ...
+ ) -> T: # pragma: no cover
+ ...
+
+ @typing.overload
+ def __call__(
+ self, key: str, cast: typing.Type[str] = ..., default: str = ...
+ ) -> str: # pragma: no cover
+ ...
+
+ @typing.overload
+ def __call__(
+ self, key: str, cast: typing.Type[str] = ..., default: T = ...
+ ) -> typing.Union[T, str]: # pragma: no cover
+ ...
+
def __call__(
self, key: str, cast: typing.Callable = None, default: typing.Any =
undefined
) -> typing.Any:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/datastructures.py
new/starlette-0.17.1/starlette/datastructures.py
--- old/starlette-0.16.0/starlette/datastructures.py 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/starlette/datastructures.py 2021-11-17
17:10:27.000000000 +0100
@@ -266,7 +266,7 @@
self._dict = {k: v for k, v in _items}
self._list = _items
- def getlist(self, key: typing.Any) -> typing.List[str]:
+ def getlist(self, key: typing.Any) -> typing.List[typing.Any]:
return [item_value for item_key, item_value in self._list if item_key
== key]
def keys(self) -> typing.KeysView:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/graphql.py
new/starlette-0.17.1/starlette/graphql.py
--- old/starlette-0.16.0/starlette/graphql.py 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/starlette/graphql.py 1970-01-01 01:00:00.000000000
+0100
@@ -1,275 +0,0 @@
-import json
-import typing
-import warnings
-
-from starlette import status
-from starlette.background import BackgroundTasks
-from starlette.concurrency import run_in_threadpool
-from starlette.requests import Request
-from starlette.responses import HTMLResponse, JSONResponse, PlainTextResponse,
Response
-from starlette.types import Receive, Scope, Send
-
-warnings.warn(
- "GraphQLApp is deprecated and will be removed in a future release. "
- "Consider using a third-party GraphQL implementation. "
- "See https://github.com/encode/starlette/issues/619.",
- DeprecationWarning,
-)
-
-try:
- import graphene
- from graphql.error import GraphQLError, format_error as
format_graphql_error
- from graphql.execution.executors.asyncio import AsyncioExecutor
-except ImportError: # pragma: nocover
- graphene = None
- AsyncioExecutor = None # type: ignore
- format_graphql_error = None # type: ignore
- GraphQLError = None # type: ignore
-
-
-class GraphQLApp:
- def __init__(
- self,
- schema: "graphene.Schema",
- executor_class: type = None,
- graphiql: bool = True,
- ) -> None:
- self.schema = schema
- self.graphiql = graphiql
- self.executor_class = executor_class
- self.is_async = executor_class is not None and issubclass(
- executor_class, AsyncioExecutor
- )
-
- async def __call__(self, scope: Scope, receive: Receive, send: Send) ->
None:
- if self.executor_class is not None:
- self.executor = self.executor_class()
-
- request = Request(scope, receive=receive)
- response = await self.handle_graphql(request)
- await response(scope, receive, send)
-
- async def handle_graphql(self, request: Request) -> Response:
- if request.method in ("GET", "HEAD"):
- if "text/html" in request.headers.get("Accept", ""):
- if not self.graphiql:
- return PlainTextResponse(
- "Not Found", status_code=status.HTTP_404_NOT_FOUND
- )
- return await self.handle_graphiql(request)
-
- data: typing.Mapping[str, typing.Any] = request.query_params
-
- elif request.method == "POST":
- content_type = request.headers.get("Content-Type", "")
-
- if "application/json" in content_type:
- data = await request.json()
- elif "application/graphql" in content_type:
- body = await request.body()
- text = body.decode()
- data = {"query": text}
- elif "query" in request.query_params:
- data = request.query_params
- else:
- return PlainTextResponse(
- "Unsupported Media Type",
- status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
- )
-
- else:
- return PlainTextResponse(
- "Method Not Allowed",
status_code=status.HTTP_405_METHOD_NOT_ALLOWED
- )
-
- try:
- query = data["query"]
- variables = data.get("variables")
- operation_name = data.get("operationName")
- except KeyError:
- return PlainTextResponse(
- "No GraphQL query found in the request",
- status_code=status.HTTP_400_BAD_REQUEST,
- )
-
- background = BackgroundTasks()
- context = {"request": request, "background": background}
-
- result = await self.execute(
- query, variables=variables, context=context,
operation_name=operation_name
- )
- error_data = (
- [format_graphql_error(err) for err in result.errors]
- if result.errors
- else None
- )
- response_data = {"data": result.data}
- if error_data:
- response_data["errors"] = error_data
- status_code = (
- status.HTTP_400_BAD_REQUEST if result.errors else
status.HTTP_200_OK
- )
-
- return JSONResponse(
- response_data, status_code=status_code, background=background
- )
-
- async def execute( # type: ignore
- self, query, variables=None, context=None, operation_name=None
- ):
- if self.is_async:
- return await self.schema.execute(
- query,
- variables=variables,
- operation_name=operation_name,
- executor=self.executor,
- return_promise=True,
- context=context,
- )
- else:
- return await run_in_threadpool(
- self.schema.execute,
- query,
- variables=variables,
- operation_name=operation_name,
- context=context,
- )
-
- async def handle_graphiql(self, request: Request) -> Response:
- text = GRAPHIQL.replace("{{REQUEST_PATH}}",
json.dumps(request.url.path))
- return HTMLResponse(text)
-
-
-GRAPHIQL = """
-<!--
- * Copyright (c) Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the license found in the
- * LICENSE file in the root directory of this source tree.
--->
-<!DOCTYPE html>
-<html>
- <head>
- <style>
- body {
- height: 100%;
- margin: 0;
- width: 100%;
- overflow: hidden;
- }
- #graphiql {
- height: 100vh;
- }
- </style>
- <!--
- This GraphiQL example depends on Promise and fetch, which are available
in
- modern browsers, but can be "polyfilled" for older browsers.
- GraphiQL itself depends on React DOM.
- If you do not want to rely on a CDN, you can host these files locally or
- include them directly in your favored resource bunder.
- -->
- <link href="//cdn.jsdelivr.net/npm/[email protected]/graphiql.css"
rel="stylesheet"/>
- <script
src="//cdn.jsdelivr.net/npm/[email protected]/fetch.min.js"></script>
- <script
src="//cdn.jsdelivr.net/npm/[email protected]/umd/react.production.min.js"></script>
- <script
src="//cdn.jsdelivr.net/npm/[email protected]/umd/react-dom.production.min.js"></script>
- <script
src="//cdn.jsdelivr.net/npm/[email protected]/graphiql.min.js"></script>
- </head>
- <body>
- <div id="graphiql">Loading...</div>
- <script>
- /**
- * This GraphiQL example illustrates how to use some of GraphiQL's props
- * in order to enable reading and updating the URL parameters, making
- * link sharing of queries a little bit easier.
- *
- * This is only one example of this kind of feature, GraphiQL exposes
- * various React params to enable interesting integrations.
- */
- // Parse the search string to get url parameters.
- var search = window.location.search;
- var parameters = {};
- search.substr(1).split('&').forEach(function (entry) {
- var eq = entry.indexOf('=');
- if (eq >= 0) {
- parameters[decodeURIComponent(entry.slice(0, eq))] =
- decodeURIComponent(entry.slice(eq + 1));
- }
- });
- // if variables was provided, try to format it.
- if (parameters.variables) {
- try {
- parameters.variables =
- JSON.stringify(JSON.parse(parameters.variables), null, 2);
- } catch (e) {
- // Do nothing, we want to display the invalid JSON as a string,
rather
- // than present an error.
- }
- }
- // When the query and variables string is edited, update the URL bar so
- // that it can be easily shared
- function onEditQuery(newQuery) {
- parameters.query = newQuery;
- updateURL();
- }
- function onEditVariables(newVariables) {
- parameters.variables = newVariables;
- updateURL();
- }
- function onEditOperationName(newOperationName) {
- parameters.operationName = newOperationName;
- updateURL();
- }
- function updateURL() {
- var newSearch = '?' + Object.keys(parameters).filter(function (key) {
- return Boolean(parameters[key]);
- }).map(function (key) {
- return encodeURIComponent(key) + '=' +
- encodeURIComponent(parameters[key]);
- }).join('&');
- history.replaceState(null, null, newSearch);
- }
- // Defines a GraphQL fetcher using the fetch API. You're not required to
- // use fetch, and could instead implement graphQLFetcher however you
like,
- // as long as it returns a Promise or Observable.
- function graphQLFetcher(graphQLParams) {
- // This example expects a GraphQL server at the path /graphql.
- // Change this to point wherever you host your GraphQL server.
- return fetch({{REQUEST_PATH}}, {
- method: 'post',
- headers: {
- 'Accept': 'application/json',
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(graphQLParams),
- credentials: 'include',
- }).then(function (response) {
- return response.text();
- }).then(function (responseBody) {
- try {
- return JSON.parse(responseBody);
- } catch (error) {
- return responseBody;
- }
- });
- }
- // Render <GraphiQL /> into the body.
- // See the README in the top level of this module to learn more about
- // how you can customize GraphiQL by providing different values or
- // additional child elements.
- ReactDOM.render(
- React.createElement(GraphiQL, {
- fetcher: graphQLFetcher,
- query: parameters.query,
- variables: parameters.variables,
- operationName: parameters.operationName,
- onEditQuery: onEditQuery,
- onEditVariables: onEditVariables,
- onEditOperationName: onEditOperationName
- }),
- document.getElementById('graphiql')
- );
- </script>
- </body>
-</html>
-""" # noqa: E501
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/middleware/base.py
new/starlette-0.17.1/starlette/middleware/base.py
--- old/starlette-0.16.0/starlette/middleware/base.py 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/starlette/middleware/base.py 2021-11-17
17:10:27.000000000 +0100
@@ -23,17 +23,25 @@
return
async def call_next(request: Request) -> Response:
+ app_exc: typing.Optional[Exception] = None
send_stream, recv_stream = anyio.create_memory_object_stream()
async def coro() -> None:
+ nonlocal app_exc
+
async with send_stream:
- await self.app(scope, request.receive, send_stream.send)
+ try:
+ await self.app(scope, request.receive,
send_stream.send)
+ except Exception as exc:
+ app_exc = exc
task_group.start_soon(coro)
try:
message = await recv_stream.receive()
except anyio.EndOfStream:
+ if app_exc is not None:
+ raise app_exc
raise RuntimeError("No response returned.")
assert message["type"] == "http.response.start"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/middleware/sessions.py
new/starlette-0.17.1/starlette/middleware/sessions.py
--- old/starlette-0.16.0/starlette/middleware/sessions.py 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/starlette/middleware/sessions.py 2021-11-17
17:10:27.000000000 +0100
@@ -3,7 +3,7 @@
from base64 import b64decode, b64encode
import itsdangerous
-from itsdangerous.exc import BadTimeSignature, SignatureExpired
+from itsdangerous.exc import BadSignature
from starlette.datastructures import MutableHeaders, Secret
from starlette.requests import HTTPConnection
@@ -42,7 +42,7 @@
data = self.signer.unsign(data, max_age=self.max_age)
scope["session"] = json.loads(b64decode(data))
initial_session_was_empty = False
- except (BadTimeSignature, SignatureExpired):
+ except BadSignature:
scope["session"] = {}
else:
scope["session"] = {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/requests.py
new/starlette-0.17.1/starlette/requests.py
--- old/starlette-0.16.0/starlette/requests.py 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/starlette/requests.py 2021-11-17 17:10:27.000000000
+0100
@@ -65,7 +65,7 @@
assert scope["type"] in ("http", "websocket")
self.scope = scope
- def __getitem__(self, key: str) -> str:
+ def __getitem__(self, key: str) -> typing.Any:
return self.scope[key]
def __iter__(self) -> typing.Iterator[str]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/responses.py
new/starlette-0.17.1/starlette/responses.py
--- old/starlette-0.16.0/starlette/responses.py 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/starlette/responses.py 2021-11-17 17:10:27.000000000
+0100
@@ -125,8 +125,25 @@
cookie_val = cookie.output(header="").strip()
self.raw_headers.append((b"set-cookie", cookie_val.encode("latin-1")))
- def delete_cookie(self, key: str, path: str = "/", domain: str = None) ->
None:
- self.set_cookie(key, expires=0, max_age=0, path=path, domain=domain)
+ def delete_cookie(
+ self,
+ key: str,
+ path: str = "/",
+ domain: str = None,
+ secure: bool = False,
+ httponly: bool = False,
+ samesite: str = "lax",
+ ) -> None:
+ self.set_cookie(
+ key,
+ max_age=0,
+ expires=0,
+ path=path,
+ domain=domain,
+ secure=secure,
+ httponly=httponly,
+ samesite=samesite,
+ )
async def __call__(self, scope: Scope, receive: Receive, send: Send) ->
None:
await send(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/staticfiles.py
new/starlette-0.17.1/starlette/staticfiles.py
--- old/starlette-0.16.0/starlette/staticfiles.py 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/starlette/staticfiles.py 2021-11-17
17:10:27.000000000 +0100
@@ -7,12 +7,8 @@
import anyio
from starlette.datastructures import URL, Headers
-from starlette.responses import (
- FileResponse,
- PlainTextResponse,
- RedirectResponse,
- Response,
-)
+from starlette.exceptions import HTTPException
+from starlette.responses import FileResponse, RedirectResponse, Response
from starlette.types import Receive, Scope, Send
PathLike = typing.Union[str, "os.PathLike[str]"]
@@ -109,9 +105,16 @@
Returns an HTTP response, given the incoming path, method and request
headers.
"""
if scope["method"] not in ("GET", "HEAD"):
- return PlainTextResponse("Method Not Allowed", status_code=405)
+ raise HTTPException(status_code=405)
- full_path, stat_result = await self.lookup_path(path)
+ try:
+ full_path, stat_result = await anyio.to_thread.run_sync(
+ self.lookup_path, path
+ )
+ except PermissionError:
+ raise HTTPException(status_code=401)
+ except OSError:
+ raise
if stat_result and stat.S_ISREG(stat_result.st_mode):
# We have a static file to serve.
@@ -121,7 +124,9 @@
# We're in HTML mode, and have got a directory URL.
# Check if we have 'index.html' file to serve.
index_path = os.path.join(path, "index.html")
- full_path, stat_result = await self.lookup_path(index_path)
+ full_path, stat_result = await anyio.to_thread.run_sync(
+ self.lookup_path, index_path
+ )
if stat_result is not None and stat.S_ISREG(stat_result.st_mode):
if not scope["path"].endswith("/"):
# Directory URLs should redirect to always end in "/".
@@ -132,18 +137,19 @@
if self.html:
# Check for '404.html' if we're in HTML mode.
- full_path, stat_result = await self.lookup_path("404.html")
- if stat_result is not None and stat.S_ISREG(stat_result.st_mode):
+ full_path, stat_result = await anyio.to_thread.run_sync(
+ self.lookup_path, "404.html"
+ )
+ if stat_result and stat.S_ISREG(stat_result.st_mode):
return FileResponse(
full_path,
stat_result=stat_result,
method=scope["method"],
status_code=404,
)
+ raise HTTPException(status_code=404)
- return PlainTextResponse("Not Found", status_code=404)
-
- async def lookup_path(
+ def lookup_path(
self, path: str
) -> typing.Tuple[str, typing.Optional[os.stat_result]]:
for directory in self.all_directories:
@@ -154,10 +160,9 @@
# directory.
continue
try:
- stat_result = await anyio.to_thread.run_sync(os.stat,
full_path)
- return full_path, stat_result
- except FileNotFoundError:
- pass
+ return full_path, os.stat(full_path)
+ except (FileNotFoundError, NotADirectoryError):
+ continue
return "", None
def file_response(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/starlette/templating.py
new/starlette-0.17.1/starlette/templating.py
--- old/starlette-0.16.0/starlette/templating.py 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/starlette/templating.py 2021-11-17
17:10:27.000000000 +0100
@@ -1,4 +1,5 @@
import typing
+from os import PathLike
from starlette.background import BackgroundTask
from starlette.responses import Response
@@ -54,11 +55,13 @@
return templates.TemplateResponse("index.html", {"request": request})
"""
- def __init__(self, directory: str) -> None:
+ def __init__(self, directory: typing.Union[str, PathLike]) -> None:
assert jinja2 is not None, "jinja2 must be installed to use
Jinja2Templates"
self.env = self._create_env(directory)
- def _create_env(self, directory: str) -> "jinja2.Environment":
+ def _create_env(
+ self, directory: typing.Union[str, PathLike]
+ ) -> "jinja2.Environment":
@pass_context
def url_for(context: dict, name: str, **path_params: typing.Any) ->
str:
request = context["request"]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/tests/conftest.py
new/starlette-0.17.1/tests/conftest.py
--- old/starlette-0.16.0/tests/conftest.py 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/tests/conftest.py 2021-11-17 17:10:27.000000000
+0100
@@ -1,12 +1,9 @@
import functools
-import sys
import pytest
from starlette.testclient import TestClient
-collect_ignore = ["test_graphql.py"] if sys.version_info >= (3, 10) else []
-
@pytest.fixture
def no_trio_support(anyio_backend_name):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/tests/middleware/test_base.py
new/starlette-0.17.1/tests/middleware/test_base.py
--- old/starlette-0.16.0/tests/middleware/test_base.py 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/tests/middleware/test_base.py 2021-11-17
17:10:27.000000000 +0100
@@ -25,7 +25,7 @@
@app.route("/exc")
def exc(request):
- raise Exception()
+ raise Exception("Exc")
@app.route("/no-response")
@@ -52,8 +52,9 @@
response = client.get("/")
assert response.headers["Custom-Header"] == "Example"
- with pytest.raises(Exception):
+ with pytest.raises(Exception) as ctx:
response = client.get("/exc")
+ assert str(ctx.value) == "Exc"
with pytest.raises(RuntimeError):
response = client.get("/no-response")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/tests/middleware/test_session.py
new/starlette-0.17.1/tests/middleware/test_session.py
--- old/starlette-0.16.0/tests/middleware/test_session.py 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/tests/middleware/test_session.py 2021-11-17
17:10:27.000000000 +0100
@@ -112,3 +112,16 @@
cookie = response.headers["set-cookie"]
cookie_path = re.search(r"; path=(\S+);", cookie).groups()[0]
assert cookie_path == "/second_app"
+
+
+def test_invalid_session_cookie(test_client_factory):
+ app = create_app()
+ app.add_middleware(SessionMiddleware, secret_key="example")
+ client = test_client_factory(app)
+
+ response = client.post("/update_session", json={"some": "data"})
+ assert response.json() == {"session": {"some": "data"}}
+
+ # we expect it to not raise an exception if we provide a bogus session
cookie
+ response = client.get("/view_session", cookies={"session": "invalid"})
+ assert response.json() == {"session": {}}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/tests/test_database.py
new/starlette-0.17.1/tests/test_database.py
--- old/starlette-0.16.0/tests/test_database.py 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/tests/test_database.py 2021-11-17 17:10:27.000000000
+0100
@@ -126,8 +126,6 @@
def test_database_execute_many(test_client_factory):
with test_client_factory(app) as client:
- response = client.get("/notes")
-
data = [
{"text": "buy the milk", "completed": True},
{"text": "walk the dog", "completed": False},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/tests/test_graphql.py
new/starlette-0.17.1/tests/test_graphql.py
--- old/starlette-0.16.0/tests/test_graphql.py 2021-07-19 09:08:36.000000000
+0200
+++ new/starlette-0.17.1/tests/test_graphql.py 1970-01-01 01:00:00.000000000
+0100
@@ -1,152 +0,0 @@
-import graphene
-import pytest
-from graphql.execution.executors.asyncio import AsyncioExecutor
-
-from starlette.applications import Starlette
-from starlette.datastructures import Headers
-from starlette.graphql import GraphQLApp
-
-
-class FakeAuthMiddleware:
- def __init__(self, app) -> None:
- self.app = app
-
- async def __call__(self, scope, receive, send):
- headers = Headers(scope=scope)
- scope["user"] = "Jane" if headers.get("Authorization") == "Bearer 123"
else None
- await self.app(scope, receive, send)
-
-
-class Query(graphene.ObjectType):
- hello = graphene.String(name=graphene.String(default_value="stranger"))
- whoami = graphene.String()
-
- def resolve_hello(self, info, name):
- return "Hello " + name
-
- def resolve_whoami(self, info):
- return (
- "a mystery"
- if info.context["request"]["user"] is None
- else info.context["request"]["user"]
- )
-
-
-schema = graphene.Schema(query=Query)
-
-
[email protected]
-def client(test_client_factory):
- app = GraphQLApp(schema=schema, graphiql=True)
- return test_client_factory(app)
-
-
-def test_graphql_get(client):
- response = client.get("/?query={ hello }")
- assert response.status_code == 200
- assert response.json() == {"data": {"hello": "Hello stranger"}}
-
-
-def test_graphql_post(client):
- response = client.post("/?query={ hello }")
- assert response.status_code == 200
- assert response.json() == {"data": {"hello": "Hello stranger"}}
-
-
-def test_graphql_post_json(client):
- response = client.post("/", json={"query": "{ hello }"})
- assert response.status_code == 200
- assert response.json() == {"data": {"hello": "Hello stranger"}}
-
-
-def test_graphql_post_graphql(client):
- response = client.post(
- "/", data="{ hello }", headers={"content-type": "application/graphql"}
- )
- assert response.status_code == 200
- assert response.json() == {"data": {"hello": "Hello stranger"}}
-
-
-def test_graphql_post_invalid_media_type(client):
- response = client.post("/", data="{ hello }", headers={"content-type":
"dummy"})
- assert response.status_code == 415
- assert response.text == "Unsupported Media Type"
-
-
-def test_graphql_put(client):
- response = client.put("/", json={"query": "{ hello }"})
- assert response.status_code == 405
- assert response.text == "Method Not Allowed"
-
-
-def test_graphql_no_query(client):
- response = client.get("/")
- assert response.status_code == 400
- assert response.text == "No GraphQL query found in the request"
-
-
-def test_graphql_invalid_field(client):
- response = client.post("/", json={"query": "{ dummy }"})
- assert response.status_code == 400
- assert response.json() == {
- "data": None,
- "errors": [
- {
- "locations": [{"column": 3, "line": 1}],
- "message": 'Cannot query field "dummy" on type "Query".',
- }
- ],
- }
-
-
-def test_graphiql_get(client):
- response = client.get("/", headers={"accept": "text/html"})
- assert response.status_code == 200
- assert "<!DOCTYPE html>" in response.text
-
-
-def test_graphiql_not_found(test_client_factory):
- app = GraphQLApp(schema=schema, graphiql=False)
- client = test_client_factory(app)
- response = client.get("/", headers={"accept": "text/html"})
- assert response.status_code == 404
- assert response.text == "Not Found"
-
-
-def test_add_graphql_route(test_client_factory):
- app = Starlette()
- app.add_route("/", GraphQLApp(schema=schema))
- client = test_client_factory(app)
- response = client.get("/?query={ hello }")
- assert response.status_code == 200
- assert response.json() == {"data": {"hello": "Hello stranger"}}
-
-
-def test_graphql_context(test_client_factory):
- app = Starlette()
- app.add_middleware(FakeAuthMiddleware)
- app.add_route("/", GraphQLApp(schema=schema))
- client = test_client_factory(app)
- response = client.post(
- "/", json={"query": "{ whoami }"}, headers={"Authorization": "Bearer
123"}
- )
- assert response.status_code == 200
- assert response.json() == {"data": {"whoami": "Jane"}}
-
-
-class ASyncQuery(graphene.ObjectType):
- hello = graphene.String(name=graphene.String(default_value="stranger"))
-
- async def resolve_hello(self, info, name):
- return "Hello " + name
-
-
-async_schema = graphene.Schema(query=ASyncQuery)
-async_app = GraphQLApp(schema=async_schema, executor_class=AsyncioExecutor)
-
-
-def test_graphql_async(no_trio_support, test_client_factory):
- client = test_client_factory(async_app)
- response = client.get("/?query={ hello }")
- assert response.status_code == 200
- assert response.json() == {"data": {"hello": "Hello stranger"}}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/starlette-0.16.0/tests/test_staticfiles.py
new/starlette-0.17.1/tests/test_staticfiles.py
--- old/starlette-0.16.0/tests/test_staticfiles.py 2021-07-19
09:08:36.000000000 +0200
+++ new/starlette-0.17.1/tests/test_staticfiles.py 2021-11-17
17:10:27.000000000 +0100
@@ -1,11 +1,13 @@
import os
import pathlib
+import stat
import time
import anyio
import pytest
from starlette.applications import Starlette
+from starlette.exceptions import HTTPException
from starlette.requests import Request
from starlette.routing import Mount
from starlette.staticfiles import StaticFiles
@@ -71,8 +73,10 @@
with open(path, "w") as file:
file.write("<file content>")
- app = StaticFiles(directory=tmpdir)
+ routes = [Mount("/", app=StaticFiles(directory=tmpdir), name="static")]
+ app = Starlette(routes=routes)
client = test_client_factory(app)
+
response = client.post("/example.txt")
assert response.status_code == 405
assert response.text == "Method Not Allowed"
@@ -83,8 +87,10 @@
with open(path, "w") as file:
file.write("<file content>")
- app = StaticFiles(directory=tmpdir)
+ routes = [Mount("/", app=StaticFiles(directory=tmpdir), name="static")]
+ app = Starlette(routes=routes)
client = test_client_factory(app)
+
response = client.get("/")
assert response.status_code == 404
assert response.text == "Not Found"
@@ -95,8 +101,10 @@
with open(path, "w") as file:
file.write("<file content>")
- app = StaticFiles(directory=tmpdir)
+ routes = [Mount("/", app=StaticFiles(directory=tmpdir), name="static")]
+ app = Starlette(routes=routes)
client = test_client_factory(app)
+
response = client.get("/404.txt")
assert response.status_code == 404
assert response.text == "Not Found"
@@ -136,11 +144,15 @@
app = StaticFiles(directory=tmpdir)
client = test_client_factory(app)
assert not app.config_checked
- client.get("/")
- assert app.config_checked
- client.get("/")
+
+ with pytest.raises(HTTPException):
+ client.get("/")
+
assert app.config_checked
+ with pytest.raises(HTTPException):
+ client.get("/")
+
def test_staticfiles_prevents_breaking_out_of_directory(tmpdir):
directory = os.path.join(tmpdir, "foo")
@@ -154,9 +166,12 @@
# We can't test this with 'requests', so we test the app directly here.
path = app.get_path({"path": "/../example.txt"})
scope = {"method": "GET"}
- response = anyio.run(app.get_response, path, scope)
- assert response.status_code == 404
- assert response.body == b"Not Found"
+
+ with pytest.raises(HTTPException) as exc_info:
+ anyio.run(app.get_response, path, scope)
+
+ assert exc_info.value.status_code == 404
+ assert exc_info.value.detail == "Not Found"
def test_staticfiles_never_read_file_for_head_method(tmpdir,
test_client_factory):
@@ -214,7 +229,7 @@
assert response.content == b"<file content>"
-def test_staticfiles_html(tmpdir, test_client_factory):
+def test_staticfiles_html_normal(tmpdir, test_client_factory):
path = os.path.join(tmpdir, "404.html")
with open(path, "w") as file:
file.write("<h1>Custom not found page</h1>")
@@ -247,6 +262,73 @@
assert response.text == "<h1>Custom not found page</h1>"
+def test_staticfiles_html_without_index(tmpdir, test_client_factory):
+ path = os.path.join(tmpdir, "404.html")
+ with open(path, "w") as file:
+ file.write("<h1>Custom not found page</h1>")
+ path = os.path.join(tmpdir, "dir")
+ os.mkdir(path)
+
+ app = StaticFiles(directory=tmpdir, html=True)
+ client = test_client_factory(app)
+
+ response = client.get("/dir/")
+ assert response.url == "http://testserver/dir/"
+ assert response.status_code == 404
+ assert response.text == "<h1>Custom not found page</h1>"
+
+ response = client.get("/dir")
+ assert response.url == "http://testserver/dir"
+ assert response.status_code == 404
+ assert response.text == "<h1>Custom not found page</h1>"
+
+ response = client.get("/missing")
+ assert response.status_code == 404
+ assert response.text == "<h1>Custom not found page</h1>"
+
+
+def test_staticfiles_html_without_404(tmpdir, test_client_factory):
+ path = os.path.join(tmpdir, "dir")
+ os.mkdir(path)
+ path = os.path.join(path, "index.html")
+ with open(path, "w") as file:
+ file.write("<h1>Hello</h1>")
+
+ app = StaticFiles(directory=tmpdir, html=True)
+ client = test_client_factory(app)
+
+ response = client.get("/dir/")
+ assert response.url == "http://testserver/dir/"
+ assert response.status_code == 200
+ assert response.text == "<h1>Hello</h1>"
+
+ response = client.get("/dir")
+ assert response.url == "http://testserver/dir/"
+ assert response.status_code == 200
+ assert response.text == "<h1>Hello</h1>"
+
+ with pytest.raises(HTTPException) as exc_info:
+ response = client.get("/missing")
+ assert exc_info.value.status_code == 404
+
+
+def test_staticfiles_html_only_files(tmpdir, test_client_factory):
+ path = os.path.join(tmpdir, "hello.html")
+ with open(path, "w") as file:
+ file.write("<h1>Hello</h1>")
+
+ app = StaticFiles(directory=tmpdir, html=True)
+ client = test_client_factory(app)
+
+ with pytest.raises(HTTPException) as exc_info:
+ response = client.get("/")
+ assert exc_info.value.status_code == 404
+
+ response = client.get("/hello.html")
+ assert response.status_code == 200
+ assert response.text == "<h1>Hello</h1>"
+
+
def test_staticfiles_cache_invalidation_for_deleted_file_html_mode(
tmpdir, test_client_factory
):
@@ -284,3 +366,70 @@
)
assert resp_deleted.status_code == 404
assert resp_deleted.text == "<p>404 file</p>"
+
+
+def test_staticfiles_with_invalid_dir_permissions_returns_401(
+ tmpdir, test_client_factory
+):
+ path = os.path.join(tmpdir, "example.txt")
+ with open(path, "w") as file:
+ file.write("<file content>")
+
+ os.chmod(tmpdir, stat.S_IRWXO)
+
+ routes = [Mount("/", app=StaticFiles(directory=tmpdir), name="static")]
+ app = Starlette(routes=routes)
+ client = test_client_factory(app)
+
+ response = client.get("/example.txt")
+ assert response.status_code == 401
+ assert response.text == "Unauthorized"
+
+
+def test_staticfiles_with_missing_dir_returns_404(tmpdir, test_client_factory):
+ path = os.path.join(tmpdir, "example.txt")
+ with open(path, "w") as file:
+ file.write("<file content>")
+
+ routes = [Mount("/", app=StaticFiles(directory=tmpdir), name="static")]
+ app = Starlette(routes=routes)
+ client = test_client_factory(app)
+
+ response = client.get("/foo/example.txt")
+ assert response.status_code == 404
+ assert response.text == "Not Found"
+
+
+def test_staticfiles_access_file_as_dir_returns_404(tmpdir,
test_client_factory):
+ path = os.path.join(tmpdir, "example.txt")
+ with open(path, "w") as file:
+ file.write("<file content>")
+
+ routes = [Mount("/", app=StaticFiles(directory=tmpdir), name="static")]
+ app = Starlette(routes=routes)
+ client = test_client_factory(app)
+
+ response = client.get("/example.txt/foo")
+ assert response.status_code == 404
+ assert response.text == "Not Found"
+
+
+def test_staticfiles_unhandled_os_error_returns_500(
+ tmpdir, test_client_factory, monkeypatch
+):
+ def mock_timeout(*args, **kwargs):
+ raise TimeoutError
+
+ path = os.path.join(tmpdir, "example.txt")
+ with open(path, "w") as file:
+ file.write("<file content>")
+
+ routes = [Mount("/", app=StaticFiles(directory=tmpdir), name="static")]
+ app = Starlette(routes=routes)
+ client = test_client_factory(app, raise_server_exceptions=False)
+
+ monkeypatch.setattr("starlette.staticfiles.StaticFiles.lookup_path",
mock_timeout)
+
+ response = client.get("/example.txt")
+ assert response.status_code == 500
+ assert response.text == "Internal Server Error"