mirror of
https://github.com/fastapi/fastapi.git
synced 2025-12-27 08:10:57 -05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
033bc2a6c9 | ||
|
|
28d3b9f783 | ||
|
|
0c55553328 | ||
|
|
b66056aa34 | ||
|
|
4f10b8b98d | ||
|
|
06eb421934 |
@@ -193,7 +193,7 @@ But then <a href="https://letsencrypt.org/" target="_blank">Let's Encrypt</a> wa
|
||||
|
||||
It is a project from the Linux Foundation. It provides HTTPS certificates for free. In an automated way. These certificates use all the standard cryptographic security, and are short lived (about 3 months), so, the security is actually increased, by reducing their lifespan.
|
||||
|
||||
The domain's are securely verified and the certificates are generated automatically. This also allows automatizing the renewal of these certificates.
|
||||
The domains are securely verified and the certificates are generated automatically. This also allows automatizing the renewal of these certificates.
|
||||
|
||||
The idea is to automatize the acquisition and renewal of these certificates, so that you can have secure HTTPS, free, forever.
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
## Latest changes
|
||||
|
||||
## 0.35.0
|
||||
|
||||
* Fix typo in routing `assert`. PR [#419](https://github.com/tiangolo/fastapi/pull/419) by [@pablogamboa](https://github.com/pablogamboa).
|
||||
* Fix typo in docs. PR [#411](https://github.com/tiangolo/fastapi/pull/411) by [@bronsen](https://github.com/bronsen).
|
||||
* Fix parsing a body type declared with `Union`. PR [#400](https://github.com/tiangolo/fastapi/pull/400) by [@koxudaxi](https://github.com/koxudaxi).
|
||||
|
||||
## 0.34.0
|
||||
|
||||
* Upgrade Starlette supported range to include the latest `0.12.7`. The new range is `0.11.1,<=0.12.7`. PR [#367](https://github.com/tiangolo/fastapi/pull/367) by [@dedsm](https://github.com/dedsm).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
|
||||
|
||||
__version__ = "0.34.0"
|
||||
__version__ = "0.35.0"
|
||||
|
||||
from starlette.background import BackgroundTasks
|
||||
|
||||
|
||||
@@ -131,12 +131,17 @@ def get_flat_dependant(dependant: Dependant) -> Dependant:
|
||||
|
||||
|
||||
def is_scalar_field(field: Field) -> bool:
|
||||
return (
|
||||
if not (
|
||||
field.shape == Shape.SINGLETON
|
||||
and not lenient_issubclass(field.type_, BaseModel)
|
||||
and not lenient_issubclass(field.type_, sequence_types + (dict,))
|
||||
and not isinstance(field.schema, params.Body)
|
||||
)
|
||||
):
|
||||
return False
|
||||
if field.sub_fields:
|
||||
if not all(is_scalar_field(f) for f in field.sub_fields):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_scalar_sequence_field(field: Field) -> bool:
|
||||
|
||||
@@ -147,7 +147,7 @@ def get_websocket_app(
|
||||
if errors:
|
||||
await websocket.close(code=WS_1008_POLICY_VIOLATION)
|
||||
raise WebSocketRequestValidationError(errors)
|
||||
assert dependant.call is not None, "dependant.call must me a function"
|
||||
assert dependant.call is not None, "dependant.call must be a function"
|
||||
await dependant.call(**values)
|
||||
|
||||
return app
|
||||
|
||||
124
tests/test_union_body.py
Normal file
124
tests/test_union_body.py
Normal file
@@ -0,0 +1,124 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class OtherItem(BaseModel):
|
||||
price: int
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
def save_union_body(item: Union[OtherItem, Item]):
|
||||
return {"item": item}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
item_openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Save Union Body",
|
||||
"operationId": "save_union_body_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Item",
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/OtherItem"},
|
||||
{"$ref": "#/components/schemas/Item"},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"OtherItem": {
|
||||
"title": "OtherItem",
|
||||
"required": ["price"],
|
||||
"type": "object",
|
||||
"properties": {"price": {"title": "Price", "type": "integer"}},
|
||||
},
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"type": "object",
|
||||
"properties": {"name": {"title": "Name", "type": "string"}},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_item_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == item_openapi_schema
|
||||
|
||||
|
||||
def test_post_other_item():
|
||||
response = client.post("/items/", json={"price": 100})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item": {"price": 100}}
|
||||
|
||||
|
||||
def test_post_item():
|
||||
response = client.post("/items/", json={"name": "Foo"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item": {"name": "Foo"}}
|
||||
140
tests/test_union_inherited_body.py
Normal file
140
tests/test_union_inherited_body.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import sys
|
||||
from typing import Optional, Union
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
from pydantic import BaseModel
|
||||
from starlette.testclient import TestClient
|
||||
|
||||
# In Python 3.6:
|
||||
# u = Union[ExtendedItem, Item] == __main__.Item
|
||||
|
||||
# But in Python 3.7:
|
||||
# u = Union[ExtendedItem, Item] == typing.Union[__main__.ExtendedItem, __main__.Item]
|
||||
skip_py36 = pytest.mark.skipif(sys.version_info < (3, 7), reason="skip python3.6")
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class Item(BaseModel):
|
||||
name: Optional[str] = None
|
||||
|
||||
|
||||
class ExtendedItem(Item):
|
||||
age: int
|
||||
|
||||
|
||||
@app.post("/items/")
|
||||
def save_union_different_body(item: Union[ExtendedItem, Item]):
|
||||
return {"item": item}
|
||||
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
inherited_item_openapi_schema = {
|
||||
"openapi": "3.0.2",
|
||||
"info": {"title": "Fast API", "version": "0.1.0"},
|
||||
"paths": {
|
||||
"/items/": {
|
||||
"post": {
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {"application/json": {"schema": {}}},
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"summary": "Save Union Different Body",
|
||||
"operationId": "save_union_different_body_items__post",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"title": "Item",
|
||||
"anyOf": [
|
||||
{"$ref": "#/components/schemas/ExtendedItem"},
|
||||
{"$ref": "#/components/schemas/Item"},
|
||||
],
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": True,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Item": {
|
||||
"title": "Item",
|
||||
"type": "object",
|
||||
"properties": {"name": {"title": "Name", "type": "string"}},
|
||||
},
|
||||
"ExtendedItem": {
|
||||
"title": "ExtendedItem",
|
||||
"required": ["age"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"title": "Name", "type": "string"},
|
||||
"age": {"title": "Age", "type": "integer"},
|
||||
},
|
||||
},
|
||||
"ValidationError": {
|
||||
"title": "ValidationError",
|
||||
"required": ["loc", "msg", "type"],
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"loc": {
|
||||
"title": "Location",
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
},
|
||||
"msg": {"title": "Message", "type": "string"},
|
||||
"type": {"title": "Error Type", "type": "string"},
|
||||
},
|
||||
},
|
||||
"HTTPValidationError": {
|
||||
"title": "HTTPValidationError",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"detail": {
|
||||
"title": "Detail",
|
||||
"type": "array",
|
||||
"items": {"$ref": "#/components/schemas/ValidationError"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@skip_py36
|
||||
def test_inherited_item_openapi_schema():
|
||||
response = client.get("/openapi.json")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == inherited_item_openapi_schema
|
||||
|
||||
|
||||
@skip_py36
|
||||
def test_post_extended_item():
|
||||
response = client.post("/items/", json={"name": "Foo", "age": 5})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item": {"name": "Foo", "age": 5}}
|
||||
|
||||
|
||||
@skip_py36
|
||||
def test_post_item():
|
||||
response = client.post("/items/", json={"name": "Foo"})
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"item": {"name": "Foo"}}
|
||||
Reference in New Issue
Block a user