Parsing input parameters
The input parameters can take different shapes. When we talk about input parameters, we talk mainly about two kinds:
- String query parameters encoded into the URL. These are normally used for the GET requests, and look like the following:
http://test.com/some/path?param1=X¶m2=Y
They are part of the URL and will be stored in any log along the way. The parameters are encoded into their own format, called URL encoding (https://www.urlencoder.io/learn/). You've probably noticed that, for example, an empty space gets transformed to %20.
- Let's look at the body of the HTTP request. This is typically used in the POST and PUT requests. The specific format can be specified using the Content-Type header. By default, the Content-Type header is defined as application/x-www-form-urlencoded, which encodes it in URL encoding. In modern applications, this is replaced with application/json to encode them in JSON.
But there are two other places to pass parameters that can also be used:
- As a part of the URL: Things such as thought id are parameters. Try to follow RESTful principles and define your URLs as resources to avoid confusion. Query parameters are best left as optional.
- Headers: Normally, headers give information about metadata, such as the format of the request, the expected format, or authentication data. But they need to be treated as input parameters as well.
All of these elements are decoded automatically by Flask-RESTPlus, so we don't need to deal with encodings and low-level access.
Let's see how this works in our example. This code is extracted from the one in GitHub, and shortened to describe the parsing parameters:
authentication_parser = api_namespace.parser()
authentication_parser.add_argument('Authorization',
location='headers', type=str, help='Bearer Access
Token')
thought_parser = authentication_parser.copy()
thought_parser.add_argument('text', type=str, required=True, help='Text of the thought')
@api_namespace.route('/me/thoughts/')
class MeThoughtListCreate(Resource):
@api_namespace.expect(thought_parser)
def post(self):
args = thought_parser.parse_args()
username = authentication_header_parser(args['Authorization'])
text=args['text']
...
We define a parser in the following lines:
authentication_parser = api_namespace.parser()
authentication_parser.add_argument('Authorization',
location='headers', type=str, help='Bearer Access Token')
thought_parser = authentication_parser.copy()
thought_parser.add_argument('text', type=str, required=True, help='Text of the thought')
authentication_parser is inherited by thought_parser to extend the functionality and combine both. Each of the parameters is defined in terms of type and whether they are required or not. If a required parameter is missing or another element is incorrect, Flask-RESTPlus will raise a 400 BAD_REQUEST error, giving feedback about what went wrong.
Because we want to handle the authentication in a slightly different way, we label it as not required and allow it to use the default (as created for the framework) value of None. Note that we specify that the Authorization parameter should be in the headers.
The post method gets a decorator to show that it expects the thought_parser parameter, and we parse it with parse_args:
@api_namespace.route('/me/thoughts/')
class MeThoughtListCreate(Resource):
@api_namespace.expect(thought_parser)
def post(self):
args = thought_parser.parse_args()
...
Furthermore, args is now a dictionary with all the parameters properly parsed and used in the next lines.
In the particular case of the authentication header, there's a specific function to work with that, and it return a 401 UNAUTHORIZED status code through the usage of abort. This call immediately stops a request:
def authentication_header_parser(value):
username = validate_token_header(value, config.PUBLIC_KEY)
if username is None:
abort(401)
return username
class MeThoughtListCreate(Resource):
@api_namespace.expect(thought_parser)
def post(self):
args = thought_parser.parse_args()
username = authentication_header_parser(args['Authentication'])
...
We will leave aside for a moment the action to be performed (storing a new thought in the database), and focus on the other framework configuration, to serialize the result into a JSON object.