Async Python client for the Polis Bot API
Project description
polis-bot
Async Python client for the Polis Bot API — build bots for the Polis messaging platform.
Requires Python 3.10+. Built on httpx and Pydantic v2.
Installation
pip install polis-bot
Quick Start
from polis_bot import PolisBot, CommandContext, MessageContext
bot = PolisBot("your-bot-token")
@bot.command("/start", description="Start the bot")
async def start(ctx: CommandContext):
await ctx.reply(f"Hello {ctx.user.display_name}! 🤖")
@bot.command("/help", description="Show help")
async def help_cmd(ctx: CommandContext):
await ctx.reply("**Commands:**\n- /start — Greet\n- /help — This message")
@bot.on_message
async def echo(ctx: MessageContext):
await ctx.reply(f"You said: {ctx.message.content}")
bot.run()
Commands are auto-registered with the server on startup. The bot long-polls for updates and dispatches them to the matching handler.
Command Arguments
Handler type hints drive automatic argument parsing — like FastAPI for bots:
@bot.command("/add", description="Add two numbers")
async def add(ctx: CommandContext, a: int, b: int):
await ctx.reply(f"{a + b}")
# /add 3 5 → "8"
The last str parameter absorbs remaining tokens:
@bot.command("/echo", description="Echo text")
async def echo_cmd(ctx: CommandContext, text: str):
await ctx.reply(text)
# /echo hello world → "hello world"
Supported types: str, int, float, bool, UUID, and Pydantic BaseModel subclasses. Invalid input returns a usage hint automatically.
File Handling
Files are attachments on messages — any message (text, command, or standalone) can
carry one or more files. Each attachment is an Attachment object with metadata
properties and download() / download_to() methods — no need to manage URLs.
Sending files
# File-only message
await bot.send_file(user_id, "image.png", content_type="image/png")
# Text + file in one message
await bot.send_file(user_id, "report.pdf",
content_type="application/pdf",
content="Here's the report you requested")
# Via context
@bot.command("/cat", description="Send a cat picture")
async def cat(ctx: CommandContext):
await ctx.reply_file("cat.jpg", content_type="image/jpeg")
Receiving files
File attachments are available on any message via ctx.message.attachments.
Each Attachment exposes metadata and can download itself:
@bot.on_message
async def handle(ctx: MessageContext):
if ctx.message.attachments:
for att in ctx.message.attachments:
print(f"{att.file_name} ({att.content_type}, {att.size_bytes} bytes)")
# Download to memory
data = await att.download()
# or save to disk
await att.download_to(f"downloads/{att.file_name}")
await ctx.reply(f"Got {len(ctx.message.attachments)} file(s)")
elif ctx.message.content:
await ctx.reply(f"You said: {ctx.message.content}")
Commands can also have file attachments:
@bot.command("/analyze", description="Analyze an uploaded file")
async def analyze(ctx: CommandContext):
if not ctx.message.attachments:
await ctx.reply("Please attach a file to analyze")
return
att = ctx.message.attachments[0]
data = await att.download()
await ctx.reply(f"Analyzed **{att.file_name}**: {len(data)} bytes")
Lifecycle Events
from polis_bot import EventContext
@bot.on_event("BotStarted")
async def on_started(ctx: EventContext):
await bot.send_message(ctx.user.id, "Welcome! Send /help to get started.")
@bot.on_event("BotBlocked")
async def on_blocked(ctx: EventContext):
print(f"{ctx.user.display_name} blocked the bot")
Event types: BotStarted, BotBlocked, BotUnblocked, BotAdded, BotRemoved.
Startup Hook
@bot.on_startup
async def setup():
print(f"Running as @{bot.me.botname} ({bot.me.display_name})")
bot.me is populated before startup hooks run and returns cached BotInfo.
Error Handling
The SDK raises typed exceptions for API errors:
from polis_bot import PolisApiError, PolisAuthError, PolisRateLimitError
try:
await bot.send_message(user_id, "hello")
except PolisAuthError:
print("Invalid bot token")
except PolisRateLimitError as e:
print(f"Rate limited — retry after {e.retry_after}s")
except PolisApiError as e:
print(f"HTTP {e.status_code}: {e.message}")
| Exception | HTTP Status |
|---|---|
PolisAuthError |
401 |
PolisForbiddenError |
403 |
PolisNotFoundError |
404 |
PolisRateLimitError |
429 |
PolisApiError |
Any non-2xx |
Async Context Manager
async with PolisBot("token") as bot:
me = await bot.get_me()
await bot.send_message(user_id, "Hello!")
# client is automatically closed
Low-Level Polling
For full control over the update loop instead of using decorators:
async with PolisBot("token") as bot:
async for update in bot.poll(limit=20, timeout=30):
print(update.type, update.user.display_name)
if update.message and update.message.is_command:
await bot.send_message(update.user.id, "Got your command!")
Updates are automatically acknowledged after each batch. Disable with auto_ack=False:
async for update in bot.poll(auto_ack=False):
# manually acknowledge
await bot.acknowledge_updates([update.update_id])
API Reference
PolisBot(token, base_url="https://polis.krlv.org", *, timeout=30.0)
Properties
| Property | Description |
|---|---|
me |
Cached BotInfo (available after start() / run()) |
Decorators
| Decorator | Description |
|---|---|
@bot.command(name, *, description=None) |
Register a /command handler with auto-parsed args |
@bot.on_message |
Handle non-command messages (text and/or file attachments) |
@bot.on_event(event_type) |
Handle lifecycle events (BotStarted, etc.) |
@bot.on_startup |
Run a coroutine once on startup |
Methods
| Method | Description |
|---|---|
run(*, register_commands=True) |
Start polling and dispatch (blocking) |
start(*, register_commands=True) |
Async version of run() |
get_me() |
Get bot info |
send_message(user_id, content, *, reply_to_id=None) |
Send a text message |
send_file(user_id, file, *, filename=None, content_type=..., content=None) |
Upload a file (optionally with text) |
get_updates(*, limit, offset, timeout) |
Fetch pending updates |
acknowledge_updates(update_ids) |
Mark updates as delivered |
set_commands(commands) |
Register bot commands |
poll(*, limit=20, timeout=30, auto_ack=True) |
Async generator for long-polling |
close() |
Close the HTTP client |
Context Objects
| Class | Available On | Key Properties |
|---|---|---|
CommandContext |
@bot.command |
user, message, args, bot, reply(), reply_file() |
MessageContext |
@bot.on_message |
Same as CommandContext |
EventContext |
@bot.on_event |
user, bot |
Attachment
Each file attachment on ctx.message.attachments is an Attachment instance:
| Property / Method | Description |
|---|---|
file_name |
Original filename |
content_type |
MIME type |
size_bytes |
File size in bytes |
id |
Server-assigned file ID |
download() |
Download and return raw bytes |
download_to(dest) |
Download and save to disk, returns Path |
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file polis_bot-0.3.0.tar.gz.
File metadata
- Download URL: polis_bot-0.3.0.tar.gz
- Upload date:
- Size: 27.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc5c9a57b23a8faf1de5cfa29710c8dfb2d3481966b3dc6829a9a62d13bbdccb
|
|
| MD5 |
73a70b49601233a9fb11f116d2331090
|
|
| BLAKE2b-256 |
4e2d166273f579986158832499f92a8f803543f72fe5a72b19448da4e2be8201
|
Provenance
The following attestation bundles were made for polis_bot-0.3.0.tar.gz:
Publisher:
sdk-release.yml on roman-right/Polis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polis_bot-0.3.0.tar.gz -
Subject digest:
fc5c9a57b23a8faf1de5cfa29710c8dfb2d3481966b3dc6829a9a62d13bbdccb - Sigstore transparency entry: 1011996339
- Sigstore integration time:
-
Permalink:
roman-right/Polis@05d2a16bb5aafcfd9ed58092a92913da9d5c1b30 -
Branch / Tag:
refs/tags/sdk-v0.3.0 - Owner: https://github.com/roman-right
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
sdk-release.yml@05d2a16bb5aafcfd9ed58092a92913da9d5c1b30 -
Trigger Event:
push
-
Statement type:
File details
Details for the file polis_bot-0.3.0-py3-none-any.whl.
File metadata
- Download URL: polis_bot-0.3.0-py3-none-any.whl
- Upload date:
- Size: 16.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ecb43f534f26761461089b844bde6c8e49ec47f46daf6642d9de1fd8e57f6f4
|
|
| MD5 |
b18c79390d921149f6f9e44694a3b27a
|
|
| BLAKE2b-256 |
3b15f7bdcd4351082ecdd469180dfcd1ed9e59be71e2930eb8561d0788718b03
|
Provenance
The following attestation bundles were made for polis_bot-0.3.0-py3-none-any.whl:
Publisher:
sdk-release.yml on roman-right/Polis
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
polis_bot-0.3.0-py3-none-any.whl -
Subject digest:
3ecb43f534f26761461089b844bde6c8e49ec47f46daf6642d9de1fd8e57f6f4 - Sigstore transparency entry: 1011996399
- Sigstore integration time:
-
Permalink:
roman-right/Polis@05d2a16bb5aafcfd9ed58092a92913da9d5c1b30 -
Branch / Tag:
refs/tags/sdk-v0.3.0 - Owner: https://github.com/roman-right
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
sdk-release.yml@05d2a16bb5aafcfd9ed58092a92913da9d5c1b30 -
Trigger Event:
push
-
Statement type: