Coder Social home page Coder Social logo

encode / starlette Goto Github PK

View Code? Open in Web Editor NEW
9.5K 105.0 840.0 6.6 MB

The little ASGI framework that shines. 🌟

Home Page: https://www.starlette.io/

License: BSD 3-Clause "New" or "Revised" License

Shell 0.47% Python 99.53%
python async websockets http

starlette's Introduction

starlette

✨ The little ASGI framework that shines. ✨


Build Status Package version Supported Python Version

Documentation: https://www.starlette.io/


Starlette

Starlette is a lightweight ASGI framework/toolkit, which is ideal for building async web services in Python.

It is production-ready, and gives you the following:

  • A lightweight, low-complexity HTTP web framework.
  • WebSocket support.
  • In-process background tasks.
  • Startup and shutdown events.
  • Test client built on httpx.
  • CORS, GZip, Static Files, Streaming responses.
  • Session and Cookie support.
  • 100% test coverage.
  • 100% type annotated codebase.
  • Few hard dependencies.
  • Compatible with asyncio and trio backends.
  • Great overall performance against independent benchmarks.

Requirements

Python 3.8+

Installation

$ pip3 install starlette

You'll also want to install an ASGI server, such as uvicorn, daphne, or hypercorn.

$ pip3 install uvicorn

Example

example.py:

from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route


async def homepage(request):
    return JSONResponse({'hello': 'world'})

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

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

Then run the application using Uvicorn:

$ uvicorn example:app

For a more complete example, see encode/starlette-example.

Dependencies

Starlette only requires anyio, and the following are optional:

  • httpx - Required if you want to use the TestClient.
  • jinja2 - Required if you want to use Jinja2Templates.
  • python-multipart - Required if you want to support form parsing, with request.form().
  • itsdangerous - Required for SessionMiddleware support.
  • pyyaml - Required for SchemaGenerator support.

You can install all of these with pip3 install starlette[full].

Framework or Toolkit

Starlette is designed to be used either as a complete framework, or as an ASGI toolkit. You can use any of its components independently.

from starlette.responses import PlainTextResponse


async def app(scope, receive, send):
    assert scope['type'] == 'http'
    response = PlainTextResponse('Hello, world!')
    await response(scope, receive, send)

Run the app application in example.py:

$ uvicorn example:app
INFO: Started server process [11509]
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

Run uvicorn with --reload to enable auto-reloading on code changes.

Modularity

The modularity that Starlette is designed on promotes building re-usable components that can be shared between any ASGI framework. This should enable an ecosystem of shared middleware and mountable applications.

The clean API separation also means it's easier to understand each component in isolation.


Starlette is BSD licensed code.
Designed & crafted with care.

β€” ⭐️ β€”

starlette's People

Contributors

abersheeran avatar adriangb avatar agronholm avatar alex-oleshkevich avatar aminalaee avatar blueyed avatar dependabot[bot] avatar didip avatar em92 avatar erewok avatar erm avatar florimondmanca avatar graingert avatar harrysky avatar jayh5 avatar jeffbuttars avatar jordaneremieff avatar kludex avatar koddr avatar marcosschroh avatar perdy avatar pvanliefland avatar rafalp avatar simonw avatar steinnes avatar taoufik07 avatar techniick avatar tiangolo avatar tomchristie avatar woile avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

starlette's Issues

Error serving static files larger than 4096 bytes

Static files larger than 4096 bytes do not appear to be served correctly.

Here's a test I just wrote that illustrates the problem: simonw@e2d6665

Running that test gives me the following output:

(venv) starlette $ PYTHONPATH=. pytest -k test_large_staticfile 
===================================================== test session starts ======================================================
platform darwin -- Python 3.6.5, pytest-3.6.1, py-1.5.3, pluggy-0.6.0
rootdir: /Users/simonw/Dropbox/Development/starlette, inifile:
collected 43 items / 42 deselected                                                                                             

tests/test_staticfiles.py F                                                                                              [100%]

=========================================================== FAILURES ===========================================================
____________________________________________________ test_large_staticfile _____________________________________________________

