Skip to content

Commit

Permalink
Merge branch 'master' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
PipeKnight committed Sep 2, 2022
2 parents e9fd538 + fbe1a80 commit 3cfb1c3
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 12 deletions.
1 change: 0 additions & 1 deletion README.md
Expand Up @@ -49,7 +49,6 @@ The key features are:
<a href="https://bit.ly/3dmXC5S" target="_blank" title="The data structure for unstructured multimodal data"><img src="https://fastapi.tiangolo.com/img/sponsors/docarray.svg"></a>
<a href="https://bit.ly/3JJ7y5C" target="_blank" title="Build cross-modal and multimodal applications on the cloud"><img src="https://fastapi.tiangolo.com/img/sponsors/jina2.svg"></a>
<a href="https://cryptapi.io/" target="_blank" title="CryptAPI: Your easy to use, secure and privacy oriented payment gateway."><img src="https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg"></a>
<a href="https://app.imgwhale.xyz/" target="_blank" title="The ultimate solution to unlimited and forever cloud storage."><img src="https://fastapi.tiangolo.com/img/sponsors/imgwhale.svg"></a>
<a href="https://doist.com/careers/9B437B1615-wa-senior-backend-engineer-python" target="_blank" title="Help us migrate doist to FastAPI"><img src="https://fastapi.tiangolo.com/img/sponsors/doist.svg"></a>
<a href="https://www.deta.sh/?ref=fastapi" target="_blank" title="The launchpad for all your (team's) ideas"><img src="https://fastapi.tiangolo.com/img/sponsors/deta.svg"></a>
<a href="https://www.investsuite.com/jobs" target="_blank" title="Wealthtech jobs with FastAPI"><img src="https://fastapi.tiangolo.com/img/sponsors/investsuite.svg"></a>
Expand Down
3 changes: 0 additions & 3 deletions docs/en/data/sponsors.yml
Expand Up @@ -8,9 +8,6 @@ gold:
- url: https://cryptapi.io/
title: "CryptAPI: Your easy to use, secure and privacy oriented payment gateway."
img: https://fastapi.tiangolo.com/img/sponsors/cryptapi.svg
- url: https://app.imgwhale.xyz/
title: The ultimate solution to unlimited and forever cloud storage.
img: https://fastapi.tiangolo.com/img/sponsors/imgwhale.svg
- url: https://doist.com/careers/9B437B1615-wa-senior-backend-engineer-python
title: Help us migrate doist to FastAPI
img: https://fastapi.tiangolo.com/img/sponsors/doist.svg
Expand Down
36 changes: 36 additions & 0 deletions docs/en/docs/advanced/custom-response.md
Expand Up @@ -21,6 +21,12 @@ For example, if you are squeezing performance, you can install and use <a href="

Import the `Response` class (sub-class) you want to use and declare it in the *path operation decorator*.

For large responses, returning a `Response` directly is much faster than returning a dictionary.

This is because by default, FastAPI will inspect every item inside and make sure it is serializable with JSON, using the same [JSON Compatible Encoder](../tutorial/encoder.md){.internal-link target=_blank} explained in the tutorial. This is what allows you to return **arbitrary objects**, for example database models.

But if you are certain that the content that you are returning is **serializable with JSON**, you can pass it directly to the response class and avoid the extra overhead that FastAPI would have by passing your return content through the `jsonable_encoder` before passing it to the response class.

```Python hl_lines="2 7"
{!../../../docs_src/custom_response/tutorial001b.py!}
```
Expand Down Expand Up @@ -244,6 +250,36 @@ You can also use the `response_class` parameter:

In this case, you can return the file path directly from your *path operation* function.

## Custom response class

You can create your own custom response class, inheriting from `Response` and using it.

For example, let's say that you want to use <a href="https://github.com/ijl/orjson" class="external-link" target="_blank">`orjson`</a>, but with some custom settings not used in the included `ORJSONResponse` class.

Let's say you want it to return indented and formatted JSON, so you want to use the orjson option `orjson.OPT_INDENT_2`.

You could create a `CustomORJSONResponse`. The main thing you have to do is create a `Response.render(content)` method that returns the content as `bytes`:

```Python hl_lines="9-14 17"
{!../../../docs_src/custom_response/tutorial009c.py!}
```

Now instead of returning:

```json
{"message": "Hello World"}
```

...this response will return:

```json
{
"message": "Hello World"
}
```

