Skip to content

Commit

Permalink
Version 0.13 (encode#704)
Browse files Browse the repository at this point in the history
* Version 0.13

* Fix misnamed requirement

* Updating routing docs

* Update routing docs

* Update docs

* Simpler Middleware signature

* Update middleware docs

* Update exception docs

* Allow 'None' in middleware lists, for easy disabling of middleware.

* Update README
  • Loading branch information
tomchristie authored Nov 13, 2019
1 parent 7822039 commit 7f8cd04
Show file tree
Hide file tree
Showing 26 changed files with 669 additions and 418 deletions.
20 changes: 14 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,20 +61,28 @@ $ pip3 install uvicorn

## Example

**example.py**:

```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
import uvicorn

app = Starlette(debug=True)
from starlette.routing import Route


@app.route('/')
async def homepage(request):
return JSONResponse({'hello': 'world'})

if __name__ == '__main__':
uvicorn.run(app, host='0.0.0.0', port=8000)
routes = [
Route("/", endpoint=homepage)
]

app = Starlette(debug=True, routes=route)
```

Then run the application using Uvicorn:

```shell
$ uvicorn example:app
```

For a more complete example, see [encode/starlette-example](https://github.com/encode/starlette-example).
Expand Down
62 changes: 15 additions & 47 deletions docs/applications.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,77 +5,45 @@ its other functionality.
```python
from starlette.applications import Starlette
from starlette.responses import PlainTextResponse
from starlette.routing import Route, Mount, WebSocketRoute
from starlette.staticfiles import StaticFiles


app = Starlette()
app.debug = True
app.mount('/static', StaticFiles(directory="static"))


@app.route('/')
def homepage(request):
return PlainTextResponse('Hello, world!')

@app.route('/user/me')
def user_me(request):
username = "John Doe"
return PlainTextResponse('Hello, %s!' % username)

@app.route('/user/{username}')
def user(request):
username = request.path_params['username']
return PlainTextResponse('Hello, %s!' % username)


@app.websocket_route('/ws')
async def websocket_endpoint(websocket):
await websocket.accept()
await websocket.send_text('Hello, websocket!')
await websocket.close()


@app.on_event('startup')
def startup():
print('Ready to go')
```

### Instantiating the application

* `Starlette(debug=False)` - Create a new Starlette application.

### Adding routes to the application

You can use any of the following to add handled routes to the application:

* `app.add_route(path, func, methods=["GET"])` - Add an HTTP route. The function may be either a coroutine or a regular function, with a signature like `func(request, **kwargs) -> response`.
* `app.add_websocket_route(path, func)` - Add a websocket session route. The function must be a coroutine, with a signature like `func(session, **kwargs)`.
* `@app.route(path)` - Add an HTTP route, decorator style.
* `@app.websocket_route(path)` - Add a WebSocket route, decorator style.
routes = [
Route('/', homepage),
Route('/user/me', user_me),
Route('/user/{username}', user),
WebSocketRoute('/ws', websocket_endpoint),
Mount('/static', StaticFiles(directory="static")),
]

### Adding event handlers to the application

There are two ways to add event handlers:

* `@app.on_event(event_type)` - Add an event, decorator style
* `app.add_event_handler(event_type, func)` - Add an event through a function call.

`event_type` must be specified as either `'startup'` or `'shutdown'`.

### Submounting other applications

Submounting applications is a powerful way to include reusable ASGI applications.

* `app.mount(prefix, app)` - Include an ASGI app, mounted under the given path prefix

### Customizing exception handling
app = Starlette(debug=True, routes=routes, on_startup=[startup])
```

You can use either of the following to catch and handle particular types of
exceptions that occur within the application:
### Instantiating the application

* `app.add_exception_handler(exc_class_or_status_code, handler)` - Add an error handler. The handler function may be either a coroutine or a regular function, with a signature like `func(request, exc) -> response`.
* `@app.exception_handler(exc_class_or_status_code)` - Add an error handler, decorator style.
* `app.debug` - Enable or disable error tracebacks in the browser.
::: starlette.applications.Starlette
:docstring:

### Storing state on the app instance

Expand All @@ -88,6 +56,6 @@ For example:
app.state.ADMIN_EMAIL = '[email protected]'
```

### Acessing the app instance
### Accessing the app instance

Where a `request` is available (i.e. endpoints and middleware), the app is available on `request.app`. For other situations it can be imported from wherever it's instantiated.
Where a `request` is available (i.e. endpoints and middleware), the app is available on `request.app`.
31 changes: 15 additions & 16 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ from starlette.authentication import (
AuthenticationBackend, AuthenticationError, SimpleUser, UnauthenticatedUser,
AuthCredentials
)
from starlette.middleware import Middleware
from starlette.middleware.authentication import AuthenticationMiddleware
from starlette.responses import PlainTextResponse
from starlette.routing import Route
import base64
import binascii

Expand All @@ -30,21 +32,24 @@ class BasicAuthBackend(AuthenticationBackend):
raise AuthenticationError('Invalid basic auth credentials')

username, _, password = decoded.partition(":")
# TODO: You'd want to verify the username and password here,
# possibly by installing `DatabaseMiddleware`
# and retrieving user information from `request.database`.
# TODO: You'd want to verify the username and password here.
return AuthCredentials(["authenticated"]), SimpleUser(username)


app = Starlette()
app.add_middleware(AuthenticationMiddleware, backend=BasicAuthBackend())


@app.route('/')
async def homepage(request):
if request.user.is_authenticated:
return PlainTextResponse('hello, ' + request.user.display_name)
return PlainTextResponse('hello, you')
return PlainTextResponse('Hello, ' + request.user.display_name)
return PlainTextResponse('Hello, you')

routes = [
Route("/", endpoint=homepage)
]

middleware = [
Middleware(AuthenticationMiddleware, backend=BasicAuthBackend())
]

app = Starlette(routes=routes, middleware=middleware)
```

## Users
Expand Down Expand Up @@ -81,7 +86,6 @@ incoming request includes the required authentication scopes.
from starlette.authentication import requires


@app.route('/dashboard')
@requires('authenticated')
async def dashboard(request):
...
Expand All @@ -93,7 +97,6 @@ You can include either one or multiple required scopes:
from starlette.authentication import requires


@app.route('/dashboard')
@requires(['authenticated', 'admin'])
async def dashboard(request):
...
Expand All @@ -107,7 +110,6 @@ about the URL layout from unauthenticated users.
from starlette.authentication import requires


@app.route('/dashboard')
@requires(['authenticated', 'admin'], status_code=404)
async def dashboard(request):
...
Expand All @@ -120,12 +122,10 @@ page.
from starlette.authentication import requires


@app.route('/homepage')
async def homepage(request):
...


@app.route('/dashboard')
@requires('authenticated', redirect='homepage')
async def dashboard(request):
...
Expand All @@ -135,7 +135,6 @@ For class-based endpoints, you should wrap the decorator
around a method on the class.

```python
@app.route("/dashboard")
class Dashboard(HTTPEndpoint):
@requires("authenticated")
async def get(self, request):
Expand Down
21 changes: 16 additions & 5 deletions docs/background.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ Signature: `BackgroundTask(func, *args, **kwargs)`
```python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
from starlette.background import BackgroundTask

app = Starlette()

@app.route('/user/signup', methods=['POST'])
...

async def signup(request):
data = await request.json()
username = data['username']
Expand All @@ -28,6 +29,14 @@ async def signup(request):

async def send_welcome_email(to_address):
...


routes = [
...
Route('/user/signup', endpoint=signup, methods=['POST'])
]

app = Starlette(routes=routes)
```

### BackgroundTasks
Expand All @@ -41,9 +50,6 @@ from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.background import BackgroundTasks

app = Starlette()

@app.route('/user/signup', methods=['POST'])
async def signup(request):
data = await request.json()
username = data['username']
Expand All @@ -60,4 +66,9 @@ async def send_welcome_email(to_address):
async def send_admin_notification(username):
...

routes = [
Route('/user/signup', endpoint=signup, methods=['POST'])
]

app = Starlette(routes=routes)
```
32 changes: 15 additions & 17 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ DATABASE_URL = config('DATABASE_URL', cast=databases.DatabaseURL)
SECRET_KEY = config('SECRET_KEY', cast=Secret)
ALLOWED_HOSTS = config('ALLOWED_HOSTS', cast=CommaSeparatedStrings)

app = Starlette()
app.debug = DEBUG
app = Starlette(debug=DEBUG)
...
```

Expand Down Expand Up @@ -160,28 +159,27 @@ organisations = sqlalchemy.Table(

```python
from starlette.applications import Starlette
from starlette.middleware.database import DatabaseMiddleware
from starlette.middleware import Middleware
from starlette.middleware.session import SessionMiddleware
from starlette.routing import Route
from myproject import settings


app = Starlette()
async def homepage(request):
...

app.debug = settings.DEBUG
routes = [
Route("/", endpoint=homepage)
]

app.add_middleware(
SessionMiddleware,
secret_key=settings.SECRET_KEY,
)
app.add_middleware(
DatabaseMiddleware,
database_url=settings.DATABASE_URL,
rollback_on_shutdown=settings.TESTING
)
middleware = [
Middleware(
SessionMiddleware,
secret_key=settings.SECRET_KEY,
)
]

@app.route('/', methods=['GET'])
async def homepage(request):
...
app = Starlette(debug=settings.DEBUG, routes=routes, middleware=middleware)
```

Now let's deal with our test configuration.
Expand Down
27 changes: 12 additions & 15 deletions docs/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,10 @@ notes = sqlalchemy.Table(
sqlalchemy.Column("completed", sqlalchemy.Boolean),
)

# Main application code.
database = databases.Database(DATABASE_URL)
app = Starlette()


@app.on_event("startup")
async def startup():
await database.connect()


@app.on_event("shutdown")
async def shutdown():
await database.disconnect()


@app.route("/notes", methods=["GET"])
# Main application code.
async def list_notes(request):
query = notes.select()
results = await database.fetch_all(query)
Expand All @@ -73,8 +61,6 @@ async def list_notes(request):
]
return JSONResponse(content)


@app.route("/notes", methods=["POST"])
async def add_note(request):
data = await request.json()
query = notes.insert().values(
Expand All @@ -86,6 +72,17 @@ async def add_note(request):
"text": data["text"],
"completed": data["completed"]
})

routes = [
Route("/notes", endpoint=list_notes, methods=["GET"]),
Route("/notes", endpoint=add_note, methods=["POST"]),
]

app = Starlette(
routes=routes,
on_startup=[database.connect],
on_shutdown=[database.disconnect]
)
```

## Queries
Expand Down
Loading

0 comments on commit 7f8cd04

Please sign in to comment.