Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Empty file added .env
Empty file.
46 changes: 46 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
name: CI
on: [pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install python
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: 0.7
- name: Install dependencies
run: uv sync
- name: Lint check
run: uv run ruff check
- name: Lint fix check
run: |
uv run ruff check --fix
git diff --exit-code
- name: Formatting check
run: uv run ruff format --check
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install python
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
version: 0.7
- name: Install dependencies
run: uv sync
- name: Run tests
run: PYTHON_ENV=test uv run pytest
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
__pycache__
.pytest_cache
.venv
.ruff_cache
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,37 @@
# bot-python
Starter kit for Python bot for Automa

Please read the [Bot Development](https://docs.automa.app/bot-development) docs to understand how this bot works.

* `/automa` endpoint is the receiver for the webhook from [Automa](https://automa.app)
* `update` function in `app/update.py` is the logic responsible for updating code.
* `AUTOMA_WEBHOOK_SECRET` environment variable is available to be set instead of hard-coding it.

### Production

Start the app in production mode:

```
PYTHON_ENV=production uv run fastapi run
```

### Development

Start the app in development mode:

```
uv run fastapi dev
```

### Testing

Run tests with:

```
uv run pytest
```

### Stack

* Uses [uv](https://docs.astral.sh/uv/) as a package manager.
* Uses [fastapi](https://fastapi.tiangolo.com/) as a server.
Empty file added app/__init__.py
Empty file.
17 changes: 17 additions & 0 deletions app/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import os
from functools import lru_cache

from pydantic_settings import BaseSettings, SettingsConfigDict

environment = os.getenv("PYTHON_ENV", "development")


class Config(BaseSettings):
model_config = SettingsConfigDict(env_file=".env")

automa_webhook_secret: str = "atma_whsec_bot-python"


@lru_cache
def env():
return Config()
64 changes: 64 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import json
import logging

from automa.bot import AsyncAutoma
from automa.bot.webhook import verify_webhook
from fastapi import FastAPI, Request, Response

from .env import env
from .update import update

app = FastAPI()


@app.get("/health")
async def health_check():
return Response(status_code=200)


@app.post("/automa")
async def automa_hook(request: Request):
signature = request.headers.get("webhook-signature")
payload = (await request.body()).decode("utf-8")

# Verify request
if not verify_webhook(env().automa_webhook_secret, signature, payload):
logging.warning(
"Invalid signature",
)

return Response(status_code=401)

base_url = request.headers.get("x-automa-server-host")
body = json.loads(payload)

# Create client with base URL
automa = AsyncAutoma(base_url=base_url)

# Download code
folder = await automa.code.download(body["data"])

try:
# Main logic for updating the code. It takes
# the folder location of the downloaded code
# and updates it.
#
# **NOTE**: If this takes a long time, make
# sure to return a response to the webhook
# before starting the update process.
update(folder)

# Propose code
await automa.code.propose(
{
**body["data"],
"proposal": {
"message": "We changed your code",
},
}
)
finally:
# Clean up
await automa.code.cleanup(body["data"])

return Response(status_code=200)
4 changes: 4 additions & 0 deletions app/update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
def update(folder: str):
"""
Update code in the specified folder.
"""
22 changes: 22 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[project]
name = "bot-python"
version = "0.1.0"

requires-python = ">=3.11"

classifiers = ["Private :: Do Not Upload"]

dependencies = [
"automa-bot~=0.1.4",
"fastapi-cli~=0.0.7",
"fastapi~=0.115.11",
"pydantic-settings~=2.8.1",
"uvicorn~=0.34.0",
]

[dependency-groups]
dev = ["httpx~=0.28.1", "pytest-cov~=6.0.0", "pytest~=8.3.5", "ruff~=0.11.2"]

[tool.pytest.ini_options]
pythonpath = "."
testpaths = ["tests"]
Empty file added tests/__init__.py
Empty file.
11 changes: 11 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import pytest
from fastapi.testclient import TestClient

from app.main import app


@pytest.fixture
def client():
"""Create a test client for the app."""
with TestClient(app) as test_client:
yield test_client
Empty file added tests/fixtures/code/.gitkeep
Empty file.
12 changes: 12 additions & 0 deletions tests/fixtures/task.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"id": "whmsg_1",
"type": "task.created",
"timestamp": "2025-05-30T09:30:06.261Z",
"data": {
"task": {
"id": 1,
"token": "abcdef",
"title": "Running bot-python on sample-repo"
}
}
}
Loading
Loading