Code Monkey home page Code Monkey logo

responder's Introduction

Responder: a familiar HTTP Service Framework for Python

Build Status Documentation Status image image image image

Powered by Starlette. That async declaration is optional. View documentation.

This gets you a ASGI app, with a production static files server pre-installed, jinja2 templating (without additional imports), and a production webserver based on uvloop, serving up requests with gzip compression automatically.

Testimonials

"Pleasantly very taken with python-responder. @kennethreitz at his absolute best." —Rudraksh M.K.

"ASGI is going to enable all sorts of new high-performance web services. It's awesome to see Responder starting to take advantage of that." — Tom Christie author of Django REST Framework

"I love that you are exploring new patterns. Go go go!" — Danny Greenfield, author of Two Scoops of Django

More Examples

See the documentation's feature tour for more details on features available in Responder.

Installing Responder

Install the stable release:

$ pipenv install responder
✨🍰✨

Or, install from the development branch:

$ pipenv install -e git+https://github.com/taoufik07/responder.git#egg=responder

Only Python 3.6+ is supported.

The Basic Idea

The primary concept here is to bring the niceties that are brought forth from both Flask and Falcon and unify them into a single framework, along with some new ideas I have. I also wanted to take some of the API primitives that are instilled in the Requests library and put them into a web framework. So, you'll find a lot of parallels here with Requests.

  • Setting resp.content sends back bytes.
  • Setting resp.text sends back unicode, while setting resp.html sends back HTML.
  • Setting resp.media sends back JSON/YAML (.text/.html/.content override this).
  • Case-insensitive req.headers dict (from Requests directly).
  • resp.status_code, req.method, req.url, and other familiar friends.

Ideas

  • Flask-style route expression, with new capabilities -- all while using Python 3.6+'s new f-string syntax.
  • I love Falcon's "every request and response is passed into to each view and mutated" methodology, especially response.media, and have used it here. In addition to supporting JSON, I have decided to support YAML as well, as Kubernetes is slowly taking over the world, and it uses YAML for all the things. Content-negotiation and all that.
  • A built in testing client that uses the actual Requests you know and love.
  • The ability to mount other WSGI apps easily.
  • Automatic gzipped-responses.
  • In addition to Falcon's on_get, on_post, etc methods, Responder features an on_request method, which gets called on every type of request, much like Requests.
  • A production static file server is built-in.
  • Uvicorn built-in as a production web server. I would have chosen Gunicorn, but it doesn't run on Windows. Plus, Uvicorn serves well to protect against slowloris attacks, making nginx unnecessary in production.
  • GraphQL support, via Graphene. The goal here is to have any GraphQL query exposable at any route, magically.
  • Provide an official way to run webpack.

responder's People

Contributors

aitoehigie avatar artemgordinskiy avatar barrust avatar bnmetrics avatar dependabot[bot] avatar frostming avatar ibnesayeed avatar javad94 avatar jayjeetatgithub avatar jordaneremieff avatar kennethreitz avatar kobayashi avatar majiang avatar metakermit avatar mlschneid avatar mmanhertz avatar parberge avatar pbsds avatar rparrapy avatar ryuuji avatar sheb avatar shyamjos avatar taoufik07 avatar timofurrer avatar tkamenoko avatar tomchristie avatar ucpr avatar valtyr avatar vlcinsky avatar waghanza 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

responder's Issues

Error in view not getting reported

import tempfile
import responder

api = responder.API()

@api.route("/bugtest")
async def test(req, res):
    with tempfile.NamedTemporaryFile() as f:
        # This write call will fail
        f.write('this should be bytes, not str')
    resp.content = 'Success!'