tmpdir = local('/private/var/folders/jj/fngnv0810tn2lt_kd3911pdc0000gp/T/pytest-of-simonw/pytest-8/test_large_staticfile0')

    def test_large_staticfile(tmpdir):
        path = os.path.join(tmpdir, "example.txt")
        content = "this is a lot of content" * 200
        print("content len = ", len(content))
        with open(path, "w") as file:
            file.write(content)
    
        app = StaticFile(path=path)
        client = TestClient(app)
        response = client.get("/")
        assert response.status_code == 200
>       assert len(content) == len(response.text)
E       AssertionError: assert 4800 == 4096
E        +  where 4800 = len('this is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of cont...is is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of content')
E        +  and   4096 = len(' contentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot...ontentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of')
E        +    where ' contentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot...ontentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of contentthis is a lot of' = <Response [200]>.text

tests/test_staticfiles.py:30: AssertionError

Option to enforce CORS server-side

Like most CORS implementations, we leave enforcement strictly as a browser concern, and just make sure that we're setting the correct headers for the browser to deal with that.

If we wanted we could strictly enforce the policy server-side too. That might be useful for eg. ensuring that it's easier to test, that developers get logs that reflect any cors failures, etc...

We do some similar checks in preflight_response, which does provide useful informative responses, either 400 or 200.

If we add server-side enforcement, then I think it should probably be optional, but default to being on. enforce_server_side=True.

We'd want to use the same sort of checking, and useful errors as we currently do in preflight_response, and accumulate the different error types (origin, method, headers, credentials).

Also worth noting that preflight_response doesn't currently check for the existence of cookies against allow_credentials. (It doesn't strictly need to but if we're going to add server-side enforcement then it ought to be used in both places.)

Startup / Shutdown events

Refs django/asgiref#63

Add @app.startup and @app.shutdown decorators that call into code on lifespan startup/shutdown.

Eg...

app = Starlette()

@app.startup
async def initialize_database_connection_pool():
    ...

@app.shutdown
async def close_database_connection():
    ...

You'll be able to add multiple startup/shutdown events.

CORSMiddleware is sending an extra 'http.response.body'

It seems that even with all tests passing and cors being successfully applied, CORSMiddleware still raises a runtime error.

Code being tested:

app = Starlette()

app.add_middleware(CORSMiddleware, allow_origins=["*"])

@app.route("/")
async def homepage(request):
    return PlainTextResponse('Hello', status_code=200)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

And the error being produced:

ERROR: Exception in ASGI application
Traceback (most recent call last):
  File "/home/alexbotello/.local/share/virtualenvs/starlette-dshJy1CJ/lib/python3.7/site-packages/uvicorn/protocols/http/httptools_impl.py", line 384, in run_asgi
    result = await asgi(self.receive, self.send)
  File "/home/alexbotello/Code/starlette/starlette/exceptions.py", line 60, in app
    raise exc from None
  File "/home/alexbotello/Code/starlette/starlette/exceptions.py", line 52, in app
    await instance(receive, sender)
  File "/home/alexbotello/Code/starlette/starlette/middleware/cors.py", line 116, in simple_response
    await inner(receive, send)
  File "/home/alexbotello/Code/starlette/starlette/applications.py", line 26, in awaitable
    await response(receive, send)
  File "/home/alexbotello/Code/starlette/starlette/responses.py", line 100, in __call__
    await send({"type": "http.response.body", "body": self.body})
  File "/home/alexbotello/Code/starlette/starlette/middleware/cors.py", line 130, in send
    await send(message)
  File "/home/alexbotello/Code/starlette/starlette/exceptions.py", line 47, in sender
    await send(message)
  File "/home/alexbotello/.local/share/virtualenvs/starlette-dshJy1CJ/lib/python3.7/site-packages/uvicorn/protocols/http/httptools_impl.py", line 518, in send
    raise RuntimeError(msg % message_type)
RuntimeError: Unexpected ASGI message 'http.response.body' sent, after response already completed.

It seems the issue is originating from send. Specifically:

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

Removing this fixes the issue and does not break any tests.

