Separate resource link Flask Restful API - python

I am learning Flask Restful API, while following some tutorials I came across an example
class Student(Resource):
def get(self):
return {student data}
def post(self, details):
return {data stored}
api.add_resource(Student,'/student')
here, looking at above example, we can use /student with GET,POST methods to retrieve and store data.
But I would like to have 2 different endpoints for retrieving and storing data, each.
for example
/student/get
which will call get() function of class Student, to retrieve records of all students, and
/student/post
which will call post() function of class Student, to store the sent/posted data.
Is it possible to have a single student class but call different methods referred by different endpoints.

Yes, it is possible to have a single Resource class but call different methods referred by different endpoints.
Scenario:
We have a Student class with get and post method.
We can use different route to execute the get and post method separately or combined.
E.g.:
Endpoint http://localhost:5000/students/get can only be used for get request of Student class.
Endpoint http://localhost:5000/students/post can only be used for post request of Student class.
Endpoint http://localhost:5000/students/ can be used for both get, and post request of Student class.
Solution:
To control the different endpoint requests in the resource class, we need to pass some keyword arguments to it.
We will use resource_class_kwargs option of add_resource method. Details of add_resource can be found in the official documentation
We will block any unwanted method call using abort method. We will return a HTTP status 405 with a response message Method not allowed for unwanted method calls in endpoints.
Code:
from flask import Flask
from flask_restful import Resource, Api, abort, reqparse
app = Flask(__name__)
api = Api(app)
parser = reqparse.RequestParser()
parser.add_argument('id', type=int, help='ID of the student')
parser.add_argument('name', type=str, help='Name of the student')
def abort_if_method_not_allowed():
abort(405, message="Method not allowed")
students = [{"id" : 1, "name": "Shovon"},
{"id" : 2, "name": "arsho"}]
class Student(Resource):
def __init__(self, **kwargs):
self.get_request_allowed = kwargs.get("get_request_allowed", False)
self.post_request_allowed = kwargs.get("post_request_allowed", False)
def get(self):
if not self.get_request_allowed:
abort_if_method_not_allowed()
return students
def post(self):
if not self.post_request_allowed:
abort_if_method_not_allowed()
student_arguments = parser.parse_args()
student = {'id': student_arguments['id'],
'name': student_arguments['name']}
students.append(student)
return student, 201
api.add_resource(Student, '/students', endpoint="student",
resource_class_kwargs={"get_request_allowed": True, "post_request_allowed": True})
api.add_resource(Student, '/students/get', endpoint="student_get",
resource_class_kwargs={"get_request_allowed": True})
api.add_resource(Student, '/students/post', endpoint="student_post",
resource_class_kwargs={"post_request_allowed": True})
Expected behaviors:
curl http://localhost:5000/students/get should call the get method of Student class.
curl http://localhost:5000/students/post -d "id=3" -d "name=Sho" -X POST -v should call the post method of Student class.
curl http://localhost:5000/students can call both of the methods of Student class.
Testing:
We will call our enlisted endpoints and test if the behavior is expected for each endpoints.
Output of get request in students/get using curl http://localhost:5000/students/get:
[
{
"id": 1,
"name": "Shovon"
},
{
"id": 2,
"name": "arsho"
}
]
Output of post request in students/post using curl http://localhost:5000/students/post -d "id=3" -d "name=Shody" -X POST -v:
{
"id": 3,
"name": "Shody"
}
Output of get request in students using curl http://localhost:5000/students:
[
{
"id": 1,
"name": "Shovon"
},
{
"id": 2,
"name": "arsho"
},
{
"id": 3,
"name": "Shody"
}
]
Output of post request in students using curl http://localhost:5000/students -d "id=4" -d "name=Ahmedur" -X POST -v:
{
"id": 4,
"name": "Ahmedur"
}
Output of post request in students/get using curl http://localhost:5000/students/get -d "id=5" -d "name=Rahman" -X POST -v:
{
"message": "Method not allowed"
}
Output of get request in students/post using curl http://localhost:5000/students/post:
{
"message": "Method not allowed"
}
References:
Official documentation of add_resource method

Related

How to get scrapyrt's POST meta data?

