responder icon indicating copy to clipboard operation
responder copied to clipboard

Need more example code

Open drax2gma opened this issue 5 years ago • 20 comments

Hello,

is it possible to get more examples for using awesome responder? Maybe best practices for converting existing Flask often used method calls and alike. Thanks!

Mark

drax2gma avatar Nov 17 '18 17:11 drax2gma

I'd be interested in a example file-structure of a project using this framework

Serkan-devel avatar Nov 17 '18 18:11 Serkan-devel

Here's an example for how we are currently structuring a small service were I work (to be deployed as a docker container to kubernetes):

<project_root>/
├── service/
│   ├── apis/
│   │     ├── __init__.py
│   │     ├── <resource1>.py
│   │     └── <resource2>.py
│   ├── models/
│   │     ├── __init__.py
│   │     ├── <resource1>.py
│   │     └── <resource2>.py
│   └── app.py
├── test/
│   ├── __init__.py
│   ├── <resource1>.py
│   └── <resource2>.py
├── .env
├── .gitignore
├── Dockerfile
├── k8s-deployment.yaml
├── Pipfile
├── Pipfile.lock
└── README.md

The <resourceX>.py files in service/apis contain the class based views implementing the different GET, POST, PUT, etc... endpoints for the different resources the service is concerned with.

The corresponding files in service/models contain the marshmallow schemas to go with that.

In service/app.py this is then tied together like this:

import responder

from service.apis.resource1 import Resource1View
from service.apis.resource2 import Resource2View
from service.models.resource1 import Resource1Schema
from service.models.resource2 import Resource2Schema

api = responder.API(
    title="My Service",
    version="1.0",
    openapi="3.0.0",
    docs_route='/docs',
)

api.add_schema('Resource1', Resource1Schema)
api.add_schema('Resource2', Resource2Schema)
api.add_route('/route/to/resource1', Resource1View)
api.add_route('/route/to/resource2', Resource2View)

if __name__ == "__main__":
    api.run(port=5000, address='0.0.0.0')

This works for us. YMMV. Feel free to use this however you like. I'd also appreciate feedback, if you think this doesn't make sense or could be improved.

mmanhertz avatar Nov 19 '18 12:11 mmanhertz

Makes perfect sense to me!

kennethreitz avatar Nov 19 '18 15:11 kennethreitz

@mmanhertz thank you for the example. I might orientate from it on this project https://github.com/Serkan-devel/Nebelscheibe but there's nothing there yet

Serkan-devel avatar Nov 19 '18 17:11 Serkan-devel

@mmanhertz how would you import the schemas for the swagger documentation in the above example? When all the endpoints are in one views.py, adding a decorator before the route adds it to the swagger doc as well.

here0to0learn avatar Nov 20 '18 01:11 here0to0learn

@here0to0learn if you look at my example for service/app.py there are the lines

api.add_schema('Resource1', Resource1Schema)
api.add_schema('Resource2', Resource2Schema)

They are essentially using the decorator function, but instead of decorating the marshmallow schemas, they are passed in as arguments. The result is the same.

mmanhertz avatar Nov 20 '18 08:11 mmanhertz

Right. I tried your example with the following contents of respective files (Assuming the schema is correctly setup):

Content of apis.resource1

class Resource1View:
    def on_request(req, resp):
        resp.media({"hello": "world"})

Maybe i am missing something here, but this does not get picked up by the swagger docs. I wonder if the names matter and everything should match everywhere? I get the following result in the swagger page: No operations defined in spec! The schema model however, gets picked up fine and is displayed correctly at the bottom. The endpoints themselves are not picked up though.

When all the endpoints are in single views.py, the endpoint would look like following:\

class Resource1View:
    @app.route('/path/to/resource1') # this line adds the endpoint to swagger
    def on_request(req, resp):
        resp.media({"hello": "world"})

and this would let swagger pickup the endpoint to display properly.

here0to0learn avatar Nov 20 '18 08:11 here0to0learn

@here0to0learn I think you are missing the docstring, as in the example from the responder docs. Here's how we do it in our project:

class Resource1View:
    """An endpoint for resource 1
    ---
    get:
        description: Get a random pet
        responses:
            200:
                description: Success
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/Resource1'
    """
    def on_request(req, resp):
        resp.media(<Actually return a JSON dump of resource1 here>)

Any valid OpenAPI schema in the docstring will be added to the swagger UI as far as I can tell.

mmanhertz avatar Nov 20 '18 09:11 mmanhertz

Apologies for not adding that part. The schema mentioned in the the example and here is a response schema. I am talking about the request schema for the resource1 endpoint.

If you have all endpoints in views.py, i added the schema to a request like the following:

class Resource1View:
    """An endpoint for resource 1
    ---
    get:
        description: Get a random pet
        schema: Resource1Schema
        responses:
            200:
                description: Success
                content:
                    application/json:
                        schema:
                            $ref: '#/components/schemas/Resource1'
    """
    def on_request(req, resp):
        resp.media(<Actually return a JSON dump of resource1 here>)