When I hit this view, I get a 200 response (no response body). I expect a 500 with a response of "Internal server error" (which I've gotten on other occasions).

About GraphQL support

Greetings!

First let me thank you for this awesome little framework. Such a nice API!

Today I've learned that responder now supports GraphQL by using Graphene. Which is very nice.

However, after using Graphene in a rather big project I am no longer a big fan of it. While it was awesome when we started out it but as the project grew it no longer worked for us:

  • There's a huge performance problem when returning bigger datasets
  • Its currently build on graphql-core which is not very pythonic - far from it. The code looks like a 1:1 translation from JavaScript (like implementing the Promise interface in Python)
  • When working with other implementations (like Apollo Server) it makes it very difficult to switch mentally between those implementations because Graphene shields you in different ways. This is fine until you read general tutorials about the concepts of GraphQL and then wonder how to implement this with Graphene
  • The documentation of Graphene is often very basic and complex problems are hard to figure out
  • The error handling of Graphene (or graphql-core) is often not very helpful or cryptic when things go wrong.

After facing heavy performance problems in our project we looked for alternatives. This was when we learned about graphlql-core-next which is a rewrite of graphql-core in a pythonic way and makes heavy use of async.io.

We archieved much better results in our first initial tests. The downside is that this library lacks proper integration in a web framework.

Personally I feel that responder would be the perfect missing link for graphql-core-next and I wonder how difficult it would be to implement some kind of integration.

POST data in CBV?

Hi all!
I make a POST request with some data.
How can i get post data in CBV?

@api.route("/test")
class GreetingResource:

    def on_request(self, req, resp): 
        resp.text = "hello, world!"
        resp.headers.update({'X-Life': '42'})
        resp.status_code = api.status_codes.HTTP_416

As i see req.content and req.media() is coroutines. But i can't use async here, because in responder/api.py we have

                try:
                    getattr(view, "on_request")(req, resp)
                except AttributeError:
                    pass

                # Then on_get.
                method = req.method.lower()

                try:
                    getattr(view, f"on_{method}")(req, resp)
                except AttributeError:
                    pass

Any suggestions?
Maybe add some options for responder/api.py with async for getting POST data?

JSON string encoding option on resp.media

I was able to really quickly develop a simple API using Responder and Records, and am generally loving life right now. However, I need to return a json object instead of a json string. When I run:

@api.route("/")
def hello(req, resp):
resp.media = {'myJSON': 'object'}

I get a JSON object back

@api.route("/top-10/{date}")
def top(req, resp,date):
    rows = db.query('''myquery ''', date=date)
resp.media = rows.export('json')

I get a JSON string

Are there any arguments I could pass to change the format?

TechEmpower benchmarks

This’d be a good one for a contributor to jump on.

Adding TechEmpower benchmarks for Responder. I’d suggest copying the starlette case, and adapting it accordingly. https://github.com/TechEmpower/FrameworkBenchmarks/tree/master/frameworks/Python/starlette

PR would be against the TechEmpower repo, not here. They run continuos benchmarks against the suite, so the performance section could be updated once there’s a run that includes Responder.

You’ll want to use an async DB connector, as per the starlette case.

Error with background task

@api.route("/")
def hello(req, resp):

    @api.background.task
    def sleep(s=10):
        time.sleep(s)
        print("slept!")

    def callback_func():
        print("callback")

    api.background.callback(callback_func)
    sleep()
    resp.content = "processing"

Above code raises AttributeError: 'NoneType' object has no attribute 'add_done_callback' in v0.0.3 with Python 3.6.5 on macOS.
I think BackgroundQueue.run should return Future object.

def run(self, f, *args, **kwargs):
self.pool._max_workers = self.n
self.pool._adjust_thread_count()
f = self.pool.submit(f, *args, **kwargs)
self.results.append(f)

Cannot pass variable as {{ name }} in jinja template using api.template function

Problem
Passing variable named name to jinja template using the api.template function's keyword argument returns unexpected behaviors. i.e. The below implementation does not work:

@api.route("/{var}")
async def func(req, resp, *, var):
    resp.content = api.template("page.html", name=var) # using `name` for the keyword to pass variable `name` in jinja template

+

<p>Hello {{ name }}!</p>

I think variable names such as name is very often used, so it may cause errors to others in the future as well.


Cause
Keyword argument for name was a reserved keyword in the api.template function.

def template(self, name, auto_escape=True, **values):


Possible solutions
There either could be a disclaimer in the docs about the reserved words, or maybe changing the implementation like

def template(self, name, auto_escape=True, values={}):

and unpacking the dictionary variables to update the Jinja template so any variable can pass? So the endpoint for html rendering looks like

@api.route("/{var}")
async def name_page(req, resp, *, var):
    resp.content = api.template("page.html", values={"name": var})

How do you think?

Full codes and processes to recreate the issue are here

Server file:

import responder

api = responder.API()

@api.route("/namepage/{var}")
async def name_page(req, resp, *, var):
    resp.content = api.template("page.html", name=var)

@api.route("/greetpage/{var}")
async def other_variable(req, resp, *, var): # using other variable naming
    resp.content = api.template("page.html", greeting=var)

if __name__ == "__main__":
    api.run()

Template html file:

<p>Hello!</p>
<p>name variable: {{ name }}</p>
<p>greeting variable: {{ greeting }}</p>

Running the server:

$ pipenv run python server.py

Testing the endpoint with the name variable returns null:

$ curl localhost:5042/namepage/kazuya
null$

When I use different name such as greeting for the variable it works.

$ curl localhost:5042/greetpage/howdy
<p>Hello!</p>
<p>name variable: </p>
<p>greeting variable: howdy</p>

By the way, I'm a big fan of the work, and thank you @kennethreitz for encouraging submission of issues for Hacktoberfest! I'm still learning how to code, but it pushed me to make a first ever submission of an issue to an open source project.

I cannot push branch to the repo

Hello! Super excited about this work. I seem to be unable to push to this repo and was wondering if others were having the same problem? I'd love to throw up a branch and contribute to this, hoping to start with some simple tests!

screen shot 2018-10-17 at 9 07 18 pm

ALT01919% git push -u origin Username for 'https://github.com': pyasi Password for 'https://[email protected]': remote: Permission to kennethreitz/responder.git denied to pyasi. fatal: unable to access 'https://github.com/kennethreitz/responder.git/': The requested URL returned error: 403

@api.background.task example throws error

Currently I'm not able to use the @api.background.task in the example. Stack trace is below, full app file information also available below - Python v 3.6.6

Serving on http://localhost:5000
ERROR:waitress:Exception when serving /
Traceback (most recent call last):
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/waitress/channel.py", line 338, in service
    task.service()
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/waitress/task.py", line 169, in service
    self.execute()
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/waitress/task.py", line 399, in execute
    app_iter = self.channel.server.application(env, start_response)
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/responder/api.py", line 99, in __call__
    return self.wsgi_app(environ, start_response)
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/responder/api.py", line 93, in wsgi_app
    return DispatcherMiddleware(main, apps)(environ, start_response)
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/werkzeug/wsgi.py", line 826, in __call__
    return app(environ, start_response)
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/responder/api.py", line 86, in _wsgi_app
    return self.whitenoise(environ, start_response)
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/whitenoise/base.py", line 75, in __call__
    return self.application(environ, start_response)
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/responder/api.py", line 82, in __wsgi_app
    resp = self._dispatch_request(req)
  File "/Users/mp/.local/share/virtualenvs/resp_test-wtUUHlHQ/lib/python3.6/site-packages/responder/api.py", line 125, in _dispatch_request
    self.routes[route].endpoint(req, resp, **params)
  File "main_app.py", line 14, in hello
    @api.background.task
AttributeError: 'API' object has no attribute 'background'

Full main_app.py file -

import responder
api = responder.API()
@api.route("/")
def hello(req, resp):
    @api.background.task
    def sleep(s=10):
        time.sleep(s)
        print("slept!")
    sleep()
    resp.content = "processing"

if __name__ == '__main__':
    api.run(port=5000)

Lean on Starlette.

Opening this issue first for discussion rather than just jumping in, because I want to understand where @kennethreitz would like to draw the boundaries on this.

There's currently quite a lot of duplication between Starlette and Responder. Starting with one class to consider there's responder.Response...

  • headers - Exposes a case-insensitive dict. Starlette already has a request.headers which is a case-insensitive multidict.
  • method - Exposes the request method, lowercased. Starlette already exposes this, uppercased, as request.method.
  • .full_url, .url - Exposes the str-URL and parsed URL. Starlette already exposes a str-like interface that also allows accessing parsed components on it. request.url, request.url.path, etc...
  • .params - Exposes a QueryDict. Starlette already exposes a query param multidict, as request.query_params.

(For reference here's Starlette's request docs... https://www.starlette.io/requests/)

Ideally we'd try to minimize reproducing too much stuff in differing interfaces. Or at least work towards having the data-structures be Starlette's low-level stuff, while Responder uses whatever high-level request interface it wants to stitch those together. (Ie. try to ensure that Starlette is to Responder as Werkzeug is to Flask.)

Initial thoughts?

Error when a method is passed as an endpoint

When a method is passed as an endpoint and we try to add is_routed to it, an attribute error is raised and it's passed it silently, So the route.is_function fails and that's what caused schemas to fail since the endpoint is a mehtod API.schema_response.

UPDATE

One of the solutions :

@property
def is_function(self):
    # We can also remove is_routed ?
    routed = hasattr(self.endpoint, "is_routed") or callable(self.endpoint)
    code = hasattr(self.endpoint, "__code__")
    kwdefaults = hasattr(self.endpoint, "__kwdefaults__")
    return all((routed, code, kwdefaults))

or :

try:
    if callable(endpoint):
        endpoint.__dict__["is_routed"] = True
except TypeError:
    pass

@kennethreitz What solution you prefer ?

Background task considerations.

There are likely some considerations around backlogs and process shutdown to think about here.

In Starlette, background tasks run within the request/response task. The response has been sent, and the server can continue to service other requests on the same connection, but it is also implicitly able to determine how many background tasks are running (response sent, but ASGI call not yet complete), and is able to effect graceful shutdown (for standard process restarts. Close connections, wait until background tasks are complete with a suitable timeout ceiling, then shutdown)

A global queue attached to the app is valid too, but ideally you’d want to 1. Ensure that the ASGI lifecycle shutdown waits for the queue to empty. 2. Consider how you want to deal with task backlogs.

Actions for now could be:

  1. Attach background tasks to responses instead, ala Starlette. Everything’ll be handled for you then.
  2. Don’t sweat the finer details for now, since it’s all alpha. We can come back to it.

Proposal: on_request should not be called when on_{method} is defined

Upon reviewing the source code, I noticed that on_request runs on each request even if on_{method} ( like on_get) is defined. I propose to change this so that only on_{method} is called and on_request is not called if on_{method} is defined.

I've already made this change on this fork: https://github.com/3lpsy/responder/tree/no-default-request-with-method

At first I thought this was unintentional; however, after reviewing the tests, it seems like this is intended behavior. I understand that the benefit of having on_request run on each request even if on_{method} is defined is useful for calling common functionality that runs on different methods; however, I think this is less intuitive.

I wanted to make this issue before creating a PR in case I do not fully understand the benefit of keeping this behavior.

On modularity & reusable ASGI components

So I've been following a general approach with the design of Starlette, that I'd love to see Responder follow on with. The aims are to keep complexity nicely bounded into smallish components, that then end up being reusable across different ASGI frameworks. Some examples:

  • Starlette's router implementation is, itself, just a plain old ASGI app - you can run it directly, or plug in the test client to it, or submount it or whatever. There's a little light syntactic sugar if you're adding routes on an application instance, but all the complexity is neatly partitioned.
  • Similarly Starlette's GraphQL implementation is another plain old ASGI app. You can use it in any ASGI framework, or just use it directly without the application instance. Responder could use it if it wanted. (It deals with either running in a threadpool, or using an ASync executor, which is important.)
  • Starlette's class based views. Again, they're just tiny ASGI apps - it's nice because it keeps the complexity nicely bounded away from the application instance, and again, you can use them with any ASGI framework, or use them directly. Being able to plug the test client directly in at that level of interface means you end up with really simple test cases for testing the core functionality.

So, none of this means that you can't at the top level have an application interface that exposes a single point of configuration, but under the hood it all ends up just being composition of small nicely bounded components and middleware.

Places where this could be applied to responder:

  • Pull the GraphQL implementation out of the app class itself, and have an standalone ASGI implementation, that the app instance uses composition to add in.
  • Design the Router so that it's exposing an ASGI interface. Have router instances that expose the same routing API as the top-level application class, but don't come with any of the other stuff that the application gives you. That'd then be independently usable/testable. You can do neat things like mount a router instance under another app, or wrap it up in middleware, etc...
  • Similarly with the class based views. Have the on_request(req/resp) interface stuff be implemented as a tiny ASGI app. It's independently usable/testable, you can apply middleware to individual views, you can reuse it in other frameworks, or you can use it as the basis for writing alternate class based view implementations (without the implementation all being tightly bound to the rest of the application class)

Possible name conflict

As much as I love the name responder for a web framework (and especially one from the author of requests), it appears that the name Responder.py is already taken by a security tool (see https://github.com/SpiderLabs/Responder), and a google search for python responder will yield that project as the top result.

This is meant just as a heads-up and nothing more. Personally, I would like it very much for this project to keep its name.

Probable bug in GraphQL JSON request handling

There seems to be a bug in parsing of JSON GraphQL requests.

Test case (disregard the fact that it would also fail if parsed successfully):

def test_graphql_schema_json_query(api, schema):
    api.add_route("/", schema)

    r = api.session().post("http://;/", headers={"Accept": "json", "Content-type": "json"}, data={'query': '{ hello }'})
    assert r.ok

Result on the latest master:

tests/test_responder.py:123:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
.env/lib/python3.7/site-packages/requests/sessions.py:559: in post
    return self.request('POST', url, data=data, json=json, **kwargs)
.env/lib/python3.7/site-packages/starlette/testclient.py:312: in request
    json=json,
.env/lib/python3.7/site-packages/requests/sessions.py:512: in request
    resp = self.send(prep, **send_kwargs)
.env/lib/python3.7/site-packages/requests/sessions.py:622: in send
    r = adapter.send(request, **kwargs)
.env/lib/python3.7/site-packages/starlette/testclient.py:159: in send
    raise exc from None
.env/lib/python3.7/site-packages/starlette/testclient.py:156: in send
    loop.run_until_complete(connection(receive, send))
/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/asyncio/base_events.py:568: in run_until_complete
    return future.result()
responder/api.py:71: in asgi
    resp = await self._dispatch_request(req)
responder/api.py:112: in _dispatch_request
    self.graphql_response(req, resp, schema=view)
responder/api.py:200: in graphql_response
    query = self._resolve_graphql_query(req)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

req = <responder.models.Request object at 0x105f20c18>

    @staticmethod
    def _resolve_graphql_query(req):
        if "json" in req.mimetype:
>           return req.json()["query"]
E           AttributeError: 'Request' object has no attribute 'json'

I tried fixing it myself, but got tangled in the web of async/await-s and ended up breaking other stuff 😅
I will try again later unless someone else wants to pick it up.

errors from background tasks are not printed

Given:

import responder
# import time <- notice missing import

api = responder.API()

@api.route("/")
def hello(req, resp):

    @api.background.task
    def sleep(s=1):
        time.sleep(s)
        print("slept!")

    sleep()
    resp.content = "processing"

if __name__ == '__main__':
    api.run()

When running sleep NameError is thrown but no output is shown in console.

Frontend website strategy

Really like the concepts inside responder! I'm wondering what would be the strategy for integrating frontend websites? I see webpack is mentioned in the README. Would something like https://parceljs.org/ also be interesting? Since the choice of frontend framework / tooling is pretty tangental to other things in responder, would it maybe make sense to just round up a few example folders or cookiecutters and link them up? E.g.

  • responder + webpack + react
  • responder + parcel + react
  • responder + angular

One thing that would make sense to standardise IMHO would be the build command in package.json. E.g. always have an npm run build command for building the frontend. That way a standard responder Heroku buildpack / Dockerfile could be used for deploying the webapp no matter which frontend stack is used.

CBV need to be initialized

I see that you've added self to CBV method in the docs, if we run it, it will cause an error because we need to init the class.

To fix this issue we need to fix #126.

PR in a bit #128.

Unable to reach subequently defined routes

I have thrown together a quick app, and I think I discovered a bug with memoization of the does_match method in routes. The behavior I was seeing was that only the first registered route would pass the 'does_match' function, even though the route had been registered, commenting out the @memoize decorator seemed to fix the issue. I'll submit a pull request, but feel free to disregard it if you know of a better way to fix it.

Trying to access anything other than the first written route in an app would yield a 'Not found' on the web page, because None was being returned.

import responder

api = responder.API()


@api.route('/route1')
def route1(req, res):
    res.text = 'route1'


@api.route('/route2')
def route2(req, res):
    res.text = 'route2'


if __name__ == '__main__':
    api.run()

optional dependencies

This project has 16 dependencies (as of 0.0.7), and many of those have their own dependencies. Installing responder in a virtual environment results in 32 libraries being installed. This also makes packaging responder more time consuming for Linux distros.

I'm sure each of those has their own reason for being there, but have you considered allowing a more minimal install? Setuptools has an extras_require argument where optional dependencies that enable extra functionality can be specified.

Proposal: Add typings / linter / static checker

As soon as more and more developers is going to contribute to the project it will be nice to have a way to make some issues more obvious. That is why I propose to add typings / linter / static checker to the project. Before working on that I want to discuss does it makes sense or not.

Real static files (.css, .jpg etc.) should still be served with static=True

OK, I've expanded my Responder + React example by adding the basic Responder server. We've made progress, the index.html page is correctly served on / (I get <!DOCTYPE html><html lang="en"> <head>… in the network reponse). There are some issues, though, so the app isn't still rendered.

While the static_response function correctly returns index.html when / or some "frontend path" like /about is requested, when an actual static file is requested (like a stylesheet, an image or a .js file) that file should be served.

Essentially all files with extensions should still be served as normal static files. Some examples:

So, to expand on my necessary order of precedence mentioned in #53 – Responder should do the following when static=True:

  1. check responder routes first (e.g. for some /api/users/ endpoints etc.)
  2. if there aren't any, check if a file looks like an actual static file and serve that file if it can be found (actual static files end in extensions like .js, .css, .jpg etc.)
  3. if it doesn't look like an actual static file, serve index.html
  4. let the frontend handle its routing (it is responsible for frontend routes, the 404 page etc.)

I'm cooking up a patch for this and testing directly that it works on my example.

Sync views should run in a threadpool

Responder will need to ensure that sync views get run within a threadpoolexecutor.

If we don't do this then user codebases are likely to use eg. synchronous database accesses or other network I/O that ends up blocking the current task.

Bit of an extra implementation work there, but it'll nicely ensure that both async def my_view and det my_view cases are being managed properly, without the user really needing to think about it.

(Same goes for the class based interface too)

GraphiQL integration

I've noticed some TODO-s mentioning GraphiQL in the code. Does it make sense to integrate it at this point, or is it too soon?

I would be up for taking a crack at it, if @kennethreitz gives me a thumbs up.

Specify version of marshmallow

According to marshmallow's chanegelog method dump of schema returns data only for versions >= 3.0.0b7, for older versions dumps returns MarshalResult.
By default, an older version of marshmallow was installed on my system.

➜  responder_python_test mkvirtualenv --python python3.7 test_marshmallow_default_version
(test_marshmallow_default_version) ➜  responder_python_test pip install responder
(test_marshmallow_default_version) ➜  responder_python_test pip freeze | grep marshmallow       
marshmallow==2.16.0

So this example is incorrect until you update the marshmallow manually.
Perhaps the best solution would be to fix the documentation.

Middleware

Supporting ASGI middleware would be a really good way to compartmentalise away bits of complexity, as we all promoting more of a cross-framework ecosystem.

I'd suggest an interface of app.add_middleware(cls, **options)

We could then do things like:

  • Move the GZip handling out of Response, and use Starlette's GzipMiddleware, which will also take care of handling streaming responses (once responder has those)
  • Be able to use Starlette's CORSMiddleware.

We can set up a default set of middleware if needed based on the configuration options presented to the API(...) class.

Any objections or considerations?

Request.content raises error

Minimal example:

@api.route('/')
async def index(req, resp):
    print(await req.content)
    resp.text = 'Hello world'

When visit index page, error pops:

raceback (most recent call last):
  File "/Users/fming/.local/share/virtualenvs/responder-l5YZbUOR/lib/python3.7/site-packages/uvicorn/protocols/http/h11_impl.py", line 394, in run_asgi
    result = await asgi(self.receive, self.send)
  File "/Users/fming/.local/share/virtualenvs/responder-l5YZbUOR/lib/python3.7/site-packages/uvicorn/middleware/message_logger.py", line 52, in __call__
    await inner(self.receive, self.send)
  File "/Users/fming/wkspace/github/responder/responder/api.py", line 83, in asgi
    resp = await self._dispatch_request(req)
  File "/Users/fming/wkspace/github/responder/responder/api.py", line 114, in _dispatch_request
    await result
  File "test_app.py", line 9, in index
    print(await req.content)
  File "/Users/fming/wkspace/github/responder/responder/models.py", line 133, in content
    return (await self._starlette.body()).encode(self.encoding)
AttributeError: 'bytes' object has no attribute 'encode'

And moreover, there is no encoding attribute in Request object.

Issue with multiple endpoints

Hi,
I implemented two simple endpoints:

import responder

api = responder.API()

@api.route("/api/async")
async def api_async_test1(req, resp):
    resp.text = "{'status': 'OK'}"

@api.route("/api/test1")
def api_test1(req, resp):
    resp.text = "{'status': 'OK'}"

@api.route("/api/test2")
def api_test2(req, resp):
    resp.text = "{'status': 'OK'}"

if __name__ == '__main__':
    api.run()

Then I test endpoint behaviour:

$ curl http://127.0.0.1:5042/api/async
{"status": "OK"}

$ curl http://127.0.0.1:5042/api/test1
Not found.

$ curl http://127.0.0.1:5042/api/test2
Not found.

I discovered that issue is related with more than one api.route decorator added. It appears that only first route is found. Changing order, P.E.: switching first with second, makes new first route works.

I'm not sure if I am doing something wrong.

Regards!

official way to run webpack!

In light of this "future work"

If frontend websites are supported, provide an official way to run webpack.

I would like to propose a merger of my project, react-pages which runs an ejected create-react-app from a python interface.

All insults are welcome!

GAE support

Any general thoughts on using this on App Engine?

API documentation generation

Having a built-in REST API documentation builder could be a nice-to-have feature, although I acknowledge it might be out of scope (maybe as some sort of add-on?).

Some may be familiar with documentation explorers like Swagger UI, which uses the OpenAPI spec. Is there any interest in making something like this a built-in feature?

Support startup, shutdown, and timer events.

ASGI includes a lifespan messaging channel, which provides startup and shutdown events.
This also means there's a managed context within which we can hook timer events.

Interfaces for this might look something like...

@app.on_event('startup')
async def startup():
    ...
@app.on_event('tick', seconds=10)
async def do_stuff():
    ...
@app.on_event('shutdown')
async def startup():
    ...

These would be a nice easy win that ASGI gives us which WSGI isn't able to.

Starlette currently supports startup and shutdown events, so that's a good first place to look for implementation... https://www.starlette.io/events/

ImportError: cannot import name 'yaml_utils'

Current master fails with an ImportError:


In [1]: import responder
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-1-8b8681b76e19> in <module>
----> 1 import responder

~/projects/python-projects/responder/responder/__init__.py in <module>
----> 1 from .core import *

~/projects/python-projects/responder/responder/core.py in <module>
----> 1 from .api import API
      2 from .models import Request, Response

~/projects/python-projects/responder/responder/api.py in <module>
     14 from apispec import APISpec
     15 from apispec.ext.marshmallow import MarshmallowPlugin
---> 16 from apispec import yaml_utils
     17
     18 from . import models

ImportError: cannot import name 'yaml_utils'

The yaml_utils module only exists in apispec 1.0.0b. I believe this should fix it:

diff --git a/setup.py b/setup.py
index b256a41..bb779c2 100644
--- a/setup.py
+++ b/setup.py
@@ -35,7 +35,7 @@ required = [
     "rfc3986",
     "python-multipart",
     "chardet",
-    "apispec",
+    "apispec>=1.0.0b1",
     "marshmallow",
 ]
 

I can probably send a PR later today, if you don't already have it fixed by then.

ORM integration

I would love to see some simple ORM integration. It would make responder great alternative to Django for hacking quick projects.

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.