In scrapyrt's POST documentation we can pass a JSON request like this, but how do you access the meta data like category and item in start_requests?
{
"request": {
"meta": {
"category": "some category",
"item": {
"discovery_item_id": "999"
}
},
, "start_requests": true
},
"spider_name": "target.com_products"
}
Reference: https://scrapyrt.readthedocs.io/en/latest/api.html#id1
There is an unmerged PR in scrapyRT that adds support to pass extra parameters inthe POST request.
1) Patch resources.py file located in scrapyrt folder.
In my case was /usr/local/lib/python3.5/dist-packages/scrapyrt/resources.py
Replace with this code: https://github.com/gdelfresno/scrapyrt/commit/ee3be051ea647358a6bb297632d1ea277a6c02f8
2) Now your spider can access to the new parameters with self.param1
ScrapyRT curl example:
curl -XPOST -d '{
"spider_name":"quotes",
"start_requests": true,
"param1":"ok"}' "http://localhost:9080/crawl.json"
In your spider
def parse(self, response):
print(self.param1)
Regards

JSON Payload - With Date

I am exploring building an API to my application, as part of developer tool i can see the payload as below -
-X POST -H "Content-Type:application/json" -d '{ "action": "DeviceManagementRouter", "method": "addMaintWindow", "data": [{"uid": "/zport/dmd/Devices/Server/Microsoft/Windows/10.10.10.10", "durationDays":"1", "durationHours":"00", "durationMinutes":"00", "enabled":"True", "name":"Test", "repeat":"Never", "startDate":"08/15/2018", "startHours":"09", "startMinutes":"50", "startProductionState":"300" } ], "type": "rpc", "tid": 1}
I see below error -
{"uuid": "a74b6e27-c9af-402a-acd0-bd9c4254736e", "action": "DeviceManagementRouter", "result": {"msg": "TypeError: addMaintWindow() got an unexpected keyword argument 'startDate'", "type": "exception", "success": false}, "tid": 1, "type": "rpc", "method": "addMaintWindow"}
Code in below URL:
https://zenossapiclient.readthedocs.io/en/latest/_modules/zenossapi/routers/devicemanagement.html
Assuming this is your real python code, then if you want to pass multiple params in python, you should use either *args or **kwargs (keyworded arguments). For you, it seems kwargs is more appropriate.
def addMaintWindow(self, **kwargs):
"""
adds a new Maintenance Window
"""
_name = kwargs["name"]
# _name = kwargs.pop("name", default_value) to be fail-safe and
# it's more defensive because you popped off the argument
# so it won't be misused if you pass **kwargs to next function.
facade = self._getFacade()
facade.addMaintWindow(**kwargs)
return DirectResponse.succeed(msg="Maintenance Window %s added successfully." % (_name))
Here is a good answer about how to use it. A more general one is here.
Read the general one first if you are not familiar with them.
This should get you pass the error at this stage but you will need to do the same for your facade.addMaintWindow; if it not owned by you, make sure you pass in a correct number of named arguments.

Can't link new repositories to projects using Bitbucket's API

I'm building a shell application that allows my teammates to start new projects by running a few commands. It should be able to create a new project and a new repository inside that project.
Although I'm specifying the project key/uuid when creating a new repository, it doesn't work. What I'm expecting is a success message with the details for the new repository. Most of the time, this is what I get:
{"type": "error", "error": {"message": "string indices must be integers", "id": "ef4c2b1b49c74c7fbd557679a5dd0e58"}}
or the repository goes to the first project created for that team (which is the default behaviour when no project key/uuid is specified, according to Bitbucket's API documentation).
So I'm guessing there's something in between my request & their code receiving it? Because it looks like they're not even getting the request data.
# Setup Request Body
rb = {
"scm": "git",
"project": {
"key": "PROJECT_KEY_OR_UUID"
}
}
# Setup URL
url = "https://api.bitbucket.org/2.0/repositories/TEAM_NAME/REPOSITORY_NAME"
# Request
r = requests.post(url, data=rb)
In the code from the api docs you'll notice that the Content-Type header is "application/json".
$ curl -X POST -H "Content-Type: application/json" -d '{
"scm": "git",
"project": {
"key": "MARS"
}
}' https://api.bitbucket.org/2.0/repositories/teamsinspace/hablanding
In your code you're passing your data in the data parameter, which creates an "application/x-www-form-urlencoded" Content-Type header, and urlencodes your post data.
Instead, you should use the json parameter.
rb = {
"scm": "git",
"project": {
"key": "PROJECT_KEY_OR_UUID"
}
}
url = "https://api.bitbucket.org/2.0/repositories/TEAM_NAME/REPOSITORY_NAME"
r = requests.post(url, json=rb)

