falconry / falcon

The no-magic web data plane API and microservices framework for Python developers, with a focus on reliability, correctness, and performance at scale.
https://falcon.readthedocs.io/en/stable/
Apache License 2.0
9.53k stars 944 forks source link

Function Resources #1532

Open nZac opened 5 years ago

nZac commented 5 years ago

Right now Falcon requires you to use classes to define resource handlers, like this:

class MyResource:
    def on_get(self, req, resp):
        resp.body = 'hello!'
api.add_route('/', MyResource())

The community (#1455, #1499) has asked to remove the class requirement and enable direct function support.

def get_resource(req, resp):
    resp.body = 'hello!'

api.add_get_route('/', get_resource)
vytas7 commented 5 years ago

While it is always tempting to add new cool features, it would be interesting to hear which use cases we address by implementing it (I'm not implying it is necessarily a bad idea).

Alternatively we could start off by creating a recipe (or a Falcon add-on) illustrating how one can shim falcon.API (renamed to falcon.App in Falcon 3.0+) in case this functionality is important to have. I have actually created a proof-of-concept ~Gist -- removed as I was cleaning my Gists a bit~ illustrating that:

# Demonstrating how "function resources" (see https://github.com/falconry/falcon/issues/1532)
# can be shimmed in case anyone *really* needs that feature

import functools

import falcon

class FunctionResourcesAPI(falcon.API):

    def _add_method_route(self, uri_template, responder=None, **kwargs):
        class Shim:
            def on_request(self, req, resp, **kwargs):
                responder(req, resp, **kwargs)

        kwargs.pop('suffix', None)
        http_verb = kwargs.pop('http_verb')

        shim = Shim()
        setattr(shim, 'on_' + http_verb.lower(), shim.on_request)
        self.add_route(uri_template, shim, **kwargs)

for _http_verb in falcon.COMBINED_METHODS:
    setattr(
        FunctionResourcesAPI,
        'add_' + _http_verb.lower() + '_route',
        functools.partialmethod(FunctionResourcesAPI._add_method_route,
                                http_verb=_http_verb))

def say_hello(req, resp, name):
    resp.media = {'message': 'Hello, {}!'.format(name)}

api = FunctionResourcesAPI()
api.add_get_route('/hello/{name}', say_hello)
GET /hello/Developer HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: localhost:8000
User-Agent: HTTPie/0.9.9

HTTP/1.1 200 OK
Connection: close
Date: Mon, 28 Oct 2019 17:36:57 GMT
Server: gunicorn/19.9.0
content-length: 32
content-type: application/json

{
    "message": "Hello, Developer!"
}
kgriffs commented 5 years ago

Assigning this to 3.1 just so we don't forget to make a decision on it.

kgriffs commented 5 years ago

Adding to what @vytas7 said, we should also consider what patterns this will encourage/discourage and whether those are good things in the long run for the community.

vytas7 commented 5 years ago

Another reaction off the top of my head: when deciding on this, we might want to compare and contrast the deliverables to what already is available in the Falcon ecosystem. For instance, if the goal is to easily expose a bunch of arbitrary functions on HTTP, one can just plop Hug (https://github.com/hugapi/hug) decorators on them, and so on.