However, with the proposed file structure, this does not seem to work. I tried to import the schema class in resource1view again or reference the already added schema with $ref = #/components/blah but neither solved the problem. I hope i am explaining the issue correctly.

here0to0learn avatar Nov 20 '18 09:11 here0to0learn

More over, the endpoints themselves registering with swagger docs does not have anything to do with the schema itself. The docstring should be enough to register an endpoint to the swagger docs (yaml file basically). E.g. the above content i posted in app.resource1 with the docstring should show up as an endpoint in swagger docs but doesn't.

here0to0learn avatar Nov 20 '18 09:11 here0to0learn

Strange, it does work flawlessly for us. The one difference I can see: we are actually using the on_get and on_post functions instead of on_request... don't know if that's relevant, though.

I will try to hack together a quick minimal working example repo tonight, if I have the time.

mmanhertz avatar Nov 20 '18 09:11 mmanhertz

Tried that as well. It did not work :(

here0to0learn avatar Nov 20 '18 20:11 here0to0learn

Can someone give an example how do we use on_get, on_post and on_put with responder?

I'm trying to use same route for 2 diff methods GET and POST, but I get following error (looks like I can not use same route for diff routes:


Traceback (most recent call last): File "api.py", line 23, in @api.route("/{anything}") File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/responder/api.py", line 502, in decorator self.add_route(route, f, **options) File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/responder/api.py", line 400, in add_route assert route not in self.routes AssertionError


Below is the sample code I'm trying:

@api.route("/{anything}") ==> intention is that GET request should go here def mock_service(req, res, *, anything): res.text = "GET request successfully executed"

@api.route("/{anything}") ==> intention is that POST request should go here async def on_post(self, req, resp, *, anything): data = await req.media() // do something with data

Saravana9210 avatar Dec 20 '18 11:12 Saravana9210

@Saravana9210

You need to put the methods in a class and put the route decorator on that class, something like this:

@api.route("/{anything}")
class AnyApi:
    def mock_service(req, res, *, anything):
        res.text = "GET request successfully executed"

    async def on_post(self, req, resp, *, anything):
        data = await req.media()
        // do something with data

PS: You can format your code examples nicely by enclosing them in triple back-ticks ```

mmanhertz avatar Dec 20 '18 12:12 mmanhertz

I'd like to see an example, where responder is used with jinja2-tamplates

Serkan-devel avatar Dec 28 '18 14:12 Serkan-devel

How does one inplement authentication without mounting a flask app as asked here ?

Serkan-devel avatar Dec 31 '18 14:12 Serkan-devel

@Serkan-devel Open to suggestions from the community here on recommended ways.

I think you have several options (note using an HTTPS connection when outside of an isolated development environment):

req.headers["authorization"] # reverse the base64 encoding and check against your system
  • HTTP Headers with custom parameters (username and password)
req.headers["username"]
req.headers["password"]

Personally, I'm using how on_request is called before all other on_{method}s and class based views to check headers (application type, credentials, etc.) for my use case. The general organization of the class is like the above comments and examples in the documentation. I'm inheriting from a base class that handles the header checks and authorization, so that all my defined routes execute the base classes on_request to handle authorization,etc.

@Saravana9210 (note the example linked in the next paragraph includes the custom params for authorization as stated above, but is another class based example. The difference between @mmanhertz 2nd example is that "on_request" is implemented as well (as that is called before all methods, if they are defined in the class).

An example of the custom parameter option and a base_class for an api service in one of my projects is here: base_service.py and inheriting class that overrides route level authorization and executes routes.

Notes:

  • the project is currently being developed, but the link is some example code to potentially answer your question (and have no dependency on Flask).
  • Flask is in the requirements.txt, but that is not a dependency for the linked example code. It is just a preliminary design choice that that I'm choosing to mount a Flask app in my project to have Responder just be an API service (to take advantage of some of the background task features

p.s. Four ~'s also works for formatting code blocks

iancleary avatar Feb 09 '19 17:02 iancleary

This thread has been very helpful in deciding how to architect apps at a larger scale! Thanks for the examples :)

taylorperkins avatar Mar 29 '19 02:03 taylorperkins

can someone please clarify for me the difference between the decorator @api.route() and api.add_route()... ive been working on splitting a project structure with the main in an app.py and routes located in service files. in order to initialize these existing @api.route() would i need to use api.add_route in the app.py?

thanks in advance.

update: i ended up going with an object oriented route via dependency injection and injected api as a constructor parameter to my classes.

tatianajiselle avatar Jul 30 '19 22:07 tatianajiselle

I just started working on Blueprint for Responder that is similar to Flask Blueprint.

The main

import responder

from bp1 import simple_page

api = responder.API(debug=True)
api.register_blueprint(simple_page, url_prefix='/pages')

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

The blueprint

from responder.blueprints import Blueprint

simple_page = Blueprint('simple_page', __name__, template_folder='templates')

@simple_page.route("/{greeting}")
async def greet_world(req, resp, *, greeting):
    resp.text = f"{greeting}, world!"

it works like this

$ curl http://127.0.0.1:5042/pages/hello
hello, world!

knoguchi avatar Sep 01 '19 16:09 knoguchi