Support `shutdown` as a synonym for `cleanup`

  • Support either cleanup or shutdown as the ASGI lifespan message name.
  • Update uvicorn to move to shutdown - encode/uvicorn#233
  • Finally, after a small period of time, drop cleanup

Easy PR for a contributor to jump on would be addressing the first part of this, and supporting either name.

StaticFile refinements

  • HEAD.
  • Range responses.
  • 304 if ETag and Last-Modified indicate no changes against a conditional GET request.
  • Consider Manifests / Gzip / Far-future.

MyPy conformance

There are a few remaining mypy errors that it'd be nice to tidy up...

$ mypy starlette 
starlette/datastructures.py:148: error: Signature of "get" incompatible with supertype "Mapping"
starlette/datastructures.py:158: error: Argument 1 of "__contains__" incompatible with supertype "Mapping"
starlette/datastructures.py:158: error: Argument 1 of "__contains__" incompatible with supertype "Container"
starlette/testclient.py:114: error: Signature of "request" incompatible with supertype "Session"

The datastructures ones really aren't obvious. It may be that they're resolvable but I've not figured out how.

The testclient one we might want to just put an exclude against - otherwise we need to include all of the keyword arguments to request explicitly, rather than capturing them with **kwargs and passing them on.

[Question] Async test client

I'm developing an app that requires async setup and teardown during testing. For example, I insert data into a Redis database using aioredis. I'm using pytest-asyncio to mark async test functions.

The test client uses the synchronous requests API and an ASGI adapter to call the Starlette app. The setup I described above doesn't work with the adapter because it doesn't expect an event loop to be running.

I haven't worked with asyncio prior to using Starlette so I may be missing something obvious but do you have any suggestions for how to deal with this? I thought about more explicitly managing the event loop during testing but this seemed a bit tedious.

Another option was an async test client. I had a look at aiohttp and if a custom Connector could be used in place of the ASGI requests adapter but I didn't spend much time on it.

For now, I cobbled together an async test client that combines the request setup from requests.Session and starlette.testclient. _ASGIAdapter.

By the way, if there's a better place to ask questions, please let me know πŸ˜„

Exception handling

  • An ExceptionMiddleware class.
  • An HTTPException(status_code=..., detail=...) class.
  • Include by default in App.
  • Ensure app router raises for 404 and 406 cases, so cases can be caught.
  • 500 handling.

WebSockets

Currently no tooling provided here for working with websockets.
Need to have a think about what we can usefully add there.

Starlette's scope

I am very interested in contributing to some common middleware for ASGI, and this seems like it is the perfect place to do that, however I have a few questions about how much Starlette is aiming to achieve:

How much functionality will this project implement?

How much functionality does it intend to leave up to particular frameworks?

Will it potentially become a more traditional framework itself, or is the intention to remain a small library?

Request should present a scope-like interface

The Request class should present a dict-like interface so that it can be used in the same way as scope. Should also allow it to be instantiated without a receive channel being set initially.

Drop `StaticFile` app.

We have FileResponse and StaticFiles.

I think that including the StaticFile ASGI app complicates things unnecessarily, and that we should probably remove it.

  • Drop StaticFile app.
  • Put runtime checks that file exists, and file is a regular file in FileResponse.

Background tasks

  • Add a background=... keyword argument to all responses. It should be either None or a coroutine with no arguments.
  • The response __call__ should run the background coroutine if it exists (after sending the response).
  • Provide a BackgroundTask() class that wraps a callable and some arguments. This can then be used to pass a function or coroutine with args/kwargs to a response.
class BackgroundTask:
    def __init__(self, func, *args, **kwargs):
        self.func = func
        self.args = args
        self.kwargs = kwargs

    async def __call__(self):
        if asyncio.iscoroutine(self.func):
            await self.func(*self.args, **self.kwargs)
        else:
            self.func(*self.args, **self.kwargs)

Then we can do...

