Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webhook handlers as FastAPI APIRoutes to allow for Depends injection,… #13

Merged
merged 7 commits into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ exclude =
docs/,
node_modules/,
*/versions/
ignore = H101,H238,H301,H306,W503
ignore = H101,H238,H301,H306,W503,E501
max-line-length = 88
45 changes: 45 additions & 0 deletions .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: documentation
on:
push:
branches:
- main

env:
PYTHON_VERSION: 3.10.1

jobs:
documentation:
name: Build documentation
runs-on: ubuntu-latest
steps:

- name: Checkout repository
uses: actions/checkout@v2

- name: Set up Python runtime
uses: actions/setup-python@v1
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Install poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
poetry config virtualenvs.create true
poetry config virtualenvs.in-project true
poetry config --list

- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v2
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}

- name: Install Python dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install --no-interaction --no-root

- name: Deploy documentation
run: |
poetry run mkdocs gh-deploy --force
poetry run mkdocs --version
35 changes: 35 additions & 0 deletions docs/assets/saleor.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
120 changes: 120 additions & 0 deletions docs/event_handlers/http.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# HTTP Webhook Event Handling

While it's not necessary for every Saleor app to receive domain events from Saleor it is possible, as described in [:saleor-saleor: Saleor's docs](https://docs.saleor.io/docs/3.0/developer/extending#apps).

To configure your app to listen to HTTP webhooks issued from Saleor you need to **register your handlers** similarly as you would register your FastAPI endpoints.

## Setting up the Saleor App

### Getting Webhook details

The framework ensures that the webhook comes from a trusted source but to achieve that it needs to be provided with a way of retrieving the `webhook_secret` your app stored when the `save_app_data` was invoked (upon app installation). To do that you need to provide the `SaleorApp` with an async function doing just that.

```python linenums="1"
from saleor_app.schemas.core import DomainName, WebhookData


async def get_webhook_details(saleor_domain: DomainName) -> WebhookData:
return WebhookData(
webhook_id="webhook-id",
webhook_secret_key="webhook-secret-key",
) # (1)

```

1. :material-database: Typically the data would be taken from a database

The function takes the `saleor_domain` and must return a `WebhookData` Pydantic model instance

### Enabling the webhook router

The framework provides a special webhook router that allows you to use many different endpoints under the `/webhook` route. That router needs to be enabled with the `get_webhook_details` function:

```python linenums="1" hl_lines="16"
from saleor_app.app import SaleorApp
from saleor_app.schemas.core import DomainName, WebhookData


async def get_webhook_details(saleor_domain: DomainName) -> WebhookData:
return WebhookData(
webhook_id="webhook-id",
webhook_secret_key="webhook-secret-key",
)


app = SaleorApp(
#[...]
)

app.include_webhook_router(get_webhook_details=get_webhook_details)
```
### Defining webhook handlers

An HTTP webhook handler is a function that is exactly like one that one would use as a FastAPI endpoint. The difference is that we register those with a special router.

An example of a HTTP webhook handler is:

```python linenums="1" hl_lines="21-26"
from saleor_app.app import SaleorApp
from saleor_app.deps import saleor_domain_header # (1)
from saleor_app.schemas.handlers import SaleorEventType
from saleor_app.schemas.webhook import Webhook
from saleor_app.schemas.core import DomainName, WebhookData


async def get_webhook_details(saleor_domain: DomainName) -> WebhookData:
return WebhookData(
webhook_id="webhook-id",
webhook_secret_key="webhook-secret-key",
)


app = SaleorApp(
#[...]
)
app.include_webhook_router(get_webhook_details=get_webhook_details)


@app.webhook_router.http_event_route(SaleorEventType.PRODUCT_CREATED)
async def product_created(
payload: List[Webhook],
saleor_domain=Depends(saleor_domain_header) # (2)
):
await do_something(payload, saleor_domain)
```

1. :information_source: `saleor_app.deps` contains a set of FastAPI dependencies that you might find useful
2. :information_source: since `product_created` is just a FastAPI endpoint you have access to everything a usual endpoint would, like `request: Request`

If your app is bigger and you need to import your endpoints from a different module you can:

```python linenums="1" hl_lines="6 22-26"
from saleor_app.app import SaleorApp
from saleor_app.schemas.handlers import SaleorEventType
from saleor_app.schemas.webhook import Webhook
from saleor_app.schemas.core import DomainName, WebhookData

from my_app.webhook_handlers import product_created


async def get_webhook_details(saleor_domain: DomainName) -> WebhookData:
return WebhookData(
webhook_id="webhook-id",
webhook_secret_key="webhook-secret-key",
)


app = SaleorApp(
#[...]
)
app.include_webhook_router(get_webhook_details=get_webhook_details)


@app.webhook_router.http_event_route(
SaleorEventType.PRODUCT_CREATED
)(product_created)
```

### Reinstall the app

Neither Saleor nor the app will automatically update the registered webhooks, you need to reinstall the app in Saleor if it was already installed.
37 changes: 37 additions & 0 deletions docs/event_handlers/sqs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# AWS SQS Handlers

!!! warning "Experimental"

SQS event handing is in the works, more content to come


## SQS Consumer

The Saleor App Framework does not provide any means to consume events from an SQS queue. An SQS worker is a work in progress.

## Registering SQS handlers

```python
from saleor_app.schemas.handlers import SQSUrl


@app.webhook_router.sqs_event_route(
SQSUrl(
None,
scheme="awssqs",
user="test",
password="test",
host="localstack",
port="4566",
path="/00000000/product_updated",
),
SaleorEventType.PRODUCT_UPDATED,
)
async def product_updated(
payload: List[Webhook],
saleor_domain=Depends(saleor_domain_header),
example=Depends(example_dependency),
):
print("Product updated!")
print(payload)
```
Loading