A request parameter checking and parsing library

A request parameter checking and parsing library

Hi, have you used sanic-pydantic? It is a pydantic extension plugin for sanic

Every time I process request parameters, I must use the get method to get them out at first, and then check the validity of each request parameter.

I always thinking is there any good way to do parameter type checking for me?

As predicted I found python-sanicargs and sanic-pydantic When I browsed the community and awsome-sanic projects.

All of them are request parameter check plugins and I used it in a long time.

But, are they really friendly and easy to use ? I don’t think so.

python-sanicargs is very simple, but I must give all parameters as formal parameters for my function and tell it their types. It is enough for daily simple parameter type checking. But it has no way to complete more complex parameter checking.

@app.route("/me/<id>/birthdate", methods=['GET'])
@parse_query_args
async def test_datetime(req, id: str, birthdate: datetime.datetime):
    return response.json({'id': id, 'birthdate': birthdate.isoformat()})

If you want check more complex paramters, you need use sanic-pydantic. It supports custom pydantic classes and performs parameter checking.

At one time, I thought I had found the best parameter checking tool, But I found that I couldn’t install it with pipy !

What? What the fuck? all right, I can install by git. but when I installed it, I found that it doesn’t support class-based views ! oh~ dear, I worked hard, only to find that it can’t be used in class-based views.

Well, since none of them meet my requirements, I’ll have to make a tool myself.

So I created the sanic-dantic library, It is based on pydantic, which can facilitate developers to quickly check and obtain request parameters.

In here, you needn’t give all parameters as formal parameters and it supports class-based views. And the most important point is that it can quickly get the request parameters in different request methods from the same place.

Are you a little excited? please come and have a look at here

Finally, thanks to the author Eric Jolibois of pydantic and the author Ahmed Nafies of sanic-pydantic, Because of their open source, I have the inspiration to write sanic-dantic. Thank them very much

P.S. Send out a simple useage

from sanic import Sanic
from sanic.response import text

from sanic_dantic import BaseModel, parse_params

app = Sanic("SimpleExample")


class Person(BaseModel):
    name: str
    age: int


class Car(BaseModel):
    speed: int


@app.route('/example')
@parse_params(query=Car, body=Person)
async def example(request, params):
    return text(f"{params.name} is {params.age} years old, " +
                f" his speed is {params.speed}")


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

1 Like

This looks great, thanks for sharing. I had been thinking of doing something similar myself, so I am happy to see it. Here are some thoughts and questions:

  • How does it play with type annotating the handler?
  • In CBV, instead of handler wrapping, can you pass it to decorators?
  • Does it support both JSON and form data in the body?

Hi, @ahopkins,

At first, thank you for your support. I was thinking about the question you raised recently

1. How does it play with type annotating the handler?

In sanic-dantic , I referenced pydantic and imported all its packages. I just performed a forward import of these libraries, and the final type check is the pydantic class. I just made a reference to it in the decorator.

2.In CBV, instead of handler wrapping, can I pass it to decorators?

I checked the source code of HTTPMethodView , if I want the decorator to give the decorator attribute of HTTPMethodView , then I need to pass it before entering the view function. But my decorator runs after the as_view method.

Maybe I should change this to a class decorator, but if I do that, why don’t I inherit HTTPMethodView and redefine a HTTPDanticMethodView ?

3.Does it support both JSON and form data in the body?

When you asked me this question, I really didn’t consider form, because I didn’t use form for submission at ordinary times, so I didn’t consider here. Now I have updated the file and added support for form. Because both form and json use body and their formats are different, I added a check, which does not allow both form and body to be used at the same time

4. What will I do next?

Okay, I know that my code still has a lot to improve. For example, add a class inherited from HTTPMethodView, further study the source code of sanic, and optimize the code of the decorator based on class-based views. I will continue to maintain this library, if you have any good suggestions, you can put them forward, and I will continue to follow up

Awesome work. I will try it out and let you know if I have any more thoughts. The easier the implementation, I think the better. Wrapping the class methods I think is easier than forcing users to use a subclass of HTTPMethodView. Or, maybe you provide both as options?

Hey, sanic-dantic is updated, and this time I support it pass to class decorators and I redefine a class DanticView who inherits from HTTPMethodView. Now you can update it like this :slight_smile:

pip install sanic-dantic==1.1.2

I’m using mkdocs for material to set up document pages and rewrite documents,

but I find that I can’t use the domain name of sanic-dantic.github.io.
I find that the domain names of your documents are all xxx.readthedoc.io or other custom domain names.
My website can only be https://miss85246.github.io/sanic-dantic/.
How did you successfully use the domain name of readthedoc.io? Can you tell me?

Later, when I build the document, I will publish it


2020-11-27 16:00
Now I’m already rewritten the documentation and deployed it now.
You can access this page to read -> sanic-dantic
If you have any question or suggestion, Please tell me, I’m very glad to communicate with you :wink:

I don’t think RTD is compatible with mkdocs.

https://docs.readthedocs.io/en/stable/intro/getting-started-with-sphinx.html

@ahopkins Well, it doesn’t matter. It’s enough to use it for the time being.
I’m trying to make DanticView’s method model support async. I need nest_asyncio, but it is not compatible with uvloop. do you have any good ways to make dispatch_request perform async tasks ?

    def dispatch_request(self, request, *args, **kwargs):
        method = request.method.lower()
        handler = getattr(self, method, None)
        model_handler = getattr(self, f'{method}_model', None)
        if model_handler:
        # I need to make a judgment here. 
        # If the user gives an async function, 
        # I need to execute it by asyncio
            if asyncio.iscoroutinefunction(model_handler):
                loop = asyncio.get_event_loop()
                model_obj = loop.run_until_complete(model_handler())
            else:
                model_obj = model_handler()
            parsed_args = validate(request, **model_obj.items)
            kwargs.update({"params": parsed_args})
            request.ctx.params = parsed_args
        return handler(request, *args, **kwargs)

I would not suggest using that lib. Well, it’s fine to use it, but to make another lib dependent upon it will make yours very narrowing in scope.

Indeed, but as you said, users cannot be forced to use a fixed way to define functions, so I want to support both asynchronous functions and ordinary functions.

If I can’t execute async functions in dispatch_request, It’s will only support ordinary functions…

I don’t want to use nest_asyncio to execute async functions, so, do you have any good way? Or we really do not support the use of async functions for definition

I will change this first to ensure compatibility with uvloop

I cannot tell you as a maintainer of the project what you should do in your lib. You need to decide that. Sanic does indeed support functional handlers in addition to awaitables. But it does it fairly simply:

# Run response handler
response = handler(request, *args, **kwargs)
if isawaitable(response):
    response = await response