return Response(..., background=BackgroundTask(my_func, a=123)

Naming might need some thinking about - not sure that conflicting with asyncio's Task is ideal.

This'd be a good feature to get in, since it's a low amount of work, but provides functionality that WSGI is unable to do.

Include more information in `DebugMiddleware`.

Right now we just display a raw format_trackback().

It'd make sense to have a proper page with lines of context around each file in the stack, and local variables at each point. (As with Django or Werkzeug's debugger pages)

Consider moving to `request.kwargs`

In both our function and class-based endpoints, we're currently using function signatures like:

def handler(request, **kwargs):
    ...

Which are sometimes expanded to include the actual path keyword arguments that are being routed, eg...

@app.route('/{username}')
def userpage(request, username):
    ...

I think we should possibly move against the grain of what Python frameworks are all doing here, and instead have the routing be part of the request itself, so... request.kwargs.username

That'll give us really simple, consistent interfaces, that don't sometimes look like handler(request, **kwargs) and other times look like handler(request, username=None) etc...

It's also consistent with say, things like request.auth and request.user that we may eventually have - they're other bits of information on the request that are just as important as the routing keyword arguments, but they just live on the request object itself.

It'll also simplify things like our class-based interfaces where some of the methods take **kwargs and others don't.

Not typical of other frameworks, but I quite like it as a more refined, consistent interface.

Add `allow_origin_regex` to CORSMiddleware.

It'd be helpful if CORSMiddleware supported an allow_origin_regex, so that users could do...

# Enforce a subdomain CORS policy
app.add_middleware(CORSMiddleware, allow_origin_regex="(http|https)://*.example.com")

Or...

# Enforce an HTTPS-only CORS policy.
app.add_middleware(CORSMiddleware, allow_origin_regex="https://*")

The string should be compiled to a regex by the middleware and matches should be anchored to the start/end of the origin string.

Requests as MutableMapping

Requests should possibly expose a MutableMapping interface, rather than Mapping.

Although we present immutable interfaces on the request properties, it'd make sense for requests to more correctly match the scope interface, and allow dict mutation.

We'd need to take some care to invalidate cached properties if we did so, particularly if request['headers'] is mutated.

Add missing annotations

There are plenty of places where we should add type annotations...

mypy starlette --disallow-untyped-defs

starlette/datastructures.py:7: error: Function is missing a type annotation
starlette/datastructures.py:13: error: Function is missing a type annotation
starlette/datastructures.py:17: error: Function is missing a type annotation
starlette/datastructures.py:21: error: Function is missing a type annotation
starlette/datastructures.py:25: error: Function is missing a type annotation
...