bundle_errors in flask_restplus is not returning the errors for the missing arguments

I implemented a very simple API using flask-restplus, however I ran into an issue when I tried to use bundle_errors=True with the reqparse.
For the following code
from flask import Flask
from flask_restplus import Api, Resource, reqparse
app = Flask(__name__)
api = Api(app)
parser = reqparse.RequestParser(bundle_errors=False)
parser.add_argument('username', type=list, required=True, help="Missing Username", location="json")
parser.add_argument('password', type=list, required=True, help="Missing Password", location="json")
#api.route('/user')
class User(Resource):
def post(self):
args = parser.parse_args()
return {"ID":"1", "Username": args['username'], "Password": args['password']}, 201
if __name__ == '__main__':
app.run(host="0.0.0.0", debug=True)
1- When bundle_errors=False and I send a request with missing parameters
curl -X POST \
http://localhost:5051/user
-H 'content-type: application/json'
-d '{}'
I get the following response
{
"errors": {
"username": "Missing Username"
},
"message": "Input payload validation failed"
}
Which is fine except that is showed only one missing field.
2- When I used bundle_errors=True (as mentioned in the documentation), I got the following result
{
"Username": "Missing required parameter in the JSON body",
"Password": "Missing required parameter in the JSON body",
"ID": "1"
}
Which means that RequestParser didn't throw any error and returned this string "Missing required parameter in the JSON body" as the actual input
Am I doing something wrong?
I'm using python version 3.5.2 and flask-restplus==0.10.1

Flask-Restful: Passing list into MongoDB via POST request

I'm building an API for a new web service using Python, Flask-Restful w/ pymongo.
A sample MongoDB document should look like this:
{ domain: 'foobar.com',
attributes: { web: [ akamai,
google-analytics,
drupal,
... ] } }
The imports:
from flask import Flask, jsonify
from flask.ext.restful import Api, Resource, reqparse
from pymongo import MongoClient
The class:
class AttributesAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('domain', type = str, required = True, help = 'No domain given', location='json')
self.reqparse.add_argument('web', type = str, action='append', required = True, help = 'No array/list of web stuff given', location = 'json')
super(AttributesAPI, self).__init__()
def post(self):
args = self.reqparse.parse_args()
post = db.core.update( {'domain': args['domain']},
{'$set':{'attr': { 'web': args['web'] }}},
upsert=True)
return post
When I CURL post, I use this:
curl -i -H "Content-Type: application/json" -X POST -d '{"domain":"foobar", "web":"akamai", "web":"drupal", "web":"google-analytics"}' http://localhost:5000/v1/attributes
However, this is what gets saved in my document:
{ "_id" : ObjectId("5313a9006759a3e0af4e548a"), "attr" : { "web" : [ "google-analytics" ] }, "domain" : "foobar.com"}
It only stores the last value given in the curl for 'web'. I also tried to use the CLI command with multiple -d params as described in the reqparse documentation but that throws a 400 - BAD REQUEST error.
Any ideas how why it is only saving the last value instead of all values as a list?
In JSON objects and in Python dictionaries, names are unique; you cannot repeat the web key here and expect it to work. Use one web key instead and make the value a list:
{"domain": "foobar", "web": ["akamai", "drupal", "google-analytics"]}
and it should be processed as such.
In addition to #Martin Pieters answer, you would need to set your location parameter on your self.reqparse.add_argument to a tuple of json and values and the store parameter is append
self.reqparse.add_argument('domain',store='append', type = str, required = True, help = 'No domain given', location=('json','values'))
`

Categories

Resources