Compare commits

..

12 Commits

Author SHA1 Message Date
Sebastián Ramírez
e6b3b994be 🔖 Release version 0.53.1 2020-03-29 22:08:54 +02:00
Sebastián Ramírez
67f148ff83 📝 Update release notes 2020-03-29 22:06:02 +02:00
Sebastián Ramírez
6c34600599 🐛 Fix include docs example file (#1182) 2020-03-29 22:05:24 +02:00
John Paton
016a4b7491 📝 Add documentation of example kwarg of Field (#1106)
* Add documentation of example kwarg of Field

* 📝 Update info about schema examples

* 🚚 Move example file to new directory

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-03-29 21:43:31 +02:00
Sebastián Ramírez
be21b74ad5 📝 Update release notes 2020-03-29 19:28:53 +02:00
voegtlel
0f152b4e97 🐛 Check already cloned fields in create_cloned_field to support recursive models (#1164)
* FIX: #894
Include recursion check for create_cloned_field.
Added test for recursive model.

* ♻️ Refactor and format create_cloned_field()

Co-authored-by: Lukas Voegtle <lukas.voegtle@sick.de>
Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-03-29 19:26:29 +02:00
Sebastián Ramírez
0d165d1efa 📝 Update release notes 2020-03-29 18:52:33 +02:00
YangQuan
9d54215a3a 📝 Add example of Pycharm in tutorial/debugging.md (#1096)
* add example of pycharm in tutorial/debugging.md

* 📝 Update PyCharm debug instructions and screenshot

* 🚚 Move image to new location in docs

Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
2020-03-29 18:50:29 +02:00
Sebastián Ramírez
8ab916baed 📝 Update release notes 2020-03-29 17:53:49 +02:00
Paul-Louis NECH
c83c50b27d ✏️ Fix typo (#1148) 2020-03-29 17:51:58 +02:00
Sebastián Ramírez
c2ad214a84 📝 Update release notes 2020-03-29 17:05:03 +02:00
Sebastián Ramírez
459f0e11e5 🏁 Update Windows development environment and tests (#1179)
* 🏁 Fix ./scripts/docs.py encoding for Windows

* 🔥 Remove ujson from tests as it prevents Windows development

It's still tested by Starlette anyway

* 📝 Update development instructions for Windows

* 🎨 Update format for WSGIMiddleware example

*  Update tests to run on Windows
2020-03-29 17:04:04 +02:00
16 changed files with 192 additions and 77 deletions

View File

@@ -12,7 +12,7 @@ Then wrap the WSGI (e.g. Flask) app with the middleware.
And then mount that under a path.
```Python hl_lines="1 3 22"
```Python hl_lines="2 3 22"
{!../../../docs_src/wsgi/tutorial001.py!}
```

View File

@@ -113,13 +113,25 @@ $ flit install --deps develop --symlink
</div>
If you are on Windows, use `--pth-file` instead of `--symlink`:
<div class="termy">
```console
$ flit install --deps develop --pth-file
---> 100%
```
</div>
It will install all the dependencies and your local FastAPI in your local environment.
#### Using your local FastAPI
If you create a Python file that imports and uses FastAPI, and run it with the Python from your local environment, it will use your local FastAPI source code.
And if you update that local FastAPI source code, as it is installed with `--symlink`, when you run that Python file again, it will use the fresh version of FastAPI you just edited.
And if you update that local FastAPI source code, as it is installed with `--symlink` (or `--pth-file` on Windows), when you run that Python file again, it will use the fresh version of FastAPI you just edited.
That way, you don't have to "install" your local version to be able to test every change.
@@ -137,17 +149,7 @@ $ bash scripts/format.sh
It will also auto-sort all your imports.
For it to sort them correctly, you need to have FastAPI installed locally in your environment, with the command in the section above:
<div class="termy">
```console
$ flit install --symlink
---> 100%
```
</div>
For it to sort them correctly, you need to have FastAPI installed locally in your environment, with the command in the section above using `--symlink` (or `--pth-file` on Windows).
### Format imports
@@ -293,7 +295,7 @@ $ python ./scripts/docs.py live es
Now you can go to <a href="http://127.0.0.1:8008" class="external-link" target="_blank">http://127.0.0.1:8008</a> and see your changes live.
If you look at the FastAPI docs website, you will see that every language has all the pages. But some are not translated and have a notification about the the translation is missing.
If you look at the FastAPI docs website, you will see that every language has all the pages. But some pages are not translated and have a notification about the missing translation.
But when you run it locally like this, you will only see the pages that are already translated.
@@ -474,10 +476,10 @@ This command generates a directory `./htmlcov/`, if you open the file `./htmlcov
### Tests in your editor
If you want to use the integrated tests in your editor add `./docs/src` to your `PYTHONPATH` variable.
If you want to use the integrated tests in your editor add `./docs_src` to your `PYTHONPATH` variable.
For example, in VS Code you can create a file `.env` with:
```env
PYTHONPATH=./docs/src
PYTHONPATH=./docs_src
```

View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -2,6 +2,15 @@
## Latest changes
## 0.53.1
* Fix included example after translations refactor. PR [#1182](https://github.com/tiangolo/fastapi/pull/1182).
* Add docs example for `example` in `Field`. Docs at [Body - Fields: JSON Schema extras](https://fastapi.tiangolo.com/tutorial/body-fields/#json-schema-extras). PR [#1106](https://github.com/tiangolo/fastapi/pull/1106) by [@JohnPaton](https://github.com/JohnPaton).
* Fix using recursive models in `response_model`. PR [#1164](https://github.com/tiangolo/fastapi/pull/1164) by [@voegtlel](https://github.com/voegtlel).
* Add docs for [Pycharm Debugging](https://fastapi.tiangolo.com/tutorial/debugging/). PR [#1096](https://github.com/tiangolo/fastapi/pull/1096) by [@youngquan](https://github.com/youngquan).
* Fix typo in docs. PR [#1148](https://github.com/tiangolo/fastapi/pull/1148) by [@PLNech](https://github.com/PLNech).
* Update Windows development environment instructions. PR [#1179](https://github.com/tiangolo/fastapi/pull/1179).
## 0.53.0
* Update test coverage badge. PR [#1175](https://github.com/tiangolo/fastapi/pull/1175).

View File

@@ -46,16 +46,27 @@ If you know JSON Schema and want to add extra information apart from what we hav
!!! warning
Have in mind that extra parameters passed won't add any validation, only annotation, for documentation purposes.
For example, you can use that functionality to pass a <a href="http://json-schema.org/latest/json-schema-validation.html#rfc.section.8.5" class="external-link" target="_blank">JSON Schema example</a> field to a body request JSON Schema:
For example, you can use that functionality to pass an <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">example</a> for a body request:
```Python hl_lines="20 21 22 23 24 25"
{!../../../docs_src/body_fields/tutorial002.py!}
```
And it would look in the `/docs` like this:
Alternately, you can provide these extras on a per-field basis by using additional keyword arguments to `Field`:
```Python hl_lines="2 8 9 10 11"
{!../../../docs_src/body_fields/tutorial003.py!}
```
Either way, in the `/docs` it would look like this:
<img src="/img/tutorial/body-fields/image01.png">
!!! note "Technical Details"
JSON Schema defines a field <a href="https://json-schema.org/draft/2019-09/json-schema-validation.html#rfc.section.9.5" class="external-link" target="_blank">`examples`</a> in the most recent versions, but OpenAPI is based on an older version of JSON Schema that didn't have `examples`.
So, OpenAPI defines its own <a href="https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#fixed-fields-20" class="external-link" target="_blank">`example`</a> for the same purpose (as `example`, not `examples`), and that's what is used by the docs UI (using Swagger UI).
## Recap
You can use Pydantic's `Field` to declare extra validations and metadata for model attributes.

View File

@@ -95,3 +95,18 @@ It will then start the server with your **FastAPI** code, stop at your breakpoin
Here's how it might look:
<img src="/img/tutorial/debugging/image01.png">
---
If you use Pycharm, you can:
* Open the "Run" menu.
* Select the option "Debug...".
* Then a context menu shows up.
* Select the file to debug (in this case, `main.py`).
It will then start the server with your **FastAPI** code, stop at your breakpoints, etc.
Here's how it might look:
<img src="/img/tutorial/debugging/image02.png">

View File

@@ -54,7 +54,7 @@ You will see something like this:
<img src="/img/tutorial/security/image01.png">
!!! check "Authorize button!"
You already have a shinny new "Authorize" button.
You already have a shiny new "Authorize" button.
And your *path operation* has a little lock in the top-right corner that you can click.

View File

@@ -0,0 +1,17 @@
from fastapi import FastAPI
from pydantic import BaseModel, Field
app = FastAPI()
class Item(BaseModel):
name: str = Field(..., example="Foo")
description: str = Field(None, example="A very nice Item")
price: float = Field(..., example=35.4)
tax: float = Field(None, example=3.2)
@app.put("/items/{item_id}")
async def update_item(*, item_id: int, item: Item):
results = {"item_id": item_id, "item": item}
return results

View File

@@ -1,6 +1,6 @@
from flask import Flask, escape, request
from fastapi import FastAPI
from fastapi.middleware.wsgi import WSGIMiddleware
from flask import Flask, escape, request
flask_app = Flask(__name__)

View File

@@ -1,6 +1,6 @@
"""FastAPI framework, high performance, easy to learn, fast to code, ready for production"""
__version__ = "0.53.0"
__version__ = "0.53.1"
from starlette import status

View File

@@ -131,17 +131,26 @@ def create_response_field(
)
def create_cloned_field(field: ModelField) -> ModelField:
def create_cloned_field(
field: ModelField, *, cloned_types: Dict[Type[BaseModel], Type[BaseModel]] = None,
) -> ModelField:
# _cloned_types has already cloned types, to support recursive models
if cloned_types is None:
cloned_types = dict()
original_type = field.type_
if is_dataclass(original_type) and hasattr(original_type, "__pydantic_model__"):
original_type = original_type.__pydantic_model__ # type: ignore
use_type = original_type
if lenient_issubclass(original_type, BaseModel):
original_type = cast(Type[BaseModel], original_type)
use_type = create_model(original_type.__name__, __base__=original_type)
for f in original_type.__fields__.values():
use_type.__fields__[f.name] = create_cloned_field(f)
use_type = cloned_types.get(original_type)
if use_type is None:
use_type = create_model(original_type.__name__, __base__=original_type)
cloned_types[original_type] = use_type
for f in original_type.__fields__.values():
use_type.__fields__[f.name] = create_cloned_field(
f, cloned_types=cloned_types
)
new_field = create_response_field(name=field.name, type_=use_type)
new_field.has_alias = field.has_alias
new_field.alias = field.alias
@@ -157,10 +166,13 @@ def create_cloned_field(field: ModelField) -> ModelField:
new_field.validate_always = field.validate_always
if field.sub_fields:
new_field.sub_fields = [
create_cloned_field(sub_field) for sub_field in field.sub_fields
create_cloned_field(sub_field, cloned_types=cloned_types)
for sub_field in field.sub_fields
]
if field.key_field:
new_field.key_field = create_cloned_field(field.key_field)
new_field.key_field = create_cloned_field(
field.key_field, cloned_types=cloned_types
)
new_field.validators = field.validators
if PYDANTIC_1:
new_field.pre_validators = field.pre_validators

View File

@@ -58,7 +58,6 @@ test = [
"async_generator",
"python-multipart",
"aiofiles",
"ujson",
"flask"
]
doc = [

View File

@@ -53,7 +53,7 @@ def new_lang(lang: str = typer.Argument(..., callback=lang_callback)):
new_path.mkdir()
en_docs_path = Path("docs/en")
en_config_path: Path = en_docs_path / mkdocs_name
en_config: dict = mkdocs.utils.yaml_load(en_config_path.read_text())
en_config: dict = mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8"))
fastapi_url_base = "https://fastapi.tiangolo.com/"
new_config = {}
new_config["site_name"] = en_config["site_name"]
@@ -90,14 +90,16 @@ def new_lang(lang: str = typer.Argument(..., callback=lang_callback)):
extra_js.append(fastapi_url_base + js)
new_config["extra_javascript"] = extra_js
new_config_path: Path = Path(new_path) / mkdocs_name
new_config_path.write_text(yaml.dump(new_config, sort_keys=False, width=200))
new_config_path.write_text(
yaml.dump(new_config, sort_keys=False, width=200), encoding="utf-8"
)
new_config_docs_path: Path = new_path / "docs"
new_config_docs_path.mkdir()
en_index_path: Path = en_docs_path / "docs" / "index.md"
new_index_path: Path = new_config_docs_path / "index.md"
en_index_content = en_index_path.read_text()
en_index_content = en_index_path.read_text(encoding="utf-8")
new_index_content = f"{missing_translation_snippet}\n\n{en_index_content}"
new_index_path.write_text(new_index_content)
new_index_path.write_text(new_index_content, encoding="utf-8")
typer.secho(f"Successfully initialized: {new_path}", color=typer.colors.GREEN)
update_languages(lang=None)
@@ -128,10 +130,12 @@ def build_lang(
shutil.rmtree(build_lang_path, ignore_errors=True)
shutil.copytree(lang_path, build_lang_path)
en_config_path: Path = en_lang_path / mkdocs_name
en_config: dict = mkdocs.utils.yaml_load(en_config_path.read_text())
en_config: dict = mkdocs.utils.yaml_load(en_config_path.read_text(encoding="utf-8"))
nav = en_config["nav"]
lang_config_path: Path = lang_path / mkdocs_name
lang_config: dict = mkdocs.utils.yaml_load(lang_config_path.read_text())
lang_config: dict = mkdocs.utils.yaml_load(
lang_config_path.read_text(encoding="utf-8")
)
lang_nav = lang_config["nav"]
# Exclude first 2 entries FastAPI and Languages, for custom handling
use_nav = nav[2:]
@@ -146,9 +150,9 @@ def build_lang(
en_file_path: Path = en_lang_path / "docs" / file_path
lang_file_path.parent.mkdir(parents=True, exist_ok=True)
if not lang_file_path.is_file():
en_text = en_file_path.read_text()
en_text = en_file_path.read_text(encoding="utf-8")
lang_text = get_text_with_translate_missing(en_text)
lang_file_path.write_text(lang_text)
lang_file_path.write_text(lang_text, encoding="utf-8")
file_key = file_to_nav[file]
use_lang_file_to_nav[file] = file_key
if file_key:
@@ -171,7 +175,7 @@ def build_lang(
lang_config["nav"] = export_lang_nav
build_lang_config_path: Path = build_lang_path / mkdocs_name
build_lang_config_path.write_text(
yaml.dump(lang_config, sort_keys=False, width=200)
yaml.dump(lang_config, sort_keys=False, width=200), encoding="utf-8"
)
current_dir = os.getcwd()
os.chdir(build_lang_path)
@@ -272,7 +276,7 @@ def live(
def update_config(lang: str):
lang_path: Path = docs_path / lang
config_path = lang_path / mkdocs_name
config: dict = mkdocs.utils.yaml_load(config_path.read_text())
config: dict = mkdocs.utils.yaml_load(config_path.read_text(encoding="utf-8"))
languages = [{"en": "/"}]
for lang in docs_path.iterdir():
if lang.name == "en" or not lang.is_dir():
@@ -280,7 +284,9 @@ def update_config(lang: str):
name = lang.name
languages.append({name: f"/{name}/"})
config["nav"][1] = {"Languages": languages}
config_path.write_text(yaml.dump(config, sort_keys=False, width=200))
config_path.write_text(
yaml.dump(config, sort_keys=False, width=200), encoding="utf-8"
)
def get_key_section(

View File

@@ -15,4 +15,4 @@ def test_get_timed():
response = client.get("/timed")
assert response.json() == {"message": "It's the time of my life"}
assert "X-Response-Time" in response.headers
assert float(response.headers["X-Response-Time"]) > 0
assert float(response.headers["X-Response-Time"]) >= 0

View File

@@ -1,36 +0,0 @@
from fastapi.testclient import TestClient
from custom_response.tutorial001 import app
client = TestClient(app)
openapi_schema = {
"openapi": "3.0.2",
"info": {"title": "FastAPI", "version": "0.1.0"},
"paths": {
"/items/": {
"get": {
"responses": {
"200": {
"description": "Successful Response",
"content": {"application/json": {"schema": {}}},
}
},
"summary": "Read Items",
"operationId": "read_items_items__get",
}
}
},
}
def test_openapi_schema():
response = client.get("/openapi.json")
assert response.status_code == 200
assert response.json() == openapi_schema
def test_get_custom_response():
response = client.get("/items/")
assert response.status_code == 200
assert response.json() == [{"item_id": "Foo"}]

View File

@@ -0,0 +1,80 @@
from typing import List
from fastapi import FastAPI
from fastapi.testclient import TestClient
from pydantic import BaseModel
app = FastAPI()
class RecursiveItem(BaseModel):
sub_items: List["RecursiveItem"] = []
name: str
RecursiveItem.update_forward_refs()
class RecursiveSubitemInSubmodel(BaseModel):
sub_items2: List["RecursiveItemViaSubmodel"] = []
name: str
class RecursiveItemViaSubmodel(BaseModel):
sub_items1: List[RecursiveSubitemInSubmodel] = []
name: str
RecursiveSubitemInSubmodel.update_forward_refs()
@app.get("/items/recursive", response_model=RecursiveItem)
def get_recursive():
return {"name": "item", "sub_items": [{"name": "subitem", "sub_items": []}]}
@app.get("/items/recursive-submodel", response_model=RecursiveItemViaSubmodel)
def get_recursive_submodel():
return {
"name": "item",
"sub_items1": [
{
"name": "subitem",
"sub_items2": [
{
"name": "subsubitem",
"sub_items1": [{"name": "subsubsubitem", "sub_items2": []}],
}
],
}
],
}
client = TestClient(app)
def test_recursive():
response = client.get("/items/recursive")
assert response.status_code == 200
assert response.json() == {
"sub_items": [{"name": "subitem", "sub_items": []}],
"name": "item",
}
response = client.get("/items/recursive-submodel")
assert response.status_code == 200
assert response.json() == {
"name": "item",
"sub_items1": [
{
"name": "subitem",
"sub_items2": [
{
"name": "subsubitem",
"sub_items1": [{"name": "subsubsubitem", "sub_items2": []}],
}
],
}
],
}