For the purposes of this ticket, ignore the error cases (see #6) since they're pretty tricky, and just focus on including any missing annotations.

URL Reversing

The App and Router classes should support a .url_for(name, **kwargs) method.

We'll probably need to finesse our routing a bit here, eg. remove the ability to drop into plain regex, and instead add something like {username:str} / {page_number:int}.

Eventually we'll probably also want to support namespacing eg. app.url_for('users:create_user', username=foo)

Important

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Apache Bench (`ab`) times out if you don't manually add a Connection HTTP header

without manually setting the Connection HTTP header in a response, Apache Bench (Version 2.3 <$Revision: 1528965 $>) reports 5000ms + connection times.

If you manually set the Connection HTTP header to either "Keep-Alive" or "close" then ab shows results as expected (1ms connection time).

e.g.

without a valid Connection header:

$ ab -v 2 -c 1 -n 1 http://localhost:5044/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)...INFO: GET header ==
---
GET / HTTP/1.0
Host: localhost:5044
User-Agent: ApacheBench/2.3
Accept: */*


---
LOG: header received:
HTTP/1.1 200 OK
server: uvicorn
date: Thu, 18 Oct 2018 21:33:50 GMT
transfer-encoding: chunked

6
string
0


..done


Server Software:
Server Hostname:        localhost
Server Port:            5044

Document Path:          /
Document Length:        16 bytes

Concurrency Level:      1
Time taken for tests:   5.001 seconds
Complete requests:      1
Failed requests:        0
Total transferred:      139 bytes
HTML transferred:       16 bytes
Requests per second:    0.20 [#/sec] (mean)
Time per request:       5001.050 [ms] (mean)
Time per request:       5001.050 [ms] (mean, across all concurrent requests)
Transfer rate:          0.03 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:  5001 5001   0.0   5001    5001
Waiting:        1    1   0.0      1       1
Total:       5001 5001   0.0   5001    5001
$

with a properly set header ("close" or "Keep-Alive")

$ ab -v 2 -c 1 -n 1 http://localhost:5044/
This is ApacheBench, Version 2.3 <$Revision: 1528965 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking localhost (be patient)...INFO: GET header ==
---
GET / HTTP/1.0
Host: localhost:5044
User-Agent: ApacheBench/2.3
Accept: */*


---
LOG: header received:
HTTP/1.1 200 OK
server: uvicorn
date: Thu, 18 Oct 2018 21:39:02 GMT
connection: close
transfer-encoding: chunked

6
string
0


..done


Server Software:
Server Hostname:        localhost
Server Port:            5044

Document Path:          /
Document Length:        16 bytes

Concurrency Level:      1
Time taken for tests:   0.001 seconds
Complete requests:      1
Failed requests:        0
Total transferred:      136 bytes
HTML transferred:       16 bytes
Requests per second:    677.05 [#/sec] (mean)
Time per request:       1.477 [ms] (mean)
Time per request:       1.477 [ms] (mean, across all concurrent requests)
Transfer rate:          89.92 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    0   0.0      0       0
Processing:     1    1   0.0      1       1
Waiting:        1    1   0.0      1       1
Total:          1    1   0.0      1       1

code:

from starlette.applications import Starlette
from starlette.responses import JSONResponse, Response

import uvicorn

app = Starlette()

@app.route('/')
async def index(request):
    return Response('string', headers={'Connection': 'close'})

if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=5044)

Should Router be a subclass of Starlette?

Right now, Router is simply an ASGI app, which means that it can be used as a top-level app; however, it can't benefit from Starlette-specific features such as debug, lifespan events or exception handlers.

Would it make sense to make Router a subclass of Starlette? That way instead of:

app = Starlette()
app.mount('/', Router((
    Path('product/{product_id}', app=ProductEndpoint),
    Path('product', app=NewProductEndpoint),
    Path('products', app=ProductListEndpoint),
)))

we can have

app = Router((
    Path('product/{product_id}', app=ProductEndpoint),
    Path('product', app=NewProductEndpoint),
    Path('products', app=ProductListEndpoint),
))

and keep all the advantages of Starlette.

This is just a simplified example, of course.

Middleware

  • Debug - Return proper debug pages.
  • StaticFiles - Static file serving.
  • Routing - Request routing.
  • Broadcast - Pub/Sub style broadcast.
  • StrictASGI - Ensure ASGI compliance.
  • Proxy - HTTP Proxying.
  • CSRF, CORS, Sessions.

Class Based Views

This relates to discussions in #17 - figured an issue better than that PR for working this out.

Routing app refinements

  • Include HEAD if GET is in methods.
  • Set Allow header on responses.
  • Respond to OPTIONS with 200 responses.

Add `UJSONResponse`

Should we consider dropping the implicit use of ujson, and instead include a UJSONResponse class so that it needs to be used explicitly instead? I'd tend to think that by default you'll want to prefer using the stdlib json since it's a bit tighter on it's behaviour when eg. handling unexpected types.

It might well make sense to only use ujson explicitly (so eg. users can switch to it if they have particular known endpoints that're already well-tested but have a non-insignificant serialization overhead).

(In micro-benchmarking ujson is super important, but in real use-cases it generally isn't.)

CORS support

Related to #1

I'm having trouble figuring out an appropriate pattern for including CORS support. My use-case is that I am connecting a React app to an ASGI backend that uses Starlette's routing and response classes, but enabling CORS support would require altering the response class headers which cannot be accomplished simply by wrapping the ASGI app at this point.

What should CORS support look like in Starlette?

An example of my use-case can be found here, it uses APIStar and a CORS extension to achieve what I want to do with Starlette as well. Below is my work-in-progress attempt at an ASGI CORS middleware that I would ultimately like to work into Starlette somehow.

from starlette import PlainTextResponse
from starlette.datastructures import Headers

ACCESS_CONTROL_ALLOW_ORIGIN = b"Access-Control-Allow-Origin"
ACCESS_CONTROL_EXPOSE_HEADERS = b"Access-Control-Expose-Headers"
ACCESS_CONTROL_ALLOW_CREDENTIALS = b"Access-Control-Allow-Credentials"
ACCESS_CONTROL_ALLOW_HEADERS = b"Access-Control-Allow-Headers"
ACCESS_CONTROL_ALLOW_METHODS = b"Access-Control-Allow-Methods"
ACCESS_CONTROL_MAX_AGE = b"Access-Control-Max-Age"
ACCESS_CONTROL_REQUEST_METHOD = b"Access-Control-Request-Method"
ACCESS_CONTROL_REQUEST_HEADERS = b"Access-Control-Request-Headers"


DEFAULT_OPTIONS = {
    "allow_origin": [b"*"],
    "allow_credentials": False,
    "allow_headers": [b"*"],
    "allow_methods": [
        b"GET",
        b"HEAD",
        b"POST",
        b"OPTIONS",
        b"PUT",
        b"PATCH",
        b"DELETE",
    ],
    "expose_headers": [b""],
    "max_age": b"86400",
}


class CORSMiddleware:
    def __init__(self, app, options=None):
        self.app = app
        self.options = options or DEFAULT_OPTIONS

    def __call__(self, scope):
        headers = Headers(scope.get("headers", []))
        origin = headers.get("origin", None)
        if origin is None:
            return self.app(scope)

        allow_origin = self.options["allow_origin"]
        if origin not in allow_origin and b"*" not in allow_origin:
            return PlainTextResponse("Origin not allowed", status_code=400)

        if scope["method"] == "OPTIONS":
            method = headers.get(ACCESS_CONTROL_REQUEST_METHOD, None)
            if method is None:
                return PlainTextResponse(
                    "Access-Control-Request-Method header missing", status_code=400
                )

        cors_headers = []
        if origin != "*" and len(allow_origin) > 1:  # todo double-check this
            cors_headers.append((b"vary", b"origin"))

        cors_allow_origin = b", ".join(self.options["allow_origin"])
        cors_allow_credentials = self.options["allow_credentials"]
        cors_allow_headers = b", ".join(self.options["allow_headers"])
        cors_allow_methods = b", ".join(self.options["allow_methods"])
        cors_expose_headers = b", ".join(self.options["expose_headers"])
        cors_max_age = self.options["max_age"]

        cors_headers.append((ACCESS_CONTROL_ALLOW_ORIGIN, cors_allow_origin))
        cors_headers.append((ACCESS_CONTROL_ALLOW_METHODS, cors_allow_methods))
        cors_headers.append((ACCESS_CONTROL_ALLOW_HEADERS, cors_allow_headers))
        cors_headers.append((ACCESS_CONTROL_ALLOW_CREDENTIALS, cors_allow_credentials))
        cors_headers.append((ACCESS_CONTROL_EXPOSE_HEADERS, cors_expose_headers))
        cors_headers.append((ACCESS_CONTROL_MAX_AGE, cors_max_age))

        scope["headers"].extend(cors_headers)
        print(scope["headers"])

        return self.app(scope)

Routing interfaces for `Router` and `Starlette` should match.

We should probably hide away Path and Include as implementation details for now, and instead focus on making the app and router interfaces match up.

Eg:

app = Router()

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

or...

app = Router()

app.mount('/admin', AdminApp)
app.mount('/articles', BlogArticles)

This'll mean that a Router instance strictly gives you a subset of the functionality that a Starlette instance offers.

Refs #122

`WebSocketEndpoint`

Provide a class-based websocket handler.

from starlette.endpoints import WebSocketEndpoint


class App(WebSocketEndpoint):
    async def on_connect(self, websocket, **kwargs):
        ...
    async def on_receive(self, websocket, data):
        ...
    async def on_disconnect(self, websocket):
        ...

Rising Responses

Is there any cons for inherit Response from Exception to be able to raise it?

request parsing with ujson

Since you are advertising the use of ujson, I was expecting that it is being used as a drop-in everywhere in starlette. When I post to a simple route handler without any body content, I see an exception coming from the standard library, however. Is this a special case when there is no body content or is ujson only used in serializing the JSONResponse?

Code:

@app.route('/', methods=["POST"], )
async def search(request: Request) -> JSONResponse:
    query = await request.json()
    return query

Traceback:

web_1  |     query = await request.json()
web_1  |   File "/usr/local/lib/python3.7/site-packages/starlette/requests.py", line 96, in json
web_1  |     self._json = json.loads(body)
web_1  |   File "/usr/local/lib/python3.7/json/__init__.py", line 348, in loads
web_1  |     return _default_decoder.decode(s)
web_1  |   File "/usr/local/lib/python3.7/json/decoder.py", line 337, in decode
web_1  |     obj, end = self.raw_decode(s, idx=_w(s, 0).end())
web_1  |   File "/usr/local/lib/python3.7/json/decoder.py", line 355, in raw_decode
web_1  |     raise JSONDecodeError("Expecting value", s, err.value) from None
web_1  | json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

Better support for HEAD method

  • Routing should automatically include 'HEAD' in the supported methods if 'GET' is there.
  • Static files should respond appropriately to 'HEAD' requests without sending the entire file.

Important

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

Some general questions

I'm not sure this is the best place to ask these, so feel free to point me somewhere else.

  1. I wonder what the recommended way is for request validation and response serialization, i.e., I'd love to use something like marshmallow but maybe you have a different recommendation.
  2. What is the recommended way to access the running event loop? asyncio.get_event_loop()? (I'm developing on Python 3.6.)
  3. What would you recommend for spawning a thread or process pool executor? Simply from concurrent.futures import ThreadPoolExecutor? Would it be possible to maintain such a pool on the app rather than spawning one each time a request is made (which seems like a terrible idea).

Version 1.0

Introduce App first in README / Docs intro.

  • Promote the app = App() style, but then go on to demonstrate plain ASGI style example, and discuss design philosophy of "everything is just ASGI" , interoperable, eg. use TestClient with Channels.

Naming

  • WebSocketSession as just WebSocket? Rename session variable as websocket in docs?
  • Drop StaticFile, and just leave StaticFiles and FileResponse.
  • Module naming - should it be starlette.requests/starlette.responses instead of starlette.request/starlette.response? Would fit websockets and align better with documentation titles.
  • App should perhaps be Starlette instead. Less likely to be confused with plain ASGI App class examples, and mirrors eg. flask.Flask and sanic.Sanic.
  • Class based views instead as HTTPEndpoint and WebSocketEndpoint?
  • Zero-dependencies

Ensure that requests is only required if TestClient is used. Ensure that aiofiles is only required if FileResponse/StaticFiles is used. We could choose to push FileResponse into the starlette.staticfiles module, and only ever import aiofiles from there.

Features

See https://github.com/encode/starlette/milestone/1

  • URL Reversing
  • Cookies
  • Form support
  • Class based views
  • Exception handling
  • Middleware
  • StartUp / Shutdown events
  • Background tasks

Later

  • Server Sent Events
  • HTTP/2 (and server push)

Important

  • We're using Polar.sh so you can upvote and help fund this issue.
  • We receive the funding once the issue is completed & confirmed by you.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

SessionMiddleware

Implement a session middleware that adds a mutable dict-like β€œsession” interface into the scope.

The interface should track if it has been mutated. Loading the session data given the incoming request cookies, and saving any modified sessions in outgoing response cookies should be handled by a SessionBackend, so that we separate out the session load/save mechanics from the session interface itself.

Application configuration

Are there any plans to include a way to attach objects which share an app's lifetime, for example database connections and similar? Or is that supposed to be handled my middleware somehow? Thanks!

Middleware documentation

  • Document ASGI middleware patterns.
  • Support setting middleware on App callable while still leaving the app instance visible.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.