Skip to content

Commit

Permalink
v0.1.9 release (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
the-robot committed May 10, 2020
1 parent 6850a24 commit a9228e4
Show file tree
Hide file tree
Showing 32 changed files with 983 additions and 148 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -38,6 +38,7 @@ Yaat is an asynchronous web framework/toolkit.
- HTTP streaming response.
- Cookie support.
- WebSocket support.
- API schema generator and [Swagger UI](https://swagger.io/tools/swagger-ui/).
- Background tasks runner.
- Server startup and shutdown events.
- CORS support.
Expand Down
156 changes: 156 additions & 0 deletions docs/apischemas.md
@@ -0,0 +1,156 @@
# OpenAPI Schemas

Yaat can generate [OpenAPI specification](https://www.openapis.org/) for API schemas.

It looks through routes and read the docstrings to generate documentation. By default, the route will be not in the generated schema, you will have to set `has_schema=True` to indicate the route has a schema.

**Signature**

- `OpenAPISchema(title: str, description: str = None, version: str = None)`

```python
from yaat import Yaat
from yaat.openapi import OpenAPISchema
from yaat.responses import JSONResponse

app = Yaat()
openapi = OpenAPISchema("Yaat", "API documentation", "1.0")

@app.route("/schema")
async def api(request):
return openapi.Response(request)

@app.route("/users", has_schema=True)
async def users(request):
"""
responses:
200:
description: List users.
examples:
[{"name": "Jane Doe"}]
"""
return JSONResponse(["name": "Jane Doe"])
```

You can access the OpenAPI schema at `/schema`.

> `openapi.Response` return the response in `application/vnd.oai.openapi` media-type.
> If you want in JSON, use `openapi.JSONResponse` instead.
If you want to get the schema data, you can call `.get_schema(routes)`

```python
router = request.app.router
schema = openapi.get_schema(router.routes)

assert schema == {
"openapi": "3.0.0",
"info": {
"title": "Yaat",
"description": "API documentation",
"version": "1.0"
},
"paths": {
"/users": {
"get": {
"responses": {
"200": {"description": "List users."}
},
"examples": [{"name": "Jane Doe"}]
},
},
},
}
```

### Swagger UI

Yaat also has a handler to get [Swagger UI](https://swagger.io/tools/swagger-ui/).

**Signature**

```python
get_swagger_ui(
*,
openapi_url: str,
title: str,
swagger_favicon_url: str = DEFAULT_FAVICON_URL,
)
```

```python
from yaat import Yaat
from yaat.responses import TextResponse
from yaat.openapi import OpenAPISchema, get_swagger_ui

app = Yaat()
openapi = OpenAPISchema("Yaat", "API documentation", "1.0")

@app.route("/openapi")
async def api(request):
return openapi.Response(request)

@app.route("/swagger")
async def index(request):
return get_swagger_ui(openapi_url="/openapi", title="Yaat")

@app.route("/user/{userid}", has_schema=True)
async def get_user(request, userid: str):
"""
responses:
200:
description: Get user details.
"""
return TextResponse(f"This is {userid}.")
```

You can access the OpenAPI schema at `/swagger`.

- If you do not specify *path parameters*, Yaat will look at the handler's parameters and add the specification automatically. However, you can also override that by specifying inside docstrings.

- If you also want to specify the description for the path parameter, you can write it inside the docstring.

```python
@app.route("/user/{userid}", has_schema=True)
async def get_user(request, userid: str):
"""
parameters:
- name: userid
description: User's unique id.
responses:
200:
description: Get user details.
"""
return TextResponse(f"This is {userid}.")
```

![swaggerui-tags](https://raw.githubusercontent.com/yaat-project/yaat/master/docs/images/apischemas/swaggerui1.png)

- If you do not put the default value for the parameter, it will be `required` in documentation.

- For the value type, it determines by Python's type hinting.
- `int` - will be *integer*.
- `float` - will be *number*.
- `bool` - will be *boolean*.
- `str` - will be *string*.

> Also if the type is not from the above, it will be `string`.
#### Tags

You can add tags to your routes. They will be added to the OpenAPI schema and routes with the same tag will be grouped in Swagger UI.

- `tags` - a list of strings, commonly just one string inside.

```python
@app.route("/user/{userid}", has_schema=True, tags=["user"])
async def get_user(request, userid: str):
"""
responses:
200:
description: Get user details.
"""
return TextResponse(f"This is {userid}.")
```

![swaggerui-tags](https://raw.githubusercontent.com/yaat-project/yaat/master/docs/images/apischemas/swaggerui2.png)
4 changes: 2 additions & 2 deletions docs/backgroundtasks.md
Expand Up @@ -14,8 +14,8 @@ Use to run a single background task after responding to a client.
- `function` - function to be called from the background task. It can be either `sync` or `async`.

```python
from yaat.background import BackgroundTask
from yaat.responses import RunAfterResponse, TextResponse
from yaat.background import BackgroundTask, RunAfterResponse
from yaat.responses import TextResponse

async def task():
...
Expand Down
Binary file added docs/images/apischemas/swaggerui1.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/images/apischemas/swaggerui2.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions docs/index.md
Expand Up @@ -46,6 +46,7 @@ async def index(request):
- HTTP streaming response.
- Cookie support.
- WebSocket support.
- API schema generator and [Swagger UI](https://swagger.io/tools/swagger-ui/).
- Background tasks runner.
- Server startup and shutdown events.
- CORS support.
Expand Down Expand Up @@ -76,10 +77,11 @@ uvicorn app:app
## Dependencies

- [aiofiles](https://github.com/Tinche/aiofiles) - to read files for `FileResponse` or `StaticFiles`.
- [httpx](https://www.python-httpx.org/) - for test client
- [httpx](https://www.python-httpx.org/) - for test client.
- [Jinja2](https://jinja.palletsprojects.com/) - to use `Jinja2Template` for template responses.
- [parse](https://github.com/r1chardj0n3s/parse) - for parsing path parameters.
- [python-multipart](http://andrew-d.github.io/python-multipart/) - for form parser, `request.form()`
- [python-multipart](http://andrew-d.github.io/python-multipart/) - for form parser, `request.form()`.
- [PyYAML](https://pyyaml.org/) - for schema generator.

## License

Expand Down
6 changes: 6 additions & 0 deletions docs/releasenotes.md
@@ -1,5 +1,11 @@
# Release Notes

### 0.1.9

- Updated routes to only allow `GET` method if HTTP methods are not specified.
- API schema generator, `OpenAPISchema` and `SchemaGenerator`.
- [Swagger UI](https://swagger.io/tools/swagger-ui/), `get_swagger_ui`.

### 0.1.8

- Bugfix on router. Read more about issue [here](https://github.com/yaat-project/yaat/pull/25).
Expand Down
2 changes: 1 addition & 1 deletion docs/routing.md
Expand Up @@ -49,7 +49,7 @@ app.add_route(
### Handling HTTP methods

You can specify the HTTP methods during registration to restrict the route to specific HTTP method.
If you do not specify, the route will be open to all HTTP methods.
If you do not specify, only `GET` method is allowed.

```python
@app.route("/", methods=["GET"])
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Expand Up @@ -10,6 +10,7 @@ nav:
- Responses: responses.md
- Routing: routing.md
- WebSockets: websockets.md
- OpenAPI Schemas: apischemas.md
- Static Files: staticfiles.md
- Templates: templates.md
- Background Tasks: backgroundtasks.md
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Expand Up @@ -9,6 +9,7 @@ python-multipart==0.0.5
pytest==5.3.5
pytest-asyncio==0.10.0
pytest-cov==2.8.1
PyYAML==5.3.1
uvicorn==0.11.3
websocket-client==0.57.0

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Expand Up @@ -3,3 +3,4 @@ httpx==0.11.1
Jinja2==2.11.1
parse==1.14.0
python-multipart==0.0.5
PyYAML==5.3.1
2 changes: 1 addition & 1 deletion setup.py
@@ -1,6 +1,6 @@
from setuptools import setup

VERSION = "0.1.8"
VERSION = "0.1.9"


def get_long_description():
Expand Down
2 changes: 1 addition & 1 deletion tests/middleware/test_base.py
Expand Up @@ -14,7 +14,7 @@ async def process_response(self, response: Response):


@pytest.mark.asyncio
async def test_plain_text_response(app, client):
async def test_base_middleware_plain_text_response(app, client):
app.add_middleware(CustomMiddleware)

@app.route("/")
Expand Down
16 changes: 9 additions & 7 deletions tests/middleware/test_cors.py
Expand Up @@ -7,7 +7,7 @@


@pytest.mark.asyncio
async def test_allow_all(app, client):
async def test_cors_middleware_allow_all(app, client):
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
Expand Down Expand Up @@ -36,7 +36,7 @@ async def handler(request):


@pytest.mark.asyncio
async def test_allow_specific_origin(app, client):
async def test_cors_middleware_allow_specific_origin(app, client):
app.add_middleware(
CORSMiddleware,
allow_origins=["http://testserver.com"],
Expand Down Expand Up @@ -80,7 +80,7 @@ async def handler(request):


@pytest.mark.asyncio
async def test_disallowed_preflight(app, client):
async def test_cors_middleware_disallowed_preflight(app, client):
app.add_middleware(
CORSMiddleware,
allow_origins=["http://testserver.com"],
Expand All @@ -105,7 +105,9 @@ async def handler(request):


@pytest.mark.asyncio
async def test_credentialed_return_specific_origin(app, client):
async def test_cors_middleware_credentialed_return_specific_origin(
app, client
):
app.add_middleware(CORSMiddleware, allow_origins=["*"])

@app.route("/")
Expand All @@ -126,7 +128,7 @@ async def handler(request):


@pytest.mark.asyncio
async def test_vary_headers_origin_default(app, client):
async def test_cors_middleware_vary_headers_origin_default(app, client):
app.add_middleware(CORSMiddleware, allow_origins=["http://testserver.com"])

@app.route("/")
Expand All @@ -142,7 +144,7 @@ async def handler(request):


@pytest.mark.asyncio
async def test_vary_headers_set(app, client):
async def test_cors_middleware_vary_headers_set(app, client):
app.add_middleware(CORSMiddleware, allow_origins=["http://testserver.com"])

@app.route("/")
Expand All @@ -158,7 +160,7 @@ async def handler(request):


@pytest.mark.asyncio
async def test_allow_origin_regex(app, client):
async def test_cors_middleware_allow_origin_regex(app, client):
app.add_middleware(
CORSMiddleware,
allow_origin_regex=r"http://.*\.com",
Expand Down
8 changes: 4 additions & 4 deletions tests/middleware/test_lifespan.py
Expand Up @@ -4,7 +4,7 @@


@pytest.mark.asyncio
async def test_sync_lifespan():
async def test_lifespan_middleware_sync_lifespan():
ran_startup = False
ran_shutdown = False

Expand Down Expand Up @@ -38,7 +38,7 @@ async def asgi_send(*args, **kwargs):


@pytest.mark.asyncio
async def test_async_lifespan():
async def test_lifespan_middleware_async_lifespan():
ran_startup = False
ran_shutdown = False

Expand Down Expand Up @@ -72,7 +72,7 @@ async def asgi_send(*args, **kwargs):


@pytest.mark.asyncio
async def test_raise_on_startup():
async def test_lifespan_middleware_raise_on_startup():
ran_startup = False
startup_failed = False

Expand Down Expand Up @@ -104,7 +104,7 @@ async def asgi_send(*args, **kwargs):


@pytest.mark.asyncio
async def test_raise_on_shutdown():
async def test_lifespan_middleware_raise_on_shutdown():
ran_startup = False
ran_shutdown = False
shutdown_failed = False
Expand Down

0 comments on commit a9228e4

Please sign in to comment.