Synchronous Usage¶
wzgram is asynchronous. Every API method is a coroutine, and there is no synchronous mode:
calling app.send_message(...) without awaiting it returns a coroutine object and sends
nothing.
Warning
Older Pyrogram versions shipped a compatibility layer that wrapped every method so it
could be called without await — with app: instead of async with app:. wzgram
does not have it. Code relying on it fails quietly: the coroutine is created, never
scheduled, and Python warns that it was never awaited.
What to write instead is below, and none of it is more code than the sync form was.
A script that does one thing¶
asyncio.run() is the whole bridge. It starts an event loop, runs your coroutine and
shuts the loop down:
import asyncio
from pyrogram import Client
async def main():
async with Client("my_account") as app:
await app.send_message("me", "Hi!")
asyncio.run(main())
A program that stays running¶
run() does the same thing for you and then blocks, keeping the client
online until you stop it:
from pyrogram import Client, filters
app = Client("my_account")
@app.on_message(filters.private)
async def echo(client, message):
await message.reply(message.text)
app.run()
run() also accepts a coroutine, which covers the one-shot case without
writing the asyncio.run yourself:
app = Client("my_account")
async def main():
async with app:
await app.send_message("me", "Hi!")
app.run(main())
Calling from synchronous code¶
When wzgram has to live inside something that is not async — a Django view, a Flask route, a worker in a thread pool — run the client in its own loop and hand work to it:
import asyncio
import threading
from pyrogram import Client
loop = asyncio.new_event_loop()
threading.Thread(target=loop.run_forever, daemon=True).start()
app = Client("my_account")
asyncio.run_coroutine_threadsafe(app.start(), loop).result()
def send(chat_id, text):
"""Callable from ordinary synchronous code."""
future = asyncio.run_coroutine_threadsafe(
app.send_message(chat_id, text), loop
)
return future.result(timeout=30)
asyncio.run_coroutine_threadsafe() is the supported way across a thread boundary.
Do not call asyncio.run() per request — each call builds and tears down a loop, and
the client belongs to the loop it was started on.
Blocking calls inside handlers¶
A handler that blocks — a synchronous HTTP request, a heavy computation, time.sleep —
stops the event loop, and with it every other handler, the ping worker and the receive loop.
Push that work to a thread:
@app.on_message()
async def handler(client, message):
result = await asyncio.to_thread(expensive_blocking_call, message.text)
await message.reply(result)
This is the same reason wzgram runs crypto above WZGRAM_INLINE_CRYPTO_MAX in a thread
pool — see Performance and Resource Budgets.