Of course, you will probably find much better ways to take advantage of this than formatting JSON. 😉

## Default response class

When creating a **FastAPI** class instance or an `APIRouter` you can specify which response class to use by default.
Expand Down
8 changes: 8 additions & 0 deletions docs/en/docs/release-notes.md
Expand Up @@ -2,6 +2,14 @@

## Latest Changes

* ♻ Internal small refactor, move `operation_id` parameter position in delete method for consistency with the code. PR [#4474](https://github.com/tiangolo/fastapi/pull/4474) by [@hiel](https://github.com/hiel).
* ✨ Support Python internal description on Pydantic model's docstring. PR [#3032](https://github.com/tiangolo/fastapi/pull/3032) by [@Kludex](https://github.com/Kludex).
* ✨ Update `ORJSONResponse` to support non `str` keys and serializing Numpy arrays. PR [#3892](https://github.com/tiangolo/fastapi/pull/3892) by [@baby5](https://github.com/baby5).
* 🔧 Update sponsors, disable ImgWhale. PR [#5338](https://github.com/tiangolo/fastapi/pull/5338) by [@tiangolo](https://github.com/tiangolo).
* 📝 Update docs for `ORJSONResponse` with details about improving performance. PR [#2615](https://github.com/tiangolo/fastapi/pull/2615) by [@falkben](https://github.com/falkben).
* 📝 Add docs for creating a custom Response class. PR [#5331](https://github.com/tiangolo/fastapi/pull/5331) by [@tiangolo](https://github.com/tiangolo).
* 📝 Add tip about using alias for form data fields. PR [#5329](https://github.com/tiangolo/fastapi/pull/5329) by [@tiangolo](https://github.com/tiangolo).
* 🐛 Fix support for path parameters in WebSockets. PR [#3879](https://github.com/tiangolo/fastapi/pull/3879) by [@davidbrochart](https://github.com/davidbrochart).

## 0.81.0

Expand Down
2 changes: 1 addition & 1 deletion docs/en/docs/tutorial/request-forms.md
Expand Up @@ -27,7 +27,7 @@ For example, in one of the ways the OAuth2 specification can be used (called "pa

The <abbr title="specification">spec</abbr> requires the fields to be exactly named `username` and `password`, and to be sent as form fields, not JSON.

With `Form` you can declare the same metadata and validation as with `Body` (and `Query`, `Path`, `Cookie`).
With `Form` you can declare the same configurations as with `Body` (and `Query`, `Path`, `Cookie`), including validation, examples, an alias (e.g. `user-name` instead of `username`), etc.

!!! info
`Form` is a class that inherits directly from `Body`.
Expand Down
4 changes: 2 additions & 2 deletions docs/en/overrides/main.html
Expand Up @@ -40,12 +40,12 @@
<img class="sponsor-image" src="/img/sponsors/cryptapi-banner.svg" />
</a>
</div>
<div class="item">
<!-- <div class="item">
<a title="The ultimate solution to unlimited and forever cloud storage." style="display: block; position: relative;" href="https://app.imgwhale.xyz/" target="_blank">
<span class="sponsor-badge">sponsor</span>
<img class="sponsor-image" src="/img/sponsors/imgwhale-banner.svg" />
</a>
</div>
</div> -->
<div class="item">
<a title="Help us migrate doist to FastAPI" style="display: block; position: relative;" href="https://doist.com/careers/9B437B1615-wa-senior-backend-engineer-python" target="_blank">
<span class="sponsor-badge">sponsor</span>
Expand Down
2 changes: 1 addition & 1 deletion docs_src/custom_response/tutorial001b.py
Expand Up @@ -6,4 +6,4 @@

@app.get("/items/", response_class=ORJSONResponse)
async def read_items():
return [{"item_id": "Foo"}]
return ORJSONResponse([{"item_id": "Foo"}])
19 changes: 19 additions & 0 deletions docs_src/custom_response/tutorial009c.py
@@ -0,0 +1,19 @@
from typing import Any

import orjson
from fastapi import FastAPI, Response

app = FastAPI()


class CustomORJSONResponse(Response):
media_type = "application/json"

def render(self, content: Any) -> bytes:
assert orjson is not None, "orjson must be installed"
return orjson.dumps(content, option=orjson.OPT_INDENT_2)


@app.get("/", response_class=CustomORJSONResponse)
async def main():
return {"message": "Hello World"}
2 changes: 1 addition & 1 deletion fastapi/applications.py
Expand Up @@ -635,10 +635,10 @@ def delete(
response_description=response_description,
responses=responses,
deprecated=deprecated,
operation_id=operation_id,
response_model_include=response_model_include,
response_model_exclude=response_model_exclude,
response_model_by_alias=response_model_by_alias,
operation_id=operation_id,
response_model_exclude_unset=response_model_exclude_unset,
response_model_exclude_defaults=response_model_exclude_defaults,
response_model_exclude_none=response_model_exclude_none,
Expand Down
4 changes: 3 additions & 1 deletion fastapi/responses.py
Expand Up @@ -31,4 +31,6 @@ class ORJSONResponse(JSONResponse):

def render(self, content: Any) -> bytes:
assert orjson is not None, "orjson must be installed to use ORJSONResponse"
return orjson.dumps(content)
return orjson.dumps(
content, option=orjson.OPT_NON_STR_KEYS | orjson.OPT_SERIALIZE_NUMPY
)
4 changes: 2 additions & 2 deletions fastapi/routing.py
Expand Up @@ -297,14 +297,14 @@ def __init__(
self.path = path
self.endpoint = endpoint
self.name = get_name(endpoint) if name is None else name
self.dependant = get_dependant(path=path, call=self.endpoint)
self.path_regex, self.path_format, self.param_convertors = compile_path(path)
self.dependant = get_dependant(path=self.path_format, call=self.endpoint)
self.app = websocket_session(
get_websocket_app(
dependant=self.dependant,
dependency_overrides_provider=dependency_overrides_provider,
)
)
self.path_regex, self.path_format, self.param_convertors = compile_path(path)

def matches(self, scope: Scope) -> Tuple[Match, Scope]:
match, child_scope = super().matches(scope)
Expand Down
2 changes: 2 additions & 0 deletions fastapi/utils.py
Expand Up @@ -37,6 +37,8 @@ def get_model_definitions(
)
definitions.update(m_definitions)
model_name = model_name_map[model]
if "description" in m_schema:
m_schema["description"] = m_schema["description"].split("\f")[0]
definitions[model_name] = m_schema
return definitions

Expand Down
21 changes: 21 additions & 0 deletions tests/test_orjson_response_class.py
@@ -0,0 +1,21 @@
from fastapi import FastAPI
from fastapi.responses import ORJSONResponse
from fastapi.testclient import TestClient
from sqlalchemy.sql.elements import quoted_name

app = FastAPI(default_response_class=ORJSONResponse)


@app.get("/orjson_non_str_keys")
def get_orjson_non_str_keys():
key = quoted_name(value="msg", quote=False)
return {key: "Hello World", 1: 1}


client = TestClient(app)


def test_orjson_non_str_keys():
with client:
response = client.get("/orjson_non_str_keys")
assert response.json() == {"msg": "Hello World", "1": 1}
10 changes: 10 additions & 0 deletions tests/test_tutorial/test_custom_response/test_tutorial009c.py
@@ -0,0 +1,10 @@
from fastapi.testclient import TestClient

from docs_src.custom_response.tutorial009c import app

client = TestClient(app)


def test_get():
response = client.get("/")
assert response.content == b'{\n "message": "Hello World"\n}'
19 changes: 19 additions & 0 deletions tests/test_ws_router.py
Expand Up @@ -35,6 +35,14 @@ async def routerindex2(websocket: WebSocket):
await websocket.close()


@router.websocket("/router/{pathparam:path}")
async def routerindexparams(websocket: WebSocket, pathparam: str, queryparam: str):
await websocket.accept()
await websocket.send_text(pathparam)
await websocket.send_text(queryparam)
await websocket.close()


async def ws_dependency():
return "Socket Dependency"

Expand Down Expand Up @@ -106,3 +114,14 @@ def test_router_ws_depends_with_override():
app.dependency_overrides[ws_dependency] = lambda: "Override"
with client.websocket_connect("/router-ws-depends/") as websocket:
assert websocket.receive_text() == "Override"


def test_router_with_params():
client = TestClient(app)
with client.websocket_connect(
"/router/path/to/file?queryparam=a_query_param"
) as websocket:
data = websocket.receive_text()
assert data == "path/to/file"
data = websocket.receive_text()
assert data == "a_query_param"

0 comments on commit 3cfb1c3

Please sign in to comment.