-
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
487 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
# The Form | ||
|
||
This is a special way of sending a form directly using Esmerald. The `Form` like the | ||
[File](./upload-files.md), inherits from the [Body](./body-fields.md) and applies the special | ||
`media_type` as `application/x-www-form-urlencoded`. | ||
|
||
This also means that you can also use the `Body` directly to send a form with your API by simply | ||
declaring `Body(media_type="application/x-www-form-urlencoded")`. | ||
|
||
The Form is a simple and cleaner shortcut for it. | ||
|
||
The simplest way is by importing the `Form` object from Esmerald. | ||
|
||
```python hl_lines="7" | ||
{!> ../docs_src/extras/form/form_object.py !} | ||
``` | ||
|
||
You can also import via: | ||
|
||
```python | ||
from esmerald.params import Form | ||
``` | ||
|
||
As [explained here](./request-data.md#request-data), the handler is expecting a `data` field declared and from there | ||
you can pass more details about the form. | ||
|
||
|
||
## Examples | ||
|
||
You can send the form in many different formats, for example: | ||
|
||
1. [A dictionary](#sending-as-dictionary) - Send as normal dictionary. | ||
2. [A dataclass](#a-dataclass) - Send as normal dataclass. | ||
3. [A pydantic dataclass](#pydantic-dataclass) - Send a pydantic dataclass. | ||
4. [Pydantic model](#pydantic-model) - Send a pydantic BaseModel. | ||
|
||
You decide the best format to send. For the following examples, we will be using `httpx` for the | ||
requests for explanatory purposes. | ||
|
||
### Sending as dictionary | ||
|
||
```python hl_lines="9 20 23" | ||
{!> ../docs_src/extras/form/as_dict.py !} | ||
``` | ||
|
||
As you can see, we declared the return signature to be `Dict[str, str]` and the `data` payload to | ||
be a dictionary also `Dict[str, str]`. This way we acn simply send the form as you would normally | ||
do. | ||
|
||
### A dataclass | ||
|
||
What if you want to type as a dataclass and return it in your response? | ||
|
||
```python hl_lines="15 26 29" | ||
{!> ../docs_src/extras/form/dataclass.py !} | ||
``` | ||
|
||
The way the payload is sent to the API will always be the same no matter what, what is important | ||
is how you actually type it. In this example, we declared a `User` dataclass with two field | ||
`name` and `email` and we return exactly what we sent back into the response. | ||
|
||
|
||
### Pydantic dataclass | ||
|
||
A Pydantic dataclass is the same as a normal python dataclass in the end but with some internal | ||
extras from Pydantic but for Esmerald, it is the same. | ||
|
||
```python hl_lines="14 25 28" | ||
{!> ../docs_src/extras/form/pydantic_dc.py !} | ||
``` | ||
|
||
### Pydantic model | ||
|
||
What if we want to type and return as a Pydantic model? Well, it behaves exactly the same as the | ||
dataclasses. | ||
|
||
```python hl_lines="13 24 27" | ||
{!> ../docs_src/extras/form/model.py !} | ||
``` | ||
|
||
## Notes | ||
|
||
As you could see from the examples, it is very simple and direct to use the `Form` in Esmerald and | ||
the returns are simply clean. | ||
|
||
### Important | ||
|
||
Since `Form` is Pydantic field (sort of), that also means you can specify for instance, | ||
the other parameters to be evaluated. | ||
|
||
You can check [the list of available parameters default](https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.FieldInfo) | ||
as well. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,177 @@ | ||
# Webhooks | ||
|
||
OpenAPI Webhooks are those cases where you want to tell your API users that your application | ||
could/should/would call **their** own application, for example, sending a request with specific | ||
bits of data, usually to **notify** them of some type of event. | ||
|
||
This also means that instead of your users sending requests to your APIs, it is your application | ||
**sendind requests** to their application. | ||
|
||
This process is called **webhook**. | ||
|
||
## Esmerald webhooks | ||
|
||
Esmerald provides a way of declaring these webhooks in the OpenAPI specification. It is very, very | ||
similar to the way the [Gateway](routes.md#gateway) is declared but **dedicated to webhooks**. | ||
|
||
The process usually is that you define in your code, as normal, what is the message that you will | ||
send, in other words, **the body of the request**. | ||
|
||
You also define in some way at which moments your app will send those requests or events. | ||
|
||
Your users on the other hand, define some way (web dashboard, for instance) the URL where your | ||
application should send those requests. | ||
|
||
The way the logic how to register the URLs for the webhooks and the code to performs the said | ||
actions is entirely up to you. | ||
|
||
## Documenting Esmerald webhooks with OpenAPI | ||
|
||
As mentioned before, the way of doing it is very similar to the way you declare | ||
[Gateway](routes.md#gateway) but for this purpose, webhooks have a **special dedicated** object or | ||
objects to do make it happen, the [WebhookGateway](#webhookgateway). | ||
|
||
Also, the webhooks **are not *hooked* into the application routing system**, instead, they are | ||
placed in the `webhooks` list. | ||
|
||
```python hl_lines="3" | ||
from esmerald import Esmerald | ||
|
||
app = Esmerald(webhooks=[...]) | ||
``` | ||
|
||
### WebhookGateway | ||
|
||
As the name indicated, the `WebhookGateway` is the main object where you declare the hooks for | ||
the OpenAPI specification and **unlike the Gateway**, it does not declare a `path` (example, `/event`), | ||
instead, it only needs to receive the **name** of the action. | ||
|
||
Like the Gateway, the **WebhookGateway** also expects a [handler](#handlers) but | ||
**not the same handler as you usually use for the routes**, a special **webhook handler**. | ||
|
||
#### How to import it | ||
|
||
You can import them directly: | ||
|
||
```python | ||
from esmerald import WebhookGateway | ||
``` | ||
|
||
Or you can use the full path. | ||
|
||
```python | ||
from esmerald.routing.gateways import WebhookGateway | ||
``` | ||
|
||
#### Parameters | ||
|
||
* **path** - Altough is called path, it corresponds to the **name** of the webhook | ||
and it does not require a `/` at the beginning of it like a normal path would. E.g.: `subscription`. | ||
* **handler** - The [handler](#handlers) function of the webhook. | ||
* **include_in_schema** - Flag if the webhook should be included in the OpenAPI schema. | ||
* **deprecated** - Flag if the webhook is deprecated. | ||
|
||
### Handlers | ||
|
||
The handlers for the **webhooks** are pretty much similar to the normal handlers used for routing | ||
but **dedicated** only to the **WebhookGateway**. The available handlers are: | ||
|
||
* **whget** - For the `GET`. | ||
* **whpost** - For the `POST`. | ||
* **whput** - For the `PUT`. | ||
* **whpatch** - For the `PATCH`. | ||
* **whdelete** - For the `DELETE`. | ||
* **whead** - For the `HEAD`. | ||
* **whoptions** - For the `OPTION`. | ||
* **whtrace** - For the `TRACE`. | ||
* **whroute** - Used to specificy for which `http verbs` is available. This handler has the special | ||
`methods` attribute. E,g.: | ||
|
||
```python | ||
from esmerald import whroute | ||
|
||
@whroute(methods=["GET", "POST"]) | ||
... | ||
``` | ||
|
||
As you can already see, the handlers are very similar to the [routing handler](./handlers.md) but | ||
dedicated to this purpose and **all of them start with `wh`**. | ||
|
||
The `wh` at the beginning of each handler means **W**eb**H**ook. | ||
|
||
#### How to import them | ||
|
||
You can import them directly: | ||
|
||
```python | ||
from esmerald import ( | ||
whdelete, | ||
whead, | ||
whget, | ||
whoptions, | ||
whpatch, | ||
whpost, | ||
whput, | ||
whroute, | ||
whtrace | ||
) | ||
``` | ||
|
||
Or via full path. | ||
|
||
```python | ||
from esmerald.routing.webhooks.handlers import ( | ||
whdelete, | ||
whead, | ||
whget, | ||
whoptions, | ||
whpatch, | ||
whpost, | ||
whput, | ||
whroute, | ||
whtrace | ||
) | ||
``` | ||
|
||
## An Esmerald application with webhooks | ||
|
||
When you create an **Esmerald** application, as mentioned before, there is a `webhooks` attribute | ||
that you use to define your application `webhooks`, in a similar way you define the `routes`. | ||
|
||
```python hl_lines="6 21 16 28" | ||
{!> ../docs_src/routing/webhooks/example.py !} | ||
``` | ||
|
||
Note how the `whpost` and `post` are declared inside the `webhooks` and `routes` respectively, | ||
**similar but not the same** and how the `whpost` **does not require** the `/` for the path. | ||
|
||
The webhooks you define **will end up** in the **OpenAPI** schema automatically. | ||
|
||
### Using the APIView to generate webhooks | ||
|
||
Since Esmerald supports class based views, that also means you can also use them to generate | ||
webhooks. | ||
|
||
```python | ||
{!> ../docs_src/routing/webhooks/cbv.py !} | ||
``` | ||
|
||
## Important | ||
|
||
Notice that with webhooks you are actually not declaring a path (like `/user`). The text you pass | ||
there is just a `name` or an **identifier** of the webhook (name of the event). | ||
|
||
This happens because it is expected that your users would actually define the proper URL path where | ||
they want to receive the webhook in some way. | ||
|
||
## Check out the docs | ||
|
||
Let us see how it would look like in the docs if we were declaring the webhooks from the examples. | ||
|
||
**First example, no Class Based Views** | ||
|
||
<img src="https://res.cloudinary.com/dymmond/image/upload/v1690305100/esmerald/webhooks/first-example_szu28y.png" title="First example" /> | ||
|
||
**Second example, with Class Based Views** | ||
|
||
<img src="https://res.cloudinary.com/dymmond/image/upload/v1690305101/esmerald/webhooks/second-example_hdqsif.png" title="First example" /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from typing import Any, Dict | ||
|
||
import httpx | ||
|
||
from esmerald import Esmerald, Form, Gateway, post | ||
|
||
|
||
@post("/create") | ||
async def create(data: Dict[str, str] = Form()) -> Dict[str, str]: | ||
""" | ||
Creates a user in the system and does not return anything. | ||
Default status_code: 201 | ||
""" | ||
return data | ||
|
||
|
||
app = Esmerald(routes=[Gateway(handler=create)]) | ||
|
||
# Payload example | ||
data = {"name": "example", "email": "[email protected]"} | ||
|
||
# Send the request | ||
httpx.post("/create", data=data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from dataclasses import dataclass | ||
|
||
import httpx | ||
|
||
from esmerald import Esmerald, Form, Gateway, post | ||
|
||
|
||
@dataclass | ||
class User: | ||
name: str | ||
email: str | ||
|
||
|
||
@post("/create") | ||
async def create(data: User = Form()) -> User: | ||
""" | ||
Creates a user in the system and does not return anything. | ||
Default status_code: 201 | ||
""" | ||
return data | ||
|
||
|
||
app = Esmerald(routes=[Gateway(handler=create)]) | ||
|
||
# Payload example | ||
data = {"name": "example", "email": "[email protected]"} | ||
|
||
# Send the request | ||
httpx.post("/create", data=data) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
from typing import Any, Dict | ||
|
||
from esmerald import Esmerald, Form, Gateway, post | ||
|
||
|
||
@post("/create") | ||
async def create_user(data: Dict[str, Any] = Form()) -> None: | ||
""" | ||
Creates a user in the system and does not return anything. | ||
Default status_code: 201 | ||
""" | ||
|
||
|
||
app = Esmerald(routes=[Gateway(handler=create_user)]) |
Oops, something went wrong.