I am using FastAPI to make get/post/put/del requests, which all work perfectly fine in the browser. I wanted to use Postman to do the exact same thing; however, I am running into an issue trying to do anything other than GET. Below is the error I am getting:
{
"detail": [
{
"loc": [
"body"
],
"msg": "field required",
"type": "value_error.missing"
}
]
}
422 Unprocessable Entity is the exact error.
Below is the code I am using:
from lib2to3.pytree import Base
from fastapi import FastAPI, Path, Query, HTTPException, status, File, Form
from typing import Optional, Dict, Type
from pydantic import BaseModel
import inspect
app = FastAPI()
class Item(BaseModel):
name: str
price: float
brand: Optional[str] = None
class UpdateItem(BaseModel):
name: Optional[str] = None
price: Optional[float] = None
brand: Optional[str] = None
inventory = {}
#app.get("/get-item/{item_id}")
def get_item(item_id: int = Path(None, description = "The ID of the item")):
if item_id not in inventory:
raise HTTPException(status_code = 404, detail = "Item ID not found")
return inventory[item_id]
#app.get("/get-by-name/")
def get_item(name: str = Query(None, title = "Name", description = "Test")):
for item_id in inventory:
if inventory[item_id].name == name:
return inventory[item_id]
# return {"Data": "Not found"}
raise HTTPException(status_code = 404, detail = "Item ID not found")
#app.post("/create-item/{item_id}")
def create_item(item_id: int, item: Item):
if item_id in inventory:
raise HTTPException(status_code = 400, detail = "Item ID already exists")
inventory[item_id] = item
print(type(item))
return inventory[item_id]
#app.put("/update-item/{item_id}")
def update_item(item_id: int, item: UpdateItem):
if item_id not in inventory:
# return {"Error": "Item does not exist"}
raise HTTPException(status_code = 404, detail = "Item ID not found")
if item.name != None:
inventory[item_id].name = item.name
if item.brand != None:
inventory[item_id].brand = item.brand
if item.price != None:
inventory[item_id].price = item.price
return inventory[item_id]
#app.delete("/delete-item/{item_id}")
def delete_item(item_id: int = Query(..., description="ID of item you want to delete", ge=0)):
if item_id not in inventory:
# return {"Error": "ID does not exist"}
raise HTTPException(status_code = 404, detail = "Item ID not found")
del inventory[item_id]
return {"Success": "Item deleted"}
I tried this possible solution with no luck: https://github.com/tiangolo/fastapi/issues/2387
Your endpoint expects Item as JSON (body) data, but the screenshot you provided shows that you are sending the required fields as Query parameters (using the Params tab in Postman); hence, the error that the body is missing. You should instead add your data to the body of your POST request in Postman. To do that, you should go to Body > raw, and select JSON from the dropdown list to indicate the format of your data. Your payload should look something like this:
{
"name": "foo",
"price": 1.50
}
See related answers here and here as well. In case you needed to pass the parameters of Item model as Query parameters, you should then use Depends(), as described in this answer (Method 2).
On postman you need to change headers, by default, the value of Content-Type is plain/text, change it to application/json.
View answer at
POST request response 422 error {'detail': [{'loc': ['body'], 'msg': 'value is not a valid dict', 'type': 'type_error.dict'}]}
Related
I'm new to python and I faced an error which I totally don't understand why occures.
In the Insomnia client REST API I'm creating item with POST method, and it works well, below it the code
#app.post('/item')
def create_item():
item_data = request.get_json()
if (
"price" not in item_data
or "store_id" not in item_data
or "name" not in item_data
):
abort(
400,
message="Bad request"
)
for item in items.values():
if (
item_data["name"] == item["name"]
and item_data["store_id"] == item["store_id"]
):
abort(400, message="Item already exist")
if item_data["store_id"] not in stores:
abort(404, message="Store not found")
if item_data["store_id"] not in stores:
abort(404, message="Store not found")
item_id = uuid.uuid4().hex
item = {**item_data, "id": item_id}
items["item_id"] = item
return item, 201
and here is the outcome of post method, created item with "id"
{
"id": "1c0deba2c86542e3bde3bcdb5da8adf8",
"name": "chair",
"price": 17,
"store_id": "e0de0e2641d0479c9801a32444861e06"
}
when I run GET method using "id" from above item putting it to the link I get error code 304
#app.get("/item/<string:item_id>")
def get_item(item_id):
try:
return items[item_id]
except KeyError:
abort(404, message="Item not found")
Can You please suggest what is wrong here ?
thanks
Try this:
#app.route('/item/<string:id>')
def get_item(id):
instance = YourMOdel.query.filter_by(id=id).first()
if instance:
return render_template('data.html', instance = instance)
return f"Employee with id ={id} Doenst exist"
from http.client import responses
from random import randrange
from tkinter.tix import STATUS
from typing import Optional
from urllib import response
from fastapi import Body, FastAPI, Response ,status, HTTPException
from pydantic import BaseModel
app= FastAPI()
class Post(BaseModel):
title: str
content: str
Published: bool = True
rating: Optional[int] = None
my_post = [{"title": "title of post 1", "content": "content of post 1", "id": 2},{"title": "title of post 2","content":"content of post 2", "id":3}]
def find_post(id):
for p in my_post:
if p["id"] == id:
return p
def find_index_post(id):
for i,p in enumerate(my_post):
if p["id"]== id:
return i
#app.get("/posts/{id}")
def get_posts(id: int , response: Response):
post= find_post(id)
if not post :
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail= f"post with id {id} not found bludd")
# response.status_code=status.HTTP_404_NOT_FOUND
# return {"message": f" post with id : {id} not found"}
return{"here is the post": post}
#app.delete("/posts/{id}", status_code= status.HTTP_204_NO_CONTENT)
def delete_post(id: int):
index= find_index_post(id)
if index == None:
raise HTTPException(status_code= status.HTTP_404_NOT_FOUND, detail= f"post with id {id} does not exist")
my_post.pop(index)
return Response(status_code= status.HTTP_204_NO_CONTENT)
#app.put("/posts/{id}")
def update_post(id: int , post: Post):
index = find_index_post(id)
if index == None :
raise HTTPException(status_code= status.HTTP_404_NOT_FOUND, detail= f"post with id {id} does not exist")
post_dict = my_post.dict()
post_dict["id"]= id
my_post[index]= post_dict
return {"message" : "updated post"}
Everything else works, but the put/update function at the end.
Literally coding along with a tutorial and have non-stop irritating issues.
Python console says: 422 Unprocessable Entity.
Postman says:
"detail":
"loc":
"body","msg": "field required",
"type": "value_error.missing"
The 422 unprocessable entity error tells exactly which part of your request doesn't match the expected format. In your case, it says that the body is missing. When using Pydantic models, you essentially declare a JSON object (or Python dict), and hence, your endpoint expects a request body with that object. Thus, the request you send must include a JSON payload matching the model. Below is an example using Python requests, but you could also test that through OpenAPI at http://127.0.0.1:8000/docs.
import requests
url = 'http://127.0.0.1:8000/posts/2'
payload = {"title": "string", "content": "string", "Published": True,"rating": 0}
resp = requests.put(url, json=payload)
print(resp.json())
Additionally, make sure in your endpoint that you get the Post object properly. That is, the line post_dict = my_post.dict() should be replaced with post_dict = post.dict().
I know there are a ton of these questions and I have spent hours going through them and trying to figure it out but I am not able to find the problem. My main.py looks like this:
import webapp2
from google.appengine.api import oauth
app = webapp2.WSGIApplication([
('/org', 'org.Organization'),
], debug=True)
app.router.add(webapp2.Route(r'/org/<id:[0-9]+><:/?>', 'org.Organization'))
app.router.add(webapp2.Route(r'/org/search', 'org.OrgSearch'))
app.router.add(webapp2.Route(r'/resources', 'resources.Resource'))
app.router.add(webapp2.Route(r'/resources/<rid:[0-9]+>/org/<oid:[0-9]+><:/?>', 'resources.ResourceOrgs'))
And the code for my get and post request looks like this:
import webapp2
from google.appengine.ext import ndb
import dbase
import json
class Organization(webapp2.RequestHandler):
def post(self):
if 'application/json' not in self.request.accept:
self.response.status = 406
self.response.status_message = "API only supports application/json MIME type"
return
new_org = dbase.Organization()
name = self.request.get('name', default_value=None)
phone = self.request.get('phone', default_value=None)
street = self.request.get('street', default_value=None)
city = self.request.get('city', default_value=None)
state = self.request.get('state', default_value=None)
if name:
new_org.name = name
else:
self.response.status = 400
self.response.status_message = "Invalid request, Name is required"
if phone:
new_org.phone = phone
if street:
new_org.street = street
if city:
new_org.city = city
else:
self.response.status = 400
self.response.status_message = "Invalid request, City is required"
if state:
new_org.state = state
else:
self.response.status = 400
self.response.status_message = "Invalid request, State is required"
key = new_org.put()
out = new_org.to_dict()
self.response.write(json.dumps(out))
return
def get(self, **kwargs):
if 'application/json' not in self.request.accept:
self.response.status = 406
self.response.status_message = "API only supports application/json MIME type"
if 'id' in kwargs:
out = ndb.Key(dbase.Organization, int(kwargs['id'])).get().to_dict()
self.response.write(json.dumps(out))
else:
q = dbase.Organization.query()
keys = q.fetch(keys_only=True)
results = { 'keys' : [x.id() for x in keys]}
self.response.write(json.dumps(results))
I hope somebody can help me because I cannot figure it out and I am running out of time. I am using notepad++ but I changed it so it is using spaces instead of tabs.
Try indenting your def get() and def post()
I have django project with 2 models:
class DeviceModel(models.Model):
name = models.CharField(max_length=255)
def __unicode__(self):
return self.name
class Device(models.Model):
created_at = models.DateTimeField(auto_now_add=True)
device_model = models.ForeignKey(DeviceModel)
serial_number = models.CharField(max_length=255)
def __unicode__(self):
return self.device_model.name + " - " + self.serial_number
There many devices in the database and I want to plot chart "amount of devices" per "device model".
I'm trying to do this task with django chartit.
Code in view.py:
ds = PivotDataPool(
series=[
{'options': {
'source':Device.objects.all(),
'categories':'device_model'
},
'terms':{
u'Amount':Count('device_model'),
}
}
],
)
pvcht = PivotChart(
datasource=ds,
series_options =
[{'options':{
'type': 'column',
'stacking': True
},
'terms':[
u'Amount']
}],
chart_options =
{'title': {
'text': u'Device amount chart'},
'xAxis': {
'title': {
'text': u'Device name'}},
'yAxis': {
'title': {
'text': u'Amount'}}}
)
return render(request, 'charts.html', {'my_chart': pvcht})
This seems to plot result I need, but instead of device names it plots values of ForeignKey (1,2,3,4...) and I need actual device model names.
I thought that solution is to change 'categories' value to:
'categories':'device_model__name'
But this gives me error:
'ManyToOneRel' object has no attribute 'parent_model'
This type of referencing should work accoring to official example http://chartit.shutupandship.com/demo/pivot/pivot-with-legend/
What am I missing here?
C:\Anaconda\lib\site-packages\django\core\handlers\base.py in get_response
response = middleware_method(request, callback, callback_args, callback_kwargs)
if response:
break
if response is None:
wrapped_callback = self.make_view_atomic(callback)
try:
response = wrapped_callback(request, *callback_args, **callback_kwargs) ###
except Exception as e:
# If the view raised an exception, run it through exception
# middleware, and if the exception middleware returns a
# response, use that. Otherwise, reraise the exception.
for middleware_method in self._exception_middleware:
response = middleware_method(request, e)
D:\django\predator\predator\views.py in charts
series=[
{'options': {
'source':Device.objects.all(),
'categories':'device_model__name'
},
#'legend_by': 'device_model__device_class'},
'terms':{
u'Amount':Count('device_model'), ###
}
}
],
#pareto_term = 'Amount'
)
C:\Anaconda\lib\site-packages\chartit\chartdata.py in __init__
'terms': {
'asia_avg_temp': Avg('temperature')}}]
# Save user input to a separate dict. Can be used for debugging.
self.user_input = locals()
self.user_input['series'] = copy.deepcopy(series)
self.series = clean_pdps(series) ###
self.top_n_term = (top_n_term if top_n_term
in self.series.keys() else None)
self.top_n = (top_n if (self.top_n_term is not None
and isinstance(top_n, int)) else 0)
self.pareto_term = (pareto_term if pareto_term in
self.series.keys() else None)
C:\Anaconda\lib\site-packages\chartit\validation.py in clean_pdps
def clean_pdps(series):
"""Clean the PivotDataPool series input from the user.
"""
if isinstance(series, list):
series = _convert_pdps_to_dict(series)
clean_pdps(series) ###
elif isinstance(series, dict):
if not series:
raise APIInputError("'series' cannot be empty.")
for td in series.values():
# td is not a dict
if not isinstance(td, dict):
C:\Anaconda\lib\site-packages\chartit\validation.py in clean_pdps
try:
_validate_func(td['func'])
except KeyError:
raise APIInputError("Missing 'func': %s" % td)
# categories
try:
td['categories'], fa_cat = _clean_categories(td['categories'],
td['source']) ###
except KeyError:
raise APIInputError("Missing 'categories': %s" % td)
# legend_by
try:
td['legend_by'], fa_lgby = _clean_legend_by(td['legend_by'],
td['source'])
C:\Anaconda\lib\site-packages\chartit\validation.py in _clean_categories
else:
raise APIInputError("'categories' must be one of the following "
"types: basestring, tuple or list. Got %s of "
"type %s instead."
%(categories, type(categories)))
field_aliases = {}
for c in categories:
field_aliases[c] = _validate_field_lookup_term(source.model, c) ###
return categories, field_aliases
def _clean_legend_by(legend_by, source):
if isinstance(legend_by, basestring):
legend_by = [legend_by]
elif isinstance(legend_by, (tuple, list)):
C:\Anaconda\lib\site-packages\chartit\validation.py in _validate_field_lookup_term
# and m2m is True for many-to-many relations.
# When 'direct' is False, 'field_object' is the corresponding
# RelatedObject for this field (since the field doesn't have
# an instance associated with it).
field_details = model._meta.get_field_by_name(terms[0])
# if the field is direct field
if field_details[2]:
m = field_details[0].related.parent_model ###
else:
m = field_details[0].model
return _validate_field_lookup_term(m, '__'.join(terms[1:]))
def _clean_source(source):
In categories you can only use fields that are included in source queryset.
On the other hand in terms you can use foreignKey or manyTomany connected fields.
Find an example below.
Instead of using
'source':Device.objects.all()
'categories':'device_model'
try to use
'source':DeviceModel.objects.all()
'categories':'name'
and next
'Amount':Count('device__device_model')
I think there is a problem with newer version of django (1.8).
This code is deprecated:
m = field_details[0].related.parent_model
instead of it use
m = field_details[0].getattr(field.related, 'related_model', field.related.model)
You can also find fix to this problem on GitHub.
Hope this helps.
I am trying to get my feet wet in API development. I am taking most of my notes from This article.
So far, I have no issues doing curl requests for GET, POST, or DELETE. PUT requests, though, are returning a 404 error.
Here is API code I am practicing with:
class UserAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('name', type = str, required = True, help = "No name provided", location = 'json')
self.reqparse.add_argument('email', type = str, required = True, help = "No email provided", location = 'json')
self.reqparse.add_argument('password', type = str, required = True, help = "No password provided", location = 'json')
super(UserAPI, self).__init__()
def get(self, id):
if checkUser(id): #Just checks to see if user with that id exists
info = getUserInfo(id) #Gets Users info based on id
return {'id': id, 'name': info[0], 'email':info[1], 'password': info[2], 'role': info[3]}
abort(404)
def put(self, id):
if checkUser(id):
args = self.reqparse.parse_args()
deleteUser(id) #Deletes user with this id
addUser(User(args['name'], args['email'], args['password'], args['role'])) #Adds user to database
abort(404)
def delete(self, id):
deleteUser(id)
return { 'result': True}
class UserListAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('name', type = str, required = True, help = "No name provided", location = 'json')
self.reqparse.add_argument('email', type = str, required = True, help = "No email provided", location = 'json')
self.reqparse.add_argument('password', type = str, required = True, help = "No password provided", location = 'json')
self.reqparse.add_argument('role', type = bool, default = 0, location = 'json')
super(UserListAPI, self).__init__()
def get(self):
return { 'users': map(lambda u: marshal(u, user_fields), getAllUsers()) }
def post(self):
print self.reqparse.parse_args()
args = self.reqparse.parse_args()
new_user = User(args['name'], args['email'], args['password'], args['role'])
addUser(new_user)
return {'user' : marshal(new_user, user_fields)}, 201
api.add_resource(UserAPI, '/api/user/<int:id>', endpoint = 'user')
api.add_resource(UserListAPI, '/api/users/', endpoint = 'users')
Basically, one class handles looking at all the users or adding a user to the DB (UserListAPI) and the other handles get individual users, updating a user, or deleting a user (UserAPI).
Like I said, everything by PUT works.
When I type curl -H 'Content-Type: application/json' -X PUT -d '{"name": "test2", "email":"test#test.com", "password":"testpass", "role": 0}' http://127.0.0.1:5000/api/user/2
I get the following error:
{
"message": "Not Found. You have requested this URI [/api/user/2] but did you mean /api/user/<int:id> or /api/users/ or /api/drinks/<int:id> ?",
"status": 404
}
Which doesn't make sense to me. Shouldn't the <int:id> accept the integer I put at the end of the URL?
Thanks for any thoughts
EDIT
Updating my answer after a dumb error on my part was pointed out. Now, the put method looks like this:
def put(self, id):
if checkUser(id):
args = self.reqparse.parse_args()
deleteUser(id)
user = User(args['name'], args['email'], args['password'], args['role'])
addUser(user)
return {'user' : marshal(user, user_fields)}, 201
else:
abort(404)
You are not returning when PUT succees, so is always aborting(404).
Like in the other HTTP verbs:
def put(self, id):
if checkUser(id):
args = self.reqparse.parse_args()
deleteUser(id) #Deletes user with this id
addUser(User(args['name'], args['email'], args['password'], args['role']))
# Missing return when succees
abort(404) # Always executing
EDIT: I have tested your example (with some extra code to make it work, like imports and removing specific code not implemented). This is my code:
from flask import Flask
from flask.ext.restful import Api, Resource
from flask.ext.restful import reqparse
app = Flask(__name__)
api = Api(app)
class UserAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('name', type = str, required = True, help = "No name provided", location = 'json')
self.reqparse.add_argument('email', type = str, required = True, help = "No email provided", location = 'json')
self.reqparse.add_argument('password', type = str, required = True, help = "No password provided", location = 'json')
super(UserAPI, self).__init__()
def get(self, id):
if True: #Just checks to see if user with that id exists
return {"message": "You have GET me"}
abort(404)
def put(self, id):
if True:
return {"message": "You have PUT me"}
abort(404)
def delete(self, id):
deleteUser(id)
return { 'result': True}
class UserListAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('name', type = str, required = True, help = "No name provided", location = 'json')
self.reqparse.add_argument('email', type = str, required = True, help = "No email provided", location = 'json')
self.reqparse.add_argument('password', type = str, required = True, help = "No password provided", location = 'json')
self.reqparse.add_argument('role', type = bool, default = 0, location = 'json')
super(UserListAPI, self).__init__()
def get(self):
return { 'users': map(lambda u: marshal(u, user_fields), getAllUsers()) }
def post(self):
print self.reqparse.parse_args()
args = self.reqparse.parse_args()
new_user = User(args['name'], args['email'], args['password'], args['role'])
addUser(new_user)
return {'user' : marshal(new_user, user_fields)}, 201
api.add_resource(UserAPI, '/api/user/<int:id>', endpoint = 'user')
api.add_resource(UserListAPI, '/api/users/', endpoint = 'users')
if __name__ == "__main__":
app.run(debug=True)
And now I CURL it:
amegian#amegian-Ubuntu:~$ curl -H 'Content-Type: application/json' -X PUT -d '{"name": "test2", "email":"test#test.com", "password":"testpass", "role": 0}' http://127.0.0.1:5000/api/user/2 -v
* About to connect() to 127.0.0.1 port 5000 (#0)
* Trying 127.0.0.1... connected
> PUT /api/user/2 HTTP/1.1
> User-Agent: curl/7.22.0 (i686-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3
> Host: 127.0.0.1:5000
> Accept: */*
> Content-Type: application/json
> Content-Length: 76
>
* upload completely sent off: 76out of 76 bytes
* HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Content-Type: application/json < Content-Length: 39 < Server: Werkzeug/0.8.3 Python/2.7.3 < Date: Wed, 04 Dec 2013 21:08:40 GMT < {
"message": "You have PUT me" }
* Closing connection #0
So I would encourage you to check those methods I have removed... like checkUser(id)