I have a Django app that runs a Telegram chatbot script as a command.
I start the Django app with python manage.py runserver
.
I start the telegram client with python manage.py bot
.
I want to list the entries from the Animal table within the async method that is called when a user types "/animals" in the telegram chat. My code works if I use a hard-coded list or dictionary as a data source. However, I'm not able to get the ORM call to work in async mode.
File structure:
|Accounts---------------------
|------| models.py------------
|Main-------------------------
|------| Management-----------
|---------------| Commands----
|-----------------------bot.py
Animal model:
class Animal(models.Model):
id = models.AutoField(primary_key=True)
user = models.ForeignKey(User, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
I removed a lot from the file, leaving only the relevant bits.
bot.py
# removed unrelated imports
from asgiref.sync import sync_to_async
from accounts.models import Animal
class Command(BaseCommand):
help = "Starts the telegram bot."
# assume that the token is correct
TOKEN = "abc123"
def handle(self, *args, **options):
async def get_animals():
await Animal.objects.all()
async def animals_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
async_db_results = get_animals()
message = ""
counter = 0
for animal in async_db_results:
message += animal.name + "\n"
counter += 1
await update.message.reply_text(message)
application = Application.builder().token(TOKEN).build()
application.add_handler(CommandHandler("animals", animals_command))
application.run_polling(allowed_updates=Update.ALL_TYPES)
Error message for this code:
TypeError: 'coroutine' object is not iterable
Initially I had Animal.objects.all()
in place of async_db_results
. The ORM call is not async, so I got this error message:
django.core.exceptions.SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async.
This is a prototype app, I know I should not be using runserver
. And I should also use a webhook instead of long-polling, but I don't think these issues are related to my trouble with async.
The next thing I'm going to try is using asyncio but I have spent a lot of time already, I figured I would ask the question.
I have looked at these resources (and many others):
docs.djangoproject.com: asgiref.sync.sync_to_async
stackoverflow: Django: SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async
stackoverflow: Sync to Async Django ORM queryset foreign key property
I figured it out. Correct use of sync_to_async
and await
did it.
@sync_to_async
def get_animals():
return list(Animal.objects.all())
async def animals_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /animal is issued."""
async_db_results = await get_animals()
message = "Animal list:"
for h in async_db_results:
message += "{}\n".format(h.name)
await update.message.reply_text(message)