400 Bad Request would now seem to be the best HTTP/1.1 status code for your use case.
At the time of your question (and my original answer), RFC 7231 was not a thing; at which point I objected to because RFC 2616 said (with emphasis mine):400 Bad Request
The request could not be understood by the server due to malformed syntax.
and the request you describe is syntactically valid JSON encased in syntactically valid HTTP, and thus the server has no issues with the syntax of the request.
However as pointed out by Lee Saferite in the comments, RFC 7231, which obsoletes RFC 2616, does not include that restriction:
The 400 (Bad Request) status code indicates that the server cannot or will not process the request due to something that is perceived to be a client error (e.g., malformed request syntax, invalid request message framing, or deceptive request routing).
However, prior to that re-wording (or if you want to quibble about RFC 7231 only being a proposed standard right now), does not seem an incorrect HTTP status code for your use case, because as the introduction to RFC 4918 says:422 Unprocessable Entity
While the status codes provided by HTTP/1.1 are sufficient to describe most error conditions encountered by WebDAV methods, there are some errors that do not fall neatly into the existing categories. This specification defines extra status codes developed for WebDAV methods (Section 11)
And the description of says:422
The 422 (Unprocessable Entity) status code means the server understands the content type of the request entity (hence a 415(Unsupported Media Type) status code is inappropriate), and the syntax of the request entity is correct (thus a 400 (Bad Request) status code is inappropriate) but was unable to process the contained instructions.
(Note the reference to syntax; I suspect 7231 partly obsoletes 4918 too)
This sounds exactly like your situation, but just in case there was any doubt, it goes on to say:
For example, this error condition may occur if an XML request body contains well-formed (i.e., syntactically correct), but semantically erroneous, XML instructions.
(Replace "XML" with "JSON" and I think we can agree that's your situation)
Now, some will object that RFC 4918 is about "HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)" and that you (presumably) are doing nothing involving WebDAV so shouldn't use things from it.
Given the choice between using an error code in the original standard that explicitly doesn't cover the situation, and one from an extension that describes the situation exactly, I would choose the latter.
Furthermore, RFC 4918 Section 21.4 refers to the IANA Hypertext Transfer Protocol (HTTP) Status Code Registry, where 422 can be found.
I propose that it is totally reasonable for an HTTP client or server to use any status code from that registry, so long as they do so correctly.
But as of HTTP/1.1, RFC 7231 has traction, so just use !400 Bad Request
Found the solution after debugging for quite a while.
The configured pedantic model can't be used to receive requests from the client.ORM
In my case, I had to create another class that extends the class add CodeCreate configuration to that class and use ORM for body from the client.CodeCreate
class BaseCode(BaseModel):
index: Optional[int] = Field(None)
email: EmailStr
gen_time: datetime
expire_time: datetime
class CodeCreate(BaseCode):
code: int
used_time: Optional[datetime] = Field(None)
class Code(CodeCreate):
class Config:
orm_mode = True
Post request
@app.post('/verify-code')
async def verify_code(code: schemas.CodeCreate):
return 'success'
try this, if it is not working, nothing will be working
var baseUri= @"http://localhost:554";
var api = "/api/..";
using HttpClient client = new HttpClient { BaseAddress = new Uri(baseUri) };
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var json = JsonConvert.SerializeObject(loanRequest);
var content = new StringContent(json, UTF8Encoding.UTF8, "application/json");
var response = await client.PostAsync(uri, content);
if (response.IsSuccessStatusCode)
{
var stringData = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<object>(stringData);
}
UPDATE
Json you posted has
"PropertyUsage": "INV",
but LoanRequestDTO doesn' t have this property. Since you are using an API Controller it automatically validates input and return an error immediatly, without trigering the action. I am wondering how this extra property could be created during serialization. Maybe you don't have some more properties ?
Let's start by explaining what you are doing wrong.
FastAPI's is just a re-export of Starlette's TestClient which is a subclass of requests.Session. The requests library's TestClient method has the following signature:post
def post(self, url, data=None, json=None, **kwargs):
r"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
Your code
response = client.post(
"/items/",
{"id": "baz", "title": "A test title", "description": "A test description"},
"Baz",
)
is doing multiple things wrong:
data and the json parameter, which is wrong because you can't have 2 different request bodies. You either pass in data or json, but not both. The data is typically used for form-encoded inputs from HTML forms, while json is for raw JSON objects. See the requests docs on "More complicated POST requests".json argument because:
Note, the
parameter is ignored if eitherjsonordatais passed.files
"Baz" to the json parameter, which is not a valid JSON object.data parameter expects form-encoded data.The full error returned by FastAPI in the response is:
def test_create_item():
response = client.post(
"/items/", {"id": "baz", "title": "A test title", "description": "A test description"}, "Baz"
)
print(response.status_code)
print(response.reason)
return response.json()
# 422 Unprocessable Entity
# {'detail': [{'loc': ['query', 'key'],
# 'msg': 'field required',
# 'type': 'value_error.missing'},
# {'loc': ['body'],
# 'msg': 'value is not a valid dict',
# 'type': 'type_error.dict'}]}
The 1st error says is missing from the query, meaning the route parameter key value key was not in the request body and FastAPI tried to look for it from the query parameters (see the FastAPI docs on Request body + path + query parameters)."Baz"
The 2nd error is from point #4 I listed above about not being properly form-encoded (that error does go away when you wrap the dict value in data, but that's not important nor is it part of the solution).json.dumps
You said in a comment that you were trying to do the same thing as in the FastAPI Testing Tutorial. The difference of that tutorial from your code is that was POSTing all the attributes of the object in 1 body, and that it was using the Item parameter of json=..post
Now on the solutions!
Here, you'll need 2 classes, one with a attribute that you use for the POST request body (let's call it key), and your current one NewItem for the internal DB and for the response model. Your route function will then have just 1 parameter (Item) and you can just get the new_item from that object.key
main.py
class Item(BaseModel):
id: str
title: str
description: Optional[str] = None
class NewItem(Item):
key: str
@app.post("/items/", response_model=Item)
async def create_item(new_item: NewItem):
# See Pydantic https://pydantic-docs.helpmanual.io/usage/exporting_models/#modeldict
# Also, Pydantic by default will ignore the extra attribute `key` when creating `Item`
item = Item(**new_item.dict())
print(item)
fake_db[new_item.key] = item
return item
For the test code, use .post to pass all the fields in 1 dictionary.json=
test_main.py
def test_create_item():
response = client.post(
"/items/",
json={
"key": "Baz",
"id": "baz",
"title": "A test title",
"description": "A test description",
},
)
print(response.status_code, response.reason)
return response.json()
Output
id='baz' title='A test title' description='A test description'
200 OK
{'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
You can structure the POSTed body like this instead:
{
"item": {
"id": "baz",
"title": "A test title",
"description": "A test description",
},
"key": "Baz",
},
where you have the attributes in a nested dict and then have a simple Item-value pair in the same level as key. FastAPI can handle this, see the docs on Singular values in body, which fits your example quite nicely:item
For example, extending the previous model, you could decide that you want to have another key
in the same body, besides theimportanceanditem.userIf you declare it as is, because it is a singular value, FastAPI will assume that it is a
parameter.queryBut you can instruct FastAPI to treat it as another body
usingkeyBody
Note the parts I emphasized, about telling FastAPI to look for in the same body. It is important here that the parameter names key and item match the ones in the request body.key
main.py
from fastapi import Body, FastAPI
class Item(BaseModel):
id: str
title: str
description: Optional[str] = None
@app.post("/items/", response_model=Item)
async def create_item(item: Item, key: str = Body(...)):
print(item)
fake_db[key] = item
return item
Again, for making the request, use .post to pass the entire dictionary.json=
test_main.py
def test_create_item():
response = client.post(
"/items/",
json={
"item": {
"id": "baz",
"title": "A test title",
"description": "A test description",
},
"key": "Baz",
},
)
print(response.status_code, response.reason)
return response.json()
Output
id='baz' title='A test title' description='A test description'
200 OK
{'description': 'A test description', 'id': 'baz', 'title': 'A test title'}
You are sending the update request to the JSON api, i.e. with <redmine_url>/projects/Testing/wiki/WikiTesting.json. Because of this, Redmine is unable to parse the PUTed payload since it doesn't know in what format the data is.Content-Type: application/octet-stream
To solve this, you should always make sure to set the correct content type when posting data. In this case, you should set the header to Content-Type when sending any JSON-formatted data to Redmine.application/json
Note that in principal, you can send XML data to Redmine and get JSON back. The output format is determined by the file ending in the URL ( or .json), the format of the data sent by you is always identified by the .xml header.Content-Type
Although you did not publish the error, who's purpose is to tell you the problem, I'm fairly sure the problem lies in the way you perform the request.
The line
xhr.setRequestHeader('Content-Type', 'application/json')
means that you are sending json data, which is not accepted by the authentication form of openapi. Also, you are stringifying the data into json which, again, is not an accepted format.
Thus, changing the content type to and adding a www-form-urlencoded object to your request's body, will make it work.FormData
You can see it in the github discussions below
https://github.com/tiangolo/fastapi/issues/2740https://github.com/tiangolo/fastapi/issues/1431