Conversion of User Guide to the SHH stack (#2781)

This commit is contained in:
Adam Hopkins
2023-09-06 15:44:00 +03:00
committed by GitHub
parent 47215d4635
commit d255d1aae1
332 changed files with 51495 additions and 2013 deletions

View File

@@ -0,0 +1,9 @@
# Advanced
_Documentation coming EOQ1 2023_
## CBV
## Blueprints
## Components

View File

@@ -0,0 +1,141 @@
# Auto-documentation
To make documenting endpoints easier, Sanic Extensions will use a function's docstring to populate your documentation.
## Summary and description
.. column::
A function's docstring will be used to create the summary and description. As you can see from this example here, the docstring has been parsed to use the first line as the summary, and the remainder of the string as the description.
.. column::
```python
@app.get("/foo")
async def handler(request, something: str):
"""This is a simple foo handler
It is helpful to know that you could also use **markdown** inside your
docstrings.
- one
- two
- three"""
return text(">>>")
```
```json
"paths": {
"/foo": {
"get": {
"summary": "This is a simple foo handler",
"description": "It is helpful to know that you could also use **markdown** inside your<br>docstrings.<br><br>- one<br>- two<br>- three",
"responses": {
"default": {
"description": "OK"
}
},
"operationId": "get_handler"
}
}
}
```
## Operation level YAML
.. column::
You can expand upon this by adding valid OpenAPI YAML to the docstring. Simply add a line that contains `openapi:`, followed by your YAML.
The `---` shown in the example is *not* necessary. It is just there to help visually identify the YAML as a distinct section of the docstring.
.. column::
```python
@app.get("/foo")
async def handler(request, something: str):
"""This is a simple foo handler
Now we will add some more details
openapi:
---
operationId: fooDots
tags:
- one
- two
parameters:
- name: limit
in: query
description: How many items to return at one time (max 100)
required: false
schema:
type: integer
format: int32
responses:
'200':
description: Just some dots
"""
return text("...")
```
```json
"paths": {
"/foo": {
"get": {
"operationId": "fooDots",
"summary": "This is a simple foo handler",
"description": "Now we will add some more details",
"tags": [
"one",
"two"
],
"parameters": [
{
"name": "limit",
"in": "query",
"description": "How many items to return at one time (max 100)",
"required": false,
"schema": {
"type": "integer",
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "Just some dots"
}
}
}
}
}
```
.. note::
When both YAML documentation and decorators are used, it is the content from the decorators that will take priority when generating the documentation.
## Excluding docstrings
.. column::
Sometimes a function may contain a docstring that is not meant to be consumed inside the documentation.
**Option 1**: Globally turn off auto-documentation `app.config.OAS_AUTODOC = False`
**Option 2**: Disable it for the single handler with the `@openapi.no_autodoc` decorator
.. column::
```python
@app.get("/foo")
@openapi.no_autodoc
async def handler(request, something: str):
"""This is a docstring about internal info only. Do not parse it.
"""
return text("...")
```

View File

@@ -0,0 +1,70 @@
# Basics
.. note::
The OpenAPI implementation in Sanic Extensions is based upon the OAS3 implementation from [`sanic-openapi`](https://github.com/sanic-org/sanic-openapi). In fact, Sanic Extensions is in a large way the successor to that project, which entered maintenance mode upon the release of Sanic Extensions. If you were previously using OAS3 with `sanic-openapi` you should have an easy path to upgrading to Sanic Extensions. Unfortunately, this project does *NOT* support the OAS2 specification.
.. column::
Out of the box, Sanic Extensions provides automatically generated API documentation using the [v3.0 OpenAPI specification](https://swagger.io/specification/). There is nothing special that you need to do
.. column::
```python
from sanic import Sanic
app = Sanic("MyApp")
# Add all of your views
```
After doing this, you will now have beautiful documentation already generated for you based upon your existing application:
- [http://localhost:8000/docs](http://localhost:8000/docs)
- [http://localhost:8000/docs/redoc](http://localhost:8000/docs/redoc)
- [http://localhost:8000/docs/swagger](http://localhost:8000/docs/swagger)
Checkout the [section on configuration](../configuration.md) to learn about changing the routes for the docs. You can also turn off one of the two UIs, and customize which UI will be available on the `/docs` route.
.. column::
Using [Redoc](https://github.com/Redocly/redoc)
![Redoc](/assets/images/sanic-ext-redoc.png)
.. column::
or [Swagger UI](https://github.com/swagger-api/swagger-ui)
![Swagger UI](/assets/images/sanic-ext-swagger.png)
## Changing specification metadata
.. column::
If you want to change any of the metada, you should use the `describe` method.
In this example `dedent` is being used with the `description` argument to make multi-line strings a little cleaner. This is not necessary, you can pass any string value here.
.. column::
```python
from textwrap import dedent
app.ext.openapi.describe(
"Testing API",
version="1.2.3",
description=dedent(
"""
# Info
This is a description. It is a good place to add some _extra_ doccumentation.
**MARKDOWN** is supported.
"""
),
)
```

View File

@@ -0,0 +1,468 @@
# Decorators
The primary mechanism for adding content to your schema is by decorating your endpoints. If you have
used `sanic-openapi` in the past, this should be familiar to you. The decorators and their arguments match closely
the [OAS v3.0 specification](https://swagger.io/specification/).
.. column::
All of the examples show will wrap around a route definition. When you are creating these, you should make sure that
your Sanic route decorator (`@app.route`, `@app.get`, etc) is the outermost decorator. That is to say that you should
put that first and then one or more of the below decorators after.
.. column::
```python
from sanic_ext import openapi
@app.get("/path/to/<something>")
@openapi.summary("This is a summary")
@openapi.description("This is a description")
async def handler(request, something: str):
...
```
.. column::
You will also see a lot of the below examples reference a model object. For the sake of simplicity, the examples will
use `UserProfile` that will look like this. The point is that it can be any well-typed class. You could easily imagine
this being a `dataclass` or some other kind of model object.
.. column::
```python
class UserProfile:
name: str
age: int
email: str
```
## Definition decorator
### `@openapi.definition`
The `@openapi.definition` decorator allows you to define all parts of an operations on a path at once. It is an omnibums
decorator in that it has the same capabilities to create operation definitions as the rest of the decorators. Using
multiple field-specific decorators or a single decorator is a style choice for you the developer.
The fields are purposely permissive in accepting multiple types to make it easiest for you to define your operation.
**Arguments**
| Field | Type |
| ------------- | --------------------------------------------------------------------------|
| `body` | **dict, RequestBody, *YourModel*** |
| `deprecated` | **bool** |
| `description` | **str** |
| `document` | **str, ExternalDocumentation** |
| `exclude` | **bool** |
| `operation` | **str** |
| `parameter` | **str, dict, Parameter, [str], [dict], [Parameter]** |
| `response` | **dict, Response, *YourModel*, [dict], [Response]** |
| `summary` | **str** |
| `tag` | **str, Tag, [str], [Tag]** |
| `secured` | **Dict[str, Any]** |
**Examples**
.. column::
```python
@openapi.definition(
body=RequestBody(UserProfile, required=True),
summary="User profile update",
tag="one",
response=[Success, Response(Failure, status=400)],
)
```
.. column::
*See below examples for more examples. Any of the values for the below decorators can be used in the corresponding
keyword argument.*
## Field-specific decorators
All the following decorators are based on `@openapi`
### body
**Arguments**
| Field | Type |
| ----------- | ---------------------------------- |
| **content** | ***YourModel*, dict, RequestBody** |
**Examples**
.. column::
```python
@openapi.body(UserProfile)
```
```python
@openapi.body({"application/json": UserProfile})
```
```python
@openapi.body(RequestBody({"application/json": UserProfile}))
```
.. column::
```python
@openapi.body({"content": UserProfile})
```
```python
@openapi.body(RequestBody(UserProfile))
```
```python
@openapi.body({"application/json": {"description": ...}})
```
### deprecated
**Arguments**
*None*
**Examples**
.. column::
```python
@openapi.deprecated()
```
.. column::
```python
@openapi.deprecated
```
### description
**Arguments**
| Field | Type |
| ------ | ------- |
| `text` | **str** |
**Examples**
.. column::
```python
@openapi.description(
"""This is a **description**.
## You can use `markdown`
- And
- make
- lists.
"""
)
```
.. column::
### document
**Arguments**
| Field | Type |
| ------------- | ------- |
| `url` | **str** |
| `description` | **str** |
**Examples**
.. column::
```python
@openapi.document("http://example.com/docs")
```
.. column::
```python
@openapi.document(ExternalDocumentation("http://example.com/more"))
```
### exclude
Can be used on route definitions like all of the other decorators, or can be called on a Blueprint
**Arguments**
| Field | Type | Default |
| ------ | ------------- | -------- |
| `flag` | **bool** | **True** |
| `bp` | **Blueprint** | |
**Examples**
.. column::
```python
@openapi.exclude()
```
.. column::
```python
openapi.exclude(bp=some_blueprint)
```
### operation
Sets the operation ID.
**Arguments**
| Field | Type |
| ------ | ------- |
| `name` | **str** |
**Examples**
.. column::
```python
@openapi.operation("doNothing")
```
.. column::
**Arguments**
| Field | Type | Default |
| ---------- | ----------------------------------------- | ----------- |
| `name` | **str** | |
| `schema` | ***type*** | **str** |
| `location` | **"query", "header", "path" or "cookie"** | **"query"** |
**Examples**
.. column::
```python
@openapi.parameter("thing")
```
```python
@openapi.parameter(parameter=Parameter("foobar", deprecated=True))
```
.. column::
```python
@openapi.parameter("Authorization", str, "header")
```
```python
@openapi.parameter("thing", required=True, allowEmptyValue=False)
```
### response
**Arguments**
If using a `Response` object, you should not pass any other arguments.
| Field | Type |
| ------------- | ----------------------------- |
| `status` | **int** |
| `content` | ***type*, *YourModel*, dict** |
| `description` | **str** |
| `response` | **Response** |
**Examples**
.. column::
```python
@openapi.response(200, str, "This is endpoint returns a string")
```
```python
@openapi.response(200, {"text/plain": str}, "...")
```
```python
@openapi.response(response=Response(UserProfile, description="..."))
```
```python
@openapi.response(
response=Response(
{
"application/json": UserProfile,
},
description="...",
status=201,
)
)
```
.. column::
```python
@openapi.response(200, UserProfile, "...")
```
```python
@openapi.response(
200,
{
"application/json": UserProfile,
},
"Description...",
)
```
### summary
**Arguments**
| Field | Type |
| ------ | ------- |
| `text` | **str** |
**Examples**
.. column::
```python
@openapi.summary("This is an endpoint")
```
.. column::
### tag
**Arguments**
| Field | Type |
| ------- | ------------ |
| `*args` | **str, Tag** |
**Examples**
.. column::
```python
@openapi.tag("foo")
```
.. column::
```python
@openapi.tag("foo", Tag("bar"))
```
### secured
**Arguments**
| Field | Type |
| ----------------- | ----------------------- |
| `*args, **kwargs` | **str, Dict[str, Any]** |
**Examples**
.. column::
```python
@openapi.secured()
```
.. column::
.. column::
```python
@openapi.secured("foo")
```
.. column::
```python
@openapi.secured("token1", "token2")
```
.. column::
```python
@openapi.secured({"my_api_key": []})
```
.. column::
```python
@openapi.secured(my_api_key=[])
```
Do not forget to use `add_security_scheme`. See [security](./security.md) for more details.
``
## Integration with Pydantic
Pydantic models have the ability to [generate OpenAPI schema](https://pydantic-docs.helpmanual.io/usage/schema/).
.. column::
To take advantage of Pydantic model schema generation, pass the output in place of the schema.
.. column::
```python
from sanic import Sanic, json
from sanic_ext import validate, openapi
from pydantic import BaseModel, Field
@openapi.component
class Item(BaseModel):
name: str
description: str = None
price: float
tax: float = None
class ItemList(BaseModel):
items: List[Item]
app = Sanic("test")
@app.get("/")
@openapi.definition(
body={
"application/json": ItemList.schema(
ref_template="#/components/schemas/{model}"
)
},
)
async def get(request):
return json({})
```
.. note::
It is important to set that `ref_template`. By default Pydantic will select a template that is not standard OAS. This will cause the schema to not be found when generating the final document.
*Added in v22.9*

View File

@@ -0,0 +1,96 @@
# Security Schemes
To document authentication schemes, there are two steps.
_Security is only available starting in v21.12.2_
## Document the scheme
.. column::
The first thing that you need to do is define one or more security schemes. The basic pattern will be to define it as:
```python
add_security_scheme("<NAME>", "<TYPE>")
```
The `type` should correspond to one of the allowed security schemes: `"apiKey"`, `"http"`, `"oauth2"`, `"openIdConnect"`. You can then pass appropriate keyword arguments as allowed by the specification.
You should consult the [OpenAPI Specification](https://swagger.io/specification/) for details on what values are appropriate.
.. column::
```python
app.ext.openapi.add_security_scheme("api_key", "apiKey")
app.ext.openapi.add_security_scheme(
"token",
"http",
scheme="bearer",
bearer_format="JWT",
)
app.ext.openapi.add_security_scheme("token2", "http")
app.ext.openapi.add_security_scheme(
"oldschool",
"http",
scheme="basic",
)
app.ext.openapi.add_security_scheme(
"oa2",
"oauth2",
flows={
"implicit": {
"authorizationUrl": "http://example.com/auth",
"scopes": {
"on:two": "something",
"three:four": "something else",
"threefour": "something else...",
},
}
},
)
```
## Document the endpoints
.. column::
There are two options, document _all_ endpoints.
.. column::
```python
app.ext.openapi.secured()
app.ext.openapi.secured("token")
```
.. column::
Or, document only specific routes.
.. column::
```python
@app.route("/one")
async def handler1(request):
"""
openapi:
---
security:
- foo: []
"""
@app.route("/two")
@openapi.secured("foo")
@openapi.secured({"bar": []})
@openapi.secured(baz=[])
async def handler2(request):
...
@app.route("/three")
@openapi.definition(secured="foo")
@openapi.definition(secured={"bar": []})
async def handler3(request):
...
```

View File

@@ -0,0 +1,26 @@
# UI
Sanic Extensions comes with both Redoc and Swagger interfaces. You have a choice to use one, or both of them. Out of the box, the following endpoints are setup for you, with the bare `/docs` displaying Redoc.
- `/docs`
- `/docs/openapi.json`
- `/docs/redoc`
- `/docs/swagger`
- `/docs/openapi-config`
## Config options
| **Key** | **Type** | **Default** | **Desctiption** |
| -------------------------- | --------------- | ------------------- | ------------------------------------------------------------ |
| `OAS_IGNORE_HEAD` | `bool` | `True` | Whether to display `HEAD` endpoints. |
| `OAS_IGNORE_OPTIONS` | `bool` | `True` | Whether to display `OPTIONS` endpoints. |
| `OAS_PATH_TO_REDOC_HTML` | `Optional[str]` | `None` | Path to HTML to override the default Redoc HTML |
| `OAS_PATH_TO_SWAGGER_HTML` | `Optional[str]` | `None` | Path to HTML to override the default Swagger HTML |
| `OAS_UI_DEFAULT` | `Optional[str]` | `"redoc"` | Can be set to `redoc` or `swagger`. Controls which UI to display on the base route. If set to `None`, then the base route will not be setup. |
| `OAS_UI_REDOC` | `bool` | `True` | Whether to enable Redoc UI. |
| `OAS_UI_SWAGGER` | `bool` | `True` | Whether to enable Swagger UI. |
| `OAS_URI_TO_CONFIG` | `str` | `"/openapi-config"` | URI path to the OpenAPI config used by Swagger |
| `OAS_URI_TO_JSON` | `str` | `"/openapi.json"` | URI path to the JSON document. |
| `OAS_URI_TO_REDOC` | `str` | `"/redoc"` | URI path to Redoc. |
| `OAS_URI_TO_SWAGGER` | `str` | `"/swagger"` | URI path to Swagger. |
| `OAS_URL_PREFIX` | `str` | `"/docs"` | URL prefix to use for the Blueprint for OpenAPI docs. |